Upgrade one-true-awk to 944989bf683d30f306d8f29a3eb8c68c7c603fb4 am: 2b2ca9f3fb am: 78cb1093df am: 7be0794073

Change-Id: I31b2a71bd29660973c47448599812f37d575c9a6
diff --git a/ChangeLog b/ChangeLog
index 0ecc656..1afd9de 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,29 @@
+2020-01-06         Arnold D. Robbins     <arnold@skeeve.com>
+
+	Minor fixes.
+	* b.c (replace_repeat): Turn init_q back into an int.
+	* lex.c (string): Use \a instead of \007.
+	* tran.c (catstr): Use snprintf instead of sprintf.
+
+2020-01-01         Arnold D. Robbins     <arnold@skeeve.com>
+
+	* tran.c (syminit, arginit, envinit): Free sval member before
+	setting it. Thanks to valgrind.
+	* b.c: Small formatting cleanups in several routines.
+
+2019-12-27         Arnold D. Robbins     <arnold@skeeve.com>
+
+	* b.c (replace_repeat): Fix a bug whereby a{0,3} could match
+	four a's.  Thanks to Anonymous AWK fan <awkfan77@mailfence.com>
+	for the report. Also, minor code formatting cleanups.
+	* testdir/T.int-expr: New file.
+
+2019-12-11         Arnold D. Robbins     <arnold@skeeve.com>
+
+	* README: Renamed to ...
+	* README.md: ... this. Cleaned up some as well,
+	including moving to Markdown.
+
 2019-11-08         Arnold D. Robbins     <arnold@skeeve.com>
 
 	* test/T.chem: Use $oldawk instead of hardwiring 'awk'.
diff --git a/FIXES b/FIXES
index 722739e..6889e30 100644
--- a/FIXES
+++ b/FIXES
@@ -25,6 +25,21 @@
 This file lists all bug fixes, changes, etc., made since the AWK book
 was sent to the printers in August, 1987.
 
+January 5, 2020:
+	Fix a bug in the concatentation of two string constants into
+	one done in the grammar.  Fixes GitHub issue #61.  Thanks
+	to GitHub user awkfan77 for pointing out the direction for
+	the fix.  New test T.concat added to the test suite.
+	Fix a few memory leaks reported by valgrind, as well.
+
+December 27, 2019:
+	Fix a bug whereby a{0,3} could match four a's.  Thanks to
+	"Anonymous AWK fan" for the report.
+
+December 11, 2019:
+	Further printf-related fixes for 32 bit systems.
+	Thanks again to Christos Zoulas.
+
 December 8, 2019:
 	Fix the return value of sprintf("%d") on 32 bit systems.
 	Thanks to Jim Lowe for the report and to Christos Zoulas
diff --git a/METADATA b/METADATA
index c99366c..a4d2203 100644
--- a/METADATA
+++ b/METADATA
@@ -5,11 +5,11 @@
     type: GIT
     value: "https://github.com/onetrueawk/awk.git"
   }
-  version: "af86dacfad85857b2ea9fa95150ddd8c671695ed"
+  version: "944989bf683d30f306d8f29a3eb8c68c7c603fb4"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2019
-    month: 12
-    day: 9
+    year: 2020
+    month: 1
+    day: 8
   }
 }
