summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorHerbert Xu <herbert@gondor.apana.org.au>2007-10-11 22:36:28 +0800
committerHerbert Xu <herbert@gondor.apana.org.au>2007-10-11 22:36:28 +0800
commitf6e3b2f8a59922405f42c8bc283e0f5546c25d0e (patch)
tree6f8a59c3b8f836292fda23c77b1c95eefeae9cc4 /src
parent[PARSER] Report substition errors at expansion time (diff)
downloaddash-f6e3b2f8a59922405f42c8bc283e0f5546c25d0e.tar.gz
dash-f6e3b2f8a59922405f42c8bc283e0f5546c25d0e.zip
[ARITH] Add assignment and intmax_t support
This patch adds assignment operator support in arithmetic expansions.  It
also changes the type used to intmax_t.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am8
-rw-r--r--src/arith.y155
-rw-r--r--src/arith_yacc.c298
-rw-r--r--src/arith_yacc.h (renamed from src/arith_lex.l)100
-rw-r--r--src/arith_yylex.c114
-rw-r--r--src/expand.c23
-rw-r--r--src/expand.h4
-rw-r--r--src/mystring.c9
-rw-r--r--src/mystring.h1
-rw-r--r--src/shell.h10
-rw-r--r--src/var.c20
-rw-r--r--src/var.h4
12 files changed, 509 insertions, 237 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 37d6d3c..49026a3 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -18,12 +18,12 @@ COMPILE_FOR_BUILD = \
 bin_PROGRAMS = dash
 
 dash_CFILES = \
-	alias.c arith_yylex.c cd.c error.c eval.c exec.c expand.c \
+	alias.c arith_yacc.c arith_yylex.c cd.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 \
 	bltin/printf.c system.c bltin/test.c bltin/times.c var.c
 dash_SOURCES = \
-	$(dash_CFILES) arith.y \
+	$(dash_CFILES) \
 	alias.h bltin/bltin.h cd.h error.h eval.h exec.h expand.h hetio.h \
 	init.h input.h jobs.h machdep.h mail.h main.h memalloc.h miscbltin.h \
 	myhistedit.h mystring.h options.h output.h parser.h redir.h shell.h \
@@ -32,10 +32,10 @@ dash_LDADD = builtins.o init.o nodes.o signames.o syntax.o
 
 HELPERS = mkinit mksyntax mknodes mksignames
 
-BUILT_SOURCES = arith.h builtins.h nodes.h syntax.h token.h
+BUILT_SOURCES = builtins.h nodes.h syntax.h token.h
 CLEANFILES = \
 	$(BUILT_SOURCES) $(patsubst %.o,%.c,$(dash_LDADD)) \
-	arith.c $(HELPERS) builtins.def
+	$(HELPERS) builtins.def
 
 man_MANS = dash.1
 EXTRA_DIST = \
