From keithp at keithp.com  Wed Mar 15 14:19:20 2017
From: keithp at keithp.com (Keith Packard)
Date: Wed, 15 Mar 2017 14:19:20 -0700 (PDT)
Subject: [Nickle] nickle: Branch 'master' - 10 commits
Message-ID: <20170315211920.7A8603F2026A@elaine.keithp.com>


New commits:
commit c100536369045242614f71a44e09c2c3df5359ab
Author: Keith Packard <keithp at keithp.com>
Date:   Wed Mar 15 09:25:17 2017 -0700

    Adapt tests to new automake requirements
    
    Automake now has a separate variable for the program to run the test
    scripts with, and breaks if you try to embed that in the
    TESTS_ENVIRONMENT variable, which used to be how this was done.
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/examples/smlng/Makefile.am b/examples/smlng/Makefile.am
index 32c198e..78a079d 100644
--- a/examples/smlng/Makefile.am
+++ b/examples/smlng/Makefile.am
@@ -15,7 +15,8 @@ exampledir=$(pkgdatadir)/examples/smlng
 
 example_DATA=$(NICKLEFILES) COPYING
 
-TESTS_ENVIRONMENT=NICKLESTART=$(top_srcdir)/builtin.5c NICKLEPATH=$(top_srcdir):$(top_srcdir)/$(subdir) $(top_builddir)/nickle < $(top_srcdir)/$(subdir)/data.sgml > /dev/null
+TESTS_ENVIRONMENT=NICKLESTART=$(top_srcdir)/builtin.5c NICKLEPATH=$(top_srcdir):$(top_srcdir)/$(subdir)
+LOG_COMPILER=$(top_builddir)/nickle < $(top_srcdir)/$(subdir)/data.sgml
 
 TESTS=test.5c
 
diff --git a/examples/turtle/Makefile.am b/examples/turtle/Makefile.am
index 96595e9..0cbda02 100644
--- a/examples/turtle/Makefile.am
+++ b/examples/turtle/Makefile.am
@@ -15,8 +15,9 @@ example_DATA=$(NICKLEFILES) snowflake.tex COPYING
 
 EXTRA_DIST=$(example_DATA) COPYING
 
-TESTS_ENVIRONMENT=NICKLESTART=$(top_srcdir)/builtin.5c NICKLEPATH=$(top_srcdir):$(top_srcdir)/$(subdir) $(top_builddir)/nickle
+TESTS_ENVIRONMENT=NICKLESTART=$(top_srcdir)/builtin.5c NICKLEPATH=$(top_srcdir):$(top_srcdir)/$(subdir)
 TESTS=snowflake.5c
+LOG_COMPILER=$(top_builddir)/nickle
 
 CLEANFILES=snowflake.eepic snowflake.aux snowflake.dvi snowflake.log
 
diff --git a/test/Makefile.am b/test/Makefile.am
index 3a6dcce..683e5a8 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -20,8 +20,9 @@ TABLES=math-tables.5c
 
 CLEANFILES=$(TABLES)
 
-TESTS_ENVIRONMENT=NICKLESTART=$(top_srcdir)/builtin.5c NICKLEPATH=$(top_srcdir) ../nickle
+TESTS_ENVIRONMENT=NICKLESTART=$(top_srcdir)/builtin.5c NICKLEPATH=$(top_srcdir)
 TESTS=$(check_SCRIPTS)
+LOG_COMPILER=$(top_builddir)/nickle
 
 EXTRA_DIST=$(check_SCRIPTS)
 
