Module Name: src Committed By: kre Date: Mon Mar 20 11:26:07 UTC 2017
Modified Files: src/bin/sh: Makefile expand.c expand.h sh.1 Added Files: src/bin/sh: arith_token.c arith_tokens.h arithmetic.c arithmetic.h Removed Files: src/bin/sh: arith.y arith_lex.l Log Message: Finish support for all required $(( )) (shell arithmetic) operators, closing PR bin/50958 That meant adding the assignment operators ('=', and all of the +=, *= ...) Currently, ++, --, and ',' are not implemented (none of those are required by posix) but support for them (most likely ',' first) might be added later. To do this, I removed the yacc/lex arithmetic parser completely, and replaced it with a hand written recursive descent parser, that I obtained from FreeBSD, who earlier had obtained it from dash (Herbert Xu). While doing the import, I cleaned up the sources (changed some file names to avoid requiring a clean build, or signifigant surgery to the obj directories if "build.sh -u" was to be used - "build.sh -u" should work fine as it is now) removed some dashisms, applied some KNF, ... To generate a diff of this commit: cvs rdiff -u -r1.103 -r1.104 src/bin/sh/Makefile cvs rdiff -u -r1.25 -r0 src/bin/sh/arith.y cvs rdiff -u -r1.18 -r0 src/bin/sh/arith_lex.l cvs rdiff -u -r0 -r1.1 src/bin/sh/arith_token.c src/bin/sh/arith_tokens.h \ src/bin/sh/arithmetic.c src/bin/sh/arithmetic.h cvs rdiff -u -r1.102 -r1.103 src/bin/sh/expand.c cvs rdiff -u -r1.19 -r1.20 src/bin/sh/expand.h cvs rdiff -u -r1.125 -r1.126 src/bin/sh/sh.1 Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/bin/sh/Makefile diff -u src/bin/sh/Makefile:1.103 src/bin/sh/Makefile:1.104 --- src/bin/sh/Makefile:1.103 Thu Mar 16 13:09:06 2017 +++ src/bin/sh/Makefile Mon Mar 20 11:26:07 2017 @@ -1,16 +1,15 @@ -# $NetBSD: Makefile,v 1.103 2017/03/16 13:09:06 kre Exp $ +# $NetBSD: Makefile,v 1.104 2017/03/20 11:26:07 kre Exp $ # @(#)Makefile 8.4 (Berkeley) 5/5/95 .include <bsd.own.mk> -YHEADER=1 PROG= sh -SHSRCS= alias.c cd.c echo.c error.c eval.c exec.c expand.c \ - histedit.c input.c jobs.c mail.c main.c memalloc.c miscbltin.c \ - mystring.c options.c parser.c redir.c show.c trap.c output.c var.c \ - test.c kill.c syntax.c -GENSRCS=arith.c arith_lex.c builtins.c init.c nodes.c -GENHDRS=arith.h builtins.h nodes.h token.h nodenames.h +SHSRCS= alias.c arith_token.c arithmetic.c cd.c echo.c error.c eval.c exec.c \ + expand.c histedit.c input.c jobs.c mail.c main.c memalloc.c \ + miscbltin.c mystring.c options.c parser.c redir.c show.c trap.c \ + output.c var.c test.c kill.c syntax.c +GENSRCS=builtins.c init.c nodes.c +GENHDRS=builtins.h nodes.h token.h nodenames.h SRCS= ${SHSRCS} ${GENSRCS} DPSRCS+=${GENHDRS} @@ -18,21 +17,11 @@ DPSRCS+=${GENHDRS} LDADD+= -ll -ledit -lterminfo DPADD+= ${LIBL} ${LIBEDIT} ${LIBTERMINFO} -LFLAGS= -8 # 8-bit lex scanner for arithmetic - # Environment for scripts executed during build. SCRIPT_ENV= \ AWK=${TOOL_AWK:Q} \ SED=${TOOL_SED:Q} -# The .depend file can get references to these temporary files -.OPTIONAL: lex.yy.c y.tab.c - -.ifdef CRUNCHEDPROG -LFLAGS+=-L -YFLAGS+=-l -.endif - CPPFLAGS+=-DSHELL -I. -I${.CURDIR} #XXX: For testing only. #CPPFLAGS+=-DDEBUG=2 @@ -53,7 +42,7 @@ SRCS+=printf.c ${NETBSDSRCDIR}/usr.bin/printf \ ${NETBSDSRCDIR}/bin/kill -CLEANFILES+= ${GENSRCS} ${GENHDRS} y.tab.h sh.html1 +CLEANFILES+= ${GENSRCS} ${GENHDRS} sh.html1 CLEANFILES+= trace.* token.h: mktokens Index: src/bin/sh/expand.c diff -u src/bin/sh/expand.c:1.102 src/bin/sh/expand.c:1.103 --- src/bin/sh/expand.c:1.102 Sun Mar 12 04:19:29 2017 +++ src/bin/sh/expand.c Mon Mar 20 11:26:07 2017 @@ -1,4 +1,4 @@ -/* $NetBSD: expand.c,v 1.102 2017/03/12 04:19:29 kre Exp $ */ +/* $NetBSD: expand.c,v 1.103 2017/03/20 11:26:07 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -37,7 +37,7 @@ #if 0 static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; #else -__RCSID("$NetBSD: expand.c,v 1.102 2017/03/12 04:19:29 kre Exp $"); +__RCSID("$NetBSD: expand.c,v 1.103 2017/03/20 11:26:07 kre Exp $"); #endif #endif /* not lint */ @@ -64,6 +64,7 @@ __RCSID("$NetBSD: expand.c,v 1.102 2017/ #include "eval.h" #include "expand.h" #include "syntax.h" +#include "arithmetic.h" #include "parser.h" #include "jobs.h" #include "options.h" @@ -356,7 +357,7 @@ removerecordregions(int endoff) void expari(int flag) { - char *p, *start; + char *p, *q, *start; intmax_t result; int adjustment; int begoff; @@ -375,8 +376,23 @@ expari(int flag) * have to rescan starting from the beginning since CTLESC * characters have to be processed left to right. */ -/* SPACE_NEEDED is enough for all digits, plus possible "-", plus 2 (why?) */ -#define SPACE_NEEDED ((sizeof(intmax_t) * CHAR_BIT + 2) / 3 + 1 + 2) + + /* + * SPACE_NEEDED is enough for all possible digits (rounded up) + * plus possible "-", and the terminating '\0', hence, plus 2 + * + * The calculation produces the number of bytes needed to + * represent the biggest possible value, in octal. We only + * generate decimal, which takes (often) less digits (never more) + * so this is safe, if occasionally slightly wasteful. + */ +#define SPACE_NEEDED ((sizeof(intmax_t) * CHAR_BIT + 2) / 3 + 2) + /* + * Only need check for SPACE_NEEDED-2 as we have already + * consumed (but will overwrite) 2 chars, the CTLARI, and the + * flags (quoting) byte (if those had not already been seen, + * we would not be here.) + */ CHECKSTRSPACE((int)(SPACE_NEEDED - 2), expdest); USTPUTC('\0', expdest); start = stackblock(); @@ -398,7 +414,9 @@ expari(int flag) removerecordregions(begoff); if (quotes) rmescapes(p+2); + q = grabstackstr(expdest); result = arith(p+2); + ungrabstackstr(q, expdest); fmtstr(p, SPACE_NEEDED, "%"PRIdMAX, result); while (*p++) Index: src/bin/sh/expand.h diff -u src/bin/sh/expand.h:1.19 src/bin/sh/expand.h:1.20 --- src/bin/sh/expand.h:1.19 Sat Dec 22 20:15:22 2012 +++ src/bin/sh/expand.h Mon Mar 20 11:26:07 2017 @@ -1,4 +1,4 @@ -/* $NetBSD: expand.h,v 1.19 2012/12/22 20:15:22 dsl Exp $ */ +/* $NetBSD: expand.h,v 1.20 2017/03/20 11:26:07 kre Exp $ */ /*- * Copyright (c) 1991, 1993 @@ -66,8 +66,3 @@ void expari(int); int patmatch(char *, char *, int); void rmescapes(char *); int casematch(union node *, char *); - -/* From arith.y */ -intmax_t arith(const char *); -void arith_lex_reset(void); -int yylex(void); Index: src/bin/sh/sh.1 diff -u src/bin/sh/sh.1:1.125 src/bin/sh/sh.1:1.126 --- src/bin/sh/sh.1:1.125 Sat Feb 4 23:35:15 2017 +++ src/bin/sh/sh.1 Mon Mar 20 11:26:07 2017 @@ -1,4 +1,4 @@ -.\" $NetBSD: sh.1,v 1.125 2017/02/04 23:35:15 wiz Exp $ +.\" $NetBSD: sh.1,v 1.126 2017/03/20 11:26:07 kre Exp $ .\" Copyright (c) 1991, 1993 .\" The Regents of the University of California. All rights reserved. .\" @@ -1255,6 +1255,39 @@ Shell variables may be referenced by nam expression, without needing a .Dq \&$ sign. +Variables that are not set, or which have an empty (null string) value, +used this way evaluate as zero (that is, +.Dq x +in arithmetic, as an R-Value, is evaluated as +.Dq ${x:-0} ) +unless the +.Nm +.Fl u +flag is set, in which case a reference to an unset variable is an error. +Note that unset variables used in the ${var} form expand to a null +string, which might result in syntax errors. +Referencing the value of a variable which is not numeric is an error. +.Pp +All of the C expression operators applicable to integers are supported, +and operate as they would in a C expression, except the unary +.Dq ++ +and +.Dq -- +operators (in both prefix and postfix forms) and the +.Dq \&, +(comma) operator, which are currently not supported. +.PP +It should not be necessary to state that the C operators which +operate on, or produce, pointer types, are not supported. +Those include unary +.Dq \&* +and +.Dq \&& +and the struct and array referencing binary operators: +.Dq \&. , +.Dq \&-> +and +.Dq \&[ . .Ss White Space Splitting (Field Splitting) After parameter expansion, command substitution, and arithmetic expansion the shell scans the results of Added files: Index: src/bin/sh/arith_token.c diff -u /dev/null src/bin/sh/arith_token.c:1.1 --- /dev/null Mon Mar 20 11:26:07 2017 +++ src/bin/sh/arith_token.c Mon Mar 20 11:26:07 2017 @@ -0,0 +1,232 @@ +/* $NetBSD: arith_token.c,v 1.1 2017/03/20 11:26:07 kre Exp $ */ + +/*- + * Copyright (c) 2002 + * Herbert Xu. + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * From FreeBSD, from dash + */ + +#include <sys/cdefs.h> + +#ifndef lint +__RCSID("$NetBSD: arith_token.c,v 1.1 2017/03/20 11:26:07 kre Exp $"); +#endif /* not lint */ + +#include <inttypes.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#include "shell.h" +#include "arith_tokens.h" +#include "expand.h" +#include "error.h" +#include "memalloc.h" +#include "parser.h" +#include "syntax.h" + +#if ARITH_BOR + ARITH_ASS_GAP != ARITH_BORASS || \ + ARITH_ASS + ARITH_ASS_GAP != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +/* + * Scan next arithmetic token, return its type, + * leave its value (when applicable) in (global) a_t_val. + * + * Input text is in (global) arith_buf which is updated to + * refer to the next char after the token returned, except + * on error (ARITH_BAD returned) where arith_buf is not altered. + */ +int +arith_token(void) +{ + int token; + const char *buf = arith_buf; + char *end; + const char *p; + + for (;;) { + token = *buf; + + switch (token) { + case ' ': + case '\t': + case '\n': + buf++; + continue; + + default: + error("arithmetic: unexpected '%c' (%#x) in expression", + token, token); + /* NOTREACHED */ + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + a_t_val.val = strtoimax(buf, &end, 0); + arith_buf = end; + return ARITH_NUM; + + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': + case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': + case '_': + p = buf; + while (buf++, is_in_name(*buf)) + ; + a_t_val.name = stalloc(buf - p + 1); + memcpy(a_t_val.name, p, buf - p); + a_t_val.name[buf - p] = '\0'; + arith_buf = buf; + return ARITH_VAR; + + case '=': + token = ARITH_ASS; + checkeq: + buf++; + checkeqcur: + if (*buf != '=') + goto out; + token += ARITH_ASS_GAP; + break; + + case '>': + switch (*++buf) { + case '=': + token = ARITH_GE; + break; + case '>': + token = ARITH_RSHIFT; + goto checkeq; + default: + token = ARITH_GT; + goto out; + } + break; + + case '<': + switch (*++buf) { + case '=': + token = ARITH_LE; + break; + case '<': + token = ARITH_LSHIFT; + goto checkeq; + default: + token = ARITH_LT; + goto out; + } + break; + + case '|': + if (*++buf != '|') { + token = ARITH_BOR; + goto checkeqcur; + } + token = ARITH_OR; + break; + + case '&': + if (*++buf != '&') { + token = ARITH_BAND; + goto checkeqcur; + } + token = ARITH_AND; + break; + + case '!': + if (*++buf != '=') { + token = ARITH_NOT; + goto out; + } + token = ARITH_NE; + break; + + case 0: + goto out; + + case '(': + token = ARITH_LPAREN; + break; + case ')': + token = ARITH_RPAREN; + break; + + case '*': + token = ARITH_MUL; + goto checkeq; + case '/': + token = ARITH_DIV; + goto checkeq; + case '%': + token = ARITH_REM; + goto checkeq; + + case '+': + if (buf[1] == '+') + error("arithmetic: ++ operator unsupported"); + token = ARITH_ADD; + goto checkeq; + case '-': + if (buf[1] == '-') + error("arithmetic: -- operator unsupported"); + token = ARITH_SUB; + goto checkeq; + case '~': + token = ARITH_BNOT; + break; + case '^': + token = ARITH_BXOR; + goto checkeq; + + case '?': + token = ARITH_QMARK; + break; + case ':': + token = ARITH_COLON; + break; + } + break; + } + buf++; + out: + arith_buf = buf; + return token; +} Index: src/bin/sh/arith_tokens.h diff -u /dev/null src/bin/sh/arith_tokens.h:1.1 --- /dev/null Mon Mar 20 11:26:07 2017 +++ src/bin/sh/arith_tokens.h Mon Mar 20 11:26:07 2017 @@ -0,0 +1,115 @@ +/* $NetBSD: arith_tokens.h,v 1.1 2017/03/20 11:26:07 kre Exp $ */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2007 + * Herbert Xu <herb...@gondor.apana.org.au>. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * From FreeBSD who obtained it from dash (modified both times.) + */ + +/* + * Tokens returned from arith_token() + * + * Caution, values are not arbitrary. + */ + +#define ARITH_BAD 0 + +#define ARITH_ASS 1 + +#define ARITH_OR 2 +#define ARITH_AND 3 +#define ARITH_NUM 5 +#define ARITH_VAR 6 +#define ARITH_NOT 7 + +#define ARITH_BINOP_MIN 8 + +#define ARITH_LE 8 +#define ARITH_GE 9 +#define ARITH_LT 10 +#define ARITH_GT 11 +#define ARITH_EQ 12 /* Must be ARITH_ASS + ARITH_ASS_GAP */ + +#define ARITH_ASS_BASE 13 + +#define ARITH_REM 13 +#define ARITH_BAND 14 +#define ARITH_LSHIFT 15 +#define ARITH_RSHIFT 16 +#define ARITH_MUL 17 +#define ARITH_ADD 18 +#define ARITH_BOR 19 +#define ARITH_SUB 20 +#define ARITH_BXOR 21 +#define ARITH_DIV 22 + +#define ARITH_NE 23 + +#define ARITH_BINOP_MAX 24 + +#define ARITH_ASS_MIN ARITH_BINOP_MAX +#define ARITH_ASS_GAP (ARITH_ASS_MIN - ARITH_ASS_BASE) + +#define ARITH_REMASS (ARITH_ASS_GAP + ARITH_REM) +#define ARITH_BANDASS (ARITH_ASS_GAP + ARITH_BAND) +#define ARITH_LSHIFTASS (ARITH_ASS_GAP + ARITH_LSHIFT) +#define ARITH_RSHIFTASS (ARITH_ASS_GAP + ARITH_RSHIFT) +#define ARITH_MULASS (ARITH_ASS_GAP + ARITH_MUL) +#define ARITH_ADDASS (ARITH_ASS_GAP + ARITH_ADD) +#define ARITH_BORASS (ARITH_ASS_GAP + ARITH_BOR) +#define ARITH_SUBASS (ARITH_ASS_GAP + ARITH_SUB) +#define ARITH_BXORASS (ARITH_ASS_GAP + ARITH_BXOR) +#define ARITH_DIVASS (ARITH_ASS_GAP + ARITH_BXOR) + +#define ARITH_ASS_MAX 34 + +#define ARITH_LPAREN 34 +#define ARITH_RPAREN 35 +#define ARITH_BNOT 36 +#define ARITH_QMARK 37 +#define ARITH_COLON 38 + +/* + * Globals shared between arith parser, and lexer + */ + +extern const char *arith_buf; + +union a_token_val { + intmax_t val; + char *name; +}; + +extern union a_token_val a_t_val; + +int arith_token(void); Index: src/bin/sh/arithmetic.c diff -u /dev/null src/bin/sh/arithmetic.c:1.1 --- /dev/null Mon Mar 20 11:26:07 2017 +++ src/bin/sh/arithmetic.c Mon Mar 20 11:26:07 2017 @@ -0,0 +1,406 @@ +/* $NetBSD: arithmetic.c,v 1.1 2017/03/20 11:26:07 kre Exp $ */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2007 + * Herbert Xu <herb...@gondor.apana.org.au>. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * From FreeBSD, who obtained it from dash, modified both times... + */ + +#include <sys/cdefs.h> + +#ifndef lint +__RCSID("$NetBSD: arithmetic.c,v 1.1 2017/03/20 11:26:07 kre Exp $"); +#endif /* not lint */ + +#include <limits.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> + +#include "shell.h" +#include "arithmetic.h" +#include "arith_tokens.h" +#include "expand.h" +#include "error.h" +#include "memalloc.h" +#include "output.h" +#include "options.h" +#include "var.h" + +#if ARITH_BOR + ARITH_ASS_GAP != ARITH_BORASS || \ + ARITH_ASS + ARITH_ASS_GAP != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +static const char *arith_startbuf; + +const char *arith_buf; +union a_token_val a_t_val; + +static int last_token; + +#define ARITH_PRECEDENCE(op, prec) [op - ARITH_BINOP_MIN] = prec + +static const char prec[ARITH_BINOP_MAX - ARITH_BINOP_MIN] = { + ARITH_PRECEDENCE(ARITH_MUL, 0), + ARITH_PRECEDENCE(ARITH_DIV, 0), + ARITH_PRECEDENCE(ARITH_REM, 0), + ARITH_PRECEDENCE(ARITH_ADD, 1), + ARITH_PRECEDENCE(ARITH_SUB, 1), + ARITH_PRECEDENCE(ARITH_LSHIFT, 2), + ARITH_PRECEDENCE(ARITH_RSHIFT, 2), + ARITH_PRECEDENCE(ARITH_LT, 3), + ARITH_PRECEDENCE(ARITH_LE, 3), + ARITH_PRECEDENCE(ARITH_GT, 3), + ARITH_PRECEDENCE(ARITH_GE, 3), + ARITH_PRECEDENCE(ARITH_EQ, 4), + ARITH_PRECEDENCE(ARITH_NE, 4), + ARITH_PRECEDENCE(ARITH_BAND, 5), + ARITH_PRECEDENCE(ARITH_BXOR, 6), + ARITH_PRECEDENCE(ARITH_BOR, 7), +}; + +#define ARITH_MAX_PREC 8 + +int expcmd(int, char **); + +static void __dead +arith_err(const char *s) +{ + error("arithmetic expression: %s: \"%s\"", s, arith_startbuf); + /* NOTREACHED */ +} + +static intmax_t +arith_lookupvarint(char *varname) +{ + const char *str; + char *p; + intmax_t result; + + str = lookupvar(varname); + if (uflag && str == NULL) + arith_err("variable not set"); + if (str == NULL || *str == '\0') + str = "0"; + errno = 0; + result = strtoimax(str, &p, 0); + if (errno != 0 || *p != '\0') + arith_err("variable contains non-numeric value"); + return result; +} + +static inline int +arith_prec(int op) +{ + + return prec[op - ARITH_BINOP_MIN]; +} + +static inline int +higher_prec(int op1, int op2) +{ + + return arith_prec(op1) < arith_prec(op2); +} + +static intmax_t +do_binop(int op, intmax_t a, intmax_t b) +{ + + switch (op) { + default: + arith_err("token error"); + case ARITH_REM: + case ARITH_DIV: + if (b == 0) + arith_err("division by zero"); + if (a == INTMAX_MIN && b == -1) + arith_err("divide error"); + return op == ARITH_REM ? a % b : a / b; + case ARITH_MUL: + return (uintmax_t)a * (uintmax_t)b; + case ARITH_ADD: + return (uintmax_t)a + (uintmax_t)b; + case ARITH_SUB: + return (uintmax_t)a - (uintmax_t)b; + case ARITH_LSHIFT: + return (uintmax_t)a << (b & (sizeof(uintmax_t) * CHAR_BIT - 1)); + case ARITH_RSHIFT: + return a >> (b & (sizeof(uintmax_t) * CHAR_BIT - 1)); + case ARITH_LT: + return a < b; + case ARITH_LE: + return a <= b; + case ARITH_GT: + return a > b; + case ARITH_GE: + return a >= b; + case ARITH_EQ: + return a == b; + case ARITH_NE: + return a != b; + case ARITH_BAND: + return a & b; + case ARITH_BXOR: + return a ^ b; + case ARITH_BOR: + return a | b; + } +} + +static intmax_t assignment(int var, int noeval); + +static intmax_t +primary(int token, union a_token_val *val, int op, int noeval) +{ + intmax_t result; + + switch (token) { + case ARITH_LPAREN: + result = assignment(op, noeval); + if (last_token != ARITH_RPAREN) + arith_err("expecting ')'"); + last_token = arith_token(); + return result; + case ARITH_NUM: + last_token = op; + return val->val; + case ARITH_VAR: + last_token = op; + return noeval ? val->val : arith_lookupvarint(val->name); + case ARITH_ADD: + *val = a_t_val; + return primary(op, val, arith_token(), noeval); + case ARITH_SUB: + *val = a_t_val; + return -primary(op, val, arith_token(), noeval); + case ARITH_NOT: + *val = a_t_val; + return !primary(op, val, arith_token(), noeval); + case ARITH_BNOT: + *val = a_t_val; + return ~primary(op, val, arith_token(), noeval); + default: + arith_err("expecting primary"); + } + return 0; /* never reached */ +} + +static intmax_t +binop2(intmax_t a, int op, int precedence, int noeval) +{ + union a_token_val val; + intmax_t b; + int op2; + int token; + + for (;;) { + token = arith_token(); + val = a_t_val; + + b = primary(token, &val, arith_token(), noeval); + + op2 = last_token; + if (op2 >= ARITH_BINOP_MIN && op2 < ARITH_BINOP_MAX && + higher_prec(op2, op)) { + b = binop2(b, op2, arith_prec(op), noeval); + op2 = last_token; + } + + a = noeval ? b : do_binop(op, a, b); + + if (op2 < ARITH_BINOP_MIN || op2 >= ARITH_BINOP_MAX || + arith_prec(op2) >= precedence) + return a; + + op = op2; + } +} + +static intmax_t +binop(int token, union a_token_val *val, int op, int noeval) +{ + intmax_t a = primary(token, val, op, noeval); + + op = last_token; + if (op < ARITH_BINOP_MIN || op >= ARITH_BINOP_MAX) + return a; + + return binop2(a, op, ARITH_MAX_PREC, noeval); +} + +static intmax_t +and(int token, union a_token_val *val, int op, int noeval) +{ + intmax_t a = binop(token, val, op, noeval); + intmax_t b; + + op = last_token; + if (op != ARITH_AND) + return a; + + token = arith_token(); + *val = a_t_val; + + b = and(token, val, arith_token(), noeval | !a); + + return a && b; +} + +static intmax_t +or(int token, union a_token_val *val, int op, int noeval) +{ + intmax_t a = and(token, val, op, noeval); + intmax_t b; + + op = last_token; + if (op != ARITH_OR) + return a; + + token = arith_token(); + *val = a_t_val; + + b = or(token, val, arith_token(), noeval | !!a); + + return a || b; +} + +static intmax_t +cond(int token, union a_token_val *val, int op, int noeval) +{ + intmax_t a = or(token, val, op, noeval); + intmax_t b; + intmax_t c; + + if (last_token != ARITH_QMARK) + return a; + + b = assignment(arith_token(), noeval | !a); + + if (last_token != ARITH_COLON) + arith_err("expecting ':'"); + + token = arith_token(); + *val = a_t_val; + + c = cond(token, val, arith_token(), noeval | !!a); + + return a ? b : c; +} + +static intmax_t +assignment(int var, int noeval) +{ + union a_token_val val = a_t_val; + int op = arith_token(); + intmax_t result; + char sresult[DIGITS(result) + 1]; + + + if (var != ARITH_VAR) + return cond(var, &val, op, noeval); + + if (op != ARITH_ASS && (op < ARITH_ASS_MIN || op >= ARITH_ASS_MAX)) + return cond(var, &val, op, noeval); + + result = assignment(arith_token(), noeval); + if (noeval) + return result; + + if (op != ARITH_ASS) + result = do_binop(op - ARITH_ASS_GAP, + arith_lookupvarint(val.name), result); + snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR, result); + setvar(val.name, sresult, 0); + return result; +} + +intmax_t +arith(const char *s) +{ + struct stackmark smark; + intmax_t result; + + setstackmark(&smark); + + arith_buf = arith_startbuf = s; + + result = assignment(arith_token(), 0); + + if (last_token) + arith_err("expecting end of expression"); + + popstackmark(&smark); + + return result; +} + +/* + * The let(1)/exp(1) builtin. + */ +int +expcmd(int argc, char **argv) +{ + const char *p; + char *concat; + char **ap; + intmax_t i; + + if (argc > 1) { + p = argv[1]; + if (argc > 2) { + /* + * Concatenate arguments. + */ + STARTSTACKSTR(concat); + ap = argv + 2; + for (;;) { + while (*p) + STPUTC(*p++, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + } else + p = ""; + + i = arith(p); + + out1fmt(ARITH_FORMAT_STR "\n", i); + return !i; +} Index: src/bin/sh/arithmetic.h diff -u /dev/null src/bin/sh/arithmetic.h:1.1 --- /dev/null Mon Mar 20 11:26:07 2017 +++ src/bin/sh/arithmetic.h Mon Mar 20 11:26:07 2017 @@ -0,0 +1,42 @@ +/* $NetBSD: arithmetic.h,v 1.1 2017/03/20 11:26:07 kre Exp $ */ + +/*- + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * 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. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * @(#)arith.h 1.1 (Berkeley) 5/4/95 + * + * From FreeBSD, from dash + */ + +#include "shell.h" + +#define DIGITS(var) (3 + (2 + CHAR_BIT * sizeof((var))) / 3) + +#define ARITH_FORMAT_STR "%" PRIdMAX + +intmax_t arith(const char *);