diff --git a/src/arith.y b/src/arith.y
deleted file mode 100644
index 07b0b39..0000000
--- a/src/arith.y
+++ /dev/null
@@ -1,155 +0,0 @@
-%{
-/*-
- * Copyright (c) 1993
- *	The Regents of the University of California.  All rights reserved.
- * Copyright (c) 1997-2005
- *	Herbert Xu <herbert@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.
- */
-
-#include <stdlib.h>
-#include "expand.h"
-#include "shell.h"
-#include "error.h"
-#include "output.h"
-#include "memalloc.h"
-
-const char *arith_buf, *arith_startbuf;
-
-#ifndef YYBISON
-int yyparse(void);
-#endif
-void yyerror(const char *);
-#ifdef TESTARITH
-int main(int , char *[]);
-int sh_error(char *);
-#endif
-
-%}
-%token ARITH_NUM ARITH_LPAREN ARITH_RPAREN
-
-%left ARITH_OR
-%left ARITH_AND
-%left ARITH_BOR
-%left ARITH_BXOR
-%left ARITH_BAND
-%left ARITH_EQ ARITH_NE
-%left ARITH_LT ARITH_GT ARITH_GE ARITH_LE
-%left ARITH_LSHIFT ARITH_RSHIFT
-%left ARITH_ADD ARITH_SUB
-%left ARITH_MUL ARITH_DIV ARITH_REM
-%left ARITH_UNARYMINUS ARITH_UNARYPLUS ARITH_NOT ARITH_BNOT
-%%
-
-exp:	expr {
-			return ($1);
-	}
-	;
-
-
-expr:	ARITH_LPAREN expr ARITH_RPAREN { $$ = $2; }
-	| expr ARITH_OR expr	{ $$ = $1 || $3; }
-	| expr ARITH_AND expr	{ $$ = $1 && $3; }
-	| expr ARITH_BOR expr	{ $$ = $1 | $3; }
-	| expr ARITH_BXOR expr	{ $$ = $1 ^ $3; }
-	| expr ARITH_BAND expr	{ $$ = $1 & $3; }
-	| expr ARITH_EQ expr	{ $$ = $1 == $3; }
-	| expr ARITH_GT expr	{ $$ = $1 > $3; }
-	| expr ARITH_GE expr	{ $$ = $1 >= $3; }
-	| expr ARITH_LT expr	{ $$ = $1 < $3; }
-	| expr ARITH_LE expr	{ $$ = $1 <= $3; }
-	| expr ARITH_NE expr	{ $$ = $1 != $3; }
-	| expr ARITH_LSHIFT expr { $$ = $1 << $3; }
-	| expr ARITH_RSHIFT expr { $$ = $1 >> $3; }
-	| expr ARITH_ADD expr	{ $$ = $1 + $3; }
-	| expr ARITH_SUB expr	{ $$ = $1 - $3; }
-	| expr ARITH_MUL expr	{ $$ = $1 * $3; }
-	| expr ARITH_DIV expr	{
-			if ($3 == 0)
-				yyerror("division by zero");
-			$$ = $1 / $3;
-		}
-	| expr ARITH_REM expr   {
-			if ($3 == 0)
-				yyerror("division by zero");
-			$$ = $1 % $3;
-		}
-	| ARITH_NOT expr	{ $$ = !($2); }
-	| ARITH_BNOT expr	{ $$ = ~($2); }
-	| ARITH_SUB expr %prec ARITH_UNARYMINUS { $$ = -($2); }
-	| ARITH_ADD expr %prec ARITH_UNARYPLUS { $$ = $2; }
-	| ARITH_NUM
-	;
-%%
-int
-arith(s)
-	const char *s;
-{
-	long result;
-
-	arith_buf = arith_startbuf = s;
-
-	INTOFF;
-	result = yyparse();
-	arith_lex_reset();	/* reprime lex */
-	INTON;
-
-	return (result);
-}
-
-
-/*************************/
-#ifdef TEST_ARITH
-#include <stdio.h>
-main(argc, argv)
-	char *argv[];
-{
-	printf("%d\n", exp(argv[1]));
-}
-sh_error(s)
-	char *s;
-{
-	fprintf(stderr, "exp: %s\n", s);
-	exit(1);
-}
-#endif
-
-void
-yyerror(s)
-	const char *s;
-{
-
-#ifndef YYBISON
-	yyerrok;
-#endif
-	yyclearin;
-	arith_lex_reset();	/* reprime lex */
-	sh_error("arithmetic expression: %s: \"%s\"", s, arith_startbuf);
-	/* NOTREACHED */
-}
diff --git a/src/arith_yacc.c b/src/arith_yacc.c
new file mode 100644
index 0000000..ad653ed
--- /dev/null
+++ b/src/arith_yacc.c
@@ -0,0 +1,298 @@
+/*-
+ * Copyright (c) 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 2007
+ *	Herbert Xu <herbert@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.
+ */
+
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include "arith_yacc.h"
+#include "expand.h"
+#include "shell.h"
+#include "error.h"
+#include "output.h"
+#include "var.h"
+
+#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ
+#error Arithmetic tokens are out of order.
+#endif
+
+static const char *arith_startbuf;
+
+const char *arith_buf;
+union yystype yylval;
+
+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),
+};
+
+static void yyerror(const char *s) __attribute__ ((noreturn));
+static void yyerror(const char *s)
+{
+	sh_error("arithmetic expression: %s: \"%s\"", s, arith_startbuf);
+	/* NOTREACHED */
+}
+
+static inline int higher_prec(int op1, int op2)
+{
+	return prec[op1 - ARITH_BINOP_MIN] < prec[op2 - ARITH_BINOP_MIN];
+}
+
+static intmax_t do_binop(int op, intmax_t a, intmax_t b)
+{
+	imaxdiv_t div;
+
+	switch (op) {
+	default:
+	case ARITH_REM:
+	case ARITH_DIV:
+		if (!b)
+			yyerror("division by zero");
+		div = imaxdiv(a, b);
+		return op == ARITH_REM ? div.rem : div.quot;
+	case ARITH_MUL:
+		return a * b;
+	case ARITH_ADD:
+		return a + b;
+	case ARITH_SUB:
+		return a - b;
+	case ARITH_LSHIFT:
+		return a << b;
+	case ARITH_RSHIFT:
+		return a >> b;
+	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 yystype *val, int op, int noeval)
+{
+	intmax_t result;
+
+again:
+	switch (token) {
+	case ARITH_LPAREN:
+		result = assignment(op, noeval);
+		if (last_token != ARITH_RPAREN)
+			yyerror("expecting ')'");
+		last_token = yylex();
+		return result;
+	case ARITH_NUM:
+		last_token = op;
+		return val->val;
+	case ARITH_VAR:
+		last_token = op;
+		return noeval ? val->val : lookupvarint(val->name);
+	case ARITH_ADD:
+		token = op;
+		*val = yylval;
+		op = yylex();
+		goto again;
+	case ARITH_SUB:
+		*val = yylval;
+		return -primary(op, val, yylex(), noeval);
+	case ARITH_NOT:
+		*val = yylval;
+		return !primary(op, val, yylex(), noeval);
+	case ARITH_BNOT:
+		*val = yylval;
+		return ~primary(op, val, yylex(), noeval);
+	default:
+		yyerror("expecting primary");
+	}
+}
+
+static intmax_t binop2(intmax_t a, int op, int noeval)
+{
+	for (;;) {
+		union yystype val;
+		intmax_t b;
+		int op2;
+		int token;
+
+		token = yylex();
+		val = yylval;
+
+		b = primary(token, &val, yylex(), noeval);
+
+		op2 = last_token;
+		if (op2 < ARITH_BINOP_MIN || op2 >= ARITH_BINOP_MAX)
+			return noeval ? b : do_binop(op, a, b);
+
+		if (higher_prec(op2, op)) {
+			b = binop2(b, op2, noeval);
+			return noeval ? b : do_binop(op, a, b);
+		}
+
+		a = do_binop(op, a, b);
+		op = op2;
+	}
+}
+
+static intmax_t binop(int token, union yystype *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, noeval);
+}
+
+static intmax_t and(int token, union yystype *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 = yylex();
+	*val = yylval;
+
+	b = and(token, val, yylex(), noeval | !a);
+
+	return a && b;
+}
+
+static intmax_t or(int token, union yystype *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 = yylex();
+	*val = yylval;
+
+	b = or(token, val, yylex(), noeval | !!a);
+
+	return a | b;
+}
+
+static intmax_t cond(int token, union yystype *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(yylex(), noeval | !a);
+
+	if (last_token != ARITH_COLON)
+		yyerror("expecting ':'");
+
+	token = yylex();
+	*val = yylval;
+
+	c = cond(token, val, yylex(), noeval | !!a);
+
+	return a ? b : c;
+}
+
+static intmax_t assignment(int var, int noeval)
+{
+	union yystype val = yylval;
+	int op = yylex();
+	intmax_t result;
+
+	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(yylex(), noeval);
+	if (noeval)
+		return result;
+
+	return setvarint(val.name,
+			 op == ARITH_ASS ? result :
+			 do_binop(op - 11, lookupvarint(val.name), result));
+}
+
+intmax_t arith(const char *s)
+{
+	intmax_t result;
+
+	arith_buf = arith_startbuf = s;
+
+	result = assignment(yylex(), 0);
+
+	if (last_token)
+		yyerror("expecting EOF");
+
+	return result;
+}
diff --git a/src/arith_lex.l b/src/arith_yacc.h
index 85e170c..ff34d52 100644
--- a/src/arith_lex.l
+++ b/src/arith_yacc.h
@@ -1,8 +1,7 @@
-%{
 /*-
  * Copyright (c) 1993
  *	The Regents of the University of California.  All rights reserved.
- * Copyright (c) 1997-2005
+ * Copyright (c) 2007
  *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
  *
  * This code is derived from software contributed to Berkeley by
@@ -33,51 +32,58 @@
  * SUCH DAMAGE.
  */
 
-#include <stdlib.h>
-#include <unistd.h>
-#include "arith.h"
-#include "error.h"
-#include "expand.h"
+#define ARITH_ASS 1
 
-extern int yylval;
-extern const char *arith_buf, *arith_startbuf;
-#undef YY_INPUT
-#define YY_INPUT(buf,result,max) \
-	result = (*buf = *arith_buf++) ? 1 : YY_NULL;
-#define YY_NO_UNPUT
-%}
+#define ARITH_OR 2
+#define ARITH_AND 3
+#define ARITH_BAD 4
+#define ARITH_NUM 5
+#define ARITH_VAR 6
+#define ARITH_NOT 7
 