commit 334780afcc76da6e158aeefb5e7f3df533e5aaf2
Author: Keith Packard <keithp at keithp.com>
Date:   Wed Mar 15 09:24:20 2017 -0700

    Use 'G' format for elements when printing arrays in 'g' format
    
    This restricts array printing to just recurse one level.
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/array.c b/array.c
index 2c762f0..aa096ad 100644
--- a/array.c
+++ b/array.c
@@ -123,7 +123,7 @@ ArrayPrint (Value f, Value av, char format, int base, int width, int prec, int f
 	i = 0;
 	while (i < limit)
 	{
-	    if (!Print (f, ArrayValueGet (a, i), format, base, width, prec, fill))
+	    if (!Print (f, ArrayValueGet (a, i), down_format, base, width, prec, fill))
 	    {
 		ret = False;
 		break;
commit a251ccf47ef335f293d3e53dd8fd9f03c9901bc0
Author: Keith Packard <keithp at keithp.com>
Date:   Wed Mar 15 08:52:48 2017 -0700

    When using pointer as hash, first cast to intptr_t
    
    This avoids a compiler warning when pointers are not the same size as
    HashValues.
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/foreign.c b/foreign.c
index bcbe2f4..5a1247d 100644
--- a/foreign.c
+++ b/foreign.c
@@ -26,7 +26,7 @@ ForeignPrint (Value f, Value av, char format, int base, int width, int prec, int
 static HashValue
 ForeignHash (Value av)
 {
-    return (HashValue) av->foreign.data;
+    return (HashValue) (intptr_t) av->foreign.data;
 }
 
 static void
commit 3337ca1580d84430246ed8693a200031474f60e9
Author: Keith Packard <keithp at keithp.com>
Date:   Wed Mar 15 08:42:41 2017 -0700

    Initialize 'replace' in NewTypedBox
    
    When left uninitialized, undefined results will occur. This caused a
    test failure on MIPS when being built with PIE support.
    
    Debian bug 857840.
    
    Reported-by: James Cowgill <jcowgill at debian.org>
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/box.c b/box.c
index 549d97f..8c4021b 100644
--- a/box.c
+++ b/box.c
@@ -55,10 +55,10 @@ NewTypedBox (Bool array, BoxTypesPtr bt)
 
     box = ALLOCATE (&BoxType, sizeof (Box) + bt->count * sizeof (Value));
     box->constant = False;
-/*    box->array = array; */
     box->homogeneous = False;
-    box->u.types = bt;
+    box->replace = False;
     box->nvalues = bt->count;
+    box->u.types = bt;
     for (i = 0; i < bt->count; i++)
 	BoxValueSet (box, i, 0);
     RETURN (box);
commit c4f176d76f88a2046619fce0af7f7cc8cef7c1de
Author: Keith Packard <keithp at keithp.com>
Date:   Wed Mar 15 08:39:16 2017 -0700

    Open quote character is a single char, not a string
    
    When checking to see if a string needs to be dequoted, check the first
    character of the string against the quote character, rather than
    checking the whole string.
    
    Probably using strings for the quote markers wasn't the best API
    choice as this would have been caught by typechecking had the quote
    characters been an int instead.
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/string.5c b/string.5c
index 52e3878..a53c656 100644
--- a/string.5c
+++ b/string.5c
@@ -214,7 +214,7 @@ extend namespace String {
 	    string curs = "";
 	    void consume() {
 		string t = chomp(curs);
-		if (t != "" && t == q.oq)
+		if (t != "" && t[0] == q.oq[0])
 		    t = dq(t);
 		ss[dim(ss)] = t;
 		curs = "";
commit 74e92c3ff8b3b8478d44ee5e9b1f0fc75283fd88
Author: Keith Packard <keithp at keithp.com>
Date:   Fri Jul 8 16:32:55 2016 +0200

    Define release date in configure.ac and use that instead of build date
    
    This avoids encoding the current date into the resulting output so
    that it can be reproduced.
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/Makefile.am b/Makefile.am
index b4e11ce..70b19aa 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -7,7 +7,7 @@
 AUTOMAKE_OPTIONS = foreign
 SUBDIRS = bench test examples doc
 
-BUILD_DATE := $(shell sh $(top_builddir)/date-sh)
+RELEASE_DATE := @RELEASE_DATE@
 
 NICKLEFILES = builtin.5c math.5c scanf.5c mutex.5c \
 	arc4.5c prng.5c command.5c abort.5c \
@@ -64,7 +64,7 @@ man_MANS = nickle.1
 AM_YFLAGS = -d
 
 AM_CPPFLAGS = \
-	-DBUILD=\""$(BUILD_DATE)"\" \
+	-DBUILD=\""$(RELEASE_DATE)"\" \
 	-DBUILD_VERSION=\""$(VERSION)"\" \
 	-DLOCAL_BUILD \
 	-DNICKLELIBDIR=\"@nicklelibdir@\"
diff --git a/configure.ac b/configure.ac
index 23e55c9..3a4d01f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -7,7 +7,7 @@ dnl for licensing information.
 AC_PREREQ([2.64])
 
 AC_INIT([nickle],[2.77],[http://nickle.org],[nickle])
-
+RELEASE_DATE="2016-07-08"
 AC_CONFIG_SRCDIR([nickle.h])
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_AUX_DIR(.)
@@ -16,6 +16,7 @@ AM_INIT_AUTOMAKE([foreign])
 
 AM_MAINTAINER_MODE
 
+AC_SUBST([RELEASE_DATE])
 dnl Checks for programs.
 AC_PROG_CC
 AC_PROG_INSTALL
@@ -26,8 +27,6 @@ AC_CHECK_PROGS([YACC], byacc yacc bison, yacc)
 case "$YACC" in
 *bison)	YACC="$YACC -y"	;;
 esac
-AC_PROG_AWK
-AC_CHECK_PROGS([DATE], date)
 
 dnl Checks for libraries.
 AC_CHECK_FUNC(log,,AC_CHECK_LIB(m, log))
@@ -135,7 +134,6 @@ AC_CONFIG_FILES(
  examples/smlng/Makefile
  examples/turtle/Makefile
  doc/Makefile
- doc/tutorial/Makefile
- date-sh)
+ doc/tutorial/Makefile)
 
 AC_OUTPUT
diff --git a/date-sh.in b/date-sh.in
deleted file mode 100755
index 6777a7f..0000000
--- a/date-sh.in
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/sh
-#
-# Copyright © 1988-2004 Keith Packard and Bart Massey.
-# All Rights Reserved.  See the file COPYING in this directory
-# for licensing information.
-#
- at DATE@ |
- at AWK@ '
-BEGIN {
-	monthnum["Jan"] = 1
-	monthnum["Feb"] = 2
-	monthnum["Mar"] = 3
-	monthnum["Apr"] = 4
-	monthnum["May"] = 5
-	monthnum["Jun"] = 6
-	monthnum["Jul"] = 7
-	monthnum["Aug"] = 8
-	monthnum["Sep"] = 9
-	monthnum["Oct"] = 10
-	monthnum["Nov"] = 11
-	monthnum["Dec"] = 12
-}
-{
-	printf "%04d/%02d/%02d\n", $6, monthnum[$2], $3;
-}'
diff --git a/nickle.1.in b/nickle.1.in
index 7a6d88b..bff6d0e 100644
--- a/nickle.1.in
+++ b/nickle.1.in
@@ -1,4 +1,4 @@
-.TH NICKLE 1 "@BUILD_DATE@"
+.TH NICKLE 1 "@RELEASE_DATE@"
 .\" $Header$
 .SH NAME
 nickle \- a desk calculator language
commit 0da19be66deb245f83e17c6f2a998a7c7702835f
Author: Keith Packard <keithp at keithp.com>
Date:   Thu Jul 7 17:57:40 2016 +0200

    Add JSON input/output code
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/Makefile.am b/Makefile.am
index 2b31c0b..b4e11ce 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -13,7 +13,8 @@ NICKLEFILES = builtin.5c math.5c scanf.5c mutex.5c \
 	arc4.5c prng.5c command.5c abort.5c \
         printf.5c history.5c ctype.5c string.5c socket.5c \
 	file.5c parse-args.5c svg.5c process.5c \
-	prime_sieve.5c factorial.5c gamma.5c sort.5c list.5c skiplist.5c
+	prime_sieve.5c factorial.5c gamma.5c sort.5c list.5c skiplist.5c \
+	json.5c
 
 DEBIAN = debian/changelog debian/compat \
 	 debian/control debian/copyright debian/rules debian/lintian.override
diff --git a/json.5c b/json.5c
new file mode 100644
index 0000000..60c71fb
--- /dev/null
+++ b/json.5c
@@ -0,0 +1,293 @@
+/*
+ * Emit and parse JSON
+ */
+
+autoload Sort;
+autoload Ctype;
+
+namespace JSON {
+
+    string quote(string a) {
+	string result = "\"";
+	for (int i = 0; i < String::length(a); i++) {
+	    int c = a[i];
+	    switch (c) {
+	    case '"':
+	    case '\\':
+		result = result + "\\" + String::new(c);
+		break;
+	    case '\n':
+		result = result + "\\n";
+		break;
+	    default:
+		result = result + String::new(c);
+		break;
+	    }
+	}
+	return result + "\"";
+    }
+
+    public string to_json(poly arg)
+    {
+	string do_indent(int indent) {
+	    string result = "\n";
+	    for (int i = 0; i < indent; i++)
+		result = result + "\t";
+	    return result;
+	}
+
+	string json(poly arg, int indent);
+
+	string json_hash(poly[string] a, int indent) {
+	    string[] keys = hash_keys(a);
+	    string result = "{";
+	    bool first = true;
+
+	    Sort::qsort(&keys, bool func(poly a, poly b) { return a > b; });
+	    for (int i = 0; i < dim(keys); i++) {
+		string key = keys[i];
+		string value = json(a[key], indent+1);
+
+		if (!first)
+		    result = result + ",";
+		first = false;
+		result = result + do_indent(indent+1);
+		result = result + quote(key) + ": " + value;
+	    }
+	    result = result + do_indent(indent) + "}";
+	    return result;
+	}
+
+	string json_array(poly[] a, int indent) {
+	    string result = "[";
+	    bool first = true;
+
+	    for (int i = 0; i < dim(a); i++) {
+		if (!first)
+		    result = result + ",";
+		first = false;
+		result = result + do_indent(indent+1);
+		result = result + to_json(a[i]);
+	    }
+	    result = result + do_indent(indent) + "]";
+	    return result;
+	}
+
+	string json_string(string arg) {
+	    return quote(arg);
+	}
+
+	string json_int(int arg) {
+	    return sprintf ("%d", arg);
+	}
+
+	json = string func(poly arg, int indent) {
+	    if (is_hash(arg))
+		return json_hash(arg, indent);
+	    if (is_array(arg))
+		return json_array(arg, indent);
+	    if (is_string(arg))
+		return json_string(arg);
+	    if (is_int(arg))
+		return json_int(arg);
+	    return "";
+	};
+
+	return json(arg, 0);
+    }
+
+    typedef enum {
+	_string,
+	_int,
+	_oc,
+	_cc,
+	_os,
+	_cs,
+	_comma,
+	_colon,
+	_end,
+	_error
+    } term_t;
+
+    typedef struct {
+	union {
+	    int	ival;
+	    string	sval;
+	} val;
+	term_t	token;
+    } token_t;
+
+    typedef struct {
+	file	f;
+	int	line;
+	string	token;
+    } json_input_t;
+
+    json_input_t start_json(string s) {
+	return (json_input_t) { .f = File::string_read(s), .line = 1 };
+    }
+
+    token_t lex(*json_input_t f) {
+
+	int ch() {
+	    int c = File::getc(f->f);
+	    if (c == '\n')
+		f->line++;
+	    f->token = f->token + String::new(c);
+	    return c;
+	}
+
+	void unch(int c) {
+	    if (c == '\n')
+		f->line--;
+	    f->token = String::substr(f->token, 0, String::length(f->token) - 1);
+	    File::ungetc(c, f->f);
+	}
+
+	try {
+	    f->token = "";
+	    for (;;) {
+		int	c = ch();
+
+		switch (c) {
+		case '\n':
+		case ' ':
+		case '\t':
+		    continue;
+		case '{':
+		    return (token_t) { .token = term_t._oc };
+		case '}':
+		    return (token_t) { .token = term_t._cc };
+		case '[':
+		    return (token_t) { .token = term_t._os };
+		case ']':
+		    return (token_t) { .token = term_t._cs };
+		case ',':
+		    return (token_t) { .token = term_t._comma };
+		case ':':
+		    return (token_t) { .token = term_t._colon };
+		case '0': case '1': case '2': case '3': case '4':
+		case '5': case '6': case '7': case '8': case '9':
+		    int ival = 0;
+		    while (Ctype::isdigit(c)) {
+			ival = ival * 10 + c - '0';
+			c = ch();
+		    }
+		    unch(c);
+		    return (token_t) { .val = { .ival = ival }, .token = term_t._int };
+		case '"':
+		    string sval = "";
+		    for (;;) {
+			c = ch();
+			if (c == '"')
+			    break;
+			if (c == '\\') {
+			    c = ch();
+			    switch (c) {
+			    case 'n':
+				c = '\n';
+				break;
+			    case 't':
+				c = '\t';
+				break;
+			    default:
+				break;
+			    }
+			}
+			sval = sval + String::new(c);
+		    }
+		    return (token_t) { .val = { .sval = sval }, .token = term_t._string };
+		default:
+		    break;
+		}
+		break;
+	    }
+	}
+	catch File::io_eof(file f) {
+	    return (token_t) { .token = term_t._end };
+	}
+	return (token_t) { .token = term_t._error };
+    }
+
+    public exception json_parse_error(int line, string token);
+
+    public poly from_json(string arg) {
+	json_input_t f = start_json(arg);
+
+	token_t	token;
+
+	void next() {
+	    token = lex(&f);
+	}
+
+	void expect(term_t t) {
+	    if (token.token != t)
+		raise json_parse_error(f.line, f.token);
+	}
+
+	poly value();
+
+	poly[string] hash() {
+	    poly[string] ret = {};
+	    for (;;) {
+		expect(term_t._string);
+		string name = token.val.sval;
+		next();
+		expect(term_t._colon);
+		next();
+		poly val = value();
+		ret[name] = val;
+		switch (token.token) {
+		case term_t._comma:
+		    next();
+		    continue;
+		case term_t._cc:
+		    next();
+		    return ret;
+		default:
+		    raise json_parse_error(f.line, f.token);
+		}
+	    }
+	}
+
+	poly[] array() {
+	    poly[...] ret = {};
+	    for (;;) {
+		poly val = value();
+		ret[dim(ret)] = val;
+		switch (token.token) {
+		case term_t._comma:
+		    next();
+		    continue;
+		case term_t._cs:
+		    next();
+		    return ret;
+		default:
+		    raise json_parse_error(f.line, f.token);
+		}
+	    }
+	}
+
+	value = poly func() {
+	    switch (token.token) {
+	    case term_t._oc:
+	    next();
+	    return hash();
+	    case term_t._os:
+	    next();
+	    return array();
+	    case term_t._int:
+	    int ival = token.val.ival;
+	    next();
+	    return ival;
+	    case term_t._string:
+	    string sval = token.val.sval;
+	    next();
+	    return sval;
+	    }
+	};
+
+	next();
+	return value();
+    }
+}
commit a6a9375b89b3281b6ed670619e6d317a1d8af7e8
Author: Keith Packard <keithp at keithp.com>
Date:   Thu Jul 7 17:54:56 2016 +0200

    Reverse data written in linked FileChains
    
    FileChains are linked in newest-in order, so they need to be written
    last-first.
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/file.c b/file.c
index d088d44..a22e30b 100644
--- a/file.c
+++ b/file.c
@@ -916,6 +916,15 @@ FileStringWrite (void)
     RETURN (file);
 }
 
+static char *
+write_chain(char *s, FileChainPtr out)
+{
+    if (out->next)
+	s = write_chain(s, out->next);
+    memcpy(s, FileBuffer(out), out->used);
+    return s + out->used;
+}
+
 Value
 FileStringString (Value file)
 {
@@ -936,12 +945,8 @@ FileStringString (Value file)
     for (out = file->file.output; out; out = out->next)
 	len += out->used;
     str = NewString (len);
-    s = StringChars (&str->string);
-    for (out = file->file.output; out; out = out->next)
-    {
-	memcpy (s, FileBuffer(out), out->used);
-	s += out->used;
-    }
+    StringChars (&str->string);
+    s = write_chain(StringChars(&str->string), file->file.output);
     *s = '\0';
     RETURN (str);
 }
commit 18aa99c09b5be9561be9f908ed5a5aaee55e04e3
Author: Keith Packard <keithp at keithp.com>
Date:   Thu Jul 7 17:54:11 2016 +0200

    Add is_hash primitive
    
    Checks whether a value is a hash
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/builtin-toplevel.c b/builtin-toplevel.c
index 73c6573..825a66c 100644
--- a/builtin-toplevel.c
+++ b/builtin-toplevel.c
@@ -90,6 +90,10 @@ import_Toplevel_namespace()
 	    " bool is_func (poly v)\n"
 	    "\n"
 	    " Return whether 'v' is an func value.\n" },
+        { do_is_hash, "is_hash", "b", "p", "\n"
+	    " bool is_hash (poly v)\n"
+	    "\n"
+	    " Return whether 'v' is an hash value.\n" },
         { do_is_int, "is_int", "b", "p" , "\n"
 	    " bool is_int (poly v)\n"
 	    "\n"
@@ -880,6 +884,21 @@ do_is_array (Value av)
 }
 
 Value
+do_is_hash(Value av)
+{
+    ENTER();
+    switch(ValueTag(av)) {
+    case rep_hash:
+	av = TrueVal;
+	break;
+    default:
+	av = FalseVal;
+	break;
+    }
+    RETURN(av);
+}
+
+Value
 do_is_ref (Value av)
 {
     ENTER ();
diff --git a/nickle.h b/nickle.h
index cd2aba0..3c900e7 100644
--- a/nickle.h
+++ b/nickle.h
@@ -927,6 +927,7 @@ Value	do_is_thread (Value);
 Value	do_is_semaphore (Value);
 Value	do_is_continuation (Value);
 Value	do_is_array (Value);
+Value	do_is_hash (Value);
 Value	do_is_ref (Value);
 Value	do_is_struct (Value);
 Value	do_is_func (Value);
commit 7fd6e3c6727983c10098d92a469dd802c3196142
Author: Keith Packard <keithp at keithp.com>
Date:   Sat Jun 21 21:24:02 2014 -0700

    Abort karatsuba multiply in more places

diff --git a/natural.c b/natural.c
index 0a01535..6459cfb 100644
--- a/natural.c
+++ b/natural.c
@@ -420,6 +420,9 @@ DigitsKaratsuba (digit *x, int xlen, digit *y, int ylen, digit *result, digit *t
     int	    flen, m1len, m2len;
     int	    rlen;
     
+    if (aborting)
+	return 0;
+
     if (xlen < KARATSUBA_LIMIT || ylen < KARATSUBA_LIMIT)
 	return DigitsGradeSchool (x, xlen, y, ylen, result);
 
@@ -480,6 +483,8 @@ DigitsKaratsuba (digit *x, int xlen, digit *y, int ylen, digit *result, digit *t
     {
 	memset (result, 0, off * sizeof (digit));
 	rlen = DigitsKaratsuba (m1, m1len, m2, m2len, result + off, next_tmp) + off;
+	if (aborting)
+	    return rlen;
     }
     
     /*
@@ -488,6 +493,8 @@ DigitsKaratsuba (digit *x, int xlen, digit *y, int ylen, digit *result, digit *t
     if (x1len && y1len)
     {
 	flen = DigitsKaratsuba (x1, x1len, y1, y1len, f, next_tmp);
+	if (aborting)
+	    return rlen;
 	rlen = DigitsAddInPlace (result, rlen, f, flen, off2);
 	rlen = DigitsSubInPlace (result, rlen, f, flen, off);
     }
@@ -499,6 +506,8 @@ DigitsKaratsuba (digit *x, int xlen, digit *y, int ylen, digit *result, digit *t
     if (x0len && y0len)
     {
 	flen = DigitsKaratsuba (x0, x0len, y0, y0len, f, next_tmp);
+	if (aborting)
+	    return rlen;
 	rlen = DigitsAddInPlace (result, rlen, f, flen, 0);
 	rlen = DigitsSubInPlace (result, rlen, f, flen, off);
     }
@@ -583,6 +592,8 @@ NaturalTimes (Natural *a, Natural *b)
 	tmp_len = rlen << 3;
 	tmp = AllocateTemp (tmp_len * sizeof (digit));
 	rlen = DigitsKaratsuba (data(a), length (a), data(b), length (b), data(result), tmp);
+	if (aborting)
+	    RETURN(zero_natural);
 	tmp = data(result) + (rlen - 1);
 	while (rlen && *tmp == 0)
 	{
@@ -985,8 +996,14 @@ NaturalSprint (char *result, Natural *a, int base, int *width)
 	    divisors[idivisor++] = divisor;
 	    divisor = NaturalTimes (divisor, divisor);
 	    digits = digits * 2;
+	    if (aborting)
+	    {
+		r = 0;
+		break;
+	    }
 	} while (NaturalLess (divisor, a));
-	r = NaturalSplit (r, a, divisors + idivisor - 1, base, digits, False);
+	if (!aborting)
+	    r = NaturalSplit (r, a, divisors + idivisor - 1, base, digits, False);
 	free (divisors);
     }
     if (width && r)

From keithp at keithp.com  Wed Mar 15 21:56:27 2017
From: keithp at keithp.com (Keith Packard)
Date: Wed, 15 Mar 2017 21:56:27 -0700 (PDT)
Subject: [Nickle] nickle: Branch 'master' - 3 commits
Message-ID: <20170316045627.BB7AA3F202BC@elaine.keithp.com>


New commits:
commit 229a665691f244427e33793129eb76f3607790f9
Author: Keith Packard <keithp at keithp.com>
Date:   Wed Mar 15 09:40:34 2017 -0700

    Update to version 2.78
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/configure.ac b/configure.ac
index 3a4d01f..ba9d7ce 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4,10 +4,10 @@ dnl Copyright © 1988-2004 Keith Packard and Bart Massey.
 dnl All Rights Reserved.  See the file COPYING in this directory
 dnl for licensing information.
 
-AC_PREREQ([2.64])
+AC_PREREQ([2.69])
 
-AC_INIT([nickle],[2.77],[http://nickle.org],[nickle])
-RELEASE_DATE="2016-07-08"
+AC_INIT([nickle],[2.78],[http://nickle.org],[nickle])
+RELEASE_DATE="2017-03-15"
 AC_CONFIG_SRCDIR([nickle.h])
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_AUX_DIR(.)
diff --git a/debian/changelog b/debian/changelog
index 13a7111..d550556 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,14 @@
+nickle (2.78-1) unstable; urgency=low
+
+  * Add sudoku generator and solver example.
+  * Fix write order of large queued data.
+  * Add JSON input/output library.
+  * Use release date in configure.in instead of `date`
+  * Initialize 'replace' in NewTypedBox. (Closes: #857840, #857814, #857815)
+  * Adapt test to new automake.
+
+ -- Keith Packard <keithp at keithp.com>  Wed, 15 Mar 2017 09:35:37 -0700
+
 nickle (2.77-1) unstable; urgency=low
 
   * Build tutorial when docbook2pdf is available
commit 9dcb5baf7d263317f4fd4c8cd6a9695c2f3824b1
Author: Keith Packard <keithp at keithp.com>
Date:   Wed Mar 15 21:54:05 2017 -0700

    Also depend on bison and flex

diff --git a/debian/control b/debian/control
index 8d27a70..c285cfe 100644
--- a/debian/control
+++ b/debian/control
@@ -2,7 +2,7 @@ Source: nickle
 Section: interpreters
 Priority: optional
 Maintainer: Keith Packard <keithp at keithp.com>
-Build-Depends: debhelper (>= 7.0.50), libreadline-dev, autotools-dev, docbook-utils
+Build-Depends: debhelper (>= 7.0.50), libreadline-dev, autotools-dev, docbook-utils, bison, flex
 Standards-Version: 3.9.2
 
 Package: nickle
commit b568f99722b760810f209cdab5b5a29460c5d650
Author: Keith Packard <keithp at keithp.com>
Date:   Wed Mar 15 21:51:54 2017 -0700

    newer version of flex doesn't have yyunput
    
    This line was just removing a warning about yyunput unused; newer
    versions of flex don't even have that symbol anymore. Better to have a
    warning than fail to compile.
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/lex.l b/lex.l
index 91e2698..d31ca6f 100644
--- a/lex.l
+++ b/lex.l
@@ -700,7 +700,6 @@ lexdoc (void)
     int	    c;
     Value   s = NewStrString ("");
 
-    (void) yyunput;
     c = input();
     if (lexInput->at_eof)
     {

From keithp at keithp.com  Wed Mar 15 22:07:23 2017
From: keithp at keithp.com (Keith Packard)
Date: Wed, 15 Mar 2017 22:07:23 -0700 (PDT)
Subject: [Nickle] nickle: Branch 'master'
Message-ID: <20170316050723.197AC3F20899@elaine.keithp.com>


New commits:
commit f2037097c19ca6a7a9f4fad92448fe57dae34a48
Author: Keith Packard <keithp at keithp.com>
Date:   Wed Mar 15 22:06:33 2017 -0700

    Building nickle on arm64 now, change package names
    
    built .deb and .rpm files are now amd64 instead of i386
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/Makefile.am b/Makefile.am
index 70b19aa..4c09141 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -91,9 +91,9 @@ $(USES_GRAM_H): gram.h
 builtin.o main.o: Makefile
 
 TARFILE=$(PACKAGE)-$(VERSION).tar.gz
-DEBFILE=$(PACKAGE)_$(VERSION)-1_i386.deb
+DEBFILE=$(PACKAGE)_$(VERSION)-1_amd64.deb
 SRPMFILE=$(RPMDIR)/SRPMS/$(PACKAGE)-$(VERSION)-1.src.rpm
-RPMFILE=$(RPMDIR)/RPMS/$(PACKAGE)-$(VERSION)-1.i386.rpm
+RPMFILE=$(RPMDIR)/RPMS/$(PACKAGE)-$(VERSION)-1.x86_64.rpm
 RELEASE_FILES = $(TARFILE) $(DEBFILE) $(SRPMFILE) $(RPMFILE)
 DEB_TAR_DIR=$(PACKAGE)_$(VERSION).orig
 DEB_TAR=$(DEB_TAR_DIR).tar.gz

From keithp at keithp.com  Wed Mar 22 22:47:07 2017
From: keithp at keithp.com (Keith Packard)
Date: Wed, 22 Mar 2017 22:47:07 -0700 (PDT)
Subject: [Nickle] nickle: Changes to 'refs/tags/2.79'
Message-ID: <20170323054707.94DD53F20176@elaine.keithp.com>

Tag '2.79' created by Keith Packard <keithp at keithp.com> at 2017-03-23 06:46 -0700

Version 2.79
-----BEGIN PGP SIGNATURE-----


BzEvjiAAPSnV4V7vjZZlNv+/Y7L5q5nxkTwRUY5KbyeXChlp/VA=
=lmZk
-----END PGP SIGNATURE-----

Changes since 2.77-25:
---
---

From keithp at keithp.com  Wed Mar 22 22:47:07 2017
From: keithp at keithp.com (Keith Packard)
Date: Wed, 22 Mar 2017 22:47:07 -0700 (PDT)
Subject: [Nickle] nickle: Changes to 'refs/tags/2.78'
Message-ID: <20170323054707.9F1EC3F2079B@elaine.keithp.com>

Tag '2.78' created by Keith Packard <keithp at keithp.com> at 2017-03-23 06:46 -0700

Version 2.78
-----BEGIN PGP SIGNATURE-----


DcdbVdIk/bURS6KgQPaMCiz7BrE7cZEjLDKmDS8CaOKkzlRoQsc=
=ae2p
-----END PGP SIGNATURE-----

Changes since 2.77-22:
---
---

From keithp at keithp.com  Wed Mar 22 22:47:07 2017
From: keithp at keithp.com (Keith Packard)
Date: Wed, 22 Mar 2017 22:47:07 -0700 (PDT)
Subject: [Nickle] nickle: Branch 'master' - 2 commits
Message-ID: <20170323054707.750B33F2071F@elaine.keithp.com>


New commits:
commit 3d63abf4e3cc8089d8cd661fd824dc7f95a5a80e
Author: Keith Packard <keithp at keithp.com>
Date:   Thu Mar 23 06:45:53 2017 +0100

    Update to version 2.79
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/configure.ac b/configure.ac
index ba9d7ce..7a51231 100644
--- a/configure.ac
+++ b/configure.ac
@@ -6,8 +6,8 @@ dnl for licensing information.
 
 AC_PREREQ([2.69])
 
-AC_INIT([nickle],[2.78],[http://nickle.org],[nickle])
-RELEASE_DATE="2017-03-15"
+AC_INIT([nickle],[2.79],[http://nickle.org],[nickle])
+RELEASE_DATE="2017-03-16"
 AC_CONFIG_SRCDIR([nickle.h])
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_AUX_DIR(.)
diff --git a/debian/changelog b/debian/changelog
index d550556..7c3ad4c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+nickle (2.79-1) unstable; urgency=low
+
+  * Fix bus error on sparc64 due to unaligned DataCache.
+
+ -- Keith Packard <keithp at keithp.com>  Thu, 16 Mar 2017 10:52:49 -0700
+
 nickle (2.78-1) unstable; urgency=low
 
   * Add sudoku generator and solver example.
diff --git a/value.c b/value.c
index 7add3cd..8d60e8a 100644
--- a/value.c
+++ b/value.c
@@ -882,12 +882,12 @@ DataCacheMark (void *object)
 
 static DataType DataCacheType = { DataCacheMark, 0, "DataCacheType" };
 
-DataCachePtr 
-NewDataCache (int size)
+DataCachePtr
+NewDataCache (intptr_t size)
 {
     ENTER ();
     DataCachePtr   dc;
-    dc = (DataCachePtr) MemAllocate (&DataCacheType, 
+    dc = (DataCachePtr) MemAllocate (&DataCacheType,
 				      sizeof (DataCache) +
 				      size * sizeof (void *));
     dc->size = size;
commit fb25c7d70763848f4bbb110c747ece6a0a667c29
Author: Keith Packard <keithp at keithp.com>
Date:   Thu Mar 16 10:51:13 2017 -0700

    Ensure data cache value array is aligned adequately
    
    Data caches are used to store pointers and other potentially long
    values. Make sure that the position of the values portion of the cache
    is aligned to a suitable address. Fixes a bus error on sparc64 machines.
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/value.h b/value.h
index 8b7b538..365b18f 100644
--- a/value.h
+++ b/value.h
@@ -1013,11 +1013,15 @@ extern BoxPtr	NewBox (Bool constant, Bool array, int nvalues, TypePtr type);
 extern BoxPtr	NewTypedBox (Bool array, BoxTypesPtr types);
 void		BoxSetReplace (BoxPtr old, BoxPtr new, int oldstride, int newstride);
 BoxPtr		BoxRewrite (BoxPtr box, int *ep);
-			     
+
 typedef struct {
     DataType	*data;
     int		size;
-    char	values[0];
+    union {
+	double	d;
+	double_digit dd;
+	void	*p;
+    } values[0];
 } DataCache, *DataCachePtr;
 
 DataCachePtr	NewDataCache (int size);

From keithp at keithp.com  Wed Mar 22 23:52:04 2017
From: keithp at keithp.com (Keith Packard)
Date: Wed, 22 Mar 2017 23:52:04 -0700 (PDT)
Subject: [Nickle] nickle: Branch 'refs/tags/2.79'
Message-ID: <20170323065204.3596F3F200E5@elaine.keithp.com>

Rebased ref, commits from common ancestor:
commit 62538ae681138311fdb0d60419049dee291e4b4e
Author: Keith Packard <keithp at keithp.com>
Date:   Thu Mar 23 06:45:53 2017 +0100

    Update to version 2.79
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/configure.ac b/configure.ac
index ba9d7ce..7a51231 100644
--- a/configure.ac
+++ b/configure.ac
@@ -6,8 +6,8 @@ dnl for licensing information.
 
 AC_PREREQ([2.69])
 
-AC_INIT([nickle],[2.78],[http://nickle.org],[nickle])
-RELEASE_DATE="2017-03-15"
+AC_INIT([nickle],[2.79],[http://nickle.org],[nickle])
+RELEASE_DATE="2017-03-16"
 AC_CONFIG_SRCDIR([nickle.h])
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_AUX_DIR(.)
diff --git a/debian/changelog b/debian/changelog
index d550556..7c3ad4c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+nickle (2.79-1) unstable; urgency=low
+
+  * Fix bus error on sparc64 due to unaligned DataCache.
+
+ -- Keith Packard <keithp at keithp.com>  Thu, 16 Mar 2017 10:52:49 -0700
+
 nickle (2.78-1) unstable; urgency=low
 
   * Add sudoku generator and solver example.

From keithp at keithp.com  Wed Mar 22 23:52:03 2017
From: keithp at keithp.com (Keith Packard)
Date: Wed, 22 Mar 2017 23:52:03 -0700 (PDT)
Subject: [Nickle] nickle: Branch 'master' - 2 commits
Message-ID: <20170323065204.1A84F3F20150@elaine.keithp.com>

Rebased ref, commits from common ancestor:
commit b03fa4fcfe5e8bca6552c8be83eb5c67180b7f8a
Author: Keith Packard <keithp at keithp.com>
Date:   Thu Mar 23 07:49:45 2017 +0100

    Fix a pile of lintian warnings
    
    Update to standard 3.9.8 and dh 10.
    COPYING files from exmaples shouldn't be installed.
    Use 3.0 (quilt) format so we can have a packaging revision with dh 10.
    Get the tutorial installed correctly.
    Mark the sudoku example script executable.
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/debian/changelog b/debian/changelog
index 7c3ad4c..dd9e0be 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,13 @@
+nickle (2.79-2) unstable; urgency=low
+
+  * Update to standards 3.9.8
+  * Switch to 3.0 (quilt) format
+  * Update to dh version 10
+  * Avoid installing COPYING files which duplicate copyright data
+  * Make examples which are scripts executable
+
+ -- Keith Packard <keithp at keithp.com>  Thu, 23 Mar 2017 07:45:52 +0100
+
 nickle (2.79-1) unstable; urgency=low
 
   * Fix bus error on sparc64 due to unaligned DataCache.
diff --git a/debian/compat b/debian/compat
index 7f8f011..f599e28 100644
--- a/debian/compat
+++ b/debian/compat
@@ -1 +1 @@
-7
+10
diff --git a/debian/control b/debian/control
index c285cfe..fb1bff1 100644
--- a/debian/control
+++ b/debian/control
@@ -2,8 +2,8 @@ Source: nickle
 Section: interpreters
 Priority: optional
 Maintainer: Keith Packard <keithp at keithp.com>
-Build-Depends: debhelper (>= 7.0.50), libreadline-dev, autotools-dev, docbook-utils, bison, flex
-Standards-Version: 3.9.2
+Build-Depends: debhelper (>= 10), libreadline-dev, autotools-dev, docbook-utils, bison, flex
+Standards-Version: 3.9.8
 
 Package: nickle
 Architecture: any
diff --git a/debian/docs b/debian/docs
index 76399c4..8e882f4 100644
--- a/debian/docs
+++ b/debian/docs
@@ -5,4 +5,4 @@ TODO
 examples
 test
 bench
-tutorial.pdf
+doc/tutorial/nickle-tutorial.pdf
diff --git a/debian/rules b/debian/rules
index b8478dc..d6438fd 100755
--- a/debian/rules
+++ b/debian/rules
@@ -16,3 +16,11 @@ configure_flags = --mandir='$${prefix}/share/man'
 
 override_dh_auto_configure:
 	dh_auto_configure -- $(configure_flags)
+
+
+# Don't install duplicate copyright info from COPYING files
+# Make examples which happen to start with #! executable
+
+override_dh_auto_install:
+	dh_auto_install && (find debian/nickle -name COPYING -print0 | xargs -0 rm)
+	find debian/nickle/usr/share/nickle -type f -print0 | xargs -0 grep -l '^#!.*nickle' | xargs -d '\n' chmod +x
diff --git a/debian/source/format b/debian/source/format
index 89ae9db..163aaf8 100644
--- a/debian/source/format
+++ b/debian/source/format
@@ -1 +1 @@
-3.0 (native)
+3.0 (quilt)
commit 62538ae681138311fdb0d60419049dee291e4b4e
Author: Keith Packard <keithp at keithp.com>
Date:   Thu Mar 23 06:45:53 2017 +0100

    Update to version 2.79
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/configure.ac b/configure.ac
index ba9d7ce..7a51231 100644
--- a/configure.ac
+++ b/configure.ac
@@ -6,8 +6,8 @@ dnl for licensing information.
 
 AC_PREREQ([2.69])
 
-AC_INIT([nickle],[2.78],[http://nickle.org],[nickle])
-RELEASE_DATE="2017-03-15"
+AC_INIT([nickle],[2.79],[http://nickle.org],[nickle])
+RELEASE_DATE="2017-03-16"
 AC_CONFIG_SRCDIR([nickle.h])
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_AUX_DIR(.)
diff --git a/debian/changelog b/debian/changelog
index d550556..7c3ad4c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+nickle (2.79-1) unstable; urgency=low
+
+  * Fix bus error on sparc64 due to unaligned DataCache.
+
+ -- Keith Packard <keithp at keithp.com>  Thu, 16 Mar 2017 10:52:49 -0700
+
 nickle (2.78-1) unstable; urgency=low
 
   * Add sudoku generator and solver example.

From keithp at keithp.com  Wed Mar 22 23:58:15 2017
From: keithp at keithp.com (Keith Packard)
Date: Wed, 22 Mar 2017 23:58:15 -0700 (PDT)
Subject: [Nickle] nickle: Changes to 'refs/tags/2.79-2'
Message-ID: <20170323065815.DB9A13F20290@elaine.keithp.com>

Tag '2.79-2' created by Keith Packard <keithp at keithp.com> at 2017-03-23 07:57 -0700

Version 2.79-2
-----BEGIN PGP SIGNATURE-----


sZOamYs94dAReBQAOrAP2ApVPfeFMqiGL3zINz4FPxNg8QgrtGw=
=XyVN
-----END PGP SIGNATURE-----

Changes since 2.79:
Keith Packard (1):
      Fix a pile of lintian warnings

---
 debian/changelog     |   10 ++++++++++
 debian/compat        |    2 +-
 debian/control       |    4 ++--
 debian/docs          |    2 +-
 debian/rules         |    8 ++++++++
 debian/source/format |    2 +-
 6 files changed, 23 insertions(+), 5 deletions(-)
---

From keithp at keithp.com  Mon Mar 27 12:58:24 2017
From: keithp at keithp.com (Keith Packard)
Date: Mon, 27 Mar 2017 12:58:24 -0700 (PDT)
Subject: [Nickle] nickle: Branch 'master' - 3 commits
Message-ID: <20170327195824.BF8973F20680@elaine.keithp.com>

 doc/tutorial/Makefile.am |   11 ++++++-
 json.5c                  |   70 ++++++++++++++++++++++++++++++++++++-----------
 test/Makefile.am         |    3 +-
 test/jsontest.5c         |   25 ++++++++++++++++
 4 files changed, 91 insertions(+), 18 deletions(-)

New commits:
commit 0880d7445ba518ef17a8fb3febc4489734995418
Author: Keith Packard <keithp at keithp.com>
Date:   Fri Mar 24 09:45:49 2017 +0100

    Add JSON tests
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/test/Makefile.am b/test/Makefile.am
index 683e5a8..7794477 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -12,7 +12,8 @@ check_SCRIPTS=\
 	round.5c \
 	math.5c \
 	factorial.5c \
-	is_type.5c
+	is_type.5c \
+	jsontest.5c
 
 noinst_PROGRAMS=math-tables
 
diff --git a/test/jsontest.5c b/test/jsontest.5c
new file mode 100644
index 0000000..a7aa0fa
--- /dev/null
+++ b/test/jsontest.5c
@@ -0,0 +1,25 @@
+autoimport JSON;
+
+exception bad_json(poly val, string json, poly got);
+
+void json_test_val(poly val, string match) {
+    string	json = to_json(val);
+    poly	t = from_json(json);
+
+    /* Make sure the string representation is correct and parsing
+     * matches the input
+     */
+    if (json != match || val != t)
+	raise bad_json(val, json, t);
+}
+
+json_test_val(1, "1");
+json_test_val(1.1, "1.1000000000000");
+json_test_val("hello world", "\"hello world\"");
+json_test_val((poly[string]) {
+	"float" => 1.1,
+	"hash" => (poly[string]) { "hashval" => 12 },
+	"int" => 10,
+	"string" => "world"
+    },
+    "{\n\t\"float\": 1.1000000000000,\n\t\"hash\": {\n\t\t\"hashval\": 12\n\t},\n\t\"int\": 10,\n\t\"string\": \"world\"\n}");
commit 0240e97419536a7a5474afae077b42fd49725100
Author: Keith Packard <keithp at keithp.com>
Date:   Fri Mar 24 09:45:12 2017 +0100

    Add floats to JSON module.
    
    Support floating point values in JSON input and output.
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/json.5c b/json.5c
index 60c71fb..3e9fe9d 100644
--- a/json.5c
+++ b/json.5c
@@ -81,6 +81,10 @@ namespace JSON {
 	    return sprintf ("%d", arg);
 	}
 
+	string json_float(real arg) {
+	    return sprintf("%.13g", arg);
+	}
+
 	json = string func(poly arg, int indent) {
 	    if (is_hash(arg))
 		return json_hash(arg, indent);
@@ -90,6 +94,8 @@ namespace JSON {
 		return json_string(arg);
 	    if (is_int(arg))
 		return json_int(arg);
+	    if (is_number(arg))
+		return json_float(arg);
 	    return "";
 	};
 
@@ -99,6 +105,7 @@ namespace JSON {
     typedef enum {
 	_string,
 	_int,
+	_float,
 	_oc,
 	_cc,
 	_os,
@@ -111,8 +118,9 @@ namespace JSON {
 
     typedef struct {
 	union {
-	    int	ival;
+	    int		ival;
 	    string	sval;
+	    real	fval;
 	} val;
 	term_t	token;
     } token_t;
@@ -144,6 +152,26 @@ namespace JSON {
 	    File::ungetc(c, f->f);
 	}
 
+	bool is_int_char(int c) {
+	    if (Ctype::isdigit(c))
+		return true;
+	    if (c == '-')
+		return true;
+	    return false;
+	}
+
+	bool is_float_char(int c) {
+	    if (Ctype::isdigit(c))
+		return true;
+	    if (c == '-')
+		return true;
+	    if (c == '.')
+		return true;
+	    if (c == 'e' || c == 'E')
+		return true;
+	    return false;
+	}
+
 	try {
 	    f->token = "";
 	    for (;;) {
@@ -168,13 +196,21 @@ namespace JSON {
 		    return (token_t) { .token = term_t._colon };
 		case '0': case '1': case '2': case '3': case '4':
 		case '5': case '6': case '7': case '8': case '9':
-		    int ival = 0;
-		    while (Ctype::isdigit(c)) {
-			ival = ival * 10 + c - '0';
+		case '-': case '.':
+		    string nval = "";
+		    bool has_float = false;
+		    while (is_float_char(c)) {
+			nval = nval + String::new(c);
+			if (!is_int_char(c))
+			    has_float = true;
+			if (File::end(f->f))
+			    break;
 			c = ch();
 		    }
 		    unch(c);
-		    return (token_t) { .val = { .ival = ival }, .token = term_t._int };
+		    if (has_float)
+			return (token_t) { .val = { .fval = atof(nval) }, .token = term_t._float };
+		    return (token_t) { .val = { .ival = atoi(nval) }, .token = term_t._int };
 		case '"':
 		    string sval = "";
 		    for (;;) {
@@ -271,19 +307,23 @@ namespace JSON {
 	value = poly func() {
 	    switch (token.token) {
 	    case term_t._oc:
-	    next();
-	    return hash();
+		    next();
+	    	return hash();
 	    case term_t._os:
-	    next();
-	    return array();
+		    next();
+		    return array();
 	    case term_t._int:
-	    int ival = token.val.ival;
-	    next();
-	    return ival;
+		    int ival = token.val.ival;
+		    next();
+		    return ival;
+	    case term_t._float:
+		    real fval = token.val.fval;
+		    next();
+		    return fval;
 	    case term_t._string:
-	    string sval = token.val.sval;
-	    next();
-	    return sval;
+		    string sval = token.val.sval;
+		    next();
+		    return sval;
 	    }
 	};
 
commit 8064554410bc05798fe7a8cafa80da0fdec16b87
Author: Keith Packard <keithp at keithp.com>
Date:   Thu Mar 23 14:38:10 2017 +0100

    Make nickle-tutorial.pdf build reproducibly
    
    Set TeX dates to RELEASE_DATE.
    Remove PDF /ID entry.
    
    Signed-off-by: Keith Packard <keithp at keithp.com>

diff --git a/doc/tutorial/Makefile.am b/doc/tutorial/Makefile.am
index baf586b..e4a1bd0 100644
--- a/doc/tutorial/Makefile.am
+++ b/doc/tutorial/Makefile.am
@@ -2,8 +2,15 @@
 SGMLFILES=nickle-tutorial.sgml
 PDFFILES=$(SGMLFILES:.sgml=.pdf)
 
+PDF_DATE=$(shell date -ud '$(RELEASE_DATE)')
+
 .sgml.pdf:
-	docbook2pdf $<
+	docbook2tex $< && \
+	pdfjadetex '\pdfinfo{/CreationDate($(PDF_DATE))/ModDate($(PDF_DATE))}\input{$*.tex}' >/dev/null && \
+	pdfjadetex '\pdfinfo{/CreationDate($(PDF_DATE))/ModDate($(PDF_DATE))}\input{$*.tex}' >/dev/null && \
+	pdfjadetex '\pdfinfo{/CreationDate($(PDF_DATE))/ModDate($(PDF_DATE))}\input{$*.tex}' >/dev/null && \
+	sed -i '/^\/ID \[<[0-9A-F][0-9A-F]*> <[0-9A-F][0-9A-F]*>\]/d' $@ && \
+	rm $*.aux $*.log *$.out *$.tex
 
 SGMLINC= \
 	tour/tour.sgml \
@@ -36,4 +43,4 @@ $(PDFFILES): $(SGMLINC)
 
 doc_DATA = $(PDFFILES)
 
-endif
\ No newline at end of file
+endif