diff --git a/README b/README
deleted file mode 100644
index 5d89359..0000000
--- a/README
+++ /dev/null
@@ -1,90 +0,0 @@
-/****************************************************************
-Copyright (C) Lucent Technologies 1997
-All Rights Reserved
-
-Permission to use, copy, modify, and distribute this software and
-its documentation for any purpose and without fee is hereby
-granted, provided that the above copyright notice appear in all
-copies and that both that the copyright notice and this
-permission notice and warranty disclaimer appear in supporting
-documentation, and that the name Lucent Technologies or any of
-its entities not be used in advertising or publicity pertaining
-to distribution of the software without specific, written prior
-permission.
-
-LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
-INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
-IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
-SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
-IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
-ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-THIS SOFTWARE.
-****************************************************************/
-
-This is the version of awk described in "The AWK Programming Language",
-by Al Aho, Brian Kernighan, and Peter Weinberger
-(Addison-Wesley, 1988, ISBN 0-201-07981-X).
-
-Changes, mostly bug fixes and occasional enhancements, are listed
-in FIXES.  If you distribute this code further, please please please
-distribute FIXES with it.  If you find errors, please report them
-to bwk@cs.princeton.edu.  Thanks.
-
-The program itself is created by
-	make
-which should produce a sequence of messages roughly like this:
-
-	yacc -d awkgram.y
-
-conflicts: 43 shift/reduce, 85 reduce/reduce
-	mv y.tab.c ytab.c
-	mv y.tab.h ytab.h
-	cc -c ytab.c
-	cc -c b.c
-	cc -c main.c
-	cc -c parse.c
-	cc maketab.c -o maketab
-	./maketab >proctab.c
-	cc -c proctab.c
-	cc -c tran.c
-	cc -c lib.c
-	cc -c run.c
-	cc -c lex.c
-	cc ytab.o b.o main.o parse.o proctab.o tran.o lib.o run.o lex.o -lm
-
-This produces an executable a.out; you will eventually want to
-move this to some place like /usr/bin/awk.
-
-If your system does not have yacc or bison (the GNU
-equivalent), you must compile the pieces manually.  We have
-included yacc output in ytab.c and ytab.h, and backup copies in
-case you overwrite them.  We have also included a copy of
-proctab.c so you do not need to run maketab.
-
-NOTE: This version uses ANSI C, as you should also.  We have
-compiled this without any changes using gcc -Wall and/or local C
-compilers on a variety of systems, but new systems or compilers
-may raise some new complaint; reports of difficulties are
-welcome.
-
-This also compiles with Visual C++ on all flavors of Windows,
-*if* you provide versions of popen and pclose.  The file
-missing95.c contains versions that can be used to get started
-with, though the underlying support has mysterious properties,
-the symptom of which can be truncated pipe output.  Beware.  The
-file makefile.win gives hints on how to proceed; if you run
-vcvars32.bat, it will set up necessary paths and parameters so
-you can subsequently run nmake -f makefile.win.  Beware also that
-when running on Windows under command.com, various quoting
-conventions are different from Unix systems: single quotes won't
-work around arguments, and various characters like % are
-interpreted within double quotes.
-
-This compiles without change on Macintosh OS X using gcc and
-the standard developer tools.
-
-The version of malloc that comes with some systems is sometimes
-astonishly slow.  If awk seems slow, you might try fixing that.
-More generally, turning on optimization can significantly improve
-awk's speed, perhaps by 1/3 for highest levels.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..77701d7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,96 @@
+# The One True Awk
+
+This is the version of `awk` described in _The AWK Programming Language_,
+by Al Aho, Brian Kernighan, and Peter Weinberger
+(Addison-Wesley, 1988, ISBN 0-201-07981-X).
+
+## Copyright
+
+Copyright (C) Lucent Technologies 1997<br/>
+All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and
+its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name Lucent Technologies or any of
+its entities not be used in advertising or publicity pertaining
+to distribution of the software without specific, written prior
+permission.
+
+LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+
+## Distribution and Reporting Problems
+
+Changes, mostly bug fixes and occasional enhancements, are listed
+in `FIXES`.  If you distribute this code further, please please please
+distribute `FIXES` with it.
+
+If you find errors, please report them
+to bwk@cs.princeton.edu.
+Please _also_ open an issue in the GitHub issue tracker, to make
+it easy to track issues.
+Thanks.
+
+## Submitting Pull Requests
+
+Pull requests are welcome.  However, please create them with a request
+to merge into the `staging` branch instead of into the `master` branch.
+This allows us to do testing, and to make any additional edits or changes
+after the merge but before merging to `master`.
+
+## Building
+
+The program itself is created by
+
+	make
+
+which should produce a sequence of messages roughly like this:
+
+	yacc -d awkgram.y
+	conflicts: 43 shift/reduce, 85 reduce/reduce
+	mv y.tab.c ytab.c
+	mv y.tab.h ytab.h
+	cc -c ytab.c
+	cc -c b.c
+	cc -c main.c
+	cc -c parse.c
+	cc maketab.c -o maketab
+	./maketab >proctab.c
+	cc -c proctab.c
+	cc -c tran.c
+	cc -c lib.c
+	cc -c run.c
+	cc -c lex.c
+	cc ytab.o b.o main.o parse.o proctab.o tran.o lib.o run.o lex.o -lm
+
+This produces an executable `a.out`; you will eventually want to
+move this to some place like `/usr/bin/awk`.
+
+If your system does not have `yacc` or `bison` (the GNU
+equivalent), you need to install one of them first.
+
+NOTE: This version uses ANSI C (C 99), as you should also.  We have
+compiled this without any changes using `gcc -Wall` and/or local C
+compilers on a variety of systems, but new systems or compilers
+may raise some new complaint; reports of difficulties are
+welcome.
+
+This compiles without change on Macintosh OS X using `gcc` and
+the standard developer tools.
+
+The version of `malloc` that comes with some systems is sometimes
+astonishly slow.  If `awk` seems slow, you might try fixing that.
+More generally, turning on optimization can significantly improve
+`awk`'s speed, perhaps by 1/3 for highest levels.
+
+#### Last Updated
+Wed Jan  1 22:44:38 IST 2020
diff --git a/b.c b/b.c
index 1e53652..5671796 100644
--- a/b.c
+++ b/b.c
@@ -180,7 +180,7 @@
 }
 
 fa *mkdfa(const char *s, bool anchor)	/* does the real work of making a dfa */