-%%
-[ \t\n]	{ ; }
-[0-9]+	{ yylval = strtoll(yytext, 0, 0); return(ARITH_NUM); }
-"("	{ return(ARITH_LPAREN); }
-")"	{ return(ARITH_RPAREN); }
-"||"	{ return(ARITH_OR); }
-"&&"	{ return(ARITH_AND); }
-"|"	{ return(ARITH_BOR); }
-"^"	{ return(ARITH_BXOR); }
-"&"	{ return(ARITH_BAND); }
-"=="	{ return(ARITH_EQ); }
-"!="	{ return(ARITH_NE); }
-">"	{ return(ARITH_GT); }
-">="	{ return(ARITH_GE); }
-"<"	{ return(ARITH_LT); }
-"<="	{ return(ARITH_LE); }
-"<<"	{ return(ARITH_LSHIFT); }
-">>"	{ return(ARITH_RSHIFT); }
-"*"	{ return(ARITH_MUL); }
-"/"	{ return(ARITH_DIV); }
-"%"	{ return(ARITH_REM); }
-"+"	{ return(ARITH_ADD); }
-"-"	{ return(ARITH_SUB); }
-"~"	{ return(ARITH_BNOT); }
-"!"	{ return(ARITH_NOT); }
-.	{ error("arith: syntax error: \"%s\"", arith_startbuf); }
-%%
+#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
+#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
 
-void
-arith_lex_reset() {
-#ifdef YY_NEW_FILE
-	YY_NEW_FILE;
-#endif
-}
+#define ARITH_ASS_MIN 24
+#define ARITH_REMASS 24
+#define ARITH_BANDASS 25
+#define ARITH_LSHIFTASS 26
+#define ARITH_RSHIFTASS 27
+#define ARITH_MULASS 28
+#define ARITH_ADDASS 29
+#define ARITH_BORASS 30
+#define ARITH_SUBASS 31
+#define ARITH_BXORASS 32
+#define ARITH_DIVASS 33
+#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
+
+union yystype {
+	intmax_t val;
+	char *name;
+};
+
+extern union yystype yylval;
+
+int yylex(void);
diff --git a/src/arith_yylex.c b/src/arith_yylex.c
index 4fa2051..0f46990 100644
--- a/src/arith_yylex.c
+++ b/src/arith_yylex.c
@@ -32,19 +32,28 @@
  * SUCH DAMAGE.
  */
 
+#include <inttypes.h>
 #include <stdlib.h>
-#include "arith.h"
+#include <string.h>
+#include "arith_yacc.h"
 #include "expand.h"
 #include "error.h"
+#include "shell.h"
+#include "memalloc.h"
+#include "syntax.h"
 