-				/* anchor = 1 for anchored matches, else 0 */
+				/* anchor = true for anchored matches, else false */
 {
 	Node *p, *p1;
 	fa *f;
@@ -223,17 +223,17 @@
 	k = *(f->re[0].lfollow);
 	xfree(f->posns[2]);
 	f->posns[2] = intalloc(k + 1,  __func__);
-	for (i=0; i <= k; i++) {
+	for (i = 0; i <= k; i++) {
 		(f->posns[2])[i] = (f->re[0].lfollow)[i];
 	}
 	if ((f->posns[2])[1] == f->accept)
 		f->out[2] = 1;
-	for (i=0; i < NCHARS; i++)
+	for (i = 0; i < NCHARS; i++)
 		f->gototab[2][i] = 0;
 	f->curstat = cgoto(f, 2, HAT);
 	if (anchor) {
 		*f->posns[2] = k-1;	/* leave out position 0 */
-		for (i=0; i < k; i++) {
+		for (i = 0; i < k; i++) {
 			(f->posns[0])[i] = (f->posns[2])[i];
 		}
 
@@ -463,9 +463,10 @@
 		}
 		if (type(p) == CCL && (*(char *) right(p)) == '\0')
 			return(0);		/* empty CCL */
-		else return(1);
+		return(1);
 	case PLUS:
-		if (first(left(p)) == 0) return(0);
+		if (first(left(p)) == 0)
+			return(0);
 		return(1);
 	case STAR:
 	case QUEST:
@@ -714,7 +715,7 @@
 			if (buf[--k] && ungetc(buf[k], f) == EOF)
 				FATAL("unable to ungetc '%c'", buf[k]);
 		while (k > i + patlen);
-		buf[k] = 0;
+		buf[k] = '\0';
 		return true;
 	}
 	else
@@ -935,7 +936,7 @@
 		buf[j++] = '(';
 		buf[j++] = ')';
 	}
-	for (i=1; i < firstnum; i++) {		/* copy x reps 	*/
+	for (i = 1; i < firstnum; i++) {	/* copy x reps 	*/
 		memcpy(&buf[j], atom, atomlen);
 		j += atomlen;
 	}
@@ -944,7 +945,7 @@
 	} else if (special_case == REPEAT_WITH_Q) {
 		if (init_q)
 			buf[j++] = '?';
-		for (i = 0; i < n_q_reps; i++) {	/* copy x? reps */
+		for (i = init_q; i < n_q_reps; i++) {	/* copy x? reps */
 			memcpy(&buf[j], atom, atomlen);
 			j += atomlen;
 			buf[j++] = '?';
@@ -1166,15 +1167,17 @@
 				if (commafound) {
 					if (digitfound) { /* {n,m} */
 						m = num;
-						if (m<n)
+						if (m < n)
 							FATAL("illegal repetition expression: class %.20s",
 								lastre);
-						if ((n==0) && (m==1)) {
+						if (n == 0 && m == 1) {
 							return QUEST;
 						}
 					} else {	/* {n,} */
-						if (n==0) return STAR;
-						if (n==1) return PLUS;
+						if (n == 0)
+							return STAR;
+						else if (n == 1)
+							return PLUS;
 					}
 				} else {
 					if (digitfound) { /* {n} same as {n,n} */
@@ -1187,7 +1190,7 @@
 				}
 				if (repeat(starttok, prestr-starttok, lastatom,
 					   startreptok - lastatom, n, m) > 0) {
-					if ((n==0) && (m==0)) {
+					if (n == 0 && m == 0) {
 						return EMPTYRE;
 					}
 					/* must rescan input for next token */
@@ -1313,7 +1316,7 @@
 	for (i = 0; i <= f->accept; i++) {
 		xfree(f->re[i].lfollow);
 		if (f->re[i].ltype == CCL || f->re[i].ltype == NCCL)
-			xfree((f->re[i].lval.np));
+			xfree(f->re[i].lval.np);
 	}
 	xfree(f->restr);
 	xfree(f->out);
diff --git a/lex.c b/lex.c
index 2de0603..d729516 100644
--- a/lex.c
+++ b/lex.c
@@ -190,7 +190,9 @@
 		if (isalpha(c) || c == '_')
 			return word(buf);
 		if (isdigit(c)) {
-			yylval.cp = setsymtab(buf, tostring(buf), atof(buf), CON|NUM, symtab);
+			char *cp = tostring(buf);
+			yylval.cp = setsymtab(buf, cp, atof(buf), CON|NUM, symtab);
+			free(cp);
 			/* should this also have STR set? */
 			RET(NUMBER);
 		}
@@ -388,7 +390,7 @@
 			case 'r': *bp++ = '\r'; break;
 			case 'b': *bp++ = '\b'; break;
 			case 'v': *bp++ = '\v'; break;
-			case 'a': *bp++ = '\007'; break;
+			case 'a': *bp++ = '\a'; break;
 			case '\\': *bp++ = '\\'; break;
 
 			case '0': case '1': case '2': /* octal: \d \dd \ddd */
@@ -431,8 +433,9 @@
 	}
 	*bp = 0;
 	s = tostring(buf);
-	*bp++ = ' '; *bp++ = 0;
+	*bp++ = ' '; *bp++ = '\0';
 	yylval.cp = setsymtab(buf, s, 0.0, CON|STR|DONTFREE, symtab);
+	free(s);
 	RET(STRING);
 }
 
diff --git a/main.c b/main.c
index e3648d0..abfa312 100644
--- a/main.c
+++ b/main.c
@@ -22,7 +22,7 @@
 THIS SOFTWARE.
 ****************************************************************/
 
-const char	*version = "version 20191208";
+const char	*version = "version 20200105";
 
 #define DEBUG
 #include <stdio.h>
diff --git a/run.c b/run.c
index 4069f59..a331449 100644
--- a/run.c
+++ b/run.c
@@ -855,8 +855,13 @@
 		for (t = fmt; (*t++ = *s) != '\0'; s++) {
 			if (!adjbuf(&fmt, &fmtsz, MAXNUMSIZE+1+t-fmt, recsize, &t, "format3"))
 				FATAL("format item %.30s... ran format() out of memory", os);
-			if (isalpha((uschar)*s) && *s != 'l' && *s != 'h' && *s != 'L')
-				break;	/* the ansi panoply */
+			/* Ignore size specifiers */
+			if (strchr("hjLlqtz", *s) != NULL) {	/* the ansi panoply */
+				t--;
+				continue;
+			}
+			if (isalpha((uschar)*s))
+				break;
 			if (*s == '$') {
 				FATAL("'$' not permitted in awk formats");
 			}
@@ -889,15 +894,8 @@
 		case 'f': case 'e': case 'g': case 'E': case 'G':
 			flag = 'f';
 			break;
-		case 'd': case 'i':
-			flag = 'd';
-			if(*(s-1) == 'l') break;
-			*(t-1) = 'j';
-			*t = 'd';
-			*++t = '\0';
-			break;
-		case 'o': case 'x': case 'X': case 'u':
-			flag = *(s-1) == 'l' ? 'd' : 'u';
+		case 'd': case 'i': case 'o': case 'x': case 'X': case 'u':
+			flag = (*s == 'd' || *s == 'i') ? 'd' : 'u';
 			*(t-1) = 'j';
 			*t = *s;
 			*++t = '\0';
diff --git a/testdir/T.concat b/testdir/T.concat
new file mode 100755
index 0000000..c6bd016
--- /dev/null
+++ b/testdir/T.concat
@@ -0,0 +1,29 @@
+echo T.concat: test constant string concatentation
+
+awk=${awk-../a.out}
+
+$awk '
+BEGIN {
+	$0 = "aaa"
+	print "abcdef" " " $0
+}
+BEGIN { print "hello" "world"; print helloworld }
+BEGIN {
+ 	print " " "hello"
+ 	print "hello" " "
+ 	print "hello" " " "world"
+ 	print "hello" (" " "world")
+}
+' > foo1
+
+cat << \EOF > foo2
+abcdef aaa
+helloworld
+
+ hello
+hello 
+hello world
+hello world
+EOF
+
+diff foo1 foo2 || echo 'BAD: T.concat (1)'
diff --git a/testdir/T.expr b/testdir/T.expr
index 7390e8b..304b3d0 100755
--- a/testdir/T.expr
+++ b/testdir/T.expr
@@ -167,15 +167,15 @@
 2	1	ah b
 3	1	ah  b
 
-try { printf("%d %ld\n", $1, $1) }
-1	1 1
-10	10 10
-10000	10000 10000
+try { printf("%d %ld %lld %zd %jd %hd %hhd\n", $1, $1, $1, $1, $1, $1, $1) }
+1	1 1 1 1 1 1 1
+10	10 10 10 10 10 10 10
+10000	10000 10000 10000 10000 10000 10000 10000
 
-try { printf("%x %lx\n", $1, $1) }
-1	1 1
-10	a a
-10000	2710 2710
+try { printf("%x %lx %llx %zx %jx %hx %hhx\n", $1, $1, $1, $1, $1, $1, $1) }
+1	1 1 1 1 1 1 1
+10	a a a a a a a
+10000	2710 2710 2710 2710 2710 2710 2710
 
 try { if ($1 ~ $2) print 1; else print 0 }
 a	\141	1
diff --git a/testdir/T.int-expr b/testdir/T.int-expr
new file mode 100755
index 0000000..e71a075
--- /dev/null
+++ b/testdir/T.int-expr
@@ -0,0 +1,82 @@
+echo T.int-expr: test interval expressions
+
+awk=${awk-../a.out}
+
+rm -f foo
+
+cat << \EOF > prog
+NF == 0		{ next }
+$1 == "pat"	{ pattern = $2; next }
+{
+	check = ($1 ~ pattern)
+	printf("%s ~ /%s/ -> should be %d, is %d\n", $1, pattern, $2, check)
+}
+EOF
+
+cat << \EOF > foo.in
+pat	ab{0}c
+ac	1
+abc	0
+
+pat	ab{1}c
+ac	0
+abc	1
+abbc	0
+
+pat	ab{1,}c
+ac	0
+abc	1
+abbc	1
+abbbc	1
+abbbbc	1
+
+pat	ab{0,1}c
+ac	1
+abc	1
+abbc	0
+
+pat	ab{0,3}c
+ac	1
+abc	1
+abbc	1
+abbbc	1
+abbbbc	0
+
+pat	ab{1,3}c
+ac	0
+abc	1
+abbc	1
+abbbc	1
+abbbbc	0
+EOF
+
+cat << \EOF > foo1
+ac ~ /ab{0}c/ -> should be 1, is 1
+abc ~ /ab{0}c/ -> should be 0, is 0
+ac ~ /ab{1}c/ -> should be 0, is 0
+abc ~ /ab{1}c/ -> should be 1, is 1
+abbc ~ /ab{1}c/ -> should be 0, is 0
+ac ~ /ab{1,}c/ -> should be 0, is 0
+abc ~ /ab{1,}c/ -> should be 1, is 1
+abbc ~ /ab{1,}c/ -> should be 1, is 1
+abbbc ~ /ab{1,}c/ -> should be 1, is 1
+abbbbc ~ /ab{1,}c/ -> should be 1, is 1
+ac ~ /ab{0,1}c/ -> should be 1, is 1
+abc ~ /ab{0,1}c/ -> should be 1, is 1
+abbc ~ /ab{0,1}c/ -> should be 0, is 0
+ac ~ /ab{0,3}c/ -> should be 1, is 1
+abc ~ /ab{0,3}c/ -> should be 1, is 1
+abbc ~ /ab{0,3}c/ -> should be 1, is 1
+abbbc ~ /ab{0,3}c/ -> should be 1, is 1
+abbbbc ~ /ab{0,3}c/ -> should be 0, is 0
+ac ~ /ab{1,3}c/ -> should be 0, is 0
+abc ~ /ab{1,3}c/ -> should be 1, is 1
+abbc ~ /ab{1,3}c/ -> should be 1, is 1
+abbbc ~ /ab{1,3}c/ -> should be 1, is 1
+abbbbc ~ /ab{1,3}c/ -> should be 0, is 0
+EOF
+
+
+$awk -f prog foo.in > foo2
+diff foo1 foo2 || echo 'BAD: T.int-expr (1)'
+rm -f prog
diff --git a/tran.c b/tran.c
index 20125e3..d659cfa 100644
--- a/tran.c
+++ b/tran.c
@@ -114,6 +114,7 @@
 	rlengthloc = setsymtab("RLENGTH", "", 0.0, NUM, symtab);
 	RLENGTH = &rlengthloc->fval;
 	symtabloc = setsymtab("SYMTAB", "", 0.0, ARR, symtab);
+	free(symtabloc->sval);
 	symtabloc->sval = (char *) symtab;
 }
 
@@ -126,6 +127,7 @@
 	ARGC = &setsymtab("ARGC", "", (Awkfloat) ac, NUM, symtab)->fval;
 	cp = setsymtab("ARGV", "", 0.0, ARR, symtab);
 	ARGVtab = makesymtab(NSYMTAB);	/* could be (int) ARGC as well */
+	free(cp->sval);
 	cp->sval = (char *) ARGVtab;
 	for (i = 0; i < ac; i++) {
 		sprintf(temp, "%d", i);
@@ -144,6 +146,7 @@
 
 	cp = setsymtab("ENVIRON", "", 0.0, ARR, symtab);
 	ENVtab = makesymtab(NSYMTAB);
+	free(cp->sval);
 	cp->sval = (char *) ENVtab;
 	for ( ; *envp; envp++) {
 		if ((p = strchr(*envp, '=')) == NULL)
@@ -524,8 +527,17 @@
 	if (p == NULL)
 		FATAL("out of space concatenating %s and %s", sa, sb);
 	snprintf(p, l, "%s%s", sa, sb);
-	c = setsymtab(p, p, 0.0, CON|STR|DONTFREE, symtab);
+
+	l++;	// add room for ' '
+	char *newbuf = malloc(l);
+	if (newbuf == NULL)
+		FATAL("out of space concatenating %s and %s", sa, sb);
+	// See string() in lex.c; a string "xx" is stored in the symbol
+	// table as "xx ".
+	snprintf(newbuf, l, "%s ", p);
+	c = setsymtab(newbuf, p, 0.0, CON|STR|DONTFREE, symtab);
 	free(p);
+	free(newbuf);
 	return c;
 }