-extern int yylval;
-extern const char *arith_buf, *arith_startbuf;
+#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ
+#error Arithmetic tokens are out of order.
+#endif
+
+extern const char *arith_buf;
 
 int
 yylex()
 {
 	int value;
 	const char *buf = arith_buf;
+	const char *p;
 
 	for (;;) {
 		switch (*buf) {
@@ -54,9 +63,7 @@ yylex()
 			buf++;
 			continue;
 		default:
-err:
-			sh_error("arith: syntax error: \"%s\"", arith_startbuf);
-			/* NOTREACHED */
+			return ARITH_BAD;
 		case '0':
 		case '1':
 		case '2':
@@ -67,13 +74,74 @@ err:
 		case '7':
 		case '8':
 		case '9':
-			yylval = strtoll(buf, (char **) &arith_buf, 0);
+			yylval.val = strtoimax(buf, (char **)&arith_buf, 0);
 			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 '_':
+		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':
+			p = buf;
+			while (buf++, is_in_name(*buf))
+				;
+			yylval.name = stalloc(buf - p + 1);
+			*(char *)mempcpy(yylval.name, p, buf - p) = 0;
+			value = ARITH_VAR;
+			goto out;
 		case '=':
-			if (*++buf != '=') {
-				goto err;
-			}
-			value = ARITH_EQ;
+			value = ARITH_ASS;
+checkeq:
+			if (*++buf != '=')
+				goto out;
+			value += 11;
 			break;
 		case '>':
 			switch (*++buf) {
@@ -82,7 +150,7 @@ err:
 				break;
 			case '>':
 				value = ARITH_RSHIFT;
-				break;
+				goto checkeq;
 			default:
 				value = ARITH_GT;
 				goto out;
@@ -95,7 +163,7 @@ err:
 				break;
 			case '<':
 				value = ARITH_LSHIFT;
-				break;
+				goto checkeq;
 			default:
 				value = ARITH_LT;
 				goto out;
@@ -104,14 +172,14 @@ err:
 		case '|':
 			if (*++buf != '|') {
 				value = ARITH_BOR;
-				goto out;
+				goto checkeq;
 			}
 			value = ARITH_OR;
 			break;
 		case '&':
 			if (*++buf != '&') {
 				value = ARITH_BAND;
-				goto out;
+				goto checkeq;
 			}
 			value = ARITH_AND;
 			break;
@@ -133,24 +201,30 @@ err:
 			break;
 		case '*':
 			value = ARITH_MUL;
-			break;
+			goto checkeq;
 		case '/':
 			value = ARITH_DIV;
-			break;
+			goto checkeq;
 		case '%':
 			value = ARITH_REM;
-			break;
+			goto checkeq;
 		case '+':
 			value = ARITH_ADD;
-			break;
+			goto checkeq;
 		case '-':
 			value = ARITH_SUB;
-			break;
+			goto checkeq;
 		case '~':
 			value = ARITH_BNOT;
 			break;
 		case '^':
 			value = ARITH_BXOR;
+			goto checkeq;
+		case '?':
+			value = ARITH_QMARK;
+			break;
+		case ':':
+			value = ARITH_COLON;
 			break;
 		}
 		break;
diff --git a/src/expand.c b/src/expand.c
index 54fe908..9cb8eab 100644
--- a/src/expand.c
+++ b/src/expand.c
@@ -42,6 +42,7 @@
 #endif
 #include <stdlib.h>
 #include <stdio.h>
+#include <stdint.h>
 #include <limits.h>
 #include <string.h>
 #if defined(__GLIBC__)
@@ -142,7 +143,7 @@ STATIC int pmatch(const char *, const char *);
 #else
 #define pmatch(a, b) !fnmatch((a), (b), 0)
 #endif
-STATIC int cvtnum(long);
+STATIC int cvtnum(intmax_t);
 STATIC size_t esclen(const char *, const char *);
 STATIC char *scanleft(char *, char *, char *, char *, int, int);
 STATIC char *scanright(char *, char *, char *, char *, int, int);
@@ -478,9 +479,11 @@ removerecordregions(int endoff)
 void
 expari(int flag)
 {
+	struct stackmark sm;
 	char *p, *start;
 	int begoff;
 	int len;
+	intmax_t result;
 
 	/*	ifsfree(); */
 
@@ -490,8 +493,9 @@ expari(int flag)
 	 * start of arithmetic.
 	 */
 	start = stackblock();
-	p = expdest - 1;
-	*p = '\0';
+	p = expdest;
+	pushstackmark(&sm, p - start);
+	*--p = '\0';
 	p--;
 	do {
 		int esc;
@@ -522,7 +526,10 @@ expari(int flag)
 	if (flag & QUOTES_ESC)
 		rmescapes(p + 1);
 
-	len = cvtnum(arith(p + 1));
+	result = arith(p + 1);
+	popstackmark(&sm);
+
+	len = cvtnum(result);
 
 	if (!(flag & EXP_QUOTED))
 		recordregion(begoff, begoff + len, 0);
@@ -1707,12 +1714,12 @@ casematch(union node *pattern, char *val)
  */
 
 STATIC int
-cvtnum(long num)
+cvtnum(intmax_t num)
 {
-	int len;
+	int len = max_int_length(sizeof(num));
 
-	expdest = makestrspace(32, expdest);
-	len = fmtstr(expdest, 32, "%ld", num);
+	expdest = makestrspace(len, expdest);
+	len = fmtstr(expdest, len, "%jd", num);
 	STADJUST(len, expdest);
 	return len;
 }
diff --git a/src/expand.h b/src/expand.h
index 4dfbc43..225b004 100644
--- a/src/expand.h
+++ b/src/expand.h
@@ -34,6 +34,8 @@
  *	@(#)expand.h	8.2 (Berkeley) 5/4/95
  */
 
+#include <stdint.h>
+
 struct strlist {
 	struct strlist *next;
 	char *text;
@@ -68,7 +70,7 @@ char *_rmescapes(char *, int);
 int casematch(union node *, char *);
 
 /* From arith.y */
-int arith(const char *);
+intmax_t arith(const char *);
 int expcmd(int , char **);
 #ifdef USE_LEX
 void arith_lex_reset(void);
diff --git a/src/mystring.c b/src/mystring.c
index df1691b..b84b7e2 100644
--- a/src/mystring.c
+++ b/src/mystring.c
@@ -112,13 +112,13 @@ prefix(const char *string, const char *pfx)
 /*
  * Convert a string into an integer of type intmax_t.  Alow trailing spaces.
  */
-intmax_t atomax10(const char *s)
+intmax_t atomax(const char *s, int base)
 {
 	char *p;
 	intmax_t r;
 
 	errno = 0;
-	r = strtoimax(s, &p, 10);
+	r = strtoimax(s, &p, base);
 
 	if (errno != 0)
 		sh_error(illnum, s);
@@ -132,6 +132,11 @@ intmax_t atomax10(const char *s)
 	return r;
 }
 
+intmax_t atomax10(const char *s)
+{
+	return atomax(s, 10);
+}
+
 /*
  * Convert a string of digits to an integer, printing an error message on
  * failure.
diff --git a/src/mystring.h b/src/mystring.h
index c9cade6..477cd16 100644
--- a/src/mystring.h
+++ b/src/mystring.h
@@ -48,6 +48,7 @@ extern const char homestr[];
 void scopyn(const char *, char *, int);
 #endif
 char *prefix(const char *, const char *);
+intmax_t atomax(const char *, int);
 intmax_t atomax10(const char *);
 int number(const char *);
 int is_number(const char *);
diff --git a/src/shell.h b/src/shell.h
index 9b67696..98edc8b 100644
--- a/src/shell.h
+++ b/src/shell.h
@@ -92,3 +92,13 @@ extern char nullstr[1];		/* null string */
 
 #define likely(x)	__builtin_expect(!!(x),1)
 #define unlikely(x)	__builtin_expect(!!(x),0)
+
+/*
+ * Hack to calculate maximum length.
+ * (length * 8 - 1) * log10(2) + 1 + 1 + 12
+ * The second 1 is for the minus sign and the 12 is a safety margin.
+ */
+static inline int max_int_length(int bytes)
+{
+	return (bytes * 8 - 1) * 0.30102999566398119521 + 14;
+}
diff --git a/src/var.c b/src/var.c
index 501a279..17d3637 100644
--- a/src/var.c
+++ b/src/var.c
@@ -202,6 +202,21 @@ setvar(const char *name, const char *val, int flags)
 	INTON;
 }
 
+/*
+ * Set the given integer as the value of a variable.  The flags argument is
+ * ored with the flags of the variable.
+ */
+
+intmax_t setvarint(const char *name, intmax_t val)
+{
+	int len = max_int_length(sizeof(val));
+	char buf[len];
+
+	fmtstr(buf, len, "%jd", val);
+	setvar(name, buf, 0);
+	return val;
+}
+
 
 
 /*
@@ -293,6 +308,11 @@ lookupvar(const char *name)
 	return NULL;
 }
 
+intmax_t lookupvarint(const char *name)
+{
+	return atomax(lookupvar(name) ?: nullstr, 0);
+}
+
 
 
 /*
diff --git a/src/var.h b/src/var.h
index ae58c6c..66443df 100644
--- a/src/var.h
+++ b/src/var.h
@@ -34,6 +34,8 @@
  *	@(#)var.h	8.2 (Berkeley) 5/4/95
  */
 
+#include <stdint.h>
+
 /*
  * Shell variables.
  */
@@ -125,10 +127,12 @@ extern const char defpathvar[];
 
 void initvar(void);
 void setvar(const char *, const char *, int);
+intmax_t setvarint(const char *, intmax_t);
 void setvareq(char *, int);
 struct strlist;
 void listsetvar(struct strlist *, int);
 char *lookupvar(const char *);
+intmax_t lookupvarint(const char *);
 char *bltinlookup(const char *);
 char **listvars(int, int, char ***);
 #define environment() listvars(VEXPORT, VUNSET, 0)