checkin of 0.3.0
authorJohn Darrington <john@darrington.wattle.id.au>
Wed, 10 Dec 2003 23:27:28 +0000 (23:27 +0000)
committerJohn Darrington <john@darrington.wattle.id.au>
Wed, 10 Dec 2003 23:27:28 +0000 (23:27 +0000)
334 files changed:
AUTHORS [new file with mode: 0644]
COPYING [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
NEWS [new file with mode: 0644]
ONEWS [new file with mode: 0644]
README [new file with mode: 0644]
THANKS [new file with mode: 0644]
TODO [new file with mode: 0644]
acconfig.h [new file with mode: 0644]
acinclude.m4 [new file with mode: 0644]
config/ChangeLog [new file with mode: 0644]
config/Makefile.am [new file with mode: 0644]
config/devices [new file with mode: 0644]
config/html-prologue [new file with mode: 0644]
config/papersize [new file with mode: 0644]
config/ps-prologue [new file with mode: 0644]
configure.in [new file with mode: 0644]
doc/ChangeLog [new file with mode: 0644]
doc/Makefile.am [new file with mode: 0644]
doc/mdate-sh [new file with mode: 0755]
doc/pspp.man [new file with mode: 0644]
doc/pspp.texi [new file with mode: 0644]
doc/texinfo.tex [new file with mode: 0644]
examples/ChangeLog [new file with mode: 0644]
examples/descript.stat [new file with mode: 0644]
intl/ChangeLog [new file with mode: 0644]
intl/Makefile.in [new file with mode: 0644]
intl/VERSION [new file with mode: 0644]
intl/bindtextdom.c [new file with mode: 0644]
intl/cat-compat.c [new file with mode: 0644]
intl/dcgettext.c [new file with mode: 0644]
intl/dgettext.c [new file with mode: 0644]
intl/explodename.c [new file with mode: 0644]
intl/finddomain.c [new file with mode: 0644]
intl/gettext.c [new file with mode: 0644]
intl/gettext.h [new file with mode: 0644]
intl/gettextP.h [new file with mode: 0644]
intl/hash-string.h [new file with mode: 0644]
intl/intl-compat.c [new file with mode: 0644]
intl/l10nflist.c [new file with mode: 0644]
intl/libgettext.h [new file with mode: 0644]
intl/linux-msg.sed [new file with mode: 0644]
intl/loadinfo.h [new file with mode: 0644]
intl/loadmsgcat.c [new file with mode: 0644]
intl/localealias.c [new file with mode: 0644]
intl/po2tbl.sed.in [new file with mode: 0644]
intl/textdomain.c [new file with mode: 0644]
intl/xopen-msg.sed [new file with mode: 0644]
lib/ChangeLog [new file with mode: 0644]
lib/Makefile.am [new file with mode: 0644]
lib/dcdflib/COPYING [new file with mode: 0644]
lib/dcdflib/ChangeLog [new file with mode: 0644]
lib/dcdflib/Makefile.am [new file with mode: 0644]
lib/dcdflib/README [new file with mode: 0644]
lib/dcdflib/cdflib.h [new file with mode: 0644]
lib/dcdflib/dcdflib.c [new file with mode: 0644]
lib/dcdflib/ipmpar.c [new file with mode: 0644]
lib/gmp/COPYING.LIB [new file with mode: 0644]
lib/gmp/ChangeLog [new file with mode: 0644]
lib/gmp/INSTALL [new file with mode: 0644]
lib/gmp/Makefile.am [new file with mode: 0644]
lib/gmp/extract-dbl.c [new file with mode: 0644]
lib/gmp/gmp-impl.h [new file with mode: 0644]
lib/gmp/gmp-mparam.h [new file with mode: 0644]
lib/gmp/gmp.h [new file with mode: 0644]
lib/gmp/longlong.h [new file with mode: 0644]
lib/gmp/memory.c [new file with mode: 0644]
lib/gmp/mp_clz_tab.c [new file with mode: 0644]
lib/gmp/mpf/Makefile.am [new file with mode: 0644]
lib/gmp/mpf/clear.c [new file with mode: 0644]
lib/gmp/mpf/get_str.c [new file with mode: 0644]
lib/gmp/mpf/iset_d.c [new file with mode: 0644]
lib/gmp/mpf/set_d.c [new file with mode: 0644]
lib/gmp/mpf/set_dfl_prec.c [new file with mode: 0644]
lib/gmp/mpn/Makefile.am [new file with mode: 0644]
lib/gmp/mpn/add_n.c [new file with mode: 0644]
lib/gmp/mpn/addmul_1.c [new file with mode: 0644]
lib/gmp/mpn/cmp.c [new file with mode: 0644]
lib/gmp/mpn/divrem.c [new file with mode: 0644]
lib/gmp/mpn/get_str.c [new file with mode: 0644]
lib/gmp/mpn/inlines.c [new file with mode: 0644]
lib/gmp/mpn/lshift.c [new file with mode: 0644]
lib/gmp/mpn/mp_bases.c [new file with mode: 0644]
lib/gmp/mpn/mul.c [new file with mode: 0644]
lib/gmp/mpn/mul_1.c [new file with mode: 0644]
lib/gmp/mpn/mul_n.c [new file with mode: 0644]
lib/gmp/mpn/sub_n.c [new file with mode: 0644]
lib/gmp/mpn/submul_1.c [new file with mode: 0644]
lib/julcal/ChangeLog [new file with mode: 0644]
lib/julcal/Makefile.am [new file with mode: 0644]
lib/julcal/README [new file with mode: 0644]
lib/julcal/julcal.c [new file with mode: 0644]
lib/julcal/julcal.h [new file with mode: 0644]
lib/misc/ChangeLog [new file with mode: 0644]
lib/misc/Makefile.am [new file with mode: 0644]
lib/misc/alloca.c [new file with mode: 0644]
lib/misc/getdelim.c [new file with mode: 0644]
lib/misc/getline.c [new file with mode: 0644]
lib/misc/getopt.c [new file with mode: 0644]
lib/misc/getopt1.c [new file with mode: 0644]
lib/misc/memchr.c [new file with mode: 0644]
lib/misc/memcmp.c [new file with mode: 0644]
lib/misc/memcpy.c [new file with mode: 0644]
lib/misc/memmem.c [new file with mode: 0644]
lib/misc/memmove.c [new file with mode: 0644]
lib/misc/memset.c [new file with mode: 0644]
lib/misc/qsort.c [new file with mode: 0644]
lib/misc/stpcpy.c [new file with mode: 0644]
lib/misc/strcasecmp.c [new file with mode: 0644]
lib/misc/strerror.c [new file with mode: 0644]
lib/misc/strncasecmp.c [new file with mode: 0644]
lib/misc/strpbrk.c [new file with mode: 0644]
lib/misc/strstr.c [new file with mode: 0644]
lib/misc/strtok_r.c [new file with mode: 0644]
lib/misc/strtol.c [new file with mode: 0644]
lib/misc/strtoul.c [new file with mode: 0644]
po/ChangeLog [new file with mode: 0644]
po/Makefile.in.in [new file with mode: 0644]
po/POTFILES.in [new file with mode: 0644]
po/pspp.pot [new file with mode: 0644]
pref.h.orig [new file with mode: 0644]
reconfigure [new file with mode: 0755]
src/ChangeLog [new file with mode: 0644]
src/Makefile.am [new file with mode: 0644]
src/aggregate.c [new file with mode: 0644]
src/alloc.c [new file with mode: 0644]
src/alloc.h [new file with mode: 0644]
src/apply-dict.c [new file with mode: 0644]
src/approx.h [new file with mode: 0644]
src/ascii.c [new file with mode: 0644]
src/autorecode.c [new file with mode: 0644]
src/avl.c [new file with mode: 0644]
src/avl.h [new file with mode: 0644]
src/bitvector.h [new file with mode: 0644]
src/cases.c [new file with mode: 0644]
src/cases.h [new file with mode: 0644]
src/cmdline.c [new file with mode: 0644]
src/command.c [new file with mode: 0644]
src/command.def [new file with mode: 0644]
src/command.h [new file with mode: 0644]
src/compute.c [new file with mode: 0644]
src/correlations.q [new file with mode: 0644]
src/count.c [new file with mode: 0644]
src/crosstabs.q [new file with mode: 0644]
src/data-in.c [new file with mode: 0644]
src/data-in.h [new file with mode: 0644]
src/data-list.c [new file with mode: 0644]
src/data-out.c [new file with mode: 0644]
src/debug-print.h [new file with mode: 0644]
src/descript.q [new file with mode: 0644]
src/dfm.c [new file with mode: 0644]
src/dfm.h [new file with mode: 0644]
src/do-if.c [new file with mode: 0644]
src/do-ifP.h [new file with mode: 0644]
src/error.c [new file with mode: 0644]
src/error.h [new file with mode: 0644]
src/expr-evl.c [new file with mode: 0644]
src/expr-opt.c [new file with mode: 0644]
src/expr-prs.c [new file with mode: 0644]
src/expr.h [new file with mode: 0644]
src/exprP.h [new file with mode: 0644]
src/file-handle.h [new file with mode: 0644]
src/file-handle.q [new file with mode: 0644]
src/file-type.c [new file with mode: 0644]
src/filename.c [new file with mode: 0644]
src/filename.h [new file with mode: 0644]
src/flip.c [new file with mode: 0644]
src/font.h [new file with mode: 0644]
src/format.c [new file with mode: 0644]
src/format.def [new file with mode: 0644]
src/format.h [new file with mode: 0644]
src/formats.c [new file with mode: 0644]
src/frequencies.g [new file with mode: 0644]
src/frequencies.q [new file with mode: 0644]
src/get.c [new file with mode: 0644]
src/getline.c [new file with mode: 0644]
src/getline.h [new file with mode: 0644]
src/glob.c [new file with mode: 0644]
src/groff-font.c [new file with mode: 0644]
src/hash.c [new file with mode: 0644]
src/hash.h [new file with mode: 0644]
src/heap.c [new file with mode: 0644]
src/heap.h [new file with mode: 0644]
src/html.c [new file with mode: 0644]
src/htmlP.h [new file with mode: 0644]
src/include.c [new file with mode: 0644]
src/inpt-pgm.c [new file with mode: 0644]
src/inpt-pgm.h [new file with mode: 0644]
src/lexer.c [new file with mode: 0644]
src/lexer.h [new file with mode: 0644]
src/list.q [new file with mode: 0644]
src/log.h [new file with mode: 0644]
src/loop.c [new file with mode: 0644]
src/magic.c [new file with mode: 0644]
src/magic.h [new file with mode: 0644]
src/main.c [new file with mode: 0644]
src/main.h [new file with mode: 0644]
src/matrix-data.c [new file with mode: 0644]
src/matrix.c [new file with mode: 0644]
src/matrix.h [new file with mode: 0644]
src/means.q [new file with mode: 0644]
src/mis-val.c [new file with mode: 0644]
src/misc.c [new file with mode: 0644]
src/misc.h [new file with mode: 0644]
src/modify-vars.c [new file with mode: 0644]
src/numeric.c [new file with mode: 0644]
src/output.c [new file with mode: 0644]
src/output.h [new file with mode: 0644]
src/pfm-read.c [new file with mode: 0644]
src/pfm-write.c [new file with mode: 0644]
src/pfm.h [new file with mode: 0644]
src/pool.c [new file with mode: 0644]
src/pool.h [new file with mode: 0644]
src/postscript.c [new file with mode: 0644]
src/print.c [new file with mode: 0644]
src/q2c.c [new file with mode: 0644]
src/random.c [new file with mode: 0644]
src/random.h [new file with mode: 0644]
src/recode.c [new file with mode: 0644]
src/rename-vars.c [new file with mode: 0644]
src/repeat.c [new file with mode: 0644]
src/sample.c [new file with mode: 0644]
src/sel-if.c [new file with mode: 0644]
src/set.q [new file with mode: 0644]
src/settings.h [new file with mode: 0644]
src/sfm-read.c [new file with mode: 0644]
src/sfm-write.c [new file with mode: 0644]
src/sfm.h [new file with mode: 0644]
src/sfmP.h [new file with mode: 0644]
src/som.c [new file with mode: 0644]
src/som.h [new file with mode: 0644]
src/sort.c [new file with mode: 0644]
src/sort.h [new file with mode: 0644]
src/split-file.c [new file with mode: 0644]
src/stat.h [new file with mode: 0644]
src/stats.c [new file with mode: 0644]
src/stats.h [new file with mode: 0644]
src/str.c [new file with mode: 0644]
src/str.h [new file with mode: 0644]
src/sysfile-info.c [new file with mode: 0644]
src/t-test.q [new file with mode: 0644]
src/tab.c [new file with mode: 0644]
src/tab.h [new file with mode: 0644]
src/temporary.c [new file with mode: 0644]
src/title.c [new file with mode: 0644]
src/val-labs.c [new file with mode: 0644]
src/var-labs.c [new file with mode: 0644]
src/var.h [new file with mode: 0644]
src/vars-atr.c [new file with mode: 0644]
src/vars-prs.c [new file with mode: 0644]
src/vector.c [new file with mode: 0644]
src/vector.h [new file with mode: 0644]
src/version.h [new file with mode: 0644]
src/vfm.c [new file with mode: 0644]
src/vfm.h [new file with mode: 0644]
src/vfmP.h [new file with mode: 0644]
src/weight.c [new file with mode: 0644]
stamp-h.in [new file with mode: 0644]
sysdeps/ChangeLog [new file with mode: 0644]
sysdeps/README [new file with mode: 0644]
sysdeps/borlandc5.0/ChangeLog [new file with mode: 0644]
sysdeps/borlandc5.0/bc5-con32s.c [new file with mode: 0644]
sysdeps/borlandc5.0/config.h [new file with mode: 0644]
sysdeps/borlandc5.0/libintl.h [new file with mode: 0644]
sysdeps/borlandc5.0/mk-bc5-dist [new file with mode: 0755]
sysdeps/borlandc5.0/pspp.ico [new file with mode: 0755]
sysdeps/borlandc5.0/pspp.ide [new file with mode: 0644]
sysdeps/borlandc5.0/pspp.iwz.in [new file with mode: 0755]
sysdeps/borlandc5.0/setup1.bmp [new file with mode: 0755]
sysdeps/borlandc5.0/sm-gnu-hd.bmp [new file with mode: 0755]
sysdeps/borlandc5.0/unix2dos.pl [new file with mode: 0644]
sysdeps/borlandc5.0/version.c [new file with mode: 0644]
sysdeps/windows/README [new file with mode: 0644]
sysdeps/windows/con32s.c [new file with mode: 0644]
tests/ChangeLog [new file with mode: 0644]
tests/Makefile.am [new file with mode: 0644]
tests/aggregate.stat [new file with mode: 0644]
tests/autorecod.stat [new file with mode: 0644]
tests/beg-data.stat [new file with mode: 0644]
tests/bignum.data [new file with mode: 0644]
tests/bignum.stat [new file with mode: 0644]
tests/compute.stat [new file with mode: 0644]
tests/count.stat [new file with mode: 0644]
tests/crosstabs.stat [new file with mode: 0644]
tests/data-fmts.stat [new file with mode: 0644]
tests/data-list.data [new file with mode: 0644]
tests/data-list.stat [new file with mode: 0644]
tests/descript.stat [new file with mode: 0644]
tests/do-if.stat [new file with mode: 0644]
tests/do-repeat.stat [new file with mode: 0644]
tests/expect/crosstabs.stat [new file with mode: 0644]
tests/expect/data-fmts.stat [new file with mode: 0644]
tests/expect/data-list.stat [new file with mode: 0644]
tests/expect/expr.stat [new file with mode: 0644]
tests/expect/loop.stat [new file with mode: 0644]
tests/expect/mdfy-vars.stat [new file with mode: 0644]
tests/expect/means.stat [new file with mode: 0644]
tests/expect/print.stat [new file with mode: 0644]
tests/expect/t-test.stat [new file with mode: 0644]
tests/expect/vector.stat [new file with mode: 0644]
tests/expect/weighting.stat [new file with mode: 0644]
tests/expr.stat [new file with mode: 0644]
tests/file-lab.stat [new file with mode: 0644]
tests/filter.stat [new file with mode: 0644]
tests/flip.stat [new file with mode: 0644]
tests/gengarbage.c [new file with mode: 0644]
tests/inpt-pgm.stat [new file with mode: 0644]
tests/lag.stat [new file with mode: 0644]
tests/list.data [new file with mode: 0644]
tests/list.stat [new file with mode: 0644]
tests/loop.stat [new file with mode: 0644]
tests/mdfy-vars.stat [new file with mode: 0644]
tests/means.stat [new file with mode: 0644]
tests/mtch-file.stat [new file with mode: 0644]
tests/pcs-if.stat [new file with mode: 0644]
tests/print.stat [new file with mode: 0644]
tests/recode.stat [new file with mode: 0644]
tests/repeating.stat [new file with mode: 0644]
tests/reread.data [new file with mode: 0644]
tests/reread.stat [new file with mode: 0644]
tests/sample.stat [new file with mode: 0644]
tests/sort.stat [new file with mode: 0644]
tests/splt-file.stat [new file with mode: 0644]
tests/syntax [new file with mode: 0755]
tests/sys-info.stat [new file with mode: 0644]
tests/t-test.stat [new file with mode: 0644]
tests/tabs.stat [new file with mode: 0644]
tests/temporary.stat [new file with mode: 0644]
tests/time-date.stat [new file with mode: 0644]
tests/vector.stat [new file with mode: 0644]
tests/weighting.data [new file with mode: 0644]
tests/weighting.stat [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..0262209
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,7 @@
+                                 PSPP Authors
+                                       
+     * Ben Pfaff wrote most of the program and the manual.
+     * John Williams wrote the T-TEST procedure.
+     * Jim Van Zandt translated the `julcal' date calculation package
+       into C from code written by Michael Covington, which was based on
+       formulae by Jean Meeus.
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..e77696a
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                          675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..7ee9ca1
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,1751 @@
+Sun Jan  2 21:24:32 2000  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Require Automake 1.4 or later.  It's been out for
+       almost a year now, so why haven't you installed it? :-)
+
+       * TODO: Updated.
+
+       * configure.in: Updated version number.  Check for libgmp.  Check
+       of fenv.h.  Check for feholdexpect().
+
+       * pref.h.orig: Don't include debug-print.h by default.  Don't
+       disable __attribute ((unused))__ for gcc 2.7.2.  Remove LOAD_2,
+       STORE_2.  Comment fixes.
+
+       * Updated copyright notices in all files.
+
+Fri Mar 12 12:38:55 1999  Ben Pfaff  <blp@gnu.org>
+
+       * Forked 0.3.0.
+
+Tue Mar  9 12:46:31 1999  Ben Pfaff  <blp@gnu.org>
+
+       * Released 0.2.3.
+       
+       * TODO: Updated.
+
+Tue Jan  5 15:18:07 1999  Ben Pfaff  <blp@gnu.org>
+
+       * Released 0.2.2.
+
+       * TODO: Update from Zvi Grauer <z.grauer@sims.csuohio.edu>.
+
+Thu Nov 19 12:34:55 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Released 0.2.1.
+
+Sun Aug  9 11:11:32 1998  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE: Updated.
+
+Sat Aug  8 00:19:08 1998  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE: Updated.
+
+       * examples/: New directory.
+
+       * Made patchlevel 95.
+
+Tue Aug  4 23:47:31 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Bump version to 0.1.22 (0.2.0 release candidate).
+
+       * configure.in: Remove --enable-Werror, new option
+       --enable-debugging.  New gcc option -Wpointer-arith.
+
+       * pref.h.orig: Don't enable debugging by default (now a configure
+       option).  Use __inline__ instead of inline with gcc (partial -ansi
+       -pedantic support).
+       (macro local_strdup) Removed.
+
+       * Made patchlevel 94.
+       
+Wed Jul 29 22:03:11 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Bump version to 0.1.21 (0.2.0 release candidate).
+
+       * debian/: Removed.
+
+       * Makefile.am: Don't copy debian/ into distribution.
+       
+       * pref.h.orig: Only enable `unused' attribute if gcc 2.8.0 or
+       later is used.
+
+Sun Jul  5 14:20:04 1998  Ben Pfaff  <blp@gnu.org>
+
+       * configure.in: Bump version to 0.1.20 (0.2.0 release candidate).
+
+       * Made patchlevel 93.
+
+Sun Jul  5 00:13:58 1998  Ben Pfaff  <blp@gnu.org>
+
+       * README: Updated.
+
+       * TODO: Updated.
+
+       * configure.in: Remove -Wno-unused from default gcc flags.
+
+       * pref.h.orig: Add new #define, `unused', which under gcc expands
+       to an explanation to the compiler that a function argument is
+       unused, and expands to the null string under other compilers.
+
+Mon Jun  1 14:33:02 1998  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE: Updated.
+
+       * configure.in: Bump version to 0.1.19.
+
+       * Made patchlevel 92.
+
+Sun May 31 00:55:13 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+               
+       * configure.in: Generate Makefiles for lib/gmp/{,mpn,mpf}/.
+
+       * Made patchlevel 91.
+
+Fri May 29 21:43:09 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * LANGUAGE: Updated.
+
+       * unconfigure: Remove TeX cruft from doc/.
+       
+       * Made patchlevel 90.
+
+Mon May 25 12:41:54 1998  Ben Pfaff  <blp@gnu.org>
+
+       * BUGS: Updated.
+
+       * LANGUAGE: Updated.
+
+       * TODO: Updated.
+
+       * configure.in: Bumped version number up to 0.1.18.
+
+       * Made patchlevel 89.
+
+Sun May 24 22:39:55 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * Made patchlevel 88.
+
+Sat May 23 23:21:43 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * configure.in: Remove gamma from replaceable functions.
+
+       * Made patchlevel 87.
+
+Fri May 22 00:02:33 1998  Ben Pfaff  <blp@gnu.org>
+
+       * configure.in: Add gamma to list of functions with replacements.
+
+       * Made patchlevel 86.   
+
+Wed May 20 00:00:12 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * Made patchlevel 85.
+
+Sat May 16 19:38:49 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * Made patchlevel 84.
+
+Tue May 12 16:13:48 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * unconfigure: Don't delete Makefile.in under intl/.
+       
+       * Made patchlevel 83.
+
+Thu May  7 23:16:26 1998  Ben Pfaff  <blp@gnu.org>
+
+       * unconfigure: Add some more files to reap.
+
+       * Made patchlevel 82.
+
+Tue May  5 13:17:59 1998  Ben Pfaff  <blp@gnu.org>
+
+       * acconfig.h: Add HAVE_GOOD_RANDOM definition.
+
+       * acinclude.m4: New macro BLP_RANDOM.
+
+       * configure.in: Use new BLP_RANDOM macro.
+
+       * unconfigure: New file.
+       
+       * Made patchlevel 81.
+       
+Fri Apr 24 12:42:14 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Remove bad comment.
+
+       * AUTHORS: Removed Brad Appleton.
+       
+       * TODO: Updated.
+
+       * configure.in: Remove `satisfy automake' bit.  Don't generate
+       avllib Makefile, since we don't use avllib anymore.
+
+       * pref.h.orig: Define PSPP.
+
+       * Made patchlevel 80.
+
+Wed Apr 15 12:59:39 1998  Ben Pfaff  <blp@gnu.org>
+
+       * AUTHORS, BUGS, LANGUAGE, README, THANKS: No longer generated
+       from HTML.  This caused a lot of deletions from the Makefile.am.
+
+       * TODO: Updated.
+       
+       * Made patchlevel 79.
+       
+Tue Apr 14 00:48:00 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * configure.in: Check for unistd.h.  Fix AC_LN_S (should have been
+       AC_PROG_LN_S).
+
+       * Made patchlevel 78.  Must have missed 77 in there somewhere :-)
+       
+Mon Mar  9 15:40:40 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 76.
+
+       * configure.in: Bumped version up to 0.1.16.
+
+1998-03-05  Ben Pfaff  <blp@gnu.org>
+
+       * configure.in: Bumped version up to 0.1.15.
+
+1998-02-23  Ben Pfaff  <blp@gnu.org>
+
+       * acinclude.m4: Add BLP_INT_DIGITS and BLP_IS_SPRINTF_GOOD macros.
+
+       * configure.in: Those macros came from here.  Better modularity
+       this way.  Bump version up to 0.1.14.
+
+       * pref.h.orig: (macros CONFIG_PATH, INCLUDE_PATH, GROFF_FONT_PATH)
+       Removed.
+       
+       * Made patchlevel 75.
+
+1998-02-23  Ben Pfaff  <blp@gnu.org>
+
+       * acconfig.h: Hard-code PACKAGE and GNU_PACKAGE as "PSPP" and "GNU
+       PSPP" respectively.
+
+       * configure.in: Call the package pspp instead of PSPP.  Don't
+       define PACKAGE and GNU_PACKAGE symbols.  Add replacement function
+       for strtok_r.
+
+       * TODO: Updated.
+       
+       * Made patchlevel 74.
+
+1998-02-16  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Remove a few now-useless targets.
+
+       * TODO: Updated.
+
+       * configure.in: Bump version up to 0.1.13.
+
+       * reconfigure: Don't assume . is in PATH.
+
+       * Made patchlevel 73.
+
+Fri Feb 13 15:35:03 1998  Ben Pfaff  <blp@gnu.org>
+
+       * configure.in: Bump version up to 0.1.12.
+
+       * TODO: Updated.
+
+       * pref.h.orig: Make __unix equivalent to unix and __unix__; don't
+       require any of these to be defined to 1, just defined.  Invert
+       sense of some tests from testing for unix to testing for not being
+       msdog.
+
+       * Made patchlevel 72.
+
+Thu Feb  5 00:22:58 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 71.
+
+       * configure.in: Bump version up to 0.1.11.
+
+Tue Feb  3 16:12:34 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * Made patchlevel 70.
+
+       * configure.in: Bump version up to 0.1.10.
+
+Fri Jan 23 00:17:18 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 69.
+
+Thu Jan 22 00:35:52 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 68.
+
+Sun Jan 18 00:30:18 1998  Ben Pfaff  <blp@gnu.org>
+
+       * configure.in: Add ieeefp.h to list of headers to check for.
+
+       * Made patchlevel 67.
+
+Tue Jan 13 23:44:16 1998  Ben Pfaff  <blp@gnu.org>
+
+       * configure.in: Add sys/wait.h to list of headers to check for.
+
+       * Made patchlevel 66.
+       
+Sun Jan 11 21:30:09 1998  Ben Pfaff  <blp@gnu.org>
+
+       * configure.in: Bump version up to 0.1.9.
+       
+       * pref.h.orig (STORE_2): Fix parentheses.  From Alexandre
+       Oliva <oliva@dcc.unicamp.br>.
+
+       * Made patchlevel 65.
+       
+Sat Jan 10 23:59:06 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 64.
+
+Sat Jan 10 02:10:15 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * pref.h.orig: Comment fixes.
+       (macro second_lowest_flt64) New.
+
+       * Made patchlevel 63.
+
+Thu Jan  8 22:27:03 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+       
+       * Made patchlevel 62.
+
+Mon Jan  5 11:18:37 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 61.
+
+Sun Jan  4 18:10:29 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * pref.h.orig: (local_strdup) [HAVE_ALLOCA && PAGED_STACK &&
+       __GNUC__] Rewritten for space and time efficiency and to evaluate
+       its argument only once.
+
+       * Made patchlevel 60.
+
+Sat Jan  3 16:51:20 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * Made patchlevel 59.
+
+Fri Jan  2 01:38:37 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * config.sub: Got tired of `i686-unknown-linux: Unknown system',
+       so I made 686 equivalent to 586.
+
+       * pref.h.orig: (macros ASCII_*, HTML_*, PS_*) Removed.
+
+       * Made patchlevel 58.
+
+Thu Jan  1 11:50:47 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * Made patchlevel 57.
+
+Fri Dec 26 15:43:17 1997  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * Made patchlevel 56.
+
+Wed Dec 24 22:34:55 1997  Ben Pfaff  <blp@gnu.org>
+
+       * reconfigure: regularized option syntax.
+
+       * configure.in: Bumped version to 0.1.8.  Changed name from pspp
+       to PSPP.  Added lib/dcdflib/Makefile to list of output files.
+
+       * Made patchlevel 55.
+
+Sun Dec 21 15:58:52 1997  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * acconfig.h: Reformat.
+
+       * configure.in: Bumped version to 0.1.7.
+
+       * Made patchlevel 54.
+
+Fri Dec  5 23:38:12 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Replaced prep.ai.mit.edu with ftp.gnu.org and .gnu.ai.mit.edu
+       with .gnu.org, everywhere.
+
+Fri Dec  5 23:02:40 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Replaced remaining instances of Fiasco with PSPP.
+
+       * Made patchlevel 53.
+
+Fri Dec  5 22:51:18 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Every instance of the name Fiasco, throughout every file,
+       replaced in-place with PSPP, with the exceptions of a few files
+       that had `fiasco' in their names; these were renamed.
+
+       * Made patchlevel 52.
+       
+Fri Dec  5 21:50:52 1997  Ben Pfaff  <blp@gnu.org>
+
+       * pref.h.orig: (macros NO_HTML, HTML_DEFAULT_OUTPUT_FILE) New
+       macros.
+
+       * TODO: Updated.
+
+       * Made patchlevel 51.
+
+Tue Dec  2 14:35:12 1997  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * configure.in: Bumped version to 0.1.6.
+
+       * Made patchlevel 50.   
+
+Sat Nov 22 01:20:32 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 49.
+
+Fri Nov 21 00:11:41 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 48.
+
+Sun Nov 16 01:31:38 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 47.
+
+Fri Nov 14 00:17:48 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 46.
+
+       * configure.in: Bumped version to 0.1.5.
+
+Tue Oct 28 16:07:17 1997  Ben Pfaff  <blp@gnu.org>
+
+       * configure.in: Bumped version to 0.1.4.
+
+       * TODO: Updated.
+
+       * Made patchlevel 45.
+
+Wed Oct  8 15:55:50 1997  Ben Pfaff  <blp@gnu.org>
+
+       * intl: Upgraded from sources to gettext-0.10.32.
+
+       * configure.in: Bumped version to 0.1.3.
+
+       * Made patchlevel 44.
+       
+Tue Oct  7 20:21:53 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (dist-hook) Use $(top_srcdir).
+
+       * pref.h.orig: (MAX_WORKSPACE) Enlarge to 4 MB (from 1 MB).
+
+       * Made patchlevel 43.
+       
+Sun Oct  5 15:52:37 1997  Ben Pfaff  <blp@gnu.org>
+
+       * configure.in: Bumped version to 0.1.2.
+       (strerror) Replace instead of check.  From Alexandre Oliva
+       <oliva@dcc.unicamp.br>.
+
+       * pref.h.orig: Include `debug-print' instead of
+       `src/debug-print.h'.
+
+       * Made patchlevel 42.
+
+Sat Oct  4 16:19:44 1997  Ben Pfaff  <blp@gnu.org>
+
+       * pref.h.orig: Comment fixes.
+       (local_strdup) [HAVE_ALLOCA && PAGED_STACK &&
+       __GNUC__] Use local_alloc() instead of alloca(), as local_alloc()
+       isn't simply an alias for alloca().
+
+       * configure.in: Bumped version to 0.1.1.
+
+       * Made patchlevel 41.
+
+Sat Oct  4 02:13:00 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 40.
+
+Sun Sep 21 00:07:09 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 39.
+
+Thu Sep 18 21:42:27 1997  Ben Pfaff  <blp@gnu.org>
+
+       * pref.h.orig: (CONFIG_PATH) [unix] Add /usr/local/etc/fiasco,
+       /usr/etc/fiasco to search path.
+
+       * Made patchlevel 38.
+               
+Wed Aug 20 14:20:06 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (noinst_DATA) Removed ANNOUNCE, HELP-WANTED.
+       (EXTRA_DIST) Removed ANNOUNCE, FAQ, HELP-WANTED, mk-web-dist.
+       (MAINTAINERCLEANFILES) Removed ANNOUNCE, FAQ, HELP-WANTED.
+
+       * Made patchlevel 37.
+
+Wed Aug 20 12:48:25 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (doc/ANNOUNCE.html, ANNOUNCE, FAQ, doc/FAQ.html,
+       HELP-WANTED) Removed.
+       (docfiles) Removed ANNOUNCE, FAQ, HELP-WANTED.
+
+       * mk-web-dist: Removed.
+
+       * Made patchlevel 36.
+
+Mon Aug 18 18:06:12 1997  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * pref.h.orig: (macro DEFAULT_COMPAT) Removed.
+
+       * Made patchlevel 35.
+
+Sun Aug 17 22:48:36 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 34.
+
+Sat Aug 16 10:48:29 1997  Ben Pfaff  <blp@gnu.org>
+
+       * In many files, in this directory and others, messages were
+       rephrased to eliminate or reduce usage of certain deprecated terms
+       at suggestion of rms.
+       
+       * Makefile.am: (EXTRA_DIST) Removed unix2dos.pl.
+       (MAINTAINERCLEANFILES) Removed doc/ANNOUNCE.html, doc/README.html.
+       (docfiles-recursive) Removed.
+
+       * TODO: Updated.
+
+       * mk-web-dist: Doesn't produce any distributions at all, just a
+       webpage.  Doesn't configure the distribution.  Changed list of
+       files installed.
+
+       * pref.h.orig: s/VER_PCP40/VER_PC/; s/VER_WIN61/VER_WND/;
+       s/VER_X40/VER_X/; All references changed.
+
+       * Made patchlevel 33.
+
+Thu Aug 14 22:02:08 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Comment fixes.  Uses $(VERSION) instead of contents
+       of VERSION file.
+       (EXTRA_DIST) Remove fiasco.ide, mk-bc5-dist.
+       (docfiles-recursive) Works if doc/Makefile doesn't exist.
+       (DIST_BC5_ROOT) Renamed DISTBC5_DISTROOT.
+       (DISTBC5_BC5ROOT) New var.
+       (dist-bc5) Passes $(DISTBC5_BC5ROOT).
+
+       * TODO: Update.
+
+       * acinclude.m4: Remove blp_VERSION_CHEAT kluge.
+
+       * configure.in: Don't use blp_VERSION_CHEAT kluge.
+
+       * mk-web-dist, reconfigure: Extract version number from
+       configure.in.
+
+       * pref.h.orig: (CONFIG_PATH, INCLUDE_PATH, GROFF_FONT_PATH)
+       [__MSDOS__] Fixed bad use of backslashes.
+
+       * reconfigure: Pass $VERSION to Makefile.
+
+       * Made patchlevel 32.
+
+Thu Aug 14 11:49:35 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (EXTRA_DIST, docfiles) Add ONEWS.
+       (docfiles) Depends on docfiles-recursive.
+       (docfiles-recursive) New target, call make for `docfiles' target
+       in doc directory.
+       (dist-bc5) Adds `foo' second arg to mk-bc5-dist.
+       (.PHONY) Add docfiles.
+
+       * mk-bc5-dist: Checks that it is passed a second arg of `foo'.
+
+       * reconfigure: Changed == operators to = as arguments to `test'.
+       No longer uses bash -v switch.
+
+       * mk-distribution: Renamed mk-web-dist, all references changed.
+       Now takes several options, added help.  No longer uses -uv
+       options.
+
+       * Made patchlevel 31.
+
+Tue Aug  5 13:56:39 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (MAINTAINERCLEANFILES) Add HELP-WANTED.
+       (EXTRA_DIST) Add ONEWS.
+
+       * Made patchlevel 30.
+
+Sun Aug  3 11:30:17 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (noinst_data, docfiles) Added HELP-WANTED.
+       (EXTRA_DIST) Added configure, mk-bc5-dist, unix2dos.pl,
+       HELP-WANTED.
+       (HELP-WANTED) Generated from doc/HELP-WANTED.html.
+       (dist-bc5) New target.
+
+       * TODO: Updated.
+
+       * mk-distribution: Fixed bugs, added HELP-WANTED.
+
+       * reconfigure: When invoking Makefile.am, pass
+       top_srcdir=. explicitly.
+
+       * unix2dos.pl: New file.
+
+       * Made patchlevel 29.
+
+Thu Jul 17 21:49:13 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 28.
+
+Thu Jul 17 01:43:25 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Remove inactive .html suffix rule.
+       New rules to generate doc/ANNOUNCE.html and doc/README.html from
+       corresponding .in files.
+       (EXTRA_DIST) Add VERSION, fiasco.ide, mk-distribution.
+       (MAINTAINERCLEANFILES) Add doc/ANNOUNCE.html, doc/README.html.
+
+       * acinclude.m4: (blp_VERSION_CHEAT) New macro.
+
+       * configure.in: Forces _GNU_SOURCES not only to be defined, but to
+       a value of 1.  Substitutes VERSION from the new file VERSION.
+       Removed DEBIAN reference.  Checks for sys/mman.h header.
+
+       * pref.h.orig: (macro gettext) Don't put parentheses in the
+       expansion.
+       (macro N_) Same.
+
+       * reconfigure: Sets -ev in shell.  Doesn't try to pass
+       --include-deps to configure (it's an automake flag!).  Moved `make
+       docfiles'.
+
+       * sysdeps/borlandc4.0/README, sysdeps/borlandc4.0/_read.c,
+       sysdeps/borlandc4.0/_write.c, sysdeps/borlandc4.0: Removed.
+
+       * VERSION: New file.
+
+       * fiasco.ide: New file.
+
+       * mk-distribution: New file.
+
+       * Made patchlevel 27.
+       
+Fri Jul 11 23:00:53 1997  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updates.
+
+       * Made patchlevel 26.
+
+Fri Jul 11 14:08:21 1997  Ben Pfaff  <blp@gnu.org>
+
+       * pref.h.orig: __CYGWIN32__ is a form of __unix__.
+
+       * reconfigure: Add -k for make maintainer-clean.
+
+       * Made patchlevel 25.
+
+Thu Jul 10 22:13:07 1997  Ben Pfaff  <blp@gnu.org>
+
+       * configure.in: Add "-D_GNU_SOURCE" to CPPFLAGS to force GNU
+       glibc extensions to be detected.
+
+       * Made patchlevel 24.
+
+Sun Jul  6 19:13:07 1997  Ben Pfaff  <blp@gnu.org>
+
+       * pref.h.orig: Include "src/debug-print.h" instead of
+       "debug-print.h".
+       (macros local_alloc, local_free) More robust under Checker: put
+       their allocations in namespace different from malloc()/free().
+
+       * Made patchlevel 23.
+
+Sat Jul  5 23:42:14 1997  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updates.
+
+       * Made patchlevel 22.
+
+Fri Jul  4 13:20:47 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (EXTRA_DIST) Removed orphaned-rules.
+       (docfiles) Removed ChangeLog, COPYING.
+       (html, maintainer-clean-hook, install-data-hook) Removed.
+
+       * reconfigure: Added --help option.  Calls configure again even if
+       --no-include-deps.
+
+       * Made patchlevel 21.
+
+Wed Jun 25 22:47:17 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Require Automake 1.2.
+       (dist-hook) Don't copy config dir.
+       (EXTRA_DIST, MAINTAINERCLEANFILES) Add FAQ.
+       (docfiles) Made a variable as well as a target; added ChangeLog,
+       COPYING, FAQ, INSTALL, TODO.
+       (html, maintainer-clean-hook, install-data-hook, debian,
+       debian-clean, debian-clean-full) New targets.
+
+       * orphaned-rules: Removed.
+
+       * configure.in: Bumped up to version 0.1.0.
+
+       * reconfigure: New options --enable-nls, --no-include-deps.
+       Comment fixes.
+
+       * Made patchlevel 20.
+
+Sun Jun 22 22:10:27 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 19.
+
+Sun Jun 15 16:44:14 1997  Ben Pfaff  <blp@gnu.org>
+
+       * pref.h.orig: Comment fixes.  Includes debug-print.h.
+       (DEMAND_PAGE, ALWAYS_PAGE, NEVER_PAGE) Removed.
+
+       * Made patchlevel 18.
+
+Sun Jun  8 01:25:40 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 17.
+
+Fri Jun  6 22:41:08 1997  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updates.
+
+       * pref.h.orig: Reformatted macros.
+       [!ENABLE_NLS] Defines gettext() as a trivial substitution to allow
+       gcc to give warnings on printf().
+
+       * Made patchlevel 16.
+       
+Thu Jun  5 23:01:49 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 15.
+
+Tue Jun  3 23:24:08 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: AUTOMAKE_OPTIONS changed from `foreign 1.1l' to
+       `gnits 1.1p'.  SUBDIRS reordered.  New target `docfiles'.
+
+       * TODO: Updates.
+
+       * configure.in: Removed AM_MAINTAINER_MODE.  Added
+       --enable-Werror, which is implied by --with-checker.
+
+       * reconfigure: Moved `aclocal' from beginning to just before
+       cleaning `autoheader'.  Removed --enable-maintainer-mode.  Added
+       --disable-nls.  Added `make docfiles' to placate autoheader.
+       Added `aclocal' before first real `autoheader'.  Uses `make
+       mostlyclean' instead of `make depend'.
+
+       * Made patchlevel 14.
+
+Mon Jun  2 14:21:54 1997  Ben Pfaff  <blp@gnu.org>
+
+       * configure.in: Removed comment that screwed things up.
+
+       * reconfigure: Added `aclocal' at beginning.
+
+       * Made patchlevel 13.
+
+Sun Jun  1 23:25:39 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Add intl, po to SUBDIRS.  Add aclocal.m4,
+       config.h.in to MAINTAINERCLEANFILES.
+
+       * acconfig.h: Add HAVE_LC_MESSAGES, ENABLE_NLS, HAVE_CATGETS,
+       HAVE_GETTEXT, HAVE_STPCPY.
+
+       * configure.in: Reordered to placate autoheader.  Added
+       AC_ISC_POSIX, AM_PROG_CC_STDC.  Added internationalization:
+       ALL_LINGUAS="", AM_GNU_GETTEXT, AC_LINK_FILES(...).  Added
+       po/Makefile.in, intl/Makefile to generated files list.  Generates
+       po/Makefile from po/Makefile.in.  Comment fix.
+
+       * pref.h.orig: Uncommented i18n support.
+
+       * acinclude.m4: New file.
+
+       * ABOUT-NLS: New file.
+
+       * intl/: New directory, taken from gettext-0.10.27.
+
+       * missing: New file, taken from automake-1.1p.
+
+       * po/: New directory.
+
+       * Made patchlevel 12.
+
+Sun Jun  1 17:28:27 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 11.
+
+Sun Jun  1 11:58:43 1997  Ben Pfaff  <blp@gnu.org>
+
+       * pref.h.orig: Removed DEFAULT_VER_PCP40, DEFAULT_VER_WIN61,
+       DEFAULT_VER_X40.  Added a macro DEFAULT_COMPAT that takes one of
+       the VER_* enums as a value.
+       (HISTORY_FILE) Changed the definition to "~/.fiasco_history".
+
+       * Made patchlevel 10.
+
+Fri May 30 19:40:22 1997  Ben Pfaff  <blp@gnu.org>
+
+       * pref.h.orig: [__MSDOS__] Reordered INCLUDE_PATH.
+
+       * Made patchlevel 9.
+
+Sun May 25 22:32:57 1997  Ben Pfaff  <blp@gnu.org>
+
+       * acconfig.h: For support of glibc 2, define _GNU_SOURCE.
+
+       * Made patchlevel 8.
+
+Mon May  5 21:58:22 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 7. 
+
+Fri May  2 22:27:36 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 6.
+
+Thu May  1 15:34:01 1997  Ben Pfaff  <blp@gnu.org>
+
+       * All files: Changed copyright from `Ben Pfaff' to `Free Software
+       Foundation, Inc'.
+
+       * Made patchlevel 5.
+
+Thu May  1 15:00:51 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 4.
+
+Sat Apr 26 11:34:05 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ChangeLog: Split into one ChangeLog per directory.
+
+       * Made patchlevel 3.
+
+Wed Apr 23 21:33:48 1997  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Update.
+
+       * Made patchlevel 2.
+
+Fri Apr 18 16:48:41 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Removed `include', `util' from SUBDIRS; added
+       `config'.  Includes `config' directory in distributions.  Added
+       `private-install', `private-uninstall' targets.
+
+       * configure.in: AC_INIT tests for src/q2c.c now.  Removed
+       redundant AC_PROG_MAKE_SET call.  Removed include/Makefile,
+       util/Makefile from generated files list; added config/Makefile.
+
+       * include/approx.h, include/arena.h, include/common.h,
+       include/dfm.h, include/do-ifP.h, include/error.h, include/expr.h,
+       include/exprP.h, include/file-handle.h, include/filename.h,
+       include/font.h, include/getline.h, include/getopt.h,
+       include/hash.h, include/heap.h, include/log.h, include/misc.h,
+       include/output.h, include/settings.h, include/sfm.h,
+       include/sfmP.h, include/som.h, include/somP.h, include/stat.h,
+       include/stats.h, include/str.h, include/tokens.h, include/var.h,
+       include/version.h, include/vfmP.h: Moved into src/ directory.
+
+       * include/Makefile.am, include/: Removed.
+
+       * util/Makefile.am: Removed.
+
+       * util/q2c.c: Moved to src/.
+
+       * util/reconfigure: Moved to source root.
+
+       * util/: Removed.
+
+       * Made patchlevel 1.
+
+Fri Apr 18 15:42:22 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Maintainer-cleans generated documentation and
+       Makefile.in.
+
+       * include/Makefile.am, util/Makefile.am: Maintainer-cleans
+       Makefile.in.
+
+       * include/somP.h: (static struct var som) Removed passed_t member.
+
+       * TODO: Updated.
+
+       * configure.in: Fixed source directory for copying pref.h; always
+       updates pref.h or at least touch'es it.
+
+       * pref.h.orig: Made a rather pejorative comment a lot milder so it
+       wouldn't be misinterpreted.
+
+       * Made interim release x3.
+       
+Thu Mar 27 01:11:29 1997  Ben Pfaff  <blp@gnu.org>
+
+       * All directories now contain new `Makefile.am's, in some cases
+       produced from bits and pieces of the single monolithic old one.
+       
+       * PATCHLEVEL: Removed.
+
+       * acconfig.h: Added GNU_PACKAGE, PACKAGE, PROTOTYPES, VERSION;
+       removed inclusion of conf.h.
+
+       * confh.in: Removed.
+       * confh.tmp.in: Removed.
+       
+       * configure.in: Deepened.  Updated for use with Automake 1.1l.
+       Removed PATCHLEVEL hacks.  Fixed lots of functions in
+       AC_CHECK_FUNCS, AC_REPLACE_FUNCS, and similar.  Only passes
+       `-Werror' to gcc in maintainer mode.  Doesn't output conf.h.
+       Touches pref.h even if it wasn't changed.  
+
+       * aclocal.m4: New file.
+
+       * config.h.in: Renamed from configh.in.
+
+       * pref.h.orig: Renamed from prefh.orig.
+
+       * Made interim release x2.
+       
+Thu Mar 27 01:07:02 1997  Ben Pfaff  <blp@gnu.org>
+
+    Changed the distribution from flat to deep.  New configuration:
+
+       ANNOUNCE        Makefile.in     config.h.in     mkinstalldirs
+       AUTHORS         NEWS            config.sub      orphaned-rules
+       BUGS            README          configure       pref.h
+       COPYING         THANKS          configure.in    pref.h.orig
+       ChangeLog       TODO            debian          src
+       ChangeLog~      acconfig.h      doc             stamp-h.in
+       INSTALL         aclocal.m4      include         sysdeps
+       LANGUAGE        config          install-sh      tests
+       Makefile.am     config.guess    lib             util
+
+       config:
+       devices      environment  papersize    ps-fontmap   ps-prologue
+
+       debian:
+       changelog  control    postinst   rules
+       conffiles  copyright  postrm
+
+       doc:
+       ANNOUNCE.html  Makefile.in    fiasco.info-2  stamp-vti
+       AUTHORS.html   README.html    fiasco.info-3  texinfo.tex
+       BUGS.html      THANKS.html    fiasco.info-4  version.texi
+       LANGUAGE.html  fiasco.info    fiasco.texi
+       Makefile.am    fiasco.info-1  mdate-sh
+
+       include:
+       approx.h       file-handle.h  misc.h         stats.h
+       arena.h        filename.h     output.h       str.h
+       common.h       font.h         settings.h     tokens.h
+       dfm.h          getline.h      sfm.h          var.h
+       do-ifP.h       getopt.h       sfmP.h         version.h
+       error.h        hash.h         som.h          vfmP.h
+       expr.h         heap.h         somP.h
+       exprP.h        log.h          stat.h
+
+       lib:
+       Makefile.am  Makefile.in  avllib       julcal       misc
+
+       lib/avllib:
+       AVLLIB.COPYING  Makefile.in     avl.h
+       Makefile.am     avl.c
+
+       lib/julcal:
+       Makefile.am  Makefile.in  julcal.c     julcal.h
+
+       lib/misc:
+       Makefile.am    getopt1.c      memset.c       strstr.c
+       Makefile.in    memchr.c       qsort.c        strtol.c
+       alloca.c       memcmp.c       stpcpy.c       strtoul.c
+       getdelim.c     memcpy.c       strcasecmp.c
+       getline.c      memmem.c       strncasecmp.c
+       getopt.c       memmove.c      strpbrk.c
+
+       src:
+       Makefile.am     error.c         lexer.c         sfm-write.c
+       Makefile.in     expr-evl.c      list.c          show.c
+       arena.c         expr-opt.c      list.q          som-frnt.c
+       ascii.c         expr-prs.c      log.c           som-high.c
+       autorecode.c    file-handle.c   loop.c          som-low.c
+       cases.c         file-handle.q   main.c          sort.c
+       cmdline.c       file-type.c     mis-val.c       split-file.c
+       command.c       filename.c      misc.c          stats.c
+       common.c        formats.c       modify-vars.c   str.c
+       compute.c       freq.c          numeric.c       sysfile-info.c
+       count.c         frequencies.c   output.c        temporary.c
+       crosstabs.c     frequencies.g   postscript.c    title.c
+       crosstabs.q     frequencies.q   print.c         val-labs.c
+       data-in.c       get.c           recode.c        var-labs.c
+       data-list.c     getline.c       rename-vars.c   vars-atr.c
+       data-out.c      glob.c          repeat.c        vars-prs.c
+       descript.c      groff-font.c    sample.c        vector.c
+       descript.q      hash.c          sel-if.c        version.c
+       dfm.c           heap.c          set.c           vfm.c
+       display.c       include.c       set.q           weight.c
+       do-if.c         inpt-pgm.c      sfm-read.c
+
+       sysdeps:
+       BorlndC4.0  DJGPP2.0    Windows
+
+       sysdeps/BorlndC4.0:
+       Makefile     _write.c     conf.h
+       _read.c      compile.bat  config.h
+
+       sysdeps/DJGPP2.0:
+       Makefile     compile.bat  conf.h       config.h
+
+       sysdeps/Windows:
+       con32s.c
+
+       tests:
+       Makefile.am         expression.stat     reread.data
+       Makefile.in         fall92.data         reread.stat
+       autorecode.stat     fall92.stat         sample.stat
+       begin-data.stat     file-label.stat     show-check-msg
+       bignum.data         file-type.stat      sort.data
+       bignum.stat         filter.stat         sort.stat
+       bug.stat            gengarbage.c        split-file.stat
+       compute.stat        input-program.stat  sysfile-info.stat
+       count.stat          list.data           temporary.stat
+       data-formats.stat   list.stat           time-date.stat
+       data-list.data      loop.stat           vector.stat
+       data-list.stat      modify-vars.stat    weighting.data
+       descript.stat       print.stat          weighting.stat
+       do-if.stat          process-if.stat
+       do-repeat.stat      recode.stat
+
+       util:
+       Makefile.am  Makefile.in  q2c.c        reconfigure
+
+    Old configuration:
+
+       ANNOUNCE.html   count.c         hash.h          sample.c
+       AUTHORS.html    crosstabs.q     heap.c          sel-if.c
+       AVLLIB.COPYING  data-in.c       heap.h          set.q
+       BUGS.html       data-list.c     include.c       settings.h
+       COPYING         data-out.c      inpt-pgm.c      sfm-read.c
+       ChangeLog       debian          install-sh      sfm-write.c
+       INSTALL         descript.q      julcal.c        sfm.h
+       LANGUAGE.html   devices         julcal.h        sfmP.h
+       Makefile.am     dfm.c           lexer.c         show.c
+       NEWS            dfm.h           list.q          som-frnt.c
+       PATCHLEVEL      display.c       log.c           som-high.c
+       README.html     do-if.c         log.h           som-low.c
+       THANKS.html     do-ifP.h        loop.c          som.h
+       TODO            environment     main.c          somP.h
+       _read.c         error.c         makeb40.bat     sort.c
+       _write.c        error.h         makedj2.bat     split-file.c
+       acconfig.h      expr-evl.c      makefile.b40    stamp-h.in
+       alloca.c        expr-opt.c      makefile.dj2    stats.c
+       approx.h        expr-prs.c      mdate-sh        stats.h
+       arena.c         expr.h          memcmp.c        stpcpy.c
+       arena.h         exprP.h         mis-val.c       str.c
+       ascii.c         fiasco.texi     misc.c          str.h
+       autorecode.c    file-handle.h   misc.h          sysfile-info.c
+       avl.c           file-handle.q   mkinstalldirs   temporary.c
+       avl.h           file-type.c     modify-vars.c   test
+       cases.c         filename.c      numeric.c       texinfo.tex
+       cmdline.c       filename.h      output.c        title.c
+       command.c       font.h          output.h        tokens.h
+       common.c        formats.c       papersize       val-labs.c
+       common.h        freq.c          postscript.c    var-labs.c
+       compute.c       frequencies.g   prefh.orig      var.h
+       con32s.c        frequencies.q   print.c         vars-atr.c
+       confh.b40       get.c           ps-fontmap      vars-prs.c
+       confh.dj2       getline.c       ps-prologue     vector.c
+       confh.in        getline.h       q2c.c           version.c
+       confh.tmp.in    getopt.c        qsort.c         version.h
+       config.guess    getopt.h        recode.c        vfm.c
+       config.sub      getopt1.c       reconfigure     vfmP.h
+       configh.b40     glob.c          reject          weight.c
+       configh.dj2     groff-font.c    rename-vars.c
+       configure.in    hash.c          repeat.c
+
+       debian:
+       changelog  control    postinst   rules
+       conffiles  copyright  postrm
+
+       test:
+       autorecode.stat     fall92.data         recode.stat
+       begin-data.stat     fall92.stat         reread.data
+       bignum.data         file-label.stat     reread.stat
+       bignum.stat         file-type.stat      sample.stat
+       bug.stat            filter.stat         sort.stat
+       compute.stat        gengarbage.c        split-file.stat
+       count.stat          gengarbage.pl       sysfile-info.stat
+       data-formats.stat   input-program.stat  temporary.stat
+       data-list.data      list.data           time-date.stat
+       data-list.stat      list.stat           vector.stat
+       descript.stat       loop.stat           weighting.data
+       do-if.stat          modify-vars.stat    weighting.stat
+       do-repeat.stat      print.stat
+       expression.stat     process-if.stat
+
+Mon Mar 24 21:47:31 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: @ALLOCA@ is on list of source files instead of
+       alloca.c.  Added $(srcdir)/ to version.c reference.  Changed to
+       pkgdatadir (/usr/share) for pkgsysconfdir, from pkglibdir
+       (/usr/lib).  Removed some of extra distfiles.  Added bogus `check'
+       target.
+
+       * Made transition release x1.
+
+Sun Mar  2 20:51:28 1997  Ben Pfaff  <blp@gnu.org>
+
+       No longer uses debmake:
+       
+       * Makefile.am: Installs documentation according to Debian policy
+       manual.  New targets `private-uninstall', `install-data-hook' to
+       help implement this.  `debian' target also revised.
+       
+       * configure.in: Sets up for Debian installation depending on
+       DEBIAN environment variable.  Also, improved & fixed (hopefully)
+       the scheme for detecting patchlevel.
+       
+       * Made patchlevel 193.
+
+Wed Feb 19 21:30:31 1997  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * Made patchlevel 192.
+
+Sun Feb 16 20:57:20 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 191.
+       
+Sat Feb 15 21:26:53 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Removed `descript.g' from sources.
+
+       * Made patchlevel 190.
+       
+Fri Feb 14 23:32:58 1997  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+       
+       * configure.in: Fixed test for max number of digits in an `int' to
+       use char[] rather than int[].
+
+       * Made patchlevel 189.
+       
+Tue Feb  4 15:15:50 1997  Ben Pfaff  <blp@gnu.org>
+
+       * configure.in: Fixed some problems with `--with-checker' flag and
+       with detection of available libraries; no longer any lines longer
+       than 79 characters.
+
+       * Made patchlevel 188.
+       
+Wed Jan 22 21:54:00 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Add sysfile-info.c to sources.
+
+       * TODO: Moved some notes to different files where they are more
+       appropriate.
+
+       * prefh.orig: (macros STORE_2 and LOAD_2) Always load/store as
+       little-endian.
+
+       * Made patchlevel 187.
+
+Sun Jan 19 14:22:11 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added rename-vars.c to sources.  Added to distclean
+       files.
+
+       * TODO: Updates.
+
+       * Made patchlevel 186.
+
+Thu Jan 16 13:08:57 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Most files have updated copyright notices for 1997.
+
+       * Makefile.am: Added modify-vars.c to source files.  Also changed
+       `lynx' to $(HTML_FORMATTER), etc.  Changed messages.
+
+       * TODO: Updates.
+
+       * Made patchlevel 185.
+
+Sat Jan 11 15:44:15 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: By default, now installs config files in pkglibdir,
+       generally /usr/local/lib/fiasco.
+
+       * TODO: Updated.
+
+       * prefh.orig: Added `/etc/fiasco' to config paths.  Removed
+       $ARCH/$VER dirs from include paths. 
+
+       * Made patchlevel 184.
+
+Fri Jan 10 20:22:08 1997  Ben Pfaff  <blp@gnu.org>
+
+       * debian/changelog, debian/control, debian/copyright, debian/dirs,
+       debian/info, debian/menu, debian/rules: Added Debian GNU/Linux
+       control files.
+       
+       * Makefile.am: Added sfmP.h to source files.  Added several files
+       to the list of distfiles.   dist-hook now copies debian control
+       files.  New targets `debian', `debian-clean', `debian-clean-full'.
+
+       * confh.in: Defines PATCHLEVEL.
+
+       * configure.in: Adds the current patchlevel to the version
+       number.  Versions are now of the form `1.2.3pl456'.  Determines
+       the patchlevel based on directory name and contents of file
+       PATCHLEVEL.
+
+       * reconfigure: Passes automake `--strictness=foreign'.
+
+       * Made patchlevel 183.
+
+Thu Jan  2 19:08:23 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 182.
+
+Wed Jan  1 22:08:10 1997  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+       
+       * Made patchlevel 181.
+       
+Wed Jan  1 17:00:59 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: New target for test/sort.data.
+
+       * Made patchlevel 180.
+       
+Sun Dec 29 21:36:48 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 179.
+
+Tue Dec 24 20:42:32 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 178.
+
+Sun Dec 22 23:10:39 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added heap.c, heap.h to source files.  Added
+       new html files to distfiles & maintainer-clean files.
+
+       * configure.in: Tests for presence of getpid(), sys/types.h.
+
+       * prefh.orig: #defines mkdir() for MS-DOS compatibility.
+
+       * Made patchlevel 177.
+
+Sat Dec 21 21:51:04 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added README.html, LANGUAGE.html to list of
+       distfiles.  Added README, LANGUAGE to list of maintainer-clean
+       files.  Added .html to suffixes.  Added .html implicit rule that
+       calls `lynx -dump -nolist'.
+
+       * Made patchlevel 176.
+       
+Tue Dec 17 18:57:59 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 175.
+       
+Sun Dec 15 15:32:16 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added vfmP.c, qsort, sort.c to list of source
+       files.
+
+       * prefh.orig: Subtle changes to MAX_WORKSPACE, ALWAYS_PAGE,
+       NEVER_PAGE, DEMAND_PAGE macro meanings.
+
+       * Made patchlevel 174.
+       
+Sat Dec 14 10:35:30 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 173.
+       
+Fri Dec 13 21:30:53 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added autorecode.c to source files.
+
+       * prefh.orig: Fixed path GROFF_FONT_PATH.
+
+       * Made patchlevel 172.
+       
+Fri Dec  6 23:53:47 1996  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+       
+       * Made patchlevel 171.
+       
+Wed Dec  4 21:34:17 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 170.
+       
+Sun Dec  1 17:19:00 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 169.
+       
+Thu Nov 28 23:14:07 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added `set.q' to list of source files.
+
+       * Made patchlevel 168.
+
+Thu Nov 28 19:46:10 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 167.
+
+Wed Nov 27 23:18:35 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added `sfm-write.c' to list of source files.
+
+       * confh.in: New #defines RELEASE_NO, SUB_RELEASE_NO, and
+       SPEC_RELEASE_NO for each part of a version number of form 1.2.3.
+
+       * configure.in: Computes RELEASE_NO, etc., by breaking apart
+       VERSION.
+
+       * prefh.orig: (defn of int32, flt64) Formatting fixes.
+       (FLT64_MAX) New define.
+
+       * Made patchlevel 166.
+       
+Sun Nov 24 14:53:53 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Wow, it's been almost two weeks since the last update, hard to
+       believe.
+
+       * All source files: Updated e-mail address.
+       
+       * prefh.orig: local_alloc() calls xmalloc() under Checker because
+       Checker can keep track of heap blocks much more accurately.
+
+       * Made patchlevel 165.
+       
+Mon Nov 11 15:34:09 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 164.
+       
+Thu Nov  7 20:52:28 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 163.
+       
+Thu Nov  7 17:29:16 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 162.
+       
+Thu Nov  7 15:48:52 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 161.
+       
+Tue Nov  5 18:34:59 1996  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * Made patchlevel 160.
+       
+Mon Nov  4 22:03:28 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added get.c.
+
+       * TODO: Updated.
+
+       * Made patchlevel 159.
+       
+Sun Nov  3 12:24:36 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added sfm.h, sfm-read.c to source files.
+
+       * Made patchlevel 158.
+       
+Wed Oct 30 17:13:08 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added dist-zip target to AUTOMAKE_OPTIONS.
+
+       * acconfig.h: Added FPREP_* defines.
+
+       * configure.in: Added checks for the sizes of floating-point
+       types.  Added a test for the internal floating-point
+       representation of the host architecture.
+
+       * prefh.orig: Renamed `ATTRIBUTION' macro as `__attribute__'.  All
+       references changed.  Defines `flt64' 64-bit floating-point for use
+       with system files.
+       [FPREP==FPREP_IEEE754 && __GNUC__ && (ENDIAN==BIG ||
+       ENDIAN==LITTLE] Defines SECOND_LOWEST_VALUE macro.
+       
+       * Made patchlevel 157.
+
+Sat Oct 26 23:06:06 1996  Ben Pfaff  <blp@gnu.org>
+
+       * configure.in: Checks sizes of short, int, long, long long.
+
+       * prefh.orig: Defines new type int32 for use with system
+       files.
+
+       * Made patchlevel 156.
+       
+Sat Oct 26 20:46:31 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 155.
+
+Sat Oct 26 10:39:25 1996  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * Made patchlevel 154.
+       
+Thu Oct 24 20:13:42 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added back in these files: recode.c, sample.c,
+       sel-if.c.  Also added files somP.h, hash.c that should've been
+       there anyway.
+
+       * TODO: Updated.
+
+       * configure.in: Checks for strncasecmp in place of strcasecmp.
+
+       * Made patchlevel 153.
+       
+Thu Oct 24 17:47:14 1996  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * Made patchlevel 152.
+       
+Wed Oct 23 21:53:43 1996  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Organized.
+
+       * Made patchlevel 151.
+       
+Tue Oct 22 17:27:04 1996  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Culled old notes.
+
+       * Made patchlevel 150.
+
+Mon Oct 21 20:39:59 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 149.
+       
+Sun Oct 20 13:45:28 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added back in `numeric.c', `print.c', `title.c'.
+       Defined ETAGS_ARGS.
+
+       * Made patchlevel 148.
+       
+Sun Oct 20 09:04:15 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 147.
+       
+Fri Oct 18 19:46:49 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 146.
+       
+Sun Sep 29 19:37:03 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 145.
+       
+Sat Sep 28 21:28:07 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added to DISTCLEANFILES. 
+   
+       * Made patchlevel 144.
+       
+Fri Sep 27 20:08:39 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 143.
+       
+Thu Sep 26 22:20:26 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added list.c back into the list of source files.
+
+       * Made patchlevel 142.
+       
+Wed Sep 25 19:36:11 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Updated for new files.
+
+       * Made patchlevel 141.
+               
+Tue Sep 24 18:39:09 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 140.
+
+Sat Sep 21 23:16:31 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 139.
+
+Fri Sep 20 22:52:28 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 138.
+       
+Thu Sep 12 18:40:33 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 137.
+
+Wed Sep 11 22:01:41 1996  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Added timestamp.
+
+       * prefh.orig: Removed `/usr/local/share/fiasco' and
+       `/usr/share/fiasco' from CONFIG_PATH as per the Linux FSSTND,
+       which specifies that programs should never give an explicit
+       `/usr(/local)/share' path.
+
+       * Made patchlevel 136.
+
+Tue Sep 10 21:39:00 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added `display.c' back in.
+
+       * TODO: Addition.
+
+       * Made patchlevel 135.
+       
+Mon Sep  9 21:43:13 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added `split-file.c' back into the project.
+
+       * Made patchlevel 134.
+       
+Sat Sep  7 22:35:12 1996  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Updated.
+
+       * prefh.orig: (local_strdup) Moved to misc.h.
+
+       * Made patchlevel 133. 
+       
+Thu Sep  5 22:05:56 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Changed `prologue.ps' references to `ps-prologue'.
+
+       * Made patchlevel 132.
+       
+Wed Sep  4 21:45:35 1996  Ben Pfaff  <blp@gnu.org>
+
+       * prefh.orig: New i18n defines.
+
+       * This patchlevel doesn't even compile.
+
+       * Made patchlevel 131.
+       
+Sat Aug 31 23:52:38 1996  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: Addition.
+
+       * Made patchlevel 130.
+               
+Thu Aug 29 21:36:41 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 129.
+       
+Sat Aug 24 23:26:00 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: New target "private-install" to install config files
+       to $HOME/.fiasco.
+
+       * configure.in: Now that I have made a less-bogus Checker
+       distribution, removed `-b i486-linuxaout -V 2.6.3' from
+       AC_ARG_WITH(checker, ...).
+
+       * Made patchlevel 127 somewhere in there.
+       
+       * Made patchlevel 128.
+       
+Sun Aug 11 21:31:22 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Changed DISTCLEANFILES.
+
+       * Does not compile.
+       
+       * Made patchlevel 126.
+       
+Sat Aug 10 23:28:17 1996  Ben Pfaff  <blp@gnu.org>
+
+       * reconfigure: Calls `autoheader' twice: once at the beginning,
+       once after make maintainer-clean.
+
+       * Made patchlevel 125. 
+       
+Thu Aug  8 22:31:11 1996  Ben Pfaff  <blp@gnu.org>
+
+       * reconfigure: `autoheader' now first operation performed.
+
+       * Made patchlevel 124.
+       
+Sat Aug  3 20:50:35 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added postscript.c to list of source files.
+
+       * configh.in: Removed since autoheader can regenerate it.
+
+       * configure.in: Improved tests for (ncurses or termcap) and
+       (history and/or readline) libraries and associated headers.  Added
+       check for strcasecmp().  Changed default gcc CFLAGS.
+
+       * prefh.orig: Removed `.' from GROFF_FONT_PATH.
+       (local_alloc, local_free) New functions.
+
+       * reconfigure: Added call to autoheader.
+
+       * Made patchlevel 123.
+       
+Sat Jul 27 22:32:38 1996  Ben Pfaff  <blp@gnu.org>
+
+       * There were some problems with the patchfiles so I had to merge
+       what was previously patchlevels 121 and 122; now everything from
+       what was previously 122 is called 121.  Oh well, just don't let it
+       happen often.
+
+       * This patchlevel does not compile.
+
+       * configure: No longer included in patches to save lotsa space
+       when configure.in changes.
+
+       * configure.in: Changed the technique for detecting libraries.
+
+       * prefh.orig: Style changes; handles changed configure.in.
+
+       * Made patchlevel 122 (second edition).
+
+Tue Jul 23 21:48:36 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 121.
+       
+Wed Jul 17 21:23:36 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 120.
+
+Tue Jul 16 22:10:04 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 119.
+
+Sun Jul 14 15:45:31 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 118.
+
+Fri Jul 12 22:03:36 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added list.c to sources.
+       
+       * Made patchlevel 117.
+       
+Sat Jul  6 22:22:25 1996  Ben Pfaff  <blp@gnu.org>
+
+       * configure.in: Removed reference to `malloc.h'.
+       
+       * Made patchlevel 116.
+       
+Fri Jul  5 20:16:19 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 115.
+
+Thu Jul  4 20:20:24 1996  Ben Pfaff  <blp@gnu.org>
+
+       * prefh.orig: Changes to CONFIG_PATH, INCLUDE_PATH,
+       GROFF_FONT_PATH.
+
+       * Makefile.am: pkgdata_DATA file `output' changed to `devices'.
+       
+Thu Jul  4 00:35:59 1996  Ben Pfaff  <blp@gnu.org>
+
+       * TODO: doc fix.
+       
+       * Made patchlevel 114.
+       
+Tue Jul  2 22:13:23 1996  Ben Pfaff  <blp@gnu.org>
+
+       * reconfigure: (new file) Runs all the programs necessary to
+       create a Makefile that includes dependencies.
+
+       * Made patchlevel 113.
+       
+Mon Jul  1 22:13:39 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Made patchlevel 112.
+
+Mon Jul  1 13:00:00 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Most files: Changed references from `stat' (the original, rather
+       dull old name for this project) to `Fiasco' (the creative, rather
+       funny new name for this project).
+       
+       * Made patchlevel 111.
+       
+Sat Jun 29 17:40:47 1996  Ben Pfaff  <blp@gnu.org>
+
+       * prefh.orig: changed default file search paths
+       
+       * Made patchlevel 110.
+
+Fri Jun 28 11:59:48 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Added automake support; removed GNUmakefile and GNUmakefile.in.
+
+----------------------------------------------------------------------
+Local Variables:
+mode: change-log
+version-control: never
+End:
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..3b50ea9
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,176 @@
+Basic Installation
+==================
+
+   These are generic installation instructions.
+
+   The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation.  It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions.  Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, a file
+`config.cache' that saves the results of its tests to speed up
+reconfiguring, and a file `config.log' containing compiler output
+(useful mainly for debugging `configure').
+
+   If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release.  If at some point `config.cache'
+contains results you don't want to keep, you may remove or edit it.
+
+   The file `configure.in' is used to create `configure' by a program
+called `autoconf'.  You only need `configure.in' if you want to change
+it or regenerate `configure' using a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+  1. `cd' to the directory containing the package's source code and type
+     `./configure' to configure the package for your system.  If you're
+     using `csh' on an old version of System V, you might need to type
+     `sh ./configure' instead to prevent `csh' from trying to execute
+     `configure' itself.
+
+     Running `configure' takes a while.  While running, it prints some
+     messages telling which features it is checking for.
+
+  2. Type `make' to compile the package.
+
+  3. Optionally, type `make check' to run any self-tests that come with
+     the package.
+
+  4. Type `make install' to install the programs and any data files and
+     documentation.
+
+  5. You can remove the program binaries and object files from the
+     source code directory by typing `make clean'.  To also remove the
+     files that `configure' created (so you can compile the package for
+     a different kind of computer), type `make distclean'.  There is
+     also a `make maintainer-clean' target, but that is intended mainly
+     for the package's developers.  If you use it, you may have to get
+     all sorts of other programs in order to regenerate files that came
+     with the distribution.
+
+Compilers and Options
+=====================
+
+   Some systems require unusual options for compilation or linking that
+the `configure' script does not know about.  You can give `configure'
+initial values for variables by setting them in the environment.  Using
+a Bourne-compatible shell, you can do that on the command line like
+this:
+     CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
+
+Or on systems that have the `env' program, you can do it like this:
+     env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure
+
+Compiling For Multiple Architectures
+====================================
+
+   You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory.  To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'.  `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script.  `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+   If you have to use a `make' that does not supports the `VPATH'
+variable, you have to compile the package for one architecture at a time
+in the source code directory.  After you have installed the package for
+one architecture, use `make distclean' before reconfiguring for another
+architecture.
+
+Installation Names
+==================
+
+   By default, `make install' will install the package's files in
+`/usr/local/bin', `/usr/local/man', etc.  You can specify an
+installation prefix other than `/usr/local' by giving `configure' the
+option `--prefix=PATH'.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+give `configure' the option `--exec-prefix=PATH', the package will use
+PATH as the prefix for installing programs and libraries.
+Documentation and other data files will still use the regular prefix.
+
+   If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+   Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System).  The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+   For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+   There may be some features `configure' can not figure out
+automatically, but needs to determine by the type of host the package
+will run on.  Usually `configure' can figure that out, but if it prints
+a message saying it can not guess the host type, give it the
+`--host=TYPE' option.  TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name with three fields:
+     CPU-COMPANY-SYSTEM
+
+See the file `config.sub' for the possible values of each field.  If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the host type.
+
+   If you are building compiler tools for cross-compiling, you can also
+use the `--target=TYPE' option to select the type of system they will
+produce code for and the `--build=TYPE' option to select the type of
+system on which you are compiling the package.
+
+Sharing Defaults
+================
+
+   If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists.  Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Operation Controls
+==================
+
+   `configure' recognizes the following options to control how it
+operates.
+
+`--cache-file=FILE'
+     Use and save the results of the tests in FILE instead of
+     `./config.cache'.  Set FILE to `/dev/null' to disable caching, for
+     debugging `configure'.
+
+`--help'
+     Print a summary of the options to `configure', and exit.
+
+`--quiet'
+`--silent'
+`-q'
+     Do not print messages saying which checks are being made.
+
+`--srcdir=DIR'
+     Look for the package's source code in directory DIR.  Usually
+     `configure' can determine that directory automatically.
+
+`--version'
+     Print the version of Autoconf used to generate the `configure'
+     script, and exit.
+
+`configure' also accepts some other, not widely useful, options.
+
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..b945ea9
--- /dev/null
@@ -0,0 +1,29 @@
+## Process this file with automake to produce Makefile.in  -*- makefile -*-
+
+AUTOMAKE_OPTIONS = gnits 1.4
+
+SUBDIRS = doc intl po lib src config tests
+
+pkgdocdir = $(prefix)/doc/@PACKAGE@
+pkgdoc_DATA = NEWS README TODO
+noinst_DATA = AUTHORS THANKS
+
+DISTCLEANFILES = pref.h
+
+dist-hook:
+       cp -rp $(top_srcdir)/sysdeps $(distdir)
+       cp -rp $(top_srcdir)/examples $(distdir)
+
+# A `private installation' in my terms is just having the appropriate
+# configuration files in ~/.pspp instead of a global configuration
+# location.  So I let those files be installed automatically.
+
+private-install:
+       $(MAKE) private-install -C config
+private-uninstall:
+       $(MAKE) private-uninstall -C config
+
+EXTRA_DIST = NEWS ONEWS TODO pref.h.orig reconfigure configure
+
+MAINTAINERCLEANFILES = Makefile.in configure aclocal.m4 config.h.in
+
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..58d4772
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,260 @@
+PSPP NEWS -- history of user-visible changes.
+Time-stamp: <2000-01-07 20:50:17 blp>
+Copyright (C) 1996-9, 2000 Free Software Foundation, Inc.
+See the end for copying conditions.
+
+Please send PSPP bug reports to bug-gnu-pspp@gnu.org.
+\f
+Version 0.3.0 changes since 0.2.3:
+
+  Bugs fixed:
+
+    * Using alphanumeric variables in functions under AGGREGATE
+      segfaulted.  Fixed.
+
+    * Under certain circumstances, the final case would be omitted
+      from the results of an AGGREGATE operation.  Fixed.
+
+    * Undefined behavior was invoked by referencing a freed pointer
+      under certain circumstances.  Fixed.
+
+    * A wrong record size was displayed when paging the active file to
+      disk.  Fixed.
+
+    * Not having enough temporary space for sorting caused a core
+      dump.  Fixed.
+
+    * Syntax errors in function descriptions on AGGREGATE caused core
+      dumps.  Fixed.
+
+    * A null pointer was dereferenced, causing a core dump, when
+      PERCENTILES was specified on FREQUENCIES.  This fixes the
+      problem, but PSPP still doesn't calculate percentiles.
+
+    * SORT always sorted in ascending order.  Fixed.
+
+    * Some minor memory leaks in the expression parser were removed.
+
+    * Many assertions fixed for strict ANSI C compliance.
+
+  New features:
+
+    * SET ECHO ON now implemented, but turned off by default.
+
+    * PRINT specifier supported on END REPEAT.
+
+  Other:
+
+    * System libgmp2 library is used if installed instead of
+      unconditionally using the included libgmp2 subset.
+
+    * Extensive code cleanup, which continues.
+
+    * Added CORRELATIONS command parser, but not implemented.
+
+Version 0.2.3 changes since 0.2.2:
+
+  Bugs fixed:
+
+    * SPLIT FILE with a string variable caused a core dump.  Fixed.
+
+    * Nested INCLUDEs didn't work.  Fixed.
+
+    * The MATCH FILES procedure set the values of variables not present
+      to 0.  It should have been SYSMIS.  This is now fixed.
+
+    * The REMARK command was too aggressive about skipping lines.  It
+      didn't like being the last command in a file.
+
+    * Comment parsing wasn't consistent with the rest of the code in its
+      idea of where one command ends and another starts.  This meant
+      that sometimes commands would be mysteriously ignored.  Thanks to
+      Dr. Dirk Melcher <BZN-mdksh@t-online.de> for reporting this bug.
+
+    * The TABLE subcommand on MATCH FILES worked only erratically at
+      best.  This fixes it.  Thanks to Dr. Dirk Melcher
+      <BZN-mdksh@t-online.de> for reporting this bug.
+
+    * VARIABLE LABELS rejected a slash before the first variable
+      specification, contradicting the documentation.  Thanks to Walter
+      M. Gray <graywm@northernc.on.ca> for reporting this bug.
+
+    * Because of an incorrect optimization in memory allocation,
+      CROSSTABS sometimes segfaulted when asked to output multiple
+      tables.  Thanks to Walter M. Gray <graywm@northernc.on.ca> for
+      reporting this bug.
+
+    * CROSSTABS didn't display value labels for column and row
+      variables.  Thanks to Walter M. Gray <graywm@northernc.on.ca> for
+      reporting this bug.
+
+    * WRITE didn't write line ends.  Fixed.  Thanks to Dr. Dirk Melcher
+      <BZN-mdksh@t-online.de> for reporting this bug.
+
+    * The TABLE subcommand on MATCH FILES worked only erratically at
+      best.  This fixes it.  Thanks to Dr. Dirk Melcher
+      <BZN-mdksh@t-online.de> for reporting this bug.
+
+    * VARIABLE LABELS rejected a slash before the first variable
+      specification, contradicting the documentation.  Thanks to Walter
+      M. Gray <graywm@northernc.on.ca> for reporting this bug.
+
+    * Because of an incorrect optimization in memory allocation,
+      CROSSTABS sometimes segfaulted when asked to output multiple
+      tables.  Thanks to Walter M. Gray <graywm@northernc.on.ca> for
+      reporting this bug.
+
+    * CROSSTABS didn't display value labels for column and row
+      variables.  Thanks to Walter M. Gray <graywm@northernc.on.ca> for
+      reporting this bug.
+
+    * WRITE didn't write line ends.  Fixed.  Thanks to Dr. Dirk Melcher
+      <BZN-mdksh@t-online.de> for reporting this bug.
+
+    * MATCH FILES corrupted memory and dumped core on some syntax
+      errors.  Fixed.
+
+    * MATCH FILES should set numeric values not available to the
+      system-missing value, not to 0.  Thanks to Dr. Dirk Melcher
+      <BZN-mdksh@t-online.de> for reporting this bug.
+
+    * KEEP didn't work properly on the SAVE procedure.  Fixed.  Thanks
+      to Ralf Geschke <ralf@kuerbis.org> for reporting this bug.
+
+    * Memory leak fix.
+
+    * Some systems didn't like the way open_file was coded.  Thanks to
+      Hankin <hankin@rogue.consultco.com> for pointing this out.
+
+    * The SAVE procedure didn't save long string variables properly.
+      Fixed by this patch.  Thanks to Hankin
+      <hankin@rogue.consultco.com> for this patch.
+
+    * Minor documentation fixes for MATCH FILES.
+
+Version 0.2.2 changes since 0.2.1:
+
+  Bugs fixed:
+
+    * Fix behavior of PRINT SPACE for negative arguments.
+
+    * Fix reading some unusual system files.
+
+    * Fix LIST problems with very long variables.  Thanks to Hankin
+      <hankin@dunno.com> for this bug report.
+
+    * Fix problems with some string format specifiers.
+
+    * Fix use of $CASENUM in expressions.  Thanks to Dirk Melcher
+      <BZN-mdksh@t-online.de> for reporting this bug.
+
+    * Additional DATA LIST FREE and DATA LIST LIST fixes.  Thanks to
+      Hankin <hankin@dunno.com> again on this one.
+
+    * Sometimes you may encounter a PSPP script that has to be
+      interpreted in interactive mode.  Now you can use the -i flag to
+      do this.
+
+    * Warnings for egcs 1.1.1 cleaned up.  (However you'll get lots of
+      `unused variable' warnings under gcc 2.7.2.3, fixing this will
+      take more effort.)
+
+    * Tests fixed.
+
+    * The files in gmp need the internationalization directory in
+      their include path.  Thanks to OKUJI Yoshinori
+      <okuji@kuicr.kyoto-u.ac.jp> for pointing this out.
+
+Version 0.2.1 changes since 0.2.0:
+
+  Bugs fixed:
+
+    * Remember to include examples/ directory in distribution :-)
+
+    * Fixed gmp compile problems for some non-i386 architectures.
+      Thanks to Hans Olav Eggestad <olav@jordforsk.nlh.no> and others
+      for reporting this.
+
+    * DATA LIST FREE and DATA LIST LIST parsing of input files is now
+      matches the documented behavior exactly, and error messages are
+      more helpful.  Thanks to Mark H. Wood <mwood@IUPUI.Edu>.
+
+Version 0.2.0 changes since 0.1.0:
+
+  Procedures now implemented:
+    * CROSSTABS.  Please see documentation for caveats.
+
+  Transformations and utilities now implemented:
+    * AGGREGATE
+    * APPLY DICTIONARY
+    * CLEAR TRANSFORMATIONS
+    * DISPLAY (all subcommands).
+    * ERASE
+    * FLIP
+    * EXPORT
+    * HOST
+    * IMPORT
+    * MATCH FILES
+    * MATRIX DATA
+    * NEW FILE
+    * REPEATING DATA
+
+  Support for input and output through pipes: "|command" and
+  "command|" filenames; support for special filenames "-", "stdin",
+  "stdout", "stderr".
+
+  New command-line features:
+    * New option --testing-mode: Invoke heuristics to assist testing.
+    * New option --safer, -s: Don't allow certain unsafe operations.
+    * New option --command=CMD, -c CMD: Perform literal command CMD.
+    * rc file ~/.pspp/rc is executed before any other files.
+    * Now multiple syntax files can be specified.
+
+  Operator LAG is now implemented.
+
+  Added missing FILE subcommand to REREAD.
+
+  Table output manager completely rewritten.
+
+  Device configuration file syntax changed.  You will need to
+  reinstall your `devices' file.
+
+  New output driver for HTML.
+
+  PostScript driver and prologue simplified.
+
+  Many bugs fixed.  General source-code cleanup.
+
+  Added Texinfo documentation for:
+    * PSPP system file format
+    * PSPP portable file format
+    * How to write input for q2c parser generator
+    * HTML driver
+
+  PSPP language is now fully documented.  Please report any
+  inaccuracies or omissions in the documentation.
+
+Changes for version 0.1.0:
+
+  First public release.  For changes from unreleased development
+  versions, please see ONEWS.
+\f
+----------------------------------------------------------------------
+Copyright information:
+
+Copyright (C) 1996-9, 2000 Free Software Foundation, Inc.
+
+   Permission is granted to anyone to make or distribute verbatim
+   copies of this document as received, in any medium, provided that
+   the copyright notice and this permission notice are preserved, thus
+   giving the recipient permission to redistribute in turn.
+
+   Permission is granted to distribute modified versions of this
+   document, or of portions of it, under the above conditions,
+   provided also that they carry prominent notices stating who last
+   changed them.
+\f
+Local variables:
+version-control: never
+mode: indented-text
+end:
diff --git a/ONEWS b/ONEWS
new file mode 100644 (file)
index 0000000..ec1ff99
--- /dev/null
+++ b/ONEWS
@@ -0,0 +1,540 @@
+PSPP NEWS -- history of user-visible changes.  
+Time-stamp: <1998-08-14 10:45:12 blp>
+Copyright (C) 1996, 1997, 1998 Free Software Foundation, Inc.
+See the end for copying conditions.
+
+Please send PSPP bug reports to bug-gnu-pspp@gnu.org.
+\f
+* Changes for patchlevel 37:
+
+** Bugfixes.
+
+* Changes for patchlevel 36:
+
+** Documentation fixes.
+
+* Changes for patchlevel 35:
+
+** DO REPEAT works.
+
+** Removed PC+ emulation; merged X and Wnd emulations.
+
+** Many smaller bugfixes.
+
+* Changes for patchlevel 34:
+
+** More DO REPEAT work.  Does not compile.
+
+* Changes for patchlevel 33:
+
+** Excised politically incorrect words.
+
+* Changes for patchlevel 32:
+
+** Worked on fixing DO REPEAT problems.
+
+* Changes for patchlevel 31:
+
+** Fixed packaging problems.
+
+* Changes for patchlevel 30:
+
+** Looks for include files and data file relative to the syntax file
+directory, not the current working directory.
+
+* Changes for patchlevel 29:
+
+** Add capability for PSPP syntax files to invoked as programs with
+`#!' notation.
+
+* Changes for patchlevels 20, 21, 22, 23, 24, 25, 26, 27, 28:
+
+** Bugfixes.
+
+* Changes for version 0.1.0:
+
+** Debian package support.
+
+* Changes for patchlevel 19:
+
+** Bugfixes.
+
+** Began PSPP FAQ list.
+
+* Changes for patchlevel 18:
+
+** Scratch variables are fully implemented.  They are deleted after
+every procedure.
+
+** The virtual file manager has been completely rewritten.  Although
+all known bugs have been fixed, the new object-oriented structure to
+vfm is so different that there are likely some that are
+as-yet-undiscovered.
+
+* Changes for patchlevel 14, 15, 16, 17:
+
+** Bugfixes.
+
+* Changes for patchlevels 12, 13:
+
+** Internationalization!
+
+* Changes for patchlevels 7, 8, 9, 10, 11:
+
+** Bugfixes.
+
+* Changes for patchlevel 6:
+
+** Removed the need for a `ps-fontmap' in the PostScript driver.
+This changes the options for the PostScript driver slightly.
+
+* Changes for new patchlevels 1, 2, 3, 4, 5:
+
+** Bugfixes.
+
+* Changes for interim releases x1, x2, x3:
+
+** Package changed from `flat' to `deep' format.
+
+* Changes for patchlevel 193:
+
+** No user-visible changes.
+
+* Changes for patchlevel 192:
+
+** Bugfixes.
+
+* Changes for patchlevel 191:
+
+** Reimplemented FREQUENCIES method of calculation--it should now be
+* acceptable to numerical analysts.
+
+* Changes for patchlevel 190:
+
+** Implemented PROCESS IF to be compatible with PC+.
+
+** Reimplemented DESCRIPTIVES method of calculation--it should now
+be acceptable to numerical analysts.
+
+** DESCRIPTIVES is now correct and complete--please report any bugs
+immediately.
+
+** Implemented SYSFILE INFO, although it is limited in the way it
+displays value labels.
+
+** SAVE now records the number of cases in the system file.
+
+* Changes for patchlevels 189, 188, 187:
+
+** Bugfixes.
+
+* Changes for patchlevel 186:
+
+** Bazillions of bugfixes, and more to come.  This version ought to be
+much more usable than any previous.
+
+** Added RENAME VARIABLES command and tested it.
+
+* Changes for patchlevel 185:
+
+** Added MODIFY VARS command; poorly tested.
+
+** Bugfixes.
+
+* Changes for patchlevel 184:
+
+** Debianized and fixed a few packaging problems.
+
+** First ALPHA release.
+
+** Miscellaneous bugfixes.
+
+* Changes for patchlevel 182:
+
+* Added FILE LABEL, DOCUMENT, and DROP DOCUMENTS commands; not tested.
+
+* Changes for patchlevel 181:
+
+* Added FILTER command.
+
+* Changes for patchlevel 180:
+
+* SORT CASES bugfixes.
+
+* Changes for patchlevel 179:
+
+* SORT CASES implemented.
+
+* Changes for patchlevels 178, 177, 176, 175:
+
+* No user-visible changes; might not even compile.
+
+* Changes for patchlevel 174:
+
+** AUTORECODE has been newly implemented.
+
+* Changes for patchlevel 173:
+
+** Bugfixes.
+
+* Changes for patchlevel 172:
+
+** SET has been reintroduced.  It is somewhat incomplete.
+
+** Bugfixes.
+
+* Changes for patchlevel 171:
+
+** Several bugfixes.
+
+** Minor language improvements.
+
+* Changes for patchlevel 170:
+
+** Input/output formats DOT, PCT, EDATE, SDATE are now supported but
+not tested.
+
+* Changes for patchlevel 169:
+
+** Several bugfixes.
+
+** Implemented custom currency formats (CCA ... CCE); not tested.
+
+* Changes for patchlevel 168:
+
+** No user-visible changes.
+
+* Changes for patchlevel 167:
+
+** Compression is now available on SAVE and XSAVE.
+
+* Changes for patchlevel 166:
+
+** SAVE and XSAVE are implemented.  Compression is not yet available.
+
+* Changes for patchlevel 165:
+
+** GET is now fully implemented for both compressed and uncompressed
+system files.
+
+* Changes for patchlevel 164:
+
+** GET now works on system files (uncompressed only).
+
+* Changes for patchlevels 163, 162, 161, 160, 159, 158:
+
+** No user-visible changes.
+
+** Supports keywords LOWEST and HIGHEST on MISSING VALUES.
+
+* Changes for patchlevel 157:
+
+** Fixed longtime bug with cross-compilation.
+
+* Changes for patchlevel 156:
+
+** Fixed the (known) bugs introduced in patchlevel 155.
+
+** Fixed a longtime bug in RECODE that might have affected other
+transformations as well.
+
+* Changes for patchlevel 155:
+
+** A few bugs fixed, probably several introduced.
+
+* Changes for patchlevel 154:
+
+** FILE HANDLE now supports most of the SPSS/Wnd compatible features.
+
+* Changes for patchlevel 153:
+
+** PRINT now supports OUTFILE.
+
+** WRITE is now distinct from PRINT.
+
+** RECODE, SAMPLE, SELECT IF are re-enabled.
+
+* Changes for patchlevel 152:
+
+** Bugfixes for times & dates.
+
+** Misc. bugfixes.
+
+** System variables supported on expressions.
+
+* Changes for patchlevel 151:
+
+** Newly implemented input/output formats:
+
+Time/date output formats.
+Preliminary testing has been done on times & dates.
+
+* Changes for patchlevel 150:
+
+** Newly implemented input/output formats
+
+Zoned decimal input/output format.
+Time/date input formats, but not output formats.
+All of these are untested.
+
+* Changes for patchlevel 149:
+
+** Bugfixes.
+
+* Changes for patchlevel 148:
+
+** Many bugfixes.
+
+** Re-enabled the following transformations:
+
+LEAVE, NUMERIC, PRINT, PRINT EJECT, PRINT FORMATS, PRINT SPACE,
+STRING, TITLE, WRITE.
+
+* Changes for patchlevel 147:
+
+** Crushed partial tables are much better.
+
+* Changes for patchlevel 146:
+
+** Bugfixes.
+
+** Crushed tables are working better!
+
+** Still pretty broken.
+
+* Changes for patchlevel 145:
+
+** Bugfixes.
+
+** Broken stuff.
+
+* Changes for patchlevels 144, 143:
+
+** Bugfixes.
+
+* Changes for patchlevel 142:
+
+** LIST procedure is back, but not well-implemented.
+
+* Changes for patchlevel 141:
+
+** No user-visible changes.
+
+* Changes for patchlevels 140, 139, 138:
+
+** Worked on manual.
+
+** Minor bugfixes.
+
+* Changes for patchlevel 136:
+
+** Began revisions to manual.
+
+** Changed default path for configuration files.
+
+* Changes for patchlevel 135:
+
+** PostScript driver bugfixes.
+
+** Many memory leaks eliminated.
+
+** Miscellaneous Bugfixes.
+
+* Changes for patchlevel 134:
+
+** SPLIT FILE works again.
+
+** Documentation changes in README.
+
+** New documentation in LANGUAGE, BUGS.
+
+* Changes for patchlevel 133:
+
+** PostScript driver supports encodings.
+
+It also works now, as opposed to the brokenness of the last
+patchlevel.
+
+* Changes for patchlevel 132:
+
+** PostScript driver supports font changes!
+
+Not well tested.
+
+* Changes for patchlevel 131:
+
+** Does not compile.
+
+* Changes for patchlevel 130:
+
+** Generated PostScript code is smaller in size.
+
+This is because, as long as the PostScript option `optimize-line-size'
+is at least 1, individual contiguous short lines are consolidated into
+longer monster lines.
+
+* Changes for patchlevel 129:
+
+** PostScript output much improved.
+
+Mirror no longer necessary.
+
+* Changes for patchlevel 128:
+
+** Try out the PostScript driver, if you've got a mirror handy.
+
+* Changes for patchlevel 126:
+
+** Does not compile.
+
+* Changes for patchlevel 125:
+
+** No user-visible changes.
+
+* Changes for patchlevel 124:
+
+** PostScript driver.  Don't use it yet.
+
+** Bugfixes.
+
+* Changes for patchlevel 123:
+
+** No user-visible changes.
+
+* Changes for patchlevel 122:
+
+** FREQUENCIES procedure is more complete.
+
+It can now print out sorted frequency tables as well as all statistics
+except median.  No percentiles.  Full syntax.  No integer mode.
+
+* Changes for patchlevel 121:
+
+** Compiles again!
+
+** FREQUENCIES procedure works but it is incomplete.
+
+* Changes for patchlevels 120, 119:
+
+** Does not compile.
+
+* Changes for patchlevel 118:
+
+** Does not compile. 
+
+** Bugfix regarding titles on LIST procedure.
+
+* Changes for patchlevel 117:
+
+** LIST procedure implemented.
+
+** Bugfix regarding unsupported REMARK utility.
+
+* Changes for patchlevel 116:
+
+** Does not compile.
+
+* Changes for patchlevel 115:
+
+** New output driver initialization interface. 
+
+*** Changed option syntax.
+
+`-o driver' is the new syntax.  The default driver is named `default'.
+
+*** The initialization file `output' has been renamed `devices'.
+
+*** Driver names actually specify categories.
+
+Each driver name specified can actually result in 0, 1, 2, or any
+greater number of actual drivers being used, depending solely on the
+contents of the `devices' output initialization file.
+
+*** The driver initialization file is read in a `termcap'-like manner.
+
+That is, it determines whether to use a driver based on the parameters
+passed to it, rather than mainly on the contents of the `devices' file
+plus some goofy hacks with command-line options.
+
+*** Macros defined in the `devices' file can be overridden.
+
+Do it by specifying a definition on the command line of form
+`KEY=VALUE'.  See `devices' for details.
+
+** Short form of option `--verbose' changed to `-V'.
+
+** New option `-v' or `--verbose'.
+
+`-v' causes PSPP to display more info about what it's doing.
+Multiple `-v's display even more.
+
+** Support for small 25-line screens. 
+
+The ASCII driver minimum for page length is now 15 lines instead of
+29.
+
+* Changes for patchlevel 114:
+
+** Rich text now supported in the ascii driver.
+
+The style changes are done with overstriking or with defined
+sequences.
+
+** New ascii output driver option `carriage-return-style'.
+
+This can be set to `cr' or to `bs', depending on whether returning to
+the left margin should be done with an ASCII CR or with multiple
+backspaces.
+
+* Changes for patchlevel 113:
+
+** Table titles are more complete.
+
+Now they include a description of the table contents.
+
+* Changes for patchlevel 112:
+
+** Tables now are preceded by a descriptive `title'.
+
+This line shows what procedure emitted it, etc.
+
+** Some tables are now divided into multiple columns.
+
+These columns are displayed across the page in order to save vertical
+space.
+
+* Changes for patchlevel 111:
+
+** Bugfixes.
+
+* Changes for patchlevel 110:
+
+** `stat' has now been renamed `PSPP', for `PSPP Implements Accurate
+Statistical COmputations'!  Let's all celebrate the clever acronym!
+
+** Bugfixes.
+
+* Changes for patchlevel 109:
+
+** Bugfixes.
+\f
+----------------------------------------------------------------------
+Copyright information:
+
+Copyright (C) 1996, 1997, 1998 Free Software Foundation, Inc.
+
+   Permission is granted to anyone to make or distribute verbatim
+   copies of this document as received, in any medium, provided that
+   the copyright notice and this permission notice are preserved, thus
+   giving the recipient permission to redistribute in turn.
+
+   Permission is granted to distribute modified versions of this
+   document, or of portions of it, under the above conditions,
+   provided also that they carry prominent notices stating who last
+   changed them.
+\f
+Local variables:
+version-control: never
+mode: text
+mode: outline-minor
+end:
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..51fb8ea
--- /dev/null
+++ b/README
@@ -0,0 +1,18 @@
+PSPP is a program for statistical analysis of sampled data. It
+interprets commands in the SPSS language and produces tabular output
+in ASCII or PostScript format.
+
+PSPP development is ongoing. It already supports a large subset of
+SPSS's transformation language.  Its statistical procedure support is
+currently limited, but growing.
+
+Source code for the latest development release of PSPP is available at
+ftp://alpha.gnu.org/gnu/pspp and ftp://pspp.stat.wisc.edu/pub/PSPP.
+   
+For information on differences from previous versions, please see file
+NEWS.  Full documentation on PSPP's language and information on known
+bugs can in the doc/ directory.
+          
+Questions and comments regarding PSPP can be sent to Ben Pfaff
+<blp@gnu.org>.  PSPP bug reports should be sent to
+bug-gnu-pspp@gnu.org.
diff --git a/THANKS b/THANKS
new file mode 100644 (file)
index 0000000..914eadf
--- /dev/null
+++ b/THANKS
@@ -0,0 +1,12 @@
+Thanks to...
+                                       
+     * David MacKenzie for writing Autoconf, the automatic configuration
+       tool.
+     * David MacKenzie and Tom Tromey for writing Automake, the tool for
+       generating `Makefile's.
+     * Ulrich Drepper et al for writing gettext, the GNU
+       internationalization package.
+     * François Pinard for advice on proceeding with development.
+     * Jim Van Zandt for Debian packaging and suggestions.
+     * Torbj"orn Granlund and TMG Datakonsult for GNU gmp2 used in the 
+       portable file routines.
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..7486135
--- /dev/null
+++ b/TODO
@@ -0,0 +1,334 @@
+Time-stamp: <1999-12-30 22:58:42 blp>
+
+TODO
+----
+
+The way that data-in.c and data-out.c deal with strings is wrong.  Instead of
+the way it's done now, we should make it dynamically allocate a buffer and
+return a pointer to it.  This is a much safer interface.
+
+Add libplot output driver.  Suggested by Robert S. Maier
+<rsm@math.arizona.edu>: "it produces output in idraw-editable PS format, PCL5
+format, xfig-editable format, Illustrator format,..., and can draw vector
+graphics on X11 displays also".
+
+Storage of value labels on disk is inefficient.  Invent new data structure.
+
+Add an output flag which would cause a page break if a table segment could fit
+vertically on a page but it just happens to be positioned such that it won't.
+
+Fix spanned joint cells, i.e., EDLEVEL on crosstabs.stat.
+
+Cell footnotes.
+
+PostScript driver should emit thin lines, then thick lines, to optimize time
+and space.
+
+New functions?  var_name_or_label(), tab_value_or_label()
+
+Should be able to bottom-justify cells.  It'll be expensive, though, by
+requiring an extra metrics call.
+
+Perhaps instead of the current lines we should define the following line types:
+null, thin, thick, double.  It might look pretty classy.
+
+Perhaps thick table borders that are cut off by a page break should decay to
+thin borders.  (i.e., on a thick bordered table that's longer than one page,
+but narrow, the bottom border would be thin on the first page, and the top and
+bottom borders on middle pages.)
+
+Support multi-line titles on tables. (For the first page only, presumably.)
+
+Rewrite the convert_F() function in data-out.c to be nicer code.
+
+In addition to searching the source directory, we should search the current
+directory (for data files).  (Yuck!)
+
+Fix line-too-long problems in PostScript code, instead of covering them up.
+setlinecap is *not* a proper solution.
+
+Need a better way than MAX_WORKSPACE to detect low-memory conditions.
+
+When malloc() returns 0, page to disk and free() unnecessary data.
+
+Remove ccase * argument from procfunc argument to procedure().
+
+See if process_active_file() has wider applicability.
+
+Looks like there's a potential problem with value labels--we use free_val_lab
+from avl_destroy(), but free_val_lab doesn't decrement the reference count, it
+just frees the label.  Check into this sometime soon.
+
+Eliminate private data in struct variable through use of pointers.
+
+Fix som_columns().
+
+There needs to be another layer onto the lexer, which should probably be
+entirely rewritten anyway.  The lexer needs to read entire *commands* at a
+time, not just a *line* at a time.  This would vastly simplify the
+(yet-to-be-implemented) logging mechanism and other stuff as well.
+          
+Has glob.c been pared down enough?
+
+Improve interactivity of output by allowing a `commit' function for a page.
+This will also allow for infinite-length pages.
+
+All the tests need to be looked over.  Some of the SET calls don't make sense
+any more.
+
+Implement thin single lines, should be pretty easy now.
+
+SELECT IF should be moved before other transformations whenever possible.  It
+should only be impossible when one of the variables referred to in SELECT IF is
+created or modified by a previous transformation.
+
+The manual: add text, add index entries, add examples.
+
+The inline file should be improved: There should be *real* detection of whether
+it is used (in dfm.c:cmd_begin_data), not after-the-fact detection.
+
+Figure out a stylesheet for messages displayed by PSPP: i.e., what quotation
+marks around filenames, etc.
+
+Data input and data output are currently arranged in reciprocal pairs: input is
+done directly, with write_record() or whatever; output is done on a callback
+event-driven basis.  It would definitely be easier if both could be done on a
+direct basis, with read_record() and write_record() routines, with a coroutine
+implementation (see Knuth).  But I'm not sure that coroutines can be
+implemented in ANSI C.  This will require some thought.  Perhaps 0.4.0 can do
+this.
+
+New SET subcommand: OUTPUT.  i.e., SET OUTPUT="filename" to send output to that
+file; SET OUTPUT="filename"(APPEND) to append to that file; SET OUTPUT=DEFAULT
+to reset everything.  There might be a better approach, though--think about it.
+
+HDF export capabilities (http://hdf.ncsa.uiuc.edu).  Suggested by Marcus
+G. Daniels <mgd@santafe.edu>.
+
+From Zvi Grauer <z.grauer@csuohio.edu> and <zvi@mail.ohio.net>:
+
+   1. design of experiments software, specifically Factorial, response surface
+   methodology and mixrture design.  
+
+   These would be EXTREMELY USEFUL for chemists, engineeris, and anyone
+   involved in the production of chemicals or formulations.
+
+   2. Multidimensional Scaling analysis (for market analysis) - 
+
+   3. Preference mapping software for market analysis
+
+   4. Hierarchical clustering (as well as partition clustering)
+
+   5. Conjoint analysis
+
+   6. Categorical data analsys ?
+
+IDEAS
+-----
+
+In addition to an "infinite journal", we should keep a number of
+individual-session journals, pspp.jnl-1 through pspp.jnl-X, renaming and
+deleting as needed.  All of the journals should have date/time comments.
+
+Qualifiers for variables giving type--categorical, ordinal, ...
+
+Analysis Wizard
+
+Consider consequences of xmalloc(), fail(), hcf() in interactive
+use:
+a. Can we safely just use setjmp()/longjmp()?
+b. Will that leak memory?
+i. I don't think so: all procedure-created memory is either
+garbage-collected or globally-accessible.
+ii. But you never know... esp. w/o Checker.
+c. Is this too early to worry? too late?
+
+Need to implement a shared buffer for funny functions that require relatively
+large permanent transient buffers (1024 bytes or so), that is, buffers that are
+permanent in the sense that they probably shouldn't be deallocated but are only
+used from time to time, buffers that can't be allocated on the stack because
+they are of variable and unpredictable but usually relatively small (usually
+line buffers).  There are too many of these lurking around; can save a sizeable
+amount of space at very little overhead and with very little effort by merging
+them.
+
+Clever multiplatform GUI idea (due partly to John Williams): write a GUI in
+Java where each statistical procedure dialog box could be downloaded from the
+server independently.  The statistical procedures would run on (the/a) server
+and results would be reported through HTML tables viewed with the user's choice
+of web browsers.  Help could be implemented through the browser as well.
+
+Design a plotting API, with scatterplots, line plots, pie charts, barcharts,
+Pareto plots, etc., as subclasses of the plot superclass.
+
+HOWTOs
+------
+
+1. How to add an operator for use in PSPP expressions:
+
+a. Add the operator to the enumerated type at the top of expr.h.  If the
+operator has arguments (i.e., it's not a terminal) then add it *before*
+OP_TERMINAL; otherwise, add it *after* OP_TERMINAL.  All these begin with OP_.
+
+b. If the operator's a terminal then you'll want to design a structure to hold
+its content.  Add the structure to the union any_node.  (You can also reuse one
+of the prefab structures, of course.)
+
+c. Now switch to expr-prs.c--the module for expression parsing.  Insert the
+operator somewhere in the precedence hierarchy.
+
+(1) If you're adding a operator that is a function (like ACOS, ABS, etc.) then
+add the function to functab in `void init_functab(void)'.  Order is not
+important here.  The first element is the function name, like "ACOS".  The
+second is the operator enumerator you added in expr.h, like OP_ARCOS.  The
+third element is the C function to parse the PSPP function.  The predefined
+functions will probably suit your needs, but if not, you can write your own.
+The fourth element is an argument to the parsing function; it's only used
+currently by generic_str_func(), which handles a rather general syntax for
+functions that return strings; see the comment at the beginning of its code for
+details.
+
+(2) If you're adding an actual operator you'll have to put a function in
+between two of the operators there already in functions `exprtype
+parse_*(any_node **n)'.  Each of these stores the tree for its result into *n,
+and returns the result type, or EX_ERROR on error.  Be sure to delete all the
+allocated memory on error before returning.
+
+d. Add the operator to the table `op_desc ops[OP_SENTINEL+1]' in expr-prs.c,
+which has an entry for every operator.  These entries *must* be in the same
+order as they are in expr.h.  The entries have the form `op(A,B,C,D)'.  A is
+the name of the operator as it should be printed in a postfix output format.
+For example, the addition operator is printed as `plus'.  B is a bitmapped set
+of flags:
+
+* Set the 001 bit (OP_VAR_ARGS) if the operator takes a variable number of
+arguments.  If a function can take, say, two args or three args, but no other
+numbers of args, this is a poor way to do it--instead implement the operator as
+two separate operators, one with two args, the other with three.  (The main
+effect of this bit is to cause the number of arguments to be output to the
+postfix form so that the expression evaluator can know how many args the
+operator takes.  It also causes the expression optimizer to calculate the
+needed stack height differently, without referencing C.)
+
+* Set the 002 bit (OP_MIN_ARGS) if the operator can take an optional `dotted
+argument' that specified the minimum number of non-SYSMIS arguments in order to
+have a non-SYSMIS result.  For instance, MIN.3(e1,e2,e3,e4,e5) returns a
+non-SYSMIS result only if at least 3 out of 5 of the expressions e1 to e5 are
+not missing.
+
+Minargs are passed in the nonterm_node structure in `arg[]''s elements past
+`n'--search expr-prs.c for the words `terrible crock' for an example of this.
+
+Minargs are output to the postfix form.  A default value is output if none was
+specified by the user.
+
+You can use minargs for anything you want--they're not limited to actually
+describing a minimum number of valid arguments; that's just what they're most
+*commonly* used for.
+
+* Set the 004 bit (OP_FMT_SPEC) if the operator has an argument that is a
+format specifier.  (This causes the format specifier to be output to the
+postfix representation.)
+
+Format specs are passed in the nonterm_node structure in the same way as
+minargs, except that there are three args, in this order: type, width, # of
+decimals--search expr-prs.c for the words `is a crock' for an example of this.
+
+* Set the 010 bit (OP_ABSORB_MISS) if the operator can *ever* have a result of
+other than SYSMIS when given one or more arguments of SYSMIS.  Operators
+lacking this bit and known to have a SYSMIS argument are short-circuited to
+SYSMIS by the expression optimizer.
+
+* If your operator doesn't fit easily into the existing categories,
+congratulations, you get to write lots of code to adjust everything to cope
+with this new operator.  Are you really sure you want to do that?
+
+C is the effect the operator has on stack height.  Set this to `varies' if the
+operator has a variable number of arguments.  Otherwise this 1, minus the
+number of arguments the operator has.  (Since terminals have no arguments, they
+have a value of +1 for this; other operators have a value of 0 or less.)
+
+D is the number of items output to the postfix form after the operator proper.
+This is 0, plus 1 if the operator has varargs, plus 1 if the operator has
+minargs, plus 3 if the operator has a format spec.  Note that minargs/varargs
+can't coexist with a format spec on the same operator as currently coded.  Some
+terminals also have a nonzero value for this but don't fit into the above
+categories.
+
+e. Switch to expr-opt.c.  Add code to evaluate_tree() to evaluate the
+expression when all arguments are known to be constants.  Pseudo-random
+functions can't be evaluated even if their arguments are constants.  If the
+function can be optimized even if its arguments aren't all known constants, add
+code to optimize_tree() to do it.
+
+f. Switch to expr-evl.c.  Add code to evaluate_expression() to evaluate the
+expression.  You must be absolutely certain that the code in evaluate_tree(),
+optimize_tree(), and evaluate_expression() will always return the same results,
+otherwise users will get inconsistent results, a Bad Thing.  You must be
+certain that even on boundary conditions users will get identical results, for
+instance for the values 0, 1, -1, SYSMIS, or, for string functions, the null
+string, 1-char strings, and 255-char strings.
+
+g. Test the code.  Write some test syntax files.  Examine the output carefully.
+
+NOTES ON SEARCH ALGORITHMS
+--------------------------
+
+1. Trees are nicer when you want a sorted table.  However, you can always
+sort a hash table after you're done adding values.
+
+2. Brent's variation of Algorithm D is best when the table is fixed: it's
+memory-efficient, having small, fixed overhead.  It's easier to use
+when you know in advance how many entries the table will contain.
+
+3. Algorithm L is rather slow for a hash algorithm, however it's easy.
+
+4. Chaining is best in terms of speed; ordered/self-ordering is even
+better.
+
+5. Rehashing is slow.
+
+6. Might want to decide on an algorithm empirically since there are no
+clear mathematical winners in some cases.
+
+7. gprof?  Hey, it works!
+
+MORE NOTES/IDEAS/BUGS
+---------------------
+
+The behavior of converting a floating point to an integer when the value of the
+float is out of range of the integer type is UNDEFINED!  See ANSI 6.2.1.3.
+
+What should we do for *negative* times in expressions?
+
+Sometimes very wide (or very tall) columns can occur in tables.  What is a good
+way to truncate them?  It doesn't seem to cause problems for the ascii or
+postscript drivers, but it's not good in the general case.  Should they be
+split somehow?  (One way that wide columns can occur is through user request,
+for instance through a wide PRINT request--try time-date.stat with a narrow
+ascii page or with the postscript driver on letter size paper.)
+
+NULs in input files break the products we're replacing: although it will input
+them properly and display them properly as AHEX format, it truncates them in A
+format.  Also, string-manipulation functions such as CONCAT truncate their
+results after the first NUL.  This should simplify the result of PSPP design.
+Perhaps those ugly a_string, b_string, ..., can all be eliminated.
+
+From Moshe Braner <mbraner@nessie.vdh.state.vt.us>: An idea regarding MATCH
+FILES, again getting BEYOND the state of SPSS: it always bothered me that if I
+have a large data file and I want to match it to a small lookup table, via
+MATCH FILES FILE= /TABLE= /BY key, I need to SORT the large file on key, do the
+match, then (usually) re-sort back into the order I really want it.  There is
+no reason to do this, when the lookup table is small.  Even a dumb sequential
+search through the table, for every case in the big file, is better, in some
+cases, than the sort.  So here's my idea: first look at the /TABLE file, if it
+is "small enough", read it into memory, and create an index (or hash table,
+whatever) for it.  Then read the /FILE and use the index to match to each case.
+OTOH, if the /TABLE is too large, then do it the old way, complaining if either
+file is not sorted on key.
+
+-------------------------------------------------------------------------------
+Local Variables:
+mode: text
+fill-column: 79
+End:
diff --git a/acconfig.h b/acconfig.h
new file mode 100644 (file)
index 0000000..4c06d67
--- /dev/null
@@ -0,0 +1,84 @@
+/* Special definitions, to process by autoheader.
+   Copyright (C) 1997-9, 2000 Free Software Foundation. */
+
+/* Definitions for byte order, according to significance of bytes, from low
+   addresses to high addresses.  The value is what you get by putting '4'
+   in the most significant byte, '3' in the second most significant byte,
+   '2' in the second least significant byte, and '1' in the least
+   significant byte.  These definitions never need to be modified. */
+#define BIG    4321    /* 68k */
+#define LITTLE  1234   /* i[3456]86 */
+#define UNKNOWN 0000   /* Endianness must be determined at runtime. */
+
+/* Definitions for floating-point representation. */
+#define FPREP_IEEE754  754     /* The usual IEEE-754 format. */
+#define FPREP_UNKNOWN  666     /* Triggers an error at compile time. */
+
+/* We want prototypes for all the GNU extensions. */
+#define _GNU_SOURCE    1
+
+/* Name of the distribution. */
+#define PACKAGE "PSPP"
+
+/* Version of the distribution. */
+#undef VERSION
+
+/* The concatenation of the strings "GNU ", and PACKAGE.  */
+#define GNU_PACKAGE "GNU PSPP"
+
+/* Define to 1 if ANSI function prototypes are usable.  */
+#undef PROTOTYPES
+
+
+@TOP@
+
+/* Define if sprintf() returns the number of characters written to
+   the destination string, excluding the null terminator. */
+#undef HAVE_GOOD_SPRINTF
+
+/* Define if rand() and company work according to ANSI. */
+#undef HAVE_GOOD_RANDOM
+
+/* Define endianness of computer here as BIG or LITTLE, if known.
+   If not known, define as UNKNOWN. */
+#define ENDIAN BIG
+
+/* Define as floating-point representation of this computer.  For
+   i386, m68k, and other common chips, this is FPREP_IEEE754. */
+#define FPREP FPREP_IEEE754
+
+/* Number of digits in longest `long' value, including sign.  This is
+   usually 11, for 32-bit `long's, or 19, for 64-bit `long's. */
+#define INT_DIGITS 19
+
+/* Define if you have the history library (-lhistory).  */
+#undef HAVE_LIBHISTORY
+
+/* Define if you have the termcap library (-ltermcap).  */
+#undef HAVE_LIBTERMCAP
+
+/* Stolen from Ulrich Drepper, <drepper@gnu.org> gettext-0.10,
+   1995.  */
+
+/* Define if your locale.h file contains LC_MESSAGES.  */
+#undef HAVE_LC_MESSAGES
+
+/* Define to 1 if NLS is requested.  */
+#undef ENABLE_NLS
+
+/* Define as 1 if you have catgets and don't want to use GNU gettext.  */
+#undef HAVE_CATGETS
+
+/* Define as 1 if you have gettext and don't want to use GNU gettext.  */
+#undef HAVE_GETTEXT
+
+/* Define as 1 if you have the stpcpy function.  */
+#undef HAVE_STPCPY
+
+@BOTTOM@
+
+#include <pref.h>
+
+/* Local Variables: */
+/* mode:c */
+/* End: */
diff --git a/acinclude.m4 b/acinclude.m4
new file mode 100644 (file)
index 0000000..5f81131
--- /dev/null
@@ -0,0 +1,433 @@
+dnl --------------------------------------------------------- ##
+dnl The following definitions are from gettext-0.10.27.              ##
+dnl --------------------------------------------------------- ##
+
+# Macro to add for using GNU gettext.
+# Ulrich Drepper <drepper@cygnus.com>, 1995.
+
+# serial 2
+
+AC_DEFUN(AM_WITH_NLS,
+  [AC_MSG_CHECKING([whether NLS is requested])
+    dnl Default is enabled NLS
+    AC_ARG_ENABLE(nls,
+      [  --disable-nls           do not use Native Language Support],
+      USE_NLS=$enableval, USE_NLS=yes)
+    AC_MSG_RESULT($USE_NLS)
+    AC_SUBST(USE_NLS)
+
+    USE_INCLUDED_LIBINTL=no
+
+    dnl If we use NLS figure out what method
+    if test "$USE_NLS" = "yes"; then
+      AC_DEFINE(ENABLE_NLS)
+      AC_MSG_CHECKING([whether included gettext is requested])
+      AC_ARG_WITH(included-gettext,
+        [  --with-included-gettext use the GNU gettext library included here],
+        nls_cv_force_use_gnu_gettext=$withval,
+        nls_cv_force_use_gnu_gettext=no)
+      AC_MSG_RESULT($nls_cv_force_use_gnu_gettext)
+
+      nls_cv_use_gnu_gettext="$nls_cv_force_use_gnu_gettext"
+      if test "$nls_cv_force_use_gnu_gettext" != "yes"; then
+        dnl User does not insist on using GNU NLS library.  Figure out what
+        dnl to use.  If gettext or catgets are available (in this order) we
+        dnl use this.  Else we have to fall back to GNU NLS library.
+       dnl catgets is only used if permitted by option --with-catgets.
+       nls_cv_header_intl=
+       nls_cv_header_libgt=
+       CATOBJEXT=NONE
+
+       AC_CHECK_HEADER(libintl.h,
+         [AC_CACHE_CHECK([for gettext in libc], gt_cv_func_gettext_libc,
+           [AC_TRY_LINK([#include <libintl.h>], [return (int) gettext ("")],
+              gt_cv_func_gettext_libc=yes, gt_cv_func_gettext_libc=no)])
+
+          if test "$gt_cv_func_gettext_libc" != "yes"; then
+            AC_CHECK_LIB(intl, bindtextdomain,
+              [AC_CACHE_CHECK([for gettext in libintl],
+                gt_cv_func_gettext_libintl,
+                [AC_TRY_LINK([], [return (int) gettext ("")],
+                gt_cv_func_gettext_libintl=yes,
+                gt_cv_func_gettext_libintl=no)])])
+          fi
+
+          if test "$gt_cv_func_gettext_libc" = "yes" \
+             || test "$gt_cv_func_gettext_libintl" = "yes"; then
+             AC_DEFINE(HAVE_GETTEXT)
+             AM_PATH_PROG_WITH_TEST(MSGFMT, msgfmt,
+               [test -z "`$ac_dir/$ac_word -h 2>&1 | grep 'dv '`"], no)dnl
+             if test "$MSGFMT" != "no"; then
+               AC_CHECK_FUNCS(dcgettext)
+               AC_PATH_PROG(GMSGFMT, gmsgfmt, $MSGFMT)
+               AM_PATH_PROG_WITH_TEST(XGETTEXT, xgettext,
+                 [test -z "`$ac_dir/$ac_word -h 2>&1 | grep '(HELP)'`"], :)
+               AC_TRY_LINK(, [extern int _nl_msg_cat_cntr;
+                              return _nl_msg_cat_cntr],
+                 [CATOBJEXT=.gmo
+                  DATADIRNAME=share],
+                 [CATOBJEXT=.mo
+                  DATADIRNAME=lib])
+               INSTOBJEXT=.mo
+             fi
+           fi
+       ])
+
+        if test "$CATOBJEXT" = "NONE"; then
+         AC_MSG_CHECKING([whether catgets can be used])
+         AC_ARG_WITH(catgets,
+           [  --with-catgets          use catgets functions if available],
+           nls_cv_use_catgets=$withval, nls_cv_use_catgets=no)
+         AC_MSG_RESULT($nls_cv_use_catgets)
+
+         if test "$nls_cv_use_catgets" = "yes"; then
+           dnl No gettext in C library.  Try catgets next.
+           AC_CHECK_LIB(i, main)
+           AC_CHECK_FUNC(catgets,
+             [AC_DEFINE(HAVE_CATGETS)
+              INTLOBJS="\$(CATOBJS)"
+              AC_PATH_PROG(GENCAT, gencat, no)dnl
+              if test "$GENCAT" != "no"; then
+                AC_PATH_PROG(GMSGFMT, gmsgfmt, no)
+                if test "$GMSGFMT" = "no"; then
+                  AM_PATH_PROG_WITH_TEST(GMSGFMT, msgfmt,
+                   [test -z "`$ac_dir/$ac_word -h 2>&1 | grep 'dv '`"], no)
+                fi
+                AM_PATH_PROG_WITH_TEST(XGETTEXT, xgettext,
+                  [test -z "`$ac_dir/$ac_word -h 2>&1 | grep '(HELP)'`"], :)
+                USE_INCLUDED_LIBINTL=yes
+                CATOBJEXT=.cat
+                INSTOBJEXT=.cat
+                DATADIRNAME=lib
+                INTLDEPS="../intl/libintl.a"
+                INTLLIBS=$INTLDEPS
+                LIBS=`echo $LIBS | sed -e 's/-lintl//'`
+                nls_cv_header_intl=intl/libintl.h
+                nls_cv_header_libgt=intl/libgettext.h
+              fi])
+         fi
+        fi
+
+        if test "$CATOBJEXT" = "NONE"; then
+         dnl Neither gettext nor catgets in included in the C library.
+         dnl Fall back on GNU gettext library.
+         nls_cv_use_gnu_gettext=yes
+        fi
+      fi
+
+      if test "$nls_cv_use_gnu_gettext" = "yes"; then
+        dnl Mark actions used to generate GNU NLS library.
+        INTLOBJS="\$(GETTOBJS)"
+        AM_PATH_PROG_WITH_TEST(MSGFMT, msgfmt,
+         [test -z "`$ac_dir/$ac_word -h 2>&1 | grep 'dv '`"], msgfmt)
+        AC_PATH_PROG(GMSGFMT, gmsgfmt, $MSGFMT)
+        AM_PATH_PROG_WITH_TEST(XGETTEXT, xgettext,
+         [test -z "`$ac_dir/$ac_word -h 2>&1 | grep '(HELP)'`"], :)
+        AC_SUBST(MSGFMT)
+       USE_INCLUDED_LIBINTL=yes
+        CATOBJEXT=.gmo
+        INSTOBJEXT=.mo
+        DATADIRNAME=share
+       INTLDEPS="../intl/libintl.a"
+       INTLLIBS=$INTLDEPS
+       LIBS=`echo $LIBS | sed -e 's/-lintl//'`
+        nls_cv_header_intl=intl/libintl.h
+        nls_cv_header_libgt=intl/libgettext.h
+      fi
+
+      dnl Test whether we really found GNU xgettext.
+      if test "$XGETTEXT" != ":"; then
+       dnl If it is no GNU xgettext we define it as : so that the
+       dnl Makefiles still can work.
+       if $XGETTEXT --omit-header /dev/null 2> /dev/null; then
+         : ;
+       else
+         AC_MSG_RESULT(
+           [found xgettext programs is not GNU xgettext; ignore it])
+         XGETTEXT=":"
+       fi
+      fi
+
+      # We need to process the po/ directory.
+      POSUB=po
+    else
+      DATADIRNAME=share
+      nls_cv_header_intl=intl/libintl.h
+      nls_cv_header_libgt=intl/libgettext.h
+    fi
+
+    # If this is used in GNU gettext we have to set USE_NLS to `yes'
+    # because some of the sources are only built for this goal.
+    if test "$PACKAGE" = gettext; then
+      USE_NLS=yes
+      USE_INCLUDED_LIBINTL=yes
+    fi
+
+    dnl These rules are solely for the distribution goal.  While doing this
+    dnl we only have to keep exactly one list of the available catalogs
+    dnl in configure.in.
+    for lang in $ALL_LINGUAS; do
+      GMOFILES="$GMOFILES $lang.gmo"
+      POFILES="$POFILES $lang.po"
+    done
+
+    dnl Make all variables we use known to autoconf.
+    AC_SUBST(USE_INCLUDED_LIBINTL)
+    AC_SUBST(CATALOGS)
+    AC_SUBST(CATOBJEXT)
+    AC_SUBST(DATADIRNAME)
+    AC_SUBST(GMOFILES)
+    AC_SUBST(INSTOBJEXT)
+    AC_SUBST(INTLDEPS)
+    AC_SUBST(INTLLIBS)
+    AC_SUBST(INTLOBJS)
+    AC_SUBST(POFILES)
+    AC_SUBST(POSUB)
+  ])
+
+AC_DEFUN(AM_GNU_GETTEXT,
+  [AC_REQUIRE([AC_PROG_MAKE_SET])dnl
+   AC_REQUIRE([AC_PROG_CC])dnl
+   AC_REQUIRE([AC_ISC_POSIX])dnl
+   AC_REQUIRE([AC_HEADER_STDC])dnl
+   AC_REQUIRE([AC_C_CONST])dnl
+   AC_REQUIRE([AC_C_INLINE])dnl
+   AC_REQUIRE([AC_TYPE_OFF_T])dnl
+   AC_REQUIRE([AC_TYPE_SIZE_T])dnl
+   AC_REQUIRE([AC_FUNC_ALLOCA])dnl
+   AC_REQUIRE([AC_FUNC_MMAP])dnl
+
+   AC_CHECK_HEADERS([argz.h limits.h locale.h nl_types.h malloc.h string.h \
+unistd.h values.h])
+   AC_CHECK_FUNCS([getcwd munmap putenv setenv setlocale strchr strcasecmp \
+__argz_count __argz_stringify __argz_next])
+
+   if test "${ac_cv_func_stpcpy+set}" != "set"; then
+     AC_CHECK_FUNCS(stpcpy)
+   fi
+   if test "${ac_cv_func_stpcpy}" = "yes"; then
+     AC_DEFINE(HAVE_STPCPY)
+   fi
+
+   AM_LC_MESSAGES
+   AM_WITH_NLS
+
+   if test "x$CATOBJEXT" != "x"; then
+     if test "x$ALL_LINGUAS" = "x"; then
+       LINGUAS=
+     else
+       AC_MSG_CHECKING(for catalogs to be installed)
+       NEW_LINGUAS=
+       for lang in ${LINGUAS=$ALL_LINGUAS}; do
+         case "$ALL_LINGUAS" in
+          *$lang*) NEW_LINGUAS="$NEW_LINGUAS $lang" ;;
+         esac
+       done
+       LINGUAS=$NEW_LINGUAS
+       AC_MSG_RESULT($LINGUAS)
+     fi
+
+     dnl Construct list of names of catalog files to be constructed.
+     if test -n "$LINGUAS"; then
+       for lang in $LINGUAS; do CATALOGS="$CATALOGS $lang$CATOBJEXT"; done
+     fi
+   fi
+
+   dnl Determine which catalog format we have (if any is needed)
+   dnl For now we know about two different formats:
+   dnl   Linux libc-5 and the normal X/Open format
+   test -d intl || mkdir intl
+   if test "$CATOBJEXT" = ".cat"; then
+     AC_CHECK_HEADER(linux/version.h, msgformat=linux, msgformat=xopen)
+
+     dnl Transform the SED scripts while copying because some dumb SEDs
+     dnl cannot handle comments.
+     sed -e '/^#/d' $srcdir/intl/$msgformat-msg.sed > intl/po2msg.sed
+   fi
+   dnl po2tbl.sed is always needed.
+   sed -e '/^#.*[^\\]$/d' -e '/^#$/d' \
+     $srcdir/intl/po2tbl.sed.in > intl/po2tbl.sed
+
+   dnl In the intl/Makefile.in we have a special dependency which makes
+   dnl only sense for gettext.  We comment this out for non-gettext
+   dnl packages.
+   if test "$PACKAGE" = "gettext"; then
+     GT_NO="#NO#"
+     GT_YES=
+   else
+     GT_NO=
+     GT_YES="#YES#"
+   fi
+   AC_SUBST(GT_NO)
+   AC_SUBST(GT_YES)
+
+   dnl If the AC_CONFIG_AUX_DIR macro for autoconf is used we possibly
+   dnl find the mkinstalldirs script in another subdir but ($top_srcdir).
+   dnl Try to locate is.
+   MKINSTALLDIRS=
+   if test $ac_aux_dir; then
+     MKINSTALLDIRS="$ac_aux_dir/mkinstalldirs"
+   fi
+   if test -z $MKINSTALLDIRS; then
+     MKINSTALLDIRS="\$(top_srcdir)/mkinstalldirs"
+   fi
+   AC_SUBST(MKINSTALLDIRS)
+
+   dnl Configure the intl/Makefile for shared libs.
+   if test "${enable_shared+set}" = set; then
+     l=l
+   else
+     l=
+   fi
+   AC_SUBST(l)
+
+   dnl Generate list of files to be processed by xgettext which will
+   dnl be included in po/Makefile.
+   test -d po || mkdir po
+   if test "x$srcdir" != "x."; then
+     if test "x`echo $srcdir | sed 's@/.*@@'`" = "x"; then
+       posrcprefix="$srcdir/"
+     else
+       posrcprefix="../$srcdir/"
+     fi
+   else
+     posrcprefix="../"
+   fi
+   sed -e "/^#/d" -e "/^\$/d" -e "s,.*,        $posrcprefix& \\\\," -e "\$s/\(.*\) \\\\/\1/" \
+       < $srcdir/po/POTFILES.in > po/POTFILES
+  ])
+
+# Search path for a program which passes the given test.
+# Ulrich Drepper <drepper@cygnus.com>, 1996.
+
+# serial 1
+
+dnl AM_PATH_PROG_WITH_TEST(VARIABLE, PROG-TO-CHECK-FOR,
+dnl   TEST-PERFORMED-ON-FOUND_PROGRAM [, VALUE-IF-NOT-FOUND [, PATH]])
+AC_DEFUN(AM_PATH_PROG_WITH_TEST,
+[# Extract the first word of "$2", so it can be a program name with args.
+set dummy $2; ac_word=[$]2
+AC_MSG_CHECKING([for $ac_word])
+AC_CACHE_VAL(ac_cv_path_$1,
+[case "[$]$1" in
+  /*)
+  ac_cv_path_$1="[$]$1" # Let the user override the test with a path.
+  ;;
+  *)
+  IFS="${IFS=  }"; ac_save_ifs="$IFS"; IFS="${IFS}:"
+  for ac_dir in ifelse([$5], , $PATH, [$5]); do
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$ac_word; then
+      if [$3]; then
+       ac_cv_path_$1="$ac_dir/$ac_word"
+       break
+      fi
+    fi
+  done
+  IFS="$ac_save_ifs"
+dnl If no 4th arg is given, leave the cache variable unset,
+dnl so AC_PATH_PROGS will keep looking.
+ifelse([$4], , , [  test -z "[$]ac_cv_path_$1" && ac_cv_path_$1="$4"
+])dnl
+  ;;
+esac])dnl
+$1="$ac_cv_path_$1"
+if test -n "[$]$1"; then
+  AC_MSG_RESULT([$]$1)
+else
+  AC_MSG_RESULT(no)
+fi
+AC_SUBST($1)dnl
+])
+
+# Check whether LC_MESSAGES is available in <locale.h>.
+# Ulrich Drepper <drepper@cygnus.com>, 1995.
+
+# serial 1
+
+AC_DEFUN(AM_LC_MESSAGES,
+  [if test $ac_cv_header_locale_h = yes; then
+    AC_CACHE_CHECK([for LC_MESSAGES], am_cv_val_LC_MESSAGES,
+      [AC_TRY_LINK([#include <locale.h>], [return LC_MESSAGES],
+       am_cv_val_LC_MESSAGES=yes, am_cv_val_LC_MESSAGES=no)])
+    if test $am_cv_val_LC_MESSAGES = yes; then
+      AC_DEFINE(HAVE_LC_MESSAGES)
+    fi
+  fi])
+
+dnl Check longest integer in digits.
+
+AC_DEFUN([BLP_INT_DIGITS],
+[
+AC_MSG_CHECKING(number of digits in LONG_MIN (incl. sign))
+AC_CACHE_VAL(blp_int_digits,
+            [AC_TRY_RUN([#include <stdio.h>
+                          #include <limits.h>
+                         int
+                         main()
+                         {
+                           int len;
+                           char s[80];
+                           sprintf(s, "%ld", LONG_MAX);
+                           len = strlen(s);
+                           sprintf(s, "%ld", LONG_MIN);
+                           if(strlen(s)>len) len=strlen(s);
+                           sprintf(s, "%lu", ULONG_MAX);
+                           if(strlen(s)>len) len=strlen(s);
+                           exit(len);
+                         }
+                        ],
+                        eval "blp_int_digits=19",
+                        eval "blp_int_digits=$?"
+                        if test "$blp_int_digits" -lt 11; then
+                          blp_int_digits=11
+                        fi,
+                        eval "blp_int_digits=19")
+            ])
+AC_DEFINE_UNQUOTED(INT_DIGITS, $blp_int_digits)
+AC_MSG_RESULT($blp_int_digits)
+])dnl
+
+dnl Check quality of this machine's sprintf implementation.
+
+AC_DEFUN([BLP_IS_SPRINTF_GOOD],
+[
+AC_MSG_CHECKING(if sprintf returns a char count)
+AC_CACHE_VAL(blp_is_sprintf_good,
+             [AC_TRY_RUN([#include <stdio.h>
+                          int 
+                          main()
+                          {
+                            char s[8];
+                            exit((int)sprintf(s, "abcdefg")!=7);
+                          }
+                         ], 
+                         eval "blp_is_sprintf_good=yes",
+                        eval "blp_is_sprintf_good=no",
+                        eval "blp_is_sprintf_good=no")
+             ])
+if test "$blp_is_sprintf_good" = yes; then
+  AC_DEFINE(HAVE_GOOD_SPRINTF)
+  AC_MSG_RESULT(yes)
+else
+  AC_MSG_RESULT(no)
+fi
+])dnl
+
+dnl Check for proper random number generator.
+
+AC_DEFUN([BLP_RANDOM],
+[
+AC_MSG_CHECKING(random number generator)
+AC_CACHE_VAL(blp_random_good, 
+  AC_TRY_COMPILE([#include <stdlib.h>], [int x=RAND_MAX;], 
+    blp_random_good=yes, blp_random_good=no))
+if test "$blp_random_good" = yes; then
+  AC_DEFINE(HAVE_GOOD_RANDOM)
+  AC_MSG_RESULT(good)
+else
+  AC_MSG_RESULT(bad)
+fi
+])dnl
+
+dnl aclocal.m4 ends here
diff --git a/config/ChangeLog b/config/ChangeLog
new file mode 100644 (file)
index 0000000..12f1ea8
--- /dev/null
@@ -0,0 +1,158 @@
+Sun May 24 22:40:13 1998  Ben Pfaff  <blp@gnu.org>
+
+       * ps-prologue: Add %%DocumentMedia: comment.
+
+Wed May 20 00:02:51 1998  Ben Pfaff  <blp@gnu.org>
+
+       * ps-prologue: Comment out misleading Bounding-Box comment for
+       now.  SF arguments rearranged.  BP removed.
+
+Wed Apr 15 13:00:46 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (private-install) Make it work for separate source
+       and build directories.
+
+       * ps-prologue: New TL macro for a thick line.  New thick-width arg
+       to BP.
+
+Sun Jan  4 18:11:11 1998  Ben Pfaff  <blp@gnu.org>
+
+       * ps-prologue: Minor reorganization.  New GB macro to draw a gray
+       box.
+
+Wed Dec 24 22:35:13 1997  Ben Pfaff  <blp@gnu.org>
+
+       * devices: Added devicetype options and documentation for them.
+
+Fri Dec  5 21:51:08 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (pkgsysconf_DATA) Add html-prologue.
+       (EXTRA_DIST) Add html-prologue.
+
+       * devices: Add `html' device.  Add `listing', `screen', and
+       `printer' flags to devices as appropriate.
+
+       * html-prologue: New file.
+
+       * ps-prologue: Comment fixes.
+
+Thu Sep 18 21:31:02 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (pkgsysconfdir) Changed from $(pkgdatadir) to
+       $(sysconfdir)/$(PACKAGE).
+
+Thu Aug 14 22:05:54 1997  Ben Pfaff  <blp@gnu.org>
+
+       * devices: (tty) Define as null instead of not defining.
+
+Sun Aug  3 11:33:28 1997  Ben Pfaff  <blp@gnu.org>
+
+       * devices: tty-ascii has no bold or italic by default.
+
+Wed Jun 25 22:50:19 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (EXTRA_DIST) New target.
+
+Mon May  5 21:56:54 1997  Ben Pfaff  <blp@gnu.org>
+
+       * devices, papersize, ps-prologue: Comment fixes.
+
+Fri May  2 22:05:44 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Removed ps-fontmap.
+
+       * ps-fontmap: Removed.
+
+       * ps-prologue: Added comments.  Fixed DSC comments.
+       (BP) Two new arguments; fixed problem with SF argument conflict
+       with SF function.
+
+Thu May  1 14:57:52 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ps-prologue: (BP) New argument, SF or scale factor.
+       
+Fri Apr 18 16:48:41 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: New file.
+       
+       * environment: Comment fix.
+
+Sat Feb 15 21:26:53 1997  Ben Pfaff  <blp@gnu.org>
+
+       * devices: Added ml520 and ml520-ul printer devices.
+
+Sat Jan 11 15:44:15 1997  Ben Pfaff  <blp@gnu.org>
+
+       * devices: Default listing device is list-ascii, not list-ibmpc.
+
+Sun Dec 29 21:36:48 1996  Ben Pfaff  <blp@gnu.org>
+
+       * devices: Changed default devices.
+
+Sat Sep  7 22:35:12 1996  Ben Pfaff  <blp@gnu.org>
+
+       * ps-prologue: Added `!encodings' line to cause encodings to be
+       output.
+       (T) Fixed.  Yes, really this time.
+
+Thu Sep  5 22:05:56 1996  Ben Pfaff  <blp@gnu.org>
+
+       * ps-prologue: (T) Now works correctly.
+       (SF) Parameters changed to: size in psus, target font name,
+       encoding, PostScript font name.
+
+Wed Sep  4 21:45:35 1996  Ben Pfaff  <blp@gnu.org>
+
+       * prologue.ps: Renamed ps-prologue, all references changed.
+       (T) New definition.
+
+       * ps-encodings: New PostScript configuration file (not present in
+       distribution).
+
+Sat Aug 31 23:52:38 1996  Ben Pfaff  <blp@gnu.org>
+
+       * prologue.ps: One minor comment change.
+
+Thu Aug 29 21:36:41 1996  Ben Pfaff  <blp@gnu.org>
+
+       * prologue.ps: Portions other than DSC comments are essentially
+       completely new.
+
+Sat Aug 24 23:26:00 1996  Ben Pfaff  <blp@gnu.org>
+
+       * devices: Added PostScript driver.
+
+Sun Aug 11 21:31:22 1996  Ben Pfaff  <blp@gnu.org>
+
+       * prologue.ps: Calls `setlinecap' in setup code.
+
+Sat Aug 10 23:28:17 1996  Ben Pfaff  <blp@gnu.org>
+
+       * prologue.ps: DSC comment changes.  New call to `setlinewidth' in
+       setup code.
+
+Thu Aug  8 22:31:11 1996  Ben Pfaff  <blp@gnu.org>
+
+       * prologue.ps: Changes to scaling & translating code.
+
+Sat Aug  3 20:50:35 1996  Ben Pfaff  <blp@gnu.org>
+
+       * environment: New file.  Yet another new time- and memory-hogging
+       redundant config file; why not?
+       
+       * papersize: Comment changes.
+
+       * prologue.ps: Changed vars from $varname$ to ${varname} format.
+       Miscellaneous changes.
+
+       * ps-fontmap: Comment changes.  Fixed ZC family.
+
+Sat Jul 27 22:32:38 1996  Ben Pfaff  <blp@gnu.org>
+
+       * ps-fontmap: New configuration file.  Added to Makefile.am.
+
+----------------------------------------------------------------------
+Local Variables:
+mode: change-log
+version-control: never
+End:
diff --git a/config/Makefile.am b/config/Makefile.am
new file mode 100644 (file)
index 0000000..9924e25
--- /dev/null
@@ -0,0 +1,18 @@
+## Process this file with automake to produce Makefile.in  -*- makefile -*-
+
+pkgsysconfdir = $(sysconfdir)/$(PACKAGE)
+pkgsysconf_DATA = devices html-prologue papersize ps-prologue
+EXTRA_DIST = devices html-prologue papersize ps-prologue
+
+# A `private installation' in my terms is just having the appropriate
+# configuration files in ~/.pspp instead of a global configuration
+# location.  So I let those files be installed automatically.
+
+private-install:
+       $(mkinstalldirs) $$HOME/.pspp
+       cd $(srcdir); cp $(pkgsysconf_DATA) $$HOME/.pspp
+private-uninstall:
+       -cd $$HOME/.pspp; rm -f $(pkgsysconf_DATA)
+       -rmdir $$HOME/.pspp
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/config/devices b/config/devices
new file mode 100644 (file)
index 0000000..c1aa6c0
--- /dev/null
@@ -0,0 +1,165 @@
+# PSPP's standard output drivers.
+#
+# An introduction to the use of PSPP output drivers and this file
+# follows.  However, refer to PSPP's Texinfo documentation for full
+# information.
+#
+# Each output driver specification must be on a single line; however,
+# lines may be spliced with a \ at the end of a line.  Line splicing
+# is performed *before* comments (introduced by `#') are removed.
+#
+# Format is `DRIVERNAME:CLASSNAME:DEVICETYPE:OPTIONS'.
+#
+# DRIVERNAME is the name that identifies the driver to the user.  It
+# is the name used on the -o command-line option.
+#
+# CLASSNAME is the internal name of the type of driver.  Device
+# classes can be listed with `pspp -l'.
+#
+# DEVICETYPE identifies what type or types the devices is.  Zero or
+# more of the following keywords may be given here: screen, printer,
+# or listing.
+#
+# OPTIONS is a list of key/value pairs to pass to the driver.  Use
+# spaces to separate pairs, and '=' to separate keys and values.
+# Quotes " or ' can be used to delimit values that contain spaces.
+# Example: paper-size="Envelope #10" charset=latin1
+#
+# Driver categories may be defined with lines of the form:
+#
+#   category=driver1 driver2 driver3 ... driverN
+#
+# To disable a driver, define a category with nothing on the right
+# side.
+#
+# Macros may be defined with lines of the form:
+#
+#   define macro-name definition
+#
+# Macros may not be recursive; they may not take arguments.  (However,
+# `definition' is macro-expanded *at time of definition*.)  Macros are
+# referenced with $var or ${var} syntax; the latter is preferred.
+# Macro definitions on the PSPP command-line take precedence without
+# warning.
+
+# Preferred devices.
+default=tty list
+tty=#tty-ibmpc
+list=list-ascii
+
+# Output files.
+define tty-output-file "/dev/tty"
+define list-output-file "pspp.list"
+
+define no-attributes bold-on="" italic-on="" bold-italic-on=""
+
+# Generic ASCII devices
+tty-ascii:ascii:screen:char-set=ascii output-file=${tty-output-file} \
+  ${no-attributes}
+list-ascii:ascii:listing:length=66 width=79 char-set=ascii \
+  output-file=${list-output-file} ${no-attributes}
+
+# ASCII devices that support bold & underline via backspacing.
+tty-ascii-bi:ascii:screen:char-set=ascii output-file=${tty-output-file}
+list-ascii-bi:ascii:listing:length=66 width=79 char-set=ascii \
+  output-file=${list-output-file}
+
+# HTML device.
+html:html::
+
+# Devices that support the IBM PC line-drawing characters.
+define ibmpc-graphics \
+  box[0000]='\x20' box[0001]='\xb3' box[0002]='\xba' box[0003]='\xba' \
+  box[0010]='\xc4' box[0011]='\xd9' box[0012]='\xbd' box[0013]='\xbd' \
+  box[0020]='\xcd' box[0021]='\xbe' box[0022]='\xbc' box[0023]='\xbc' \
+  box[0030]='\xf0' box[0031]='\xbe' box[0032]='\xbc' box[0033]='\xbc' \
+  box[0100]='\xb3' box[0101]='\xb3' box[0102]='\xc4' box[0103]='\xf0' \
+  box[0110]='\xbf' box[0111]='\xb4' box[0112]='\xb6' box[0113]='\xb6' \
+  box[0120]='\xb8' box[0121]='\xb5' box[0122]='\xb9' box[0123]='\xb9' \
+  box[0130]='\xb8' box[0131]='\xb5' box[0132]='\xb9' box[0133]='\xb9' \
+  box[0200]='\xba' box[0201]='\xba' box[0202]='\xba' box[0203]='\xba' \
+  box[0210]='\xb7' box[0211]='\xb6' box[0212]='\xb6' box[0213]='\xb6' \
+  box[0220]='\xbb' box[0221]='\xb9' box[0222]='\xb9' box[0223]='\xb9' \
+  box[0300]='\xb3' box[0301]='\xba' box[0302]='\xba' box[0303]='\xba' \
+  box[0310]='\xb7' box[0311]='\xb6' box[0312]='\xb6' box[0313]='\xb6' \
+  box[0320]='\xbb' box[0321]='\xb9' box[0322]='\xb9' box[0323]='\xb9' \
+  box[0330]='\xbb' box[0331]='\xb9' box[0332]='\xb9' box[0333]='\xb9' \
+  box[1000]='\xc4' box[1001]='\xc0' box[1002]='\xd3' box[1003]='\xd3' \
+  box[1010]='\xc4' box[1011]='\xc1' box[1012]='\xd0' box[1013]='\xd0' \
+  box[1020]='\xcd' box[1021]='\xcf' box[1022]='\xca' box[1023]='\xca' \
+  box[1030]='\xf0' box[1031]='\xcf' box[1032]='\xca' box[1033]='\xca' \
+  box[1100]='\xda' box[1101]='\xc3' box[1102]='\xc7' box[1103]='\xc7' \
+  box[1110]='\xc2' box[1111]='\xc5' box[1112]='\xd7' box[1113]='\xd7' \
+  box[1120]='\xd1' box[1121]='\xd8' box[1122]='\xce' box[1123]='\xce' \
+  box[1130]='\xd1' box[1131]='\xd8' box[1132]='\xce' box[1133]='\xce' \
+  box[1200]='\xd6' box[1201]='\xc7' box[1202]='\xc7' box[1203]='\xc7' \
+  box[1210]='\xd2' box[1211]='\xd7' box[1212]='\xd7' box[1213]='\xd7' \
+  box[1220]='\xca' box[1221]='\xce' box[1222]='\xce' box[1223]='\xce' \
+  box[1230]='\xca' box[1231]='\xce' box[1232]='\xce' box[1233]='\xce' \
+  box[1300]='\xd6' box[1301]='\xc7' box[1302]='\xc7' box[1303]='\xc7' \
+  box[1310]='\xd2' box[1311]='\xd7' box[1312]='\xd7' box[1313]='\xd7' \
+  box[1320]='\xca' box[1321]='\xce' box[1322]='\xce' box[1323]='\xce' \
+  box[1330]='\xca' box[1331]='\xce' box[1332]='\xce' box[1333]='\xce' \
+  box[2000]='\xcd' box[2001]='\xd4' box[2002]='\xc8' box[2003]='\xc8' \
+  box[2010]='\xcd' box[2011]='\xcf' box[2012]='\xca' box[2013]='\xca' \
+  box[2020]='\xcd' box[2021]='\xcf' box[2022]='\xca' box[2023]='\xca' \
+  box[2030]='\xf0' box[2031]='\xcf' box[2032]='\xca' box[2033]='\xca' \
+  box[2100]='\xd5' box[2101]='\xc6' box[2102]='\xcc' box[2103]='\xcc' \
+  box[2110]='\xd1' box[2111]='\xd8' box[2112]='\xce' box[2113]='\xce' \
+  box[2120]='\xd1' box[2121]='\xd8' box[2122]='\xce' box[2123]='\xce' \
+  box[2130]='\xd1' box[2131]='\xd8' box[2132]='\xce' box[2133]='\xce' \
+  box[2200]='\xc9' box[2201]='\xcc' box[2202]='\xcc' box[2203]='\xcc' \
+  box[2210]='\xcb' box[2211]='\xce' box[2212]='\xce' box[2213]='\xce' \
+  box[2220]='\xcb' box[2221]='\xce' box[2222]='\xce' box[2223]='\xce' \
+  box[2230]='\xcb' box[2231]='\xce' box[2232]='\xce' box[2233]='\xce' \
+  box[2300]='\xc9' box[2301]='\xcc' box[2302]='\xcc' box[2303]='\xce' \
+  box[2310]='\xcb' box[2311]='\xce' box[2312]='\xce' box[2313]='\xce' \
+  box[2320]='\xcb' box[2321]='\xce' box[2322]='\xce' box[2323]='\xce' \
+  box[2330]='\xcb' box[2331]='\xce' box[2332]='\xce' box[2333]='\xce' \
+  box[3000]='\xcd' box[3001]='\xd4' box[3002]='\xc8' box[3003]='\xc8' \
+  box[3010]='\xcd' box[3011]='\xcf' box[3012]='\xca' box[3013]='\xca' \
+  box[3020]='\xcd' box[3021]='\xcf' box[3022]='\xca' box[3023]='\xca' \
+  box[3030]='\xcd' box[3031]='\xcf' box[3032]='\xca' box[3033]='\xca' \
+  box[3100]='\xd5' box[3101]='\xc6' box[3102]='\xcc' box[3103]='\xcc' \
+  box[3110]='\xd1' box[3111]='\xd8' box[3112]='\xce' box[3113]='\xce' \
+  box[3120]='\xd1' box[3121]='\xd8' box[3122]='\xce' box[3123]='\xce' \
+  box[3130]='\xd1' box[3131]='\xd8' box[3132]='\xce' box[3133]='\xce' \
+  box[3200]='\xc9' box[3201]='\xcc' box[3202]='\xcc' box[3203]='\xcc' \
+  box[3210]='\xcb' box[3211]='\xce' box[3212]='\xce' box[3213]='\xce' \
+  box[3220]='\xcb' box[3221]='\xce' box[3222]='\xce' box[3223]='\xce' \
+  box[3230]='\xcb' box[3231]='\xce' box[3232]='\xce' box[3233]='\xce' \
+  box[3300]='\xc9' box[3301]='\xcc' box[3302]='\xcc' box[3303]='\xce' \
+  box[3310]='\xcb' box[3311]='\xce' box[3312]='\xce' box[3313]='\xce' \
+  box[3320]='\xcb' box[3321]='\xce' box[3322]='\xce' box[3323]='\xce' \
+  box[3330]='\xcb' box[3331]='\xce' box[3332]='\xce' box[3333]='\xce'
+
+tty-ibmpc:ascii:screen:length=$viewlength width=$viewwidth ${ibmpc-graphics} \
+  output-file=${tty-output-file}
+list-ibmpc:ascii:listing:length=66 width=79 output-file=${list-output-file} \
+  ${ibmpc-graphics}
+
+# PostScript device.  Tested with HP LaserJet 6MP.
+list-ps:postscript::
+
+# Okidata Microline 520 (these use the Microline emulation mode).
+define ml520-common output-file=${list-output-file} ${ibmpc-graphics} \
+  bold-on='\x1b\x54' bold-off='\x1b\x49' init='\x1b\x7b\x21\x1b\x23\x30'
+define ml520-italic italic-on='\x1b\x21\x2f' italic-off='\x1b\x21\x2a' \
+  bold-italic-on='\x1b\x21\x2f\x1b\x54' bold-italic-off='\x1b\x21\x2a\x1b\x49'
+define ml520-ul italic-on='\x1b\x43' italic-off='\x1b\x44' \
+  bold-italic-on='\x1b\x43\x1b\x54' bold-italic-off='\x1b\x44\x1b\x49'
+ml520=ml520-10cpi
+ml520-10cpi:ascii:printer:length=66 width=79 ${ml520-common} ${ml520-italic}
+ml520-10cpi-ul:ascii:printer:length=66 width=79 ${ml520-common} ${ml520-ul}
+ml520-17cpi:ascii:printer:length=66 width=144 ${ml520-common} ${ml520-italic} \
+  cpi=17 init='\x1b\x7b\x21\x1b\x23\x30\x1d'
+ml520-17cpi-ul:ascii:printer:length=66 width=144 ${ml520-common} ${ml520-ul} \
+  cpi=17 init='\x1b\x7b\x21\x1b\x23\x30\x1d'
+ml520-20cpi:ascii:printer:length=66 width=160 ${ml520-common} ${ml520-italic} \
+  cpi=17 init='\x1b\x7b\x21\x1b\x23\x30\x1b\x23\x33'
+ml520-20cpi-ul:ascii:printer:length=66 width=160 ${ml520-common} ${ml520-ul} \
+  cpi=17 init='\x1b\x7b\x21\x1b\x23\x30\x1b\x23\x33'
+
+# Local Variables:
+# fill-prefix: "# "
+# End:
diff --git a/config/html-prologue b/config/html-prologue
new file mode 100644 (file)
index 0000000..fa0b57d
--- /dev/null
@@ -0,0 +1,23 @@
+!!!
+!!! This prologue is hereby placed in the public domain.
+!!!
+!!! PSPP does not place any restrictions on the distribution terms
+!!! of its output.  You are encouraged to allow your PSPP outputs to
+!!! be freely distributed.
+!!!
+<!DOCTYPE html PUBLIC "-//IETF//DTD HTML 2.0//EN">
+<!-- Generated ${date} by ${generator}
+     from ${source-file} -->
+<HTML>
+<HEAD>
+<TITLE>${title}</TITLE>                !title
+<META NAME="generator" CONTENT="${generator}">
+<META NAME="author" CONTENT="${author}">
+</HEAD>
+<BODY BGCOLOR="#ffffff" TEXT="#000000" LINK="#1f00ff" ALINK="#ff0000"
+ VLINK="#9900dd">
+<H1>${title}</H1>              !title
+<H2>${subtitle}</H2>           !subtitle
+!!! Local Variables:
+!!! fill-prefix: "!!! "
+!!! End:
diff --git a/config/papersize b/config/papersize
new file mode 100644 (file)
index 0000000..f3866ed
--- /dev/null
@@ -0,0 +1,60 @@
+# List of standard paper sizes for use with PSPP output drivers.
+#
+# Valid units include "in"=inches, "cm"=centimeters, "mm"=millimeters.
+# Default units are "in" for dimensions less than 50, "mm" otherwise.
+# This automagically determines units for all the standard sizes.
+#
+# Fractional values are allowed: (1) as decimals, or (2) in the form
+# "a-b/c", which has the value of a, plus b divided by c.
+#
+# Also allowed are synonyms: `"B4/JIS"="B4/ISO"'.  The left hand size
+# is replaced by the right hand size.
+
+# U.S.
+"Letter"               8-1/2 x 11
+"Legal"                        8-1/2 x 14
+"Letter Extra"         9-1/2 x 12
+"Legal Extra"          9-1/2 x 15
+"Executive"            7-1/4 x 10-1/2
+"Ledger"               17 x 11
+"Tabloid"              11 x 17
+"Tabloid Extra"                11.69 x 18
+"US Standard Fanfold"="U.S. Standard Fanfold"
+"U.S. Standard Fanfold"        14-7/8 x 11
+"Standard Fanfold"     8-1/2 x 12
+"Legal Fanfold"                8-1/2 x 12
+
+# Envelopes.
+"DL"                   8-2/3 x 4-1/3
+"Monarch"              3-7/8 x 7-1/2
+"6 3/4 Envelope"="6-3/4 Envelope"
+"6-3/4 Envelope"       3-5/8 x 6-1/2
+"#9"                   3-7/8 x 8-7/8
+"#10"                  4-1/8 x 9-1/2
+"#11"                  4-1/2 x 10-3/8
+"#12"                  4-3/4 x 11
+"#14"                  5 x 11-1/2
+
+# Metric.
+"B4"="B4/ISO"
+"B5"="B5/ISO"
+"A3"           297 x 420
+"A4"           210 x 297
+"B4/ISO"       250 x 353
+"B4/JIS"       257 x 364
+"B5/ISO"       176 x 250
+"B5/JIS"       182 x 257
+"B6"           176 x 125
+"C3"           324 x 458
+"C4"           229 x 324
+"C5"           162 x 229
+"C6"           114 x 162
+"C65"          114 x 229
+"Envelope"     110 x 230
+
+# Demonstration of units.
+#"Bizarre" 55mm x 10in
+
+# Local Variables:
+# fill-prefix: "# "
+# End:
diff --git a/config/ps-prologue b/config/ps-prologue
new file mode 100644 (file)
index 0000000..3c75230
--- /dev/null
@@ -0,0 +1,75 @@
+!!!
+!!! This prologue is hereby placed in the public domain.
+!!!
+!!! PSPP does not place any restrictions on the distribution terms
+!!! of its output.  You are encouraged to allow your PSPP outputs to
+!!! be freely distributed.
+!!!
+%!PS-Adobe-3.0 EPSF-3.0                           !eps
+%!PS-Adobe-3.0                                   !ps
+%%Pages: (atend)
+%%DocumentNeededResources: (atend)
+%%DocumentSuppliedResources: procset PSPP-Prologue 1.0 0
+!!! %%Bounding-Box: ${bounding-box}
+%%Copyright: This prologue is public domain.
+%%Creator: ${creator}
+%%CreationDate: ${date}
+%%DocumentData: ${data}
+%%DocumentMedia: Plain ${paper-width} ${paper-length} 75 white ()
+%%Orientation: ${orientation}
+%%For: ${user}@${host}
+%%Title: ${title}
+%FscoSourceFile: ${source-file}
+%%EndComments
+%%BeginDefaults
+%%PageResources:
+%%+ ${prop-font}
+%%+ ${fixed-font}
+%%EndDefaults
+%%BeginProlog
+%%BeginResource: procset PSPP-Prologue 1.0 0
+/L{moveto lineto stroke}bind def
+/TL{TW setlinewidth 0 setlinecap 
+ moveto lineto stroke 
+ LW setlinewidth 2 setlinecap}def
+/D{moveto lineto moveto lineto stroke}bind def
+/S{moveto show}bind def
+/T{currentpoint exch pop moveto show}bind def
+/ED{exch def}bind def
+!!! SF arguments:
+!!!    identifier      dictionary entry to save font in
+!!!    font encoding   font encoding vector
+!!!    fontsize        thousandths of a point
+!!!    font name       string
+!!! Usage example: 12000/F0 E0 (Times-Roman) SF
+/SF{
+ findfont exch scalefont
+ dup maxlength 1 index/FontName known not{1 add}if dict begin
+ {
+  1 index/FID ne{def}{pop pop}ifelse
+ }forall
+ /Encoding ED
+ dup/FontName ED
+ currentdict end 1 index exch definefont dup setfont 
+ [exch/setfont cvx] cvx bind def
+}bind def
+/F{setfont}bind def
+/EP{
+ pg restore
+ showpage
+}bind def
+/GB{
+ /y2 ED/x2 ED/y1 ED/x1 ED
+ x1 y1 moveto x2 y1 lineto x2 y2 lineto x1 y2 lineto closepath
+ gsave 0.9 setgray fill grestore stroke
+}bind def
+%%EndResource
+%%EndProlog
+%%BeginSetup
+%%IncludeResource: ${prop-font}
+%%IncludeResource: ${fixed-font}
+!encodings
+%%EndSetup
+!!! Local Variables:
+!!! fill-prefix: "!!! "
+!!! End:
diff --git a/configure.in b/configure.in
new file mode 100644 (file)
index 0000000..64bbc41
--- /dev/null
@@ -0,0 +1,241 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_INIT(src/q2c.c)
+AC_PREREQ(2.12)
+AM_CONFIG_HEADER(config.h)
+CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE=1"
+AC_ISC_POSIX
+AC_PROG_CC
+AM_PROG_CC_STDC
+AC_CANONICAL_SYSTEM
+AM_INIT_AUTOMAKE(pspp, [0.3.0])
+
+#AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE")
+AC_DEFINE_UNQUOTED(VERSION, "$VERSION")
+
+#GNU_PACKAGE="GNU $PACKAGE"
+#AC_DEFINE_UNQUOTED(GNU_PACKAGE, "$GNU_PACKAGE")
+
+ALL_LINGUAS=""
+
+AC_ARG_PROGRAM
+AC_PROG_INSTALL
+AC_PROG_RANLIB
+
+dnl internationalization macros
+AM_GNU_GETTEXT
+
+AC_LINK_FILES($nls_cv_header_libgt, $nls_cv_header_intl)
+
+AC_ARG_WITH(checker, 
+           [  --with-checker          compile with Checker (for debugging)],
+           CC="checkergcc" LOCAL_CC="gcc" CC_OPTIONS="-Werror")
+AC_ARG_ENABLE(debugging,
+           [  --enable-debugging      turn on debugging options],
+           CC_OPTIONS="-DDEBUGGING=1")
+
+# LOCAL_CC runs on the build system, targets the build system.
+# CC runs on the build system, targets the host system.
+if test -z "$LOCAL_CC"; then
+  LOCAL_CC="$CC"
+fi
+AC_SUBST(LOCAL_CC)
+
+AC_CHECK_LIB(m, sin)
+AC_CHECK_LIB(gmp, mpf_get_str, 
+  LIBS="-lgmp $LIBS" GMP_SUBDIRS= GMP_LIBS=,
+  GMP_SUBDIRS=gmp GMP_LIBS='$(GMP_LIBS)')
+AC_SUBST(GMP_SUBDIRS)
+AC_SUBST(GMP_LIBS)
+
+AC_CHECK_LIB(ncurses, tgetent, LIBS="-lncurses $LIBS" termcap=yes,
+  AC_CHECK_LIB(termcap, tgetent, LIBS="-ltermcap $LIBS" termcap=yes,
+               termcap=no))
+if test "$termcap" = yes; then
+  AC_CHECK_HEADERS(termcap.h)
+  AC_DEFINE(HAVE_LIBTERMCAP)
+fi
+
+AC_CHECK_LIB(readline, readline)
+if test "$ac_cv_lib_readline_readline" = yes; then
+  AC_CHECK_HEADERS(readline/readline.h)
+  AC_CHECK_LIB(readline, add_history, history=yes,
+    AC_CHECK_LIB(history, add_history, LIBS="-lhistory" history=yes,
+                 history=no))
+  if test "$history" = yes; then
+    AC_CHECK_HEADERS(readline/history.h)
+    AC_DEFINE(HAVE_LIBHISTORY)
+  fi
+fi
+
+AC_CHECK_HEADERS(limits.h memory.h sys/stat.h sys/time.h sys/types.h \
+                fpu_control.h sys/mman.h sys/wait.h ieeefp.h fenv.h)
+AC_HEADER_STAT
+AC_HEADER_STDC
+AC_HEADER_TIME
+
+dnl This test must precede tests of compiler characteristics like
+dnl that for the inline keyword, since it may change the degree to
+dnl which the compiler supports such features.
+AM_C_PROTOTYPES
+
+AC_C_CONST
+AC_C_INLINE
+AC_TYPE_SIZE_T
+AC_STRUCT_TM
+
+AC_CHECK_SIZEOF(short, 2)
+AC_CHECK_SIZEOF(int, 4)
+AC_CHECK_SIZEOF(long, 4)
+AC_CHECK_SIZEOF(long long, 0)
+AC_CHECK_SIZEOF(float, 0)
+AC_CHECK_SIZEOF(double, 8)
+AC_CHECK_SIZEOF(long double, 0)
+
+dnl There used to be a check for floating-point representation here, but
+dnl for some reason it didn't work on certain m68k GNU/Linux machines, and
+dnl I was unable to determine why.  So, since every modern computer uses
+dnl ieee754 format anyway, I've hard-coded it to ieee754.  Anyone who uses
+dnl something else can enumerate the exceptions.
+
+AC_DEFINE(FPREP, FPREP_IEEE754)
+
+dnl if test "$cross_compiling" = yes; then
+dnl   AC_MSG_WARN([Edit config.h to set proper values for SIZEOF_SHORT, \
+dnl SIZEOF_INT,])
+dnl   AC_MSG_WARN([SIZEOF_LONG, and SIZEOF_LONG_LONG (if available), if the \
+dnl values])
+dnl   AC_MSG_WARN([are not 2, 4, 4, and 8, respectively.])
+dnl   AC_MSG_WARN([Also set the floating point representation (IEEE754, etc.).])
+dnl else
+dnl   AC_CACHE_CHECK(
+dnl       floating point representation, ac_cv_sys_fprep,
+dnl 
+dnl       AC_TRY_RUN(
+dnl             [changequote(<<, >>)dnl
+dnl             <<
+dnl          main () {
+dnl          /* Test for IEEE754 floating point representation. */
+dnl          union { unsigned char c[8]; double d; }
+dnl            l = {{0x1c, 0xbc, 0x6e, 0xf2, 0x54, 0x8b, 0x11, 0x43}},
+dnl            b = {{0x43, 0x11, 0x8b, 0x54, 0xf2, 0x6e, 0xbc, 0x1c}};
+dnl          return l.d!=1234567891234567.0 && b.d!=1234567891234567.0;
+dnl          }
+dnl             >>
+dnl             changequote([, ])dnl
+dnl             ], ac_cv_sys_fprep=ieee754, ac_cv_sys_fprep=unknown,
+dnl        AC_MSG_WARN([This error cannot occur.])))
+dnl   if test "$ac_cv_sys_fprep" = ieee754; then
+dnl     AC_DEFINE(FPREP, FPREP_IEEE754)
+dnl   else
+dnl     AC_MSG_WARN([Unknown floating-point representation.  This is a serious \
+dnl error.])
+dnl     AC_MSG_WARN([Please contact the author for porting information.])
+dnl     AC_MSG_WARN([(It should be a fairly simple port, by the way.)])
+dnl     AC_DEFINE(FPREP, FPREP_UNKNOWN)
+dnl   fi
+dnl fi
+
+if test "$cross_compiling" = no; then
+  dnl This code was taken from acspecific.m4 and modified.
+  dnl It began life as AC_C_BIGENDIAN.
+  AC_CACHE_CHECK(
+      whether byte ordering is bigendian, ac_cv_c_bigendian,
+      [ac_cv_c_bigendian=unknown
+       # See if sys/param.h defines the BYTE_ORDER macro.
+       AC_TRY_COMPILE(
+          [#include <sys/types.h>
+           #include <sys/param.h>], 
+          [#if !BYTE_ORDER || !BIG_ENDIAN || !LITTLE_ENDIAN
+           bogus endian macros
+           #endif], 
+          [# It does; now see whether it defined to BIG_ENDIAN or not.
+           AC_TRY_COMPILE(
+               [#include <sys/types.h>
+                #include <sys/param.h>], 
+               [#if BYTE_ORDER != BIG_ENDIAN
+                not big endian
+                #endif], 
+               ac_cv_c_bigendian=yes, ac_cv_c_bigendian=no)])
+       if test $ac_cv_c_bigendian = unknown; then
+          AC_TRY_RUN(
+              [main () {
+               /* Are we little or big endian?  From Harbison&Steele.  */
+               union
+                 {
+                   long l;
+                   char c[sizeof (long)];
+                 } u;
+               u.l = 1;
+               exit (u.c[sizeof (long) - 1] == 1);
+               }], 
+              ac_cv_c_bigendian=no, ac_cv_c_bigendian=yes, 
+               AC_MSG_ERROR([Internal error determining endianness.]))
+       fi])
+
+  if test "$ac_cv_c_bigendian" = yes; then
+    AC_DEFINE(ENDIAN, BIG)
+  elif test "$ac_cv_c_bigendian" = no; then
+    AC_DEFINE(ENDIAN, LITTLE)
+  else
+    AC_MSG_ERROR([Machine's endianness is unknown.])
+  fi
+else
+  AC_DEFINE(ENDIAN, UNKNOWN)
+
+  AC_MSG_WARN([Optionally set value for endianness for best performance.])
+fi
+
+BLP_IS_SPRINTF_GOOD
+BLP_INT_DIGITS
+BLP_RANDOM
+
+AC_FUNC_ALLOCA
+AC_FUNC_MEMCMP
+AC_FUNC_VPRINTF
+AC_REPLACE_FUNCS(memmove memset stpcpy strpbrk strerror strtol strtoul memchr \
+                 getline getdelim strcasecmp strncasecmp memmem strtok_r)
+AC_CHECK_FUNCS(gethostname strstr strtod __setfpucw isinf isnan finite getpid \
+              feholdexcept)
+
+AC_PROG_LN_S
+
+dnl This must be after other tests so warnings don't provoke errors above.
+if test "$ac_cv_prog_gcc" = yes; then
+       CFLAGS="-g -Wall -W -Wno-uninitialized -Wwrite-strings \
+-Wstrict-prototypes -Wpointer-arith"
+       if test "$CC_OPTIONS" != ""; then
+               CFLAGS="$CFLAGS $CC_OPTIONS"
+       fi
+fi
+AC_SUBST(CFLAGS)
+
+AC_OUTPUT(Makefile                             \
+         intl/Makefile                         \
+         po/Makefile.in                        \
+         lib/Makefile                          \
+         lib/gmp/Makefile                      \
+         lib/gmp/mpn/Makefile                  \
+         lib/gmp/mpf/Makefile                  \
+         lib/julcal/Makefile                   \
+         lib/misc/Makefile                     \
+         lib/dcdflib/Makefile                  \
+         doc/Makefile                          \
+         src/Makefile                          \
+         config/Makefile                       \
+          tests/Makefile,
+          [sed -e "/POTFILES =/r po/POTFILES" po/Makefile.in > po/Makefile
+
+          # Copy pref.h from pref.h.orig if prudent
+          if test ! -f pref.h; then
+            echo "creating pref.h"
+            cp $ac_given_srcdir/pref.h.orig pref.h
+          elif test "`ls -t pref.h.orig pref.h 2>/dev/null | sed 1q`" = pref.h.orig; then
+            echo "replacing pref.h with newer pref.h.orig"
+            cp $ac_given_srcdir/pref.h.orig pref.h
+          else
+            echo "pref.h exists"
+          fi
+          if test -f pref.h; then touch pref.h; fi
+         ])
+
+dnl configure.in ends here
diff --git a/doc/ChangeLog b/doc/ChangeLog
new file mode 100644 (file)
index 0000000..15eb058
--- /dev/null
@@ -0,0 +1,481 @@
+Sun Jan  2 21:30:53 2000  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Updated.
+
+Tue Mar  9 12:47:20 1999  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Updated.
+
+Mon Jan 18 19:29:21 1999  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Updated.
+
+Tue Jan  5 12:04:09 1999  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Updated.
+
+Thu Nov 19 12:35:01 1998  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Revised.
+
+Sun Aug  9 11:11:43 1998  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Revised.
+
+Sat Aug  8 00:19:22 1998  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Revised.
+
+Sun Jul  5 00:14:24 1998  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Updated.
+
+Fri May 29 21:43:52 1998  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Revised.
+
+Wed May 20 00:03:50 1998  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Updated.
+
+Fri Apr 24 12:51:28 1998  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Updated.
+
+Wed Apr 15 13:01:28 1998  Ben Pfaff  <blp@gnu.org>
+
+       * AUTHORS.html, BUGS.html, LANGUAGE.html, README.html,
+       THANKS.html: Removed.
+
+       * Makefile.am: Don't reference the deleted files.
+
+Mon Mar  9 00:55:59 1998  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html: Updated.
+
+1998-03-05  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Updated.
+
+1998-02-23  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Updated.
+
+Fri Feb 13 15:35:44 1998  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html: Updated.
+
+Thu Feb  5 00:18:10 1998  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html: Updated.
+
+       * pspp.texi: Revised.
+
+Tue Jan 13 23:44:43 1998  Ben Pfaff  <blp@gnu.org>
+
+       * BUGS.html: Updated.
+
+       * LANGUAGE.html: Updated.
+
+Thu Jan  8 22:27:29 1998  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Updated.
+
+Sun Jan  4 18:12:11 1998  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html: Updated.
+
+Wed Dec 24 22:36:09 1997  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Updated.
+
+Sun Dec 21 16:18:18 1997  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.texi: Updated.
+
+Fri Dec  5 22:53:35 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.man: Renamed pspp.man.
+
+       * fiasco.texi: Renamed pspp.texi.
+
+Fri Dec  5 21:52:29 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Updated.
+
+Tue Dec  2 14:35:34 1997  Ben Pfaff  <blp@gnu.org>
+
+       * BUGS.html: Updated.
+
+Sat Nov 22 01:20:41 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Revised.
+
+Fri Nov 21 00:02:36 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.man, fiasco.texi: Revised.
+
+Tue Oct 28 16:08:01 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Revised.
+
+Tue Oct  7 20:22:14 1997  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html: Updated.
+
+Sat Oct  4 16:19:27 1997  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html: Updated.
+
+Thu Sep 18 21:33:44 1997  Ben Pfaff  <blp@gnu.org>
+
+       * BUGS.html, LANGUAGE.html: Updated.
+
+Wed Aug 20 14:21:35 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (info_TEXINFOS) Remove FAQ.texi.
+
+Wed Aug 20 12:49:40 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ANNOUNCE.html.in, FAQ.texi, HELP-WANTED.html: Removed.
+       
+       * BUGS.html, LANGUAGE.html, README.html.in: Updated per
+       suggestions of rms.
+
+       * Makefile.am: (noinst_DATA) Removed ANNOUNCE.html,
+       HELP-WANTED.html.
+       (EXTRA_DIST) Removed ANNOUNCE.html, ANNOUNCE.html.in,
+       HELP-WANTED.html.
+       (MAINTAINERCLEANFILES, HTML_FORMATTER) Removed.
+
+       * fiasco.texi: Revised.
+       
+Sat Aug 16 10:51:51 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ANNOUNCE.html.in, HELP-WANTED.html, README.html.in: Updated per
+       suggestions of rms.
+
+       * AUTHORS.html, BUGS.html, FAQ.texi, LANGUAGE.html, THANKS.html,
+       fiasco.man, fiasco.texi: Updated.
+
+       * README-i386linux.html, dist.html.in.in, fiasco.lsm.in,
+       changelogs.html.top, changelogs.html.bot: Removed, all references
+       removed.
+
+Thu Aug 14 22:07:02 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ANNOUNCE.html.in, README.html.in, dist.html.in.in: Updated.
+
+       * Makefile.am: Use $(VERSION) instead of VERSION file.
+       (EXTRA_DIST) Add README-i386linux.
+
+Thu Aug 14 11:52:20 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ANNOUNCE.html.in, AUTHORS.html, BUGS.html, HELP-WANTED.html,
+       LANGUAGE.html, README-i386linux.html, README.html.in, THANKS.html,
+       changelogs.html.bot, changelogs.html.top: Revised.
+
+       * Makefile.am: (noinst_DATA) Remove dist.html, add dist.html.in.
+       (EXTRA_DIST) Add ONEWS, remove dist.html, dist.html.in, add
+       dist.html.in.in.
+       (MAINTAINERCLEANFILES) Add dist.html.in.
+       (dist.html) Removed.
+       (dist.html.in) New target depending on dist.html.in.in.
+       (docfiles) New target.
+
+       * dist.html.in: Renamed dist.html.in.in.
+       
+Tue Aug  5 13:57:20 1997  Ben Pfaff  <blp@gnu.org>
+
+       * FAQ.texi, fiasco.texi: Updated.
+
+Sun Aug  3 11:34:43 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ANNOUNCE.html.in, AUTHORS.html, BUGS.html, FAQ.texi,
+       LANGUAGE.html, README-i386linux.html, README.html.in, THANKS.html,
+       changelogs.html.bot, dist.html.in, fiasco.texi: Updated.
+
+       * Makefile.am: (noinst_DATA, EXTRA_DIST) Add HELP-WANTED.html,
+       remove README-i386gnuwin32.html.
+       (MAINTAINERCLEANFILES) Remove README-i386gnuwin32.html, add
+       README-i386linux.
+       (README-i386linux) New target.
+
+       * README-i386gnuwin32.html.in: Removed.
+
+       * HELP-WANTED.html: New file.
+       
+Thu Jul 17 21:40:28 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Generates fiasco.lsm from fiasco.lsm.in.
+
+Thu Jul 17 01:49:06 1997  Ben Pfaff  <blp@gnu.org>
+
+       * FAQ.texi: Updated.
+
+       * Makefile.am: Completely rewritten.
+
+       * ANNOUNCE.html.in, README-i386gnuwin32.html.in,
+       README-i386linux.html, README.html.in, dist.html.in,
+       fiasco.lsm.in: New files.
+
+Fri Jul 11 23:01:32 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Updated.
+
+Sun Jul  6 20:46:38 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ANNOUNCE.html, FAQ.texi, README.html: Updated.
+
+       * Makefile.am: Add all the recent new files to EXTRA_DIST.
+
+Sat Jul  5 23:43:50 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ANNOUNCE.html, FAQ.texi, README.html: Updated.
+
+       * changelogs.html.bot: Fix copyright notice.
+
+       * fiasco.man: New file.
+
+Fri Jul  4 13:23:57 1997  Ben Pfaff  <blp@gnu.org>
+
+       * changelogs.html.bot, changelogs.html.top: New files.
+
+       * fiasco.lsm: New file.
+
+       * ANNOUNCE.html, FAQ.texi, README.html: Updated.
+
+       * Makefile.am: (EXTRA_DIST) Removed duplicate assignment.
+
+Wed Jun 25 22:51:39 1997  Ben Pfaff  <blp@gnu.org>
+
+       * FAQ.texi: Finished.
+
+       * README.html: Updates.
+
+Sun Jun 22 21:59:07 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ANNOUNCE.html, BUGS.html, LANGUAGE.html, README.html,
+       fiasco.texi: Updates.
+
+       * Makefile.am: Add `FAQ.texi' to info_TEXINFOS.
+       
+       * FAQ.texi: New file.
+
+Tue Jun  3 23:25:51 1997  Ben Pfaff  <blp@gnu.org>
+
+       * AUTHORS.html, BUGS.html, README.html, THANKS.html: Updates.
+
+       * fiasco.texi: Update.
+
+Sun Jun  1 11:58:27 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Development.
+
+Fri May 30 19:39:37 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Development.
+
+Mon May  5 21:57:20 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Development.
+
+Fri May  2 22:07:26 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Development.
+
+Thu May  1 14:58:31 1997  Ben Pfaff  <blp@gnu.org>
+
+       * BUGS.html: Update.
+
+       * fiasco.texi: Development.
+
+Wed Apr 23 21:33:48 1997  Ben Pfaff  <blp@gnu.org>
+
+       * THANKS.html: Update.
+
+Fri Apr 18 15:42:22 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Maintainer-clean Makefile.in.
+       
+Thu Mar 27 01:11:29 1997  Ben Pfaff  <blp@gnu.org>
+
+       * THANKS.html: Added Fran,cois Pinard.
+
+Mon Mar 24 21:47:31 1997  Ben Pfaff  <blp@gnu.org>
+
+       * THANKS.html: Spelling fix.
+
+Sat Feb 15 21:26:53 1997  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html: Updated.
+
+Fri Feb 14 23:32:58 1997  Ben Pfaff  <blp@gnu.org>
+
+       * BUGS.html: Updated.
+       
+Wed Jan 22 21:54:00 1997  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html: RENAME VARIABLES is implemented.
+
+Thu Jan 16 13:08:57 1997  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html: MODIFY VARS now works.
+
+       * README.html: Added `alpha.gnu.ai.mit.edu' to list of sites.
+
+Sat Jan 11 15:44:15 1997  Ben Pfaff  <blp@gnu.org>
+
+       * README.html: Commented out sunsite reference and added
+       ALPHA-release warning.
+
+Fri Jan 10 20:22:08 1997  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html: Reformatted.
+
+Thu Jan  2 19:08:23 1997  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html: Updated.
+
+Wed Jan  1 22:08:10 1997  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html: Updated.
+       
+Sun Dec 29 21:36:48 1996  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html: Updated.
+       
+       * fiasco.texi: Updated.
+
+Tue Dec 24 20:42:32 1996  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html, README.html: Miscellaneous changes.
+
+Sun Dec 22 23:10:39 1996  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE.html, README.html: Miscellaneous changes.
+       
+       * AUTHORS.html, BUGS.html, THANKS.html: New files derived from
+       corresponding files without the `.html'.
+
+Sat Dec 21 21:51:04 1996  Ben Pfaff  <blp@gnu.org>
+
+       * AUTHORS: Grammar fix.
+
+       * LANGUAGE.html: New file.  LANGUAGE is now automatically
+       generated from this html source through lynx.
+
+       * README.html: Similar situation to LANGUAGE.html.
+
+Sun Dec 15 15:32:16 1996  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE: Updated.
+
+Fri Dec  6 23:53:47 1996  Ben Pfaff  <blp@gnu.org>
+
+       * AUTHORS, BUGS, LANGUAGE, README: Updated.
+
+       * fiasco.texi: Fixes.
+
+Wed Dec  4 21:34:17 1996  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE: Updated.
+       
+Sun Dec  1 17:19:00 1996  Ben Pfaff  <blp@gnu.org>
+
+       * BUGS, LANGUAGE, NEWS: Misc. changes.
+
+Sun Nov 24 14:53:53 1996  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Changed many instances of `illegal' to `invalid'.
+
+Wed Oct 30 17:13:08 1996  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE: Updated.
+
+       * README: Updated.
+
+Sat Oct 26 23:06:06 1996  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE: Updated.
+
+Sat Oct 26 10:39:25 1996  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE: Updated.
+
+Thu Oct 24 20:13:42 1996  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE: Updated.
+
+       * README: Updated.
+
+       * fiasco.texi: Updated.
+
+Thu Oct 24 17:47:14 1996  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE: Updated.
+
+Wed Oct 23 21:53:43 1996  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE: Updated.
+
+Tue Oct 22 17:27:04 1996  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE: Updated.
+
+       * fiasco.texi: Very minor changes.
+
+Sun Sep 29 19:37:03 1996  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Continued development.
+
+Tue Sep 24 18:39:09 1996  Ben Pfaff  <blp@gnu.org>
+
+       * avl.texi, gpl.texi: Removed.
+
+       * fiasco.texi: Changed copyright notices; deleted references to
+       avl.texi, gpl.texi.
+
+Sat Sep 21 23:16:31 1996  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Continued work--added to configuration chapter.
+
+Fri Sep 20 22:52:28 1996  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Continued work--added to configuration chapter.
+
+Thu Sep 12 18:40:33 1996  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Continued work--added section on bug reports.
+
+Wed Sep 11 22:01:41 1996  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Added timestamp.  Started some updating.
+
+Tue Sep 10 21:39:00 1996  Ben Pfaff  <blp@gnu.org>
+
+       * LANGUAGE: Updated.
+
+       * README: Minor change.
+
+Mon Sep  9 21:43:13 1996  Ben Pfaff  <blp@gnu.org>
+
+       * NEWS: Added automagic timestamp.
+
+       * README: Restructured, extended.
+
+       * BUGS, LANGUAGE: New files.
+
+Sat Jul  6 22:22:25 1996  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.texi: Remarked on broken Borland alloca().
+
+Mon Jul  1 13:00:00 1996  Ben Pfaff  <blp@gnu.org>
+
+       * stat.texi: Renamed to `fiasco.texi'.
+       
+----------------------------------------------------------------------
+Local Variables:
+mode: change-log
+version-control: never
+End:
diff --git a/doc/Makefile.am b/doc/Makefile.am
new file mode 100644 (file)
index 0000000..77de977
--- /dev/null
@@ -0,0 +1,11 @@
+## Process this file with automake to produce Makefile.in  -*- makefile -*-
+
+info_TEXINFOS = pspp.texi
+
+# FIXME: remove this when the manual is fixed to eliminate dangling
+# references.
+MAKEINFO = makeinfo --no-validate
+
+EXTRA_DIST = pspp.man
+
+MAINTAINERCLEANFILES = Makefile.in README.html
diff --git a/doc/mdate-sh b/doc/mdate-sh
new file mode 100755 (executable)
index 0000000..0c7ad12
--- /dev/null
@@ -0,0 +1,91 @@
+#!/bin/sh
+# mdate-sh - get modification time of a file and pretty-print it
+# Copyright (C) 1995 Software Foundation, Inc.
+# Written by Ulrich Drepper <drepper@gnu.org>, June 1995
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# Prevent date giving response in another language.
+LANG=C
+export LANG
+LC_ALL=C
+export LC_ALL
+LC_TIME=C
+export LC_TIME
+
+# Get the extended ls output of the file.
+if ls -L /dev/null 1>/dev/null 2>&1; then
+  set - `ls -L -l $1`
+else
+  set - `ls -l $1`
+fi
+# The month is at least the fourth argument.
+# (3 shifts here, the next inside the loop)
+shift
+shift
+shift
+
+# Find the month.  Next argument is day, followed by the year or time.
+month=
+until test $month
+do
+  shift
+  case $1 in
+    Jan) month=January; nummonth=1;;
+    Feb) month=February; nummonth=2;;
+    Mar) month=March; nummonth=3;;
+    Apr) month=April; nummonth=4;;
+    May) month=May; nummonth=5;;
+    Jun) month=June; nummonth=6;;
+    Jul) month=July; nummonth=7;;
+    Aug) month=August; nummonth=8;;
+    Sep) month=September; nummonth=9;;
+    Oct) month=October; nummonth=10;;
+    Nov) month=November; nummonth=11;;
+    Dec) month=December; nummonth=12;;
+  esac
+done
+
+day=$2
+
+# Here we have to deal with the problem that the ls output gives either
+# the time of day or the year.
+case $3 in
+  *:*) set `date`; eval year=\$$#
+       case $2 in
+        Jan) nummonthtod=1;;
+        Feb) nummonthtod=2;;
+        Mar) nummonthtod=3;;
+        Apr) nummonthtod=4;;
+        May) nummonthtod=5;;
+        Jun) nummonthtod=6;;
+        Jul) nummonthtod=7;;
+        Aug) nummonthtod=8;;
+        Sep) nummonthtod=9;;
+        Oct) nummonthtod=10;;
+        Nov) nummonthtod=11;;
+        Dec) nummonthtod=12;;
+       esac
+       # For the first six month of the year the time notation can also
+       # be used for files modified in the last year.
+       if (expr $nummonth \> $nummonthtod) > /dev/null;
+       then
+        year=`expr $year - 1`
+       fi;;
+  *) year=$3;;
+esac
+
+# The result.
+echo $day $month $year
diff --git a/doc/pspp.man b/doc/pspp.man
new file mode 100644 (file)
index 0000000..0579230
--- /dev/null
@@ -0,0 +1,45 @@
+.\" PSPP - computes sample statistics.
+.\" Copyright (C) 1997, 1998 Free Software Foundation, Inc.
+.\" Written by Ben Pfaff <blp@gnu.org>.
+.\"
+.\" This program is free software; you can redistribute it and/or
+.\" modify it under the terms of the GNU General Public License as
+.\" published by the Free Software Foundation; either version 2 of the
+.\" License, or (at your option) any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful, but
+.\" WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+.\" General Public License for more details.
+.\"
+.\" You should have received a copy of the GNU General Public License
+.\" along with this program; if not, write to the Free Software
+.\" Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+.\" 02111-1307, USA.
+.\"
+.TH pspp 1 "4 Jul 1997" "manpage v1.0" "PSPP manual"
+.SH NAME
+pspp \- a system for statistical analysis
+.SH SYNOPSIS
+.B pspp
+\&.\|.\|.
+.SH DESCRIPTION
+.B pspp
+starts up the PSPP program.  PSPP performs statistical analysis on
+sampled data.  Please see Info document `pspp' for more details on
+using PSPP.  For other miscellaneous information about PSPP, see
+the PSPP FAQ, which should be installed in /usr/doc/pspp.
+
+These documents and others are available in various formats.  On
+Debian GNU/Linux systems, full documentation is available in directory
+`/usr/doc/pspp', in HTML and ASCII formats, and in `/usr/info', in
+Info format.  TeX can be used to convert the Texinfo documentation in
+the source distribution to nice-looking output for printing.
+
+Documentation is also at http://www.gnu.org/software/pspp.
+
+.SH BUGS
+
+Probably a lot.  Known bugs are listed in the documentation files
+BUGS, LANGUAGE, and TODO, depending on type.  Please see those files
+for more details.
diff --git a/doc/pspp.texi b/doc/pspp.texi
new file mode 100644 (file)
index 0000000..86a9be0
--- /dev/null
@@ -0,0 +1,9832 @@
+\input texinfo @c -*- texinfo -*-
+@c %**start of header
+@setfilename pspp.info
+@settitle PSPP
+@set TIMESTAMP Time-stamp: <2000-01-02 22:32:14 blp>
+@set EDITION 0.2
+@set VERSION 0.2
+@c For double-sided printing, uncomment:
+@c @setchapternewpage odd
+@c %**end of header
+
+@iftex
+@finalout
+@end iftex
+
+@ifinfo
+@format
+START-INFO-DIR-ENTRY
+* PSPP: (pspp).             Statistical analysis package.
+END-INFO-DIR-ENTRY
+@end format
+
+PSPP, for statistical analysis of sampled data, by Ben Pfaff.
+
+This file documents PSPP, a statistical package for analysis of
+sampled data that uses a command language compatible with SPSS.
+
+Copyright (C) 1996-9, 2000 Free Software Foundation, Inc.
+
+This version of the PSPP documentation is consistent with version 2 of
+``texinfo.tex''.
+
+Permission is granted to make and distribute verbatim copies of this
+manual provided the copyright notice and this permission notice are
+preserved on all copies.
+
+@ignore
+Permission is granted to process this file through TeX and print the
+results, provided the printed document carries copying permission notice
+identical to this one except for the removal of this paragraph (this
+paragraph not being relevant to the printed manual).
+
+@end ignore
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided that the
+entire resulting derived work is distributed under the terms of a
+permission notice identical to this one.
+
+Permission is granted to copy and distribute translations of this
+manual into another language, under the above condition for modified 
+versions, except that this permission notice may be stated in a 
+translation approved by the Free Software Foundation.
+@end ifinfo
+
+@titlepage
+@title PSPP
+@subtitle A System for Statistical Analysis
+@subtitle Edition @value{EDITION}, for PSPP version @value{VERSION}
+@author by Ben Pfaff
+
+@page
+@vskip 0pt plus 1filll
+
+PSPP Copyright @copyright{} 1997, 1998 Free Software Foundation, Inc.
+
+Permission is granted to make and distribute verbatim copies of this
+manual provided the copyright notice and this permission notice are
+preserved on all copies.
+
+Permission is granted to copy and distribute modified versions of this
+manual under the conditions for verbatim copying, provided that the
+entire derived work is distributed under the terms of a permission
+notice identical to this one.
+
+Permission is granted to copy and distribute translations of this manual
+into another language, under the above conditions for modified versions,
+except that this permission notice may be stated in a translation
+approved by the Foundation.
+@end titlepage
+
+@node Top, Introduction, (dir), (dir)
+@ifinfo
+@top PSPP
+
+This file documents the PSPP package for statistical analysis of sampled
+data.  This is edition @value{EDITION}, for PSPP version
+@value{VERSION}, last modified at @value{TIMESTAMP}.
+
+@end ifinfo
+
+@menu
+* Introduction::                Description of the package.
+* License::                     Your rights and obligations.
+* Credits::                     Acknowledgement of authors.
+
+* Installation::                How to compile and install PSPP.
+* Configuration::               Configuring PSPP.
+* Invocation::                  Starting and running PSPP.
+
+* Language::                    Basics of the PSPP command language.
+* Expressions::                 Numeric and string expression syntax.
+
+* Data Input and Output::       Reading data from user files.
+* System and Portable Files::   Dealing with system & portable files.
+* Variable Attributes::         Adjusting and examining variables.
+* Data Manipulation::           Simple operations on data.
+* Data Selection::              Select certain cases for analysis.
+* Conditionals and Looping::    Doing things many times or not at all.
+* Statistics::                  Basic statistical procedures.
+* Utilities::                   Other commands.
+* Not Implemented::             What's not here yet
+
+* Data File Format::            Format of PSPP system files.
+* Portable File Format::        Format of PSPP portable files.
+* q2c Input Format::            Format of syntax accepted by q2c.
+
+* Bugs::                        Known problems; submitting bug reports.
+
+* Function Index::              Index of PSPP functions for expressions.
+* Concept Index::               Index of concepts.
+* Command Index::               Index of PSPP procedures.
+
+@end menu
+
+@node Introduction, License, Top, Top
+@chapter Introduction
+@cindex introduction
+
+@cindex PSPP language
+@cindex language, PSPP
+PSPP is a tool for statistical analysis of sampled data.  It reads a
+syntax file and a data file, analyzes the data, and writes the results
+to a listing file or to standard output.
+
+The language accepted by PSPP is similar to those accepted by SPSS
+statistical products.  The details of PSPP's language are given
+later in this manual.
+
+@cindex files, PSPP
+@cindex output, PSPP
+@cindex PostScript
+@cindex graphics
+@cindex Ghostscript
+@cindex Free Software Foundation
+PSPP produces output in two forms: tables and charts.  Both of these can
+be written in several formats; currently, ASCII, PostScript, and HTML
+are supported.  In the future, more drivers, such as PCL and X Window
+System drivers, may be developed.  For now, Ghostscript, available from
+the Free Software Foundation, may be used to convert PostScript chart
+output to other formats.
+
+The current version of PSPP, @value{VERSION}, is woefully incomplete in
+terms of its statistical procedure support.  PSPP is a work in progress.
+The author hopes to support fully support all features in the products
+that PSPP replaces, eventually.  The author welcomes questions,
+comments, donations, and code submissions.  @xref{Bugs,,Submitting Bug
+Reports}, for instructions on contacting the author.
+
+@node License, Credits, Introduction, Top
+@chapter Your rights and obligations
+@cindex license
+@cindex your rights and obligations
+@cindex rights, your
+@cindex obligations, your
+
+@cindex Free Software Foundation
+@cindex GNU General Public License
+@cindex General Public License
+@cindex GPL
+@cindex distribution
+@cindex redistribution
+Most of PSPP is distributed under the GNU General Public
+License.  The General Public License says, in effect, that you may
+modify and distribute PSPP as you like, as long as you grant the
+same rights to others.  It also states that you must provide source code
+when you distribute PSPP, or, if you obtained PSPP
+source code from an anonymous ftp site, give out the name of that site.
+
+The General Public License is given in full in the source distribution
+as file @file{COPYING}.  In Debian GNU/Linux, this file is also
+available as file @file{/usr/doc/copyright/GPL}.
+
+To quote the GPL itself:
+
+@quotation
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+675 Mass Ave, Cambridge, MA 02139, USA.
+@end quotation
+
+@node Credits, Installation, License, Top
+@chapter Credits
+@cindex credits
+@cindex authors
+
+@cindex Minton, Claire
+@cindex @cite{Cat's Cradle}
+@cindex Vonnegut, Kurt, Jr.
+@cindex quotations
+@quotation
+I'm always embarrassed when I see an index an author has made of his own
+work.  It's a shameless exhibition---to the @i{trained} eye.  Never
+index your own book.
+
+---Claire Minton, @cite{Cat's Cradle}, Kurt Vonnegut, Jr.
+@end quotation
+
+@cindex Pfaff, Ben
+Most of PSPP, as well as this manual (including the indices),
+was written by Ben Pfaff.  @xref{Contacting the Author}, for
+instructions on contacting the author.
+
+@cindex Covington, Michael A.
+@cindex Van Zandt, James
+@cindex @file{ftp.cdrom.com}
+@cindex @file{/pub/algorithms/c/julcal10}
+@cindex @file{julcal.c}
+@cindex @file{julcal.h}
+The PSPP source code incorporates @code{julcal10} originally
+written by Michael A. Covington and translated into C by Jim Van Zandt.
+The original package can be found in directory
+@file{ftp://ftp.cdrom.com/pub/algorithms/c/julcal10}.  The entire
+contents of that directory constitute the package.  The files actually
+used in PSPP are @code{julcal.c} and @code{julcal.h}.
+
+@node Installation, Configuration, Credits, Top
+@chapter Installing PSPP
+@cindex installation
+@cindex PSPP, installing
+
+@cindex GNU C compiler
+@cindex gcc
+@cindex compiler, recommended
+@cindex compiler, gcc
+PSPP conforms to the GNU Coding Standards.  PSPP is written in, and
+requires for proper operation, ANSI/ISO C.  You might want to
+additionally note the following points:
+
+@itemize @bullet
+@item
+The compiler and linker must allow for significance of several
+characters in external identifiers.  The exact number is unknown but at
+least 31 is recommended.
+
+@item
+The @code{int} type must be 32 bits or wider.
+
+@item
+The recommended compiler is gcc 2.7.2.1 or later, but any ANSI compiler
+will do if it fits the above criteria.
+@end itemize
+
+Many UNIX variants should work out-of-the-box, as PSPP uses GNU
+autoconf to detect differences between environments.  Please report any
+problems with compilation of PSPP under UNIX and UNIX-like operating
+systems---portability is a major concern of the author.
+
+The pages below give specific instructions for installing PSPP
+on each type of system mentioned above.
+
+@menu
+* UNIX installation::           Installing on UNIX-like environments.
+@end menu
+
+@node UNIX installation,  , Installation, Installation
+@section UNIX installation
+@cindex UNIX, installing PSPP under
+@cindex installation, under UNIX
+@noindent
+To install PSPP under a UNIX-like operating system, follow the steps
+below in order.  Some of the text below was taken directly from various
+Free Software Foundation sources.
+
+@enumerate
+@item
+@code{cd} to the directory containing the PSPP source.
+
+@cindex configure, GNU
+@cindex GNU configure
+@item
+Type @samp{./configure} to configure for your particular operating
+system and compiler.  Running @code{configure} takes a while.  While
+running, it displays some messages telling which features it is checking
+for.
+
+You can optionally supply some options to @code{configure} in order to
+give it hints about how to do its job.  Type @code{./configure --help}
+to see a list of options.  One of the most useful options is
+@samp{--with-checker}, which enables the use of the Checker memory
+debugger under supported operating systems.  Checker must already be
+installed to use this option.  Do not use @samp{--with-checker} if you
+are not debugging PSPP itself.
+
+@cindex @file{Makefile}
+@cindex @file{config.h}
+@cindex @file{pref.h}
+@cindex makefile
+@item
+(optional) Edit @file{Makefile}, @file{config.h}, and @file{pref.h}.
+These files are produced by @code{configure}.  Note that most PSPP
+settings can be changed at runtime.
+
+@file{pref.h} is only generated by @code{configure} if it does not
+already exist.  (It's copied from @file{prefh.orig}.)
+
+@cindex compiling
+@item
+Type @samp{make} to compile the package.  If there are any errors during
+compilation, try to fix them.  If modifications are necessary to compile
+correctly under your configuration, contact the author.
+@xref{Bugs,,Submitting Bug Reports}, for details.
+
+@cindex self-tests, running
+@item
+Type @samp{make check} to run self-tests on the compiled PSPP package.
+
+@cindex installation
+@cindex PSPP, installing
+@cindex @file{/usr/local/share/pspp/}
+@cindex @file{/usr/local/bin/}
+@cindex @file{/usr/local/info/}
+@cindex documentation, installing
+@item
+Become the superuser and type @samp{make install} to install the
+PSPP binaries, by default in @file{/usr/local/bin/}.  The
+directory @file{/usr/local/share/pspp/} is created and populated with
+files needed by PSPP at runtime.  This step will also cause the
+PSPP documentation to be installed in @file{/usr/local/info/},
+but only if that directory already exists.
+
+@item
+(optional) Type @samp{make clean} to delete the PSPP binaries
+from the source tree.
+@end enumerate
+
+@node Configuration, Invocation, Installation, Top
+@chapter Configuring PSPP
+@cindex configuration
+@cindex PSPP, configuring
+
+PSPP has dozens of configuration possibilities and hundreds of
+settings.  This is both a bane and a blessing.  On one hand, it's
+possible to easily accommodate diverse ranges of setups.  But, on the
+other, the multitude of possibilities can overwhelm the casual user.
+Fortunately, the configuration mechanisms are profusely described in the
+sections below@enddots{}
+
+@menu
+* File locations::              How PSPP finds config files.
+* Configuration techniques::    Many different methods of configuration@enddots{}
+* Configuration files::         How configuration files are read.
+* Environment variables::       All about environment variables.
+* Output devices::              Describing your terminal(s) and printer(s).
+* PostScript driver class::     Configuration of PostScript devices.
+* ASCII driver class::          Configuration of character-code devices.
+* HTML driver class::           Configuration for HTML output.
+* Miscellaneous configuring::   Even more configuration variables.
+* Improving output quality::    Hints for producing ever-more-lovely output.
+@end menu
+
+@node File locations, Configuration techniques, Configuration, Configuration
+@section Locating configuration files
+
+PSPP uses the same method to find most of its configuration files:
+
+@enumerate
+@item
+The @dfn{base name} of the file being sought is determined.
+
+@item
+The path to search is determined.  
+
+@item
+Each directory in the search path, from left to right, is searched for a
+file with the name of the base name.  The first occurrence is read
+as the configuration file.
+@end enumerate
+
+The first two steps are elaborated below for the sake of our pedantic
+friends.
+
+@enumerate
+@item
+A @dfn{base name} is a file name lacking an absolute directory
+reference.  Some examples of base names are: @file{ps-encodings},
+@file{devices}, @file{devps/DESC} (under UNIX), @file{devps\DESC} (under
+M$ environments).
+
+Determining the base name is a two-step process:
+
+@enumerate a
+@item
+If the appropriate environment variable is defined, the value of that
+variable is used (@pxref{Environment variables}).  For instance, when
+searching for the output driver initialization file, the variable
+examined is @code{STAT_OUTPUT_INIT_FILE}.
+
+@item
+Otherwise, the compiled-in default is used.  For example, when searching
+for the output driver initialization file, the default base name is
+@file{devices}.
+@end enumerate
+
+@strong{Please note:} If a user-specified base name does contain an
+absolute directory reference, as in a file name like
+@file{/home/pfaff/fonts/TR}, no path is searched---the file name is used
+exactly as given---and the algorithm terminates.
+
+@item
+The path is the first of the following that is defined:
+
+@itemize @bullet
+@item
+A variable definition for the path given in the user environment.  This
+is a PSPP-specific environment variable name; for instance,
+@code{STAT_OUTPUT_INIT_PATH}.
+
+@item 
+In some cases, another, less-specific environment variable is checked.
+For instance, when searching for font files, the PostScript driver first
+checks for a variable with name @code{STAT_GROFF_FONT_PATH}, then for
+one with name @code{GROFF_FONT_PATH}.  (However, font searching has its
+own list of esoteric search rules.)
+
+@item
+The configuration file path, which is itself determined by the
+following rules:
+
+@enumerate a
+@item
+If the command line contains an option of the form @samp{-B @var{path}}
+or @samp{--config-dir=@var{path}}, then the value given on the
+rightmost occurrence of such an option is used.
+
+@item
+Otherwise, if the environment variable @code{STAT_CONFIG_PATH} is
+defined, the value of that variable is used.
+
+@item
+Otherwise, the compiled-in fallback default is used.  On UNIX machines,
+the default fallback path is
+
+@enumerate 1
+@item
+@file{~/.pspp}
+
+@item
+@file{/usr/local/lib/pspp}
+
+@item
+@file{/usr/lib/pspp}
+@end enumerate
+
+On DOS machines, the default fallback path is:
+
+@enumerate 1
+@item
+All the paths from the DOS search path in the @samp{PATH} environment
+variable, in left-to-right order.
+
+@item
+@file{C:\PSPP}, as a last resort.
+@end enumerate
+
+Note that the installer of PSPP can easily change this default
+fallback path; thus the above should not be taken as gospel.
+@end enumerate
+@end itemize
+@end enumerate
+
+As a final note: Under DOS, directories given in paths are delimited by
+semicolons (@samp{;}); under UNIX, directories are delimited by colons
+(@samp{:}).  This corresponds with the standard path delimiter under
+these OSes.
+
+@node Configuration techniques, Configuration files, File locations, Configuration
+@section Configuration techniques
+
+There are many ways that PSPP can be configured.  These are
+described in the list below.  Values given by earlier items take
+precedence over those given by later items.
+
+@enumerate
+@item
+Syntax commands that modify settings, such as @code{SET}.
+
+@item
+Command-line options.  @xref{Invocation}.
+
+@item
+PSPP-specific environment variable contents.  @xref{Environment
+variables}.
+
+@item
+General environment variable contents.  @xref{Environment variables}.
+
+@item
+Configuration file contents.  @xref{Configuration files}.
+
+@item
+Fallback defaults.
+@end enumerate
+
+Some of the above may not apply to a particular setting.  For instance,
+the current pager (such as @samp{more}, @samp{most}, or @samp{less})
+cannot be determined by configuration file contents because there is no
+appropriate configuration file.
+
+@node Configuration files, Environment variables, Configuration techniques, Configuration
+@section Configuration files
+
+Most configuration files have a common form:
+
+@itemize @bullet
+@item
+Each line forms a separate command or directive.  This means that lines
+cannot be broken up, unless they are spliced together with a trailing
+backslash, as described below.
+
+@item
+Before anything else is done, trailing whitespace is removed.
+
+@item
+When a line ends in a backslash (@samp{\}), the backslash is removed,
+and the next line is read and appended to the current line.
+
+@itemize @minus
+@item
+Whitespace preceding the backslash is retained.
+
+@item
+This rule continues to be applied until the line read does not end in a
+backslash.
+
+@item
+It is an error if the last line in the file ends in a backslash.
+@end itemize
+
+@item
+Comments are introduced by an octothorpe (#), and continue until the
+end of the line.
+
+@itemize @minus
+@item
+An octothorpe inside balanced pairs of double quotation marks (@samp{"})
+or single quotation marks (@samp{'}) does not introduce a comment.
+
+@item
+The backslash character can be used inside balanced quotes of either
+type to escape the following character as a literal character.  
+
+(This is distinct from the use of a backslash as a line-splicing
+character.)
+
+@item
+Line splicing takes place before comment removal.
+@end itemize
+
+@item
+Blank lines, and lines that contain only whitespace, are ignored.
+@end itemize
+
+@node Environment variables, Output devices, Configuration files, Configuration
+@section Environment variables
+
+You may think the concept of environment variables is a fairly simple
+one.  However, the author of PSPP has found a way to complicate
+even something so simple.  Environment variables are further described
+in the sections below:
+
+@menu
+* Variable values::             Values of variables are determined this way.
+* Environment substitutions::   How environment substitutions are made.
+* Predefined variables::        A few variables are automatically defined.
+@end menu
+
+@node Variable values, Environment substitutions, Environment variables, Environment variables
+@subsection Values of environment variables
+
+Values for environment variables are obtained by the following means,
+which are arranged in order of decreasing precedence:
+
+@enumerate
+@item
+Command-line options.  @xref{Invocation}.
+
+@item
+The @file{environment} configuration file---more on this below.
+
+@item
+Actual environment variables (defined in the shell or other parent
+process).
+@end enumerate
+
+The @file{environment} configuration file is located through application
+of the usual algorithm for configuration files (@pxref{File locations}),
+except that its contents do not affect the search path used to find
+@file{environment} itself.  Use of @file{environment} is discouraged on
+systems that allow an arbitrarily large environment; it is supported for
+use on systems like MS-DOS that limit environment size.
+
+@file{environment} is composed of lines having the form
+@samp{@var{key}=@var{value}}, where @var{key} and the equals sign
+(@samp{=}) are required, and @var{value} is optional.  If @var{value} is
+given, variable @var{key} is given that value; if @var{value} is absent,
+variable @var{key} is undefined (deleted).  Variables may not be defined
+with a null value.
+
+Environment substitutions are performed on each line in the file
+(@pxref{Environment substitutions}).
+
+See @ref{Configuration files}, for more details on formatting of the
+environment configuration file.
+
+@quotation
+@strong{Please note:} Support for @file{environment} is not yet
+implemented.
+@end quotation
+
+@node Environment substitutions, Predefined variables, Variable values, Environment variables
+@subsection Environment substitutions
+
+Much of the power of environment variables lies in the way that they may
+be substituted into configuration files.  Variable substitutions are
+described below.
+
+The line is scanned from left to right.  In this scan, all characters
+other than dollar signs (@samp{$}) are retained unmolested.  Dollar
+signs, however, introduce an environment variable reference.  References
+take three forms:
+
+@table @code
+@item $@var{var}
+Replaced by the value of environment variable @var{var}, determined as
+specified in @ref{Variable values}.  @var{var} must be one of the
+following:
+
+@itemize @bullet
+@item
+One or more letters.
+
+@item
+Exactly one nonalphabetic character.  This may not be a left brace
+(@samp{@{}).
+@end itemize
+
+@item $@{@var{var}@}
+Same as above, but @var{var} may contain any character (except
+@samp{@}}).
+
+@item $$
+Replaced by a single dollar sign.
+@end table
+
+Undefined variables expand to a empty value.
+
+@node Predefined variables,  , Environment substitutions, Environment variables
+@subsection Predefined environment variables
+
+There are two environment variables predefined for use in environment
+substitutions:
+
+@table @samp
+@item VER
+Defined as the version number of PSPP, as a string, in a format
+something like @samp{0.9.4}.
+
+@item ARCH
+Defined as the host architecture of PSPP, as a string, in standard
+cpu-manufacturer-OS format.  For instance, Debian GNU/Linux 1.1 on an
+Intel machine defines this as @samp{i586-unknown-linux}.  This is
+somewhat dependent on the system used to compile PSPP.
+@end table
+
+Nothing prevents these values from being overridden, although it's a
+good idea not to do so.
+
+@node Output devices, PostScript driver class, Environment variables, Configuration
+@section Output devices
+
+Configuring output devices is the most complicated aspect of configuring
+PSPP.  The output device configuration file is named
+@file{devices}.  It is searched for using the usual algorithm for
+finding configuration files (@pxref{File locations}).  Each line in the
+file is read in the usual manner for configuration files
+(@pxref{Configuration files}).
+
+Lines in @file{devices} are divided into three categories, described
+briefly in the table below:
+
+@table @i
+@item driver category definitions
+Define a driver in terms of other drivers.
+
+@item macro definitions
+Define environment variables local to the the output driver
+configuration file.
+
+@item device definitions
+Describe the configuration of an output device.
+@end table
+
+The following sections further elaborate the contents of the
+@file{devices} file.
+
+@menu
+* Driver categories::           How to organize the driver namespace.
+* Macro definitions::           Environment variables local to @file{devices}.
+* Device definitions::          Output device descriptions.
+* Dimensions::                  Lengths, widths, sizes, @enddots{}
+* papersize::                   Letter, legal, A4, envelope, @enddots{}
+* Distinguishing line types::   Details on @file{devices} parsing.
+* Tokenizing lines::            Dividing @file{devices} lines into tokens.
+@end menu
+
+@node Driver categories, Macro definitions, Output devices, Output devices
+@subsection Driver categories
+
+Drivers can be divided into categories.  Drivers are specified by their
+names, or by the names of the categories that they are contained in.
+Only certain drivers are enabled each time PSPP is run; by
+default, these are the drivers in the category `default'.  To enable a
+different set of drivers, use the @samp{-o @var{device}} command-line
+option (@pxref{Invocation}).
+
+Categories are specified with a line of the form
+@samp{@var{category}=@var{driver1} @var{driver2} @var{driver3} @var{@dots{}}
+@var{driver@var{n}}}.  This line specifies that the category
+@var{category} is composed of drivers named @var{driver1},
+@var{driver2}, and so on.  There may be any number of drivers in the
+category, from zero on up.
+
+Categories may also be specified on the command line
+(@pxref{Invocation}).
+
+This is all you need to know about categories.  If you're still curious,
+read on.
+
+First of all, the term `categories' is a bit of a misnomer.  In fact,
+the internal representation is nothing like the hierarchy that the term
+seems to imply: a linear list is used to keep track of the enabled
+drivers.
+
+When PSPP first begins reading @file{devices}, this list contains
+the name of any drivers or categories specified on the command line, or
+the single item `default' if none were specified.
+
+Each time a category definition is specified, the list is searched for
+an item with the value of @var{category}.  If a matching item is found,
+it is deleted.  If there was a match, the list of drivers (@var{driver1}
+through @var{driver@var{n}}) is then appended to the list.
+
+Each time a driver definition line is encountered, the list is searched.
+If the list contains an item with that driver's name, the driver is
+enabled and the item is deleted from the list.  Otherwise, the driver
+is not enabled.
+
+It is an error if the list is not empty when the end of @file{devices}
+is reached.
+
+@node Macro definitions, Device definitions, Driver categories, Output devices
+@subsection Macro definitions
+
+Macro definitions take the form @samp{define @var{macroname}
+@var{definition}}.  In such a macro definition, the environment variable
+@var{macroname} is defined to expand to the value @var{definition}.
+Before the definition is made, however, any macros used in
+@var{definition} are expanded.
+
+Please note the following nuances of macro usage:
+
+@itemize @bullet
+@item
+For the purposes of this section, @dfn{macro} and @dfn{environment
+variable} are synonyms.
+
+@item
+Macros may not take arguments.
+
+@item
+Macros may not recurse.
+
+@item
+Macros are just environment variable definitions like other environment
+variable definitions, with the exception that they are limited in scope
+to the @file{devices} configuration file.
+
+@item
+Macros override other all environment variables of the same name (within
+the scope of @file{devices}).
+
+@item
+Earlier macro definitions for a particular @var{key} override later
+ones.  In particular, macro definitions on the command line override
+those in the device definition file.  @xref{Non-option Arguments}.
+
+@item
+There are two predefined macros, whose values are determined at runtime:
+
+@table @samp
+@item viewwidth
+Defined as the width of the console screen, in columns of text.
+
+@item viewlength
+Defined as the length of the console screen, in lines of text.
+@end table
+@end itemize
+
+@node Device definitions, Dimensions, Macro definitions, Output devices
+@subsection Driver definitions
+
+Driver definitions are the ultimate purpose of the @file{devices}
+configuration file.  These are where the real action is.  Driver
+definitions tell PSPP where it should send its output.
+
+Each driver definition line is divided into four fields.  These fields
+are delimited by colons (@samp{:}).  Each line is subjected to
+environment variable interpolation before it is processed further
+(@pxref{Environment substitutions}).  From left to right, the four
+fields are, in brief:
+
+@table @i
+@item driver name
+A unique identifier, used to determine whether to enable the driver.
+
+@item class name
+One of the predefined driver classes supported by PSPP.  The
+currently supported driver classes include `postscript' and `ascii'.
+
+@item device type(s)
+Zero or more of the following keywords, delimited by spaces:
+
+@table @code
+@item screen
+
+Indicates that the device is a screen display.  This may reduce the
+amount of buffering done by the driver, to make interactive use more
+convenient.
+
+@item printer
+
+Indicates that the device is a printer.
+
+@item listing
+
+Indicates that the device is a listing file.
+@end table
+
+These options are just hints to PSPP and do not cause the output to be
+directed to the screen, or to the printer, or to a listing file---those
+must be set elsewhere in the options.  They are used primarily to decide
+which devices should be enabled at any given time.  @xref{SET}, for more
+information.
+
+@item options
+An optional set of options to pass to the driver itself.  The exact
+format for the options varies among drivers.
+@end table
+
+The driver is enabled if:
+
+@enumerate
+@item
+Its driver name is specified on the command line, or
+
+@item
+It's in a category specified on the command line, or
+
+@item
+If no categories or driver names are specified on the command line, it
+is in category @code{default}.
+@end enumerate
+
+For more information on driver names, see @ref{Driver categories}.
+
+The class name must be one of those supported by PSPP.  The
+classes supported depend on the options with which PSPP was
+compiled.  See later sections in this chapter for descriptions of the
+available driver classes.
+
+Options are dependent on the driver.  See the driver descriptions for
+details.
+
+@node Dimensions, papersize, Device definitions, Output devices
+@subsection Dimensions
+
+Quite often in configuration it is necessary to specify a length or a
+size.  PSPP uses a common syntax for all such, calling them
+collectively by the name @dfn{dimensions}.
+
+@itemize @bullet
+@item
+You can specify dimensions in decimal form (@samp{12.5}) or as
+fractions, either as mixed numbers (@samp{12-1/2}) or raw fractions
+(@samp{25/2}).
+
+@item 
+A number of different units are available.  These are suffixed to the
+numeric part of the dimension.  There must be no spaces between the
+number and the unit.  The available units are identical to those offered
+by the popular typesetting system @TeX{}:
+
+@table @code
+@item in
+inch (1 @code{in} = 2.54 @code{cm})
+
+@item "
+inch (1 @code{in} = 2.54 @code{cm})
+
+@item pt
+printer's point (1 @code{in} = 72.27 @code{pt})
+
+@item pc
+pica (12 @code{pt} = 1 @code{pc})
+
+@item bp
+PostScript point (1 @code{in} = 72 @code{bp})
+
+@item cm
+centimeter
+
+@item mm
+millimeter (10 @code{mm} = 1 @code{cm})
+
+@item dd
+didot point (1157 @code{dd} = 1238 @code{pt})
+
+@item cc
+cicero (1 @code{cc} = 12 @code{dd})
+
+@item sp
+scaled point (65536 @code{sp} = 1 @code{pt})
+@end table
+
+@item
+If no explicit unit is given, a DWIM@footnote{Do What I Mean}
+``feature'' attempts to guess the best unit:
+
+@itemize @minus
+@item
+Numbers less than 50 are assumed to be in inches.
+
+@item
+Numbers 50 or greater are assumed to be in millimeters.
+@end itemize
+@end itemize
+
+@node papersize, Distinguishing line types, Dimensions, Output devices
+@subsection Paper sizes
+
+Output drivers usually deal with some sort of hardcopy media.  This
+media is called @dfn{paper} by the drivers, though in reality it could
+be a transparency or film or thinly veiled sarcasm.  To make it easier
+for you to deal with paper, PSPP allows you to have (of course!) a
+configuration file that gives symbolic names, like ``letter'' or
+``legal'' or ``a4'', to paper sizes, rather than forcing you to use
+cryptic numbers like ``8-1/2 x 11'' or ``210 by 297''.  Surprisingly
+enough, this configuration file is named @file{papersize}.
+@xref{Configuration files}.
+
+When PSPP tries to connect a symbolic paper name to a paper size, it
+reads and parses each non-comment line in the file, in order.  The first
+field on each line must be a symbolic paper name in double quotes.
+Paper names may not contain double quotes.  Paper names are not
+case-sensitive: @samp{legal} and @samp{Legal} are equivalent.
+
+If a match is found for the paper name, the rest of the line is parsed.
+If it is found to be a pair of dimensions (@pxref{Dimensions}) separated
+by either @samp{x} or @samp{by}, then those are taken to be the paper
+size, in order of width followed by length.  There @emph{must} be at
+least one space on each side of @samp{x} or @samp{by}.
+
+Otherwise the line must be of the form
+@samp{"@var{paper-1}"="@var{paper-2}"}.  In this case the target of the
+search becomes paper name @var{paper-2} and the search through the file
+continues.
+
+@node Distinguishing line types, Tokenizing lines, papersize, Output devices
+@subsection How lines are divided into types
+
+The lines in @file{devices} are distinguished in the following manner:
+
+@enumerate
+@item
+Leading whitespace is removed.
+
+@item
+If the resulting line begins with the exact string @code{define},
+followed by one or more whitespace characters, the line is processed as
+a macro definition.
+
+@item
+Otherwise, the line is scanned for the first instance of a colon
+(@samp{:}) or an equals sign (@samp{=}).
+
+@item
+If a colon is encountered first, the line is processed as a driver
+definition.
+
+@item
+Otherwise, if an equals sign is encountered, the line is processed as a
+macro definition.
+
+@item
+Otherwise, the line is ill-formed.
+@end enumerate
+
+@node Tokenizing lines,  , Distinguishing line types, Output devices
+@subsection How lines are divided into tokens
+
+Each driver definition line is run through a simple tokenizer.  This
+tokenizer recognizes two basic types of tokens.
+
+The first type is an equals sign (@samp{=}).  Equals signs are both
+delimiters between tokens and tokens in themselves.
+
+The second type is an identifier or string token.  Identifiers and
+strings are equivalent after tokenization, though they are written
+differently.  An identifier is any string of characters other than
+whitespace or equals sign.
+
+A string is introduced by a single- or double-quote character (@samp{'}
+or @samp{"}) and, in general, continues until the next occurrence of
+that same character.  The following standard C escapes can also be
+embedded within strings:
+
+@table @code
+@item \'
+A single-quote (@samp{'}).
+
+@item \"
+A double-quote (@samp{"}).
+
+@item \?
+A question mark (@samp{?}).  Included for hysterical raisins.
+
+@item \\
+A backslash (@samp{\}).
+
+@item \a
+Audio bell (ASCII 7).
+
+@item \b
+Backspace (ASCII 8).
+
+@item \f
+Formfeed (ASCII 12).
+
+@item \n
+Newline (ASCII 10)
+
+@item \r
+Carriage return (ASCII 13).
+
+@item \t
+Tab (ASCII 9).
+
+@item \v
+Vertical tab (ASCII 11).
+
+@item \@var{o}@var{o}@var{o}
+Each @samp{o} must be an octal digit.  The character is the one having
+the octal value specified.  Any number of octal digits is read and
+interpreted; only the lower 8 bits are used.
+
+@item \x@var{h}@var{h}
+Each @samp{h} must be a hex digit.  The character is the one having the
+hexadecimal value specified.  Any number of hex digits is read and
+interpreted; only the lower 8 bits are used.
+@end table
+
+Tokens, outside of quoted strings, are delimited by whitespace or equals
+signs.
+
+@node PostScript driver class, ASCII driver class, Output devices, Configuration
+@section The PostScript driver class
+
+The @code{postscript} driver class is used to produce output that is
+acceptable to PostScript printers and to PC-based PostScript
+interpreters such as Ghostscript.  Continuing a long tradition,
+PSPP's PostScript driver is configurable to the point of
+absurdity.
+
+There are actually two PostScript drivers.  The first one,
+@samp{postscript}, produces ordinary DSC-compliant PostScript output.
+The second one @samp{epsf}, produces an Encapsulated PostScript file.
+The two drivers are otherwise identical in configuration and in
+operation.
+
+The PostScript driver is described in further detail below.
+
+@menu
+* PS output options::           Output file options.
+* PS page options::             Paper, margins, scaling & rotation, more!
+* PS file options::             Configuration files.
+* PS font options::             Default fonts, font options.
+* PS line options::             Line widths, options.
+* Prologue::                    Details on the PostScript prologue.
+* Encodings::                   Details on PostScript font encodings.
+@end menu
+
+@node PS output options, PS page options, PostScript driver class, PostScript driver class
+@subsection PostScript output options
+
+These options deal with the form of the output and the output file
+itself:
+
+@table @code
+@item output-file=@var{filename}
+
+File to which output should be sent.  This can be an ordinary filename
+(i.e., @code{"pspp.ps"}), a pipe filename (i.e., @code{"|lpr"}), or
+stdout (@code{"-"}).  Default: @code{"pspp.ps"}.
+
+@item color=@var{boolean}
+
+Most of the time black-and-white PostScript devices are smart enough to
+map colors to shades themselves.  However, you can cause the PSPP
+output driver to do an ugly simulation of this in its own driver by
+turning @code{color} off.  Default: @code{on}.
+
+This is a boolean setting, as are many settings in the PostScript
+driver.  Valid positive boolean values are @samp{on}, @samp{true},
+@samp{yes}, and nonzero integers.  Negative boolean values are
+@samp{off}, @samp{false}, @samp{no}, and zero.
+
+@item data=@var{data-type}
+
+One of @code{clean7bit}, @code{clean8bit}, or @code{binary}.  This
+controls what characters will be written to the output file.  PostScript
+produced with @code{clean7bit} can be transmitted over 7-bit
+transmission channels that use ASCII control characters for line
+control.  @code{clean8bit} is similar but allows characters above 127 to
+be written to the output file.  @code{binary} allows any character in
+the output file.  Default: @code{clean7bit}.
+
+@item line-ends=@var{line-end-type}
+
+One of @code{cr}, @code{lf}, or @code{crlf}.  This controls what is used
+for newline in the output file.  Default: @code{cr}.
+
+@item optimize-line-size=@var{level}
+
+Either @code{0} or @code{1}.  If @var{level} is @code{1}, then short
+line segments will be collected and merged into longer ones.  This
+reduces output file size but requires more time and memory.  A
+@var{level} of @code{0} has the advantage of being better for
+interactive environments.  @code{1} is the default unless the
+@code{screen} flag is set; in that case, the default is @code{0}.
+
+@item optimize-text-size=@var{level}
+
+One of @code{0}, @code{1}, or @code{2}, each higher level representing
+correspondingly more aggressive space savings for text in the output
+file and requiring correspondingly more time and memory.  Unfortunately
+the levels presently are all the same.  @code{1} is the default unless
+the @code{screen} flag is set; in that case, the default is @code{0}.
+@end table
+
+@node PS page options, PS file options, PS output options, PostScript driver class
+@subsection PostScript page options
+
+These options affect page setup:
+
+@table @code
+@item headers=@var{boolean}
+
+Controls whether the standard headers showing the time and date and
+title and subtitle are printed at the top of each page.  Default:
+@code{on}.
+
+@item paper-size=@var{paper-size}
+
+Paper size, either as a symbolic name (i.e., @code{letter} or @code{a4})
+or specific measurements (i.e., @code{8-1/2x11} or @code{"210 x 297"}.
+@xref{papersize, , Paper sizes}.  Default: @code{letter}.
+
+@item orientation=@var{orientation}
+
+Either @code{portrait} or @code{landscape}.  Default: @code{portrait}.
+
+@item left-margin=@var{dimension}
+@itemx right-margin=@var{dimension}
+@itemx top-margin=@var{dimension}
+@itemx bottom-margin=@var{dimension}
+
+Sets the margins around the page.  The headers, if enabled, are not
+included in the margins; they are in addition to the margins.  For a
+description of dimensions, see @ref{Dimensions}.  Default: @code{0.5in}.
+
+@end table
+
+@node PS file options, PS font options, PS page options, PostScript driver class
+@subsection PostScript file options
+
+Oh, my.  You don't really want to know about the way that the PostScript
+driver deals with files, do you?  Well I suppose you're entitled, but I
+warn you right now: it's not pretty.  Here goes@enddots{}
+
+First let's look at the options that are available:
+
+@table @code
+
+@item font-dir=@var{font-directory}
+
+Sets the font directory.  Default: @code{devps}.
+
+@item prologue-file=@var{prologue-file-name}
+
+Sets the name of the PostScript prologue file.  You can write your own
+prologue, though I have no idea why you'd want to: see @ref{Prologue}.
+Default: @code{ps-prologue}.
+
+@item device-file=@var{device-file-name}
+
+Sets the name of the Groff-format device description file.  The
+PostScript driver reads this in order to know about the scaling of fonts
+and so on.  The format of such files is described in groff_font(5),
+included with Groff.  Default: @code{DESC}.
+
+@item encoding-file=@var{encoding-file-name}
+
+Sets the name of the encoding file.  This file contains a list of all
+font encodings that will be needed so that the driver can put all of
+them at the top of the prologue.  @xref{Encodings}.  Default:
+@code{ps-encodings}.
+
+If the specified encoding file cannot be found, this error will be
+silently ignored, since most people do not need any encodings besides
+the ones that can be found using @code{auto-encodings}, described below.
+
+@item auto-encode=@var{boolean}
+
+When enabled, the font encodings needed by the default proportional- and
+fixed-pitch fonts will automatically be dumped to the PostScript
+output.  Otherwise, it is assumed that the user has an encoding file
+and knows how to use it (@pxref{Encodings}).  There is probably no good
+reason to turn off this convenient feature.  Default: @code{on}.
+
+@end table
+
+Next I suppose it's time to describe the search algorithm.  When the
+PostScript driver needs a file, whether that file be a font, a
+PostScript prologue, or what you will, it searches in this manner:
+
+@enumerate
+
+@item
+Constructs a path by taking the first of the following that is defined:
+
+@enumerate a
+
+@item
+Environment variable @code{STAT_GROFF_FONT_PATH}.  @xref{Environment
+variables}.
+
+@item
+Environment variable @code{GROFF_FONT_PATH}.
+
+@item
+The compiled-in fallback default.
+@end enumerate
+
+@item
+Constructs a base name from concatenating, in order, the font directory,
+a path separator (@samp{/} or @samp{\}), and the file to be found.  A
+typical base name would be something like @code{devps/ps-encodings}.
+
+@item
+Searches for the base name in the path constructed above.  If the file
+is found, the algorithm terminates.
+
+@item
+Searches for the base name in the standard configuration path.  See
+@ref{File locations}, for more details.  If the file is found, the
+algorithm terminates.
+
+@item
+At this point we remove the font directory and path separator from the
+base name.  Now the base name is simply the file to be found, i.e.,
+@code{ps-encodings}.
+
+@item
+Searches for the base name in the path constructed in the first step.
+If the file is found, the algorithm terminates.
+
+@item
+Searches for the base name in the standard configuration path.  If the
+file is found, the algorithm terminates.
+
+@item
+The algorithm terminates unsuccessfully.
+@end enumerate
+
+So, as you see, there are several ways to configure the PostScript
+drivers.  Careful selection of techniques can make the configuration
+very flexible indeed.
+
+@node PS font options, PS line options, PS file options, PostScript driver class
+@subsection PostScript font options
+
+The list of available font options is short and sweet:
+
+@table @code
+@item prop-font=@var{font-name}
+
+Sets the default proportional font.  The name should be that of a
+PostScript font.  Default: @code{"Helvetica"}.
+
+@item fixed-font=@var{font-name}
+
+Sets the default fixed-pitch font.  The name should be that of a
+PostScript font.  Default: @code{"Courier"}.
+
+@item font-size=@var{font-size}
+
+Sets the size of the default fonts, in thousandths of a point.  Default:
+@code{10000}.
+
+@end table
+
+@node PS line options, Prologue, PS font options, PostScript driver class
+@subsection PostScript line options
+
+Most tables contain lines, or rules, between cells.  Some features of
+the way that lines are drawn in PostScript tables are user-definable:
+
+@table @code
+
+@item line-style=@var{style}
+
+Sets the style used for lines used to divide tables into sections.
+@var{style} must be either @code{thick}, in which case thick lines are
+used, or @var{double}, in which case double lines are used.  Default:
+@code{thick}.
+
+@item line-gutter=@var{dimension}
+
+Sets the line gutter, which is the amount of whitespace on either side
+of lines that border text or graphics objects.  @xref{Dimensions}.
+Default: @code{0.5pt}.
+
+@item line-spacing=@var{dimension}
+
+Sets the line spacing, which is the amount of whitespace that separates
+lines that are side by side, as in a double line.  Default:
+@code{0.5pt}.
+
+@item line-width=@var{dimension}
+
+Sets the width of a typical line used in tables.  Default: @code{0.5pt}.
+
+@item line-width-thick=@var{dimension}
+
+Sets the width of a thick line used in tables.  Not used if
+@code{line-style} is set to @code{thick}.  Default: @code{1.5pt}.
+
+@end table
+
+@node Prologue, Encodings, PS line options, PostScript driver class
+@subsection The PostScript prologue
+
+Most PostScript files that are generated mechanically by programs
+consist of two parts: a prologue and a body.  The prologue is generally
+a collection of boilerplate.  Only the body differs greatly between
+two outputs from the same program.
+This is also the strategy used in the PSPP PostScript driver.  In
+general, the prologue supplied with PSPP will be more than sufficient.
+In this case, you will not need to read the rest of this section.
+However, hackers might want to know more.  Read on, if you fall into
+this category.
+
+The prologue is dumped into the output stream essentially unmodified.
+However, two actions are performed on its lines.  First, certain lines
+may be omitted as specified in the prologue file itself.  Second,
+variables are substituted.
+
+The following lines are omitted:
+
+@enumerate
+@item
+All lines that contain three bangs in a row (@code{!!!}).
+
+@item
+Lines that contain @code{!eps}, if the PostScript driver is producing
+ordinary PostScript output.  Otherwise an EPS file is being produced,
+and the line is included in the output, although everything following
+@code{!eps} is deleted.
+
+@item
+Lines that contain @code{!ps}, if the PostScript driver is producing EPS
+output.  Otherwise, ordinary PostScript is being produced, and the line
+is included in the output, although everything following @code{!ps} is
+deleted.
+@end enumerate
+
+The following are the variables that are substituted.  Only the
+variables listed are substituted; environment variables are not.
+@xref{Environment substitutions}.
+
+@table @code
+@item bounding-box
+
+The page bounding box, in points, as four space-separated numbers.  For
+U.S. letter size paper, this is @samp{0 0 612 792}.
+
+@item creator
+
+PSPP version as a string: @samp{GNU PSPP 0.1b}, for example.
+
+@item date
+
+Date the file was created.  Example: @samp{Tue May 21 13:46:22 1991}.
+
+@item data
+
+Value of the @code{data} PostScript driver option, as one of the strings
+@samp{Clean7Bit}, @samp{Clean8Bit}, or @samp{Binary}.
+
+@item orientation
+
+Page orientation, as one of the strings @code{Portrait} or
+@code{Landscape}.
+
+@item user
+
+Under multiuser OSes, the user's login name, taken either from the
+environment variable @code{LOGNAME} or, if that fails, the result of the
+C library function @code{getlogin()}.  Defaults to @samp{nobody}.
+
+@item host
+
+System hostname as reported by @code{gethostname()}.  Defaults to
+@samp{nowhere}.
+
+@item prop-font
+
+Name of the default proportional font, prefixed by the word
+@samp{font} and a space.  Example: @samp{font Times-Roman}.
+
+@item fixed-font
+
+Name of the default fixed-pitch font, prefixed by the word @samp{font}
+and a space.
+
+@item scale-factor
+
+The page scaling factor as a floating-point number.  Example:
+@code{1.0}.  Note that this is also passed as an argument to the BP
+macro.
+
+@item paper-length
+@item paper-width
+
+The paper length and paper width, respectively, in thousandths of a
+point.  Note that these are also passed as arguments to the BP macro.
+
+@item left-margin
+@item top-margin
+
+The left margin and top margin, respectively, in thousandths of a
+point.  Note that these are also passed as arguments to the BP macro.
+
+@item title
+
+Document title as a string.  This is not the title specified in the
+PSPP syntax file.  A typical title is the word @samp{PSPP} followed
+by the syntax file name in parentheses.  Example: @samp{PSPP
+(<stdin>)}.
+
+@item source-file
+
+PSPP syntax file name.  Example: @samp{mary96/first.stat}.
+
+@end table
+
+Any other questions about the PostScript prologue can best be answered
+by examining the default prologue or the PSPP source.
+
+@node Encodings,  , Prologue, PostScript driver class
+@subsection PostScript encodings
+
+PostScript fonts often contain many more than 256 characters, in order
+to accommodate foreign language characters and special symbols.
+PostScript uses @dfn{encodings} to map these onto single-byte symbol
+sets.  Each font can have many different encodings applied to it.
+
+PSPP's PostScript driver needs to know which encoding to apply to each
+font.  It can determine this from the information encapsulated in the
+Groff font description that it reads.  However, there is an additional
+problem---for efficiency, the PostScript driver needs to have a complete
+list of all encodings that will be used in the entire session @emph{when
+it opens the output file}.  For this reason, it can't use the
+information built into the fonts because it doesn't know which fonts
+will be used.
+
+As a stopgap solution, there are two mechanisms for specifying which
+encodings will be used.  The first mechanism is automatic and it is the
+only one that most PSPP users will ever need.  The second mechanism is
+manual, but it is more flexible.  Either mechanism or both may be used
+at one time.
+
+The first mechanism is activated by the @samp{auto-encode} driver option
+(@pxref{PS file options}).  When enabled, @samp{auto-encode} causes the
+PostScript driver to include the encodings used by the default
+proportional and fixed-pitch fonts (@pxref{PS font options}).  Many
+PSPP output files will only need these encodings.
+
+The second mechanism is the file specified by the @samp{encoding-file}
+option (@pxref{PS file options}).  If it exists, this file must consist
+of lines in PSPP configuration-file format (@pxref{Configuration
+files}).  Each line that is not a comment should name a PostScript
+encoding to include in the output.
+
+It is not an error if an encoding is included more than once, by either
+mechanism.  It will appear only once in the output.  It is also not an
+error if an encoding is included in the output but never used.  It
+@emph{is} an error if an encoding is used but not included by one of
+these mechanisms.  In this case, the built-in PostScript encoding
+@samp{ISOLatin1Encoding} is substituted.
+
+@node ASCII driver class, HTML driver class, PostScript driver class, Configuration
+@section The ASCII driver class
+
+The ASCII driver class produces output that can be displayed on a
+terminal or output to printers.  All of its options are highly
+configurable.  The ASCII driver has class name @samp{ascii}.
+
+The ASCII driver is described in further detail below.
+
+@menu
+* ASCII output options::        Output file options.
+* ASCII page options::          Page size, margins, more.
+* ASCII font options::          Box character, bold & italics.
+@end menu
+
+@node ASCII output options, ASCII page options, ASCII driver class, ASCII driver class
+@subsection ASCII output options
+
+@table @code
+@item output-file=@var{filename}
+
+File to which output should be sent.  This can be an ordinary filename
+(i.e., @code{"pspp.ps"}), a pipe filename (i.e., @code{"|lpr"}), or
+stdout (@code{"-"}).  Default: @code{"pspp.list"}.
+
+@item char-set=@var{char-set-type}
+
+One of @samp{ascii} or @samp{latin1}.  This has no effect on output at
+the present time.  Default: @code{ascii}.
+
+@item form-feed-string=@var{form-feed-value}
+
+The string written to the output to cause a formfeed.  See also
+@code{paginate}, described below, for a related setting.  Default:
+@code{"\f"}.
+
+@item newline-string=@var{newline-value}
+
+The string written to the output to cause a newline (carriage return
+plus linefeed).  The default, which can be specified explicitly with
+@code{newline-string=default}, is to use the system-dependent newline
+sequence by opening the output file in text mode.  This is usually the
+right choice.
+
+However, @code{newline-string} can be set to any string.  When this is
+done, the output file is opened in binary mode.
+
+@item paginate=@var{boolean}
+
+If set, a formfeed (as set in @code{form-feed-string}, described above)
+will be written to the device after every page.  Default: @code{on}.
+
+@item tab-width=@var{tab-width-value}
+
+The distance between tab stops for this device.  If set to 0, tabs will
+not be used in the output.  Default: @code{8}.
+
+@item init=@var{initialization-string}.
+
+String written to the device before anything else, at the beginning of
+the output.  Default: @code{""} (the empty string).
+
+@item done=@var{finalization-string}.
+
+String written to the device after everything else, at the end of the
+output.  Default: @code{""} (the empty string).
+@end table
+
+@node ASCII page options, ASCII font options, ASCII output options, ASCII driver class
+@subsection ASCII page options
+
+These options affect page setup:
+
+@table @code
+@item headers=@var{boolean}
+
+If enabled, two lines of header information giving title and subtitle,
+page number, date and time, and PSPP version are printed at the top of
+every page.  These two lines are in addition to any top margin
+requested.  Default: @code{on}.
+
+@item length=@var{line-count}
+
+Physical length of a page, in lines.  Headers and margins are subtracted
+from this value.  Default: @code{66}.
+
+@item width=@var{character-count}
+
+Physical width of a page, in characters.  Margins are subtracted from
+this value.  Default: @code{130}.
+
+@item lpi=@var{lines-per-inch}
+
+Number of lines per vertical inch.  Not currently used.  Default: @code{6}.
+
+@item cpi=@var{characters-per-inch}
+
+Number of characters per horizontal inch.  Not currently used.  Default:
+@code{10}.
+
+@item left-margin=@var{left-margin-width}
+
+Width of the left margin, in characters.  PSPP subtracts this value
+from the page width.  Default: @code{0}.
+
+@item right-margin=@var{right-margin-width}
+
+Width of the right margin, in characters.  PSPP subtracts this value
+from the page width.  Default: @code{0}.
+
+@item top-margin=@var{top-margin-lines}
+
+Length of the top margin, in lines.  PSPP subtracts this value from
+the page length.  Default: @code{2}.
+
+@item bottom-margin=@var{bottom-margin-lines}
+
+Length of the bottom margin, in lines.  PSPP subtracts this value from
+the page length.  Default: @code{2}.
+
+@end table
+
+@node ASCII font options,  , ASCII page options, ASCII driver class
+@subsection ASCII font options
+
+These are the ASCII font options:
+
+@table @code
+@item box[@var{line-type}]=@var{box-chars}
+
+The characters used for lines in tables produced by the ASCII driver can
+be changed using this option.  @var{line-type} is used to indicate which
+type of line to change; @var{box-chars} is the character or string of
+characters to use for this type of line.
+
+@var{line-type} must be a 4-digit number in base 4.  The digits are in
+the order `right', `bottom', `left', `top'.  The four possibilities for
+each digit are:
+
+@table @asis
+@item 0
+No line.
+
+@item 1
+Single line.
+
+@item 2
+Double line.
+
+@item 3
+Special device-defined line, if one is available; otherwise, a double
+line.
+@end table
+
+Examples:
+
+@table @code
+@item box[0101]="|"
+
+Sets @samp{|} as the character to use for a single-width line with
+bottom and top components.
+
+@item box[2222]="#"
+
+Sets @samp{#} as the character to use for the intersection of four
+double-width lines, one each from the top, bottom, left and right.
+
+@item box[1100]="\xda"
+
+Sets @samp{"\xda"}, which under MS-DOG is a box character suitable for
+the top-left corner of a box, as the character for the intersection of
+two single-width lines, one each from the right and bottom.
+
+@end table
+
+Defaults:
+
+@itemize @bullet
+@item
+@code{box[0000]=" "}
+
+@item
+@code{box[1000]="-"}
+@*@code{box[0010]="-"}
+@*@code{box[1010]="-"}
+
+@item
+@code{box[0100]="|"}
+@*@code{box[0001]="|"}
+@*@code{box[0101]="|"}
+
+@item
+@code{box[2000]="="}
+@*@code{box[0020]="="}
+@*@code{box[2020]="="}
+
+@item
+@code{box[0200]="#"}
+@*@code{box[0002]="#"}
+@*@code{box[0202]="#"}
+
+@item
+@code{box[3000]="="}
+@*@code{box[0030]="="}
+@*@code{box[3030]="="}
+
+@item
+@code{box[0300]="#"}
+@*@code{box[0003]="#"}
+@*@code{box[0303]="#"}
+
+@item
+For all others, @samp{+} is used unless there are double lines or
+special lines, in which case @samp{#} is used.
+@end itemize
+
+@item italic-on=@var{italic-on-string}
+
+Character sequence written to turn on italics or underline printing.  If
+this is set to @code{overstrike}, then the driver will simulate
+underlining by overstriking with underscore characters (@samp{_}) in the
+manner described by @code{overstrike-style} and
+@code{carriage-return-style}.  Default: @code{overstrike}.
+
+@item italic-off=@var{italic-off-string}
+
+Character sequence to turn off italics or underline printing.  Default:
+@code{""} (the empty string).
+
+@item bold-on=@var{bold-on-string}
+
+Character sequence written to turn on bold or emphasized printing.  If
+set to @code{overstrike}, then the driver will simulated bold printing
+by overstriking characters in the manner described by
+@code{overstrike-style} and @code{carriage-return-style}.  Default:
+@code{overstrike}.
+
+@item bold-off=@var{bold-off-string}
+
+Character sequence to turn off bold or emphasized printing.  Default:
+@code{""} (the empty string).
+
+@item bold-italic-on=@var{bold-italic-on-string}
+
+Character sequence written to turn on bold-italic printing.  If set to
+@code{overstrike}, then the driver will simulate bold-italics by
+overstriking twice, once with the character, a second time with an
+underscore (@samp{_}) character, in the manner described by
+@code{overstrike-style} and @code{carriage-return-style}.  Default:
+@code{overstrike}.
+
+@item bold-italic-off=@var{bold-italic-off-string}
+
+Character sequence to turn off bold-italic printing.  Default: @code{""}
+(the empty string).
+
+@item overstrike-style=@var{overstrike-option}
+
+Either @code{single} or @code{line}:
+
+@itemize @bullet
+@item
+If @code{single} is selected, then, to overstrike a line of text, the
+output driver will output a character, backspace, overstrike, output a
+character, backspace, overstrike, and so on along a line.
+
+@item
+If @code{line} is selected then the output driver will output an entire
+line, then backspace or emit a carriage return (as indicated by
+@code{carriage-return-style}), then overstrike the entire line at once.
+@end itemize
+
+@code{single} is recommended for use with ttys and programs that
+understand overstriking in text files, such as the pager @code{less}.
+@code{single} will also work with printer devices but results in rapid
+back-and-forth motions of the printhead that can cause the printer to
+physically overheat!
+
+@code{line} is recommended for use with printer devices.  Most programs
+that understand overstriking in text files will not properly deal with
+@code{line} mode.  
+
+Default: @code{single}.
+
+@item carriage-return-style=@var{carriage-return-type}
+
+Either @code{bs} or @code{cr}.  This option applies only when one or
+more of the font commands is set to @code{overstrike} and, at the same
+time, @code{overstrike-style} is set to @code{line}.
+
+@itemize @bullet
+@item
+If @code{bs} is selected then the driver will return to the beginning of
+a line by emitting a sequence of backspace characters (ASCII 8).
+
+@item
+If @code{cr} is selected then the driver will return to the beginning of
+a line by emitting a single carriage-return character (ASCII 13).
+@end itemize
+
+Although @code{cr} is preferred as being more compact, @code{bs} is more
+general since some devices do not interpret carriage returns in the
+desired manner.  Default: @code{bs}.
+@end table
+
+@node HTML driver class, Miscellaneous configuring, ASCII driver class, Configuration
+@section The HTML driver class
+
+The @code{html} driver class is used to produce output for viewing in
+tables-capable web browsers such as Emacs' w3-mode.  Its configuration
+is very simple.  Currently, the output has a very plain format.  In the
+future, further work may be done on improving the output appearance.
+
+There are few options for use with the @code{html} driver class:
+
+@table @code
+@item output-file=@var{filename}
+
+File to which output should be sent.  This can be an ordinary filename
+(i.e., @code{"pspp.ps"}), a pipe filename (i.e., @code{"|lpr"}), or
+stdout (@code{"-"}).  Default: @code{"pspp.html"}.
+
+@item prologue-file=@var{prologue-file-name}
+
+Sets the name of the PostScript prologue file.  You can write your own
+prologue if you want to customize colors or other settings: see
+@ref{HTML Prologue}.  Default: @code{html-prologue}.
+@end table
+
+@menu
+* HTML Prologue::               Format of the HTML prologue file.
+@end menu
+
+@node HTML Prologue,  , HTML driver class, HTML driver class
+@subsection The HTML prologue
+
+HTML files that are generated by PSPP consist of two parts: a prologue
+and a body.  The prologue is a collection of boilerplate.  Only the body
+differs greatly between two outputs.  You can tune the colors and other
+attributes of the output by editing the prologue.
+The prologue is dumped into the output stream essentially unmodified.
+However, two actions are performed on its lines.  First, certain lines
+may be omitted as specified in the prologue file itself.  Second,
+variables are substituted.
+
+The following lines are omitted:
+
+@enumerate
+@item
+All lines that contain three bangs in a row (@code{!!!}).
+
+@item
+Lines that contain @code{!title}, if no title is set for the output.  If
+a title is set, then the characters @code{!title} are removed before the
+line is output.
+
+@item
+Lines that contain @code{!subtitle}, if no subtitle is set for the
+output.  If a subtitle is set, then the characters @code{!subtitle} are
+removed before the line is output.
+@end enumerate
+
+The following are the variables that are substituted.  Only the
+variables listed are substituted; environment variables are not.
+@xref{Environment substitutions}.
+
+@table @code
+@item generator
+
+PSPP version as a string: @samp{GNU PSPP 0.1b}, for example.
+
+@item date
+
+Date the file was created.  Example: @samp{Tue May 21 13:46:22 1991}.
+
+@item user
+
+Under multiuser OSes, the user's login name, taken either from the
+environment variable @code{LOGNAME} or, if that fails, the result of the
+C library function @code{getlogin()}.  Defaults to @samp{nobody}.
+
+@item host
+
+System hostname as reported by @code{gethostname()}.  Defaults to
+@samp{nowhere}.
+
+@item title
+
+Document title as a string.  This is the title specified in the PSPP
+syntax file.
+
+@item subtitle
+
+Document subtitle as a string.  
+
+@item source-file
+
+PSPP syntax file name.  Example: @samp{mary96/first.stat}.
+@end table
+
+@node Miscellaneous configuring, Improving output quality, HTML driver class, Configuration
+@section Miscellaneous configuration
+
+The following environment variables can be used to further configure
+PSPP:
+
+@table @code
+@item HOME
+
+Used to determine the user's home directory.  No default value.
+
+@item STAT_INCLUDE_PATH
+
+Path used to find include files in PSPP syntax files.  Defaults vary
+across operating systems:
+
+@table @asis
+@item UNIX
+
+@itemize @bullet
+@item
+@file{.}
+
+@item
+@file{~/.pspp/include}
+
+@item
+@file{/usr/local/lib/pspp/include}
+
+@item
+@file{/usr/lib/pspp/include}
+
+@item
+@file{/usr/local/share/pspp/include}
+
+@item
+@file{/usr/share/pspp/include}
+@end itemize
+
+@item MS-DOS
+
+@itemize @bullet
+@item
+@file{.}
+
+@item
+@file{C:\PSPP\INCLUDE}
+
+@item
+@file{$PATH}
+@end itemize
+
+@item Other OSes
+No default path.
+@end table
+
+@item STAT_PAGER
+@itemx PAGER
+
+When PSPP invokes an external pager, it uses the first of these that
+is defined.  There is a default pager only if the person who compiled
+PSPP defined one.
+
+@item TERM
+
+The terminal type @code{termcap} or @code{ncurses} will use, if such
+support was compiled into PSPP.
+
+@item STAT_OUTPUT_INIT_FILE
+
+The basename used to search for the driver definition file.
+@xref{Output devices}.  @xref{File locations}.  Default: @code{devices}.
+
+@item STAT_OUTPUT_PAPERSIZE_FILE
+
+The basename used to search for the papersize file.  @xref{papersize}.
+@xref{File locations}.  Default: @code{papersize}.
+
+@item STAT_OUTPUT_INIT_PATH
+
+The path used to search for the driver definition file and the papersize
+file.  @xref{File locations}.  Default: the standard configuration path.
+
+@item TMPDIR
+
+The @code{sort} procedure stores its temporary files in this directory.
+Default: (UNIX) @file{/tmp}, (MS-DOS) @file{\}, (other OSes) empty string.
+
+@item TEMP
+@item TMP
+
+Under MS-DOS only, these variables are consulted after TMPDIR, in this
+order.
+@end table
+
+@node Improving output quality,  , Miscellaneous configuring, Configuration
+@section Improving output quality
+
+When its drivers are set up properly, PSPP can produce output that
+looks very good indeed.  The PostScript driver, suitably configured, can
+produce presentation-quality output.  Here are a few guidelines for
+producing better-looking output, regardless of output driver.  Your
+mileage may vary, of course, and everyone has different esthetic
+preferences.
+
+@itemize @bullet
+@item
+Width is important in PSPP output.  Greater output width leads to more
+readable output, to a point.  Try the following to increase the output
+width:
+
+@itemize @minus
+@item
+If you're using the ASCII driver with a dot-matrix printer, figure out
+what you need to do to put the printer into compressed mode.  Put that
+string into the @code{init-string} setting.  Try to get 132 columns; 160
+might be better, but you might find that print that tiny is difficult to
+read.
+
+@item
+With the PostScript driver, try these ideas:
+
+@itemize +
+@item
+Landscape mode.
+
+@item
+Legal-size (8.5" x 14") paper in landscape mode.
+
+@item
+Reducing font sizes.  If you're using 12-point fonts, try 10 point; if
+you're using 10-point fonts, try 8 point.  Some fonts are more readable
+than others at small sizes.
+@end itemize
+@end itemize
+
+Try to strike a balance between character size and page width.
+
+@item
+Use high-quality fonts.  Many public domain fonts are poor in quality.
+Recently, URW made some high-quality fonts available under the GPL.
+These are probably suitable.
+
+@item
+Be sure you're using the proper font metrics.  The font metrics provided
+with PSPP may not correspond to the fonts actually being printed.
+This can cause bizarre-looking output.
+
+@item
+Make sure that you're using good ink/ribbon/toner.  Darker print is
+easier to read.
+
+@item
+Use plain fonts with serifs, such as Times-Roman or Palatino.  Avoid
+choosing italic or bold fonts as document base fonts.
+@end itemize
+
+@node Invocation, Language, Configuration, Top
+@chapter Invoking PSPP
+@cindex invocation
+@cindex PSPP, invoking
+
+@cindex command line, options
+@cindex options, command-line
+@example
+pspp [ -B @var{dir} | --config-dir=@var{dir} ] [ -o @var{device} | --device=@var{device} ]
+       [ -d @var{var}[=@var{value}] | --define=@var{var}[=@var{value}] ] [-u @var{var} | --undef=@var{var} ]
+       [ -f @var{file} | --out-file=@var{file} ] [ -p | --pipe ] [ -I- | --no-include ]
+       [ -I @var{dir} | --include=@var{dir} ] [ -i | --interactive ] 
+       [ -n | --edit | --dry-run | --just-print | --recon ] 
+       [ -r | --no-statrc ] [ -h | --help ] [ -l | --list ] 
+       [ -c @var{command} | --command @var{command} ] [ -s | --safer ]
+       [ --testing-mode ] [ -V | --version ] [ -v | --verbose ] 
+       [ @var{key}=@var{value} ] @var{file}@enddots{}
+@end example
+
+@menu
+* Non-option Arguments::        Specifying syntax files and output devices.
+* Configuration Options::       Change the configuration for the current run.
+* Input and output options::    Controlling input and output files.
+* Language control options::    Language variants.
+* Informational options::       Helpful information about PSPP.
+@end menu
+
+@node Non-option Arguments, Configuration Options, Invocation, Invocation
+@section Non-option Arguments
+
+Syntax files and output device substitutions can be specified on
+PSPP's command line:
+
+@table @code
+@item @var{file}
+
+A file by itself on the command line will be executed as a syntax file.
+PSPP terminates after the syntax file runs, unless the @code{-i} or
+@code{--interactive} option is given (@pxref{Language control options}).
+
+@item @var{file1} @var{file2}
+
+When two or more filenames are given on the command line, the first
+syntax file is executed, then PSPP's dictionary is cleared, then the second
+syntax file is executed.
+
+@item @var{file1} + @var{file2}
+
+If syntax files' names are delimited by a plus sign (@samp{+}), then the
+dictionary is not cleared between their executions, as if they were
+concatenated together into a single file.
+
+@item @var{key}=@var{value}
+
+Defines an output device macro @var{key} to expand to @var{value},
+overriding any macro having the same @var{key} defined in the device
+configuration file.  @xref{Macro definitions}.
+
+@end table
+
+There is one other way to specify a syntax file, if your operating
+system supports it.  If you have a syntax file @file{foobar.stat}, put
+the notation
+
+@example
+#! /usr/local/bin/pspp
+@end example
+
+at the top, and mark the file as executable with @code{chmod +x
+foobar.stat}.  (If PSPP is not installed in @file{/usr/local/bin},
+then insert its actual installation directory into the syntax file
+instead.)  Now you should be able to invoke the syntax file just by
+typing its name.  You can include any options on the command line as
+usual.  PSPP entirely ignores any lines beginning with @samp{#!}.
+
+@node Configuration Options, Input and output options, Non-option Arguments, Invocation
+@section Configuration Options
+
+Configuration options are used to change PSPP's configuration for the
+current run.  The configuration options are:
+
+@table @code
+@item -B @var{dir}
+@itemx --config-dir=@var{dir}
+
+Sets the configuration directory to @var{dir}.  @xref{File locations}.
+
+@item -o @var{device}
+@itemx --device=@var{device}
+
+Selects the output device with name @var{device}.  If this option is
+given more than once, then all devices mentioned are selected.  This
+option disables all devices besides those mentioned on the command line.
+
+@item -d @var{var}[=@var{value}]
+@itemx --define=@var{var}[=@var{value}]
+
+Defines an `environment variable' named @var{var} having the optional
+value @var{value} specified.  @xref{Variable values}.
+
+@item -u @var{var}
+@itemx --undef=@var{var}
+
+Undefines the `environment variable' named @var{var}.  @xref{Variable
+values}.
+@end table
+
+@node Input and output options, Language control options, Configuration Options, Invocation
+@section Input and output options
+
+Input and output options affect how PSPP reads input and writes
+output.  These are the input and output options:
+
+@table @code
+@item -f @var{file}
+@itemx --out-file=@var{file}
+
+This overrides the output file name for devices designated as listing
+devices.  If a file named @var{file} already exists, it is overwritten.
+
+@item -p
+@itemx --pipe
+
+Allows PSPP to be used as a filter by causing the syntax file to be
+read from stdin and output to be written to stdout.  Conflicts with the
+@code{-f @var{file}} and @code{--file=@var{file}} options.
+
+@item -I-
+@itemx --no-include
+
+Clears all directories from the include path.  This includes all
+directories put in the include path by default.  @xref{Miscellaneous
+configuring}.
+
+@item -I @var{dir}
+@itemx --include=@var{dir}
+
+Appends directory @var{dir} to the path that is searched for include
+files in PSPP syntax files.
+
+@item -c @var{command}
+@itemx --command=@var{command}
+
+Execute literal command @var{command}.  The command is executed before
+startup syntax files, if any.
+
+@item --testing-mode
+
+Invoke heuristics to assist with testing PSPP.  For use by @code{make
+check} and similar scripts.
+@end table
+
+@node Language control options, Informational options, Input and output options, Invocation
+@section Language control options
+
+Language control options control how PSPP syntax files are parsed and
+interpreted.  The available language control options are:
+
+@table @code
+@item -i
+@itemx --interactive
+
+When a syntax file is specified on the command line, PSPP normally
+terminates after processing it.  Giving this option will cause PSPP to
+bring up a command prompt after processing the syntax file.
+
+In addition, this forces syntax files to be interpreted in interactive
+mode, rather than the default batch mode.  @xref{Tokenizing lines}, for
+information on the differences between batch mode and interactive mode
+command interpretation.
+
+@item -n
+@itemx --edit
+@itemx --dry-run
+@itemx --just-print
+@itemx --recon
+
+Only the syntax of any syntax file specified or of commands entered at
+the command line is checked.  Transformations are not performed and
+procedures are not executed.  Not yet implemented.
+
+@item -r
+@itemx --no-statrc
+
+Prevents the execution of the PSPP startup syntax file.  Not yet
+implemented, as startup syntax files aren't, either.
+
+@item -s
+@itemx --safer
+
+Disables certain unsafe operations.  This includes the @code{ERASE} and
+@code{HOST} commands, as well as use of pipes as input and output files.
+@end table
+
+@node Informational options,  , Language control options, Invocation
+@section Informational options
+
+Informational options cause information about PSPP to be written to
+the terminal.  Here are the available options:
+
+@table @code
+@item -h
+@item --help
+
+Prints a message describing PSPP command-line syntax and the available
+device driver classes, then terminates.
+
+@item -l
+@item --list
+
+Lists the available device driver classes, then terminates.
+
+@item -V
+@item --version
+
+Prints a brief message listing PSPP's version, warranties you don't
+have, copying conditions and copyright, and e-mail address for bug
+reports, then terminates.
+
+@item -v
+@item --verbose
+
+Increments PSPP's verbosity level.  Higher verbosity levels cause
+PSPP to display greater amounts of information about what it is
+doing.  Often useful for debugging PSPP's configuration.  
+
+This option can be given multiple times to set the verbosity level to
+that value.  The default verbosity level is 0, in which no informational
+messages will be displayed.
+
+Higher verbosity levels cause messages to be displayed when the
+corresponding events take place.
+
+@table @asis
+@item 1
+
+Driver and subsystem initializations.
+
+@item 2
+
+Completion of driver initializations.  Beginning of driver closings.
+
+@item 3
+
+Completion of driver closings.
+
+@item 4
+
+Files searched for; success of searches.
+
+@item 5
+
+Individual directories included in file searches.
+@end table
+
+Each verbosity level also includes messages from lower verbosity levels.
+
+@end table
+
+@node Language, Expressions, Invocation, Top
+@chapter The PSPP language
+@cindex language, PSPP
+@cindex PSPP, language
+
+@quotation
+@strong{Please note:} PSPP is not even close to completion.
+Only a few actual statistical procedures are implemented.  PSPP
+is a work in progress.
+@end quotation
+
+This chapter discusses elements common to many PSPP commands.
+Later chapters will describe individual commands in detail.
+
+@menu
+* Tokens::                      Characters combine to form tokens.
+* Commands::                    Tokens combine to form commands.
+* Types of Commands::           Commands come in several flavors.
+* Order of Commands::           Commands combine to form syntax files.
+* Missing Observations::        Handling missing observations.
+* Variables::                   The unit of data storage.
+* Files::                       Files used by PSPP.
+* BNF::                         How command syntax is described.
+@end menu
+
+@node Tokens, Commands, Language, Language
+@section Tokens
+@cindex language, lexical analysis
+@cindex language, tokens
+@cindex tokens
+@cindex lexical analysis
+@cindex lexemes
+
+PSPP divides most syntax file lines into series of short chunks
+called @dfn{tokens}, @dfn{lexical elements}, or @dfn{lexemes}.  These
+tokens are then grouped to form commands, each of which tells
+PSPP to take some action---read in data, write out data, perform
+a statistical procedure, etc.  The process of dividing input into tokens
+is @dfn{tokenization}, or @dfn{lexical analysis}.  Each type of token is
+described below.
+
+@cindex delimiters
+@cindex whitespace
+Tokens must be separated from each other by @dfn{delimiters}.
+Delimiters include whitespace (spaces, tabs, carriage returns, line
+feeds, vertical tabs), punctuation (commas, forward slashes, etc.), and
+operators (plus, minus, times, divide, etc.)  Note that while whitespace
+only separates tokens, other delimiters are tokens in themselves.
+
+@table @strong
+@cindex identifiers
+@item Identifiers
+Identifiers are names that specify variable names, commands, or command
+details.
+
+@itemize @bullet
+@item
+The first character in an identifier must be a letter, @samp{#}, or
+@samp{@@}.  Some system identifiers begin with @samp{$}, but
+user-defined variables' names may not begin with @samp{$}.
+
+@item
+The remaining characters in the identifier must be letters, digits, or
+one of the following special characters: 
+
+@example
+.  _  $  #  @@
+@end example
+
+@item
+@cindex variable names
+@cindex names, variable
+Variable names may be any length, but only the first 8 characters are
+significant.
+
+@item
+@cindex case-sensitivity
+Identifiers are not case-sensitive: @code{foobar}, @code{Foobar},
+@code{FooBar}, @code{FOOBAR}, and @code{FoObaR} are different
+representations of the same identifier.
+
+@item
+@cindex keywords
+Identifiers other than variable names may be abbreviated to their first
+3 characters if this abbreviation is unambiguous.  These identifiers are
+often called @dfn{keywords}.  (Unique abbreviations of more than 3
+characters are also accepted: @samp{FRE}, @samp{FREQ}, and
+@samp{FREQUENCIES} are equivalent when the last is a keyword.)
+
+@item
+Whether an identifier is a keyword depends on the context.
+
+@item
+@cindex keywords, reserved
+@cindex reserved keywords
+Some keywords are reserved.  These keywords may not be used in any
+context besides those explicitly described in this manual.  The reserved
+keywords are:
+
+@example
+ALL  AND  BY  EQ  GE  GT  LE  LT  NE  NOT  OR  TO  WITH
+@end example
+
+@item 
+Since keywords are identifiers, all the rules for identifiers apply.
+Specifically, they must be delimited as are other identifiers:
+@code{WITH} is a reserved keyword, but @code{WITHOUT} is a valid
+variable name.
+@end itemize
+
+@cindex @samp{.}
+@cindex period
+@cindex variable names, ending with period
+@strong{Caution:} It is legal to end a variable name with a period, but
+@emph{don't do it!}  The variable name will be misinterpreted when it is
+the final token on a line: @code{FOO.} will be divided into two separate
+tokens, @samp{FOO} and @samp{.}, the @dfn{terminal dot}.
+@xref{Commands, , Forming commands of tokens}.
+
+@item Numbers
+@cindex numbers
+@cindex integers
+@cindex reals
+Numbers may be specified as integers or reals.  Integers are internally
+converted into reals.  Scientific notation is not supported.  Here are
+some examples of valid numbers:
+
+@example
+1234  3.14159265359  .707106781185  8945.
+@end example
+
+@strong{Caution:} The last example will be interpreted as two tokens,
+@samp{8945} and @samp{.}, if it is the last token on a line.
+
+@item Strings
+@cindex strings
+@cindex @samp{'}
+@cindex @samp{"}
+@cindex case-sensitivity
+Strings are literal sequences of characters enclosed in pairs of single
+quotes (@samp{'}) or double quotes (@samp{"}).
+
+@itemize @bullet
+@item
+Whitespace and case of letters @emph{are} significant inside strings.
+@item
+Whitespace characters inside a string are not delimiters.
+@item
+To include single-quote characters in a string, enclose the string in
+double quotes.
+@item
+To include double-quote characters in a string, enclose the string in
+single quotes.
+@item
+It is not possible to put both single- and double-quote characters
+inside one string.
+@end itemize
+
+@item Hexstrings
+@cindex hexstrings
+Hexstrings are string variants that use hex digits to specify
+characters.
+
+@itemize @bullet
+@item
+A hexstring may be used anywhere that an ordinary string is allowed.
+
+@item
+@cindex @samp{X'}
+@cindex @samp{'}
+A hexstring begins with @samp{X'} or @samp{x'}, and ends with @samp{'}.
+
+@cindex whitespace
+@item
+No whitespace is allowed between the initial @samp{X} and @samp{'}.
+
+@item
+Double quotes @samp{"} may be used in place of single quotes @samp{'} if
+done in both places.
+
+@item
+Each pair of hex digits is internally changed into a single character
+with the given value.
+
+@item
+If there is an odd number of hex digits, the missing last digit is
+assumed to be @samp{0}.
+
+@item
+@cindex portability
+@strong{Please note:} Use of hexstrings is nonportable because the same
+numeric values are associated with different glyphs by different
+operating systems.  Therefore, their use should be confined to syntax
+files that will not be widely distributed.
+
+@item
+@cindex characters, reserved
+@cindex 0
+@cindex whitespace
+@strong{Please note also:} The character with value 00 is reserved for
+internal use by PSPP.  Its use in strings causes an error and
+replacement with a blank space (in ASCII, hex 20, decimal 32).
+@end itemize
+
+@item Punctuation
+@cindex punctuation
+Punctuation separates tokens; punctuators are delimiters.  These are the
+punctuation characters:
+
+@example
+,  /  =  (  )
+@end example
+
+@item Operators
+@cindex operators
+Operators describe mathematical operations.  Some operators are delimiters:
+
+@example
+(  )  +  -  *  /  **
+@end example
+
+Many of the above operators are also punctuators.  Punctuators are
+distinguished from operators by context. 
+
+The other operators are all reserved keywords.  None of these are
+delimiters:
+
+@example
+AND  EQ  GE  GT  LE  LT  NE  OR
+@end example
+
+@item Terminal Dot
+@cindex terminal dot
+@cindex dot, terminal
+@cindex period
+@cindex @samp{.}
+A period (@samp{.}) at the end of a line (except for whitespace) is one
+type of a @dfn{terminal dot}, although not every terminal dot is a
+period at the end of a line.  @xref{Commands, , Forming commands of
+tokens}.  A period is a terminal dot @emph{only}
+when it is at the end of a line; otherwise it is part of a
+floating-point number.  (A period outside a number in the middle of a
+line is an error.)
+
+@quotation
+@cindex terminal dot, changing
+@cindex dot, terminal, changing
+@strong{Please note:} The character used for the @dfn{terminal dot} can
+be changed with the SET command.  This is strongly discouraged, and
+throughout all the remainder of this manual it will be assumed that the
+default setting is in effect.
+@end quotation
+
+@end table
+
+@node Commands, Types of Commands, Tokens, Language
+@section Forming commands of tokens
+
+@cindex PSPP, command structure
+@cindex language, command structure
+@cindex commands, structure
+
+Most PSPP commands share a common structure, diagrammed below:
+
+@example
+@var{cmd}@dots{} [@var{sbc}[=][@var{spec} [[,]@var{spec}]@dots{}]] [[/[=][@var{spec} [[,]@var{spec}]@dots{}]]@dots{}].
+@end example
+
+@cindex @samp{[  ]}
+In the above, rather daunting, expression, pairs of square brackets
+(@samp{[ ]}) indicate optional elements, and names such as @var{cmd}
+indicate parts of the syntax that vary from command to command.
+Ellipses (@samp{...}) indicate that the preceding part may be repeated
+an arbitrary number of times.  Let's pick apart what it says above:
+
+@itemize @bullet
+@cindex commands, names
+@item
+A command begins with a command name of one or more keywords, such as
+@code{FREQUENCIES}, @code{DATA LIST}, or @code{N OF CASES}.  @var{cmd}
+may be abbreviated to its first word if that is unambiguous; each word
+in @var{cmd} may be abbreviated to a unique prefix of three or more
+characters as described above.
+
+@cindex subcommands
+@item
+The command name may be followed by one or more @dfn{subcommands}:
+
+@itemize @minus
+@item
+Each subcommand begins with a unique keyword, indicated by @var{sbc}
+above.  This is analogous to the command name.
+
+@item
+The subcommand name is optionally followed by an equals sign (@samp{=}).
+
+@item
+Some subcommands accept a series of one or more specifications
+(@var{spec}), optionally separated by commas.
+
+@item
+Each subcommand must be separated from the next (if any) by a forward
+slash (@samp{/}).
+@end itemize
+
+@cindex dot, terminal
+@cindex terminal dot
+@item
+Each command must be terminated with a @dfn{terminal dot}.  
+The terminal dot may be given one of three ways:
+
+@itemize @minus
+@item
+(most commonly) A period character at the very end of a line, as
+described above.
+
+@item
+(only if NULLINE is on: @xref{SET, , Setting user preferences}, for more
+details.)  A completely blank line.
+
+@item
+(in batch mode only) Any line that is not indented from the left side of
+the page causes a terminal dot to be inserted before that line.
+Therefore, each command begins with a line that is flush left, followed
+by zero or more lines that are indented one or more characters from the
+left margin.
+
+In batch mode, PSPP will ignore a plus sign, minus sign, or period
+(@samp{+}, @samp{@minus{}}, or @samp{.}) as the first character in a
+line.  Any of these characters as the first character on a line will
+begin a new command.  This allows for visual indentation of a command
+without that command being considered part of the previous command.
+
+PSPP is in batch mode when it is reading input from a file, rather
+than from an interactive user.  Note that the other forms of the
+terminal dot may also be used in batch mode.
+
+Sometimes, one encounters syntax files that are intended to be
+interpreted in interactive mode rather than batch mode (for instance,
+this can happen if a session log file is used directly as a syntax
+file).  When this occurs, use the @samp{-i} command line option to force
+interpretation in interactive mode (@pxref{Language control options}).
+@end itemize
+@end itemize
+
+PSPP ignores empty commands when they are generated by the above
+rules.  Note that, as a consequence of these rules, each command must
+begin on a new line.
+
+@node Types of Commands, Order of Commands, Commands, Language
+@section Types of Commands
+
+Commands in PSPP are divided roughly into six categories:
+
+@table @strong
+@item Utility commands
+Set or display various global options that affect PSPP operations.
+May appear anywhere in a syntax file.  @xref{Utilities, , Utility
+commands}.
+
+@item File definition commands
+Give instructions for reading data from text files or from special
+binary ``system files''.  Most of these commands discard any previous
+data or variables in order to replace it with the new data and
+variables.  At least one must appear before the first command in any of
+the categories below.  @xref{Data Input and Output}.
+
+@item Input program commands
+Though rarely used, these provide powerful tools for reading data files
+in arbitrary textual or binary formats.  @xref{INPUT PROGRAM}.
+
+@item Transformations
+Perform operations on data and write data to output files.  Transformations
+are not carried out until a procedure is executed.  
+
+@item Restricted transformations
+Same as transformations for most purposes.  @xref{Order of Commands}, for a
+detailed description of the differences.
+
+@item Procedures
+Analyze data, writing results of analyses to the listing file.  Cause
+transformations specified earlier in the file to be performed.  In a
+more general sense, a @dfn{procedure} is any command that causes the
+active file (the data) to be read.
+@end table
+
+@node Order of Commands, Missing Observations, Types of Commands, Language
+@section Order of Commands
+@cindex commands, ordering
+@cindex order of commands
+
+PSPP does not place many restrictions on ordering of commands.
+The main restriction is that variables must be defined with one of the
+file-definition commands before they are otherwise referred to.
+
+Of course, there are specific rules, for those who are interested.
+PSPP possesses five internal states, called initial, INPUT
+PROGRAM, FILE TYPE, transformation, and procedure states.  (Please note
+the distinction between the INPUT PROGRAM and FILE TYPE @emph{commands}
+and the INPUT PROGRAM and FILE TYPE @emph{states}.)
+
+PSPP starts up in the initial state.  Each successful completion
+of a command may cause a state transition.  Each type of command has its
+own rules for state transitions:
+
+@table @strong
+@item Utility commands
+@itemize @bullet
+@item
+Legal in all states, except Pennsylvania.
+@item
+Do not cause state transitions.  Exception: when the N OF CASES command
+is executed in the procedure state, it causes a transition to the
+transformation state.
+@end itemize
+
+@item DATA LIST
+@itemize @bullet
+@item
+Legal in all states.
+@item
+When executed in the initial or procedure state, causes a transition to
+the transformation state.  
+@item
+Clears the active file if executed in the procedure or transformation
+state.
+@end itemize
+
+@item INPUT PROGRAM
+@itemize @bullet
+@item
+Invalid in INPUT PROGRAM and FILE TYPE states.
+@item
+Causes a transition to the INPUT PROGRAM state.  
+@item
+Clears the active file.
+@end itemize
+
+@item FILE TYPE
+@itemize @bullet
+@item
+Invalid in INPUT PROGRAM and FILE TYPE states.
+@item
+Causes a transition to the FILE TYPE state.
+@item
+Clears the active file.
+@end itemize
+
+@item Other file definition commands
+@itemize @bullet
+@item
+Invalid in INPUT PROGRAM and FILE TYPE states.
+@item
+Cause a transition to the transformation state.
+@item
+Clear the active file, except for ADD FILES, MATCH FILES, and UPDATE.
+@end itemize
+
+@item Transformations
+@itemize @bullet
+@item
+Invalid in initial and FILE TYPE states.
+@item
+Cause a transition to the transformation state.
+@end itemize
+
+@item Restricted transformations
+@itemize @bullet
+@item
+Invalid in initial, INPUT PROGRAM, and FILE TYPE states.
+@item
+Cause a transition to the transformation state.
+@end itemize
+
+@item Procedures
+@itemize @bullet
+@item
+Invalid in initial, INPUT PROGRAM, and FILE TYPE states.
+@item
+Cause a transition to the procedure state.
+@end itemize
+@end table
+
+@node Missing Observations, Variables, Order of Commands, Language
+@section Handling missing observations
+@cindex missing values
+@cindex values, missing
+
+PSPP includes special support for unknown numeric data values.
+Missing observations are assigned a special value, called the
+@dfn{system-missing value}.  This ``value'' actually indicates the
+absence of value; it means that the actual value is unknown.  Procedures
+automatically exclude from analyses those observations or cases that
+have missing values.  Whether single observations or entire cases are
+excluded depends on the procedure.
+
+The system-missing value exists only for numeric variables.  String
+variables always have a defined value, even if it is only a string of
+spaces.
+
+Variables, whether numeric or string, can have designated
+@dfn{user-missing values}.  Every user-missing value is an actual value
+for that variable.  However, most of the time user-missing values are
+treated in the same way as the system-missing value.  String variables
+that are wider than a certain width, usually 8 characters (depending on
+computer architecture), cannot have user-missing values.
+
+For more information on missing values, see the following sections:
+@ref{Variables}, @ref{MISSING VALUES}, @ref{Expressions}.  See also the
+documentation on individual procedures for information on how they
+handle missing values.
+
+@node Variables, Files, Missing Observations, Language
+@section Variables
+@cindex variables
+
+Variables are the basic unit of data storage in PSPP.  All the
+variables in a file taken together, apart from any associated data, are
+said to form a @dfn{dictionary}.  Each case contain a value for each
+variable.  Some details of variables are described in the sections
+below.
+
+@menu
+* Attributes::                  Attributes of variables.
+* System Variables::            Variables automatically defined by PSPP.
+* Sets of Variables::           Lists of variable names.
+* Input/Output Formats::        Input and output formats.
+* Scratch Variables::           Variables deleted by procedures.
+@end menu
+
+@node Attributes, System Variables, Variables, Variables
+@subsection Attributes of Variables
+@cindex variables, attributes of
+@cindex attributes of variables
+Each variable has a number of attributes, including:
+
+@table @strong
+@item Name
+This is an identifier.  Each variable must have a different name.
+@xref{Tokens}.
+
+@cindex variables, type
+@cindex type of variables
+@item Type
+Numeric or string.
+
+@cindex variables, width
+@cindex width of variables
+@item Width
+(string variables only) String variables with a width of 8 characters or
+fewer are called @dfn{short string variables}.  Short string variables
+can be used in many procedures where @dfn{long string variables} (those
+with widths greater than 8) are not allowed.
+
+@quotation
+@strong{Please note:} Certain systems may consider strings longer than 8
+characters to be short strings.  Eight characters represents a minimum
+figure for the maximum length of a short string.
+@end quotation
+
+@item Position
+Variables in the dictionary are arranged in a specific order.  The
+DISPLAY command can be used to show this order: see @ref{DISPLAY}.
+
+@item Orientation
+Dexter or sinister.  @xref{LEAVE}.
+
+@cindex missing values
+@cindex values, missing
+@item Missing values
+Optionally, up to three values, or a range of values, or a specific
+value plus a range, can be specified as @dfn{user-missing values}.
+There is also a @dfn{system-missing value} that is assigned to an
+observation when there is no other obvious value for that observation.
+Observations with missing values are automatically excluded from
+analyses.  User-missing values are actual data values, while the
+system-missing value is not a value at all.  @xref{Missing Observations}.
+
+@cindex variable labels
+@cindex labels, variable
+@item Variable label
+A string that describes the variable.  @xref{VARIABLE LABELS}.
+
+@cindex value labels
+@cindex labels, value
+@item Value label
+Optionally, these associate each possible value of the variable with a
+string.  @xref{VALUE LABELS}.
+
+@cindex print format
+@item Print format
+Display width, format, and (for numeric variables) number of decimal
+places.  This attribute does not affect how data are stored, just how
+they are displayed.  Example: a width of 8, with 2 decimal places.
+@xref{PRINT FORMATS}.
+
+@cindex write format
+@item Write format
+Similar to print format, but used by certain commands that are
+designed to write to binary files.  @xref{WRITE FORMATS}.
+@end table
+
+@node System Variables, Sets of Variables, Attributes, Variables
+@subsection Variables Automatically Defined by PSPP
+@cindex system variables
+@cindex variables, system
+
+There are seven system variables.  These are not like ordinary
+variables, as they are not stored in each case.  They can only be used
+in expressions.  These system variables, whose values and output formats
+cannot be modified, are described below.
+
+@table @code
+@cindex @code{$CASENUM}
+@item $CASENUM
+Case number of the case at the moment.  This changes as cases are
+shuffled around.
+
+@cindex @code{$DATE}
+@item $DATE
+Date the PSPP process was started, in format A9, following the
+pattern @code{DD MMM YY}.
+
+@cindex @code{$JDATE}
+@item $JDATE
+Number of days between 15 Oct 1582 and the time the PSPP process
+was started.
+
+@cindex @code{$LENGTH}
+@item $LENGTH
+Page length, in lines, in format F11.
+
+@cindex @code{$SYSMIS}
+@item $SYSMIS
+System missing value, in format F1.
+
+@cindex @code{$TIME}
+@item $TIME
+Number of seconds between midnight 14 Oct 1582 and the time the active file
+was read, in format F20.
+
+@cindex @code{$WIDTH}
+@item $WIDTH
+Page width, in characters, in format F3.
+@end table
+
+@node Sets of Variables, Input/Output Formats, System Variables, Variables
+@subsection Lists of variable names
+@cindex TO convention
+@cindex convention, TO
+
+There are several ways to specify a set of variables:
+
+@enumerate
+@item
+(Most commonly.)  List the variable names one after another, optionally
+separating them by commas.
+
+@cindex @code{TO}
+@item
+(This method cannot be used on commands that define the dictionary, such
+as @code{DATA LIST}.)  The syntax is the names of two existed variables,
+separated by the reserved keyword @code{TO}.  The meaning is to include
+every variable in the dictionary between and including the variables
+specified.  For instance, if the dictionary contains six variables with
+the names @code{ID}, @code{X1}, @code{X2}, @code{GOAL}, @code{MET}, and
+@code{NEXTGOAL}, in that order, then @code{X2 TO MET} would include
+variables @code{X2}, @code{GOAL}, and @code{MET}.
+
+@item
+(This method can be used only on commands that define the dictionary,
+such as @code{DATA LIST}.)  It is used to define sequences of variables
+that end in consecutive integers.  The syntax is two identifiers that
+end in numbers.  This method is best illustrated with examples:
+
+@itemize @bullet
+@item
+The syntax @code{X1 TO X5} defines 5 variables:
+
+@itemize @minus
+@item
+X1
+@item
+X2
+@item
+X3
+@item
+X4
+@item
+X5
+@end itemize
+
+@item
+The syntax @code{ITEM0008 TO ITEM0013} defines 6 variables:
+
+@itemize @minus
+@item
+ITEM0008
+@item
+ITEM0009
+@item
+ITEM0010
+@item
+ITEM0011
+@item
+ITEM0012
+@item
+ITEM0013
+@end itemize
+
+@item
+Each of the syntaxes @code{QUES001 TO QUES9} and @code{QUES6 TO QUES3}
+are invalid, although for different reasons, which should be evident.
+@end itemize
+
+Note that after a set of variables has been defined on @code{DATA LIST}
+or another command with this method, the same set can be referenced on
+later commands using the same syntax.
+
+@item
+The above methods can be combined, either one after another or delimited
+by commas.  For instance, the combined syntax @code{A Q5 TO Q8 X TO Z}
+is legal as long as each part @code{A}, @code{Q5 TO Q8}, @code{X TO Z}
+is individually legal.
+@end enumerate
+
+@node Input/Output Formats, Scratch Variables, Sets of Variables, Variables
+@subsection Input and Output Formats
+
+Data that PSPP inputs and outputs must have one of a number of formats.
+These formats are described, in general, by a format specification of
+the form @code{NAMEw.d}, where @var{name} is the
+format name and @var{w} is a field width.  @var{d} is the optional
+desired number of decimal places, if appropriate.  If @var{d} is not
+included then it is assumed to be 0.  Some formats do not allow @var{d}
+to be specified.
+
+When an input format is specified on DATA LIST or another command, then
+it is converted to an output format for the purposes of PRINT and other
+data output commands.  For most purposes, input and output formats are
+the same; the salient differences are described below.
+
+Below are listed the input and output formats supported by PSPP.  If an
+input format is mapped to a different output format by default, then
+that mapping is indicated with @result{}.  Each format has the listed
+bounds on input width (iw) and output width (ow).
+
+The standard numeric input and output formats are given in the following
+table:
+
+@table @asis
+@item Fw.d: 1 <= iw,ow <= 40
+Standard decimal format with @var{d} decimal places.  If the number is
+too large to fit within the field width, it is expressed in scientific
+notation (@code{1.2+34}) if w >= 6, with always at least two digits in
+the exponent.  When used as an input format, scientific notation is
+allowed but an E or an F must be used to introduce the exponent.
+
+The default output format is the same as the input format, except if
+@var{d} > 1.  In that case the output @var{w} is always made to be at
+least 2 + @var{d}.
+
+@item Ew.d: 1 <= iw <= 40; 6 <= ow <= 40
+For input this is equivalent to F format except that no E or F is
+require to introduce the exponent.  For output, produces scientific
+notation in the form @code{1.2+34}.  There are always at least two
+digits given in the exponent.
+
+The default output @var{w} is the largest of the input @var{w}, the
+input @var{d} + 7, and 10.  The default output @var{d} is the input
+@var{d}, but at least 3.
+
+@item COMMAw.d: 1 <= iw,ow <= 40
+Equivalent to F format, except that groups of three digits are
+comma-separated on output.  If the number is too large to express in the
+field width, then first commas are eliminated, then if there is still
+not enough space the number is expressed in scientific notation given
+that w >= 6.  Commas are allowed and ignored when this is used as an
+input format.  
+
+@item DOTw.d: 1 <= iw,ow <= 40
+Equivalent to COMMA format except that the roles of comma and decimal
+point are interchanged.  However: If SET /DECIMAL=DOT is in effect, then
+COMMA uses @samp{,} for a decimal point and DOT uses @samp{.} for a
+decimal point.
+
+@item DOLLARw.d: 1 <= iw <= 40; 2 <= ow <= 40
+Equivalent to COMMA format, except that the number is prefixed by a
+dollar sign (@samp{$}) if there is room.  On input the value is allowed
+to be prefixed by a dollar sign, which is ignored.
+
+The default output @var{w} is the input @var{w}, but at least 2.
+
+@item PCTw.d: 2 <= iw,ow <= 40
+Equivalent to F format, except that the number is suffixed by a percent
+sign (@samp{%}) if there is room.  On input the value is allowed to be
+suffixed by a percent sign, which is ignored.
+
+The default output @var{w} is the input @var{w}, but at least 2.
+
+@item Nw.d: 1 <= iw,ow <= 40
+Only digits are allowed within the field width.  The decimal point is
+assumed to be @var{d} digits from the right margin.
+
+The default output format is F with the same @var{w} and @var{d}, except
+if @var{d} > 1.  In that case the output @var{w} is always made to be at
+least 2 + @var{d}.
+
+@item Zw.d @result{} F: 1 <= iw,ow <= 40
+Zoned decimal input.  If you need to use this then you know how.
+
+@item IBw.d @result{} F: 1 <= iw,ow <= 8
+Integer binary format.  The field is interpreted as a fixed-point
+positive or negative binary number in two's-complement notation.  The
+location of the decimal point is implied.  Endianness is the same as the
+host machine.
+
+The default output format is F8.2 if @var{d} is 0.  Otherwise it is F,
+with output @var{w} as 9 + input @var{d} and output @var{d} as input
+@var{d}.
+
+@item PIB @result{} F: 1 <= iw,ow <= 8
+Positive integer binary format.  The field is interpreted as a
+fixed-point positive binary number.  The location of the decimal point
+is implied.  Endianness is teh same as the host machine.
+
+The default output format follows the rules for IB format.
+
+@item Pw.d @result{} F: 1 <= iw,ow <= 16
+Binary coded decimal format.  Each byte from left to right, except the
+rightmost, represents two digits.  The upper nibble of each byte is more
+significant.  The upper nibble of the final byte is the least
+significant digit.  The lower nibble of the final byte is the sign; a
+value of D represents a negative sign and all other values are
+considered positive.  The decimal point is implied.
+
+The default output format follows the rules for IB format.
+
+@item PKw.d @result{} F: 1 <= iw,ow <= 16
+Positive binary code decimal format.  Same as P but the last byte is the
+same as the others.
+
+The default output format follows the rules for IB format.
+
+@item RBw @result{} F: 2 <= iw,ow <= 8
+
+Binary C architecture-dependent ``double'' format.  For a standard
+IEEE754 implementation @var{w} should be 8.
+
+The default output format follows the rules for IB format.
+
+@item PIBHEXw.d @result{} F: 2 <= iw,ow <= 16
+PIB format encoded as textual hex digit pairs.  @var{w} must be even.
+
+The input width is mapped to a default output width as follows:
+2@result{}4, 4@result{}6, 6@result{}9, 8@result{}11, 10@result{}14,
+12@result{}16, 14@result{}18, 16@result{}21.  No allowances are made for
+decimal places.
+
+@item RBHEXw @result{} F: 4 <= iw,ow <= 16
+
+RB format encoded as textual hex digits pairs.  @var{w} must be even.
+
+The default output format is F8.2.
+
+@item CCAw.d: 1 <= ow <= 40
+@itemx CCBw.d: 1 <= ow <= 40
+@itemx CCCw.d: 1 <= ow <= 40
+@itemx CCDw.d: 1 <= ow <= 40
+@itemx CCEw.d: 1 <= ow <= 40
+
+User-defined custom currency formats.  May not be used as an input
+format.  @xref{SET}, for more details.
+@end table
+
+The date and time numeric input and output formats accept a number of
+possible formats.  Before describing the formats themselves, some
+definitions of the elements that make up their formats will be helpful:
+
+@table @dfn
+@item leader
+All formats accept an optional whitespace leader.
+
+@item day
+An integer between 1 and 31 representing the day of month.
+
+@item day-count
+An integer representing a number of days.
+
+@item date-delimiter
+One or more characters of whitespace or the following characters:
+@code{- / . ,}
+
+@item month
+A month name in one of the following forms:
+@itemize @bullet
+@item
+An integer between 1 and 12.
+@item
+Roman numerals representing an integer between 1 and 12.
+@item
+At least the first three characters of an English month name (January,
+February, @dots{}).
+@end itemize
+
+@item year
+An integer year number between 1582 and 19999, or between 1 and 199.
+Years between 1 and 199 will have 1900 added.
+
+@item julian
+A single number with a year number in the first 2, 3, or 4 digits (as
+above) and the day number within the year in the last 3 digits.
+
+@item quarter
+An integer between 1 and 4 representing a quarter.
+
+@item q-delimiter
+The letter @samp{Q} or @samp{q}.
+
+@item week
+An integer between 1 and 53 representing a week within a year.
+
+@item wk-delimiter
+The letters @samp{wk} in any case.
+
+@item time-delimiter
+At least one characters of whitespace or @samp{:} or @samp{.}.
+
+@item hour
+An integer greater than 0 representing an hour.
+
+@item minute
+An integer between 0 and 59 representing a minute within an hour.
+
+@item opt-second
+Optionally, a time-delimiter followed by a real number representing a
+number of seconds.
+
+@item hour24
+An integer between 0 and 23 representing an hour within a day.
+
+@item weekday
+At least the first two characters of an English day word.
+
+@item spaces
+Any amount or no amount of whitespace.
+
+@item sign
+An optional positive or negative sign.
+
+@item trailer
+All formats accept an optional whitespace trailer.
+@end table
+
+The date input formats are strung together from the above pieces.  On
+output, the date formats are always printed in a single canonical
+manner, based on field width.  The date input and output formats are
+described below:
+
+@table @asis
+@item DATEw: 9 <= iw,ow <= 40
+Date format. Input format: leader + day + date-delimiter +
+month + date-delimiter + year + trailer.  Output format: DD-MMM-YY for
+@var{w} < 11, DD-MMM-YYYY otherwise.
+
+@item EDATEw: 8 <= iw,ow <= 40
+European date format.  Input format same as DATE.  Output format:
+DD.MM.YY for @var{w} < 10, DD.MM.YYYY otherwise.
+
+@item SDATEw: 8 <= iw,ow <= 40
+Standard date format. Input format: leader + year + date-delimiter +
+month + date-delimiter + day + trailer.  Output format: YY/MM/DD for
+@var{w} < 10, YYYY/MM/DD otherwise.
+
+@item ADATEw: 8 <= iw,ow <= 40
+American date format.  Input format: leader + month + date-delimiter +
+day + date-delimiter + year + trailer.  Output format: MM/DD/YY for
+@var{w} < 10, MM/DD/YYYY otherwise.
+
+@item JDATEw: 5 <= iw,ow <= 40
+Julian date format.  Input format: leader + julian + trailer.  Output
+format: YYDDD for @var{w} < 7, YYYYDDD otherwise.
+
+@item QYRw: 4 <= iw <= 40, 6 <= ow <= 40
+Quarter/year format.  Input format: leader + quarter + q-delimiter +
+year + trailer.  Output format: @samp{Q Q YY}, where the first
+@samp{Q} is one of the digits 1, 2, 3, 4, if @var{w} < 8, @code{Q Q
+YYYY} otherwise.
+
+@item MOYRw: 6 <= iw,ow <= 40
+Month/year format.  Input format: leader + month + date-delimiter + year
++ trailer.  Output format: @samp{MMM YY} for @var{w} < 8, @samp{MMM
+YYYY} otherwise.
+
+@item WKYRw: 6 <= iw <= 40, 8 <= ow <= 40
+Week/year format.  Input format: leader + week + wk-delimiter + year +
+trailer.  Output format: @samp{WW WK YY} for @var{w} < 10, @samp{WW WK
+YYYY} otherwise.
+
+@item DATETIMEw.d: 17 <= iw,ow <= 40
+Date and time format.  Input format: leader + day + date-delimiter +
+month + date-delimiter + yaer + time-delimiter + hour24 + time-delimiter
++ minute + opt-second.  Output format: @samp{DD-MMM-YYYY HH:MM}.  If
+@var{w} > 19 then seconds @samp{:SS} is added.  If @var{w} > 22 and
+@var{d} > 0 then fractional seconds @samp{.SS} are added.
+
+@item TIMEw.d: 5 <= iw,ow <= 40
+Time format.  Input format: leader + sign + spaces + hour +
+time-delimiter + minute + opt-second.  Output format: @samp{HH:MM}.
+Seconds and fractional seconds are available with @var{w} of at least 8
+and 10, respectively.
+
+@item DTIMEw.d: 1 <= iw <= 40, 8 <= ow <= 40
+Time format with day count.  Input format: leader + sign + spaces +
+day-count + time-delimiter + hour + time-delimiter + minute +
+opt-second.  Output format: @samp{DD HH:MM}.  Seconds and fractional
+seconds are available with @var{w} of at least 8 and 10, respectively.
+
+@item WKDAYw: 2 <= iw,ow <= 40
+A weekday as a number between 1 and 7, where 1 is Sunday.  Input format:
+leader + weekday + trailer.  Output format: as many characters, in all
+capital letters, of the English name of the weekday as will fit in the
+field width.
+
+@item MONTHw: 3 <= iw,ow <= 40
+A month as a number between 1 and 12, where 1 is January.  Input format:
+leader + month + trailer.  Output format: as many character, in all
+capital letters, of the English name of the month as will fit in the
+field width.
+@end table
+
+There are only two formats that may be used with string variables:
+
+@table @asis
+@item Aw: 1 <= iw <= 255, 1 <= ow <= 254
+The entire field is treated as a string value.
+
+@item AHEXw @result{} A: 2 <= iw <= 254; 2 <= ow <= 510
+The field is composed of characters in a string encoded as textual hex
+digit pairs.
+
+The default output @var{w} is half the input @var{w}.
+@end table
+
+@node Scratch Variables,  , Input/Output Formats, Variables
+@subsection Scratch Variables
+
+Most of the time, variables don't retain their values between cases.
+Instead, either they're being read from a data file or the active file,
+in which case they assume the value read, or, if created with COMPUTE or
+another transformation, they're initialized to the system-missing value
+or to blanks, depending on type.
+
+However, sometimes it's useful to have a variable that keeps its value
+between cases.  You can do this with LEAVE (@pxref{LEAVE}), or you can
+use a @dfn{scratch variable}.  Scratch variables are variables whose
+names begin with an octothorpe (@samp{#}).  
+
+Scratch variables have the same properties as variables left with LEAVE:
+they retain their values between cases, and for the first case they are
+initialized to 0 or blanks.  They have the additional property that they
+are deleted before the execution of any procedure.  For this reason,
+scratch variables can't be used for analysis.  To obtain the same
+effect, use COMPUTE (@pxref{COMPUTE}) to copy the scratch variable's
+value into an ordinary variable, then analysis that variable.
+
+@node Files, BNF, Variables, Language
+@section Files Used by PSPP
+
+PSPP makes use of many files each time it runs.  Some of these it
+reads, some it writes, some it creates.  Here is a table listing the
+most important of these files:
+
+@table @strong
+@cindex file, command
+@cindex file, syntax file
+@cindex command file
+@cindex syntax file
+@item command file
+@itemx syntax file
+These names (synonyms) refer to the file that contains instructions to
+PSPP that tell it what to do.  The syntax file's name is specified on
+the PSPP command line.  Syntax files can also be pulled in with the
+@code{INCLUDE} command.
+
+@cindex file, data
+@cindex data file
+@item data file
+Data files contain raw data in ASCII format suitable for being read in
+by the @code{DATA LIST} command.  Data can be embedded in the syntax
+file with @code{BEGIN DATA} and @code{END DATA} commands: this makes the
+syntax file a data file too.
+
+@cindex file, output
+@cindex output file
+@item listing file
+One or more output files are created by PSPP each time it is
+run.  The output files receive the tables and charts produced by
+statistical procedures.  The output files may be in any number of formats,
+depending on how PSPP is configured.
+
+@cindex active file
+@cindex file, active
+@item active file
+The active file is the ``file'' on which all PSPP procedures
+are performed.  The active file contains variable definitions and
+cases.  The active file is not necessarily a disk file: it is stored
+in memory if there is room.
+@end table
+
+@node BNF,  , Files, Language
+@section Backus-Naur Form
+@cindex BNF
+@cindex Backus-Naur Form
+@cindex command syntax, description of
+@cindex description of command syntax
+
+The syntax of some parts of the PSPP language is presented in this
+manual using the formalism known as @dfn{Backus-Naur Form}, or BNF. The
+following table describes BNF:
+
+@itemize @bullet
+@cindex keywords
+@cindex terminals
+@item
+Words in all-uppercase are PSPP keyword tokens.  In BNF, these are
+often called @dfn{terminals}.  There are some special terminals, which
+are actually written in lowercase for clarity:
+
+@table @asis
+@cindex @code{number}
+@item @code{number}
+A real number.
+
+@cindex @code{integer}
+@item @code{integer}
+An integer number.
+
+@cindex @code{string}
+@item @code{string}
+A string.
+
+@cindex @code{var-name}
+@item @code{var-name}
+A single variable name.
+
+@cindex operators
+@cindex punctuators
+@item @code{=}, @code{/}, @code{+}, @code{-}, etc.
+Operators and punctuators.
+
+@cindex @code{.}
+@cindex terminal dot
+@cindex dot, terminal
+@item @code{.}
+The terminal dot.  This is not necessarily an actual dot in the syntax
+file: @xref{Commands}, for more details.
+@end table
+
+@item
+@cindex productions
+@cindex nonterminals
+Other words in all lowercase refer to BNF definitions, called
+@dfn{productions}.  These productions are also known as
+@dfn{nonterminals}.  Some nonterminals are very common, so they are
+defined here in English for clarity:
+
+@table @code
+@cindex @code{var-list}
+@item var-list
+A list of one or more variable names or the keyword @code{ALL}.
+
+@cindex @code{expression}
+@item expression
+An expression.  @xref{Expressions}, for details.
+@end table
+
+@item
+@cindex @code{::=}
+@cindex ``is defined as''
+@cindex productions
+@samp{::=} means ``is defined as''.  The left side of @samp{::=} gives
+the name of the nonterminal being defined.  The right side of @samp{::=}
+gives the definition of that nonterminal.  If the right side is empty,
+then one possible expansion of that nonterminal is nothing.  A BNF
+definition is called a @dfn{production}.
+
+@item
+@cindex terminals and nonterminals, differences
+So, the key difference between a terminal and a nonterminal is that a
+terminal cannot be broken into smaller parts---in fact, every terminal
+is a single token (@pxref{Tokens}).  On the other hand, nonterminals are
+composed of a (possibly empty) sequence of terminals and nonterminals.
+Thus, terminals indicate the deepest level of syntax description.  (In
+parsing theory, terminals are the leaves of the parse tree; nonterminals
+form the branches.)
+
+@item
+@cindex start symbol
+@cindex symbol, start
+The first nonterminal defined in a set of productions is called the
+@dfn{start symbol}.  The start symbol defines the entire syntax for
+that command.
+@end itemize
+
+@node Expressions, Data Input and Output, Language, Top
+@chapter Mathematical Expressions
+@cindex expressions, mathematical
+@cindex mathematical expressions
+
+Some PSPP commands use expressions, which share a common syntax
+among all PSPP commands.  Expressions are made up of
+@dfn{operands}, which can be numbers, strings, or variable names,
+separated by @dfn{operators}.  There are five types of operators:
+grouping, arithmetic, logical, relational, and functions.
+
+Every operator takes one or more @dfn{arguments} as input and produces
+or @dfn{returns} exactly one result as output.  Both strings and numeric
+values can be used as arguments and are produced as results, but each
+operator accepts only specific combinations of numeric and string values
+as arguments.  With few exceptions, operator arguments may be
+full-fledged expressions in themselves.
+
+@menu
+* Booleans::                       Boolean values.
+* Missing Values in Expressions::  Using missing values in expressions.
+* Grouping Operators::             ( )
+* Arithmetic Operators::           + - * / **
+* Logical Operators::              AND NOT OR
+* Relational Operators::           EQ GE GT LE LT NE
+* Functions::                      More-sophisticated operators.
+* Order of Operations::            Operator precedence.
+@end menu
+
+@node Booleans, Missing Values in Expressions, Expressions, Expressions
+@section Boolean values
+@cindex Boolean
+@cindex values, Boolean
+
+There is a third type for arguments and results, the @dfn{Boolean} type,
+which is used to represent true/false conditions.  Booleans have only
+three possible values: 0 (false), 1 (true), and system-missing.
+System-missing is neither true or false.
+
+@itemize @bullet
+@item
+A numeric expression that has value 0, 1, or system-missing may be used
+in place of a Boolean.  Thus, the expression @code{0 AND 1} is valid
+(although it is always true).
+
+@item
+A numeric expression with any other value will cause an error if it is
+used as a Boolean.  So, @code{2 OR 3} is invalid.
+
+@item
+A Boolean expression may not be used in place of a numeric expression.
+Thus, @code{(1>2) + (3<4)} is invalid.
+
+@item
+Strings and Booleans are not compatible, and neither may be used in
+place of the other.
+@end itemize
+
+@node Missing Values in Expressions, Grouping Operators, Booleans, Expressions
+@section Missing Values in Expressions
+
+String missing values are not treated specially in expressions.  Most
+numeric operators return system-missing when given system-missing
+arguments.  Exceptions are listed under particular operator
+descriptions.
+
+User-missing values for numeric variables are always transformed into
+the system-missing value, except inside the arguments to the
+@code{VALUE}, @code{SYSMIS}, and @code{MISSING} functions.
+
+The missing-value functions can be used to precisely control how missing
+values are treated in expressions.  @xref{Missing Value Functions}, for
+more details.
+
+@node Grouping Operators, Arithmetic Operators, Missing Values in Expressions, Expressions
+@section Grouping Operators
+@cindex parentheses
+@cindex @samp{(  )}
+@cindex grouping operators
+@cindex operators, grouping
+
+Parentheses (@samp{()}) are the grouping operators.  Surround an
+expression with parentheses to force early evaluation.
+
+Parentheses also surround the arguments to functions, but in that
+situation they act as punctuators, not as operators.
+
+@node Arithmetic Operators, Logical Operators, Grouping Operators, Expressions
+@section Arithmetic Operators
+@cindex operators, arithmetic
+@cindex arithmetic operators
+
+The arithmetic operators take numeric arguments and produce numeric
+results.
+
+@table @code
+@cindex @samp{+}
+@cindex addition
+@item @var{a} + @var{b}
+Adds @var{a} and @var{b}, returning the sum.
+
+@cindex @samp{-}
+@cindex subtraction
+@item @var{a} - @var{b}
+Subtracts @var{b} from @var{a}, returning the difference.
+
+@cindex @samp{*}
+@cindex multiplication
+@item @var{a} * @var{b}
+Multiplies @var{a} and @var{b}, returning the product.
+
+@cindex @samp{/}
+@cindex division
+@item @var{a} / @var{b}
+Divides @var{a} by @var{b}, returning the quotient.  If @var{b} is
+zero, the result is system-missing.
+
+@cindex @samp{**}
+@cindex exponentiation
+@item @var{a} ** @var{b}
+Returns the result of raising @var{a} to the power @var{b}.  If
+@var{a} is negative and @var{b} is not an integer, the result is
+system-missing.  The result of @code{0**0} is system-missing as well.
+
+@cindex @samp{-}
+@cindex negation
+@item - @var{a}
+Reverses the sign of @var{a}.  
+@end table
+
+@node Logical Operators, Relational Operators, Arithmetic Operators, Expressions
+@section Logical Operators
+@cindex logical operators
+@cindex operators, logical
+
+@cindex true
+@cindex false
+@cindex Boolean
+@cindex values, system-missing
+@cindex system-missing
+The logical operators take logical arguments and produce logical
+results, meaning ``true or false''.  PSPP logical operators are
+not true Boolean operators because they may also result in a
+system-missing value.
+
+@table @code
+@cindex @code{AND}
+@cindex @samp{&}
+@cindex intersection, logical
+@cindex logical intersection
+@item @var{a} AND @var{b}
+@itemx @var{a} & @var{b}
+True if both @var{a} and @var{b} are true.  However, if one argument is
+false and the other is missing, the result is false, not missing.  If
+both arguments are missing, the result is missing.
+
+@cindex @code{OR}
+@cindex @samp{|}
+@cindex union, logical
+@cindex logical union
+@item @var{a} OR @var{b}
+@itemx @var{a} | @var{b}
+True if at least one of @var{a} and @var{b} is true.  If one argument is
+true and the other is missing, the result is true, not missing.  If both
+arguments are missing, the result is missing.
+
+@cindex @code{NOT}
+@cindex @samp{~}
+@cindex inversion, logical
+@cindex logical inversion
+@item NOT @var{a}
+@itemx ~ @var{a}
+True if @var{a} is false.
+@end table
+
+@node Relational Operators, Functions, Logical Operators, Expressions
+@section Relational Operators
+
+The relational operators take numeric or string arguments and produce Boolean
+results.
+
+Note that, with numeric arguments, PSPP does not make exact
+relational tests.  Instead, two numbers are considered to be equal even
+if they differ by a small amount.  This amount, @dfn{epsilon}, is
+dependent on the PSPP configuration and determined at compile
+time.  (The default value is 0.000000001, or 
+@ifinfo
+@code{10**(-9)}.)
+@end ifinfo
+@tex
+$10 ^ -9$.)
+@end tex
+Use of epsilon allows for round-off errors.  Use of epsilon is also
+idiotic, but the author is not a numeric analyst.
+
+Strings cannot be compared to numbers.  When strings of different
+lengths are compared, the shorter string is right-padded with spaces
+to match the length of the longer string.
+
+The results of string comparisons, other than tests for equality or
+inequality, are dependent on the character set in use.  String
+comparisons are case-sensitive.
+
+@table @code
+@cindex equality, testing
+@cindex testing for equality
+@cindex @code{EQ}
+@cindex @samp{=}
+@item @var{a} EQ @var{b}
+@itemx @var{a} = @var{b}
+True if @var{a} is equal to @var{b}.
+
+@cindex less than or equal to
+@cindex @code{LE}
+@cindex @code{<=}
+@item @var{a} LE @var{b}
+@itemx @var{a} <= @var{b}
+True if @var{a} is less than or equal to @var{b}.
+
+@cindex less than
+@cindex @code{LT}
+@cindex @code{<}
+@item @var{a} LT @var{b}
+@itemx @var{a} < @var{b}
+True if @var{a} is less than @var{b}.
+
+@cindex greater than or equal to
+@cindex @code{GE}
+@cindex @code{>=}
+@item @var{a} GE @var{b}
+@itemx @var{a} >= @var{b}
+True if @var{a} is greater than or equal to @var{b}.
+
+@cindex greater than
+@cindex @code{GT}
+@cindex @samp{>}
+@item @var{a} GT @var{b}
+@itemx @var{a} > @var{b}
+True if @var{a} is greater than @var{b}.
+
+@cindex inequality, testing
+@cindex testing for inequality
+@cindex @code{NE}
+@cindex @code{~=}
+@cindex @code{<>}
+@item @var{a} NE @var{b}
+@itemx @var{a} ~= @var{b}
+@itemx @var{a} <> @var{b}
+True is @var{a} is not equal to @var{b}.
+@end table
+
+@node Functions, Order of Operations, Relational Operators, Expressions
+@section Functions
+@cindex functions
+
+@cindex mathematics
+@cindex operators
+@cindex parentheses
+@cindex @code{(}
+@cindex @code{)}
+@cindex names, of functions
+PSPP functions provide mathematical abilities above and beyond
+those possible using simple operators.  Functions have a common
+syntax: each is composed of a function name followed by a left
+parenthesis, one or more arguments, and a right parenthesis.  Function
+names are @strong{not} reserved; their names are specially treated
+only when followed by a left parenthesis: @code{EXP(10)} refers to the
+constant value @code{e} raised to the 10th power, but @code{EXP} by
+itself refers to the value of variable EXP.
+
+The sections below describe each function in detail.
+
+@menu
+* Advanced Mathematics::        EXP LG10 LN SQRT
+* Miscellaneous Mathematics::   ABS MOD MOD10 RND TRUNC
+* Trigonometry::                ACOS ARCOS ARSIN ARTAN ASIN ATAN COS SIN TAN
+* Missing Value Functions::     MISSING NMISS NVALID SYSMIS VALUE
+* Pseudo-Random Numbers::       NORMAL UNIFORM
+* Set Membership::              ANY RANGE
+* Statistical Functions::       CFVAR MAX MEAN MIN SD SUM VARIANCE
+* String Functions::            CONCAT INDEX LENGTH LOWER LPAD LTRIM NUMBER 
+                                RINDEX RPAD RTRIM STRING SUBSTR UPCASE
+* Time & Date::                 CTIME.xxx DATE.xxx TIME.xxx XDATE.xxx
+* Miscellaneous Functions::     LAG YRMODA
+* Functions Not Implemented::   CDF.xxx CDFNORM IDF.xxx NCDF.xxx PROBIT RV.xxx
+@end menu
+
+@node Advanced Mathematics, Miscellaneous Mathematics, Functions, Functions
+@subsection Advanced Mathematical Functions
+@cindex mathematics, advanced
+
+Advanced mathematical functions take numeric arguments and produce
+numeric results.
+
+@deftypefn {Function} {} EXP (@var{exponent})
+Returns @i{e} (approximately 2.71828) raised to power @var{exponent}.
+@end deftypefn
+
+@cindex logarithms
+@deftypefn {Function} {} LG10 (@var{number})
+Takes the base-10 logarithm of @var{number}.  If @var{number} is
+not positive, the result is system-missing.
+@end deftypefn
+
+@deftypefn {Function} {} LN (@var{number})
+Takes the base-@samp{e} logarithm of @var{number}.  If @var{number} is
+not positive, the result is system-missing.
+@end deftypefn
+
+@cindex square roots
+@deftypefn {Function} {} SQRT (@var{number})
+Takes the square root of @var{number}.  If @var{number} is negative,
+the result is system-missing.
+@end deftypefn
+
+@node Miscellaneous Mathematics, Trigonometry, Advanced Mathematics, Functions
+@subsection Miscellaneous Mathematical Functions
+@cindex mathematics, miscellaneous
+
+Miscellaneous mathematical functions take numeric arguments and produce
+numeric results.
+
+@cindex absolute value
+@deftypefn {Function} {} ABS (@var{number})
+Results in the absolute value of @var{number}.
+@end deftypefn
+
+@cindex modulus
+@deftypefn {Function} {} MOD (@var{numerator}, @var{denominator})
+Returns the remainder (modulus) of @var{numerator} divided by
+@var{denominator}.  If @var{denominator} is 0, the result is
+system-missing.  However, if @var{numerator} is 0 and
+@var{denominator} is system-missing, the result is 0.
+@end deftypefn
+
+@cindex modulus, by 10
+@deftypefn {Function} {} MOD10 (@var{number})
+Returns the remainder when @var{number} is divided by 10.  If
+@var{number} is negative, MOD10(@var{number}) is negative or zero.
+@end deftypefn
+
+@cindex rounding
+@deftypefn {Function} {} RND (@var{number})
+Takes the absolute value of @var{number} and rounds it to an integer.
+Then, if @var{number} was negative originally, negates the result.
+@end deftypefn
+
+@cindex truncation
+@deftypefn {Function} {} TRUNC (@var{number})
+Discards the fractional part of @var{number}; that is, rounds
+@var{number} towards zero.
+@end deftypefn
+
+@node Trigonometry, Missing Value Functions, Miscellaneous Mathematics, Functions
+@subsection Trigonometric Functions
+@cindex trigonometry
+
+Trigonometric functions take numeric arguments and produce numeric
+results.
+
+@cindex arccosine
+@cindex inverse cosine
+@deftypefn {Function} {} ACOS (@var{number})
+@deftypefnx {Function} {} ARCOS (@var{number})
+Takes the arccosine, in radians, of @var{number}.  Results in
+system-missing if @var{number} is not between -1 and 1.  Portability:
+none.
+@end deftypefn
+
+@cindex arcsine
+@cindex inverse sine
+@deftypefn {Function} {} ARSIN (@var{number})
+Takes the arcsine, in radians, of @var{number}.  Results in
+system-missing if @var{number} is not between -1 and 1 inclusive.
+@end deftypefn
+
+@cindex arctangent
+@cindex inverse tangent
+@deftypefn {Function} {} ARTAN (@var{number})
+Takes the arctangent, in radians, of @var{number}.
+@end deftypefn
+
+@cindex arcsine
+@cindex inverse sine
+@deftypefn {Function} {} ASIN (@var{number})
+Takes the arcsine, in radians, of @var{number}.  Results in
+system-missing if @var{number} is not between -1 and 1 inclusive.
+Portability: none.
+@end deftypefn
+
+@cindex arctangent
+@cindex inverse tangent
+@deftypefn {Function} {} ATAN (@var{number})
+Takes the arctangent, in radians, of @var{number}.
+@end deftypefn
+
+@quotation
+@strong{Please note:} Use of the AR* group of inverse trigonometric
+functions is recommended over the A* group because they are more
+portable.
+@end quotation
+
+@cindex cosine
+@deftypefn {Function} {} COS (@var{radians})
+Takes the cosine of @var{radians}.
+@end deftypefn
+
+@cindex sine
+@deftypefn {Function} {} SIN (@var{angle})
+Takes the sine of @var{radians}.
+@end deftypefn
+
+@cindex tangent
+@deftypefn {Function} {} TAN (@var{angle})
+Takes the tangent of @var{radians}.  Results in system-missing at values
+of @var{angle} that are too close to odd multiples of pi/2.
+Portability: none.
+@end deftypefn
+
+@node Missing Value Functions, Pseudo-Random Numbers, Trigonometry, Functions
+@subsection Missing-Value Functions
+@cindex missing values
+@cindex values, missing
+@cindex functions, missing-value
+
+Missing-value functions take various types as arguments, returning
+various types of results.
+
+@deftypefn {Function} {} MISSING (@var{variable or expression})
+@var{num} may be a single variable name or an expression.  If it is a
+variable name, results in 1 if the variable has a user-missing or
+system-missing value for the current case, 0 otherwise.  If it is an
+expression, results in 1 if the expression has the system-missing value,
+0 otherwise.
+
+@quotation
+@strong{Please note:} If the argument is a string expression other than
+a variable name, MISSING is guaranteed to return 0, because strings do
+not have a system-missing value.  Also, when using a numeric expression
+argument, remember that user-missing values are converted to the
+system-missing value in most contexts.  Thus, the expressions
+@code{MISSING(VAR1 @var{op} VAR2)} and @code{MISSING(VAR1) OR
+MISSING(VAR2)} are often equivalent, depending on the specific operator
+@var{op} used.
+@end quotation
+@end deftypefn
+
+@deftypefn {Function} {} NMISS (@var{expr} [, @var{expr}]@dots{})
+Each argument must be a numeric expression.  Returns the number of
+user- or system-missing values in the list.  As a special extension,
+the syntax @code{@var{var1} TO @var{var2}} may be used to refer to a
+range of variables; see @ref{Sets of Variables}, for more details.
+@end deftypefn
+
+@deftypefn {Function} {} NVALID (@var{expr} [, @var{expr}]@dots{})
+Each argument must be a numeric expression.  Returns the number of
+values in the list that are not user- or system-missing.  As a special extension,
+the syntax @code{@var{var1} TO @var{var2}} may be used to refer to a
+range of variables; see @ref{Sets of Variables}, for more details.
+@end deftypefn
+
+@deftypefn {Function} {} SYSMIS (@var{variable or expression})
+When given the name of a numeric variable, returns 1 if the value of
+that variable is system-missing.  Otherwise, if the value is not
+missing or if it is user-missing, returns 0.  If given the name of a
+string variable, always returns 1.  If given an expression other than
+a single variable name, results in 1 if the value is system- or
+user-missing, 0 otherwise.
+@end deftypefn
+
+@deftypefn {Function} {} VALUE (@var{variable})
+Prevents the user-missing values of @var{variable} from being
+transformed into system-missing values: If @var{variable} is not
+system- or user-missing, results in the value of @var{variable}.  If
+@var{variable} is user-missing, results in the value of @var{variable}
+anyway.  If @var{variable} is system-missing, results in system-missing.
+@end deftypefn
+
+@node Pseudo-Random Numbers, Set Membership, Missing Value Functions, Functions
+@subsection Pseudo-Random Number Generation Functions
+@cindex random numbers
+@cindex pseudo-random numbers (see random numbers)
+
+Pseudo-random number generation functions take numeric arguments and
+produce numeric results.
+
+@cindex Knuth
+The system's C library random generator is used as a basis for
+generating random numbers, since random number generation is a
+system-dependent task.  However, Knuth's Algorithm B is used to
+shuffle the resultant values, which is enough to make even a stream of
+consecutive integers random enough for most applications.
+
+(If you're worried about the quality of the random number generator,
+well, you're using a statistical processing package---analyze it!)
+
+@cindex random numbers, normally-distributed
+@deftypefn {Function} {} NORMAL (@var{number})
+Results in a random number.  Results from @code{NORMAL} are normally
+distributed with a mean of 0 and a standard deviation of @var{number}.
+@end deftypefn
+
+@cindex random numbers, uniformly-distributed
+@deftypefn {Function} {} UNIFORM (@var{number})
+Results in a random number between 0 and @var{number}.  Results from
+@code{UNIFORM} are evenly distributed across its entire range.  There
+may be a maximum on the largest random number ever generated---this is
+often 2**31-1 (2,147,483,647), but it may be orders of magnitude
+higher or lower.
+@end deftypefn
+
+@node Set Membership, Statistical Functions, Pseudo-Random Numbers, Functions
+@subsection Set-Membership Functions
+@cindex set membership
+@cindex membership, of set
+
+Set membership functions determine whether a value is a member of a set.
+They take a set of numeric arguments or a set of string arguments, and
+produce Boolean results.
+
+String comparisons are performed according to the rules given in
+@ref{Relational Operators}.
+
+@deftypefn {Function} {} ANY (@var{value}, @var{set} [, @var{set}]@dots{})
+Results in true if @var{value} is equal to any of the @var{set}
+values.  Otherwise, results in false.  If @var{value} is
+system-missing, returns system-missing.  System-missing values in
+@var{set} do not cause ANY to return system-missing.
+@end deftypefn
+
+@deftypefn {Function} {} RANGE (@var{value}, @var{low}, @var{high} [, @var{low}, @var{high}]@dots{})
+Results in true if @var{value} is in any of the intervals bounded by
+@var{low} and @var{high} inclusive.  Otherwise, results in false.
+Each @var{low} must be less than or equal to its corresponding
+@var{high} value.  @var{low} and @var{high} must be given in pairs.
+If @var{value} is system-missing, returns system-missing.
+System-missing values in @var{set} do not cause RANGE to return
+system-missing.
+@end deftypefn
+
+@node Statistical Functions, String Functions, Set Membership, Functions
+@subsection Statistical Functions
+@cindex functions, statistical
+@cindex statistics
+
+Statistical functions compute descriptive statistics on a list of
+values.  Some statistics can be computed on numeric or string values;
+other can only be computed on numeric values.  They result in the same
+type as their arguments.
+
+@cindex arguments, minimum valid
+@cindex minimum valid number of arguments
+With statistical functions it is possible to specify a minimum number of
+non-missing arguments for the function to be evaluated.  To do so,
+append a dot and the number to the function name.  For instance, to
+specify a minimum of three valid arguments to the MEAN function, use the
+name @code{MEAN.3}.
+
+@cindex coefficient of variation
+@cindex variation, coefficient of
+@deftypefn {Function} {} CFVAR (@var{number}, @var{number}[, @dots{}])
+Results in the coefficient of variation of the values of @var{number}.
+This function requires at least two valid arguments to give a
+non-missing result.  (The coefficient of variation is the standard
+deviation divided by the mean.)
+@end deftypefn
+
+@cindex maximum
+@deftypefn {Function} {} MAX (@var{value}, @var{value}[, @dots{}])
+Results in the value of the greatest @var{value}.  The @var{value}s may
+be numeric or string.  Although at least two arguments must be given,
+only one need be valid for MAX to give a non-missing result.
+@end deftypefn
+
+@cindex mean
+@deftypefn {Function} {} MEAN (@var{number}, @var{number}[, @dots{}])
+Results in the mean of the values of @var{number}.  Although at least
+two arguments must be given, only one need be valid for MEAN to give a
+non-missing result.
+@end deftypefn
+
+@cindex minimum
+@deftypefn {Function} {} MIN (@var{number}, @var{number}[, @dots{}])
+Results in the value of the least @var{value}.  The @var{value}s may
+be numeric or string.  Although at least two arguments must be given,
+only one need be valid for MAX to give a non-missing result.
+@end deftypefn
+
+@cindex standard deviation
+@cindex deviation, standard
+@deftypefn {Function} {} SD (@var{number}, @var{number}[, @dots{}])
+Results in the standard deviation of the values of @var{number}.
+This function requires at least two valid arguments to give a
+non-missing result.
+@end deftypefn
+
+@cindex sum
+@deftypefn {Function} {} SUM (@var{number}, @var{number}[, @dots{}])
+Results in the sum of the values of @var{number}.  Although at least two
+arguments must be given, only one need by valid for SUM to give a
+non-missing result.
+@end deftypefn
+
+@cindex variance
+@deftypefn {Function} {} VAR (@var{number}, @var{number}[, @dots{}])
+Results in the variance of the values of @var{number}.  This function
+requires at least two valid arguments to give a non-missing result.
+@end deftypefn
+
+@deftypefn {Function} {} VARIANCE (@var{number}, @var{number}[, @dots{}])
+Results in the variance of the values of @var{number}.  This function
+requires at least two valid arguments to give a non-missing result.
+(Use VAR in preference to VARIANCE for reasons of portability.)
+@end deftypefn
+
+@node String Functions, Time & Date, Statistical Functions, Functions
+@subsection String Functions
+@cindex functions, string
+@cindex string functions
+
+String functions take various arguments and return various results.
+
+@cindex concatenation
+@cindex strings, concatenation of
+@deftypefn {Function} {} CONCAT (@var{string}, @var{string}[, @dots{}])
+Returns a string consisting of each @var{string} in sequence.
+@code{CONCAT("abc", "def", "ghi")} has a value of @code{"abcdefghi"}.
+The resultant string is truncated to a maximum of 255 characters.
+@end deftypefn
+
+@cindex searching strings
+@deftypefn {Function} {} INDEX (@var{haystack}, @var{needle})
+Returns a positive integer indicating the position of the first
+occurrence @var{needle} in @var{haystack}.  Returns 0 if @var{haystack}
+does not contain @var{needle}.  Returns system-missing if @var{needle}
+is an empty string.
+@end deftypefn
+
+@deftypefn {Function} {} INDEX (@var{haystack}, @var{needle}, @var{divisor})
+Divides @var{needle} into parts, each with length @var{divisor}.
+Searches @var{haystack} for the first occurrence of each part, and
+returns the smallest value.  Returns 0 if @var{haystack} does not
+contain any part in @var{needle}.  It is an error if @var{divisor}
+cannot be evenly divided into the length of @var{needle}.  Returns
+system-missing if @var{needle} is an empty string.
+@end deftypefn
+
+@cindex strings, finding length of
+@deftypefn {Function} {} LENGTH (@var{string})
+Returns the number of characters in @var{string}.
+@end deftypefn
+
+@cindex strings, case of
+@deftypefn {Function} {} LOWER (@var{string})
+Returns a string identical to @var{string} except that all uppercase
+letters are changed to lowercase letters.  The definitions of
+``uppercase'' and ``lowercase'' are system-dependent.
+@end deftypefn
+
+@cindex strings, padding
+@deftypefn {Function} {} LPAD (@var{string}, @var{length})
+If @var{string} is at least @var{length} characters in length, returns
+@var{string} unchanged.  Otherwise, returns @var{string} padded with
+spaces on the left side to length @var{length}.  Returns an empty string
+if @var{length} is system-missing, negative, or greater than 255.
+@end deftypefn
+
+@deftypefn {Function} {} LPAD (@var{string}, @var{length}, @var{padding})
+If @var{string} is at least @var{length} characters in length, returns
+@var{string} unchanged.  Otherwise, returns @var{string} padded with
+@var{padding} on the left side to length @var{length}.  Returns an empty
+string if @var{length} is system-missing, negative, or greater than 255, or
+if @var{padding} does not contain exactly one character.
+@end deftypefn
+
+@cindex strings, trimming
+@cindex whitespace, trimming
+@deftypefn {Function} {} LTRIM (@var{string})
+Returns @var{string}, after removing leading spaces.  Other whitespace,
+such as tabs, carriage returns, line feeds, and vertical tabs, is not
+removed.
+@end deftypefn
+
+@deftypefn {Function} {} LTRIM (@var{string}, @var{padding})
+Returns @var{string}, after removing leading @var{padding} characters.
+If @var{padding} does not contain exactly one character, returns an
+empty string.
+@end deftypefn
+
+@cindex numbers, converting from strings
+@cindex strings, converting to numbers
+@deftypefn {Function} {} NUMBER (@var{string})
+Returns the number produced when @var{string} is interpreted according
+to format F@var{x}.0, where @var{x} is the number of characters in
+@var{string}.  If @var{string} does not form a proper number,
+system-missing is returned without an error message.  Portability: none.
+@end deftypefn
+
+@deftypefn {Function} {} NUMBER (@var{string}, @var{format})
+Returns the number produced when @var{string} is interpreted according
+to format specifier @var{format}.  Only the number of characters in
+@var{string} specified by @var{format} are examined.  For example,
+@code{NUMBER("123", F3.0)} and @code{NUMBER("1234", F3.0)} both have
+value 123.  If @var{string} does not form a proper number,
+system-missing is returned without an error message.
+@end deftypefn
+
+@cindex strings, searching backwards
+@deftypefn {Function} {} RINDEX (@var{string}, @var{format})
+Returns a positive integer indicating the position of the last
+occurrence of @var{needle} in @var{haystack}.  Returns 0 if
+@var{haystack} does not contain @var{needle}.  Returns system-missing if
+@var{needle} is an empty string.
+@end deftypefn
+
+@deftypefn {Function} {} RINDEX (@var{haystack}, @var{needle}, @var{divisor})
+Divides @var{needle} into parts, each with length @var{divisor}.
+Searches @var{haystack} for the last occurrence of each part, and
+returns the largest value.  Returns 0 if @var{haystack} does not contain
+any part in @var{needle}.  It is an error if @var{divisor} cannot be
+evenly divided into the length of @var{needle}.  Returns system-missing
+if @var{needle} is an empty string.
+@end deftypefn
+
+@cindex padding strings
+@cindex strings, padding
+@deftypefn {Function} {} RPAD (@var{string}, @var{length})
+If @var{string} is at least @var{length} characters in length, returns
+@var{string} unchanged.  Otherwise, returns @var{string} padded with
+spaces on the right to length @var{length}.  Returns an empty string if
+@var{length} is system-missing, negative, or greater than 255.
+@end deftypefn
+
+@deftypefn {Function} {} RPAD (@var{string}, @var{length}, @var{padding})
+If @var{string} is at least @var{length} characters in length, returns
+@var{string} unchanged.  Otherwise, returns @var{string} padded with
+@var{padding} on the right to length @var{length}.  Returns an empty
+string if @var{length} is system-missing, negative, or greater than 255,
+or if @var{padding} does not contain exactly one character.
+@end deftypefn
+
+@cindex strings, trimming
+@cindex whitespace, trimming
+@deftypefn {Function} {} RTRIM (@var{string})
+Returns @var{string}, after removing trailing spaces.  Other types of
+whitespace are not removed.
+@end deftypefn
+
+@deftypefn {Function} {} RTRIM (@var{string}, @var{padding})
+Returns @var{string}, after removing trailing @var{padding} characters.
+If @var{padding} does not contain exactly one character, returns an
+empty string.
+@end deftypefn
+
+@cindex strings, converting from numbers
+@cindex numbers, converting to strings
+@deftypefn {Function} {} STRING (@var{number}, @var{format})
+Returns a string corresponding to @var{number} in the format given by
+format specifier @var{format}.  For example, @code{STRING(123.56, F5.1)}
+has the value @code{"123.6"}.
+@end deftypefn
+
+@cindex substrings
+@cindex strings, taking substrings of
+@deftypefn {Function} {} SUBSTR (@var{string}, @var{start})
+Returns a string consisting of the value of @var{string} from position
+@var{start} onward.  Returns an empty string if @var{start} is system-missing
+or has a value less than 1 or greater than the number of characters in
+@var{string}.
+@end deftypefn
+
+@deftypefn {Function} {} SUBSTR (@var{string}, @var{start}, @var{count})
+Returns a string consisting of the first @var{count} characters from
+@var{string} beginning at position @var{start}.  Returns an empty string
+if @var{start} or @var{count} is system-missing, if @var{start} is less
+than 1 or greater than the number of characters in @var{string}, or if
+@var{count} is less than 1.  Returns a string shorter than @var{count}
+characters if @var{start} + @var{count} - 1 is greater than the number
+of characters in @var{string}.  Examples: @code{SUBSTR("abcdefg", 3, 2)}
+has value @code{"cd"}; @code{SUBSTR("Ben Pfaff", 5, 10)} has the value
+@code{"Pfaff"}.
+@end deftypefn
+
+@cindex case conversion
+@cindex strings, case of
+@deftypefn {Function} {} UPCASE (@var{string})
+Returns @var{string}, changing lowercase letters to uppercase letters.
+@end deftypefn
+
+@node Time & Date, Miscellaneous Functions, String Functions, Functions
+@subsection Time & Date Functions
+@cindex functions, time & date
+@cindex times
+@cindex dates
+
+@cindex dates, legal range of
+The legal range of dates for use in PSPP is 15 Oct 1582
+through 31 Dec 19999.
+
+@cindex arguments, invalid
+@cindex invalid arguments
+@quotation
+@strong{Please note:} Most time & date extraction functions will accept
+invalid arguments:
+
+@itemize @bullet
+@item
+Negative numbers in PSPP time format.
+@item
+Numbers less than 86,400 in PSPP date format.
+@end itemize
+
+However, sensible results are not guaranteed for these invalid values.
+The given equivalents for these functions are definitely not guaranteed
+for invalid values.
+@end quotation
+
+@quotation
+@strong{Please note also:} The time & date construction
+functions @strong{do} produce reasonable and useful results for
+out-of-range values; these are not considered invalid.
+@end quotation
+
+@menu
+* Time & Date Concepts::        How times & dates are defined and represented
+* Time Construction::           TIME.@{DAYS HMS@}
+* Time Extraction::             CTIME.@{DAYS HOURS MINUTES SECONDS@}
+* Date Construction::           DATE.@{DMY MDY MOYR QYR WKYR YRDAY@}
+* Date Extraction::             XDATE.@{DATE HOUR JDAY MDAY MINUTE MONTH
+                                       QUARTER SECOND TDAY TIME WEEK
+                                       WKDAY YEAR@}
+@end menu
+
+@node Time & Date Concepts, Time Construction, Time & Date, Time & Date
+@subsubsection How times & dates are defined and represented
+
+@cindex time, concepts
+@cindex time, intervals
+Times and dates are handled by PSPP as single numbers.  A
+@dfn{time} is an interval.  PSPP measures times in seconds.
+Thus, the following intervals correspond with the numeric values given:
+                
+@example
+          10 minutes                        600
+          1 hour                          3,600
+          1 day, 3 hours, 10 seconds     97,210
+          40 days                     3,456,000
+          10010 d, 14 min, 24 s     864,864,864
+@end example
+
+@cindex dates, concepts
+@cindex time, instants of
+A @dfn{date}, on the other hand, is a particular instant in the past or
+the future.  PSPP represents a date as a number of seconds after the
+midnight that separated 8 Oct 1582 and 9 Oct 1582.  (Please note that 15
+Oct 1582 immediately followed 9 Oct 1582.)  Thus, the midnights before
+the dates given below correspond with the numeric PSPP dates given:
+
+@example
+              15 Oct 1582                86,400
+               4 Jul 1776         6,113,318,400
+               1 Jan 1900        10,010,390,400
+               1 Oct 1978        12,495,427,200
+              24 Aug 1995        13,028,601,600
+@end example
+
+@cindex time, mathematical properties of
+@cindex mathematics, applied to times & dates
+@cindex dates, mathematical properties of
+@noindent
+Please note: 
+
+@itemize @bullet
+@item
+A time may be added to, or subtracted from, a date, resulting in a date.
+
+@item
+The difference of two dates may be taken, resulting in a time.  
+
+@item 
+Two times may be added to, or subtracted from, each other, resulting in
+a time.
+@end itemize
+
+(Adding two dates does not produce a useful result.)
+
+Since times and dates are merely numbers, the ordinary addition and
+subtraction operators are employed for these purposes.
+
+@quotation
+@strong{Please note:} Many dates and times have extremely large
+values---just look at the values above.  Thus, it is not a good idea to
+take powers of these values; also, the accuracy of some procedures may
+be affected.  If necessary, convert times or dates in seconds to some
+other unit, like days or years, before performing analysis.
+@end quotation
+
+@node Time Construction, Time Extraction, Time & Date Concepts, Time & Date
+@subsubsection Functions that Produce Times
+@cindex times, constructing
+@cindex constructing times
+
+These functions take numeric arguments and produce numeric results in
+PSPP time format.
+
+@cindex days
+@cindex time, in days
+@deftypefn {Function} {} TIME.DAYS (@var{ndays})
+Results in a time value corresponding to @var{ndays} days.
+(@code{TIME.DAYS(@var{x})} is equivalent to @code{@var{x} * 60 * 60 *
+24}.)
+@end deftypefn
+
+@cindex hours-minutes-seconds
+@cindex time, in hours-minutes-seconds
+@deftypefn {Function} {} TIME.HMS (@var{nhours}, @var{nmins}, @var{nsecs})
+Results in a time value corresponding to @var{nhours} hours, @var{nmins}
+minutes, and @var{nsecs} seconds.  (@code{TIME.HMS(@var{h}, @var{m},
+@var{s})} is equivalent to @code{@var{h}*60*60 + @var{m}*60 +
+@var{s}}.)
+@end deftypefn
+
+@node Time Extraction, Date Construction, Time Construction, Time & Date
+@subsubsection Functions that Examine Times
+@cindex extraction, of time
+@cindex time examination
+@cindex examination, of times
+@cindex time, lengths of
+
+These functions take numeric arguments in PSPP time format and
+give numeric results.
+
+@cindex days
+@cindex time, in days
+@deftypefn {Function} {} CTIME.DAYS (@var{time})
+Results in the number of days and fractional days in @var{time}.
+(@code{CTIME.DAYS(@var{x})} is equivalent to @code{@var{x}/60/60/24}.)
+@end deftypefn
+
+@cindex hours
+@cindex time, in hours
+@deftypefn {Function} {} CTIME.HOURS (@var{time})
+Results in the number of hours and fractional hours in @var{time}.
+(@code{CTIME.HOURS(@var{x})} is equivalent to @code{@var{x}/60/60}.)
+@end deftypefn
+
+@cindex minutes
+@cindex time, in minutes
+@deftypefn {Function} {} CTIME.MINUTES (@var{time})
+Results in the number of minutes and fractional minutes in @var{time}.
+(@code{CTIME.MINUTES(@var{x})} is equivalent to @code{@var{x}/60}.)
+@end deftypefn
+
+@cindex seconds
+@cindex time, in seconds
+@deftypefn {Function} {} CTIME.SECONDS (@var{time})
+Results in the number of seconds and fractional seconds in @var{time}.
+(@code{CTIME.SECONDS} does nothing; @code{CTIME.SECONDS(@var{x})} is
+equivalent to @code{@var{x}}.)
+@end deftypefn
+
+@node Date Construction, Date Extraction, Time Extraction, Time & Date
+@subsubsection Functions that Produce Dates
+@cindex dates, constructing
+@cindex constructing dates
+
+@cindex arguments, of date construction functions
+These functions take numeric arguments and give numeric results in the
+PSPP date format.  Arguments taken by these functions are:
+
+@table @var
+@item day
+Refers to a day of the month between 1 and 31.
+
+@item month
+Refers to a month of the year between 1 and 12.
+
+@item quarter
+Refers to a quarter of the year between 1 and 4.  The quarters of the
+year begin on the first days of months 1, 4, 7, and 10.
+
+@item week
+Refers to a week of the year between 1 and 53.
+
+@item yday
+Refers to a day of the year between 1 and 366.
+
+@item year
+Refers to a year between 1582 and 19999.
+@end table
+
+@cindex arguments, invalid
+If these functions' arguments are out-of-range, they are correctly
+normalized before conversion to date format.  Non-integers are rounded
+toward zero.
+
+@cindex day-month-year
+@cindex dates, day-month-year
+@deftypefn {Function} {} DATE.DMY (@var{day}, @var{month}, @var{year})
+@deftypefnx {Function} {} DATE.MDY (@var{month}, @var{day}, @var{year})
+Results in a date value corresponding to the midnight before day
+@var{day} of month @var{month} of year @var{year}.
+@end deftypefn
+
+@cindex month-year
+@cindex dates, month-year
+@deftypefn {Function} {} DATE.MOYR (@var{month}, @var{year})
+Results in a date value corresponding to the midnight before the first
+day of month @var{month} of year @var{year}.
+@end deftypefn
+
+@cindex quarter-year
+@cindex dates, quarter-year
+@deftypefn {Function} {} DATE.QYR (@var{quarter}, @var{year})
+Results in a date value corresponding to the midnight before the first
+day of quarter @var{quarter} of year @var{year}.
+@end deftypefn
+
+@cindex week-year
+@cindex dates, week-year
+@deftypefn {Function} {} DATE.WKYR (@var{week}, @var{year})
+Results in a date value corresponding to the midnight before the first
+day of week @var{week} of year @var{year}.
+@end deftypefn
+
+@cindex year-day
+@cindex dates, year-day
+@deftypefn {Function} {} DATE.YRDAY (@var{year}, @var{yday})
+Results in a date value corresponding to the midnight before day
+@var{yday} of year @var{year}.
+@end deftypefn
+
+@node Date Extraction,  , Date Construction, Time & Date
+@subsubsection Functions that Examine Dates
+@cindex extraction, of dates
+@cindex date examination
+
+@cindex arguments, of date extraction functions
+These functions take numeric arguments in PSPP date or time
+format and give numeric results.  These names are used for arguments:
+
+@table @var
+@item date
+A numeric value in PSPP date format.
+
+@item time
+A numeric value in PSPP time format.
+
+@item time-or-date
+A numeric value in PSPP time or date format.
+@end table
+
+@cindex days
+@cindex dates, in days
+@cindex time, in days
+@deftypefn {Function} {} XDATE.DATE (@var{time-or-date})
+For a time, results in the time corresponding to the number of whole
+days @var{date-or-time} includes.  For a date, results in the date
+corresponding to the latest midnight at or before @var{date-or-time};
+that is, gives the date that @var{date-or-time} is in.
+(XDATE.DATE(@var{x}) is equivalent to TRUNC(@var{x}/86400)*86400.)
+Applying this function to a time is a Portability: none feature.
+@end deftypefn
+
+@cindex hours
+@cindex dates, in hours
+@cindex time, in hours
+@deftypefn {Function} {} XDATE.HOUR (@var{time-or-date})
+For a time, results in the number of whole hours beyond the number of
+whole days represented by @var{date-or-time}.  For a date, results in
+the hour (as an integer between 0 and 23) corresponding to
+@var{date-or-time}.  (XDATE.HOUR(@var{x}) is equivalent to
+MOD(TRUNC(@var{x}/3600),24))  Applying this function to a time is a
+Portability: none feature.
+@end deftypefn
+
+@cindex day of the year
+@cindex dates, day of the year
+@deftypefn {Function} {} XDATE.JDAY(@var{date})
+Results in the day of the year (as an integer between 1 and 366)
+corresponding to @var{date}.
+@end deftypefn
+
+@cindex day of the month
+@cindex dates, day of the month
+@deftypefn {Function} {} XDATE.MDAY(@var{date})
+Results in the day of the month (as an integer between 1 and 31)
+corresponding to @var{date}.
+@end deftypefn
+
+@cindex minutes
+@cindex dates, in minutes
+@cindex time, in minutes
+@deftypefn {Function} {} XDATE.MINUTE(@var{time-or-date})
+Results in the number of minutes (as an integer between 0 and 59) after
+the last hour in @var{time-or-date}.  (XDATE.MINUTE(@var{x}) is
+equivalent to MOD(TRUNC(@var{x}/60),60)) Applying this function to a
+time is a Portability: none feature.
+@end deftypefn
+
+@cindex months
+@cindex dates, in months
+@deftypefn {Function} {} XDATE.MONTH(@var{date})
+Results in the month of the year (as an integer between 1 and 12)
+corresponding to @var{date}.
+@end deftypefn
+
+@cindex quarters
+@cindex dates, in quarters
+@deftypefn {Function} {} XDATE.QUARTER(@var{date})
+Results in the quarter of the year (as an integer between 1 and 4)
+corresponding to @var{date}.
+@end deftypefn
+
+@cindex seconds
+@cindex dates, in seconds
+@cindex time, in seconds
+@deftypefn {Function} {} XDATE.SECOND(@var{time-or-date})
+Results in the number of whole seconds after the last whole minute (as
+an integer between 0 and 59) in @var{time-or-date}.
+(XDATE.SECOND(@var{x}) is equivalent to MOD(@var{x}, 60).)  Applying
+this function to a time is a Portability: none feature.
+@end deftypefn
+
+@cindex days
+@cindex times, in days
+@deftypefn {Function} {} XDATE.TDAY(@var{time})
+Results in the number of whole days (as an integer) in @var{time}.
+(XDATE.TDAY(@var{x}) is equivalent to TRUNC(@var{x}/86400).)
+@end deftypefn
+
+@cindex time
+@cindex dates, time of day
+@deftypefn {Function} {} XDATE.TIME(@var{date})
+Results in the time of day at the instant corresponding to @var{date},
+in PSPP time format.  This is the number of seconds since
+midnight on the day corresponding to @var{date}.  (XDATE.TIME(@var{x}) is
+equivalent to TRUNC(@var{x}/86400)*86400.)
+@end deftypefn
+
+@cindex week
+@cindex dates, in weeks
+@deftypefn {Function} {} XDATE.WEEK(@var{date})
+Results in the week of the year (as an integer between 1 and 53)
+corresponding to @var{date}.
+@end deftypefn
+
+@cindex day of the week
+@cindex weekday
+@cindex dates, day of the week
+@cindex dates, in weekdays
+@deftypefn {Function} {} XDATE.WKDAY(@var{date})
+Results in the day of week (as an integer between 1 and 7) corresponding
+to @var{date}.  The days of the week are:
+
+@table @asis
+@item 1
+Sunday
+@item 2
+Monday
+@item 3
+Tuesday
+@item 4
+Wednesday
+@item 5
+Thursday
+@item 6
+Friday
+@item 7
+Saturday
+@end table
+@end deftypefn
+
+@cindex years
+@cindex dates, in years
+@deftypefn {Function} {} XDATE.YEAR (@var{date})
+Returns the year (as an integer between 1582 and 19999) corresponding to
+@var{date}.
+@end deftypefn
+
+@node Miscellaneous Functions, Functions Not Implemented, Time & Date, Functions
+@subsection Miscellaneous Functions
+@cindex functions, miscellaneous
+
+Miscellaneous functions take various arguments and produce various
+results.
+
+@cindex cross-case function
+@cindex function, cross-case
+@deftypefn {Function} {} LAG (@var{variable})
+@var{variable} must be a numeric or string variable name.  @code{LAG}
+results in the value of that variable for the case before the current
+one.  In case-selection procedures, @code{LAG} results in the value of
+the variable for the last case selected.  Results in system-missing (for
+numeric variables) or blanks (for string variables) for the first case
+or before any cases are selected.
+@end deftypefn
+
+@deftypefn {Function} {} LAG (@var{variable}, @var{ncases})
+@var{variable} must be a numeric or string variable name.  @var{ncases}
+must be a small positive constant integer, although there is no explicit
+limit.  (Use of a large value for @var{ncases} will increase memory
+consumption, since PSPP must keep @var{ncases} cases in memory.)
+@code{LAG (@var{variable}, @var{ncases}} results in the value of
+@var{variable} that is @var{ncases} before the case currently being
+processed.  See @code{LAG (@var{variable})} above for more details.
+@end deftypefn
+
+@cindex date, Julian
+@cindex Julian date
+@deftypefn {Function} {} YRMODA (@var{year}, @var{month}, @var{day})
+@var{year} is a year between 0 and 199 or 1582 and 19999.  @var{month} is
+a month between 1 and 12.  @var{day} is a day between 1 and 31.  If
+@var{month} or @var{day} is out-of-range, it changes the next higher
+unit.  For instance, a @var{day} of 0 refers to the last day of the
+previous month, and a @var{month} of 13 refers to the first month of the
+next year.  @var{year} must be in range.  If @var{year} is between 0 and
+199, 1900 is added.  @var{year}, @var{month}, and @var{day} must all be
+integers.
+
+@code{YRMODA} results in the number of days between 15 Oct 1582 and
+the date specified, plus one.  The date passed to @code{YRMODA} must be
+on or after 15 Oct 1582.  15 Oct 1582 has a value of 1.
+@end deftypefn
+
+@node Functions Not Implemented,  , Miscellaneous Functions, Functions
+@subsection Functions Not Implemented
+@cindex functions, not implemented
+@cindex not implemented
+@cindex features, not implemented
+
+These functions are not yet implemented and thus not yet documented,
+since it's a hassle.
+
+@findex CDF.xxx
+@findex CDFNORM
+@findex IDF.xxx
+@findex NCDF.xxx
+@findex PROBIT
+@findex RV.xxx
+
+@itemize @bullet
+@item
+@code{CDF.xxx}
+@item
+@code{CDFNORM}
+@item
+@code{IDF.xxx}
+@item
+@code{NCDF.xxx}
+@item
+@code{PROBIT}
+@item
+@code{RV.xxx}
+@end itemize
+
+@node Order of Operations,  , Functions, Expressions
+@section Operator Precedence
+@cindex operator precedence
+@cindex precedence, operator
+@cindex order of operations
+@cindex operations, order of
+
+The following table describes operator precedence.  Smaller-numbered
+levels in the table have higher precedence.  Within a level, operations
+are performed from left to right, except for level 2 (exponentiation),
+where operations are performed from right to left.  If an operator
+appears in the table in two places (@code{-}), the first occurrence is
+unary, the second is binary.
+
+@enumerate
+@item
+@code{(  )}
+@item
+@code{**}
+@item
+@code{-}
+@item
+@code{*  /}
+@item
+@code{+  -}
+@item
+@code{EQ  GE  GT  LE  LT  NE}
+@item
+@code{AND  NOT  OR}
+@end enumerate
+
+@node Data Input and Output, System and Portable Files, Expressions, Top
+@chapter Data Input and Output
+@cindex input
+@cindex output
+@cindex data
+
+Data is the focus of the PSPP language.  This chapter examines
+the PSPP commands for defining variables and reading and writing data.
+
+@quotation
+@strong{Please note:} Data is not actually read until a procedure is
+executed.  These commands tell PSPP how to read data, but they
+do not @emph{cause} PSPP to read data.
+@end quotation
+
+@menu
+* BEGIN DATA::                  Embed data within a syntax file.
+* CLEAR TRANSFORMATIONS::       Clear pending transformations.
+* DATA LIST::                   Fundamental data reading command.
+* END CASE::                    Output the current case.
+* END FILE::                    Terminate the current input program.
+* FILE HANDLE::                 Support for fixed-length records.
+* INPUT PROGRAM::               Support for complex input programs.
+* LIST::                        List cases in the active file.
+* MATRIX DATA::                 Read matrices in text format.
+* NEW FILE::                    Clear the active file and dictionary.
+* PRINT::                       Display values in print formats.
+* PRINT EJECT::                 Eject the current page then print.
+* PRINT SPACE::                 Print blank lines.
+* REREAD::                      Take another look at the previous input line.
+* REPEATING DATA::              Multiple cases on a single line.
+* WRITE::                       Display values in write formats.
+@end menu
+
+@node BEGIN DATA, CLEAR TRANSFORMATIONS, Data Input and Output, Data Input and Output
+@section BEGIN DATA
+@vindex BEGIN DATA
+@vindex END DATA
+@cindex Embedding data in syntax files
+@cindex Data, embedding in syntax files
+
+@display
+BEGIN DATA.
+@dots{}
+END DATA.
+@end display
+
+BEGIN DATA and END DATA can be used to embed raw ASCII data in a PSPP
+syntax file.  DATA LIST or another input procedure must be used before
+BEGIN DATA (@pxref{DATA LIST}).  BEGIN DATA and END DATA must be used
+together.  The END DATA command must appear by itself on a single line,
+with no leading whitespace and exactly one space between the words
+@code{END} and @code{DATA}, followed immediately by the terminal dot,
+like this:
+
+@example
+END DATA.
+@end example
+
+@node CLEAR TRANSFORMATIONS, DATA LIST, BEGIN DATA, Data Input and Output
+@section CLEAR TRANSFORMATIONS
+@vindex CLEAR TRANSFORMATIONS
+
+@display
+CLEAR TRANSFORMATIONS.
+@end display
+
+The CLEAR TRANSFORMATIONS command clears out all pending
+transformations.  It does not cancel the current input program.  It is
+valid only when PSPP is interactive, not in syntax files.
+
+@node DATA LIST, END CASE, CLEAR TRANSFORMATIONS, Data Input and Output
+@section DATA LIST
+@vindex DATA LIST
+@cindex reading data from a file
+@cindex data, reading from a file
+@cindex data, embedding in syntax files
+@cindex embedding data in syntax files
+
+Used to read text or binary data, DATA LIST is the most
+fundamental data-reading command.  Even the more sophisticated input
+methods use DATA LIST commands as a building block.
+Understanding DATA LIST is important to understanding how to use
+PSPP to read your data files.
+
+There are two major variants of DATA LIST, which are fixed
+format and free format.  In addition, free format has a minor variant,
+list format, which is discussed in terms of its differences from vanilla
+free format.
+
+Each form of DATA LIST is described in detail below.
+
+@menu
+* DATA LIST FIXED::             Fixed columnar locations for data.
+* DATA LIST FREE::              Any spacing you like.
+* DATA LIST LIST::              Each case must be on a single line.
+@end menu
+
+@node DATA LIST FIXED, DATA LIST FREE, DATA LIST, DATA LIST
+@subsection DATA LIST FIXED
+@vindex DATA LIST FIXED
+@cindex reading fixed-format data
+@cindex fixed-format data, reading
+@cindex data, fixed-format, reading
+@cindex embedding fixed-format data
+
+@display
+DATA LIST [FIXED]
+        @{TABLE,NOTABLE@}
+        FILE='filename'
+        RECORDS=record_count
+        END=end_var
+        /[line_no] var_spec@dots{}
+
+where each var_spec takes one of the forms
+        var_list start-end [type_spec]
+        var_list (fortran_spec)
+@end display
+
+DATA LIST FIXED is used to read data files that have values at fixed
+positions on each line of single-line or multiline records.  The
+keyword FIXED is optional.
+
+The FILE subcommand must be used if input is to be taken from an
+external file.  It may be used to specify a filename as a string or a
+file handle (@pxref{FILE HANDLE}).  If the FILE subcommand is not used,
+then input is assumed to be specified within the command file using
+BEGIN DATA@dots{}END DATA (@pxref{BEGIN DATA}).
+
+The optional RECORDS subcommand, which takes a single integer as an
+argument, is used to specify the number of lines per record.  If RECORDS
+is not specified, then the number of lines per record is calculated from
+the list of variable specifications later in the DATA LIST command.
+
+The END subcommand is only useful in conjunction with the INPUT PROGRAM
+input procedure, and for that reason it is not discussed here
+(@pxref{INPUT PROGRAM}).
+
+DATA LIST can optionally output a table describing how the data file
+will be read.  The TABLE subcommand enables this output, and NOTABLE
+disables it.  The default is to output the table.
+
+The list of variables to be read from the data list must come last in
+the DATA LIST command.  Each line in the data record is introduced by a
+slash (@samp{/}).  Optionally, a line number may follow the slash.
+Following, any number of variable specifications may be present.
+
+Each variable specification consists of a list of variable names
+followed by a description of their location on the input line.  Sets of
+variables may specified using DATA LIST's TO convention (@pxref{Sets of
+Variables}).  There are two ways to specify the location of the variable
+on the line: SPSS style and FORTRAN style.
+
+With SPSS style, the starting column and ending column for the field
+are specified after the variable name, separated by a dash (@samp{-}).
+For instance, the third through fifth columns on a line would be
+specified @samp{3-5}.  By default, variables are considered to be in
+@samp{F} format (@pxref{Input/Output Formats}).  (This default can be
+changed; see @ref{SET} for more information.)
+
+When using SPSS style, to use a variable format other than the default,
+specify the format type in parentheses after the column numbers.  For
+instance, for alphanumeric @samp{A} format, use @samp{(A)}.  
+
+In addition, implied decimal places can be specified in parentheses
+after the column numbers.  As an example, suppose that a data file has a
+field in which the characters @samp{1234} should be interpreted as
+having the value 12.34.  Then this field has two implied decimal places,
+and the corresponding specification would be @samp{(2)}.  If a field
+that has implied decimal places contains a decimal point, then the
+implied decimal places are not applied.
+
+Changing the variable format and adding implied decimal places can be
+done together; for instance, @samp{(N,5)}.
+
+When using SPSS style, the input and output width of each variable is
+computed from the field width.  The field width must be evenly divisible
+into the number of variables specified.
+
+FORTRAN style is an altogether different approach to specifying field
+locations.  With this approach, a list of variable input format
+specifications, separated by commas, are placed after the variable names
+inside parentheses.  Each format specifier advances as many characters
+into the input line as it uses.
+
+In addition to the standard format specifiers (@pxref{Input/Output
+Formats}), FORTRAN style defines some extensions:
+
+@table @asis
+@item @code{X}
+Advance the current column on this line by one character position.
+
+@item @code{T}@var{x}
+Set the current column on this line to column @var{x}, with column
+numbers considered to begin with 1 at the left margin.
+
+@item @code{NEWREC}@var{x}
+Skip forward @var{x} lines in the current record, resetting the active
+column to the left margin.
+
+@item Repeat count
+Any format specifier may be preceded by a number.  This causes the
+action of that format specifier to be repeated the specified number of
+times.
+
+@item (@var{spec1}, @dots{}, @var{specN})
+Group the given specifiers together.  This is most useful when preceded
+by a repeat count.  Groups may be nested arbitrarily.
+@end table
+
+FORTRAN and SPSS styles may be freely intermixed.  SPSS style leaves the
+active column immediately after the ending column specified.  Record
+motion using @code{NEWREC} in FORTRAN style also applies to later
+FORTRAN and SPSS specifiers.
+@menu
+* DATA LIST FIXED Examples::    Examples of DATA LIST FIXED.
+@end menu
+
+@node DATA LIST FIXED Examples,  , DATA LIST FIXED, DATA LIST FIXED
+@unnumberedsubsubsec Examples
+
+@enumerate
+@item
+@example
+DATA LIST TABLE /NAME 1-10 (A) INFO1 TO INFO3 12-17 (1).
+
+BEGIN DATA.
+John Smith 102311
+Bob Arnold 122015
+Bill Yates  918 6
+END DATA.
+@end example
+
+Defines the following variables:
+
+@itemize @bullet
+@item
+@code{NAME}, a 10-character-wide long string variable, in columns 1
+through 10.
+
+@item
+@code{INFO1}, a numeric variable, in columns 12 through 13.
+
+@item
+@code{INFO2}, a numeric variable, in columns 14 through 15.
+
+@item
+@code{INFO3}, a numeric variable, in columns 16 through 17.
+@end itemize
+
+The @code{BEGIN DATA}/@code{END DATA} commands cause three cases to be
+defined:
+
+@example
+Case   NAME         INFO1   INFO2   INFO3
+   1   John Smith     10      23      11
+   2   Bob Arnold     12      20      15
+   3   Bill Yates      9      18       6
+@end example
+
+The @code{TABLE} keyword causes PSPP to print out a table
+describing the four variables defined.
+
+@item
+@example
+DAT LIS FIL="survey.dat"
+        /ID 1-5 NAME 7-36 (A) SURNAME 38-67 (A) MINITIAL 69 (A)
+        /Q01 TO Q50 7-56
+        /.
+@end example
+
+Defines the following variables:
+
+@itemize @bullet
+@item
+@code{ID}, a numeric variable, in columns 1-5 of the first record.
+
+@item
+@code{NAME}, a 30-character long string variable, in columns 7-36 of the
+first record.
+
+@item
+@code{SURNAME}, a 30-character long string variable, in columns 38-67 of
+the first record.
+
+@item
+@code{MINITIAL}, a 1-character short string variable, in column 69 of
+the first record.
+
+@item
+Fifty variables @code{Q01}, @code{Q02}, @code{Q03}, @dots{}, @code{Q49},
+@code{Q50}, all numeric, @code{Q01} in column 7, @code{Q02} in column 8,
+@dots{}, @code{Q49} in column 55, @code{Q50} in column 56, all in the second
+record.
+@end itemize
+
+Cases are separated by a blank record.
+
+Data is read from file @file{survey.dat} in the current directory.
+
+This example shows keywords abbreviated to their first 3 letters.
+
+@end enumerate
+
+@node DATA LIST FREE, DATA LIST LIST, DATA LIST FIXED, DATA LIST
+@subsection DATA LIST FREE
+@vindex DATA LIST FREE
+
+@display
+DATA LIST FREE
+        [@{NOTABLE,TABLE@}]
+        FILE='filename'
+        END=end_var
+        /var_spec@dots{}
+
+where each var_spec takes one of the forms
+        var_list [(type_spec)]
+        var_list *
+@end display
+
+In free format, the input data is structured as a series of comma- or
+whitespace-delimited fields (end of line is one form of whitespace; it
+is not treated specially).  Field contents may be surrounded by matched
+pairs of apostrophes (@samp{'}) or quotes (@samp{"}), or they may be
+unenclosed.  For any type of field leading white space (up to the
+apostrophe or quote, if any) is not included in the field.
+
+Multiple consecutive delimiters are equivalent to a single delimiter.
+To specify an empty field, write an empty set of single or double
+quotes; for instance, @samp{""}.
+
+The NOTABLE and TABLE subcommands are as in DATA LIST FIXED above.
+NOTABLE is the default.
+
+The FILE and END subcommands are as in DATA LIST FIXED above.
+
+The variables to be parsed are given as a single list of variable names.
+This list must be introduced by a single slash (@samp{/}).  The set of
+variable names may contain format specifications in parentheses
+(@pxref{Input/Output Formats}).  Format specifications apply to all
+variables back to the previous parenthesized format specification.  
+
+In addition, an asterisk may be used to indicate that all variables
+preceding it are to have input/output format @samp{F8.0}.
+
+Specified field widths are ignored on input, although all normal limits
+on field width apply, but they are honored on output.
+
+@node DATA LIST LIST,  , DATA LIST FREE, DATA LIST
+@subsection DATA LIST LIST
+@vindex DATA LIST LIST
+
+@display
+DATA LIST LIST
+        [@{NOTABLE,TABLE@}]
+        FILE='filename'
+        END=end_var
+        /var_spec@dots{}
+
+where each var_spec takes one of the forms
+        var_list [(type_spec)]
+        var_list *
+@end display
+
+Syntactically and semantically, DATA LIST LIST is equivalent to DATA
+LIST FREE, with one exception: each input line is expected to correspond
+to exactly one input record.  If more or fewer fields are found on an
+input line than expected, an appropriate diagnostic is issued.
+
+@node END CASE, END FILE, DATA LIST, Data Input and Output
+@section END CASE
+@vindex END CASE
+
+@display
+END CASE.
+@end display
+
+END CASE is used within INPUT PROGRAM to output the current case.
+@xref{INPUT PROGRAM}.
+
+@node END FILE, FILE HANDLE, END CASE, Data Input and Output
+@section END FILE
+@vindex END FILE
+
+@display
+END FILE.
+@end display
+
+END FILE is used within INPUT PROGRAM to terminate the current input
+program.  @xref{INPUT PROGRAM}.
+
+@node FILE HANDLE, INPUT PROGRAM, END FILE, Data Input and Output
+@section FILE HANDLE
+@vindex FILE HANDLE
+
+@display
+FILE HANDLE handle_name
+        /NAME='filename'
+        /RECFORM=@{VARIABLE,FIXED,SPANNED@}
+        /LRECL=rec_len
+        /MODE=@{CHARACTER,IMAGE,BINARY,MULTIPUNCH,360@}
+@end display
+
+Use the FILE HANDLE command to define the attributes of a file that does
+not use conventional variable-length records terminated by newline
+characters.
+
+Specify the file handle name as an identifier.  Any given identifier may
+only appear once in a PSPP run.  File handles may not be reassigned to a
+different file.  The file handle name must immediately follow the FILE
+HANDLE command name.
+
+The NAME subcommand specifies the name of the file associated with the
+handle.  It is the only required subcommand.
+
+The RECFORM subcommand specifies how the file is laid out.  VARIABLE
+specifies variable-length lines terminated with newlines, and it is the
+default.  FIXED specifies fixed-length records.  SPANNED is not
+supported.
+
+LRECL specifies the length of fixed-length records.  It is required if
+@code{/RECFORM FIXED} is specified.  
+
+MODE specifies a file mode.  CHARACTER, the default, causes the data
+file to be opened in ANSI C text mode.  BINARY causes the data file to
+be opened in ANSI C binary mode.  The other possibilities are not
+supported.
+
+@node INPUT PROGRAM, LIST, FILE HANDLE, Data Input and Output
+@section INPUT PROGRAM
+@vindex INPUT PROGRAM
+
+@display
+INPUT PROGRAM.
+@dots{} input commands @dots{}
+END INPUT PROGRAM.
+@end display
+
+The INPUT PROGRAM@dots{}END INPUT PROGRAM construct is used to specify a
+complex input program.  By placing data input commands within INPUT
+PROGRAM, PSPP programs can take advantage of more complex file
+structures than available by using DATA LIST by itself.
+
+The first sort of extended input program is to simply put multiple DATA
+LIST commands within the INPUT PROGRAM.  This will cause all of the data
+files to be read in parallel.  Input will stop when end of file is
+reached on any of the data files.
+
+Transformations, such as conditional and looping constructs, can also be
+included within an INPUT PROGRAM.  These can be used to combine input
+from several data files in more complex ways.  However, input will still
+stop when end of file is reached on any of the data files.
+
+To prevent INPUT PROGRAM from terminating at the first end of file, use
+the END subcommand on DATA LIST.  This subcommand takes a variable name,
+which should be a numeric scratch variable (@pxref{Scratch Variables}).
+(It need not be a scratch variable but otherwise the results can be
+surprising.)  The value of this variable is set to 0 when reading the
+data file, or 1 when end of file is encountered.
+
+Some additional commands are useful in conjunction with INPUT PROGRAM.
+END CASE is the first one.  Normally each loop through the INPUT PROGRAM
+structure produces one case.  But with END CASE you can control exactly
+when cases are output.  When END CASE is used, looping from the end of
+INPUT PROGRAM to the beginning does not cause a case to be output.
+
+END FILE is the other command.  When the END subcommand is used on DATA
+LIST, there is no way for the INPUT PROGRAM construct to stop looping,
+so an infinite loop results.  The END FILE command, when executed,
+stops the flow of input data and passes out of the INPUT PROGRAM
+structure.
+
+All this is very confusing.  A few examples should help to clarify.
+
+@example
+INPUT PROGRAM.
+        DATA LIST NOTABLE FILE='a.data'/X 1-10.
+        DATA LIST NOTABLE FILE='b.data'/Y 1-10.
+END INPUT PROGRAM.
+LIST.
+@end example
+
+The example above reads variable X from file @file{a.data} and variable
+Y from file @file{b.data}.  If one file is shorter than the other then
+the extra data in the longer file is ignored.
+
+@example
+INPUT PROGRAM.
+        NUMERIC #A #B.
+        
+        DO IF NOT #A.
+                DATA LIST NOTABLE END=#A FILE='a.data'/X 1-10.
+        END IF.
+        DO IF NOT #B.
+                DATA LIST NOTABLE END=#B FILE='b.data'/Y 1-10.
+        END IF.
+        DO IF #A AND #B.
+                END FILE.
+        END IF.
+        END CASE.
+END INPUT PROGRAM.
+LIST.
+@end example
+
+This example reads variable X from @file{a.data} and variable Y from
+@file{b.data}.  If one file is shorter than the other then the missing
+field is set to the system-missing value alongside the present value for
+the remaining length of the longer file.
+
+@example
+INPUT PROGRAM.
+        NUMERIC #A #B.
+
+        DO IF #A.
+                DATA LIST NOTABLE END=#B FILE='b.data'/X 1-10.
+                DO IF #B.
+                        END FILE.
+                ELSE.
+                        END CASE.
+                END IF.
+        ELSE.
+                DATA LIST NOTABLE END=#A FILE='a.data'/X 1-10.
+                DO IF NOT #A.
+                        END CASE.
+                END IF.
+        END IF.
+END INPUT PROGRAM.
+LIST.
+@end example
+
+The above example reads data from file @file{a.data}, then from
+@file{b.data}, and concatenates them into a single active file.
+
+@example
+INPUT PROGRAM.
+        NUMERIC #EOF.
+
+        LOOP IF NOT #EOF.
+                DATA LIST NOTABLE END=#EOF FILE='a.data'/X 1-10.
+                DO IF NOT #EOF.
+                        END CASE.
+                END IF.
+        END LOOP.
+
+        COMPUTE #EOF = 0.
+        LOOP IF NOT #EOF.
+                DATA LIST NOTABLE END=#EOF FILE='b.data'/X 1-10.
+                DO IF NOT #EOF.
+                        END CASE.
+                END IF.
+        END LOOP.
+
+        END FILE.
+END INPUT PROGRAM.
+LIST.
+@end example
+
+The above example does the same thing as the previous example, in a
+different way.
+
+@example
+INPUT PROGRAM.
+        LOOP #I=1 TO 50.
+                COMPUTE X=UNIFORM(10).
+                END CASE.
+        END LOOP.
+        END FILE.
+END INPUT PROGRAM.
+LIST/FORMAT=NUMBERED.
+@end example
+
+The above example causes an active file to be created consisting of 50
+random variates between 0 and 10.
+
+@node LIST, MATRIX DATA, INPUT PROGRAM, Data Input and Output
+@section LIST
+@vindex LIST
+
+@display
+LIST
+        /VARIABLES=var_list
+        /CASES=FROM start_index TO end_index BY incr_index
+        /FORMAT=@{UNNUMBERED,NUMBERED@} @{WRAP,SINGLE@} 
+                @{NOWEIGHT,WEIGHT@}
+@end display
+
+The LIST procedure prints the values of specified variables to the
+listing file.
+
+The VARIABLES subcommand specifies the variables whose values are to be
+printed.  Keyword VARIABLES is optional.  If VARIABLES subcommand is not
+specified then all variables in the active file are printed.
+
+The CASES subcommand can be used to specify a subset of cases to be
+printed.  Specify FROM and the case number of the first case to print,
+TO and the case number of the last case to print, and BY and the number
+of cases to advance between printing cases, or any subset of those
+settings.  If CASES is not specified then all cases are printed.
+
+The FORMAT subcommand can be used to change the output format.  NUMBERED
+will print case numbers along with each case; UNNUMBERED, the default,
+causes the case numbers to be omitted.  The WRAP and SINGLE settings are
+currently not used.  WEIGHT will cause case weights to be printed along
+with variable values; NOWEIGHT, the default, causes case weights to be
+omitted from the output.
+
+Case numbers start from 1.  They are counted after all transformations
+have been considered.
+
+LIST will attempt to fit all the values on a single line.  If necessary,
+variable names will be display vertically in order to fit.  If values
+cannot fit on a single line, then a multi-line format will be used.
+
+LIST is a procedure.  It causes the data to be read.
+
+@node MATRIX DATA, NEW FILE, LIST, Data Input and Output
+@section MATRIX DATA
+@vindex MATRIX DATA
+
+@display
+MATRIX DATA
+        /VARIABLES=var_list
+        /FILE='filename'
+        /FORMAT=@{LIST,FREE@} @{LOWER,UPPER,FULL@} @{DIAGONAL,NODIAGONAL@}
+        /SPLIT=@{new_var,var_list@}
+        /FACTORS=var_list
+        /CELLS=n_cells
+        /N=n
+        /CONTENTS=@{N_VECTOR,N_SCALAR,N_MATRIX,MEAN,STDDEV,COUNT,MSE,
+                   DFE,MAT,COV,CORR,PROX@}
+@end display
+
+The MATRIX DATA command reads square matrices in one of several textual
+formats.  MATRIX DATA clears the dictionary and replaces it and reads a
+data file.
+
+Use VARIABLES to specify the variables that form the rows and columns of
+the matrices.  You may not specify a variable named VARNAME_.  You
+should specify VARIABLES first.
+
+Specify the file to read on FILE, either as a file name string or a file
+handle (@pxref{FILE HANDLE}).  If FILE is not specified then matrix data
+must immediately follow MATRIX DATA with a BEGIN DATA@dots{}END DATA
+construct (@pxref{BEGIN DATA}).
+
+The FORMAT subcommand specifies how the matrices are formatted.  LIST,
+the default, indicates that there is one line per row of matrix data;
+FREE allows single matrix rows to be broken across multiple lines.  This
+is analogous to the difference between DATA LIST FREE and DATA LIST LIST
+(@pxref{DATA LIST}).  LOWER, the default, indicates that the lower
+triangle of the matrix is given; UPPER indicates the upper triangle; and
+FULL indicates that the entire matrix is given.  DIAGONAL, the default,
+indicates that the diagonal is part of the data; NODIAGONAL indicates
+that it is omitted.  DIAGONAL/NODIAGONAL have no effect when FULL is
+specified.
+
+The SPLIT subcommand is used to specify SPLIT FILE variables for the
+input matrices (@pxref{SPLIT FILE}).  Specify either a single variable
+not specified on VARIABLES, or one or more variables that are specified
+on VARIABLES.  In the former case, the SPLIT values are not present in
+the data and ROWTYPE_ may not be specified on VARIABLES.  In the latter
+case, the SPLIT values are present in the data.
+
+Specify a list of factor variables on FACTORS.  Factor variables must
+also be listed on VARIABLES.  Factor variables are used when there are
+some variables where, for each possible combination of their values,
+statistics on the matrix variables are included in the data.
+
+If FACTORS is specified and ROWTYPE_ is not specified on VARIABLES, the
+CELLS subcommand is required.  Specify the number of factor variable
+combinations that are given.  For instance, if factor variable A has 2
+values and factor variable B has 3 values, specify 6.
+
+The N subcommand specifies a population number of observations.  When N
+is specified, one N record is output for each SPLIT FILE.
+
+Use CONTENTS to specify what sort of information the matrices include.
+Each possible option is described in more detail below.  When ROWTYPE_
+is specified on VARIABLES, CONTENTS is optional; otherwise, if CONTENTS
+is not specified then /CONTENTS=CORR is assumed.
+
+@table @asis
+@item N
+@item N_VECTOR
+Number of observations as a vector, one value for each variable.
+@item N_SCALAR
+Number of observations as a single value.
+@item N_MATRIX
+Matrix of counts.
+@item MEAN
+Vector of means.
+@item STDDEV
+Vector of standard deviations.
+@item COUNT
+Vector of counts.
+@item MSE
+Vector of mean squared errors.
+@item DFE
+Vector of degrees of freedom.
+@item MAT
+Generic matrix.
+@item COV
+Covariance matrix.
+@item CORR
+Correlation matrix.
+@item PROX
+Proximities matrix.
+@end table
+
+The exact semantics of the matrices read by MATRIX DATA are complex.
+Right now MATRIX DATA isn't too useful due to a lack of procedures
+accepting or producing related data, so these semantics aren't
+documented.  Later, they'll be described here in detail.
+
+@node NEW FILE, PRINT, MATRIX DATA, Data Input and Output
+@section NEW FILE
+@vindex NEW FILE
+
+@display
+NEW FILE.
+@end display
+
+The NEW FILE command clears the current active file.
+
+@node PRINT, PRINT EJECT, NEW FILE, Data Input and Output
+@section PRINT
+@vindex PRINT
+
+@display
+PRINT 
+        OUTFILE='filename'
+        RECORDS=n_lines
+        @{NOTABLE,TABLE@}
+        /[line_no] arg@dots{}
+
+arg takes one of the following forms:
+        'string' [start-end]
+        var_list start-end [type_spec]
+        var_list (fortran_spec)
+        var_list *
+@end display
+
+The PRINT transformation writes variable data to an output file.  PRINT
+is executed when a procedure causes the data to be read.  In order to
+execute the PRINT transformation without invoking a procedure, use the
+EXECUTE command (@pxref{EXECUTE}).
+
+All PRINT subcommands are optional.
+
+The OUTFILE subcommand specifies the file to receive the output.  The
+file may be a file name as a string or a file handle (@pxref{FILE
+HANDLE}).  If OUTFILE is not present then output will be sent to PSPP's
+output listing file.
+
+The RECORDS subcommand specifies the number of lines to be output.  The
+number of lines may optionally be surrounded by parentheses.
+
+TABLE will cause the PRINT command to output a table to the listing file
+that describes what it will print to the output file.  NOTABLE, the
+default, suppresses this output table.
+
+Introduce the strings and variables to be printed with a slash
+(@samp{/}).  Optionally, the slash may be followed by a number
+indicating which output line will be specified.  In the absence of this
+line number, the next line number will be specified.  Multiple lines may
+be specified using multiple slashes with the intended output for a line
+following its respective slash.
+
+Literal strings may be printed.  Specify the string itself.  Optionally
+the string may be followed by a column number or range of column
+numbers, specifying the location on the line for the string to be
+printed.  Otherwise, the string will be printed at the current position
+on the line.
+
+Variables to be printed can be specified in the same ways as available
+for DATA LIST FIXED (@pxref{DATA LIST FIXED}).  In addition, a variable
+list may be followed by an asterisk (@samp{*}), which indicates that the
+variables should be printed in their dictionary print formats, separated
+by spaces.  A variable list followed by a slash or the end of command
+will be interpreted the same way.
+
+If a FORTRAN type specification is used to move backwards on the current
+line, then text is written at that point on the line, the line will be
+truncated to that length, although additional text being added will
+again extend the line to that length.
+
+@node PRINT EJECT, PRINT SPACE, PRINT, Data Input and Output
+@section PRINT EJECT
+@vindex PRINT EJECT
+
+@display
+PRINT EJECT 
+        OUTFILE='filename'
+        RECORDS=n_lines
+        @{NOTABLE,TABLE@}
+        /[line_no] arg@dots{}
+
+arg takes one of the following forms:
+        'string' [start-end]
+        var_list start-end [type_spec]
+        var_list (fortran_spec)
+        var_list *
+@end display
+
+PRINT EJECT is used to write data to an output file.  Before the data is
+written, the current page in the listing file is ejected.
+
+@xref{PRINT}, for more information on syntax and usage.
+
+@node PRINT SPACE, REREAD, PRINT EJECT, Data Input and Output
+@section PRINT SPACE
+@vindex PRINT SPACE
+
+@display
+PRINT SPACE OUTFILE='filename' n_lines.
+@end display
+
+The PRINT SPACE prints one or more blank lines to an output file.
+
+The OUTFILE subcommand is optional.  It may be used to direct output to
+a file specified by file name as a string or file handle (@pxref{FILE
+HANDLE}).  If OUTFILE is not specified then output will be directed to
+the listing file.
+
+n_lines is also optional.  If present, it is an expression
+(@pxref{Expressions}) specifying the number of blank lines to be
+printed.  The expression must evaluate to a nonnegative value.
+
+@node REREAD, REPEATING DATA, PRINT SPACE, Data Input and Output
+@section REREAD
+@vindex REREAD
+
+@display
+REREAD FILE=handle COLUMN=column.
+@end display
+
+The REREAD transformation allows the previous input line in a data file
+already processed by DATA LIST or another input command to be re-read
+for further processing.
+
+The FILE subcommand, which is optional, is used to specify the file to
+have its line re-read.  The file must be specified in the form of a file
+handle (@pxref{FILE HANDLE}).  If FILE is not specified then the last
+file specified on DATA LIST will be assumed (last file specified
+lexically, not in terms of flow-of-control).
+
+By default, the line re-read is re-read in its entirety.  With the
+COLUMN subcommand, a prefix of the line can be exempted from
+re-reading.  Specify an expression (@pxref{Expressions}) evaluating to
+the first column that should be included in the re-read line.  Columns
+are numbered from 1 at the left margin.
+
+Multiple REREAD commands will not back up in the data file.  Instead,
+they will re-read the same line multiple times.
+
+@node REPEATING DATA, WRITE, REREAD, Data Input and Output
+@section REPEATING DATA
+@vindex REPEATING DATA
+
+@display
+REPEATING DATA
+        /STARTS=start-end
+        /OCCURS=n_occurs
+        /FILE='filename'
+        /LENGTH=length
+        /CONTINUED[=cont_start-cont_end]
+        /ID=id_start-id_end=id_var
+        /@{TABLE,NOTABLE@}
+        /DATA=var_spec@dots{}
+
+where each var_spec takes one of the forms
+        var_list start-end [type_spec]
+        var_list (fortran_spec)
+@end display
+
+The REPEATING DATA command is used to parse groups of data repeating in
+a uniform format, possibly with several groups on a single line.  Each
+group of data corresponds with one case.  REPEATING DATA may only be
+used within an INPUT PROGRAM structure.  When used with DATA LIST, it
+can be used to parse groups of cases that share a subset of variables
+but differ in their other data.
+
+The STARTS subcommand is required.  Specify a range of columns, using
+literal numbers or numeric variable names.  This range specifies the
+columns on the first line that are used to contain groups of data.  The
+ending column is optional.  If it is not specified, then the record
+width of the input file is used.  For the inline file (@pxref{BEGIN
+DATA}) this is 80 columns; for a file with fixed record widths it is the
+record width; for other files it is 1024 characters by default.
+
+The OCCURS subcommand is required.  It must be a number or the name of a
+numeric variable.  Its value is the number of groups present in the
+current record.
+
+The DATA subcommand is required.  It must be the last subcommand
+specified.  It is used to specify the data present within each repeating
+group.  Column numbers are specified relative to the beginning of a
+group at column 1.  Data is specified in the same way as with DATA LIST
+FIXED (@pxref{DATA LIST FIXED}).
+
+All other subcommands are optional.
+
+FILE specifies the file to read, either a file name as a string or a
+file handle (@pxref{FILE HANDLE}).  If FILE is not present then the
+default is the last file handle used on DATA LIST (lexically, not in
+terms of flow of control).
+
+By default REPEATING DATA will output a table describing how it will
+parse the input data.  Specifying NOTABLE will disable this behavior;
+specifying TABLE will explicitly enable it.
+
+The LENGTH subcommand specifies the length in characters of each group.
+If it is not present then length is inferred from the DATA subcommand.
+LENGTH can be a number or a variable name.
+
+Normally all the data groups are expected to be present on a single
+line.  Use the CONTINUED command to indicate that data can be continued
+onto additional lines.  If data on continuation lines starts at the left
+margin and continues through the entire field width, no column
+specifications are necessary on CONTINUED.  Otherwise, specify the
+possible range of columns in the same way as on STARTS.
+
+When data groups are continued from line to line, it's easily possible
+for cases to get out of sync if hand editing is not done carefully.  The
+ID subcommand allows a case identifier to be present on each line of
+repeating data groups.  REPEATING DATA will check for the same
+identifier on each line and report mismatches.  Specify the range of
+columns that the identifier will occupy, followed by an equals sign
+(@samp{=}) and the identifier variable name.  The variable must already
+have been declared with NUMERIC or another command.
+
+@node WRITE,  , REPEATING DATA, Data Input and Output
+@section WRITE
+@vindex WRITE
+
+@display
+WRITE 
+        OUTFILE='filename'
+        RECORDS=n_lines
+        @{NOTABLE,TABLE@}
+        /[line_no] arg@dots{}
+
+arg takes one of the following forms:
+        'string' [start-end]
+        var_list start-end [type_spec]
+        var_list (fortran_spec)
+        var_list *
+@end display
+
+WRITE is used to write text or binary data to an output file.  
+
+@xref{PRINT}, for more information on syntax and usage.  The main
+difference between PRINT and WRITE is that whereas by default PRINT uses
+variables' print formats, WRITE uses write formats.
+
+The sole additional difference is that if WRITE is used to send output
+to a binary file, carriage control characters will not be output.
+@xref{FILE HANDLE}, for information on how to declare a file as binary.
+
+@node System and Portable Files, Variable Attributes, Data Input and Output, Top
+@chapter System Files and Portable Files
+
+The commands in this chapter read, write, and examine system files and
+portable files.
+
+@menu
+* APPLY DICTIONARY::            Apply system file dictionary to active file.
+* EXPORT::                      Write to a portable file.
+* GET::                         Read from a system file.
+* IMPORT::                      Read from a portable file.
+* MATCH FILES::                 Merge system files.
+* SAVE::                        Write to a system file.
+* SYSFILE INFO::                Display system file dictionary.
+* XSAVE::                       Write to a system file, as a transform.
+@end menu
+
+@node APPLY DICTIONARY, EXPORT, System and Portable Files, System and Portable Files
+@section APPLY DICTIONARY
+@vindex APPLY DICTIONARY
+
+@display
+APPLY DICTIONARY FROM='filename'.
+@end display
+
+The APPLY DICTIONARY command applies the variable labels, value labels,
+and missing values from variables in a system file to corresponding
+variables in the active file.  In some cases it also updates the
+weighting variable.
+
+Specify a system file with a file name string or as a file handle
+(@pxref{FILE HANDLE}).  The dictionary in the system file will be read,
+but it will not replace the active file dictionary.  The system file's
+data will not be read.
+
+Only variables with names that exist in both the active file and the
+system file are considered.  Variables with the same name but different
+types (numeric, string) will cause an error message.  Otherwise, the
+system file variables' attributes will replace those in their matching
+active file variables, as described below.
+
+If a system file variable has a variable label, then it will replace the
+active file variable's variable label.  If the system file variable does
+not have a variable label, then the active file variable's variable
+label, if any, will be retained.
+
+If the active file variable is numeric or short string, then value
+labels and missing values, if any, will be copied to the active file
+variable.  If the system file variable does not have value labels or
+missing values, then those in the active file variable, if any, will not
+be disturbed.
+
+Finally, weighting of the active file is updated (@pxref{WEIGHT}).  If
+the active file has a weighting variable, and the system file does not,
+or if the weighting variable in the system file does not exist in the
+active file, then the active file weighting variable, if any, is
+retained.  Otherwise, the weighting variable in the system file becomes
+the active file weighting variable.
+
+APPLY DICTIONARY takes effect immediately.  It does not read the active
+file.  The system file is not modified.
+
+@node EXPORT, GET, APPLY DICTIONARY, System and Portable Files
+@section EXPORT
+@vindex EXPORT
+
+@display
+EXPORT
+        /OUTFILE='filename'
+        /DROP=var_list
+        /KEEP=var_list
+        /RENAME=(src_names=target_names)@dots{}
+@end display
+
+The EXPORT procedure writes the active file dictionary and data to a
+specified portable file.
+
+The OUTFILE subcommand, which is the only required subcommand, specifies
+the portable file to be written as a file name string or a file handle
+(@pxref{FILE HANDLE}).
+
+DROP, KEEP, and RENAME follow the same format as the SAVE procedure
+(@pxref{SAVE}).
+
+EXPORT is a procedure.  It causes the active file to be read.
+
+@node GET, IMPORT, EXPORT, System and Portable Files
+@section GET
+@vindex GET
+
+@display
+GET
+        /FILE='filename'
+        /DROP=var_list
+        /KEEP=var_list
+        /RENAME=(src_names=target_names)@dots{}
+@end display
+
+The GET transformation clears the current dictionary and active file and
+replaces them with the dictionary and data from a specified system file.
+
+The FILE subcommand is the only required subcommand.  Specify the system
+file to be read as a string file name or a file handle (@pxref{FILE
+HANDLE}).
+
+By default, all the variables in a system file are read.  The DROP
+subcommand can be used to specify a list of variables that are not to be
+read.  By contrast, the KEEP subcommand can be used to specify variable
+that are to be read, with all other variables not read.
+
+Normally variables in a system file retain the names that they were
+saved under.  Use the RENAME subcommand to change these names.  Specify,
+within parentheses, a list of variable names followed by an equals sign
+(@samp{=}) and the names that they should be renamed to.  Multiple
+parenthesized groups of variable names can be included on a single
+RENAME subcommand.  Variables' names may be swapped using a RENAME
+subcommand of the form @samp{/RENAME=(A B=B A)}.
+
+Alternate syntax for the RENAME subcommand allows the parentheses to be
+eliminated.  When this is done, only a single variable may be renamed at
+once.  For instance, @samp{/RENAME=A=B}.  This alternate syntax is
+deprecated.
+
+DROP, KEEP, and RENAME are performed in left-to-right order.  They each
+may be present any number of times.
+
+Please note that DROP, KEEP, and RENAME do not cause the system file on
+disk to be modified.  Only the active file read from the system file is
+changed.
+
+GET does not cause the data to be read, only the dictionary.  The data
+is read later, when a procedure is executed.
+
+@node IMPORT, MATCH FILES, GET, System and Portable Files
+@section IMPORT
+@vindex IMPORT
+
+@display
+IMPORT
+        /FILE='filename'
+        /TYPE=@{COMM,TAPE@}
+        /DROP=var_list
+        /KEEP=var_list
+        /RENAME=(src_names=target_names)@dots{}
+@end display
+
+The IMPORT transformation clears the active file dictionary and data and
+replaces them with a dictionary and data from a portable file on disk.
+
+The FILE subcommand, which is the only required subcommand, specifies
+the portable file to be read as a file name string or a file handle
+(@pxref{FILE HANDLE}).
+
+The TYPE subcommand is currently not used.
+
+DROP, KEEP, and RENAME follow the syntax used by GET (@pxref{GET}).
+
+IMPORT does not cause the data to be read, only the dictionary.  The
+data is read later, when a procedure is executed.
+
+@node MATCH FILES, SAVE, IMPORT, System and Portable Files
+@section MATCH FILES
+@vindex MATCH FILES
+
+@display
+MATCH FILES
+        /BY var_list
+        /@{FILE,TABLE@}=@{*,'filename'@}
+        /DROP=var_list
+        /KEEP=var_list
+        /RENAME=(src_names=target_names)@dots{}
+        /IN=var_name
+        /FIRST=var_name
+        /LAST=var_name
+        /MAP
+@end display
+
+The MATCH FILES command merges one or more system files, optionally
+including the active file.  Records with the same values for BY
+variables are combined into a single record.  Records with different
+values are output in order.  Thus, multiple sorted system files are
+combined into a single sorted system file based on the value of the BY
+variables.
+
+The BY subcommand specifies a list of variables that are used to match
+records from each of the system files.  Variables specified must exist
+in all the files specified on FILE and TABLE.  BY should usually be
+specified.  If TABLE is used then BY is required.
+
+Specify FILE with a system file as a file name string or file handle
+(@pxref{FILE HANDLE}).  An asterisk (@samp{*}) may also be specified to
+indicate the current active file.  The files specified on FILE are
+merged together based on the BY variables, or combined case-by-case if
+BY is not specified.  Normally at least two FILE subcommands should be
+specified.
+
+Specify TABLE with a system file in order to use it as a @dfn{table
+lookup file}.  Records in table lookup files are not used up after
+they've been used once.  This means that data in table lookup files can
+correspond to any number of records in FILE files.  Table lookup files
+correspond to lookup tables in traditional relational database systems.
+It is incorrect to have records with duplicate BY values in table lookup
+files.
+
+Any number of FILE and TABLE subcommands may be specified.  Each
+instance of FILE or TABLE can be followed by DROP, KEEP, and/or RENAME
+subcommands.  These take the same form as the corresponding subcommands
+of GET (@pxref{GET}), and perform the same functions.
+
+Variables belonging to files that are not present for the current case
+are set to the system-missing value for numeric variables or spaces for
+string variables.
+
+IN, FIRST, LAST, and MAP are currently not used.
+
+@node SAVE, SYSFILE INFO, MATCH FILES, System and Portable Files
+@section SAVE
+@vindex SAVE
+
+@display
+SAVE
+        /OUTFILE='filename'
+        /@{COMPRESSED,UNCOMPRESSED@}
+        /DROP=var_list
+        /KEEP=var_list
+        /RENAME=(src_names=target_names)@dots{}
+@end display
+
+The SAVE procedure causes the dictionary and data in the active file to
+be written to a system file.
+
+The FILE subcommand is the only required subcommand.  Specify the system
+file to be written as a string file name or a file handle (@pxref{FILE
+HANDLE}).
+
+The COMPRESS and UNCOMPRESS subcommand determine whether the saved
+system file is compressed.  By default, system files are compressed.
+This default can be changed with the SET command (@pxref{SET}).
+
+By default, all the variables in the active file dictionary are written
+to the system file.  The DROP subcommand can be used to specify a list
+of variables not to be written.  In contrast, KEEP specifies variables
+to be written, with all variables not specified not written.
+
+Normally variables are saved to a system file under the same names they
+have in the active file.  Use the RENAME command to change these names.
+Specify, within parentheses, a list of variable names followed by an
+equals sign (@samp{=}) and the names that they should be renamed to.
+Multiple parenthesized groups of variable names can be included on a
+single RENAME subcommand.  Variables' names may be swapped using a
+RENAME subcommand of the form @samp{/RENAME=(A B=B A)}.
+
+Alternate syntax for the RENAME subcommand allows the parentheses to be
+eliminated.  When this is done, only a single variable may be renamed at
+once.  For instance, @samp{/RENAME=A=B}.  This alternate syntax is
+deprecated.
+
+DROP, KEEP, and RENAME are performed in left-to-right order.  They each
+may be present any number of times.
+
+Please note that DROP, KEEP, and RENAME do not cause the active file to
+be modified.  Only the system file written to disk is changed.
+
+SAVE causes the data to be read.  It is a procedure.
+
+@node SYSFILE INFO, XSAVE, SAVE, System and Portable Files
+@section SYSFILE INFO
+@vindex SYSFILE INFO
+
+@display 
+SYSFILE INFO FILE='filename'.
+@end display
+
+The SYSFILE INFO command reads the dictionary in a system file and
+displays the information in its dictionary.
+
+Specify a file name or file handle.  SYSFILE INFO will read that file as
+a system file and display information on its dictionary.
+
+The file does not replace the current active file.
+
+@node XSAVE,  , SYSFILE INFO, System and Portable Files
+@section XSAVE
+@vindex XSAVE
+
+@display
+XSAVE
+        /FILE='filename'
+        /@{COMPRESSED,UNCOMPRESSED@}
+        /DROP=var_list
+        /KEEP=var_list
+        /RENAME=(src_names=target_names)@dots{}
+@end display
+
+The XSAVE transformation writes the active file dictionary and data to a
+system file stored on disk.
+
+XSAVE is a transformation, not a procedure.  It is executed when the
+data is read by a procedure or procedure-like command.  In all other
+respects, XSAVE is identical to SAVE.  @xref{SAVE}, for more information
+on syntax and usage.
+
+@node Variable Attributes, Data Manipulation, System and Portable Files, Top
+@chapter Manipulating variables
+
+The variables in the active file dictionary are important.  There are
+several utility functions for examining and adjusting them.
+
+@menu
+* ADD VALUE LABELS::            Add value labels to variables.
+* DISPLAY::                     Display variable names & descriptions.
+* DISPLAY VECTORS::             Display a list of vectors.
+* FORMATS::                     Set print and write formats.
+* LEAVE::                       Don't clear variables between cases.
+* MISSING VALUES::              Set missing values for variables.
+* MODIFY VARS::                 Rename, reorder, and drop variables.
+* NUMERIC::                     Create new numeric variables.
+* PRINT FORMATS::               Set variable print formats.
+* RENAME VARIABLES::            Rename variables.
+* VALUE LABELS::                Set value labels for variables.
+* STRING::                      Create new string variables.
+* VARIABLE LABELS::             Set variable labels for variables.
+* VECTOR::                      Declare an array of variables.
+* WRITE FORMATS::               Set variable write formats.
+@end menu
+
+@node ADD VALUE LABELS, DISPLAY, Variable Attributes, Variable Attributes
+@section ADD VALUE LABELS
+@vindex ADD VALUE LABELS
+
+@display 
+ADD VALUE LABELS
+        /var_list value 'label' [value 'label']@dots{}
+@end display
+
+ADD VALUE LABELS has the same syntax and purpose as VALUE LABELS (see
+above), but it does not clear away value labels from the variables
+before adding the ones specified.
+
+@node DISPLAY, DISPLAY VECTORS, ADD VALUE LABELS, Variable Attributes
+@section DISPLAY
+@vindex DISPLAY
+
+@display
+DISPLAY @{NAMES,INDEX,LABELS,VARIABLES,DICTIONARY,SCRATCH@}
+        [SORTED] [var_list]
+@end display
+
+DISPLAY displays requested information on variables.  Variables can
+optionally be sorted alphabetically.  The entire dictionary or just
+specified variables can be described.
+
+One of the following keywords can be present:
+
+@table @asis
+@item NAMES
+The variables' names are displayed.
+
+@item INDEX
+The variables' names are displayed along with a value describing their
+position within the active file dictionary.
+
+@item LABELS
+Variable names, positions, and variable labels are displayed.
+
+@item VARIABLES
+Variable names, positions, print and write formats, and missing values
+are displayed.
+
+@item DICTIONARY
+Variable names, positions, print and write formats, missing values,
+variable labels, and value labels are displayed.
+
+@item SCRATCH
+Varible names are displayed, for scratch variables only (@pxref{Scratch
+Variables}).
+@end table
+
+If SORTED is specified, then the variables are displayed in ascending
+order based on their names; otherwise, they are displayed in the order
+that they occur in the active file dictionary.
+
+@node DISPLAY VECTORS, FORMATS, DISPLAY, Variable Attributes
+@section DISPLAY VECTORS
+@vindex DISPLAY VECTORS
+
+@display
+DISPLAY VECTORS.
+@end display
+
+The DISPLAY VECTORS command causes a list of the currently declared
+vectors to be displayed.
+
+@node FORMATS, LEAVE, DISPLAY VECTORS, Variable Attributes
+@section FORMATS
+@vindex FORMATS
+
+@display
+FORMATS var_list (fmt_spec).
+@end display
+
+The FORMATS command set the print and write formats for the specified
+variables to the specified format specification.  @xref{Input/Output
+Formats}.
+
+Specify a list of variables followed by a format specification in
+parentheses.  The print and write formats of the specified variables
+will be changed.
+
+Additional lists of variables and formats may be included if they are
+delimited by a slash (@samp{/}).
+
+The FORMATS command takes effect immediately.  It is not affected by
+conditional and looping structures such as DO IF or LOOP.
+
+@node LEAVE, MISSING VALUES, FORMATS, Variable Attributes
+@section LEAVE
+@vindex LEAVE
+
+@display
+LEAVE var_list.
+@end display
+
+The LEAVE command prevents the specified variables from being
+reinitialized whenever a new case is processed.
+
+Normally, when a data file is processed, every variable in the active
+file is initialized to the system-missing value or spaces at the
+beginning of processing for each case.  When a variable has been
+specified on LEAVE, this is not the case.  Instead, that variable is
+initialized to 0 (not system-missing) or spaces for the first case.
+After that, it retains its value between cases.
+
+This becomes useful for counters.  For instance, in the example below
+the variable SUM maintains a running total of the values in the ITEM
+variable.
+
+@example
+DATA LIST /ITEM 1-3.
+COMPUTE SUM=SUM+ITEM.
+PRINT /ITEM SUM.
+LEAVE SUM
+BEGIN DATA.
+123
+404
+555
+999
+END DATA.
+@end example
+
+@noindent Partial output from this example:
+
+@example
+123   123.00
+404   527.00
+555  1082.00
+999  2081.00
+@end example
+
+It is best to use the LEAVE command immediately before invoking a
+procedure command, because it is reset by certain transformations---for
+instance, COMPUTE and IF.  LEAVE is also reset by all procedure
+invocations.
+
+@node MISSING VALUES, MODIFY VARS, LEAVE, Variable Attributes
+@section MISSING VALUES
+@vindex MISSING VALUES
+
+@display
+MISSING VALUES var_list (missing_values).
+
+missing_values takes one of the following forms:
+        num1
+        num1, num2
+        num1, num2, num3
+        num1 THRU num2
+        num1 THRU num2, num3
+        string1
+        string1, string2
+        string1, string2, string3
+As part of a range, LO or LOWEST may take the place of num1;
+HI or HIGHEST may take the place of num2.
+@end display
+
+The MISSING VALUES command sets user-missing values for numeric and
+short string variables.  Long string variables may not have missing
+values.
+
+Specify a list of variables, followed by a list of their user-missing
+values in parentheses.  Up to three discrete values may be given, or,
+for numeric variables only, a range of values optionally accompanied by
+a single discrete value.  Ranges may be open-ended on one end, indicated
+through the use of the keyword LO or LOWEST or HI or HIGHEST.
+
+The MISSING VALUES command takes effect immediately.  It is not affected
+by conditional and looping constructs such as DO IF or LOOP.
+
+@node MODIFY VARS, NUMERIC, MISSING VALUES, Variable Attributes
+@section MODIFY VARS
+@vindex MODIFY VARS
+
+@display 
+MODIFY VARS
+        /REORDER=@{FORWARD,BACKWARD@} @{POSITIONAL,ALPHA@} (var_list)@dots{}
+        /RENAME=(old_names=new_names)@dots{}
+        /@{DROP,KEEP@}=var_list
+        /MAP    
+@end display
+
+The MODIFY VARS commands allows variables in the active file to be
+reordered, renamed, or deleted from the active file.
+
+At least one subcommand must be specified, and no subcommand may be
+specified more than once.  DROP and KEEP may not both be specified.
+
+The REORDER subcommand changes the order of variables in the active
+file.  Specify one or more lists of variable names in parentheses.  By
+default, each list of variables is rearranged into the specified order.
+To put the variables into the reverse of the specified order, put
+keyword BACKWARD before the parentheses.  To put them into alphabetical
+order in the dictionary, specify keyword ALPHA before the parentheses.
+BACKWARD and ALPHA may also be combined.
+
+To rename variables in the active file, specify RENAME, an equals sign
+(@samp{=}), and lists of the old variable names and new variable names
+separated by another equals sign within parentheses.  There must be the
+same number of old and new variable names.  Each old variable is renamed to
+the corresponding new variable name.  Multiple parenthesized groups of
+variables may be specified.
+
+The DROP subcommand deletes a specified list of variables from the
+active file.
+
+The KEEP subcommand keeps the specified list of variables in the active
+file.  Any unlisted variables are delete from the active file.
+
+MAP is currently ignored.
+
+MODIFY VARS takes effect immediately.  It does not cause the data to be
+read.
+
+@node NUMERIC, PRINT FORMATS, MODIFY VARS, Variable Attributes
+@section NUMERIC
+@vindex NUMERIC
+
+@display
+NUMERIC /var_list [(fmt_spec)].
+@end display
+
+The NUMERIC command explicitly declares new numeric variables,
+optionally setting their output formats.
+
+Specify a slash (@samp{/}), followed by the names of the new numeric
+variables.  If you wish to set their output formats, follow their names
+by an output format specification in parentheses (@pxref{Input/Output
+Formats}).  If no output format specification is given then the
+variables will default to F8.2.
+
+Variables created with NUMERIC will be initialized to the system-missing
+value.
+
+@node PRINT FORMATS, RENAME VARIABLES, NUMERIC, Variable Attributes
+@section PRINT FORMATS
+@vindex PRINT FORMATS
+
+@display
+PRINT FORMATS var_list (fmt_spec).
+@end display
+
+The PRINT FORMATS command sets the print formats for the specified
+variables to the specified format specification.
+
+Syntax is identical to that of FORMATS (@pxref{FORMATS}), but the PRINT
+FORMATS command sets only print formats, not write formats.
+
+@node RENAME VARIABLES, VALUE LABELS, PRINT FORMATS, Variable Attributes
+@section RENAME VARIABLES
+@vindex RENAME VARIABLES
+
+@display
+RENAME VARIABLES (old_names=new_names)@dots{} .
+@end display
+
+The RENAME VARIABLES command allows the names of variables in the active
+file to be changed.
+
+To rename variables, specify lists of the old variable names and new
+variable names, separated by an equals sign (@samp{=}), within
+parentheses.  There must be the same number of old and new variable
+names.  Each old variable is renamed to the corresponding new variable
+name.  Multiple parenthesized groups of variables may be specified.
+
+RENAME VARIABLES takes effect immediately.  It does not cause the data
+to be read.
+
+@node VALUE LABELS, STRING, RENAME VARIABLES, Variable Attributes
+@section VALUE LABELS
+@vindex VALUE LABELS
+
+@display 
+VALUE LABELS
+        /var_list value 'label' [value 'label']@dots{}
+@end display
+
+The VALUE LABELS command allows values of numeric and short string
+variables to be associated with labels.  In this way, a short value can
+stand for a long value.
+
+In order to set up value labels for a set of variables, specify the
+variable names after a slash (@samp{/}), followed by a list of values
+and their associated labels, separated by spaces.
+
+Before the VALUE LABELS command is executed, any existing value labels
+are cleared from the variables specified.
+
+@node STRING, VARIABLE LABELS, VALUE LABELS, Variable Attributes
+@section STRING
+@vindex STRING
+
+@display
+STRING /var_list (fmt_spec).
+@end display
+
+The STRING command creates new string variables for use in
+transformations.
+
+Specify a slash (@samp{/}), followed by the names of the string
+variables to create and the desired output format specification in
+parentheses (@pxref{Input/Output Formats}).  Variable widths are
+implicitly derived from the specified output formats.
+
+Created variables are initialized to spaces.
+
+@node VARIABLE LABELS, VECTOR, STRING, Variable Attributes
+@section VARIABLE LABELS
+@vindex VARIABLE LABELS
+
+@display
+VARIABLE LABELS
+        /var_list 'var_label'.
+@end display
+
+The VARIABLE LABELS command is used to associate an explanatory name
+with a group of variables.  This name (a variable label) is displayed by
+statistical procedures.
+
+To assign a variable label to a group of variables, specify a slash
+(@samp{/}), followed by the list of variable names and the variable
+label as a string.
+
+@node VECTOR, WRITE FORMATS, VARIABLE LABELS, Variable Attributes
+@section VECTOR
+@vindex VECTOR
+
+@display
+Two possible syntaxes:
+        VECTOR vec_name=var_list.
+        VECTOR vec_name_list(count).
+@end display
+
+The VECTOR command allows a group of variables to be accessed as if they
+were consecutive members of an array with a vector(index) notation.
+
+To make a vector out of a set of existing variables, specify a name for
+the vector followed by an equals sign (@samp{=}) and the variables that
+belong in the vector.
+
+To make a vector and create variables at the same time, specify one or
+more vector names followed by a count in parentheses.  This will cause
+variables named @code{@var{vec}1} through @code{@var{vec}@var{count}} to
+be created as numeric variables.  Variable names including numeric
+suffixes may not exceed 8 characters in length, and none of the
+variables may exist prior to the VECTOR command.
+
+All the variables in a vector must be the same type.
+
+Vectors created with VECTOR disappear after any procedure or
+procedure-like command is executed.  The variables contained in the
+vectors remain, unless they are scratch variables (@pxref{Scratch
+Variables}).
+
+Variables within a vector may be references in expressions using
+vector(index) syntax.
+
+@node WRITE FORMATS,  , VECTOR, Variable Attributes
+@section WRITE FORMATS
+@vindex WRITE FORMATS
+
+@display
+WRITE FORMATS var_list (fmt_spec).
+@end display
+
+The WRITE FORMATS command sets the write formats for the specified
+variables to the specified format specification.
+
+Syntax is identical to that of FORMATS (@pxref{FORMATS}), but the WRITE
+FORMATS command sets only write formats, not print formats.
+
+@node Data Manipulation, Data Selection, Variable Attributes, Top
+@chapter Data transformations
+
+The PSPP procedures examined in this chapter manipulate data and
+prepare the active file for later analyses.  They do not produce output,
+as a rule.
+
+@menu
+* AGGREGATE::                   Summarize multiple cases into a single case.
+* AUTORECODE::                  Automatic recoding of variables.
+* COMPUTE::                     Assigning a variable a calculated value.
+* COUNT::                       Counting variables with particular values.
+* FLIP::                        Exchange variables with cases.
+* IF::                          Conditionally assigning a calculated value.
+* RECODE::                      Mapping values from one set to another.
+* SORT CASES::                  Sort the active file.
+@end menu
+
+@node AGGREGATE, AUTORECODE, Data Manipulation, Data Manipulation
+@section AGGREGATE
+@vindex AGGREGATE
+
+@display
+AGGREGATE
+        /BREAK=var_list
+        /PRESORTED
+        /OUTFILE=@{*,'filename'@}
+        /DOCUMENT
+        /MISSING=COLUMNWISE
+        /dest_vars=agr_func(src_vars, args@dots{})@dots{}
+@end display
+
+The AGGREGATE command summarizes groups of cases into single cases.
+Cases are divided into groups that have the same values for one or more
+variables called @dfn{break variables}.  Several functions are available
+for summarizing case contents.
+
+BREAK is the only required subcommand (in addition, at least one
+aggregation variable must be specified).  Specify a list of variable
+names.  The values of these variables are used to divide the active file
+into groups to be summarized.
+
+By default, the active file is sorted based on the break variables
+before aggregation takes place.  If the active file is already sorted,
+specify PRESORTED to save time.
+
+The OUTFILE subcommand specifies a system file by file name string or
+file handle (@pxref{FILE HANDLE}).  The aggregated cases are sent to
+this file.  If OUTFILE is not specified, or if @samp{*} is specified,
+then the aggregated cases replace the active file.
+
+Normally the aggregate file does not receive the documents from the
+active file, even if the aggregate file replaces the active file.
+Specify DOCUMENT to have the documents from the active file copied to
+the aggregate file.
+
+At least one aggregation variable must be specified.  Specify a list of
+aggregation variables, an equals sign (@samp{=}), an aggregation
+function name (see the list below), and a list of source variables in
+parentheses.  In addition, some aggregation functions expect additional
+arguments in the parentheses following the source variable names.
+
+There must be exactly as many source variables as aggregation variables.
+Each aggregation variable receives the results of applying the specified
+aggregation function to the corresponding source variable.  Most
+aggregation functions may be applied to numeric and short and long
+string variables.  Others are restricted to numeric values; these are
+marked as such in this list below.
+
+Any number of sets of aggregation variables may be specified.
+
+The available aggregation functions are as follows:
+
+@table @asis
+@item SUM(var_name)
+Sum.  Limited to numeric values.
+@item MEAN(var_name)
+Arithmetic mean.  Limited to numeric values.
+@item SD(var_name)
+Standard deviation of the mean.  Limited to numeric values.
+@item MAX(var_name)
+Maximum value.
+@item MIN(var_name)
+Minimum value.
+@item FGT(var_name, value)
+@itemx PGT(var_name, value)
+Fraction between 0 and 1, or percentage between 0 and 100, respectively,
+of values greater than the specified constant.
+@item FLT(var_name, value)
+@itemx PLT(var_name, value)
+Fraction or percentage, respectively, of values less than the specified
+constant.
+@item FIN(var_name, low, high)
+@itemx PIN(var_name, low, high)
+Fraction or percentage, respectively, of values within the specified
+inclusive range of constants.
+@item FOUT(var_name, low, high)
+@itemx POUT(var_name, low, high)
+Fraction or percentage, respectively, of values strictly outside the
+specified range of constants.
+@item N(var_name)
+Number of non-missing values.
+@item N
+Number of cases aggregated to form this group.  Don't supply a source
+variable for this aggregation function.
+@item NU(var_name)
+Number of non-missing values.  Each case is considered to have a weight
+of 1, regardless of the current weighting variable (@pxref{WEIGHT}).
+@item NU
+Number of cases aggregated to form this group.  Each case is considered
+to have a weight of 1, regardless of the current weighting variable.
+@item NMISS(var_name)
+Number of missing values.
+@item NUMISS(var_name)
+Number of missing values.  Each case is considered to have a weight of
+1, regardless of the current weighting variable.
+@item FIRST(var_name)
+First value in this group.
+@item LAST(var_name)
+Last value in this group.
+@end table
+
+When string values are compared by aggregation functions, they are done
+in terms of internal character codes.  On most modern computers, this is
+a form of ASCII.
+
+In addition, there is a parallel set of aggregation functions having the
+same names as those above, but with a dot after the last character (for
+instance, @samp{SUM.}).  These functions are the same as the above,
+except that they cause user-missing values, which are normally excluded
+from calculations, to be included.
+
+Normally, only a single case (2 for SD and SD.) need be non-missing in
+each group in order for the aggregate variable to be non-missing.  If
+/MISSING=COLUMNWISE is specified, the behavior reverses: that is, a
+single missing value is enough to make the aggregate variable become a
+missing value.
+
+AGGREGATE ignores the current SPLIT FILE settings and causes them to be
+canceled (@pxref{SPLIT FILE}).  
+
+@node AUTORECODE, COMPUTE, AGGREGATE, Data Manipulation
+@section AUTORECODE
+@vindex AUTORECODE
+
+@display
+AUTORECODE VARIABLES=src_vars INTO dest_vars
+        /DESCENDING
+        /PRINT
+@end display
+
+The AUTORECODE procedure considers the @var{n} values that a variable
+takes on and maps them onto values 1@dots{}@var{n} on a new numeric
+variable.
+
+Subcommand VARIABLES is the only required subcommand and must come
+first.  Specify VARIABLES, an equals sign (@samp{=}), a list of source
+variables, INTO, and a list of target variables.  There must the same
+number of source and target variables.  The target variables must not
+already exist.
+
+By default, increasing values of a source variable (for a string, this
+is based on character code comparisons) are recoded to increasing values
+of its target variable.  To cause increasing values of a source variable
+to be recoded to decreasing values of its target variable (@var{n} down
+to 1), specify DESCENDING.
+
+PRINT is currently ignored.
+
+AUTORECODE is a procedure.  It causes the data to be read.
+
+@node COMPUTE, COUNT, AUTORECODE, Data Manipulation
+@section COMPUTE
+
+@display
+COMPUTE var_name = expression.
+@end display
+
+@code{COMPUTE} creates a variable with the name specified (if
+necessary), then evaluates the given expression for every case and
+assigns the result to the variable.  @xref{Expressions}.
+
+Numeric variables created or computed by @code{COMPUTE} are assigned an
+output width of 8 character with two decimal places (@code{F8.2}).
+String variables created or computed by @code{COMPUTE} have the same
+width as the existing variable or constant.
+
+COMPUTE is a transformation.  It does not cause the active file to be
+read.
+
+@node COUNT, FLIP, COMPUTE, Data Manipulation
+@section COUNT
+
+@display
+COUNT var_name = var@dots{} (value@dots{}).
+
+Each value takes one of the following forms:
+        number
+        string
+        num1 THRU num2
+        MISSING
+        SYSMIS
+In addition, num1 and num2 can be LO or LOWEST, or HI or HIGHEST,
+respectively.
+@end display
+
+@code{COUNT} creates or replaces a numeric @dfn{target} variable that
+counts the occurrence of a @dfn{criterion} value or set of values over
+one or more @dfn{test} variables for each case.
+
+The target variable values are always nonnegative integers.  They are
+never missing.  The target variable is assigned an F8.2 output format.
+@xref{Input/Output Formats}.  Any variables, including long and short
+string variables, may be test variables.
+
+User-missing values of test variables are treated just like any other
+values.  They are @strong{not} treated as system-missing values.
+User-missing values that are criterion values or inside ranges of
+criterion values are counted as any other values.  However (for numeric
+variables), keyword @code{MISSING} may be used to refer to all system-
+and user-missing values.
+
+
+@code{COUNT} target variables are assigned values in the order
+specified.  In the command @code{COUNT A=A B(1) /B=A B(2).}, the
+following actions occur:
+
+@itemize @minus
+@item
+The number of occurrences of 1 between @code{A} and @code{B} is counted.
+
+@item
+@code{A} is assigned this value.
+
+@item
+The number of occurrences of 1 between @code{B} and the @strong{new}
+value of @code{A} is counted.
+
+@item
+@code{B} is assigned this value.
+@end itemize
+
+Despite this ordering, all @code{COUNT} criterion variables must exist
+before the procedure is executed---they may not be created as target
+variables earlier in the command!  Break such a command into two
+separate commands.
+
+The examples below may help to clarify.
+
+@enumerate A
+@item
+Assuming @code{Q0}, @code{Q2}, @dots{}, @code{Q9} are numeric variables,
+the following commands:
+
+@enumerate
+@item
+Count the number of times the value 1 occurs through these variables
+for each case and assigns the count to variable @code{QCOUNT}.  
+
+@item
+Print out the total number of times the value 1 occurs throughout
+@emph{all} cases using @code{DESCRIPTIVES}.  @xref{DESCRIPTIVES}, for
+details.
+@end enumerate
+
+@example
+COUNT QCOUNT=Q0 TO Q9(1).
+DESCRIPTIVES QCOUNT /STATISTICS=SUM.
+@end example
+
+@item
+Given these same variables, the following commands:
+
+@enumerate
+@item
+Count the number of valid values of these variables for each case and
+assigns the count to variable @code{QVALID}.
+
+@item
+Multiplies each value of @code{QVALID} by 10 to obtain a percentage of
+valid values, using @code{COMPUTE}.  @xref{COMPUTE}, for details.
+
+@item
+Print out the percentage of valid values across all cases, using
+@code{DESCRIPTIVES}.  @xref{DESCRIPTIVES}, for details.
+@end enumerate
+
+@example
+COUNT QVALID=Q0 TO Q9 (LO THRU HI).
+COMPUTE QVALID=QVALID*10.
+DESCRIPTIVES QVALID /STATISTICS=MEAN.
+@end example
+@end enumerate
+
+@node FLIP, IF, COUNT, Data Manipulation
+@section FLIP
+@vindex FLIP
+
+@display
+FLIP /VARIABLES=var_list /NEWNAMES=var_name.
+@end display
+
+The FLIP command transposes rows and columns in the active file.  It
+causes cases to be swapped with variables, and vice versa.
+
+There are no required subcommands.  The VARIABLES subcommand specifies
+variables that will be transformed into cases.  Variables not specified
+are discarded.  By default, all variables are selected for
+transposition.
+
+The variables specified by NEWNAMES, which must be a string variable, is
+used to give names to the variables created by FLIP.  If NEWNAMES is not
+specified then the default is a variable named CASE_LBL, if it exists.
+If it does not then the variables created by FLIP are named VAR000
+through VAR999, then VAR1000, VAR1001, and so on.
+
+When a NEWNAMES variable is available, the names must be canonicalized
+before becoming variable names.  Invalid characters are replaced by
+letter @samp{V} in the first position, or by @samp{_} in subsequent
+positions.  If the name thus generated is not unique, then numeric
+extensions are added, starting with 1, until a unique name is found or
+there are no remaining possibilities.  If the latter occurs then the
+FLIP operation aborts.
+
+The resultant dictionary contains a CASE_LBL variable, which stores the
+names of the variables in the dictionary before the transposition.  If
+the active file is subsequently transposed using FLIP, this variable can
+be used to recreate the original variable names.
+
+@node IF, RECODE, FLIP, Data Manipulation
+@section IF
+
+@display
+Two possible syntaxes: 
+        IF test_expr target_var=target_expr.
+        IF test_expr target_vec(target_index)=target_expr.
+@end display
+
+The IF transformation conditionally assigns the value of a target
+expression to a target variable, based on the truth of a test
+expression.
+
+Specify a boolean-valued expression (@pxref{Expressions}) to be tested
+following the IF keyword.  This expression is calculated for each case.
+If the value is true, then the value of target_expr is computed and
+assigned to target_var.  If the value is false or missing, nothing is
+done.  Numeric and short and long string variables may be used.  The
+type of target_expr must match the type of target_var.
+
+For numeric variables only, target_var need not exist before the IF
+transformation is executed.  In this case, target_var is assigned the
+system-missing value if the IF condition is not true.  String variables
+must be declared before they can be used as targets for IF.
+
+In addition to ordinary variables, the target variable may be an element
+of a vector.  In this case, the vector index must be specified in
+parentheses following the vector name.
+
+@node RECODE, SORT CASES, IF, Data Manipulation
+@section RECODE
+
+@display
+RECODE var_list (src_value@dots{}=dest_value)@dots{} [INTO var_list].
+
+src_value may take the following forms:
+        number
+        string
+        num1 THRU num2
+        MISSING
+        SYSMIS
+        ELSE
+Open-ended ranges may be specified using LO or LOWEST for num1
+or HI or HIGHEST for num2.
+
+dest_value may take the following forms:
+        num
+        string
+        SYSMIS
+        COPY
+@end display
+
+The RECODE command is used to translate data from one range of values to
+another, using flexible user-specified mappings.  Data may be remapped
+in-place or copied to new variables.  Numeric, short string, and long
+string data can be recoded.
+
+Specify the list of source variables, followed by one or more mapping
+specifications each enclosed in parentheses.  If the data is to be
+copied to new variables, specify INTO, then the list of target
+variables.  String target variables must already have been declared
+using STRING or another transformation, but numeric target variables can
+be created on the fly.  There must be exactly as many target variables
+as source variables.  Each source variable is remapped into its
+corresponding target variable.
+
+When INTO is not used, the input and output variables must be of the
+same type.  Otherwise, string values can be recoded into numeric values,
+and vice versa.  When this is done and there is no mapping for a
+particular value, either a value consisting of all spaces or the
+system-missing value is assigned, depending on variable type.
+
+Mappings are considered from left to right.  The first src_value that
+matches the value of the source variable causes the target variable to
+receive the value indicated by the dest_value.  Literal number, string,
+and range src_value's should be self-explanatory.  MISSING as a
+src_value matches any user- or system-missing value.  SYSMIS matches the
+system missing value only.  ELSE is a catch-all that matches anything.
+It should be the last src_value specified.
+
+Numeric and string dest_value's should also be self-explanatory.  COPY
+causes the input values to be copied to the output.  This is only value
+if the source and target variables are of the same type.  SYSMIS
+indicates the system-missing value.
+
+If the source variables are strings and the target variables are
+numeric, then there is one additional mapping available: (CONVERT),
+which must be the last specified mapping.  CONVERT causes a number
+specified as a string to be converted to a numeric value.  If the string
+cannot be parsed as a number, then the system-missing value is assigned.
+
+Multiple recodings can be specified on the same RECODE command.
+Introduce additional recodings with a slash (@samp{/}) in order to
+separate them from the previous recodings.
+
+@node SORT CASES,  , RECODE, Data Manipulation
+@section SORT CASES
+@vindex SORT CASES
+
+@display
+SORT CASES BY var_list.
+@end display
+
+SORT CASES sorts the active file by the values of one or more
+variables.
+
+Specify BY and a list of variables to sort by.  By default, variables
+are sorted in ascending order.  To override sort order, specify (D) or
+(DOWN) after a list of variables to get descending order, or (A) or (UP)
+for ascending order.  These apply to the entire list of variables
+preceding them.
+
+SORT CASES is a procedure.  It causes the data to be read.
+
+SORT CASES will attempt to sort the entire active file in main memory.
+If main memory is exhausted then it will use a merge sort algorithm that
+involves writing and reading numerous temporary files.  Environment
+variables determine the temporary files' location.  The first of
+SPSSTMPDIR, SPSSXTMPDIR, or TMPDIR that is set determines the location.
+Otherwise, if the compiler environment defined P_tmpdir, that is used.
+Otherwise, under Unix-like OSes /tmp is used; under MS-DOS, the first of
+TEMP, TMP, or root on the current drive is used; under other OSes, the
+current directory.
+
+@node Data Selection, Conditionals and Looping, Data Manipulation, Top
+@chapter Selecting data for analysis
+
+This chapter documents PSPP commands that temporarily or permanently
+select data records from the active file for analysis.
+
+@menu
+* FILTER::                      Exclude cases based on a variable.
+* N OF CASES::                  Limit the size of the active file.
+* PROCESS IF::                  Temporarily excluding cases.
+* SAMPLE::                      Select a specified proportion of cases.
+* SELECT IF::                   Permanently delete selected cases.
+* SPLIT FILE::                  Do multiple analyses with one command.
+* TEMPORARY::                   Make transformations' effects temporary.
+* WEIGHT::                      Weight cases by a variable.
+@end menu
+
+@node FILTER, N OF CASES, Data Selection, Data Selection
+@section FILTER
+@vindex FILTER
+
+@display
+FILTER BY var_name.
+FILTER OFF.
+@end display
+
+The FILTER command allows a boolean-valued variable to be used to select
+cases from the data stream for processing.
+
+In order to set up filtering, specify BY and a variable name.  Keyword
+BY is optional but recommended.  Cases which have a zero or system- or
+user-missing value are excluded from analysis, but not deleted from the
+data stream.  Cases with other values are analyzed.
+
+Use FILTER OFF to turn off case filtering.
+
+Filtering takes place immediately before cases pass to a procedure for
+analysis.  Only one filter variable may be active at once.  Normally,
+case filtering continues until it is explicitly turned off with FILTER
+OFF.  However, if FILTER is placed after TEMPORARY, then filtering stops
+after execution of the next procedure or procedure-like command.
+
+@node N OF CASES, PROCESS IF, FILTER, Data Selection
+@section N OF CASES
+@vindex N OF CASES
+
+@display
+N [OF CASES] num_of_cases [ESTIMATED].
+@end display
+
+Sometimes you may want to disregard cases of your input.  The @code{N}
+command can be used to do this.  @code{N 100} tells PSPP to
+disregard all cases after the first 100.
+
+If the value specified for @code{N} is greater than the number of cases
+read in, the value is ignored.
+
+@code{N} does not discard cases or cause them not to be read in.  It
+just causes cases beyond the last one specified to be ignored by data
+analysis commands.
+
+A later @code{N} command can increase or decrease the number of cases
+selected.  (To select all the cases without knowing how many there are,
+specify a very high number: 100000 or whatever you think is large enough.)
+
+Transformation procedures performed after @code{N} is executed
+@emph{do} cause cases to be discarded.
+
+The @code{SAMPLE}, @code{PROCESS IF}, and @code{SELECT IF} commands have
+precedence over @code{N}---the same results are obtained by both of the
+following fragments, given the same random number seeds:
+
+@example
+@i{@dots{}set up, read in data@dots{}}
+N 100.
+SAMPLE .5.
+@i{@dots{}analyze data@dots{}}
+
+@i{@dots{}set up, read in data@dots{}}  
+SAMPLE .5.
+N 100.
+@i{@dots{}analyze data@dots{}}
+@end example
+
+Both fragments above first randomly sample approximately half of the
+cases, then select the first 100 of those sampled.
+
+@code{N} with the @code{ESTIMATED} keyword can be used to give an
+estimated number of cases before DATA LIST or another command to
+read in data.  (@code{ESTIMATED} never limits the number of cases
+processed by procedures.)
+
+@node PROCESS IF, SAMPLE, N OF CASES, Data Selection
+@section PROCESS IF
+@vindex PROCESS IF
+
+@example
+PROCESS IF expression.
+@end example
+
+The PROCESS IF command is used to temporarily eliminate cases from the
+data stream.  Its effects are active only through the execution of the
+next procedure or procedure-like command.
+
+Specify a boolean expression (@pxref{Expressions}).  If the value of the
+expression is true for a particular case, the case will be analyzed.  If
+the expression has a false or missing value, then the case will be
+deleted from the data stream for this procedure only.
+
+Regardless of its placement relative to other commands, PROCESS IF
+always takes effect immediately before data passes to the procedure.
+Only one PROCESS IF command may be in effect at any given time.
+
+The effects of PROCESS IF are similar not identical to the effects of
+executing TEMPORARY then SELECT IF (@pxref{SELECT IF}).
+
+Use of PROCESS IF is deprecated.  It is included for compatibility with
+old command files.  New syntax files should use SELECT IF or FILTER
+instead.
+
+@node SAMPLE, SELECT IF, PROCESS IF, Data Selection
+@section SAMPLE
+@vindex SAMPLE
+
+@display
+SAMPLE num1 [FROM num2].
+@end display
+
+@code{SAMPLE} is used to randomly sample a proportion of the cases in
+the active file.  @code{SAMPLE} is temporary, affecting only the next
+procedure, unless that is a data transformation, such as @code{SELECT IF}
+or @code{RECODE}.
+
+The proportion to sample can be expressed as a single number between 0
+and 1.  If @code{k} is the number specified, and @code{N} is the number
+of currently-selected cases in the active file, then after
+@code{SAMPLE @var{k}.}, there will be @code{k*N}, plus or minus one, cases
+selected.
+
+The proportion to sample can also be specified in the style @code{SAMPLE
+@var{m} FROM @var{N}}.  With this style, cases are selected as follows:
+
+@enumerate
+@item
+If @var{N} is equal to the number of currently-selected cases in the
+active file, exactly @var{m} cases will be selected.
+
+@item
+If @var{N} is greater than the number of currently-selected cases in the
+active file, an equivalent proportion of cases will be selected.
+
+@item
+If @var{N} is less than the number of currently-selected cases in the
+active, exactly @var{m} cases will be selected @emph{from the first
+@var{N} cases in the active file.}
+@end enumerate
+
+@code{SAMPLE}, @code{SELECT IF}, and @code{PROCESS IF} are performed in
+the order specified by the syntax file.
+
+@code{SAMPLE} is ignored before @code{SORT CASES}.
+
+@code{SAMPLE} is always performed before @code{N OF CASES}, regardless
+of ordering in the syntax file.  @xref{N OF CASES}.
+
+The same values for @code{SAMPLE} may result in different samples.  To
+obtain the same sample, use the @code{SET} command to set the random
+number seed to the same value before each @code{SAMPLE}.  By default,
+the random number seed is based on the system time.
+
+@node SELECT IF, SPLIT FILE, SAMPLE, Data Selection
+@section SELECT IF
+@vindex SELECT IF
+
+@display
+SELECT IF expression.
+@end display
+
+The SELECT IF command is used to select particular cases for analysis
+based on the value of a boolean expression.  Cases not selected are
+permanently eliminated, unless TEMPORARY is in effect
+(@pxref{TEMPORARY}).
+
+Specify a boolean expression (@pxref{Expressions}).  If the value of the
+expression is true for a particular case, the case will be analyzed.  If
+the expression has a false or missing value, then the case will be
+deleted from the data stream.
+
+Always place SELECT IF commands as early in the command file as
+possible.  Cases that are deleted early can be processed more
+efficiently in time and space.
+
+@node SPLIT FILE, TEMPORARY, SELECT IF, Data Selection
+@section SPLIT FILE
+@vindex SPLIT FILE
+
+@display
+Two possible syntaxes:
+        SPLIT FILE BY var_list.
+        SPLIT FILE OFF.
+@end display
+
+The SPLIT FILE command allows multiple sets of data present in one data
+file to be analyzed separately using single statistical procedure
+commands.
+
+Specify a list of variable names in order to analyze multiple sets of
+data separately.  Groups of cases having the same values for these
+variables are analyzed by statistical procedure commands as one group.
+An independent analysis is carried out for each group of cases, and the
+variable values for the group are printed along with the analysis.
+
+Specify OFF in order to disable SPLIT FILE and resume analysis of the
+entire active file as a single group of data.
+
+@node TEMPORARY, WEIGHT, SPLIT FILE, Data Selection
+@section TEMPORARY
+@vindex TEMPORARY
+
+@display
+TEMPORARY.
+@end display
+
+The TEMPORARY command is used to make the effects of transformations
+following its execution temporary.  These transformations will
+affect only the execution of the next procedure or procedure-like
+command.  Their effects will not be saved to the active file.
+
+The only specification is the command name.
+
+TEMPORARY may not appear within a DO IF or LOOP construct.  It may
+appear only once between procedures and procedure-like commands.
+
+An example may help to clarify:
+
+@example
+DATA LIST /X 1-2.
+BEGIN DATA.
+ 2
+ 4
+10
+15
+20
+24
+END DATA.
+COMPUTE X=X/2.
+TEMPORARY.
+COMPUTE X=X+3.
+DESCRIPTIVES X.
+DESCRIPTIVES X.
+@end example
+
+The data read by the first DESCRIPTIVES command are 4, 5, 8,
+10.5, 13, 15.  The data read by the first DESCRIPTIVES command are 1, 2,
+5, 7.5, 10, 12.
+
+@node WEIGHT,  , TEMPORARY, Data Selection
+@section WEIGHT
+@vindex WEIGHT
+
+@display
+WEIGHT BY var_name.
+WEIGHT OFF.
+@end display
+
+WEIGHT can be used to assign cases varying weights in order to
+change the frequency distribution of the active file.  Execution of
+WEIGHT is delayed until data have been read in.
+
+If a variable name is specified, WEIGHT causes the values of that
+variable to be used as weighting factors for subsequent statistical
+procedures.  Use of keyword BY is optional but recommended.  Weighting
+variables must be numeric.  Scratch variables may not be used for
+weighting (@pxref{Scratch Variables}).
+
+When OFF is specified, subsequent statistical procedures will weight all
+cases equally.
+
+Weighting values do not need to be integers.  However, negative and
+system- and user-missing values for the weighting variable are
+interpreted as weighting factors of 0.
+
+WEIGHT does not cause cases in the active file to be replicated in
+memory.
+
+@node Conditionals and Looping, Statistics, Data Selection, Top
+@chapter Conditional and Looping Constructs
+@cindex conditionals
+@cindex loops
+@cindex flow of control
+@cindex control flow
+
+This chapter documents PSPP commands used for conditional execution,
+looping, and flow of control.
+
+@menu
+* BREAK::                       Exit a loop.
+* DO IF::                       Conditionally execute a block of code.
+* DO REPEAT::                   Textually repeat a code block.
+* LOOP::                        Repeat a block of code.
+@end menu
+
+@node BREAK, DO IF, Conditionals and Looping, Conditionals and Looping
+@section BREAK
+@vindex BREAK
+
+@display
+BREAK.
+@end display
+
+BREAK terminates execution of the innermost currently executing LOOP
+construct.
+
+BREAK is allowed only inside a LOOP construct.  @xref{LOOP}, for more
+details.
+
+@node DO IF, DO REPEAT, BREAK, Conditionals and Looping
+@section DO IF
+@vindex DO IF
+
+@display
+DO IF condition.
+        @dots{}
+[ELSE IF condition.
+        @dots{}
+]@dots{}
+[ELSE.
+        @dots{}]
+END IF.
+@end display
+
+The DO IF command allows one of several sets of transformations to be
+executed, depending on user-specified conditions.
+
+Specify a boolean expression.  If the condition is true, then the block
+of code following DO IF is executed.  If the condition is missing, then
+none of the code blocks is executed.  If the condition is false, then
+the boolean expressions on the first ELSE IF, if present, is tested in
+turn, with the same rules applied.  If all expressions evaluate to
+false, then the ELSE code block is executed, if it is present.
+
+@node DO REPEAT, LOOP, DO IF, Conditionals and Looping
+@section DO REPEAT
+@vindex DO REPEAT
+
+@display
+DO REPEAT repvar_name=expansion@dots{}.
+        @dots{}
+END REPEAT [PRINT].
+
+expansion takes one of the following forms:
+        var_list
+        num_or_range@dots{}
+        'string'@dots{}
+
+num_or_range takes one of the following forms:
+        number
+        num1 TO num2
+@end display
+
+The DO REPEAT command causes a block of code to be repeated a number of
+times with different variables, numbers, or strings textually
+substituted into the block with each repetition.
+
+Specify a repeat variable name followed by an equals sign (@samp{=}) and
+the list of replacements.  Replacements can be a list of variables
+(which may be existing variables or new variables or a combination
+thereof), of numbers, or of strings.  When new variable names are
+specified, DO REPEAT creates them as numeric variables.  When numbers
+are specified, runs of integers may be indicated with TO notation, for
+instance @samp{1 TO 5} and @samp{1 2 3 4 5} would be equivalent.  There
+is no equivalent notation for string values.
+
+Multiple repeat variables can be specified.  When this is done, each
+variable must have the same number of replacements.
+
+The code within DO REPEAT is repeated as many times as there are
+replacements for each variable.  The first time, the first value for
+each repeat variable is substituted; the second time, the second value
+for each repeat variable is substituted; and so on.
+
+Repeat variable substitutions work like macros.  They take place
+anywhere in a line that the repeat variable name occurs as a token,
+including command and subcommand names.  For this reason it is not a
+good idea to select words commonly used in command and subcommand names
+as repeat variable identifiers.
+
+If PRINT is specified on END REPEAT, the commands after substitutions
+are made are printed to the listing file, prefixed by a plus sign
+(@samp{+}).
+
+@node LOOP,  , DO REPEAT, Conditionals and Looping
+@section LOOP
+@vindex LOOP
+
+@display
+LOOP [index_var=start TO end [BY incr]] [IF condition].
+        @dots{}
+END LOOP [IF condition].
+@end display
+
+The LOOP command allows a group of commands to be iterated.  A number of
+termination options are offered.
+
+Specify index_var in order to make that variable count from one value to
+another by a particular increment.  index_var must be a pre-existing
+numeric variable.  start, end, and incr are numeric expressions
+(@pxref{Expressions}.)  
+
+During the first iteration, index_var is set to the value of start.
+During each successive iteration, index_var is increased by the value of
+incr.  If end > start, then the loop terminates when index_var > end;
+otherwise it terminates when index_var < end.  If incr is not specified
+then it defaults to +1 or -1 as appropriate.
+
+If end > start and incr < 0, or if end < start and incr > 0, then the
+loop is never executed.  index_var is nevertheless set to the value of
+start.
+
+Modifying index_var within the loop is allowed, but it has no effect on
+the value of index_var in the next iteration.
+
+Specify a boolean expression for the condition on the LOOP command to
+cause the loop to be executed only if the condition is true.  If the
+condition is false or missing before the loop contents are executed the
+first time, the loop contents are not executed at all.
+
+If index and condition clauses are both present on LOOP, the index
+clause is always evaluated first.
+
+Specify a boolean expression for the condition on the END LOOP to cause
+the loop to terminate if the condition is not true after the enclosed
+code block is executed.  The condition is evaluated at the end of the
+loop, not at the beginning.
+
+If the index clause and both condition clauses are not present, then the
+loop is executed MXLOOPS (@pxref{SET}) times or until BREAK
+(@pxref{BREAK}) is executed.
+
+The BREAK command provides another way to terminate execution of a LOOP
+construct.
+
+@node Statistics, Utilities, Conditionals and Looping, Top
+@chapter Statistics
+
+This chapter documents the statistical procedures that PSPP supports so
+far.
+
+@menu
+* DESCRIPTIVES::                Descriptive statistics.
+* FREQUENCIES::                 Frequency tables.
+* CROSSTABS::                   Crosstabulation tables.
+@end menu
+
+@node DESCRIPTIVES, FREQUENCIES, Statistics, Statistics
+@section DESCRIPTIVES
+
+@display
+DESCRIPTIVES
+        /VARIABLES=var_list
+        /MISSING=@{VARIABLE,LISTWISE@} @{INCLUDE,NOINCLUDE@}
+        /FORMAT=@{LABELS,NOLABELS@} @{NOINDEX,INDEX@} @{LINE,SERIAL@}
+        /SAVE
+        /STATISTICS=@{ALL,MEAN,SEMEAN,STDDEV,VARIANCE,KURTOSIS,
+                     SKEWNESS,RANGE,MINIMUM,MAXIMUM,SUM,DEFAULT,
+                     SESKEWNESS,SEKURTOSIS@}
+        /SORT=@{NONE,MEAN,SEMEAN,STDDEV,VARIANCE,KURTOSIS,SKEWNESS,
+               RANGE,MINIMUM,MAXIMUM,SUM,SESKEWNESS,SEKURTOSIS,NAME@}
+              @{A,D@}
+@end display
+
+The DESCRIPTIVES procedure reads the active file and outputs descriptive
+statistics requested by the user.  In addition, it can optionally
+compute Z-scores.
+
+The VARIABLES subcommand, which is required, specifies the list of
+variables to be analyzed.  Keyword VARIABLES is optional.
+
+All other subcommands are optional:
+
+The MISSING subcommand determines the handling of missing variables.  If
+INCLUDE is set, then user-missing values are included in the
+calculations.  If NOINCLUDE is set, which is the default, user-missing
+values are excluded.  If VARIABLE is set, then missing values are
+excluded on a variable by variable basis; if LISTWISE is set, then
+the entire case is excluded whenever any value in that case has a
+system-missing or, if INCLUDE is set, user-missing value.
+
+The FORMAT subcommand affects the output format.  Currently the
+LABELS/NOLABELS and NOINDEX/INDEX settings is not used.  When SERIAL is
+set, both valid and missing number of cases are listed in the output;
+when NOSERIAL is set, only valid cases are listed.
+
+The SAVE subcommand causes DESCRIPTIVES to calculate Z scores for all
+the specified variables.  The Z scores are saved to new variables.
+Variable names are generated by trying first the original variable name
+with Z prepended and truncated to a maximum of 8 characters, then the
+names ZSC000 through ZSC999, STDZ00 through STDZ09, ZZZZ00 through
+ZZZZ09, ZQZQ00 through ZQZQ09, in that sequence.  In addition, Z score
+variable names can be specified explicitly on VARIABLES in the variable
+list by enclosing them in parentheses after each variable.
+
+The STATISTICS subcommand specifies the statistics to be displayed:
+
+@table @code
+@item ALL
+All of the statistics below.
+@item MEAN
+Arithmetic mean.
+@item SEMEAN
+Standard error of the mean.
+@item STDDEV
+Standard deviation.
+@item VARIANCE
+Variance.
+@item KURTOSIS
+Kurtosis and standard error of the kurtosis.
+@item SKEWNESS
+Skewness and standard error of the skewness.
+@item RANGE
+Range.
+@item MINIMUM
+Minimum value.
+@item MAXIMUM
+Maximum value.
+@item SUM
+Sum.
+@item DEFAULT
+Mean, standard deviation of the mean, minimum, maximum.
+@item SEKURTOSIS
+Standard error of the kurtosis.
+@item SESKEWNESS
+Standard error of the skewness.
+@end table
+
+The SORT subcommand specifies how the statistics should be sorted.  Most
+of the possible values should be self-explanatory.  NAME causes the
+statistics to be sorted by name.  By default, the statistics are listed
+in the order that they are specified on the VARIABLES subcommand.  The A
+and D settings request an ascending or descending sort order,
+respectively.
+
+@node FREQUENCIES, CROSSTABS, DESCRIPTIVES, Statistics
+@section FREQUENCIES
+
+@display
+FREQUENCIES
+        /VARIABLES=var_list
+        /FORMAT=@{TABLE,NOTABLE,LIMIT(limit)@}
+                @{STANDARD,CONDENSE,ONEPAGE[(onepage_limit)]@}
+                @{LABELS,NOLABELS@}
+                @{AVALUE,DVALUE,AFREQ,DFREQ@}
+                @{SINGLE,DOUBLE@}
+                @{OLDPAGE,NEWPAGE@}
+        /MISSING=@{EXCLUDE,INCLUDE@}
+        /STATISTICS=@{DEFAULT,MEAN,SEMEAN,MEDIAN,MODE,STDDEV,VARIANCE,
+                     KURTOSIS,SKEWNESS,RANGE,MINIMUM,MAXIMUM,SUM,
+                     SESKEWNESS,SEKURTOSIS,ALL,NONE@}
+        /NTILES=ntiles
+        /PERCENTILES=percent@dots{}
+
+(These options are not currently implemented.)
+        /BARCHART=@dots{}
+        /HISTOGRAM=@dots{}
+        /HBAR=@dots{}
+        /GROUPED=@dots{}
+
+(Integer mode.)
+        /VARIABLES=var_list (low,high)@dots{}
+@end display
+
+FREQUENCIES causes the data to be read and frequency tables to be built
+and output for specified variables.  FREQUENCIES can also calculate and
+display descriptive statistics (including median and mode) and
+percentiles.
+
+In the future, FREQUENCIES will also support graphical output in the
+form of bar charts and histograms.  In addition, it will be able to
+support percentiles for grouped data.  (As a historical note, these
+options were supported in a version of PSPP written years ago, but the
+code has not survived.)
+
+The VARIABLES subcommand is the only required subcommand.  Specify the
+variables to be analyzed.  In most cases, this is all that is required.
+This is known as @dfn{general mode}.
+
+Occasionally, one may want to invoke a special mode called @dfn{integer
+mode}.  Normally, in general mode, PSPP will automatically determine
+what values occur in the data.  In integer mode, the user specifies the
+range of values that the data assumes.  To invoke this mode, specify a
+range of data values in parentheses, separated by a comma.  Data values
+inside the range are truncated to the nearest integer, then assigned to
+that value.  If values occur outside this range, they are discarded.
+
+The FORMAT subcommand controls the output format.  It has several
+possible settings:  
+
+@itemize @bullet
+@item
+TABLE, the default, causes a frequency table to be output for every
+variable specified.  NOTABLE prevents them from being output.  LIMIT
+with a numeric argument causes them to be output except when there are
+more than the specified number of values in the table.
+
+@item
+STANDARD frequency tables contain more complete information, but also to
+take up more space on the printed page.  CONDENSE frequency tables are
+less informative but take up less space.  ONEPAGE with a numeric
+argument will output standard frequency tables if there are the
+specified number of values or less, condensed tables otherwise.  ONEPAGE
+without an argument defaults to a threshold of 50 values.
+
+@item
+LABELS causes value labels to be displayed in STANDARD frequency
+tables.  NOLABLES prevents this.
+
+@item
+Normally frequency tables are sorted in ascending order by value.  This
+is AVALUE.  DVALUE tables are sorted in descending order by value.
+AFREQ and DFREQ tables are sorted in ascending and descending order,
+respectively, by frequency count.
+
+@item
+SINGLE spaced frequency tables are closely spaced.  DOUBLE spaced
+frequency tables have wider spacing.
+
+@item
+OLDPAGE and NEWPAGE are not currently used.
+@end itemize
+
+The MISSING subcommand controls the handling of user-missing values.
+When EXCLUDE, the default, is set, user-missing values are not included
+in frequency tables or statistics.  When INCLUDE is set, user-missing
+are included.  System-missing values are never included in statistics,
+but are listed in frequency tables.
+
+The available STATISTICS are the same as available in DESCRIPTIVES
+(@pxref{DESCRIPTIVES}), with the addition of MEDIAN, the data's median
+value, and MODE, the mode.  (If there are multiple modes, the smallest
+value is reported.)  By default, the mean, standard deviation of the
+mean, minimum, and maximum are reported for each variable.
+
+NTILES causes the specified quartiles to be reported.  For instance,
+@code{/NTILES=4} would cause quartiles to be reported.  In addition,
+particular percentiles can be requested with the PERCENTILES subcommand.
+
+@node CROSSTABS,  , FREQUENCIES, Statistics
+@section CROSSTABS
+
+@display
+CROSSTABS
+        /TABLES=var_list BY var_list [BY var_list]@dots{}
+        /MISSING=@{TABLE,INCLUDE,REPORT@}
+        /WRITE=@{NONE,CELLS,ALL@}
+        /FORMAT=@{TABLES,NOTABLES@}
+                @{LABELS,NOLABELS,NOVALLABS@}
+                @{PIVOT,NOPIVOT@}
+                @{AVALUE,DVALUE@}
+                @{NOINDEX,INDEX@}
+                @{BOX,NOBOX@}
+        /CELLS=@{COUNT,ROW,COLUMN,TOTAL,EXPECTED,RESIDUAL,SRESIDUAL,
+                ASRESIDUAL,ALL,NONE@}
+        /STATISTICS=@{CHISQ,PHI,CC,LAMBDA,UC,BTAU,CTAU,RISK,GAMMA,D,
+                     KAPPA,ETA,CORR,ALL,NONE@}
+        
+(Integer mode.)
+        /VARIABLES=var_list (low,high)@dots{}
+@end display
+
+CROSSTABS reads the active file and builds and displays crosstabulation
+tables requested by the user.  It can calculate several statistics for
+each cell in the crosstabulation tables.  In addition, a number of
+statistics can be calculated for each table itself.
+
+The TABLES subcommand is used to specify the tables to be reported.  Any
+number of dimensions is permitted, and any number of variables per
+dimension is allowed.  The TABLES subcommand may be repeated as many
+times as needed.  This is the only required subcommand in @dfn{general
+mode}.
+
+Occasionally, one may want to invoke a special mode called @dfn{integer
+mode}.  Normally, in general mode, PSPP will automatically determine
+what values occur in the data.  In integer mode, the user specifies the
+range of values that the data assumes.  To invoke this mode, specify the
+VARIABLES subcommand, giving a range of data values in parentheses for
+each variable to be used on the TABLES subcommand.  Data values inside
+the range are truncated to the nearest integer, then assigned to that
+value.  If values occur outside this range, they are discarded.  When it
+is present, the VARIABLES subcommand must precede the TABLES subcommand.
+
+The MISSING subcommand determines the handling of user-missing values.
+When set to TABLE, the default, missing values are dropped on a table by
+table basis.  When set to INCLUDE, user-missing values are included in
+tables and statistics.  When set to REPORT, which is allowed only in
+integer mode, user-missing values are included in tables but marked with
+an @samp{M} (for ``missing'') and excluded from statistical
+calculations.
+
+Currently the WRITE subcommand is not used.
+
+The FORMAT subcommand controls the characteristics of the
+crosstabulation tables to be displayed.  It has a number of possible
+settings:
+
+@itemize @bullet
+@item
+TABLES, the default, causes crosstabulation tables to be output.
+NOTABLES suppresses them.
+
+@item
+LABELS, the default, allows variable labels and value labels to appear
+in the output.  NOLABELS suppresses them.  NOVALLABS displays variable
+labels but suppresses value labels.
+
+@item
+PIVOT, the default, causes each TABLES subcommand to be displayed in a
+pivot table format.  NOPIVOT causes the old-style crosstabulation format
+to be used.
+
+@item
+AVALUE, the default, causes values to be sorted in ascending order.
+DVALUE asserts a descending sort order.
+
+@item
+INDEX/NOINDEX is currently ignored.
+
+@item
+BOX/NOBOX is currently ignored.
+@end itemize
+
+The CELLS subcommand controls the contents of each cell in the displayed
+crosstabulation table.  The possible settings are:
+
+@table @asis
+@item COUNT
+Frequency count.
+@item ROW
+Row percent.
+@item COLUMN
+Column percent.
+@item TOTAL
+Table percent.
+@item EXPECTED
+Expected value.
+@item RESIDUAL 
+Residual.
+@item SRESIDUAL
+Standardized residual.
+@item ASRESIDUAL
+Adjusted standardized residual.
+@item ALL
+All of the above.
+@item NONE
+Suppress cells entirely.
+@end table
+
+@samp{/CELLS} without any settings specified requests COUNT, ROW,
+COLUMN, and TOTAL.  If CELLS is not specified at all then only COUNT
+will be selected.
+
+The STATISTICS subcommand selects statistics for computation:
+
+@table @asis
+@item CHISQ
+Pearson chi-square, likelihood ratio, Fisher's exact test, continuity
+correction, linear-by-linear association.
+@item PHI
+Phi.
+@item CC
+Contingency coefficient.
+@item LAMBDA
+Lambda.
+@item UC
+Uncertainty coefficient.
+@item BTAU
+Tau-b.
+@item CTAU
+Tau-c.
+@item RISK
+Risk estimate.
+@item GAMMA
+Gamma.
+@item D
+Somers' D.
+@item KAPPA
+Cohen's Kappa.
+@item ETA
+Eta.
+@item CORR
+Spearman correlation, Pearson's r.
+@item ALL
+All of the above.
+@item NONE
+No statistics.
+@end table
+
+Selected statistics are only calculated when appropriate for the
+statistic.  Certain statistics require tables of a particular size, and
+some statistics are calculated only in integer mode.
+
+@samp{/STATISTICS} without any settings selects CHISQ.  If the
+STATISTICS subcommand is not given, no statistics are calculated.
+
+@strong{Please note:} Currently the implementation of CROSSTABS has the
+followings bugs:
+
+@itemize @bullet
+@item
+Pearson's R (but not Spearman!) is off a little.
+@item
+T values for Spearman's R and Pearson's R are wrong.
+@item
+How to calculate significance of symmetric and directional measures?
+@item
+Asymmetric ASEs and T values for lambda are wrong.
+@item
+ASE of Goodman and Kruskal's tau is not calculated.
+@item
+ASE of symmetric somers' d is wrong.
+@item
+Approx. T of uncertainty coefficient is wrong.
+@end itemize
+
+Fix for any of these deficiencies would be welcomed.
+
+@node Utilities, Not Implemented, Statistics, Top
+@chapter Utilities
+
+Commands that don't fit any other category are placed here.
+
+Most of these commands are not affected by commands like IF and LOOP:
+they take effect only once, unconditionally, at the time that they are
+encountered in the input.
+
+@menu
+* COMMENT::                     Document your syntax file.
+* DOCUMENT::                    Document the active file.
+* DISPLAY DOCUMENTS::           Display active file documents.
+* DISPLAY FILE LABEL::          Display the active file label.
+* DROP DOCUMENTS::              Remove documents from the active file.
+* EXECUTE::                     Execute pending transformations.
+* FILE LABEL::                  Set the active file's label.
+* INCLUDE::                     Include a file within the current one.
+* QUIT::                        Terminate the PSPP session.
+* SET::                         Adjust PSPP runtime parameters.
+* SUBTITLE::                    Provide a document subtitle.
+* SYSFILE INFO::                Display the dictionary in a system file.
+* TITLE::                       Provide a document title.
+@end menu
+
+@node COMMENT, DOCUMENT, Utilities, Utilities
+@section COMMENT
+@vindex COMMENT
+@vindex *
+
+@display 
+Two possibles syntaxes:
+        COMMENT comment text @dots{} .
+        *comment text @dots{} .
+@end display
+
+The COMMENT command is ignored.  It is used to provide information to
+the author and other readers of the PSPP syntax file.  
+
+A COMMENT command can extend over any number of lines.  Don't forget to
+terminate it with a dot or a blank line!
+
+@node DOCUMENT, DISPLAY DOCUMENTS, COMMENT, Utilities
+@section DOCUMENT
+@vindex DOCUMENT
+
+@display
+DOCUMENT documentary_text.
+@end display
+
+The DOCUMENT command adds one or more lines of descriptive commentary to
+the active file.  Documents added in this way are saved to system files.
+They can be viewed using SYSFILE INFO or DISPLAY DOCUMENTS.  They can be
+removed from the active file with DROP DOCUMENTS.
+
+Specify the documentary text following the DOCUMENT keyword.  You can
+extend the documentary text over as many lines as necessary.  Lines are
+truncated at 80 characters width.  Don't forget to terminate the
+DOCUMENT command with a dot or a blank line.
+
+@node DISPLAY DOCUMENTS, DISPLAY FILE LABEL, DOCUMENT, Utilities
+@section DISPLAY DOCUMENTS
+@vindex DISPLAY DOCUMENTS
+
+@display
+DISPLAY DOCUMENTS.
+@end display
+
+DISPLAY DOCUMENTS displays the documents in the active file.  Each
+document is preceded by a line giving the time and date that it was
+added.  @xref{DOCUMENT}.
+
+@node DISPLAY FILE LABEL, DROP DOCUMENTS, DISPLAY DOCUMENTS, Utilities
+@section DISPLAY FILE LABEL
+@vindex DISPLAY FILE LABEL
+
+@display
+DISPLAY FILE LABEL.
+@end display
+
+DISPLAY FILE LABEL displays the file label contained in the active file,
+if any.  @xref{FILE LABEL}.
+
+@node DROP DOCUMENTS, EXECUTE, DISPLAY FILE LABEL, Utilities
+@section DROP DOCUMENTS
+@vindex DROP DOCUMENTS
+
+@display
+DROP DOCUMENTS.
+@end display
+
+The DROP DOCUMENTS command removes all documents from the active file.
+New documents can be added with the DOCUMENT utility (@pxref{DOCUMENT}).
+
+DROP DOCUMENTS only changes the active file.  It does not modify any
+system files stored on disk.
+
+@node EXECUTE, FILE LABEL, DROP DOCUMENTS, Utilities
+@section EXECUTE
+@vindex EXECUTE
+
+@display
+EXECUTE.
+@end display
+
+The EXECUTE utility causes the active file to be read and all pending
+transformations to be executed.
+
+@node FILE LABEL, INCLUDE, EXECUTE, Utilities
+@section FILE LABEL
+@vindex FILE LABEL
+
+@display
+FILE LABEL file_label.
+@end display
+
+Use the FILE LABEL command to provide a title for the active file.  This
+title will be saved into system files and portable files that are
+created during this PSPP run.
+
+It is not necessary to include quotes around file_label.  If they are
+included then they become part of the file label.
+
+@node INCLUDE, QUIT, FILE LABEL, Utilities
+@section INCLUDE
+@vindex INCLUDE
+@vindex @@
+
+@display
+Two possible syntaxes:
+        INCLUDE 'filename'.
+        @@filename.
+@end display
+
+The INCLUDE command causes the PSPP command processor to read an
+additional command file as if it were included bodily in the current
+command file.
+
+INCLUDE files may be nested to any depth, up to the limit of available
+memory.
+
+@node QUIT, SET, INCLUDE, Utilities
+@section QUIT
+@vindex QUIT
+
+@display
+Two possible syntaxes:
+        QUIT.
+        EXIT.
+@end display
+
+The QUIT command terminates the current PSPP session and returns control
+to the operating system.  
+
+This command is not valid within a command file.
+
+@node SET, SUBTITLE, QUIT, Utilities
+@section SET
+@vindex SET
+
+@display
+SET
+
+(data input)
+        /BLANKS=@{SYSMIS,'.',number@}
+        /DECIMAL=@{DOT,COMMA@}
+        /FORMAT=fmt_spec
+
+(program input)
+        /ENDCMD='.'
+        /NULLINE=@{ON,OFF@}
+
+(interaction)
+        /CPROMPT='cprompt_string'
+        /DPROMPT='dprompt_string'
+        /ERRORBREAK=@{OFF,ON@}
+        /MXERRS=max_errs
+        /MXWARNS=max_warnings
+        /PROMPT='prompt'
+        /VIEWLENGTH=@{MINIMUM,MEDIAN,MAXIMUM,n_lines@}
+        /VIEWWIDTH=n_characters
+
+(program execution)
+        /MEXPAND=@{ON,OFF@}
+        /MITERATE=max_iterations
+        /MNEST=max_nest
+        /MPRINT=@{ON,OFF@}
+        /MXLOOPS=max_loops
+        /SEED=@{RANDOM,seed_value@}
+        /UNDEFINED=@{WARN,NOWARN@}
+
+(data output)
+        /CC@{A,B,C,D,E@}=@{'npre,pre,suf,nsuf','npre.pre.suf.nsuf'@}
+        /DECIMAL=@{DOT,COMMA@}
+        /FORMAT=fmt_spec
+
+(output routing)
+        /ECHO=@{ON,OFF@}
+        /ERRORS=@{ON,OFF,TERMINAL,LISTING,BOTH,NONE@}
+        /INCLUDE=@{ON,OFF@}
+        /MESSAGES=@{ON,OFF,TERMINAL,LISTING,BOTH,NONE@}
+        /PRINTBACK=@{ON,OFF@}
+        /RESULTS=@{ON,OFF,TERMINAL,LISTING,BOTH,NONE@}
+
+(output activation)
+        /LISTING=@{ON,OFF@}
+        /PRINTER=@{ON,OFF@}
+        /SCREEN=@{ON,OFF@}
+
+(output driver options)
+        /HEADERS=@{NO,YES,BLANK@}
+        /LENGTH=@{NONE,length_in_lines@}
+        /LISTING=filename
+        /MORE=@{ON,OFF@}
+        /PAGER=@{OFF,"pager_name"@}
+        /WIDTH=@{NARROW,WIDTH,n_characters@}
+
+(logging)
+        /JOURNAL=@{ON,OFF@} [filename]
+        /LOG=@{ON,OFF@} [filename]
+
+(system files)
+        /COMPRESSION=@{ON,OFF@}
+        /SCOMPRESSION=@{ON,OFF@}
+
+(security)
+        /SAFER=ON
+
+(obsolete settings accepted for compatibility, but ignored)
+        /AUTOMENU=@{ON,OFF@}
+        /BEEP=@{ON,OFF@}
+        /BLOCK='c'
+        /BOXSTRING=@{'xxx','xxxxxxxxxxx'@}
+        /CASE=@{UPPER,UPLOW@}
+        /COLOR=@dots{}
+        /CPI=cpi_value
+        /DISK=@{ON,OFF@}
+        /EJECT=@{ON,OFF@}
+        /HELPWINDOWS=@{ON,OFF@}
+        /HIGHRES=@{ON,OFF@}
+        /HISTOGRAM='c'
+        /LOWRES=@{AUTO,ON,OFF@}
+        /LPI=lpi_value
+        /MENUS=@{STANDARD,EXTENDED@}
+        /MXMEMORY=max_memory
+        /PTRANSLATE=@{ON,OFF@}
+        /RCOLORS=@dots{}
+        /RUNREVIEW=@{AUTO,MANUAL@}
+        /SCRIPTTAB='c'
+        /TB1=@{'xxx','xxxxxxxxxxx'@}
+        /TBFONTS='string'
+        /WORKDEV=drive_letter
+        /WORKSPACE=workspace_size
+        /XSORT=@{YES,NO@}
+@end display
+
+The SET command allows the user to adjust several parameters relating to
+PSPP's execution.  Since there are many subcommands to this command, its
+subcommands will be examined in groups.
+
+As a general comment, ON and YES are considered synonymous, and
+so are OFF and NO, when used as subcommand values.
+
+The data input subcommands affect the way that data is read from data
+files.  The data input subcommands are
+
+@table @asis
+@item BLANKS
+This is the value assigned to an item data item that is empty or
+contains only whitespace.  An argument of SYSMIS or '.' will cause the
+system-missing value to be assigned to null items.  This is the
+default.  Any real value may be assigned.
+
+@item DECIMAL
+The default DOT setting causes the decimal point character to be
+@samp{.}.  A setting of COMMA causes the decimal point character to be
+@samp{,}.
+
+@item FORMAT
+Allows the default numeric input/output format to be specified.  The
+default is F8.2.  @xref{Input/Output Formats}.
+@end table
+
+Program input subcommands affect the way that programs are parsed when
+they are typed interactively or run from a script.  They are
+
+@table @asis
+@item ENDCMD
+This is a single character indicating the end of a command.  The default
+is @samp{.}.  Don't change this.
+
+@item NULLINE
+Whether a blank line is interpreted as ending the current command.  The
+default is ON.
+@end table
+
+Interaction subcommands affect the way that PSPP interacts with an
+online user.  The interaction subcommands are
+
+@table @asis
+@item CPROMPT
+The command continuation prompt.  The default is @samp{    > }.
+
+@item DPROMPT
+Prompt used when expecting data input within BEGIN DATA (@pxref{BEGIN
+DATA}).  The default is @samp{data> }.
+
+@item ERRORBREAK
+Whether an error causes PSPP to stop processing the current command
+file after finishing the current command.  The default is OFF.
+
+@item MXERRS
+The maximum number of errors before PSPP halts processing of the current
+command file.  The default is 50.
+
+@item MXWARNS
+The maximum number of warnings + errors before PSPP halts processing the
+current command file.  The default is 100.
+
+@item PROMPT
+The command prompt.  The default is @samp{PSPP> }.
+
+@item VIEWLENGTH
+The length of the screen in lines.  MINIMUM means 25 lines, MEDIAN and
+MAXIMUM mean 43 lines.  Otherwise specify the number of lines.  Normally
+PSPP should auto-detect your screen size so this shouldn't have to be
+used.
+
+@item VIEWWIDTH
+The width of the screen in characters.  Normally 80 or 132.
+@end table
+
+Program execution subcommands control the way that PSPP commands
+execute.  The program execution subcommands are
+
+@table @asis
+@item MEXPAND
+@itemx MITERATE
+@itemx MNEST
+@itemx MPRINT
+Currently not used.
+
+@item MXLOOPS
+The maximum number of iterations for an uncontrolled loop.
+
+@item SEED
+The initial pseudo-random number seed.  Set to a real number or to
+RANDOM, which will obtain an initial seed from the current time of day.
+
+@item UNDEFINED
+Currently not used.
+@end table
+
+Data output subcommands affect the format of output data.  These
+subcommands are
+
+@table @asis
+@item CCA
+@itemx CCB
+@itemx CCC
+@itemx CCD
+@itemx CCE
+Set up custom currency formats.  The argument is a string which must
+contain exactly three commas or exactly three periods.  If commas, then
+the grouping character for the currency format is @samp{,}, and the
+decimal point character is @samp{.}; if periods, then the situation is
+reversed.  
+
+The commas or periods divide the string into four fields, which are, in
+order, the negative prefix, prefix, suffix, and negative suffix.  When a
+value is formatted using the custom currency format, the prefix precedes
+the value formatted and the suffix follows it.  In addition, if the
+value is negative, the negative prefix precedes the prefix and the
+negative suffix follows the suffix.
+
+@item DECIMAL
+The default DOT setting causes the decimal point character to be
+@samp{.}.  A setting of COMMA causes the decimal point character to be
+@samp{,}.
+
+@item FORMAT
+Allows the default numeric input/output format to be specified.  The
+default is F8.2.  @xref{Input/Output Formats}.
+@end table
+
+Output routing subcommands affect where the output of transformations
+and procedures is sent.  These subcommands are
+
+@table @asis
+@item ECHO
+
+If turned on, commands are written to the listing file as they are read
+from command files.  The default is OFF.
+
+@itemx ERRORS
+@itemx INCLUDE
+@itemx MESSAGES
+@item PRINTBACK
+@item RESULTS
+Currently not used.
+@end table
+
+Output activation subcommands affect whether output devices of
+particular types are enabled.  These subcommands are
+
+@table @asis
+@item LISTING
+Enable or disable listing devices.
+
+@item PRINTER
+Enable or disable printer devices.
+
+@item SCREEN
+Enable or disable screen devices.
+@end table
+
+Output driver option subcommands affect output drivers' settings.  These
+subcommands are
+
+@table @asis
+@item HEADERS
+@itemx LENGTH
+@itemx LISTING
+@itemx MORE
+@itemx PAGER 
+@itemx WIDTH
+Currently not used.
+@end table
+
+Logging subcommands affect logging of commands executed to external
+files.  These subcommands are
+
+@table @asis
+@item JOURNAL
+@item LOG
+Not currently used.
+@end table
+
+System file subcommands affect the default format of system files
+produced by PSPP.  These subcommands are
+
+@table @asis
+@item COMPRESSION
+Not currently used.
+
+@item SCOMPRESSION
+Whether system files created by SAVE or XSAVE are compressed by default.
+The default is ON.
+@end table
+
+Security subcommands affect the operations that commands are allowed to
+perform.  The security subcommands are
+
+@table @asis
+@item SAFER
+When set, this setting cannot ever be reset, for obvious security
+reasons.  Setting this option disables the following operations:
+
+@itemize @bullet
+@item
+The ERASE command.
+@item
+The HOST command.
+@item
+Pipe filenames (filenames beginning or ending with @samp{|}).
+@item
+@end itemize
+
+Be aware that this setting does not guarantee safety (commands can still
+overwrite files, for instance) but it is an improvement.
+@end table
+
+@node SUBTITLE, TITLE, SET, Utilities
+@section SUBTITLE
+@vindex SUBTITLE
+
+@display
+Two possible syntaxes:
+        SUBTITLE 'subtitle_string'.
+        SUBTITLE subtitle_string.
+@end display
+
+The SUBTITLE command is used to provide a subtitle to a particular PSPP
+run.  This subtitle appears at the top of each output page below the
+title, if titles are enabled on the output device.
+
+Specify a subtitle as a string in quotes.  The alternate syntax that did
+not require quotes is now obsolete.  If it is used then the subtitle is
+converted to all uppercase.
+
+@node TITLE,  , SUBTITLE, Utilities
+@section TITLE
+@vindex TITLE
+
+@display
+Two possible syntaxes:
+        TITLE 'title_string'.
+        TITLE title_string.
+@end display
+
+The TITLE command is used to provide a title to a particular PSPP run.
+This title appears at the top of each output page, if titles are enabled
+on the output device.
+
+Specify a title as a string in quotes.  The alternate syntax that did
+not require quotes is now obsolete.  If it is used then the title is
+converted to all uppercase.
+
+@node Not Implemented, Data File Format, Utilities, Top
+@chapter Not Implemented
+
+This chapter lists parts of the PSPP language that are not yet
+implemented.
+
+The following transformations and utilities are not yet implemented, but
+they will be supported in a later release.
+
+@itemize @bullet
+@item
+ADD FILES
+@item
+DEFINE
+@item
+FILE TYPE
+@item
+GET SAS
+@item
+GET TRANSLATE
+@item
+MCONVERT
+@item
+PRESERVE
+@item
+PROCEDURE OUTPUT
+@item
+RESTORE
+@item
+SAVE TRANSLATE
+@item
+SHOW
+@item
+UPDATE
+@end itemize
+
+The following transformations and utilities are not implemented.  There
+are no plans to support them in future releases.  Contributions to
+implement them will still be accepted.
+
+@itemize @bullet
+@item
+EDIT
+@item
+GET DATABASE
+@item
+GET OSIRIS
+@item
+GET SCSS
+@item
+GSET
+@item
+HELP
+@item
+INFO
+@item
+INPUT MATRIX
+@item
+KEYED DATA LIST
+@item
+NUMBERED and UNNUMBERED
+@item
+OPTIONS
+@item
+REVIEW
+@item
+SAVE SCSS
+@item
+SPSS MANAGER
+@item
+STATISTICS
+@end itemize
+
+@node Data File Format, Portable File Format, Not Implemented, Top
+@chapter Data File Format
+
+PSPP necessarily uses the same format for system files as do the
+products with which it is compatible.  This chapter is a description of
+that format.
+
+There are three data types used in system files: 32-bit integers, 64-bit
+floating points, and 1-byte characters.  In this document these will
+simply be referred to as @code{int32}, @code{flt64}, and @code{char},
+the names that are used in the PSPP source code.  Every field of type
+@code{int32} or @code{flt64} is aligned on a 32-bit boundary.
+
+The endianness of data in PSPP system files is not specified.  System
+files output on a computer of a particular endianness will have the
+endianness of that computer.  However, PSPP can read files of either
+endianness, regardless of its host computer's endianness.  PSPP
+translates endianness for both integer and floating point numbers.
+
+Floating point formats are also not specified.  PSPP does not
+translate between floating point formats.  This is unlikely to be a
+problem as all modern computer architectures use IEEE 754 format for
+floating point representation.
+
+The PSPP system-missing value is represented by the largest possible
+negative number in the floating point format; in C, this is most likely
+@code{-DBL_MAX}.  There are two other important values used in missing
+values: @code{HIGHEST} and @code{LOWEST}.  These are represented by the
+largest possible positive number (probably @code{DBL_MAX}) and the
+second-largest negative number.  The latter must be determined in a
+system-dependent manner; in IEEE 754 format it is represented by value
+@code{0xffeffffffffffffe}.
+
+System files are divided into records.  Each record begins with an
+@code{int32} giving a numeric record type.  Individual record types are
+described below:
+
+@menu
+* File Header Record::          
+* Variable Record::             
+* Value Label Record::          
+* Value Label Variable Record::  
+* Document Record::             
+* Machine int32 Info Record::   
+* Machine flt64 Info Record::   
+* Miscellaneous Informational Records::  
+* Dictionary Termination Record::  
+* Data Record::                 
+@end menu
+
+@node File Header Record, Variable Record, Data File Format, Data File Format
+@section File Header Record
+
+The file header is always the first record in the file.
+
+@example
+struct sysfile_header
+  @{
+    char                rec_type[4];
+    char                prod_name[60];
+    int32               layout_code;
+    int32               case_size;
+    int32               compressed;
+    int32               weight_index;
+    int32               ncases;
+    flt64               bias;
+    char                creation_date[9];
+    char                creation_time[8];
+    char                file_label[64];
+    char                padding[3];
+  @};
+@end example
+
+@table @code
+@item char rec_type[4];
+Record type code.  Always set to @samp{$FL2}.  This is the only record
+for which the record type is not of type @code{int32}.
+
+@item char prod_name[60];
+Product identification string.  This always begins with the characters
+@samp{@@(#) SPSS DATA FILE}.  PSPP uses the remaining characters to
+give its version and the operating system name; for example, @samp{GNU
+pspp 0.1.4 - sparc-sun-solaris2.5.2}.  The string is truncated if it
+would be longer than 60 characters; otherwise it is padded on the right
+with spaces.
+
+@item int32 layout_code;
+Always set to 2.  PSPP reads this value in order to determine the
+file's endianness.
+
+@item int32 case_size;
+Number of data elements per case.  This is the number of variables,
+except that long string variables add extra data elements (one for every
+8 characters after the first 8).
+
+@item int32 compressed;
+Set to 1 if the data in the file is compressed, 0 otherwise.
+
+@item int32 weight_index;
+If one of the variables in the data set is used as a weighting variable,
+set to the index of that variable.  Otherwise, set to 0.
+
+@item int32 ncases;
+Set to the number of cases in the file if it is known, or -1 otherwise.
+
+In the general case it is not possible to determine the number of cases
+that will be output to a system file at the time that the header is
+written.  The way that this is dealt with is by writing the entire
+system file, including the header, then seeking back to the beginning of
+the file and writing just the @code{ncases} field.  For `files' in which
+this is not valid, the seek operation fails.  In this case,
+@code{ncases} remains -1.
+
+@item flt64 bias;
+Compression bias.  Always set to 100.  The significance of this value is
+that only numbers between @code{(1 - bias)} and @code{(251 - bias)} can
+be compressed.
+
+@item char creation_date[9];
+Set to the date of creation of the system file, in @samp{dd mmm yy}
+format, with the month as standard English abbreviations, using an
+initial capital letter and following with lowercase.  If the date is not
+available then this field is arbitrarily set to @samp{01 Jan 70}.
+
+@item char creation_time[8];
+Set to the time of creation of the system file, in @samp{hh:mm:ss}
+format and using 24-hour time.  If the time is not available then this
+field is arbitrarily set to @samp{00:00:00}.
+
+@item char file_label[64];
+Set the the file label declared by the user, if any.  Padded on the
+right with spaces.
+
+@item char padding[3];
+Ignored padding bytes to make the structure a multiple of 32 bits in
+length.  Set to zeros.
+@end table
+
+@node Variable Record, Value Label Record, File Header Record, Data File Format
+@section Variable Record
+
+Immediately following the header must come the variable records.  There
+must be one variable record for every variable and every 8 characters in
+a long string beyond the first 8; i.e., there must be exactly as many
+variable records as the value specified for @code{case_size} in the file
+header record.
+
+@example
+struct sysfile_variable
+  @{
+    int32               rec_type;
+    int32               type;
+    int32               has_var_label;
+    int32               n_missing_values;
+    int32               print;
+    int32               write;
+    char                name[8];
+
+    /* The following two fields are present 
+       only if has_var_label is 1. */
+    int32               label_len;
+    char                label[/* variable length */];
+
+    /* The following field is present only
+       if n_missing_values is not 0. */
+    flt64               missing_values[/* variable length*/];
+  @};
+@end example
+
+@table @code
+@item int32 rec_type;
+Record type code.  Always set to 2.
+
+@item int32 type;
+Variable type code.  Set to 0 for a numeric variable.  For a short
+string variable or the first part of a long string variable, this is set
+to the width of the string.  For the second and subsequent parts of a
+long string variable, set to -1, and the remaining fields in the
+structure are ignored.
+
+@item int32 has_var_label;
+If this variable has a variable label, set to 1; otherwise, set to 0.
+
+@item int32 n_missing_values;
+If the variable has no missing values, set to 0.  If the variable has
+one, two, or three discrete missing values, set to 1, 2, or 3,
+respectively.  If the variable has a range for missing variables, set to
+-2; if the variable has a range for missing variables plus a single
+discrete value, set to -3.
+
+@item int32 print;
+Print format for this variable.  See below.
+
+@item int32 write;
+Write format for this variable.  See below.
+
+@item char name[8];
+Variable name.  The variable name must begin with a capital letter or
+the at-sign (@samp{@@}).  Subsequent characters may also be octothorpes
+(@samp{#}), dollar signs (@samp{$}), underscores (@samp{_}), or full
+stops (@samp{.}).  The variable name is padded on the right with spaces.
+
+@item int32 label_len;
+This field is present only if @code{has_var_label} is set to 1.  It is
+set to the length, in characters, of the variable label, which must be a
+number between 0 and 120.
+
+@item char label[/* variable length */];
+This field is present only if @code{has_var_label} is set to 1.  It has
+length @code{label_len}, rounded up to the nearest multiple of 32 bits.
+The first @code{label_len} characters are the variable's variable label.
+
+@item flt64 missing_values[/* variable length */];
+This field is present only if @code{n_missing_values} is not 0.  It has
+the same number of elements as the absolute value of
+@code{n_missing_values}.  For discrete missing values, each element
+represents one missing value.  When a range is present, the first
+element denotes the minimum value in the range, and the second element
+denotes the maximum value in the range.  When a range plus a value are
+present, the third element denotes the additional discrete missing
+value.  HIGHEST and LOWEST are indicated as described in the chapter
+introduction.
+@end table
+
+The @code{print} and @code{write} members of sysfile_variable are output
+formats coded into @code{int32} types.  The LSB (least-significant byte)
+of the @code{int32} represents the number of decimal places, and the
+next two bytes in order of increasing significance represent field width
+and format type, respectively.  The MSB (most-significant byte) is not
+used and should be set to zero.
+
+Format types are defined as follows:
+@table @asis
+@item 0
+Not used.
+@item 1
+@code{A}
+@item 2
+@code{AHEX}
+@item 3
+@code{COMMA}
+@item 4
+@code{DOLLAR}
+@item 5
+@code{F}
+@item 6
+@code{IB}
+@item 7
+@code{PIBHEX}
+@item 8
+@code{P}
+@item 9
+@code{PIB}
+@item 10
+@code{PK}
+@item 11
+@code{RB}
+@item 12
+@code{RBHEX}
+@item 13
+Not used.
+@item 14
+Not used.
+@item 15
+@code{Z}
+@item 16
+@code{N}
+@item 17
+@code{E}
+@item 18
+Not used.
+@item 19
+Not used.
+@item 20
+@code{DATE}
+@item 21
+@code{TIME}
+@item 22
+@code{DATETIME}
+@item 23
+@code{ADATE}
+@item 24
+@code{JDATE}
+@item 25
+@code{DTIME}
+@item 26
+@code{WKDAY}
+@item 27
+@code{MONTH}
+@item 28
+@code{MOYR}
+@item 29
+@code{QYR}
+@item 30
+@code{WKYR}
+@item 31
+@code{PCT}
+@item 32
+@code{DOT}
+@item 33
+@code{CCA}
+@item 34
+@code{CCB}
+@item 35
+@code{CCC}
+@item 36
+@code{CCD}
+@item 37
+@code{CCE}
+@item 38
+@code{EDATE}
+@item 39
+@code{SDATE}
+@end table
+
+@node Value Label Record, Value Label Variable Record, Variable Record, Data File Format
+@section Value Label Record
+
+Value label records must follow the variable records and must precede
+the header termination record.  Other than this, they may appear
+anywhere in the system file.  Every value label record must be
+immediately followed by a label variable record, described below.
+
+Value label records begin with @code{rec_type}, an @code{int32} value
+set to the record type of 3.  This is followed by @code{count}, an
+@code{int32} value set to the number of value labels present in this
+record.
+
+These two fields are followed by a series of @code{count} tuples.  Each
+tuple is divided into two fields, the value and the label.  The first of
+these, the value, is composed of a 64-bit value, which is either a
+@code{flt64} value or up to 8 characters (padded on the right to 8
+bytes) denoting a short string value.  Whether the value is a
+@code{flt64} or a character string is not defined inside the value label
+record.
+
+The second field in the tuple, the label, has variable length.  The
+first @code{char} is a count of the number of characters in the value
+label.  The remainder of the field is the label itself.  The field is
+padded on the right to a multiple of 64 bits in length.
+
+@node Value Label Variable Record, Document Record, Value Label Record, Data File Format
+@section Value Label Variable Record
+
+Every value label variable record must be immediately preceded by a
+value label record, described above.
+
+@example
+struct sysfile_value_label_variable
+  @{
+     int32              rec_type;
+     int32              count;
+     int32              vars[/* variable length */];
+  @};
+@end example
+
+@table @code
+@item int32 rec_type;
+Record type.  Always set to 4.
+
+@item int32 count;
+Number of variables that the associated value labels from the value
+label record are to be applied.
+
+@item int32 vars[/* variable length];
+A list of variables to which to apply the value labels.  There are
+@code{count} elements.
+@end table
+
+@node Document Record, Machine int32 Info Record, Value Label Variable Record, Data File Format
+@section Document Record
+
+There must be no more than one document record per system file.
+Document records must follow the variable records and precede the
+dictionary termination record.
+
+@example
+struct sysfile_document
+  @{
+    int32               rec_type;
+    int32               n_lines;
+    char                lines[/* variable length */][80];
+  @};
+@end example
+
+@table @code
+@item int32 rec_type;
+Record type.  Always set to 6.
+
+@item int32 n_lines;
+Number of lines of documents present.
+
+@item char lines[/* variable length */][80];
+Document lines.  The number of elements is defined by @code{n_lines}.
+Lines shorter than 80 characters are padded on the right with spaces.
+@end table
+
+@node Machine int32 Info Record, Machine flt64 Info Record, Document Record, Data File Format
+@section Machine @code{int32} Info Record
+
+There must be no more than one machine @code{int32} info record per
+system file.  Machine @code{int32} info records must follow the variable
+records and precede the dictionary termination record.
+
+@example
+struct sysfile_machine_int32_info
+  @{
+    /* Header. */
+    int32               rec_type;
+    int32               subtype;
+    int32               size;
+    int32               count;
+
+    /* Data. */
+    int32               version_major;
+    int32               version_minor;
+    int32               version_revision;
+    int32               machine_code;
+    int32               floating_point_rep;
+    int32               compression_code;
+    int32               endianness;
+    int32               character_code;
+  @};
+@end example
+
+@table @code
+@item int32 rec_type;
+Record type.  Always set to 7.
+
+@item int32 subtype;
+Record subtype.  Always set to 3.
+
+@item int32 size;
+Size of each piece of data in the data part, in bytes.  Always set to 4.
+
+@item int32 count;
+Number of pieces of data in the data part.  Always set to 8.
+
+@item int32 version_major;
+PSPP major version number.  In version @var{x}.@var{y}.@var{z}, this
+is @var{x}.
+
+@item int32 version_minor;
+PSPP minor version number.  In version @var{x}.@var{y}.@var{z}, this
+is @var{y}.
+
+@item int32 version_revision;
+PSPP version revision number.  In version @var{x}.@var{y}.@var{z},
+this is @var{z}.
+
+@item int32 machine_code;
+Machine code.  PSPP always set this field to value to -1, but other
+values may appear.
+
+@item int32 floating_point_rep;
+Floating point representation code.  For IEEE 754 systems this is 1.
+IBM 370 sets this to 2, and DEC VAX E to 3.
+
+@item int32 compression_code;
+Compression code.  Always set to 1.
+
+@item int32 endianness;
+Machine endianness.  1 indicates big-endian, 2 indicates little-endian.
+
+@item int32 character_code;
+Character code.  1 indicates EBCDIC, 2 indicates 7-bit ASCII, 3
+indicates 8-bit ASCII, 4 indicates DEC Kanji.
+@end table
+
+@node Machine flt64 Info Record, Miscellaneous Informational Records, Machine int32 Info Record, Data File Format
+@section Machine @code{flt64} Info Record
+
+There must be no more than one machine @code{flt64} info record per
+system file.  Machine @code{flt64} info records must follow the variable
+records and precede the dictionary termination record.
+
+@example
+struct sysfile_machine_flt64_info
+  @{
+    /* Header. */
+    int32               rec_type;
+    int32               subtype;
+    int32               size;
+    int32               count;
+
+    /* Data. */
+    flt64               sysmis;
+    flt64               highest;
+    flt64               lowest;
+  @};
+@end example
+
+@table @code
+@item int32 rec_type;
+Record type.  Always set to 3.
+
+@item int32 subtype;
+Record subtype.  Always set to 4.
+
+@item int32 size;
+Size of each piece of data in the data part, in bytes.  Always set to 4.
+
+@item int32 count;
+Number of pieces of data in the data part.  Always set to 3.
+
+@item flt64 sysmis;
+The system missing value.
+
+@item flt64 highest;
+The value used for HIGHEST in missing values.
+
+@item flt64 lowest;
+The value used for LOWEST in missing values.
+@end table
+
+@node Miscellaneous Informational Records, Dictionary Termination Record, Machine flt64 Info Record, Data File Format
+@section Miscellaneous Informational Records
+
+Miscellaneous informational records must follow the variable records and
+precede the dictionary termination record.
+
+Miscellaneous informational records are ignored by PSPP when reading
+system files.  They are not written by PSPP when writing system files.
+
+@example
+struct sysfile_misc_info
+  @{
+    /* Header. */
+    int32               rec_type;
+    int32               subtype;
+    int32               size;
+    int32               count;
+
+    /* Data. */
+    char                data[/* variable length */];
+  @};
+@end example
+
+@table @code
+@item int32 rec_type;
+Record type.  Always set to 3.
+
+@item int32 subtype;
+Record subtype.  May take any value.
+
+@item int32 size;
+Size of each piece of data in the data part.  Should have the value 4 or
+8, for @code{int32} and @code{flt64}, respectively.
+
+@item int32 count;
+Number of pieces of data in the data part.
+
+@item char data[/* variable length */];
+Arbitrary data.  There must be @code{size} times @code{count} bytes of
+data.
+@end table
+
+@node Dictionary Termination Record, Data Record, Miscellaneous Informational Records, Data File Format
+@section Dictionary Termination Record
+
+The dictionary termination record must follow all other records, except
+for the actual cases, which it must precede.  There must be exactly one
+dictionary termination record in every system file.
+
+@example
+struct sysfile_dict_term
+  @{
+    int32               rec_type;
+    int32               filler;
+  @};
+@end example
+
+@table @code
+@item int32 rec_type;
+Record type.  Always set to 999.
+
+@item int32 filler;
+Ignored padding.  Should be set to 0.
+@end table
+
+@node Data Record,  , Dictionary Termination Record, Data File Format
+@section Data Record
+
+Data records must follow all other records in the data file.  There must
+be at least one data record in every system file.
+
+The format of data records varies depending on whether the data is
+compressed.  Regardless, the data is arranged in a series of 8-byte
+elements.
+
+When data is not compressed, Every case is composed of @code{case_size}
+of these 8-byte elements, where @code{case_size} comes from the file
+header record (@pxref{File Header Record}).  Each element corresponds to
+the variable declared in the respective variable record (@pxref{Variable
+Record}).  Numeric values are given in @code{flt64} format; string
+values are literal characters string, padded on the right when
+necessary.
+
+Compressed data is arranged in the following manner: the first 8-byte
+element in the data section is divided into a series of 1-byte command
+codes.  These codes have meanings as described below:
+
+@table @asis
+@item 0
+Ignored.  If the program writing the system file accumulates compressed
+data in blocks of fixed length, 0 bytes can be used to pad out extra
+bytes remaining at the end of a fixed-size block.
+
+@item 1 through 251
+These values indicate that the corresponding numeric variable has the
+value @code{(@var{code} - @var{bias})} for the case being read, where
+@var{code} is the value of the compression code and @var{bias} is the
+variable @code{compression_bias} from the file header.  For example,
+code 105 with bias 100.0 (the normal value) indicates a numeric variable
+of value 5.
+
+@item 252
+End of file.  This code may or may not appear at the end of the data
+stream.  PSPP always outputs this code but its use is not required.
+
+@item 253
+This value indicates that the numeric or string value is not
+compressible.  The value is stored in the 8-byte element following the
+current block of command bytes.  If this value appears twice in a block
+of command bytes, then it indicates the second element following the
+command bytes, and so on.
+
+@item 254
+Used to indicate a string value that is all spaces.
+
+@item 255
+Used to indicate the system-missing value.
+@end table
+
+When the end of the first 8-byte element of command bytes is reached,
+any blocks of non-compressible values are skipped, and the next element
+of command bytes is read and interpreted, until the end of the file is
+reached.
+
+@node Portable File Format, q2c Input Format, Data File Format, Top
+@chapter Portable File Format
+
+These days, most computers use the same internal data formats for
+integer and floating-point data, if one ignores little differences like
+big- versus little-endian byte ordering.  However, occasionally it is
+necessary to exchange data between systems with incompatible data
+formats.  This is what portable files are designed to do.
+
+@strong{Please note:} Although all of the following information is
+correct, as far as the author has been able to ascertain, it is gleaned
+from examination of ASCII-formatted portable files only, so some of it
+may be incorrect in the general case.
+
+@menu
+* Portable File Characters::    
+* Portable File Structure::     
+* Portable File Header::        
+* Version and Date Info Record::  
+* Identification Records::      
+* Variable Count Record::       
+* Variable Records::            
+* Value Label Records::         
+* Portable File Data::          
+@end menu
+
+@node Portable File Characters, Portable File Structure, Portable File Format, Portable File Format
+@section Portable File Characters
+
+Portable files are arranged as a series of lines of exactly 80
+characters each.  Each line is terminated by a carriage-return,
+line-feed sequence (henceforth, ``newline'').  Newlines are not
+delimiters: they are only used to avoid line-length limitations existing
+on some operating systems.
+
+The file must be terminated with a @samp{Z} character.  In addition, if
+the final line in the file does not have exactly 80 characters, then it
+is padded on the right with @samp{Z} characters.  (The file contents may
+be in any character set; the file contains a description of its own
+character set, as explained in the next section.  Therefore, the
+@samp{Z} character is not necessarily an ASCII @samp{Z}.)
+
+For the rest of the description of the portable file format, newlines
+and the trailing @samp{Z}s will be ignored, as if they did not exist,
+because they are not an important part of understanding the file
+contents.
+
+@node Portable File Structure, Portable File Header, Portable File Characters, Portable File Format
+@section Portable File Structure
+
+Every portable file consists of the following records, in sequence:
+
+@itemize @bullet
+
+@item
+File header.
+
+@item
+Version and date info.
+
+@item
+Product identification.
+
+@item
+Subproduct identification (optional).
+
+@item
+Variable count.
+
+@item
+Variables.  Each variable record may optionally be followed by a
+missing value record and a variable label record.
+
+@item
+Value labels (optional).
+
+@item
+Data.
+@end itemize
+
+Most records are identified by a single-character tag code.  The file
+header and version info record do not have a tag.
+
+Other than these single-character codes, there are three types of fields
+in a portable file: floating-point, integer, and string.  Floating-point
+fields have the following format:
+
+@itemize @bullet
+
+@item
+Zero or more leading spaces.
+
+@item
+Optional asterisk (@samp{*}), which indicates a missing value.  The
+asterisk must be followed by a single character, generally a period
+(@samp{.}), but it appears that other characters may also be possible.
+This completes the specification of a missing value.
+
+@item
+Optional minus sign (@samp{-}) to indicate a negative number.
+
+@item
+A whole number, consisting of one or more base-30 digits: @samp{0}
+through @samp{9} plus capital letters @samp{A} through @samp{T}.
+
+@item
+A fraction, consisting of a radix point (@samp{.}) followed by one or
+more base-30 digits (optional).
+
+@item
+An exponent, consisting of a plus or minus sign (@samp{+} or @samp{-})
+followed by one or more base-30 digits (optional).
+
+@item
+A forward slash (@samp{/}).
+@end itemize
+
+Integer fields take form identical to floating-point fields, but they
+may not contain a fraction.
+
+String fields take the form of a integer field having value @var{n},
+followed by exactly @var{n} characters, which are the string content.
+
+@node Portable File Header, Version and Date Info Record, Portable File Structure, Portable File Format
+@section Portable File Header
+
+Every portable file begins with a 464-byte header, consisting of a
+200-byte collection of vanity splash strings, followed by a 256-byte
+character set translation table, followed by an 8-byte tag string.
+
+The 200-byte segment is divided into five 40-byte sections, each of
+which represents the string @code{ASCII SPSS PORT FILE} in a different
+character set encoding.  (If the file is encoded in EBCDIC then the
+string is actually @code{EBCDIC SPSS PORT FILE}, and so on.)  These
+strings are padded on the right with spaces in their own character set.
+
+It appears that these strings exist only to inform those who might view
+the file on a screen, and that they are not parsed by SPSS products.
+Thus, they can be safely ignored.  For those interested, the strings are
+supposed to be in the following character sets, in the specified order:
+EBCDIC, 7-bit ASCII, CDC 6-bit ASCII, 6-bit ASCII, Honeywell 6-bit
+ASCII.
+
+The 256-byte segment describes a mapping from the character set used in
+the portable file to an arbitrary character set having characters at the
+following positions:
+
+@table @asis
+@item 0--60
+
+Control characters.  Not important enough to describe in full here.
+
+@item 61--63
+
+Reserved.
+
+@item 64--73
+
+Digits @samp{0} through @samp{9}.
+
+@item 74--99
+
+Capital letters @samp{A} through @samp{Z}. 
+
+@item 100--125
+
+Lowercase letters @samp{a} through @samp{z}.
+
+@item 126
+
+Space.
+
+@item 127--130
+
+Symbols @code{.<(+}
+
+@item 131
+
+Solid vertical pipe.
+
+@item 132--142
+
+Symbols @code{&[]!$*);^-/}
+
+@item 143
+
+Broken vertical pipe.
+
+@item 144--150
+
+Symbols @code{,%_>}?@code{`:}   @c @code{?} is an inverted question mark
+
+@item 151
+
+British pound symbol.
+
+@item 152--155
+
+Symbols @code{@@'="}.
+
+@item 156
+
+Less than or equal symbol.
+
+@item 157
+
+Empty box.
+
+@item 158
+
+Plus or minus.
+
+@item 159
+
+Filled box.
+
+@item 160
+
+Degree symbol.
+
+@item 161
+
+Dagger.
+
+@item 162
+
+Symbol @samp{~}.
+
+@item 163
+
+En dash.
+
+@item 164
+
+Lower left corner box draw.
+
+@item 165
+
+Upper left corner box draw.
+
+@item 166
+
+Greater than or equal symbol.
+
+@item 167--176
+
+Superscript @samp{0} through @samp{9}.
+
+@item 177
+
+Lower right corner box draw.
+
+@item 178
+
+Upper right corner box draw.
+
+@item 179
+
+Not equal symbol.
+
+@item 180
+
+Em dash.
+
+@item 181
+
+Superscript @samp{(}.
+
+@item 182
+
+Superscript @samp{)}.
+
+@item 183
+
+Horizontal dagger (?).
+
+@item 184--186
+
+Symbols @samp{@{@}\}.
+@item 187
+
+Cents symbol.
+
+@item 188
+
+Centered dot, or bullet.
+
+@item 189--255
+
+Reserved.
+@end table
+
+Symbols that are not defined in a particular character set are set to
+the same value as symbol 64; i.e., to @samp{0}.
+
+The 8-byte tag string consists of the exact characters @code{SPSSPORT}
+in the portable file's character set, which can be used to verify that
+the file is indeed a portable file.
+
+@node Version and Date Info Record, Identification Records, Portable File Header, Portable File Format
+@section Version and Date Info Record
+
+This record does not have a tag code.  It has the following structure:
+
+@itemize @bullet
+@item
+A single character identifying the file format version.  The letter A
+represents version 0, and so on.
+
+@item
+An 8-character string field giving the file creation date in the format
+YYYYMMDD.
+
+@item
+A 6-character string field giving the file creation time in the format
+HHMMSS.
+@end itemize
+
+@node Identification Records, Variable Count Record, Version and Date Info Record, Portable File Format
+@section Identification Records
+
+The product identification record has tag code @samp{1}.  It consists of
+a single string field giving the name of the product that wrote the
+portable file.
+
+The subproduct identification record has tag code @samp{3}.  It
+consists of a single string field giving additional information on the
+product that wrote the portable file.
+
+@node Variable Count Record, Variable Records, Identification Records, Portable File Format
+@section Variable Count Record
+
+The variable count record has tag code @samp{4}.  It consists of two
+integer fields.  The first contains the number of variables in the file
+dictionary.  The purpose of the second is unknown; it contains the value
+161 in all portable files examined so far.
+
+@node Variable Records, Value Label Records, Variable Count Record, Portable File Format
+@section Variable Records
+
+Each variable record represents a single variable.  Variable records
+have tag code @samp{7}.  They have the following structure:
+
+@itemize @bullet
+
+@item
+Width (integer).  This is 0 for a numeric variable, and a number between 1
+and 255 for a string variable.
+
+@item
+Name (string).  1--8 characters long.  Must be in all capitals.
+
+@item
+Print format.  This is a set of three integer fields:
+
+@itemize @minus
+
+@item
+Format type (@pxref{Variable Record}).
+
+@item
+Format width.  1--40.
+
+@item
+Number of decimal places.  1--40.
+@end itemize
+
+@item
+Write format.  Same structure as the print format described above.
+@end itemize
+
+Each variable record can optionally be followed by a missing value
+record, which has tag code @samp{8}.  A missing value record has one
+field, the missing value itself (a floating-point or string, as
+appropriate).  Up to three of these missing value records can be used.
+
+There is also a record for missing value ranges, which has tag code
+@samp{B}.  It is followed by two fields representing the range, which
+are floating-point or string as appropriate.  If a missing value range
+is present, it may be followed by a single missing value record.
+
+Tag codes @samp{9} and @samp{A} represent @code{LO THRU @var{x}} and
+@code{@var{x} THRU HI} ranges, respectively.  Each is followed by a
+single field representing @var{x}.  If one of the ranges is present, it
+may be followed by a single missing value record.
+
+In addition, each variable record can optionally be followed by a
+variable label record, which has tag code @samp{C}.  A variable label
+record has one field, the variable label itself (string).
+
+@node Value Label Records, Portable File Data, Variable Records, Portable File Format
+@section Value Label Records
+
+Value label records have tag code @samp{D}.  They have the following
+format:
+
+@itemize @bullet
+@item
+Variable count (integer).
+
+@item
+List of variables (strings).  The variable count specifies the number in
+the list.  Variables are specified by their names.  All variables must
+be of the same type (numeric or string).
+
+@item
+Label count (integer).
+
+@item
+List of (value, label) tuples.  The label count specifies the number of
+tuples.  Each tuple consists of a value, which is numeric or string as
+appropriate to the variables, followed by a label (string).
+@end itemize
+
+@node Portable File Data,  , Value Label Records, Portable File Format
+@section Portable File Data
+
+The data record has tag code @samp{F}.  There is only one tag for all
+the data; thus, all the data must follow the dictionary.  The data is
+terminated by the end-of-file marker @samp{Z}, which is not valid as the
+beginning of a data element.
+
+Data elements are output in the same order as the variable records
+describing them.  String variables are output as string fields, and
+numeric variables are output as floating-point fields.
+
+@node q2c Input Format, Bugs, Portable File Format, Top
+@chapter @code{q2c} Input Format
+
+PSPP statistical procedures have a bizarre and somewhat irregular
+syntax.  Despite this, a parser generator has been written that
+adequately addresses many of the possibilities and tries to provide
+hooks for the exceptional cases.  This parser generator is named
+@code{q2c}.
+
+@menu
+* Invoking q2c::                q2c command-line syntax.
+* q2c Input Structure::         High-level layout of the input file.
+* Grammar Rules::               Syntax of the grammar rules.
+@end menu
+
+@node Invoking q2c, q2c Input Structure, q2c Input Format, q2c Input Format
+@section Invoking q2c
+
+@example
+q2c @var{input.q} @var{output.c}
+@end example
+
+@code{q2c} translates a @samp{.q} file into a @samp{.c} file.  It takes
+exactly two command-line arguments, which are the input file name and
+output file name, respectively.  @code{q2c} does not accept any
+command-line options.
+
+@node q2c Input Structure, Grammar Rules, Invoking q2c, q2c Input Format
+@section @code{q2c} Input Structure
+
+@code{q2c} input files are divided into two sections: the grammar rules
+and the supporting code.  The @dfn{grammar rules}, which make up the
+first part of the input, are used to define the syntax of the
+statistical procedure to be parsed.  The @dfn{supporting code},
+following the grammar rules, are copied largely unchanged to the output
+file, except for certain escapes.
+
+The most important lines in the grammar rules are used for defining
+procedure syntax.  These lines can be prefixed with a dollar sign
+(@samp{$}), which prevents Emacs' CC-mode from munging them.  Besides
+this, a bang (@samp{!}) at the beginning of a line causes the line,
+minus the bang, to be written verbatim to the output file (useful for
+comments).  As a third special case, any line that begins with the exact
+characters @code{/* *INDENT} is ignored and not written to the output.
+This allows @code{.q} files to be processed through @code{indent}
+without being munged.
+
+The syntax of the grammar rules themselves is given in the following
+sections.
+
+The supporting code is passed into the output file largely unchanged.
+However, the following escapes are supported.  Each escape must appear
+on a line by itself.
+
+@table @code
+@item /* (header) */
+
+Expands to a series of C @code{#include} directives which include the
+headers that are required for the parser generated by @code{q2c}.
+
+@item /* (decls @var{scope}) */
+
+Expands to C variable and data type declarations for the variables and
+@code{enum}s input and output by the @code{q2c} parser.  @var{scope}
+must be either @code{local} or @code{global}.  @code{local} causes the
+declarations to be output as function locals.  @code{global} causes them
+to be declared as @code{static} module variables; thus, @code{global} is
+a bit of a misnomer.
+
+@item /* (parser) */
+
+Expands to the entire parser.  Must be enclosed within a C function.
+
+@item /* (free) */
+
+Expands to a set of calls to the @code{free} function for variables
+declared by the parser.  Only needs to be invoked if subcommands of type
+@code{string} are used in the grammar rules.
+@end table
+
+@node Grammar Rules,  , q2c Input Structure, q2c Input Format
+@section Grammar Rules
+
+The grammar rules describe the format of the syntax that the parser
+generated by @code{q2c} will understand.  The way that the grammar rules
+are included in @code{q2c} input file are described above.
+
+The grammar rules are divided into tokens of the following types:
+
+@table @asis
+@item Identifier (@code{ID})
+
+An identifier token is a sequence of letters, digits, and underscores
+(@samp{_}).  Identifiers are @emph{not} case-sensitive.
+
+@item String (@code{STRING})
+
+String tokens are initiated by a double-quote character (@samp{"}) and
+consist of all the characters between that double quote and the next
+double quote, which must be on the same line as the first.  Within a
+string, a backslash can be used as a ``literal escape''.  The only
+reasons to use a literal escape are to include a double quote or a
+backslash within a string.
+
+@item Special character
+
+Other characters, other than whitespace, constitute tokens in
+themselves.
+
+@end table
+
+The syntax of the grammar rules is as follows:
+
+@example
+grammar-rules ::= ID : subcommands .
+subcommands ::= subcommand
+            ::= subcommands ; subcommand
+@end example
+
+The syntax begins with an ID or STRING token that gives the name of the
+procedure to be parsed.  The rest of the syntax consists of subcommands
+separated by semicolons (@samp{;}) and terminated with a full stop
+(@samp{.}).
+
+@example
+subcommand ::= sbc-options ID sbc-defn
+sbc-options ::= 
+            ::= sbc-option
+            ::= sbc-options sbc-options
+sbc-option ::= *
+           ::= +
+sbc-defn ::= opt-prefix = specifiers
+         ::= [ ID ] = array-sbc
+         ::= opt-prefix = sbc-special-form
+opt-prefix ::=
+           ::= ( ID )
+@end example
+
+Each subcommand can be prefixed with one or more option characters.  An
+asterisk (@samp{*}) is used to indicate the default subcommand; the
+keyword used for the default subcommand can be omitted in the PSPP
+syntax file.  A plus sign (@samp{+}) is used to indicate that a
+subcommand can appear more than once; if it is not present then that
+subcommand can appear no more than once.
+
+The subcommand name appears after the option characters.
+
+There are three forms of subcommands.  The first and most common form
+simply gives an equals sign (@samp{=}) and a list of specifiers, which
+can each be set to a single setting.  The second form declares an array,
+which is a set of flags that can be individually turned on by the user.
+There are also several special forms that do not take a list of
+specifiers.
+
+Arrays require an additional @code{ID} argument.  This is used as a
+prefix, prepended to the variable names constructed from the
+specifiers.  The other forms also allow an optional prefix to be
+specified.
+
+@example
+array-sbc ::= alternatives
+          ::= array-sbc , alternatives
+alternatives ::= ID
+             ::= alternatives | ID
+@end example
+
+An array subcommand is a set of Boolean values that can independently be
+turned on by the user, listed separated by commas (@samp{,}).  If an value has more
+than one name then these names are separated by pipes (@samp{|}).
+
+@example
+specifiers ::= specifier
+           ::= specifiers , specifier
+specifier ::= opt-id : settings
+opt-id ::=
+       ::= ID
+@end example
+
+Ordinary subcommands (other than arrays and special forms) require a
+list of specifiers.  Each specifier has an optional name and a list of
+settings.  If the name is given then a correspondingly named variable
+will be used to store the user's choice of setting.  If no name is given
+then there is no way to tell which setting the user picked; in this case
+the settings should probably have values attached.
+
+@example
+settings ::= setting
+         ::= settings / setting
+setting ::= setting-options ID setting-value
+setting-options ::=
+                ::= *
+                ::= !
+                ::= * !
+@end example
+
+Individual settings are separated by forward slashes (@samp{/}).  Each
+setting can be as little as an @code{ID} token, but options and values
+can optionally be included.  The @samp{*} option means that, for this
+setting, the @code{ID} can be omitted.  The @samp{!} option means that
+this option is the default for its specifier.
+
+@example
+setting-value ::=
+              ::= ( setting-value-2 )
+              ::= setting-value-2
+setting-value-2 ::= setting-value-options setting-value-type : ID 
+                    setting-value-restriction
+setting-value-options ::=
+                      ::= *
+setting-value-type ::= N
+                   ::= D
+setting-value-restriction ::= 
+                          ::= , STRING
+@end example
+
+Settings may have values.  If the value must be enclosed in parentheses,
+then enclose the value declaration in parentheses.  Declare the setting
+type as @samp{n} or @samp{d} for integer or floating point type,
+respectively.  The given @code{ID} is used to construct a variable name.
+If option @samp{*} is given, then the value is optional; otherwise it
+must be specified whenever the corresponding setting is specified.  A
+``restriction'' can also be specified which is a string giving a C
+expression limiting the valid range of the value.  The special escape
+@code{%s} should be used within the restriction to refer to the
+setting's value variable.
+
+@example
+sbc-special-form ::= VAR
+                 ::= VARLIST varlist-options
+                 ::= INTEGER opt-list
+                 ::= DOUBLE opt-list
+                 ::= PINT
+                 ::= STRING @r{(the literal word STRING)} string-options
+                 ::= CUSTOM
+varlist-options ::= 
+                ::= ( STRING )
+opt-list ::=
+         ::= LIST
+string-options ::= 
+               ::= ( STRING STRING )
+@end example
+
+The special forms are of the following types:
+
+@table @code
+@item VAR
+
+A single variable name.
+
+@item VARLIST
+
+A list of variables.  If given, the string can be used to provide
+@code{PV_@var{*}} options to the call to @code{parse_variables}. 
+
+@item INTEGER
+
+A single integer value.
+
+@item INTEGER LIST
+
+A list of integers separated by spaces or commas.
+
+@item DOUBLE
+
+A single floating-point value.
+
+@item DOUBLE LIST
+
+A list of floating-point values.
+
+@item PINT
+
+A single positive integer value.
+
+@item STRING
+
+A string value.  If the options are given then the first string is an
+expression giving a restriction on the value of the string; the second
+string is an error message to display when the restriction is violated.
+
+@item CUSTOM
+
+A custom function is used to parse this subcommand.  The function must
+have prototype @code{int custom_@var{name} (void)}.  It should return 0
+on failure (when it has already issued an appropriate diagnostic), 1 on
+success, or 2 if it fails and the calling function should issue a syntax
+error on behalf of the custom handler.
+
+@end table
+
+@node Bugs, Function Index, q2c Input Format, Top
+@chapter Bugs
+
+@quotation
+As of fvwm 0.99 there were exactly 39.342 unidentified bugs.  Identified
+bugs have mostly been fixed, though.  Since then 9.34 bugs have been
+fixed.  Assuming that there are at least 10 unidentified bugs for every
+identified one, that leaves us with 39.342 - 9.34 + 10 * 9.34 = 123.422
+unidentified bugs.  If we follow this to its logical conclusion we
+will have an infinite number of unidentified bugs before the number of
+bugs can start to diminish, at which point the program will be
+bug-free.  Since this is a computer program infinity = 3.4028e+38 if you
+don't insist on double-precision.  At the current rate of bug discovery
+we should expect to achieve this point in 3.37e+27 years.  I guess I
+better plan on passing this thing on to my children@enddots{}
+
+---Robert Nation, @cite{fvwm manpage}.
+@end quotation
+
+@menu
+* Known bugs::                  Pointers to other files.
+* Contacting the Author::       Where to send the bug reports.
+@end menu
+
+@node Known bugs, Contacting the Author, Bugs, Bugs
+@section Known bugs
+
+This is the list of known bugs in PSPP.  In addition, @xref{Not
+Implemented}, and @xref{Functions Not Implemented}, for lists of bugs
+due to features not implemented.  For known bugs in individual language
+features, see the documentation for that feature.
+
+@itemize @bullet
+@item
+Nothing has yet been tested exhaustively. Be cautious using PSPP to
+make important decisions.
+
+@item
+@code{make check} fails on some systems that don't like the syntax.  I'm
+not sure why.  If someone could make an attempt to track this down, it
+would be appreciated.
+
+@item
+PostScript driver bugs:
+
+@itemize @minus
+@item
+Does not support driver arguments `max-fonts-simult' or
+`optimize-text-size'.
+
+@item
+Minor problems with font-encodings.
+
+@item
+Fails to align fonts along their baselines.
+
+@item
+Does not support certain bizarre line intersections--should
+never crop up in practice.
+
+@item
+Does not gracefully substitute for existing fonts whose
+encodings are missing.
+
+@item
+Does not perform italic correction or left italic correction
+on font changes.
+
+@item
+Encapsulated PostScript is unimplemented.
+@end itemize
+
+@item
+ASCII driver bugs:
+
+@itemize @minus
+Does not support `infinite length' or `infinite width' paper.
+@end itemize
+@end itemize
+
+See below for information on reporting bugs not listed here.
+
+@node Contacting the Author,  , Known bugs, Bugs
+@section Contacting the Author
+
+The author can be contacted at e-mail address
+@ifinfo
+<blp@@gnu.org>.
+@end ifinfo
+@iftex
+@code{<blp@@gnu.org>}.
+@end iftex
+
+PSPP bug reports should be sent to 
+@ifinfo
+<bug-gnu-pspp@@gnu.org>.
+@end ifinfo
+@iftex
+@code{<bug-gnu-pspp@@gnu.org>}.
+@end iftex
+
+@node Function Index, Concept Index, Bugs, Top
+@chapter Function Index
+@printindex fn
+
+@node Concept Index, Command Index, Function Index, Top
+@chapter Concept Index
+@printindex cp
+
+@node Command Index,  , Concept Index, Top
+@chapter Command Index
+@printindex vr
+
+@contents
+@bye
+
+@c Local Variables:
+@c compile-command: "makeinfo pspp.texi"
+@c End:
diff --git a/doc/texinfo.tex b/doc/texinfo.tex
new file mode 100644 (file)
index 0000000..4c03dfa
--- /dev/null
@@ -0,0 +1,4424 @@
+%% TeX macros to handle texinfo files
+
+%   Copyright (C) 1985, 86, 88, 90, 91, 92, 93, 1994 Free Software Foundation, Inc.
+
+%This texinfo.tex file is free software; you can redistribute it and/or
+%modify it under the terms of the GNU General Public License as
+%published by the Free Software Foundation; either version 2, or (at
+%your option) any later version.
+
+%This texinfo.tex file is distributed in the hope that it will be
+%useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+%of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%General Public License for more details.
+
+%You should have received a copy of the GNU General Public License
+%along with this texinfo.tex file; see the file COPYING.  If not, write
+%to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+%USA.
+
+
+%In other words, you are welcome to use, share and improve this program.
+%You are forbidden to forbid anyone else to use, share and improve
+%what you give them.   Help stamp out software-hoarding!
+
+
+% Send bug reports to bug-texinfo@prep.ai.mit.edu.
+% Please include a *precise* test case in each bug report.
+
+
+% Make it possible to create a .fmt file just by loading this file:
+% if the underlying format is not loaded, start by loading it now.
+% Added by gildea November 1993.
+\expandafter\ifx\csname fmtname\endcsname\relax\input plain\fi
+
+% This automatically updates the version number based on RCS.
+\def\deftexinfoversion$#1: #2 ${\def\texinfoversion{#2}}
+\deftexinfoversion$Revision: 1.1 $
+\message{Loading texinfo package [Version \texinfoversion]:}
+
+% If in a .fmt file, print the version number
+% and turn on active characters that we couldn't do earlier because
+% they might have appeared in the input file name.
+\everyjob{\message{[Texinfo version \texinfoversion]}\message{}
+  \catcode`+=\active \catcode`\_=\active}
+
+% Save some parts of plain tex whose names we will redefine.
+
+\let\ptextilde=\~
+\let\ptexlbrace=\{
+\let\ptexrbrace=\}
+\let\ptexdots=\dots
+\let\ptexdot=\.
+\let\ptexstar=\*
+\let\ptexend=\end
+\let\ptexbullet=\bullet
+\let\ptexb=\b
+\let\ptexc=\c
+\let\ptexi=\i
+\let\ptext=\t
+\let\ptexl=\l
+\let\ptexL=\L
+
+% Be sure we're in horizontal mode when doing a tie, since we make space
+% equivalent to this in @example-like environments. Otherwise, a space
+% at the beginning of a line will start with \penalty -- and
+% since \penalty is valid in vertical mode, we'd end up putting the
+% penalty on the vertical list instead of in the new paragraph.
+{\catcode`@ = 11
+ \gdef\tie{\leavevmode\penalty\@M\ }
+}
+\let\~ = \tie                  % And make it available as @~.
+
+\message{Basics,}
+\chardef\other=12
+
+% If this character appears in an error message or help string, it
+% starts a new line in the output.
+\newlinechar = `^^J
+
+% Set up fixed words for English.
+\ifx\putwordChapter\undefined{\gdef\putwordChapter{Chapter}}\fi%
+\def\putwordInfo{Info}%
+\ifx\putwordSee\undefined{\gdef\putwordSee{See}}\fi%
+\ifx\putwordsee\undefined{\gdef\putwordsee{see}}\fi%
+\ifx\putwordfile\undefined{\gdef\putwordfile{file}}\fi%
+\ifx\putwordpage\undefined{\gdef\putwordpage{page}}\fi%
+\ifx\putwordsection\undefined{\gdef\putwordsection{section}}\fi%
+\ifx\putwordSection\undefined{\gdef\putwordSection{Section}}\fi%
+\ifx\putwordTableofContents\undefined{\gdef\putwordTableofContents{Table of Contents}}\fi%
+\ifx\putwordShortContents\undefined{\gdef\putwordShortContents{Short Contents}}\fi%
+\ifx\putwordAppendix\undefined{\gdef\putwordAppendix{Appendix}}\fi%
+
+% Ignore a token.
+%
+\def\gobble#1{}
+
+\hyphenation{ap-pen-dix}
+\hyphenation{mini-buf-fer mini-buf-fers}
+\hyphenation{eshell}
+
+% Margin to add to right of even pages, to left of odd pages.
+\newdimen \bindingoffset  \bindingoffset=0pt
+\newdimen \normaloffset   \normaloffset=\hoffset
+\newdimen\pagewidth \newdimen\pageheight
+\pagewidth=\hsize \pageheight=\vsize
+
+% Sometimes it is convenient to have everything in the transcript file
+% and nothing on the terminal.  We don't just call \tracingall here,
+% since that produces some useless output on the terminal.
+%
+\def\gloggingall{\begingroup \globaldefs = 1 \loggingall \endgroup}%
+\def\loggingall{\tracingcommands2 \tracingstats2
+   \tracingpages1 \tracingoutput1 \tracinglostchars1
+   \tracingmacros2 \tracingparagraphs1 \tracingrestores1
+   \showboxbreadth\maxdimen\showboxdepth\maxdimen
+}%
+
+%---------------------Begin change-----------------------
+%
+%%%% For @cropmarks command.
+% Dimensions to add cropmarks at corners Added by P. A. MacKay, 12 Nov. 1986
+%
+\newdimen\cornerlong \newdimen\cornerthick
+\newdimen \topandbottommargin
+\newdimen \outerhsize \newdimen \outervsize
+\cornerlong=1pc\cornerthick=.3pt       % These set size of cropmarks
+\outerhsize=7in
+%\outervsize=9.5in
+% Alternative @smallbook page size is 9.25in
+\outervsize=9.25in
+\topandbottommargin=.75in
+%
+%---------------------End change-----------------------
+
+% \onepageout takes a vbox as an argument.  Note that \pagecontents
+% does insertions itself, but you have to call it yourself.
+\chardef\PAGE=255  \output={\onepageout{\pagecontents\PAGE}}
+\def\onepageout#1{\hoffset=\normaloffset
+\ifodd\pageno  \advance\hoffset by \bindingoffset
+\else \advance\hoffset by -\bindingoffset\fi
+{\escapechar=`\\\relax % makes sure backslash is used in output files.
+\shipout\vbox{{\let\hsize=\pagewidth \makeheadline} \pagebody{#1}%
+{\let\hsize=\pagewidth \makefootline}}}%
+\advancepageno \ifnum\outputpenalty>-20000 \else\dosupereject\fi}
+
+%%%% For @cropmarks command %%%%
+
+% Here is a modification of the main output routine for Near East Publications
+% This provides right-angle cropmarks at all four corners.
+% The contents of the page are centerlined into the cropmarks,
+% and any desired binding offset is added as an \hskip on either
+% site of the centerlined box.  (P. A. MacKay, 12 November, 1986)
+%
+\def\croppageout#1{\hoffset=0pt % make sure this doesn't mess things up
+{\escapechar=`\\\relax % makes sure backslash is used in output files.
+                \shipout
+                \vbox to \outervsize{\hsize=\outerhsize
+                 \vbox{\line{\ewtop\hfill\ewtop}}
+                 \nointerlineskip
+                 \line{\vbox{\moveleft\cornerthick\nstop}
+                       \hfill
+                       \vbox{\moveright\cornerthick\nstop}}
+                 \vskip \topandbottommargin
+                 \centerline{\ifodd\pageno\hskip\bindingoffset\fi
+                       \vbox{
+                       {\let\hsize=\pagewidth \makeheadline}
+                       \pagebody{#1}
+                       {\let\hsize=\pagewidth \makefootline}}
+                       \ifodd\pageno\else\hskip\bindingoffset\fi}
+                \vskip \topandbottommargin plus1fill minus1fill
+                 \boxmaxdepth\cornerthick
+                 \line{\vbox{\moveleft\cornerthick\nsbot}
+                       \hfill
+                       \vbox{\moveright\cornerthick\nsbot}}
+                 \nointerlineskip
+                 \vbox{\line{\ewbot\hfill\ewbot}}
+       }}
+  \advancepageno
+  \ifnum\outputpenalty>-20000 \else\dosupereject\fi}
+%
+% Do @cropmarks to get crop marks
+\def\cropmarks{\let\onepageout=\croppageout }
+
+\newinsert\margin \dimen\margin=\maxdimen
+
+\def\pagebody#1{\vbox to\pageheight{\boxmaxdepth=\maxdepth #1}}
+{\catcode`\@ =11
+\gdef\pagecontents#1{\ifvoid\topins\else\unvbox\topins\fi
+% marginal hacks, juha@viisa.uucp (Juha Takala)
+\ifvoid\margin\else % marginal info is present
+  \rlap{\kern\hsize\vbox to\z@{\kern1pt\box\margin \vss}}\fi
+\dimen@=\dp#1 \unvbox#1
+\ifvoid\footins\else\vskip\skip\footins\footnoterule \unvbox\footins\fi
+\ifr@ggedbottom \kern-\dimen@ \vfil \fi}
+}
+
+%
+% Here are the rules for the cropmarks.  Note that they are
+% offset so that the space between them is truly \outerhsize or \outervsize
+% (P. A. MacKay, 12 November, 1986)
+%
+\def\ewtop{\vrule height\cornerthick depth0pt width\cornerlong}
+\def\nstop{\vbox
+  {\hrule height\cornerthick depth\cornerlong width\cornerthick}}
+\def\ewbot{\vrule height0pt depth\cornerthick width\cornerlong}
+\def\nsbot{\vbox
+  {\hrule height\cornerlong depth\cornerthick width\cornerthick}}
+
+% Parse an argument, then pass it to #1.  The argument is the rest of
+% the input line (except we remove a trailing comment).  #1 should be a
+% macro which expects an ordinary undelimited TeX argument.
+%
+\def\parsearg#1{%
+  \let\next = #1%
+  \begingroup
+    \obeylines
+    \futurelet\temp\parseargx
+}
+
+% If the next token is an obeyed space (from an @example environment or
+% the like), remove it and recurse.  Otherwise, we're done.
+\def\parseargx{%
+  % \obeyedspace is defined far below, after the definition of \sepspaces.
+  \ifx\obeyedspace\temp
+    \expandafter\parseargdiscardspace
+  \else
+    \expandafter\parseargline
+  \fi
+}
+
+% Remove a single space (as the delimiter token to the macro call).
+{\obeyspaces %
+ \gdef\parseargdiscardspace {\futurelet\temp\parseargx}}
+
+{\obeylines %
+  \gdef\parseargline#1^^M{%
+    \endgroup % End of the group started in \parsearg.
+    %
+    % First remove any @c comment, then any @comment.
+    % Result of each macro is put in \toks0.
+    \argremovec #1\c\relax %
+    \expandafter\argremovecomment \the\toks0 \comment\relax %
+    %
+    % Call the caller's macro, saved as \next in \parsearg.
+    \expandafter\next\expandafter{\the\toks0}%
+  }%
+}
+
+% Since all \c{,omment} does is throw away the argument, we can let TeX
+% do that for us.  The \relax here is matched by the \relax in the call
+% in \parseargline; it could be more or less anything, its purpose is
+% just to delimit the argument to the \c.
+\def\argremovec#1\c#2\relax{\toks0 = {#1}}
+\def\argremovecomment#1\comment#2\relax{\toks0 = {#1}}
+
+% \argremovec{,omment} might leave us with trailing spaces, though; e.g.,
+%    @end itemize  @c foo
+% will have two active spaces as part of the argument with the
+% `itemize'.  Here we remove all active spaces from #1, and assign the
+% result to \toks0.
+%
+% This loses if there are any *other* active characters besides spaces
+% in the argument -- _ ^ +, for example -- since they get expanded.
+% Fortunately, Texinfo does not define any such commands.  (If it ever
+% does, the catcode of the characters in questionwill have to be changed
+% here.)  But this means we cannot call \removeactivespaces as part of
+% \argremovec{,omment}, since @c uses \parsearg, and thus the argument
+% that \parsearg gets might well have any character at all in it.
+%
+\def\removeactivespaces#1{%
+  \begingroup
+    \ignoreactivespaces
+    \edef\temp{#1}%
+    \global\toks0 = \expandafter{\temp}%
+  \endgroup
+}
+
+% Change the active space to expand to nothing.
+%
+\begingroup
+  \obeyspaces
+  \gdef\ignoreactivespaces{\obeyspaces\let =\empty}
+\endgroup
+
+
+\def\flushcr{\ifx\par\lisppar \def\next##1{}\else \let\next=\relax \fi \next}
+
+%% These are used to keep @begin/@end levels from running away
+%% Call \inENV within environments (after a \begingroup)
+\newif\ifENV \ENVfalse \def\inENV{\ifENV\relax\else\ENVtrue\fi}
+\def\ENVcheck{%
+\ifENV\errmessage{Still within an environment.  Type Return to continue.}
+\endgroup\fi} % This is not perfect, but it should reduce lossage
+
+% @begin foo  is the same as @foo, for now.
+\newhelp\EMsimple{Type <Return> to continue.}
+
+\outer\def\begin{\parsearg\beginxxx}
+
+\def\beginxxx #1{%
+\expandafter\ifx\csname #1\endcsname\relax
+{\errhelp=\EMsimple \errmessage{Undefined command @begin #1}}\else
+\csname #1\endcsname\fi}
+
+% @end foo executes the definition of \Efoo.
+%
+\def\end{\parsearg\endxxx}
+\def\endxxx #1{%
+  \removeactivespaces{#1}%
+  \edef\endthing{\the\toks0}%
+  %
+  \expandafter\ifx\csname E\endthing\endcsname\relax
+    \expandafter\ifx\csname \endthing\endcsname\relax
+      % There's no \foo, i.e., no ``environment'' foo.
+      \errhelp = \EMsimple
+      \errmessage{Undefined command `@end \endthing'}%
+    \else
+      \unmatchedenderror\endthing
+    \fi
+  \else
+    % Everything's ok; the right environment has been started.
+    \csname E\endthing\endcsname
+  \fi
+}
+
+% There is an environment #1, but it hasn't been started.  Give an error.
+%
+\def\unmatchedenderror#1{%
+  \errhelp = \EMsimple
+  \errmessage{This `@end #1' doesn't have a matching `@#1'}%
+}
+
+% Define the control sequence \E#1 to give an unmatched @end error.
+%
+\def\defineunmatchedend#1{%
+  \expandafter\def\csname E#1\endcsname{\unmatchedenderror{#1}}%
+}
+
+
+% Single-spacing is done by various environments (specifically, in
+% \nonfillstart and \quotations).
+\newskip\singlespaceskip \singlespaceskip = 12.5pt
+\def\singlespace{%
+  % Why was this kern here?  It messes up equalizing space above and below
+  % environments.  --karl, 6may93
+  %{\advance \baselineskip by -\singlespaceskip
+  %\kern \baselineskip}%
+  \setleading \singlespaceskip
+}
+
+%% Simple single-character @ commands
+
+% @@ prints an @
+% Kludge this until the fonts are right (grr).
+\def\@{{\tt \char '100}}
+
+% This is turned off because it was never documented
+% and you can use @w{...} around a quote to suppress ligatures.
+%% Define @` and @' to be the same as ` and '
+%% but suppressing ligatures.
+%\def\`{{`}}
+%\def\'{{'}}
+
+% Used to generate quoted braces.
+
+\def\mylbrace {{\tt \char '173}}
+\def\myrbrace {{\tt \char '175}}
+\let\{=\mylbrace
+\let\}=\myrbrace
+
+% @: forces normal size whitespace following.
+\def\:{\spacefactor=1000 }
+
+% @* forces a line break.
+\def\*{\hfil\break\hbox{}\ignorespaces}
+
+% @. is an end-of-sentence period.
+\def\.{.\spacefactor=3000 }
+
+% @enddots{} is an end-of-sentence ellipsis.
+\gdef\enddots{$\mathinner{\ldotp\ldotp\ldotp\ldotp}$\spacefactor=3000}
+
+% @! is an end-of-sentence bang.
+\gdef\!{!\spacefactor=3000 }
+
+% @? is an end-of-sentence query.
+\gdef\?{?\spacefactor=3000 }
+
+% @w prevents a word break.  Without the \leavevmode, @w at the
+% beginning of a paragraph, when TeX is still in vertical mode, would
+% produce a whole line of output instead of starting the paragraph.
+\def\w#1{\leavevmode\hbox{#1}}
+
+% @group ... @end group forces ... to be all on one page, by enclosing
+% it in a TeX vbox.  We use \vtop instead of \vbox to construct the box
+% to keep its height that of a normal line.  According to the rules for
+% \topskip (p.114 of the TeXbook), the glue inserted is
+% max (\topskip - \ht (first item), 0).  If that height is large,
+% therefore, no glue is inserted, and the space between the headline and
+% the text is small, which looks bad.
+%
+\def\group{\begingroup
+  \ifnum\catcode13=\active \else
+    \errhelp = \groupinvalidhelp
+    \errmessage{@group invalid in context where filling is enabled}%
+  \fi
+  %
+  % The \vtop we start below produces a box with normal height and large
+  % depth; thus, TeX puts \baselineskip glue before it, and (when the
+  % next line of text is done) \lineskip glue after it.  (See p.82 of
+  % the TeXbook.)  Thus, space below is not quite equal to space
+  % above.  But it's pretty close.
+  \def\Egroup{%
+    \egroup           % End the \vtop.
+    \endgroup         % End the \group.
+  }%
+  %
+  \vtop\bgroup
+    % We have to put a strut on the last line in case the @group is in
+    % the midst of an example, rather than completely enclosing it.
+    % Otherwise, the interline space between the last line of the group
+    % and the first line afterwards is too small.  But we can't put the
+    % strut in \Egroup, since there it would be on a line by itself.
+    % Hence this just inserts a strut at the beginning of each line.
+    \everypar = {\strut}%
+    %
+    % Since we have a strut on every line, we don't need any of TeX's
+    % normal interline spacing.
+    \offinterlineskip
+    %
+    % OK, but now we have to do something about blank
+    % lines in the input in @example-like environments, which normally
+    % just turn into \lisppar, which will insert no space now that we've
+    % turned off the interline space.  Simplest is to make them be an
+    % empty paragraph.
+    \ifx\par\lisppar
+      \edef\par{\leavevmode \par}%
+      %
+      % Reset ^^M's definition to new definition of \par.
+      \obeylines
+    \fi
+    %
+    % Do @comment since we are called inside an environment such as
+    % @example, where each end-of-line in the input causes an
+    % end-of-line in the output.  We don't want the end-of-line after
+    % the `@group' to put extra space in the output.  Since @group
+    % should appear on a line by itself (according to the Texinfo
+    % manual), we don't worry about eating any user text.
+    \comment
+}
+%
+% TeX puts in an \escapechar (i.e., `@') at the beginning of the help
+% message, so this ends up printing `@group can only ...'.
+%
+\newhelp\groupinvalidhelp{%
+group can only be used in environments such as @example,^^J%
+where each line of input produces a line of output.}
+
+% @need space-in-mils
+% forces a page break if there is not space-in-mils remaining.
+
+\newdimen\mil  \mil=0.001in
+
+\def\need{\parsearg\needx}
+
+% Old definition--didn't work.
+%\def\needx #1{\par %
+%% This method tries to make TeX break the page naturally
+%% if the depth of the box does not fit.
+%{\baselineskip=0pt%
+%\vtop to #1\mil{\vfil}\kern -#1\mil\penalty 10000
+%\prevdepth=-1000pt
+%}}
+
+\def\needx#1{%
+  % Go into vertical mode, so we don't make a big box in the middle of a
+  % paragraph.
+  \par
+  %
+  % Don't add any leading before our big empty box, but allow a page
+  % break, since the best break might be right here.
+  \allowbreak
+  \nointerlineskip
+  \vtop to #1\mil{\vfil}%
+  %
+  % TeX does not even consider page breaks if a penalty added to the
+  % main vertical list is 10000 or more.  But in order to see if the
+  % empty box we just added fits on the page, we must make it consider
+  % page breaks.  On the other hand, we don't want to actually break the
+  % page after the empty box.  So we use a penalty of 9999.
+  %
+  % There is an extremely small chance that TeX will actually break the
+  % page at this \penalty, if there are no other feasible breakpoints in
+  % sight.  (If the user is using lots of big @group commands, which
+  % almost-but-not-quite fill up a page, TeX will have a hard time doing
+  % good page breaking, for example.)  However, I could not construct an
+  % example where a page broke at this \penalty; if it happens in a real
+  % document, then we can reconsider our strategy.
+  \penalty9999
+  %
+  % Back up by the size of the box, whether we did a page break or not.
+  \kern -#1\mil
+  %
+  % Do not allow a page break right after this kern.
+  \nobreak
+}
+
+% @br   forces paragraph break
+
+\let\br = \par
+
+% @dots{}  output some dots
+
+\def\dots{$\ldots$}
+
+% @page    forces the start of a new page
+
+\def\page{\par\vfill\supereject}
+
+% @exdent text....
+% outputs text on separate line in roman font, starting at standard page margin
+
+% This records the amount of indent in the innermost environment.
+% That's how much \exdent should take out.
+\newskip\exdentamount
+
+% This defn is used inside fill environments such as @defun.
+\def\exdent{\parsearg\exdentyyy}
+\def\exdentyyy #1{{\hfil\break\hbox{\kern -\exdentamount{\rm#1}}\hfil\break}}
+
+% This defn is used inside nofill environments such as @example.
+\def\nofillexdent{\parsearg\nofillexdentyyy}
+\def\nofillexdentyyy #1{{\advance \leftskip by -\exdentamount
+\leftline{\hskip\leftskip{\rm#1}}}}
+
+%\hbox{{\rm#1}}\hfil\break}}
+
+% @include file    insert text of that file as input.
+
+\def\include{\parsearg\includezzz}
+%Use \input\thisfile to avoid blank after \input, which may be an active
+%char (in which case the blank would become the \input argument).
+%The grouping keeps the value of \thisfile correct even when @include
+%is nested.
+\def\includezzz #1{\begingroup
+\def\thisfile{#1}\input\thisfile
+\endgroup}
+
+\def\thisfile{}
+
+% @center line   outputs that line, centered
+
+\def\center{\parsearg\centerzzz}
+\def\centerzzz #1{{\advance\hsize by -\leftskip
+\advance\hsize by -\rightskip
+\centerline{#1}}}
+
+% @sp n   outputs n lines of vertical space
+
+\def\sp{\parsearg\spxxx}
+\def\spxxx #1{\par \vskip #1\baselineskip}
+
+% @comment ...line which is ignored...
+% @c is the same as @comment
+% @ignore ... @end ignore  is another way to write a comment
+
+\def\comment{\catcode 64=\other \catcode 123=\other \catcode 125=\other%
+\parsearg \commentxxx}
+
+\def\commentxxx #1{\catcode 64=0 \catcode 123=1 \catcode 125=2 }
+
+\let\c=\comment
+
+% Prevent errors for section commands.
+% Used in @ignore and in failing conditionals.
+\def\ignoresections{%
+\let\chapter=\relax
+\let\unnumbered=\relax
+\let\top=\relax
+\let\unnumberedsec=\relax
+\let\unnumberedsection=\relax
+\let\unnumberedsubsec=\relax
+\let\unnumberedsubsection=\relax
+\let\unnumberedsubsubsec=\relax
+\let\unnumberedsubsubsection=\relax
+\let\section=\relax
+\let\subsec=\relax
+\let\subsubsec=\relax
+\let\subsection=\relax
+\let\subsubsection=\relax
+\let\appendix=\relax
+\let\appendixsec=\relax
+\let\appendixsection=\relax
+\let\appendixsubsec=\relax
+\let\appendixsubsection=\relax
+\let\appendixsubsubsec=\relax
+\let\appendixsubsubsection=\relax
+\let\contents=\relax
+\let\smallbook=\relax
+\let\titlepage=\relax
+}
+
+% Used in nested conditionals, where we have to parse the Texinfo source
+% and so want to turn off most commands, in case they are used
+% incorrectly.
+%
+\def\ignoremorecommands{%
+  \let\defcv = \relax
+  \let\deffn = \relax
+  \let\deffnx = \relax
+  \let\defindex = \relax
+  \let\defivar = \relax
+  \let\defmac = \relax
+  \let\defmethod = \relax
+  \let\defop = \relax
+  \let\defopt = \relax
+  \let\defspec = \relax
+  \let\deftp = \relax
+  \let\deftypefn = \relax
+  \let\deftypefun = \relax
+  \let\deftypevar = \relax
+  \let\deftypevr = \relax
+  \let\defun = \relax
+  \let\defvar = \relax
+  \let\defvr = \relax
+  \let\ref = \relax
+  \let\xref = \relax
+  \let\printindex = \relax
+  \let\pxref = \relax
+  \let\settitle = \relax
+  \let\include = \relax
+  \let\lowersections = \relax
+  \let\down = \relax
+  \let\raisesections = \relax
+  \let\up = \relax
+  \let\set = \relax
+  \let\clear = \relax
+  \let\item = \relax
+  \let\message = \relax
+}
+
+% Ignore @ignore ... @end ignore.
+%
+\def\ignore{\doignore{ignore}}
+
+% Also ignore @ifinfo, @ifhtml, @html, @menu, and @direntry text.
+%
+\def\ifinfo{\doignore{ifinfo}}
+\def\ifhtml{\doignore{ifhtml}}
+\def\html{\doignore{html}}
+\def\menu{\doignore{menu}}
+\def\direntry{\doignore{direntry}}
+
+% Ignore text until a line `@end #1'.
+%
+\def\doignore#1{\begingroup
+  % Don't complain about control sequences we have declared \outer.
+  \ignoresections
+  %
+  % Define a command to swallow text until we reach `@end #1'.
+  \long\def\doignoretext##1\end #1{\enddoignore}%
+  %
+  % Make sure that spaces turn into tokens that match what \doignoretext wants.
+  \catcode32 = 10
+  %
+  % And now expand that command.
+  \doignoretext
+}
+
+% What we do to finish off ignored text.
+%
+\def\enddoignore{\endgroup\ignorespaces}%
+
+\newif\ifwarnedobs\warnedobsfalse
+\def\obstexwarn{%
+  \ifwarnedobs\relax\else
+  % We need to warn folks that they may have trouble with TeX 3.0.
+  % This uses \immediate\write16 rather than \message to get newlines.
+    \immediate\write16{}
+    \immediate\write16{***WARNING*** for users of Unix TeX 3.0!}
+    \immediate\write16{This manual trips a bug in TeX version 3.0 (tex hangs).}
+    \immediate\write16{If you are running another version of TeX, relax.}
+    \immediate\write16{If you are running Unix TeX 3.0, kill this TeX process.}
+    \immediate\write16{  Then upgrade your TeX installation if you can.}
+    \immediate\write16{If you are stuck with version 3.0, run the}
+    \immediate\write16{  script ``tex3patch'' from the Texinfo distribution}
+    \immediate\write16{  to use a workaround.}
+    \immediate\write16{}
+    \warnedobstrue
+    \fi
+}
+
+% **In TeX 3.0, setting text in \nullfont hangs tex.  For a
+% workaround (which requires the file ``dummy.tfm'' to be installed),
+% uncomment the following line:
+%%%%%\font\nullfont=dummy\let\obstexwarn=\relax
+
+% Ignore text, except that we keep track of conditional commands for
+% purposes of nesting, up to an `@end #1' command.
+%
+\def\nestedignore#1{%
+  \obstexwarn
+  % We must actually expand the ignored text to look for the @end
+  % command, so that nested ignore constructs work.  Thus, we put the
+  % text into a \vbox and then do nothing with the result.  To minimize
+  % the change of memory overflow, we follow the approach outlined on
+  % page 401 of the TeXbook: make the current font be a dummy font.
+  %
+  \setbox0 = \vbox\bgroup
+    % Don't complain about control sequences we have declared \outer.
+    \ignoresections
+    %
+    % Define `@end #1' to end the box, which will in turn undefine the
+    % @end command again.
+    \expandafter\def\csname E#1\endcsname{\egroup\ignorespaces}%
+    %
+    % We are going to be parsing Texinfo commands.  Most cause no
+    % trouble when they are used incorrectly, but some commands do
+    % complicated argument parsing or otherwise get confused, so we
+    % undefine them.
+    %
+    % We can't do anything about stray @-signs, unfortunately;
+    % they'll produce `undefined control sequence' errors.
+    \ignoremorecommands
+    %
+    % Set the current font to be \nullfont, a TeX primitive, and define
+    % all the font commands to also use \nullfont.  We don't use
+    % dummy.tfm, as suggested in the TeXbook, because not all sites
+    % might have that installed.  Therefore, math mode will still
+    % produce output, but that should be an extremely small amount of
+    % stuff compared to the main input.
+    %
+    \nullfont
+    \let\tenrm = \nullfont  \let\tenit = \nullfont  \let\tensl = \nullfont
+    \let\tenbf = \nullfont  \let\tentt = \nullfont  \let\smallcaps = \nullfont
+    \let\tensf = \nullfont
+    % Similarly for index fonts (mostly for their use in
+    % smallexample)
+    \let\indrm = \nullfont  \let\indit = \nullfont  \let\indsl = \nullfont
+    \let\indbf = \nullfont  \let\indtt = \nullfont  \let\indsc = \nullfont
+    \let\indsf = \nullfont
+    %
+    % Don't complain when characters are missing from the fonts.
+    \tracinglostchars = 0
+    %
+    % Don't bother to do space factor calculations.
+    \frenchspacing
+    %
+    % Don't report underfull hboxes.
+    \hbadness = 10000
+    %
+    % Do minimal line-breaking.
+    \pretolerance = 10000
+    %
+    % Do not execute instructions in @tex
+    \def\tex{\doignore{tex}}
+}
+
+% @set VAR sets the variable VAR to an empty value.
+% @set VAR REST-OF-LINE sets VAR to the value REST-OF-LINE.
+%
+% Since we want to separate VAR from REST-OF-LINE (which might be
+% empty), we can't just use \parsearg; we have to insert a space of our
+% own to delimit the rest of the line, and then take it out again if we
+% didn't need it.
+%
+\def\set{\parsearg\setxxx}
+\def\setxxx#1{\setyyy#1 \endsetyyy}
+\def\setyyy#1 #2\endsetyyy{%
+  \def\temp{#2}%
+  \ifx\temp\empty \global\expandafter\let\csname SET#1\endcsname = \empty
+  \else \setzzz{#1}#2\endsetzzz % Remove the trailing space \setxxx inserted.
+  \fi
+}
+% Can't use \xdef to pre-expand #2 and save some time, since \temp or
+% \next or other control sequences that we've defined might get us into
+% an infinite loop. Consider `@set foo @cite{bar}'.
+\def\setzzz#1#2 \endsetzzz{\expandafter\gdef\csname SET#1\endcsname{#2}}
+
+% @clear VAR clears (i.e., unsets) the variable VAR.
+%
+\def\clear{\parsearg\clearxxx}
+\def\clearxxx#1{\global\expandafter\let\csname SET#1\endcsname=\relax}
+
+% @value{foo} gets the text saved in variable foo.
+%
+\def\value#1{\expandafter
+               \ifx\csname SET#1\endcsname\relax
+                       {\{No value for ``#1''\}}
+               \else \csname SET#1\endcsname \fi}
+
+% @ifset VAR ... @end ifset reads the `...' iff VAR has been defined
+% with @set.
+%
+\def\ifset{\parsearg\ifsetxxx}
+\def\ifsetxxx #1{%
+  \expandafter\ifx\csname SET#1\endcsname\relax
+    \expandafter\ifsetfail
+  \else
+    \expandafter\ifsetsucceed
+  \fi
+}
+\def\ifsetsucceed{\conditionalsucceed{ifset}}
+\def\ifsetfail{\nestedignore{ifset}}
+\defineunmatchedend{ifset}
+
+% @ifclear VAR ... @end ifclear reads the `...' iff VAR has never been
+% defined with @set, or has been undefined with @clear.
+%
+\def\ifclear{\parsearg\ifclearxxx}
+\def\ifclearxxx #1{%
+  \expandafter\ifx\csname SET#1\endcsname\relax
+    \expandafter\ifclearsucceed
+  \else
+    \expandafter\ifclearfail
+  \fi
+}
+\def\ifclearsucceed{\conditionalsucceed{ifclear}}
+\def\ifclearfail{\nestedignore{ifclear}}
+\defineunmatchedend{ifclear}
+
+% @iftex always succeeds; we read the text following, through @end
+% iftex).  But `@end iftex' should be valid only after an @iftex.
+%
+\def\iftex{\conditionalsucceed{iftex}}
+\defineunmatchedend{iftex}
+
+% We can't just want to start a group at @iftex (for example) and end it
+% at @end iftex, since then @set commands inside the conditional have no
+% effect (they'd get reverted at the end of the group).  So we must
+% define \Eiftex to redefine itself to be its previous value.  (We can't
+% just define it to fail again with an ``unmatched end'' error, since
+% the @ifset might be nested.)
+%
+\def\conditionalsucceed#1{%
+  \edef\temp{%
+    % Remember the current value of \E#1.
+    \let\nece{prevE#1} = \nece{E#1}%
+    %
+    % At the `@end #1', redefine \E#1 to be its previous value.
+    \def\nece{E#1}{\let\nece{E#1} = \nece{prevE#1}}%
+  }%
+  \temp
+}
+
+% We need to expand lots of \csname's, but we don't want to expand the
+% control sequences after we've constructed them.
+%
+\def\nece#1{\expandafter\noexpand\csname#1\endcsname}
+
+% @asis just yields its argument.  Used with @table, for example.
+%
+\def\asis#1{#1}
+
+% @math means output in math mode.
+% We don't use $'s directly in the definition of \math because control
+% sequences like \math are expanded when the toc file is written.  Then,
+% we read the toc file back, the $'s will be normal characters (as they
+% should be, according to the definition of Texinfo).  So we must use a
+% control sequence to switch into and out of math mode.
+%
+% This isn't quite enough for @math to work properly in indices, but it
+% seems unlikely it will ever be needed there.
+%
+\let\implicitmath = $
+\def\math#1{\implicitmath #1\implicitmath}
+
+% @bullet and @minus need the same treatment as @math, just above.
+\def\bullet{\implicitmath\ptexbullet\implicitmath}
+\def\minus{\implicitmath-\implicitmath}
+
+\def\node{\ENVcheck\parsearg\nodezzz}
+\def\nodezzz#1{\nodexxx [#1,]}
+\def\nodexxx[#1,#2]{\gdef\lastnode{#1}}
+\let\nwnode=\node
+\let\lastnode=\relax
+
+\def\donoderef{\ifx\lastnode\relax\else
+\expandafter\expandafter\expandafter\setref{\lastnode}\fi
+\global\let\lastnode=\relax}
+
+\def\unnumbnoderef{\ifx\lastnode\relax\else
+\expandafter\expandafter\expandafter\unnumbsetref{\lastnode}\fi
+\global\let\lastnode=\relax}
+
+\def\appendixnoderef{\ifx\lastnode\relax\else
+\expandafter\expandafter\expandafter\appendixsetref{\lastnode}\fi
+\global\let\lastnode=\relax}
+
+\let\refill=\relax
+
+% @setfilename is done at the beginning of every texinfo file.
+% So open here the files we need to have open while reading the input.
+% This makes it possible to make a .fmt file for texinfo.
+\def\setfilename{%
+   \readauxfile
+   \opencontents
+   \openindices
+   \fixbackslash  % Turn off hack to swallow `\input texinfo'.
+   \global\let\setfilename=\comment % Ignore extra @setfilename cmds.
+   \comment % Ignore the actual filename.
+}
+
+\outer\def\bye{\pagealignmacro\tracingstats=1\ptexend}
+
+\def\inforef #1{\inforefzzz #1,,,,**}
+\def\inforefzzz #1,#2,#3,#4**{\putwordSee{} \putwordInfo{} \putwordfile{} \file{\ignorespaces #3{}},
+  node \samp{\ignorespaces#1{}}}
+
+\message{fonts,}
+
+% Font-change commands.
+
+% Texinfo supports the sans serif font style, which plain TeX does not.
+% So we set up a \sf analogous to plain's \rm, etc.
+\newfam\sffam
+\def\sf{\fam=\sffam \tensf}
+\let\li = \sf % Sometimes we call it \li, not \sf.
+
+%% Try out Computer Modern fonts at \magstephalf
+\let\mainmagstep=\magstephalf
+
+% Set the font macro #1 to the font named #2, adding on the
+% specified font prefix (normally `cm').
+\def\setfont#1#2{\font#1=\fontprefix#2}
+
+% Use cm as the default font prefix.
+% To specify the font prefix, you must define \fontprefix
+% before you read in texinfo.tex.
+\ifx\fontprefix\undefined
+\def\fontprefix{cm}
+\fi
+
+\ifx\bigger\relax
+\let\mainmagstep=\magstep1
+\setfont\textrm{r12}
+\setfont\texttt{tt12}
+\else
+\setfont\textrm{r10 scaled \mainmagstep}
+\setfont\texttt{tt10 scaled \mainmagstep}
+\fi
+% Instead of cmb10, you many want to use cmbx10.
+% cmbx10 is a prettier font on its own, but cmb10
+% looks better when embedded in a line with cmr10.
+\setfont\textbf{b10 scaled \mainmagstep}
+\setfont\textit{ti10 scaled \mainmagstep}
+\setfont\textsl{sl10 scaled \mainmagstep}
+\setfont\textsf{ss10 scaled \mainmagstep}
+\setfont\textsc{csc10 scaled \mainmagstep}
+\font\texti=cmmi10 scaled \mainmagstep
+\font\textsy=cmsy10 scaled \mainmagstep
+
+% A few fonts for @defun, etc.
+\setfont\defbf{bx10 scaled \magstep1} %was 1314
+\setfont\deftt{tt10 scaled \magstep1}
+\def\df{\let\tentt=\deftt \let\tenbf = \defbf \bf}
+
+% Fonts for indices and small examples.
+% We actually use the slanted font rather than the italic,
+% because texinfo normally uses the slanted fonts for that.
+% Do not make many font distinctions in general in the index, since they
+% aren't very useful.
+\setfont\ninett{tt9}
+\setfont\indrm{r9}
+\setfont\indit{sl9}
+\let\indsl=\indit
+\let\indtt=\ninett
+\let\indsf=\indrm
+\let\indbf=\indrm
+\setfont\indsc{csc10 at 9pt}
+\font\indi=cmmi9
+\font\indsy=cmsy9
+
+% Fonts for headings
+\setfont\chaprm{bx12 scaled \magstep2}
+\setfont\chapit{ti12 scaled \magstep2}
+\setfont\chapsl{sl12 scaled \magstep2}
+\setfont\chaptt{tt12 scaled \magstep2}
+\setfont\chapsf{ss12 scaled \magstep2}
+\let\chapbf=\chaprm
+\setfont\chapsc{csc10 scaled\magstep3}
+\font\chapi=cmmi12 scaled \magstep2
+\font\chapsy=cmsy10 scaled \magstep3
+
+\setfont\secrm{bx12 scaled \magstep1}
+\setfont\secit{ti12 scaled \magstep1}
+\setfont\secsl{sl12 scaled \magstep1}
+\setfont\sectt{tt12 scaled \magstep1}
+\setfont\secsf{ss12 scaled \magstep1}
+\setfont\secbf{bx12 scaled \magstep1}
+\setfont\secsc{csc10 scaled\magstep2}
+\font\seci=cmmi12 scaled \magstep1
+\font\secsy=cmsy10 scaled \magstep2
+
+% \setfont\ssecrm{bx10 scaled \magstep1}    % This size an font looked bad.
+% \setfont\ssecit{cmti10 scaled \magstep1}    % The letters were too crowded.
+% \setfont\ssecsl{sl10 scaled \magstep1}
+% \setfont\ssectt{tt10 scaled \magstep1}
+% \setfont\ssecsf{ss10 scaled \magstep1}
+
+%\setfont\ssecrm{b10 scaled 1315}      % Note the use of cmb rather than cmbx.
+%\setfont\ssecit{ti10 scaled 1315}     % Also, the size is a little larger than
+%\setfont\ssecsl{sl10 scaled 1315}     % being scaled magstep1.
+%\setfont\ssectt{tt10 scaled 1315}
+%\setfont\ssecsf{ss10 scaled 1315}
+
+%\let\ssecbf=\ssecrm
+
+\setfont\ssecrm{bx12 scaled \magstephalf}
+\setfont\ssecit{ti12 scaled \magstephalf}
+\setfont\ssecsl{sl12 scaled \magstephalf}
+\setfont\ssectt{tt12 scaled \magstephalf}
+\setfont\ssecsf{ss12 scaled \magstephalf}
+\setfont\ssecbf{bx12 scaled \magstephalf}
+\setfont\ssecsc{csc10 scaled \magstep1}
+\font\sseci=cmmi12 scaled \magstephalf
+\font\ssecsy=cmsy10 scaled \magstep1
+% The smallcaps and symbol fonts should actually be scaled \magstep1.5,
+% but that is not a standard magnification.
+
+% Fonts for title page:
+\setfont\titlerm{bx12 scaled \magstep3}
+\let\authorrm = \secrm
+
+% In order for the font changes to affect most math symbols and letters,
+% we have to define the \textfont of the standard families.  Since
+% texinfo doesn't allow for producing subscripts and superscripts, we
+% don't bother to reset \scriptfont and \scriptscriptfont (which would
+% also require loading a lot more fonts).
+%
+\def\resetmathfonts{%
+  \textfont0 = \tenrm \textfont1 = \teni \textfont2 = \tensy
+  \textfont\itfam = \tenit \textfont\slfam = \tensl \textfont\bffam = \tenbf
+  \textfont\ttfam = \tentt \textfont\sffam = \tensf
+}
+
+
+% The font-changing commands redefine the meanings of \tenSTYLE, instead
+% of just \STYLE.  We do this so that font changes will continue to work
+% in math mode, where it is the current \fam that is relevant in most
+% cases, not the current.  Plain TeX does, for example,
+% \def\bf{\fam=\bffam \tenbf}  By redefining \tenbf, we obviate the need
+% to redefine \bf itself.
+\def\textfonts{%
+  \let\tenrm=\textrm \let\tenit=\textit \let\tensl=\textsl
+  \let\tenbf=\textbf \let\tentt=\texttt \let\smallcaps=\textsc
+  \let\tensf=\textsf \let\teni=\texti \let\tensy=\textsy
+  \resetmathfonts}
+\def\chapfonts{%
+  \let\tenrm=\chaprm \let\tenit=\chapit \let\tensl=\chapsl
+  \let\tenbf=\chapbf \let\tentt=\chaptt \let\smallcaps=\chapsc
+  \let\tensf=\chapsf \let\teni=\chapi \let\tensy=\chapsy
+  \resetmathfonts}
+\def\secfonts{%
+  \let\tenrm=\secrm \let\tenit=\secit \let\tensl=\secsl
+  \let\tenbf=\secbf \let\tentt=\sectt \let\smallcaps=\secsc
+  \let\tensf=\secsf \let\teni=\seci \let\tensy=\secsy
+  \resetmathfonts}
+\def\subsecfonts{%
+  \let\tenrm=\ssecrm \let\tenit=\ssecit \let\tensl=\ssecsl
+  \let\tenbf=\ssecbf \let\tentt=\ssectt \let\smallcaps=\ssecsc
+  \let\tensf=\ssecsf \let\teni=\sseci \let\tensy=\ssecsy
+  \resetmathfonts}
+\def\indexfonts{%
+  \let\tenrm=\indrm \let\tenit=\indit \let\tensl=\indsl
+  \let\tenbf=\indbf \let\tentt=\indtt \let\smallcaps=\indsc
+  \let\tensf=\indsf \let\teni=\indi \let\tensy=\indsy
+  \resetmathfonts}
+
+% Set up the default fonts, so we can use them for creating boxes.
+%
+\textfonts
+
+% Count depth in font-changes, for error checks
+\newcount\fontdepth \fontdepth=0
+
+% Fonts for short table of contents.
+\setfont\shortcontrm{r12}
+\setfont\shortcontbf{bx12}
+\setfont\shortcontsl{sl12}
+
+%% Add scribe-like font environments, plus @l for inline lisp (usually sans
+%% serif) and @ii for TeX italic
+
+% \smartitalic{ARG} outputs arg in italics, followed by an italic correction
+% unless the following character is such as not to need one.
+\def\smartitalicx{\ifx\next,\else\ifx\next-\else\ifx\next.\else\/\fi\fi\fi}
+\def\smartitalic#1{{\sl #1}\futurelet\next\smartitalicx}
+
+\let\i=\smartitalic
+\let\var=\smartitalic
+\let\dfn=\smartitalic
+\let\emph=\smartitalic
+\let\cite=\smartitalic
+
+\def\b#1{{\bf #1}}
+\let\strong=\b
+
+% We can't just use \exhyphenpenalty, because that only has effect at
+% the end of a paragraph.  Restore normal hyphenation at the end of the
+% group within which \nohyphenation is presumably called.
+%
+\def\nohyphenation{\hyphenchar\font = -1  \aftergroup\restorehyphenation}
+\def\restorehyphenation{\hyphenchar\font = `- }
+
+\def\t#1{%
+  {\tt \nohyphenation \rawbackslash \frenchspacing #1}%
+  \null
+}
+\let\ttfont = \t
+%\def\samp #1{`{\tt \rawbackslash \frenchspacing #1}'\null}
+\def\samp #1{`\tclose{#1}'\null}
+\def\key #1{{\tt \nohyphenation \uppercase{#1}}\null}
+\def\ctrl #1{{\tt \rawbackslash \hat}#1}
+
+\let\file=\samp
+
+% @code is a modification of @t,
+% which makes spaces the same size as normal in the surrounding text.
+\def\tclose#1{%
+  {%
+    % Change normal interword space to be same as for the current font.
+    \spaceskip = \fontdimen2\font
+    %
+    % Switch to typewriter.
+    \tt
+    %
+    % But `\ ' produces the large typewriter interword space.
+    \def\ {{\spaceskip = 0pt{} }}%
+    %
+    % Turn off hyphenation.
+    \nohyphenation
+    %
+    \rawbackslash
+    \frenchspacing
+    #1%
+  }%
+  \null
+}
+
+% We *must* turn on hyphenation at `-' and `_' in \code.
+% Otherwise, it is too hard to avoid overful hboxes
+% in the Emacs manual, the Library manual, etc.
+
+% Unfortunately, TeX uses one parameter (\hyphenchar) to control
+% both hyphenation at - and hyphenation within words.
+% We must therefore turn them both off (\tclose does that)
+% and arrange explicitly to hyphenate an a dash.
+%  -- rms.
+{
+\catcode`\-=\active
+\catcode`\_=\active
+\global\def\code{\begingroup \catcode`\-=\active \let-\codedash \catcode`\_=\active \let_\codeunder \codex}
+% The following is used by \doprintindex to insure that long function names
+% wrap around.  It is necessary for - and _ to be active before the index is
+% read from the file, as \entry parses the arguments long before \code is
+% ever called.  -- mycroft
+\global\def\indexbreaks{\catcode`\-=\active \let-\realdash \catcode`\_=\active \let_\realunder}
+}
+\def\realdash{-}
+\def\realunder{_}
+\def\codedash{-\discretionary{}{}{}}
+\def\codeunder{\normalunderscore\discretionary{}{}{}}
+\def\codex #1{\tclose{#1}\endgroup}
+
+%\let\exp=\tclose  %Was temporary
+
+% @kbd is like @code, except that if the argument is just one @key command,
+% then @kbd has no effect.
+
+\def\xkey{\key}
+\def\kbdfoo#1#2#3\par{\def\one{#1}\def\three{#3}\def\threex{??}%
+\ifx\one\xkey\ifx\threex\three \key{#2}%
+\else\tclose{\look}\fi
+\else\tclose{\look}\fi}
+
+% Typeset a dimension, e.g., `in' or `pt'.  The only reason for the
+% argument is to make the input look right: @dmn{pt} instead of
+% @dmn{}pt.
+%
+\def\dmn#1{\thinspace #1}
+
+\def\kbd#1{\def\look{#1}\expandafter\kbdfoo\look??\par}
+
+\def\l#1{{\li #1}\null}                %
+
+\def\r#1{{\rm #1}}             % roman font
+% Use of \lowercase was suggested.
+\def\sc#1{{\smallcaps#1}}      % smallcaps font
+\def\ii#1{{\it #1}}            % italic font
+
+\message{page headings,}
+
+\newskip\titlepagetopglue \titlepagetopglue = 1.5in
+\newskip\titlepagebottomglue \titlepagebottomglue = 2pc
+
+% First the title page.  Must do @settitle before @titlepage.
+\def\titlefont#1{{\titlerm #1}}
+
+\newif\ifseenauthor
+\newif\iffinishedtitlepage
+
+\def\shorttitlepage{\parsearg\shorttitlepagezzz}
+\def\shorttitlepagezzz #1{\begingroup\hbox{}\vskip 1.5in \chaprm \centerline{#1}%
+       \endgroup\page\hbox{}\page}
+
+\def\titlepage{\begingroup \parindent=0pt \textfonts
+   \let\subtitlerm=\tenrm
+% I deinstalled the following change because \cmr12 is undefined.
+% This change was not in the ChangeLog anyway.  --rms.
+%   \let\subtitlerm=\cmr12
+   \def\subtitlefont{\subtitlerm \normalbaselineskip = 13pt \normalbaselines}%
+   %
+   \def\authorfont{\authorrm \normalbaselineskip = 16pt \normalbaselines}%
+   %
+   % Leave some space at the very top of the page.
+   \vglue\titlepagetopglue
+   %
+   % Now you can print the title using @title.
+   \def\title{\parsearg\titlezzz}%
+   \def\titlezzz##1{\leftline{\titlefont{##1}}
+                   % print a rule at the page bottom also.
+                   \finishedtitlepagefalse
+                   \vskip4pt \hrule height 4pt width \hsize \vskip4pt}%
+   % No rule at page bottom unless we print one at the top with @title.
+   \finishedtitlepagetrue
+   %
+   % Now you can put text using @subtitle.
+   \def\subtitle{\parsearg\subtitlezzz}%
+   \def\subtitlezzz##1{{\subtitlefont \rightline{##1}}}%
+   %
+   % @author should come last, but may come many times.
+   \def\author{\parsearg\authorzzz}%
+   \def\authorzzz##1{\ifseenauthor\else\vskip 0pt plus 1filll\seenauthortrue\fi
+      {\authorfont \leftline{##1}}}%
+   %
+   % Most title ``pages'' are actually two pages long, with space
+   % at the top of the second.  We don't want the ragged left on the second.
+   \let\oldpage = \page
+   \def\page{%
+      \iffinishedtitlepage\else
+        \finishtitlepage
+      \fi
+      \oldpage
+      \let\page = \oldpage
+      \hbox{}}%
+%   \def\page{\oldpage \hbox{}}
+}
+
+\def\Etitlepage{%
+   \iffinishedtitlepage\else
+      \finishtitlepage
+   \fi
+   % It is important to do the page break before ending the group,
+   % because the headline and footline are only empty inside the group.
+   % If we use the new definition of \page, we always get a blank page
+   % after the title page, which we certainly don't want.
+   \oldpage
+   \endgroup
+   \HEADINGSon
+}
+
+\def\finishtitlepage{%
+   \vskip4pt \hrule height 2pt width \hsize
+   \vskip\titlepagebottomglue
+   \finishedtitlepagetrue
+}
+
+%%% Set up page headings and footings.
+
+\let\thispage=\folio
+
+\newtoks \evenheadline    % Token sequence for heading line of even pages
+\newtoks \oddheadline     % Token sequence for heading line of odd pages
+\newtoks \evenfootline    % Token sequence for footing line of even pages
+\newtoks \oddfootline     % Token sequence for footing line of odd pages
+
+% Now make Tex use those variables
+\headline={{\textfonts\rm \ifodd\pageno \the\oddheadline
+                            \else \the\evenheadline \fi}}
+\footline={{\textfonts\rm \ifodd\pageno \the\oddfootline
+                            \else \the\evenfootline \fi}\HEADINGShook}
+\let\HEADINGShook=\relax
+
+% Commands to set those variables.
+% For example, this is what  @headings on  does
+% @evenheading @thistitle|@thispage|@thischapter
+% @oddheading @thischapter|@thispage|@thistitle
+% @evenfooting @thisfile||
+% @oddfooting ||@thisfile
+
+\def\evenheading{\parsearg\evenheadingxxx}
+\def\oddheading{\parsearg\oddheadingxxx}
+\def\everyheading{\parsearg\everyheadingxxx}
+
+\def\evenfooting{\parsearg\evenfootingxxx}
+\def\oddfooting{\parsearg\oddfootingxxx}
+\def\everyfooting{\parsearg\everyfootingxxx}
+
+{\catcode`\@=0 %
+
+\gdef\evenheadingxxx #1{\evenheadingyyy #1@|@|@|@|\finish}
+\gdef\evenheadingyyy #1@|#2@|#3@|#4\finish{%
+\global\evenheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}}
+
+\gdef\oddheadingxxx #1{\oddheadingyyy #1@|@|@|@|\finish}
+\gdef\oddheadingyyy #1@|#2@|#3@|#4\finish{%
+\global\oddheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}}
+
+\gdef\everyheadingxxx #1{\everyheadingyyy #1@|@|@|@|\finish}
+\gdef\everyheadingyyy #1@|#2@|#3@|#4\finish{%
+\global\evenheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}
+\global\oddheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}}
+
+\gdef\evenfootingxxx #1{\evenfootingyyy #1@|@|@|@|\finish}
+\gdef\evenfootingyyy #1@|#2@|#3@|#4\finish{%
+\global\evenfootline={\rlap{\centerline{#2}}\line{#1\hfil#3}}}
+
+\gdef\oddfootingxxx #1{\oddfootingyyy #1@|@|@|@|\finish}
+\gdef\oddfootingyyy #1@|#2@|#3@|#4\finish{%
+\global\oddfootline={\rlap{\centerline{#2}}\line{#1\hfil#3}}}
+
+\gdef\everyfootingxxx #1{\everyfootingyyy #1@|@|@|@|\finish}
+\gdef\everyfootingyyy #1@|#2@|#3@|#4\finish{%
+\global\evenfootline={\rlap{\centerline{#2}}\line{#1\hfil#3}}
+\global\oddfootline={\rlap{\centerline{#2}}\line{#1\hfil#3}}}
+%
+}% unbind the catcode of @.
+
+% @headings double     turns headings on for double-sided printing.
+% @headings single     turns headings on for single-sided printing.
+% @headings off                turns them off.
+% @headings on         same as @headings double, retained for compatibility.
+% @headings after      turns on double-sided headings after this page.
+% @headings doubleafter        turns on double-sided headings after this page.
+% @headings singleafter turns on single-sided headings after this page.
+% By default, they are off.
+
+\def\headings #1 {\csname HEADINGS#1\endcsname}
+
+\def\HEADINGSoff{
+\global\evenheadline={\hfil} \global\evenfootline={\hfil}
+\global\oddheadline={\hfil} \global\oddfootline={\hfil}}
+\HEADINGSoff
+% When we turn headings on, set the page number to 1.
+% For double-sided printing, put current file name in lower left corner,
+% chapter name on inside top of right hand pages, document
+% title on inside top of left hand pages, and page numbers on outside top
+% edge of all pages.
+\def\HEADINGSdouble{
+%\pagealignmacro
+\global\pageno=1
+\global\evenfootline={\hfil}
+\global\oddfootline={\hfil}
+\global\evenheadline={\line{\folio\hfil\thistitle}}
+\global\oddheadline={\line{\thischapter\hfil\folio}}
+}
+% For single-sided printing, chapter title goes across top left of page,
+% page number on top right.
+\def\HEADINGSsingle{
+%\pagealignmacro
+\global\pageno=1
+\global\evenfootline={\hfil}
+\global\oddfootline={\hfil}
+\global\evenheadline={\line{\thischapter\hfil\folio}}
+\global\oddheadline={\line{\thischapter\hfil\folio}}
+}
+\def\HEADINGSon{\HEADINGSdouble}
+
+\def\HEADINGSafter{\let\HEADINGShook=\HEADINGSdoublex}
+\let\HEADINGSdoubleafter=\HEADINGSafter
+\def\HEADINGSdoublex{%
+\global\evenfootline={\hfil}
+\global\oddfootline={\hfil}
+\global\evenheadline={\line{\folio\hfil\thistitle}}
+\global\oddheadline={\line{\thischapter\hfil\folio}}
+}
+
+\def\HEADINGSsingleafter{\let\HEADINGShook=\HEADINGSsinglex}
+\def\HEADINGSsinglex{%
+\global\evenfootline={\hfil}
+\global\oddfootline={\hfil}
+\global\evenheadline={\line{\thischapter\hfil\folio}}
+\global\oddheadline={\line{\thischapter\hfil\folio}}
+}
+
+% Subroutines used in generating headings
+% Produces Day Month Year style of output.
+\def\today{\number\day\space
+\ifcase\month\or
+January\or February\or March\or April\or May\or June\or
+July\or August\or September\or October\or November\or December\fi
+\space\number\year}
+
+% Use this if you want the Month Day, Year style of output.
+%\def\today{\ifcase\month\or
+%January\or February\or March\or April\or May\or June\or
+%July\or August\or September\or October\or November\or December\fi
+%\space\number\day, \number\year}
+
+% @settitle line...  specifies the title of the document, for headings
+% It generates no output of its own
+
+\def\thistitle{No Title}
+\def\settitle{\parsearg\settitlezzz}
+\def\settitlezzz #1{\gdef\thistitle{#1}}
+
+\message{tables,}
+
+% @tabs -- simple alignment
+
+% These don't work.  For one thing, \+ is defined as outer.
+% So these macros cannot even be defined.
+
+%\def\tabs{\parsearg\tabszzz}
+%\def\tabszzz #1{\settabs\+#1\cr}
+%\def\tabline{\parsearg\tablinezzz}
+%\def\tablinezzz #1{\+#1\cr}
+%\def\&{&}
+
+% Tables -- @table, @ftable, @vtable, @item(x), @kitem(x), @xitem(x).
+
+% default indentation of table text
+\newdimen\tableindent \tableindent=.8in
+% default indentation of @itemize and @enumerate text
+\newdimen\itemindent  \itemindent=.3in
+% margin between end of table item and start of table text.
+\newdimen\itemmargin  \itemmargin=.1in
+
+% used internally for \itemindent minus \itemmargin
+\newdimen\itemmax
+
+% Note @table, @vtable, and @vtable define @item, @itemx, etc., with
+% these defs.
+% They also define \itemindex
+% to index the item name in whatever manner is desired (perhaps none).
+
+\newif\ifitemxneedsnegativevskip
+
+\def\itemxpar{\par\ifitemxneedsnegativevskip\vskip-\parskip\nobreak\fi}
+
+\def\internalBitem{\smallbreak \parsearg\itemzzz}
+\def\internalBitemx{\itemxpar \parsearg\itemzzz}
+
+\def\internalBxitem "#1"{\def\xitemsubtopix{#1} \smallbreak \parsearg\xitemzzz}
+\def\internalBxitemx "#1"{\def\xitemsubtopix{#1} \itemxpar \parsearg\xitemzzz}
+
+\def\internalBkitem{\smallbreak \parsearg\kitemzzz}
+\def\internalBkitemx{\itemxpar \parsearg\kitemzzz}
+
+\def\kitemzzz #1{\dosubind {kw}{\code{#1}}{for {\bf \lastfunction}}%
+                 \itemzzz {#1}}
+
+\def\xitemzzz #1{\dosubind {kw}{\code{#1}}{for {\bf \xitemsubtopic}}%
+                 \itemzzz {#1}}
+
+\def\itemzzz #1{\begingroup %
+  \advance\hsize by -\rightskip
+  \advance\hsize by -\tableindent
+  \setbox0=\hbox{\itemfont{#1}}%
+  \itemindex{#1}%
+  \nobreak % This prevents a break before @itemx.
+  %
+  % Be sure we are not still in the middle of a paragraph.
+  %{\parskip = 0in
+  %\par
+  %}%
+  %
+  % If the item text does not fit in the space we have, put it on a line
+  % by itself, and do not allow a page break either before or after that
+  % line.  We do not start a paragraph here because then if the next
+  % command is, e.g., @kindex, the whatsit would get put into the
+  % horizontal list on a line by itself, resulting in extra blank space.
+  \ifdim \wd0>\itemmax
+    %
+    % Make this a paragraph so we get the \parskip glue and wrapping,
+    % but leave it ragged-right.
+    \begingroup
+      \advance\leftskip by-\tableindent
+      \advance\hsize by\tableindent
+      \advance\rightskip by0pt plus1fil
+      \leavevmode\unhbox0\par
+    \endgroup
+    %
+    % We're going to be starting a paragraph, but we don't want the
+    % \parskip glue -- logically it's part of the @item we just started.
+    \nobreak \vskip-\parskip
+    %
+    % Stop a page break at the \parskip glue coming up.  Unfortunately
+    % we can't prevent a possible page break at the following
+    % \baselineskip glue.
+    \nobreak
+    \endgroup
+    \itemxneedsnegativevskipfalse
+  \else
+    % The item text fits into the space.  Start a paragraph, so that the
+    % following text (if any) will end up on the same line.  Since that
+    % text will be indented by \tableindent, we make the item text be in
+    % a zero-width box.
+    \noindent
+    \rlap{\hskip -\tableindent\box0}\ignorespaces%
+    \endgroup%
+    \itemxneedsnegativevskiptrue%
+  \fi
+}
+
+\def\item{\errmessage{@item while not in a table}}
+\def\itemx{\errmessage{@itemx while not in a table}}
+\def\kitem{\errmessage{@kitem while not in a table}}
+\def\kitemx{\errmessage{@kitemx while not in a table}}
+\def\xitem{\errmessage{@xitem while not in a table}}
+\def\xitemx{\errmessage{@xitemx while not in a table}}
+
+%% Contains a kludge to get @end[description] to work
+\def\description{\tablez{\dontindex}{1}{}{}{}{}}
+
+\def\table{\begingroup\inENV\obeylines\obeyspaces\tablex}
+{\obeylines\obeyspaces%
+\gdef\tablex #1^^M{%
+\tabley\dontindex#1        \endtabley}}
+
+\def\ftable{\begingroup\inENV\obeylines\obeyspaces\ftablex}
+{\obeylines\obeyspaces%
+\gdef\ftablex #1^^M{%
+\tabley\fnitemindex#1        \endtabley
+\def\Eftable{\endgraf\afterenvbreak\endgroup}%
+\let\Etable=\relax}}
+
+\def\vtable{\begingroup\inENV\obeylines\obeyspaces\vtablex}
+{\obeylines\obeyspaces%
+\gdef\vtablex #1^^M{%
+\tabley\vritemindex#1        \endtabley
+\def\Evtable{\endgraf\afterenvbreak\endgroup}%
+\let\Etable=\relax}}
+
+\def\dontindex #1{}
+\def\fnitemindex #1{\doind {fn}{\code{#1}}}%
+\def\vritemindex #1{\doind {vr}{\code{#1}}}%
+
+{\obeyspaces %
+\gdef\tabley#1#2 #3 #4 #5 #6 #7\endtabley{\endgroup%
+\tablez{#1}{#2}{#3}{#4}{#5}{#6}}}
+
+\def\tablez #1#2#3#4#5#6{%
+\aboveenvbreak %
+\begingroup %
+\def\Edescription{\Etable}% Neccessary kludge.
+\let\itemindex=#1%
+\ifnum 0#3>0 \advance \leftskip by #3\mil \fi %
+\ifnum 0#4>0 \tableindent=#4\mil \fi %
+\ifnum 0#5>0 \advance \rightskip by #5\mil \fi %
+\def\itemfont{#2}%
+\itemmax=\tableindent %
+\advance \itemmax by -\itemmargin %
+\advance \leftskip by \tableindent %
+\exdentamount=\tableindent
+\parindent = 0pt
+\parskip = \smallskipamount
+\ifdim \parskip=0pt \parskip=2pt \fi%
+\def\Etable{\endgraf\afterenvbreak\endgroup}%
+\let\item = \internalBitem %
+\let\itemx = \internalBitemx %
+\let\kitem = \internalBkitem %
+\let\kitemx = \internalBkitemx %
+\let\xitem = \internalBxitem %
+\let\xitemx = \internalBxitemx %
+}
+
+% This is the counter used by @enumerate, which is really @itemize
+
+\newcount \itemno
+
+\def\itemize{\parsearg\itemizezzz}
+
+\def\itemizezzz #1{%
+  \begingroup % ended by the @end itemsize
+  \itemizey {#1}{\Eitemize}
+}
+
+\def\itemizey #1#2{%
+\aboveenvbreak %
+\itemmax=\itemindent %
+\advance \itemmax by -\itemmargin %
+\advance \leftskip by \itemindent %
+\exdentamount=\itemindent
+\parindent = 0pt %
+\parskip = \smallskipamount %
+\ifdim \parskip=0pt \parskip=2pt \fi%
+\def#2{\endgraf\afterenvbreak\endgroup}%
+\def\itemcontents{#1}%
+\let\item=\itemizeitem}
+
+% Set sfcode to normal for the chars that usually have another value.
+% These are `.?!:;,'
+\def\frenchspacing{\sfcode46=1000 \sfcode63=1000 \sfcode33=1000
+  \sfcode58=1000 \sfcode59=1000 \sfcode44=1000 }
+
+% \splitoff TOKENS\endmark defines \first to be the first token in
+% TOKENS, and \rest to be the remainder.
+%
+\def\splitoff#1#2\endmark{\def\first{#1}\def\rest{#2}}%
+
+% Allow an optional argument of an uppercase letter, lowercase letter,
+% or number, to specify the first label in the enumerated list.  No
+% argument is the same as `1'.
+%
+\def\enumerate{\parsearg\enumeratezzz}
+\def\enumeratezzz #1{\enumeratey #1  \endenumeratey}
+\def\enumeratey #1 #2\endenumeratey{%
+  \begingroup % ended by the @end enumerate
+  %
+  % If we were given no argument, pretend we were given `1'.
+  \def\thearg{#1}%
+  \ifx\thearg\empty \def\thearg{1}\fi
+  %
+  % Detect if the argument is a single token.  If so, it might be a
+  % letter.  Otherwise, the only valid thing it can be is a number.
+  % (We will always have one token, because of the test we just made.
+  % This is a good thing, since \splitoff doesn't work given nothing at
+  % all -- the first parameter is undelimited.)
+  \expandafter\splitoff\thearg\endmark
+  \ifx\rest\empty
+    % Only one token in the argument.  It could still be anything.
+    % A ``lowercase letter'' is one whose \lccode is nonzero.
+    % An ``uppercase letter'' is one whose \lccode is both nonzero, and
+    %   not equal to itself.
+    % Otherwise, we assume it's a number.
+    %
+    % We need the \relax at the end of the \ifnum lines to stop TeX from
+    % continuing to look for a <number>.
+    %
+    \ifnum\lccode\expandafter`\thearg=0\relax
+      \numericenumerate % a number (we hope)
+    \else
+      % It's a letter.
+      \ifnum\lccode\expandafter`\thearg=\expandafter`\thearg\relax
+        \lowercaseenumerate % lowercase letter
+      \else
+        \uppercaseenumerate % uppercase letter
+      \fi
+    \fi
+  \else
+    % Multiple tokens in the argument.  We hope it's a number.
+    \numericenumerate
+  \fi
+}
+
+% An @enumerate whose labels are integers.  The starting integer is
+% given in \thearg.
+%
+\def\numericenumerate{%
+  \itemno = \thearg
+  \startenumeration{\the\itemno}%
+}
+
+% The starting (lowercase) letter is in \thearg.
+\def\lowercaseenumerate{%
+  \itemno = \expandafter`\thearg
+  \startenumeration{%
+    % Be sure we're not beyond the end of the alphabet.
+    \ifnum\itemno=0
+      \errmessage{No more lowercase letters in @enumerate; get a bigger
+                  alphabet}%
+    \fi
+    \char\lccode\itemno
+  }%
+}
+
+% The starting (uppercase) letter is in \thearg.
+\def\uppercaseenumerate{%
+  \itemno = \expandafter`\thearg
+  \startenumeration{%
+    % Be sure we're not beyond the end of the alphabet.
+    \ifnum\itemno=0
+      \errmessage{No more uppercase letters in @enumerate; get a bigger
+                  alphabet}
+    \fi
+    \char\uccode\itemno
+  }%
+}
+
+% Call itemizey, adding a period to the first argument and supplying the
+% common last two arguments.  Also subtract one from the initial value in
+% \itemno, since @item increments \itemno.
+%
+\def\startenumeration#1{%
+  \advance\itemno by -1
+  \itemizey{#1.}\Eenumerate\flushcr
+}
+
+% @alphaenumerate and @capsenumerate are abbreviations for giving an arg
+% to @enumerate.
+%
+\def\alphaenumerate{\enumerate{a}}
+\def\capsenumerate{\enumerate{A}}
+\def\Ealphaenumerate{\Eenumerate}
+\def\Ecapsenumerate{\Eenumerate}
+
+% Definition of @item while inside @itemize.
+
+\def\itemizeitem{%
+\advance\itemno by 1
+{\let\par=\endgraf \smallbreak}%
+\ifhmode \errmessage{\in hmode at itemizeitem}\fi
+{\parskip=0in \hskip 0pt
+\hbox to 0pt{\hss \itemcontents\hskip \itemmargin}%
+\vadjust{\penalty 1200}}%
+\flushcr}
+
+% @multitable macros
+% Amy Hendrickson, 8/18/94
+%
+% @multitable ... @endmultitable will make as many columns as desired.
+% Contents of each column will wrap at width given in preamble. Width
+% can be specified either with sample text given in a template line,
+% or in percent of \hsize, the current width of text on page.
+
+% Table can continue over pages but will only break between lines.
+
+% To make preamble:
+%
+% Either define widths of columns in terms of percent of \hsize: 
+%   @multitable @percentofhsize .2 .3 .5
+%   @item ...
+%
+%   Numbers following @percentofhsize are the percent of the total
+%   current hsize to be used for each column. You may use as many
+%   columns as desired.
+
+% Or use a template:
+%   @multitable {Column 1 template} {Column 2 template} {Column 3 template}
+%   @item ...
+%   using the widest term desired in each column.
+
+
+% Each new table line starts with @item, each subsequent new column 
+% starts with @tab. Empty columns may be produced by supplying @tab's
+% with nothing between them for as many times as empty columns are needed,
+% ie, @tab@tab@tab will produce two empty columns.
+
+% @item, @tab, @multicolumn or @endmulticolumn do not need to be on their
+% own lines, but it will not hurt if they are.
+
+% Sample multitable:
+
+%   @multitable {Column 1 template} {Column 2 template} {Column 3 template}
+%   @item first col stuff @tab second col stuff @tab third col
+%   @item 
+%   first col stuff 
+%   @tab 
+%   second col stuff 
+%   @tab 
+%   third col 
+%   @item first col stuff @tab second col stuff 
+%   @tab Many paragraphs of text may be used in any column.
+%     
+%         They will wrap at the width determined by the template.
+%   @item@tab@tab This will be in third column.
+%   @endmultitable
+
+% Default dimensions may be reset by user.
+% @intableparskip will set vertical space between paragraphs in table.
+% @intableparindent will set paragraph indent in table.
+% @spacebetweencols will set horizontal space to be left between columns.
+% @spacebetweenlines will set vertical space to be left between lines.
+
+%%%%
+% Dimensions 
+
+\newdimen\intableparskip
+\newdimen\intableparindent
+\newdimen\spacebetweencols
+\newdimen\spacebetweenlines
+\intableparskip=0pt
+\intableparindent=6pt
+\spacebetweencols=12pt
+\spacebetweenlines=12pt
+
+%%%%
+% Macros used to set up halign preamble:
+\let\endsetuptable\relax
+\def\xendsetuptable{\endsetuptable}
+\let\percentofhsize\relax
+\def\xpercentofhsize{\percentofhsize}
+\newif\ifsetpercent
+
+\newcount\colcount
+\def\setuptable#1{\def\firstarg{#1}%
+\ifx\firstarg\xendsetuptable\let\go\relax%
+\else
+  \ifx\firstarg\xpercentofhsize\global\setpercenttrue%
+  \else
+    \ifsetpercent
+       \if#1.\else%
+       \global\advance\colcount by1 %
+       \expandafter\xdef\csname col\the\colcount\endcsname{.#1\hsize}%
+       \fi
+    \else
+       \global\advance\colcount by1
+       \setbox0=\hbox{#1}%
+       \expandafter\xdef\csname col\the\colcount\endcsname{\the\wd0}%
+    \fi%
+  \fi%
+  \let\go\setuptable%
+\fi\go}
+%%%%
+% multitable syntax
+\def\tab{&}
+
+%%%%
+% @multitable ... @endmultitable definitions:
+
+\def\multitable#1\item{\bgroup
+\let\item\cr
+\tolerance=9500
+\hbadness=9500
+\parskip=\intableparskip
+\parindent=\intableparindent
+\overfullrule=0pt
+\global\colcount=0\relax%
+\def\Emultitable{\global\setpercentfalse\global\everycr{}\cr\egroup\egroup}%
+ % To parse everything between @multitable and @item :
+\def\one{#1}\expandafter\setuptable\one\endsetuptable
+ % Need to reset this to 0 after \setuptable.
+\global\colcount=0\relax% 
+ %
+ % This preamble sets up a generic column definition, which will
+ % be used as many times as user calls for columns.
+ % \vtop will set a single line and will also let text wrap and 
+ % continue for many paragraphs if desired.
+\halign\bgroup&\global\advance\colcount by 1\relax%
+\vtop{\hsize=\expandafter\csname col\the\colcount\endcsname
+ % In order to keep entries from bumping into each other
+ % we will add a \leftskip of \spacebetweencols to all columns after
+ % the first one.
+ %  If a template has been used, we will add \spacebetweencols 
+ % to the width of each template entry.
+ %  If user has set preamble in terms of percent of \hsize
+ % we will use that dimension as the width of the column, and
+ % the \leftskip will keep entries from bumping into each other.
+ % Table will start at left margin and final column will justify at
+ % right margin.
+\ifnum\colcount=1
+\else
+  \ifsetpercent
+  \else
+   % If user has <not> set preamble in terms of percent of \hsize
+   % we will advance \hsize by \spacebetweencols 
+  \advance\hsize by \spacebetweencols
+  \fi
+ % In either case we will make \leftskip=\spacebetweencols:
+\leftskip=\spacebetweencols
+\fi
+\noindent##}\cr%
+ % \everycr will reset column counter, \colcount, at the end of
+ % each line. Every column  entry will cause \colcount to advance by one. 
+ % The table preamble
+ % looks at the current \colcount to find the correct column width.
+\global\everycr{\noalign{\nointerlineskip\vskip\spacebetweenlines
+\filbreak%% keeps underfull box messages off when table breaks over pages.
+\global\colcount=0\relax}}}
+
+\message{indexing,}
+% Index generation facilities
+
+% Define \newwrite to be identical to plain tex's \newwrite
+% except not \outer, so it can be used within \newindex.
+{\catcode`\@=11
+\gdef\newwrite{\alloc@7\write\chardef\sixt@@n}}
+
+% \newindex {foo} defines an index named foo.
+% It automatically defines \fooindex such that
+% \fooindex ...rest of line... puts an entry in the index foo.
+% It also defines \fooindfile to be the number of the output channel for
+% the file that        accumulates this index.  The file's extension is foo.
+% The name of an index should be no more than 2 characters long
+% for the sake of vms.
+
+\def\newindex #1{
+\expandafter\newwrite \csname#1indfile\endcsname% Define number for output file
+\openout \csname#1indfile\endcsname \jobname.#1        % Open the file
+\expandafter\xdef\csname#1index\endcsname{%    % Define \xxxindex
+\noexpand\doindex {#1}}
+}
+
+% @defindex foo  ==  \newindex{foo}
+
+\def\defindex{\parsearg\newindex}
+
+% Define @defcodeindex, like @defindex except put all entries in @code.
+
+\def\newcodeindex #1{
+\expandafter\newwrite \csname#1indfile\endcsname% Define number for output file
+\openout \csname#1indfile\endcsname \jobname.#1        % Open the file
+\expandafter\xdef\csname#1index\endcsname{%    % Define \xxxindex
+\noexpand\docodeindex {#1}}
+}
+
+\def\defcodeindex{\parsearg\newcodeindex}
+
+% @synindex foo bar    makes index foo feed into index bar.
+% Do this instead of @defindex foo if you don't want it as a separate index.
+\def\synindex #1 #2 {%
+\expandafter\let\expandafter\synindexfoo\expandafter=\csname#2indfile\endcsname
+\expandafter\let\csname#1indfile\endcsname=\synindexfoo
+\expandafter\xdef\csname#1index\endcsname{%    % Define \xxxindex
+\noexpand\doindex {#2}}%
+}
+
+% @syncodeindex foo bar   similar, but put all entries made for index foo
+% inside @code.
+\def\syncodeindex #1 #2 {%
+\expandafter\let\expandafter\synindexfoo\expandafter=\csname#2indfile\endcsname
+\expandafter\let\csname#1indfile\endcsname=\synindexfoo
+\expandafter\xdef\csname#1index\endcsname{%    % Define \xxxindex
+\noexpand\docodeindex {#2}}%
+}
+
+% Define \doindex, the driver for all \fooindex macros.
+% Argument #1 is generated by the calling \fooindex macro,
+%  and it is "foo", the name of the index.
+
+% \doindex just uses \parsearg; it calls \doind for the actual work.
+% This is because \doind is more useful to call from other macros.
+
+% There is also \dosubind {index}{topic}{subtopic}
+% which makes an entry in a two-level index such as the operation index.
+
+\def\doindex#1{\edef\indexname{#1}\parsearg\singleindexer}
+\def\singleindexer #1{\doind{\indexname}{#1}}
+
+% like the previous two, but they put @code around the argument.
+\def\docodeindex#1{\edef\indexname{#1}\parsearg\singlecodeindexer}
+\def\singlecodeindexer #1{\doind{\indexname}{\code{#1}}}
+
+\def\indexdummies{%
+% Take care of the plain tex accent commands.
+\def\"{\realbackslash "}%
+\def\`{\realbackslash `}%
+\def\'{\realbackslash '}%
+\def\^{\realbackslash ^}%
+\def\~{\realbackslash ~}%
+\def\={\realbackslash =}%
+\def\b{\realbackslash b}%
+\def\c{\realbackslash c}%
+\def\d{\realbackslash d}%
+\def\u{\realbackslash u}%
+\def\v{\realbackslash v}%
+\def\H{\realbackslash H}%
+% Take care of the plain tex special European modified letters.
+\def\oe{\realbackslash oe}%
+\def\ae{\realbackslash ae}%
+\def\aa{\realbackslash aa}%
+\def\OE{\realbackslash OE}%
+\def\AE{\realbackslash AE}%
+\def\AA{\realbackslash AA}%
+\def\o{\realbackslash o}%
+\def\O{\realbackslash O}%
+\def\l{\realbackslash l}%
+\def\L{\realbackslash L}%
+\def\ss{\realbackslash ss}%
+% Take care of texinfo commands likely to appear in an index entry.
+\def\_{{\realbackslash _}}%
+\def\w{\realbackslash w }%
+\def\bf{\realbackslash bf }%
+\def\rm{\realbackslash rm }%
+\def\sl{\realbackslash sl }%
+\def\sf{\realbackslash sf}%
+\def\tt{\realbackslash tt}%
+\def\gtr{\realbackslash gtr}%
+\def\less{\realbackslash less}%
+\def\hat{\realbackslash hat}%
+\def\char{\realbackslash char}%
+\def\TeX{\realbackslash TeX}%
+\def\dots{\realbackslash dots }%
+\def\copyright{\realbackslash copyright }%
+\def\tclose##1{\realbackslash tclose {##1}}%
+\def\code##1{\realbackslash code {##1}}%
+\def\samp##1{\realbackslash samp {##1}}%
+\def\t##1{\realbackslash r {##1}}%
+\def\r##1{\realbackslash r {##1}}%
+\def\i##1{\realbackslash i {##1}}%
+\def\b##1{\realbackslash b {##1}}%
+\def\cite##1{\realbackslash cite {##1}}%
+\def\key##1{\realbackslash key {##1}}%
+\def\file##1{\realbackslash file {##1}}%
+\def\var##1{\realbackslash var {##1}}%
+\def\kbd##1{\realbackslash kbd {##1}}%
+\def\dfn##1{\realbackslash dfn {##1}}%
+\def\emph##1{\realbackslash emph {##1}}%
+}
+
+% \indexnofonts no-ops all font-change commands.
+% This is used when outputting the strings to sort the index by.
+\def\indexdummyfont#1{#1}
+\def\indexdummytex{TeX}
+\def\indexdummydots{...}
+
+\def\indexnofonts{%
+% Just ignore accents.
+\let\"=\indexdummyfont
+\let\`=\indexdummyfont
+\let\'=\indexdummyfont
+\let\^=\indexdummyfont
+\let\~=\indexdummyfont
+\let\==\indexdummyfont
+\let\b=\indexdummyfont
+\let\c=\indexdummyfont
+\let\d=\indexdummyfont
+\let\u=\indexdummyfont
+\let\v=\indexdummyfont
+\let\H=\indexdummyfont
+% Take care of the plain tex special European modified letters.
+\def\oe{oe}%
+\def\ae{ae}%
+\def\aa{aa}%
+\def\OE{OE}%
+\def\AE{AE}%
+\def\AA{AA}%
+\def\o{o}%
+\def\O{O}%
+\def\l{l}%
+\def\L{L}%
+\def\ss{ss}%
+\let\w=\indexdummyfont
+\let\t=\indexdummyfont
+\let\r=\indexdummyfont
+\let\i=\indexdummyfont
+\let\b=\indexdummyfont
+\let\emph=\indexdummyfont
+\let\strong=\indexdummyfont
+\let\cite=\indexdummyfont
+\let\sc=\indexdummyfont
+%Don't no-op \tt, since it isn't a user-level command
+% and is used in the definitions of the active chars like <, >, |...
+%\let\tt=\indexdummyfont
+\let\tclose=\indexdummyfont
+\let\code=\indexdummyfont
+\let\file=\indexdummyfont
+\let\samp=\indexdummyfont
+\let\kbd=\indexdummyfont
+\let\key=\indexdummyfont
+\let\var=\indexdummyfont
+\let\TeX=\indexdummytex
+\let\dots=\indexdummydots
+}
+
+% To define \realbackslash, we must make \ not be an escape.
+% We must first make another character (@) an escape
+% so we do not become unable to do a definition.
+
+{\catcode`\@=0 \catcode`\\=\other
+@gdef@realbackslash{\}}
+
+\let\indexbackslash=0  %overridden during \printindex.
+
+\let\SETmarginindex=\relax %initialize!
+% workhorse for all \fooindexes
+% #1 is name of index, #2 is stuff to put there
+\def\doind #1#2{%
+% Put the index entry in the margin if desired.
+\ifx\SETmarginindex\relax\else%
+\insert\margin{\hbox{\vrule height8pt depth3pt width0pt #2}}%
+\fi%
+{\count10=\lastpenalty %
+{\indexdummies % Must do this here, since \bf, etc expand at this stage
+\escapechar=`\\%
+{\let\folio=0% Expand all macros now EXCEPT \folio
+\def\rawbackslashxx{\indexbackslash}% \indexbackslash isn't defined now
+% so it will be output as is; and it will print as backslash in the indx.
+%
+% Now process the index-string once, with all font commands turned off,
+% to get the string to sort the index by.
+{\indexnofonts
+\xdef\temp1{#2}%
+}%
+% Now produce the complete index entry.  We process the index-string again,
+% this time with font commands expanded, to get what to print in the index.
+\edef\temp{%
+\write \csname#1indfile\endcsname{%
+\realbackslash entry {\temp1}{\folio}{#2}}}%
+\temp }%
+}\penalty\count10}}
+
+\def\dosubind #1#2#3{%
+{\count10=\lastpenalty %
+{\indexdummies % Must do this here, since \bf, etc expand at this stage
+\escapechar=`\\%
+{\let\folio=0%
+\def\rawbackslashxx{\indexbackslash}%
+%
+% Now process the index-string once, with all font commands turned off,
+% to get the string to sort the index by.
+{\indexnofonts
+\xdef\temp1{#2 #3}%
+}%
+% Now produce the complete index entry.  We process the index-string again,
+% this time with font commands expanded, to get what to print in the index.
+\edef\temp{%
+\write \csname#1indfile\endcsname{%
+\realbackslash entry {\temp1}{\folio}{#2}{#3}}}%
+\temp }%
+}\penalty\count10}}
+
+% The index entry written in the file actually looks like
+%  \entry {sortstring}{page}{topic}
+% or
+%  \entry {sortstring}{page}{topic}{subtopic}
+% The texindex program reads in these files and writes files
+% containing these kinds of lines:
+%  \initial {c}
+%     before the first topic whose initial is c
+%  \entry {topic}{pagelist}
+%     for a topic that is used without subtopics
+%  \primary {topic}
+%     for the beginning of a topic that is used with subtopics
+%  \secondary {subtopic}{pagelist}
+%     for each subtopic.
+
+% Define the user-accessible indexing commands
+% @findex, @vindex, @kindex, @cindex.
+
+\def\findex {\fnindex}
+\def\kindex {\kyindex}
+\def\cindex {\cpindex}
+\def\vindex {\vrindex}
+\def\tindex {\tpindex}
+\def\pindex {\pgindex}
+
+\def\cindexsub {\begingroup\obeylines\cindexsub}
+{\obeylines %
+\gdef\cindexsub "#1" #2^^M{\endgroup %
+\dosubind{cp}{#2}{#1}}}
+
+% Define the macros used in formatting output of the sorted index material.
+
+% This is what you call to cause a particular index to get printed.
+% Write
+% @unnumbered Function Index
+% @printindex fn
+
+\def\printindex{\parsearg\doprintindex}
+
+\def\doprintindex#1{%
+  \tex
+  \dobreak \chapheadingskip {10000}
+  \catcode`\%=\other\catcode`\&=\other\catcode`\#=\other
+  \catcode`\$=\other
+  \catcode`\~=\other
+  \indexbreaks
+  %
+  % The following don't help, since the chars were translated
+  % when the raw index was written, and their fonts were discarded
+  % due to \indexnofonts.
+  %\catcode`\"=\active
+  %\catcode`\^=\active
+  %\catcode`\_=\active
+  %\catcode`\|=\active
+  %\catcode`\<=\active
+  %\catcode`\>=\active
+  % %
+  \def\indexbackslash{\rawbackslashxx}
+  \indexfonts\rm \tolerance=9500 \advance\baselineskip -1pt
+  \begindoublecolumns
+  %
+  % See if the index file exists and is nonempty.
+  \openin 1 \jobname.#1s
+  \ifeof 1
+    % \enddoublecolumns gets confused if there is no text in the index,
+    % and it loses the chapter title and the aux file entries for the
+    % index.  The easiest way to prevent this problem is to make sure
+    % there is some text.
+    (Index is nonexistent)
+    \else
+    %
+    % If the index file exists but is empty, then \openin leaves \ifeof
+    % false.  We have to make TeX try to read something from the file, so
+    % it can discover if there is anything in it.
+    \read 1 to \temp
+    \ifeof 1
+      (Index is empty)
+    \else
+      \input \jobname.#1s
+    \fi
+  \fi
+  \closein 1
+  \enddoublecolumns
+  \Etex
+}
+
+% These macros are used by the sorted index file itself.
+% Change them to control the appearance of the index.
+
+% Same as \bigskipamount except no shrink.
+% \balancecolumns gets confused if there is any shrink.
+\newskip\initialskipamount \initialskipamount 12pt plus4pt
+
+\def\initial #1{%
+{\let\tentt=\sectt \let\tt=\sectt \let\sf=\sectt
+\ifdim\lastskip<\initialskipamount
+\removelastskip \penalty-200 \vskip \initialskipamount\fi
+\line{\secbf#1\hfill}\kern 2pt\penalty10000}}
+
+% This typesets a paragraph consisting of #1, dot leaders, and then #2
+% flush to the right margin.  It is used for index and table of contents
+% entries.  The paragraph is indented by \leftskip.
+%
+\def\entry #1#2{\begingroup
+  %
+  % Start a new paragraph if necessary, so our assignments below can't
+  % affect previous text.
+  \par
+  %
+  % Do not fill out the last line with white space.
+  \parfillskip = 0in
+  %
+  % No extra space above this paragraph.
+  \parskip = 0in
+  %
+  % Do not prefer a separate line ending with a hyphen to fewer lines.
+  \finalhyphendemerits = 0
+  %
+  % \hangindent is only relevant when the entry text and page number
+  % don't both fit on one line.  In that case, bob suggests starting the
+  % dots pretty far over on the line.  Unfortunately, a large
+  % indentation looks wrong when the entry text itself is broken across
+  % lines.  So we use a small indentation and put up with long leaders.
+  %
+  % \hangafter is reset to 1 (which is the value we want) at the start
+  % of each paragraph, so we need not do anything with that.
+  \hangindent=2em
+  %
+  % When the entry text needs to be broken, just fill out the first line
+  % with blank space.
+  \rightskip = 0pt plus1fil
+  %
+  % Start a ``paragraph'' for the index entry so the line breaking
+  % parameters we've set above will have an effect.
+  \noindent
+  %
+  % Insert the text of the index entry.  TeX will do line-breaking on it.
+  #1%
+  % The following is kluged to not output a line of dots in the index if
+  % there are no page numbers.  The next person who breaks this will be
+  % cursed by a Unix daemon.
+  \def\tempa{{\rm }}%
+  \def\tempb{#2}%
+  \edef\tempc{\tempa}%
+  \edef\tempd{\tempb}%
+  \ifx\tempc\tempd\ \else%
+    %
+    % If we must, put the page number on a line of its own, and fill out
+    % this line with blank space.  (The \hfil is overwhelmed with the
+    % fill leaders glue in \indexdotfill if the page number does fit.)
+    \hfil\penalty50
+    \null\nobreak\indexdotfill % Have leaders before the page number.
+    %
+    % The `\ ' here is removed by the implicit \unskip that TeX does as
+    % part of (the primitive) \par.  Without it, a spurious underfull
+    % \hbox ensues.
+    \ #2% The page number ends the paragraph.
+  \fi%
+  \par
+\endgroup}
+
+% Like \dotfill except takes at least 1 em.
+\def\indexdotfill{\cleaders
+  \hbox{$\mathsurround=0pt \mkern1.5mu ${\it .}$ \mkern1.5mu$}\hskip 1em plus 1fill}
+
+\def\primary #1{\line{#1\hfil}}
+
+\newskip\secondaryindent \secondaryindent=0.5cm
+
+\def\secondary #1#2{
+{\parfillskip=0in \parskip=0in
+\hangindent =1in \hangafter=1
+\noindent\hskip\secondaryindent\hbox{#1}\indexdotfill #2\par
+}}
+
+%% Define two-column mode, which is used in indexes.
+%% Adapted from the TeXbook, page 416.
+\catcode `\@=11
+
+\newbox\partialpage
+
+\newdimen\doublecolumnhsize
+
+\def\begindoublecolumns{\begingroup
+  % Grab any single-column material above us.
+  \output = {\global\setbox\partialpage
+    =\vbox{\unvbox255\kern -\topskip \kern \baselineskip}}%
+  \eject
+  %
+  % Now switch to the double-column output routine.
+  \output={\doublecolumnout}%
+  %
+  % Change the page size parameters.  We could do this once outside this
+  % routine, in each of @smallbook, @afourpaper, and the default 8.5x11
+  % format, but then we repeat the same computation.  Repeating a couple
+  % of assignments once per index is clearly meaningless for the
+  % execution time, so we may as well do it once.
+  %
+  % First we halve the line length, less a little for the gutter between
+  % the columns.  We compute the gutter based on the line length, so it
+  % changes automatically with the paper format.  The magic constant
+  % below is chosen so that the gutter has the same value (well, +- <
+  % 1pt) as it did when we hard-coded it.
+  %
+  % We put the result in a separate register, \doublecolumhsize, so we
+  % can restore it in \pagesofar, after \hsize itself has (potentially)
+  % been clobbered.
+  %
+  \doublecolumnhsize = \hsize
+    \advance\doublecolumnhsize by -.04154\hsize
+    \divide\doublecolumnhsize by 2
+  \hsize = \doublecolumnhsize
+  %
+  % Double the \vsize as well.  (We don't need a separate register here,
+  % since nobody clobbers \vsize.)
+  \vsize = 2\vsize
+  \doublecolumnpagegoal
+}
+
+\def\enddoublecolumns{\eject \endgroup \pagegoal=\vsize \unvbox\partialpage}
+
+\def\doublecolumnsplit{\splittopskip=\topskip \splitmaxdepth=\maxdepth
+  \global\dimen@=\pageheight \global\advance\dimen@ by-\ht\partialpage
+  \global\setbox1=\vsplit255 to\dimen@ \global\setbox0=\vbox{\unvbox1}
+  \global\setbox3=\vsplit255 to\dimen@ \global\setbox2=\vbox{\unvbox3}
+  \ifdim\ht0>\dimen@ \setbox255=\vbox{\unvbox0\unvbox2} \global\setbox255=\copy5 \fi
+  \ifdim\ht2>\dimen@ \setbox255=\vbox{\unvbox0\unvbox2} \global\setbox255=\copy5 \fi
+}
+\def\doublecolumnpagegoal{%
+  \dimen@=\vsize \advance\dimen@ by-2\ht\partialpage \global\pagegoal=\dimen@
+}
+\def\pagesofar{\unvbox\partialpage %
+  \hsize=\doublecolumnhsize % have to restore this since output routine
+  \wd0=\hsize \wd2=\hsize \hbox to\pagewidth{\box0\hfil\box2}}
+\def\doublecolumnout{%
+  \setbox5=\copy255
+  {\vbadness=10000 \doublecolumnsplit}
+  \ifvbox255
+    \setbox0=\vtop to\dimen@{\unvbox0}
+    \setbox2=\vtop to\dimen@{\unvbox2}
+    \onepageout\pagesofar \unvbox255 \penalty\outputpenalty
+  \else
+    \setbox0=\vbox{\unvbox5}
+    \ifvbox0
+      \dimen@=\ht0 \advance\dimen@ by\topskip \advance\dimen@ by-\baselineskip
+      \divide\dimen@ by2 \splittopskip=\topskip \splitmaxdepth=\maxdepth
+      {\vbadness=10000
+       \loop \global\setbox5=\copy0
+          \setbox1=\vsplit5 to\dimen@
+          \setbox3=\vsplit5 to\dimen@
+          \ifvbox5 \global\advance\dimen@ by1pt \repeat
+        \setbox0=\vbox to\dimen@{\unvbox1}
+        \setbox2=\vbox to\dimen@{\unvbox3}
+        \global\setbox\partialpage=\vbox{\pagesofar}
+        \doublecolumnpagegoal
+      }
+    \fi
+  \fi
+}
+
+\catcode `\@=\other
+\message{sectioning,}
+% Define chapters, sections, etc.
+
+\newcount \chapno
+\newcount \secno        \secno=0
+\newcount \subsecno     \subsecno=0
+\newcount \subsubsecno  \subsubsecno=0
+
+% This counter is funny since it counts through charcodes of letters A, B, ...
+\newcount \appendixno  \appendixno = `\@
+\def\appendixletter{\char\the\appendixno}
+
+\newwrite \contentsfile
+% This is called from \setfilename.
+\def\opencontents{\openout \contentsfile = \jobname.toc}
+
+% Each @chapter defines this as the name of the chapter.
+% page headings and footings can use it.  @section does likewise
+
+\def\thischapter{} \def\thissection{}
+\def\seccheck#1{\if \pageno<0 %
+\errmessage{@#1 not allowed after generating table of contents}\fi
+%
+}
+
+\def\chapternofonts{%
+\let\rawbackslash=\relax%
+\let\frenchspacing=\relax%
+\def\result{\realbackslash result}
+\def\equiv{\realbackslash equiv}
+\def\expansion{\realbackslash expansion}
+\def\print{\realbackslash print}
+\def\TeX{\realbackslash TeX}
+\def\dots{\realbackslash dots}
+\def\copyright{\realbackslash copyright}
+\def\tt{\realbackslash tt}
+\def\bf{\realbackslash bf }
+\def\w{\realbackslash w}
+\def\less{\realbackslash less}
+\def\gtr{\realbackslash gtr}
+\def\hat{\realbackslash hat}
+\def\char{\realbackslash char}
+\def\tclose##1{\realbackslash tclose {##1}}
+\def\code##1{\realbackslash code {##1}}
+\def\samp##1{\realbackslash samp {##1}}
+\def\r##1{\realbackslash r {##1}}
+\def\b##1{\realbackslash b {##1}}
+\def\key##1{\realbackslash key {##1}}
+\def\file##1{\realbackslash file {##1}}
+\def\kbd##1{\realbackslash kbd {##1}}
+% These are redefined because @smartitalic wouldn't work inside xdef.
+\def\i##1{\realbackslash i {##1}}
+\def\cite##1{\realbackslash cite {##1}}
+\def\var##1{\realbackslash var {##1}}
+\def\emph##1{\realbackslash emph {##1}}
+\def\dfn##1{\realbackslash dfn {##1}}
+}
+
+\newcount\absseclevel % used to calculate proper heading level
+\newcount\secbase\secbase=0 % @raise/lowersections modify this count
+
+% @raisesections: treat @section as chapter, @subsection as section, etc.
+\def\raisesections{\global\advance\secbase by -1}
+\let\up=\raisesections % original BFox name
+
+% @lowersections: treat @chapter as section, @section as subsection, etc.
+\def\lowersections{\global\advance\secbase by 1}
+\let\down=\lowersections % original BFox name
+
+% Choose a numbered-heading macro
+% #1 is heading level if unmodified by @raisesections or @lowersections
+% #2 is text for heading
+\def\numhead#1#2{\absseclevel=\secbase\advance\absseclevel by #1
+\ifcase\absseclevel
+  \chapterzzz{#2}
+\or
+  \seczzz{#2}
+\or
+  \numberedsubseczzz{#2}
+\or
+  \numberedsubsubseczzz{#2}
+\else
+  \ifnum \absseclevel<0
+    \chapterzzz{#2}
+  \else
+    \numberedsubsubseczzz{#2}
+  \fi
+\fi
+}
+
+% like \numhead, but chooses appendix heading levels
+\def\apphead#1#2{\absseclevel=\secbase\advance\absseclevel by #1
+\ifcase\absseclevel
+  \appendixzzz{#2}
+\or
+  \appendixsectionzzz{#2}
+\or
+  \appendixsubseczzz{#2}
+\or
+  \appendixsubsubseczzz{#2}
+\else
+  \ifnum \absseclevel<0
+    \appendixzzz{#2}
+  \else
+    \appendixsubsubseczzz{#2}
+  \fi
+\fi
+}
+
+% like \numhead, but chooses numberless heading levels
+\def\unnmhead#1#2{\absseclevel=\secbase\advance\absseclevel by #1
+\ifcase\absseclevel
+  \unnumberedzzz{#2}
+\or
+  \unnumberedseczzz{#2}
+\or
+  \unnumberedsubseczzz{#2}
+\or
+  \unnumberedsubsubseczzz{#2}
+\else
+  \ifnum \absseclevel<0
+    \unnumberedzzz{#2}
+  \else
+    \unnumberedsubsubseczzz{#2}
+  \fi
+\fi
+}
+
+
+\def\thischaptername{No Chapter Title}
+\outer\def\chapter{\parsearg\chapteryyy}
+\def\chapteryyy #1{\numhead0{#1}} % normally numhead0 calls chapterzzz
+\def\chapterzzz #1{\seccheck{chapter}%
+\secno=0 \subsecno=0 \subsubsecno=0
+\global\advance \chapno by 1 \message{\putwordChapter \the\chapno}%
+\chapmacro {#1}{\the\chapno}%
+\gdef\thissection{#1}%
+\gdef\thischaptername{#1}%
+% We don't substitute the actual chapter name into \thischapter
+% because we don't want its macros evaluated now.
+\xdef\thischapter{\putwordChapter{} \the\chapno: \noexpand\thischaptername}%
+{\chapternofonts%
+\edef\temp{{\realbackslash chapentry {#1}{\the\chapno}{\noexpand\folio}}}%
+\escapechar=`\\%
+\write \contentsfile \temp  %
+\donoderef %
+\global\let\section = \numberedsec
+\global\let\subsection = \numberedsubsec
+\global\let\subsubsection = \numberedsubsubsec
+}}
+
+\outer\def\appendix{\parsearg\appendixyyy}
+\def\appendixyyy #1{\apphead0{#1}} % normally apphead0 calls appendixzzz
+\def\appendixzzz #1{\seccheck{appendix}%
+\secno=0 \subsecno=0 \subsubsecno=0
+\global\advance \appendixno by 1 \message{Appendix \appendixletter}%
+\chapmacro {#1}{\putwordAppendix{} \appendixletter}%
+\gdef\thissection{#1}%
+\gdef\thischaptername{#1}%
+\xdef\thischapter{\putwordAppendix{} \appendixletter: \noexpand\thischaptername}%
+{\chapternofonts%
+\edef\temp{{\realbackslash chapentry
+  {#1}{\putwordAppendix{} \appendixletter}{\noexpand\folio}}}%
+\escapechar=`\\%
+\write \contentsfile \temp  %
+\appendixnoderef %
+\global\let\section = \appendixsec
+\global\let\subsection = \appendixsubsec
+\global\let\subsubsection = \appendixsubsubsec
+}}
+
+\outer\def\top{\parsearg\unnumberedyyy}
+\outer\def\unnumbered{\parsearg\unnumberedyyy}
+\def\unnumberedyyy #1{\unnmhead0{#1}} % normally unnmhead0 calls unnumberedzzz
+\def\unnumberedzzz #1{\seccheck{unnumbered}%
+\secno=0 \subsecno=0 \subsubsecno=0
+%
+% This used to be simply \message{#1}, but TeX fully expands the
+% argument to \message.  Therefore, if #1 contained @-commands, TeX
+% expanded them.  For example, in `@unnumbered The @cite{Book}', TeX
+% expanded @cite (which turns out to cause errors because \cite is meant
+% to be executed, not expanded).
+%
+% Anyway, we don't want the fully-expanded definition of @cite to appear
+% as a result of the \message, we just want `@cite' itself.  We use
+% \the<toks register> to achieve this: TeX expands \the<toks> only once,
+% simply yielding the contents of the <toks register>.
+\toks0 = {#1}\message{(\the\toks0)}%
+%
+\unnumbchapmacro {#1}%
+\gdef\thischapter{#1}\gdef\thissection{#1}%
+{\chapternofonts%
+\edef\temp{{\realbackslash unnumbchapentry {#1}{\noexpand\folio}}}%
+\escapechar=`\\%
+\write \contentsfile \temp  %
+\unnumbnoderef %
+\global\let\section = \unnumberedsec
+\global\let\subsection = \unnumberedsubsec
+\global\let\subsubsection = \unnumberedsubsubsec
+}}
+
+\outer\def\numberedsec{\parsearg\secyyy}
+\def\secyyy #1{\numhead1{#1}} % normally calls seczzz
+\def\seczzz #1{\seccheck{section}%
+\subsecno=0 \subsubsecno=0 \global\advance \secno by 1 %
+\gdef\thissection{#1}\secheading {#1}{\the\chapno}{\the\secno}%
+{\chapternofonts%
+\edef\temp{{\realbackslash secentry %
+{#1}{\the\chapno}{\the\secno}{\noexpand\folio}}}%
+\escapechar=`\\%
+\write \contentsfile \temp %
+\donoderef %
+\penalty 10000 %
+}}
+
+\outer\def\appenixsection{\parsearg\appendixsecyyy}
+\outer\def\appendixsec{\parsearg\appendixsecyyy}
+\def\appendixsecyyy #1{\apphead1{#1}} % normally calls appendixsectionzzz
+\def\appendixsectionzzz #1{\seccheck{appendixsection}%
+\subsecno=0 \subsubsecno=0 \global\advance \secno by 1 %
+\gdef\thissection{#1}\secheading {#1}{\appendixletter}{\the\secno}%
+{\chapternofonts%
+\edef\temp{{\realbackslash secentry %
+{#1}{\appendixletter}{\the\secno}{\noexpand\folio}}}%
+\escapechar=`\\%
+\write \contentsfile \temp %
+\appendixnoderef %
+\penalty 10000 %
+}}
+
+\outer\def\unnumberedsec{\parsearg\unnumberedsecyyy}
+\def\unnumberedsecyyy #1{\unnmhead1{#1}} % normally calls unnumberedseczzz
+\def\unnumberedseczzz #1{\seccheck{unnumberedsec}%
+\plainsecheading {#1}\gdef\thissection{#1}%
+{\chapternofonts%
+\edef\temp{{\realbackslash unnumbsecentry{#1}{\noexpand\folio}}}%
+\escapechar=`\\%
+\write \contentsfile \temp %
+\unnumbnoderef %
+\penalty 10000 %
+}}
+
+\outer\def\numberedsubsec{\parsearg\numberedsubsecyyy}
+\def\numberedsubsecyyy #1{\numhead2{#1}} % normally calls numberedsubseczzz
+\def\numberedsubseczzz #1{\seccheck{subsection}%
+\gdef\thissection{#1}\subsubsecno=0 \global\advance \subsecno by 1 %
+\subsecheading {#1}{\the\chapno}{\the\secno}{\the\subsecno}%
+{\chapternofonts%
+\edef\temp{{\realbackslash subsecentry %
+{#1}{\the\chapno}{\the\secno}{\the\subsecno}{\noexpand\folio}}}%
+\escapechar=`\\%
+\write \contentsfile \temp %
+\donoderef %
+\penalty 10000 %
+}}
+
+\outer\def\appendixsubsec{\parsearg\appendixsubsecyyy}
+\def\appendixsubsecyyy #1{\apphead2{#1}} % normally calls appendixsubseczzz
+\def\appendixsubseczzz #1{\seccheck{appendixsubsec}%
+\gdef\thissection{#1}\subsubsecno=0 \global\advance \subsecno by 1 %
+\subsecheading {#1}{\appendixletter}{\the\secno}{\the\subsecno}%
+{\chapternofonts%
+\edef\temp{{\realbackslash subsecentry %
+{#1}{\appendixletter}{\the\secno}{\the\subsecno}{\noexpand\folio}}}%
+\escapechar=`\\%
+\write \contentsfile \temp %
+\appendixnoderef %
+\penalty 10000 %
+}}
+
+\outer\def\unnumberedsubsec{\parsearg\unnumberedsubsecyyy}
+\def\unnumberedsubsecyyy #1{\unnmhead2{#1}} %normally calls unnumberedsubseczzz
+\def\unnumberedsubseczzz #1{\seccheck{unnumberedsubsec}%
+\plainsecheading {#1}\gdef\thissection{#1}%
+{\chapternofonts%
+\edef\temp{{\realbackslash unnumbsubsecentry{#1}{\noexpand\folio}}}%
+\escapechar=`\\%
+\write \contentsfile \temp %
+\unnumbnoderef %
+\penalty 10000 %
+}}
+
+\outer\def\numberedsubsubsec{\parsearg\numberedsubsubsecyyy}
+\def\numberedsubsubsecyyy #1{\numhead3{#1}} % normally numberedsubsubseczzz
+\def\numberedsubsubseczzz #1{\seccheck{subsubsection}%
+\gdef\thissection{#1}\global\advance \subsubsecno by 1 %
+\subsubsecheading {#1}
+  {\the\chapno}{\the\secno}{\the\subsecno}{\the\subsubsecno}%
+{\chapternofonts%
+\edef\temp{{\realbackslash subsubsecentry %
+  {#1}
+  {\the\chapno}{\the\secno}{\the\subsecno}{\the\subsubsecno}
+  {\noexpand\folio}}}%
+\escapechar=`\\%
+\write \contentsfile \temp %
+\donoderef %
+\penalty 10000 %
+}}
+
+\outer\def\appendixsubsubsec{\parsearg\appendixsubsubsecyyy}
+\def\appendixsubsubsecyyy #1{\apphead3{#1}} % normally appendixsubsubseczzz
+\def\appendixsubsubseczzz #1{\seccheck{appendixsubsubsec}%
+\gdef\thissection{#1}\global\advance \subsubsecno by 1 %
+\subsubsecheading {#1}
+  {\appendixletter}{\the\secno}{\the\subsecno}{\the\subsubsecno}%
+{\chapternofonts%
+\edef\temp{{\realbackslash subsubsecentry{#1}%
+  {\appendixletter}
+  {\the\secno}{\the\subsecno}{\the\subsubsecno}{\noexpand\folio}}}%
+\escapechar=`\\%
+\write \contentsfile \temp %
+\appendixnoderef %
+\penalty 10000 %
+}}
+
+\outer\def\unnumberedsubsubsec{\parsearg\unnumberedsubsubsecyyy}
+\def\unnumberedsubsubsecyyy #1{\unnmhead3{#1}} %normally unnumberedsubsubseczzz
+\def\unnumberedsubsubseczzz #1{\seccheck{unnumberedsubsubsec}%
+\plainsecheading {#1}\gdef\thissection{#1}%
+{\chapternofonts%
+\edef\temp{{\realbackslash unnumbsubsubsecentry{#1}{\noexpand\folio}}}%
+\escapechar=`\\%
+\write \contentsfile \temp %
+\unnumbnoderef %
+\penalty 10000 %
+}}
+
+% These are variants which are not "outer", so they can appear in @ifinfo.
+% Actually, they should now be obsolete; ordinary section commands should work.
+\def\infotop{\parsearg\unnumberedzzz}
+\def\infounnumbered{\parsearg\unnumberedzzz}
+\def\infounnumberedsec{\parsearg\unnumberedseczzz}
+\def\infounnumberedsubsec{\parsearg\unnumberedsubseczzz}
+\def\infounnumberedsubsubsec{\parsearg\unnumberedsubsubseczzz}
+
+\def\infoappendix{\parsearg\appendixzzz}
+\def\infoappendixsec{\parsearg\appendixseczzz}
+\def\infoappendixsubsec{\parsearg\appendixsubseczzz}
+\def\infoappendixsubsubsec{\parsearg\appendixsubsubseczzz}
+
+\def\infochapter{\parsearg\chapterzzz}
+\def\infosection{\parsearg\sectionzzz}
+\def\infosubsection{\parsearg\subsectionzzz}
+\def\infosubsubsection{\parsearg\subsubsectionzzz}
+
+% These macros control what the section commands do, according
+% to what kind of chapter we are in (ordinary, appendix, or unnumbered).
+% Define them by default for a numbered chapter.
+\global\let\section = \numberedsec
+\global\let\subsection = \numberedsubsec
+\global\let\subsubsection = \numberedsubsubsec
+
+% Define @majorheading, @heading and @subheading
+
+% NOTE on use of \vbox for chapter headings, section headings, and
+% such:
+%      1) We use \vbox rather than the earlier \line to permit
+%         overlong headings to fold.
+%      2) \hyphenpenalty is set to 10000 because hyphenation in a
+%         heading is obnoxious; this forbids it.
+%       3) Likewise, headings look best if no \parindent is used, and
+%          if justification is not attempted.  Hence \raggedright.
+
+
+\def\majorheading{\parsearg\majorheadingzzz}
+\def\majorheadingzzz #1{%
+{\advance\chapheadingskip by 10pt \chapbreak }%
+{\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000
+                  \parindent=0pt\raggedright
+                  \rm #1\hfill}}\bigskip \par\penalty 200}
+
+\def\chapheading{\parsearg\chapheadingzzz}
+\def\chapheadingzzz #1{\chapbreak %
+{\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000
+                  \parindent=0pt\raggedright
+                  \rm #1\hfill}}\bigskip \par\penalty 200}
+
+\def\heading{\parsearg\secheadingi}
+
+\def\subheading{\parsearg\subsecheadingi}
+
+\def\subsubheading{\parsearg\subsubsecheadingi}
+
+% These macros generate a chapter, section, etc. heading only
+% (including whitespace, linebreaking, etc. around it),
+% given all the information in convenient, parsed form.
+
+%%% Args are the skip and penalty (usually negative)
+\def\dobreak#1#2{\par\ifdim\lastskip<#1\removelastskip\penalty#2\vskip#1\fi}
+
+\def\setchapterstyle #1 {\csname CHAPF#1\endcsname}
+
+%%% Define plain chapter starts, and page on/off switching for it
+% Parameter controlling skip before chapter headings (if needed)
+
+\newskip \chapheadingskip \chapheadingskip = 30pt plus 8pt minus 4pt
+
+\def\chapbreak{\dobreak \chapheadingskip {-4000}}
+\def\chappager{\par\vfill\supereject}
+\def\chapoddpage{\chappager \ifodd\pageno \else \hbox to 0pt{} \chappager\fi}
+
+\def\setchapternewpage #1 {\csname CHAPPAG#1\endcsname}
+
+\def\CHAPPAGoff{
+\global\let\pchapsepmacro=\chapbreak
+\global\let\pagealignmacro=\chappager}
+
+\def\CHAPPAGon{
+\global\let\pchapsepmacro=\chappager
+\global\let\pagealignmacro=\chappager
+\global\def\HEADINGSon{\HEADINGSsingle}}
+
+\def\CHAPPAGodd{
+\global\let\pchapsepmacro=\chapoddpage
+\global\let\pagealignmacro=\chapoddpage
+\global\def\HEADINGSon{\HEADINGSdouble}}
+
+\CHAPPAGon
+
+\def\CHAPFplain{
+\global\let\chapmacro=\chfplain
+\global\let\unnumbchapmacro=\unnchfplain}
+
+\def\chfplain #1#2{%
+  \pchapsepmacro
+  {%
+    \chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000
+                     \parindent=0pt\raggedright
+                     \rm #2\enspace #1}%
+  }%
+  \bigskip
+  \penalty5000
+}
+
+\def\unnchfplain #1{%
+\pchapsepmacro %
+{\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000
+                  \parindent=0pt\raggedright
+                  \rm #1\hfill}}\bigskip \par\penalty 10000 %
+}
+\CHAPFplain % The default
+
+\def\unnchfopen #1{%
+\chapoddpage {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000
+                       \parindent=0pt\raggedright
+                       \rm #1\hfill}}\bigskip \par\penalty 10000 %
+}
+
+\def\chfopen #1#2{\chapoddpage {\chapfonts
+\vbox to 3in{\vfil \hbox to\hsize{\hfil #2} \hbox to\hsize{\hfil #1} \vfil}}%
+\par\penalty 5000 %
+}
+
+\def\CHAPFopen{
+\global\let\chapmacro=\chfopen
+\global\let\unnumbchapmacro=\unnchfopen}
+
+% Parameter controlling skip before section headings.
+
+% was 17pt plus 8pt minus 4pt
+\newskip \subsecheadingskip  \subsecheadingskip = 0pt plus 4pt minus 4pt
+\def\subsecheadingbreak{\dobreak \subsecheadingskip {-500}}
+
+% was 21pt plus 8pt minus 4pt
+\newskip \secheadingskip  \secheadingskip = 0pt plus 4pt minus 4pt
+\def\secheadingbreak{\dobreak \secheadingskip {-1000}}
+
+% @paragraphindent  is defined for the Info formatting commands only.
+\let\paragraphindent=\comment
+
+% Section fonts are the base font at magstep2, which produces
+% a size a bit more than 14 points in the default situation.
+
+\def\secheading #1#2#3{\secheadingi {#2.#3\enspace #1}}
+\def\plainsecheading #1{\secheadingi {#1}}
+\def\secheadingi #1{{\advance \secheadingskip by \parskip %
+\secheadingbreak}%
+{\secfonts \vbox{\hyphenpenalty=10000\tolerance=5000
+                 \parindent=0pt\raggedright
+                 \rm #1\hfill}}%
+\ifdim \parskip<10pt \kern 10pt\kern -\parskip\fi \penalty 10000 }
+
+
+% Subsection fonts are the base font at magstep1,
+% which produces a size of 12 points.
+
+\def\subsecheading #1#2#3#4{\subsecheadingi {#2.#3.#4\enspace #1}}
+\def\subsecheadingi #1{{\advance \subsecheadingskip by \parskip %
+\subsecheadingbreak}%
+{\subsecfonts \vbox{\hyphenpenalty=10000\tolerance=5000
+                     \parindent=0pt\raggedright
+                     \rm #1\hfill}}%
+\ifdim \parskip<10pt \kern 10pt\kern -\parskip\fi \penalty 10000 }
+
+\def\subsubsecfonts{\subsecfonts} % Maybe this should change:
+                                 % Perhaps make sssec fonts scaled
+                                 % magstep half
+\def\subsubsecheading #1#2#3#4#5{\subsubsecheadingi {#2.#3.#4.#5\enspace #1}}
+\def\subsubsecheadingi #1{{\advance \subsecheadingskip by \parskip %
+\subsecheadingbreak}%
+{\subsubsecfonts \vbox{\hyphenpenalty=10000\tolerance=5000
+                       \parindent=0pt\raggedright
+                       \rm #1\hfill}}%
+\ifdim \parskip<10pt \kern 10pt\kern -\parskip\fi \penalty 10000}
+
+
+\message{toc printing,}
+
+% Finish up the main text and prepare to read what we've written
+% to \contentsfile.
+
+\newskip\contentsrightmargin \contentsrightmargin=1in
+\def\startcontents#1{%
+   \pagealignmacro
+   \immediate\closeout \contentsfile
+   \ifnum \pageno>0
+      \pageno = -1             % Request roman numbered pages.
+   \fi
+   % Don't need to put `Contents' or `Short Contents' in the headline.
+   % It is abundantly clear what they are.
+   \unnumbchapmacro{#1}\def\thischapter{}%
+   \begingroup                 % Set up to handle contents files properly.
+      \catcode`\\=0  \catcode`\{=1  \catcode`\}=2  \catcode`\@=11
+      \catcode`\^=7 % to see ^^e4 as \"a etc. juha@piuha.ydi.vtt.fi
+      \raggedbottom             % Worry more about breakpoints than the bottom.
+      \advance\hsize by -\contentsrightmargin % Don't use the full line length.
+}
+
+
+% Normal (long) toc.
+\outer\def\contents{%
+   \startcontents{\putwordTableofContents}%
+      \input \jobname.toc
+   \endgroup
+   \vfill \eject
+}
+
+% And just the chapters.
+\outer\def\summarycontents{%
+   \startcontents{\putwordShortContents}%
+      %
+      \let\chapentry = \shortchapentry
+      \let\unnumbchapentry = \shortunnumberedentry
+      % We want a true roman here for the page numbers.
+      \secfonts
+      \let\rm=\shortcontrm \let\bf=\shortcontbf \let\sl=\shortcontsl
+      \rm
+      \advance\baselineskip by 1pt % Open it up a little.
+      \def\secentry ##1##2##3##4{}
+      \def\unnumbsecentry ##1##2{}
+      \def\subsecentry ##1##2##3##4##5{}
+      \def\unnumbsubsecentry ##1##2{}
+      \def\subsubsecentry ##1##2##3##4##5##6{}
+      \def\unnumbsubsubsecentry ##1##2{}
+      \input \jobname.toc
+   \endgroup
+   \vfill \eject
+}
+\let\shortcontents = \summarycontents
+
+% These macros generate individual entries in the table of contents.
+% The first argument is the chapter or section name.
+% The last argument is the page number.
+% The arguments in between are the chapter number, section number, ...
+
+% Chapter-level things, for both the long and short contents.
+\def\chapentry#1#2#3{\dochapentry{#2\labelspace#1}{#3}}
+
+% See comments in \dochapentry re vbox and related settings
+\def\shortchapentry#1#2#3{%
+  \tocentry{\shortchaplabel{#2}\labelspace #1}{\doshortpageno{#3}}%
+}
+
+% Typeset the label for a chapter or appendix for the short contents.
+% The arg is, e.g. `Appendix A' for an appendix, or `3' for a chapter.
+% We could simplify the code here by writing out an \appendixentry
+% command in the toc file for appendices, instead of using \chapentry
+% for both, but it doesn't seem worth it.
+\setbox0 = \hbox{\shortcontrm \putwordAppendix }
+\newdimen\shortappendixwidth \shortappendixwidth = \wd0
+
+\def\shortchaplabel#1{%
+  % We typeset #1 in a box of constant width, regardless of the text of
+  % #1, so the chapter titles will come out aligned.
+  \setbox0 = \hbox{#1}%
+  \dimen0 = \ifdim\wd0 > \shortappendixwidth \shortappendixwidth \else 0pt \fi
+  %
+  % This space should be plenty, since a single number is .5em, and the
+  % widest letter (M) is 1em, at least in the Computer Modern fonts.
+  % (This space doesn't include the extra space that gets added after
+  % the label; that gets put in in \shortchapentry above.)
+  \advance\dimen0 by 1.1em
+  \hbox to \dimen0{#1\hfil}%
+}
+
+\def\unnumbchapentry#1#2{\dochapentry{#1}{#2}}
+\def\shortunnumberedentry#1#2{\tocentry{#1}{\doshortpageno{#2}}}
+
+% Sections.
+\def\secentry#1#2#3#4{\dosecentry{#2.#3\labelspace#1}{#4}}
+\def\unnumbsecentry#1#2{\dosecentry{#1}{#2}}
+
+% Subsections.
+\def\subsecentry#1#2#3#4#5{\dosubsecentry{#2.#3.#4\labelspace#1}{#5}}
+\def\unnumbsubsecentry#1#2{\dosubsecentry{#1}{#2}}
+
+% And subsubsections.
+\def\subsubsecentry#1#2#3#4#5#6{%
+  \dosubsubsecentry{#2.#3.#4.#5\labelspace#1}{#6}}
+\def\unnumbsubsubsecentry#1#2{\dosubsubsecentry{#1}{#2}}
+
+
+% This parameter controls the indentation of the various levels.
+\newdimen\tocindent \tocindent = 3pc
+
+% Now for the actual typesetting. In all these, #1 is the text and #2 is the
+% page number.
+%
+% If the toc has to be broken over pages, we would want to be at chapters
+% if at all possible; hence the \penalty.
+\def\dochapentry#1#2{%
+   \penalty-300 \vskip\baselineskip
+   \begingroup
+     \chapentryfonts
+     \tocentry{#1}{\dopageno{#2}}%
+   \endgroup
+   \nobreak\vskip .25\baselineskip
+}
+
+\def\dosecentry#1#2{\begingroup
+  \secentryfonts \leftskip=\tocindent
+  \tocentry{#1}{\dopageno{#2}}%
+\endgroup}
+
+\def\dosubsecentry#1#2{\begingroup
+  \subsecentryfonts \leftskip=2\tocindent
+  \tocentry{#1}{\dopageno{#2}}%
+\endgroup}
+
+\def\dosubsubsecentry#1#2{\begingroup
+  \subsubsecentryfonts \leftskip=3\tocindent
+  \tocentry{#1}{\dopageno{#2}}%
+\endgroup}
+
+% Final typesetting of a toc entry; we use the same \entry macro as for
+% the index entries, but we want to suppress hyphenation here.  (We
+% can't do that in the \entry macro, since index entries might consist
+% of hyphenated-identifiers-that-do-not-fit-on-a-line-and-nothing-else.)
+%
+\def\tocentry#1#2{\begingroup
+  \hyphenpenalty = 10000
+  \entry{#1}{#2}%
+\endgroup}
+
+% Space between chapter (or whatever) number and the title.
+\def\labelspace{\hskip1em \relax}
+
+\def\dopageno#1{{\rm #1}}
+\def\doshortpageno#1{{\rm #1}}
+
+\def\chapentryfonts{\secfonts \rm}
+\def\secentryfonts{\textfonts}
+\let\subsecentryfonts = \textfonts
+\let\subsubsecentryfonts = \textfonts
+
+
+\message{environments,}
+
+% Since these characters are used in examples, it should be an even number of
+% \tt widths. Each \tt character is 1en, so two makes it 1em.
+% Furthermore, these definitions must come after we define our fonts.
+\newbox\dblarrowbox    \newbox\longdblarrowbox
+\newbox\pushcharbox    \newbox\bullbox
+\newbox\equivbox       \newbox\errorbox
+
+\let\ptexequiv = \equiv
+
+%{\tentt
+%\global\setbox\dblarrowbox = \hbox to 1em{\hfil$\Rightarrow$\hfil}
+%\global\setbox\longdblarrowbox = \hbox to 1em{\hfil$\mapsto$\hfil}
+%\global\setbox\pushcharbox = \hbox to 1em{\hfil$\dashv$\hfil}
+%\global\setbox\equivbox = \hbox to 1em{\hfil$\ptexequiv$\hfil}
+% Adapted from the manmac format (p.420 of TeXbook)
+%\global\setbox\bullbox = \hbox to 1em{\kern.15em\vrule height .75ex width .85ex
+%                                      depth .1ex\hfil}
+%}
+
+\def\point{$\star$}
+
+\def\result{\leavevmode\raise.15ex\hbox to 1em{\hfil$\Rightarrow$\hfil}}
+\def\expansion{\leavevmode\raise.1ex\hbox to 1em{\hfil$\mapsto$\hfil}}
+\def\print{\leavevmode\lower.1ex\hbox to 1em{\hfil$\dashv$\hfil}}
+
+\def\equiv{\leavevmode\lower.1ex\hbox to 1em{\hfil$\ptexequiv$\hfil}}
+
+% Adapted from the TeXbook's \boxit.
+{\tentt \global\dimen0 = 3em}% Width of the box.
+\dimen2 = .55pt % Thickness of rules
+% The text. (`r' is open on the right, `e' somewhat less so on the left.)
+\setbox0 = \hbox{\kern-.75pt \tensf error\kern-1.5pt}
+
+\global\setbox\errorbox=\hbox to \dimen0{\hfil
+   \hsize = \dimen0 \advance\hsize by -5.8pt % Space to left+right.
+   \advance\hsize by -2\dimen2 % Rules.
+   \vbox{
+      \hrule height\dimen2
+      \hbox{\vrule width\dimen2 \kern3pt          % Space to left of text.
+         \vtop{\kern2.4pt \box0 \kern2.4pt}% Space above/below.
+         \kern3pt\vrule width\dimen2}% Space to right.
+      \hrule height\dimen2}
+    \hfil}
+
+% The @error{} command.
+\def\error{\leavevmode\lower.7ex\copy\errorbox}
+
+% @tex ... @end tex    escapes into raw Tex temporarily.
+% One exception: @ is still an escape character, so that @end tex works.
+% But \@ or @@ will get a plain tex @ character.
+
+\def\tex{\begingroup
+\catcode `\\=0 \catcode `\{=1 \catcode `\}=2
+\catcode `\$=3 \catcode `\&=4 \catcode `\#=6
+\catcode `\^=7 \catcode `\_=8 \catcode `\~=13 \let~=\tie
+\catcode `\%=14
+\catcode 43=12
+\catcode`\"=12
+\catcode`\==12
+\catcode`\|=12
+\catcode`\<=12
+\catcode`\>=12
+\escapechar=`\\
+%
+\let\~=\ptextilde
+\let\{=\ptexlbrace
+\let\}=\ptexrbrace
+\let\.=\ptexdot
+\let\*=\ptexstar
+\let\dots=\ptexdots
+\def\@{@}%
+\let\bullet=\ptexbullet
+\let\b=\ptexb \let\c=\ptexc \let\i=\ptexi \let\t=\ptext \let\l=\ptexl
+\let\L=\ptexL
+%
+\let\Etex=\endgroup}
+
+% Define @lisp ... @endlisp.
+% @lisp does a \begingroup so it can rebind things,
+% including the definition of @endlisp (which normally is erroneous).
+
+% Amount to narrow the margins by for @lisp.
+\newskip\lispnarrowing \lispnarrowing=0.4in
+
+% This is the definition that ^^M gets inside @lisp, @example, and other
+% such environments.  \null is better than a space, since it doesn't
+% have any width.
+\def\lisppar{\null\endgraf}
+
+% Make each space character in the input produce a normal interword
+% space in the output.  Don't allow a line break at this space, as this
+% is used only in environments like @example, where each line of input
+% should produce a line of output anyway.
+%
+{\obeyspaces %
+\gdef\sepspaces{\obeyspaces\let =\tie}}
+
+% Define \obeyedspace to be our active space, whatever it is.  This is
+% for use in \parsearg.
+{\sepspaces%
+\global\let\obeyedspace= }
+
+% This space is always present above and below environments.
+\newskip\envskipamount \envskipamount = 0pt
+
+% Make spacing and below environment symmetrical.  We use \parskip here
+% to help in doing that, since in @example-like environments \parskip
+% is reset to zero; thus the \afterenvbreak inserts no space -- but the
+% start of the next paragraph will insert \parskip
+%
+\def\aboveenvbreak{{\advance\envskipamount by \parskip
+\endgraf \ifdim\lastskip<\envskipamount
+\removelastskip \penalty-50 \vskip\envskipamount \fi}}
+
+\let\afterenvbreak = \aboveenvbreak
+
+% \nonarrowing is a flag.  If "set", @lisp etc don't narrow margins.
+\let\nonarrowing=\relax
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% \cartouche: draw rectangle w/rounded corners around argument
+\font\circle=lcircle10
+\newdimen\circthick
+\newdimen\cartouter\newdimen\cartinner
+\newskip\normbskip\newskip\normpskip\newskip\normlskip
+\circthick=\fontdimen8\circle
+%
+\def\ctl{{\circle\char'013\hskip -6pt}}% 6pt from pl file: 1/2charwidth
+\def\ctr{{\hskip 6pt\circle\char'010}}
+\def\cbl{{\circle\char'012\hskip -6pt}}
+\def\cbr{{\hskip 6pt\circle\char'011}}
+\def\carttop{\hbox to \cartouter{\hskip\lskip
+       \ctl\leaders\hrule height\circthick\hfil\ctr
+       \hskip\rskip}}
+\def\cartbot{\hbox to \cartouter{\hskip\lskip
+       \cbl\leaders\hrule height\circthick\hfil\cbr
+       \hskip\rskip}}
+%
+\newskip\lskip\newskip\rskip
+
+\long\def\cartouche{%
+\begingroup
+       \lskip=\leftskip \rskip=\rightskip
+       \leftskip=0pt\rightskip=0pt %we want these *outside*.
+       \cartinner=\hsize \advance\cartinner by-\lskip
+                         \advance\cartinner by-\rskip
+       \cartouter=\hsize
+       \advance\cartouter by 18pt % allow for 3pt kerns on either
+%                                   side, and for 6pt waste from
+%                                   each corner char
+       \normbskip=\baselineskip \normpskip=\parskip \normlskip=\lineskip
+       % Flag to tell @lisp, etc., not to narrow margin.
+       \let\nonarrowing=\comment
+       \vbox\bgroup
+               \baselineskip=0pt\parskip=0pt\lineskip=0pt
+               \carttop
+               \hbox\bgroup
+                       \hskip\lskip
+                       \vrule\kern3pt
+                       \vbox\bgroup
+                               \hsize=\cartinner
+                               \kern3pt
+                               \begingroup
+                                       \baselineskip=\normbskip
+                                       \lineskip=\normlskip
+                                       \parskip=\normpskip
+                                       \vskip -\parskip
+\def\Ecartouche{%
+                               \endgroup
+                               \kern3pt
+                       \egroup
+                       \kern3pt\vrule
+                       \hskip\rskip
+               \egroup
+               \cartbot
+       \egroup
+\endgroup
+}}
+
+
+% This macro is called at the beginning of all the @example variants,
+% inside a group.
+\def\nonfillstart{%
+  \aboveenvbreak
+  \inENV % This group ends at the end of the body
+  \hfuzz = 12pt % Don't be fussy
+  \sepspaces % Make spaces be word-separators rather than space tokens.
+  \singlespace
+  \let\par = \lisppar % don't ignore blank lines
+  \obeylines % each line of input is a line of output
+  \parskip = 0pt
+  \parindent = 0pt
+  \emergencystretch = 0pt % don't try to avoid overfull boxes
+  % @cartouche defines \nonarrowing to inhibit narrowing
+  % at next level down.
+  \ifx\nonarrowing\relax
+    \advance \leftskip by \lispnarrowing
+    \exdentamount=\lispnarrowing
+    \let\exdent=\nofillexdent
+    \let\nonarrowing=\relax
+  \fi
+}
+
+% To ending an @example-like environment, we first end the paragraph
+% (via \afterenvbreak's vertical glue), and then the group.  That way we
+% keep the zero \parskip that the environments set -- \parskip glue
+% will be inserted at the beginning of the next paragraph in the
+% document, after the environment.
+%
+\def\nonfillfinish{\afterenvbreak\endgroup}%
+
+% This macro is
+\def\lisp{\begingroup
+  \nonfillstart
+  \let\Elisp = \nonfillfinish
+  \tt
+  \rawbackslash % have \ input char produce \ char from current font
+  \gobble
+}
+
+% Define the \E... control sequence only if we are inside the
+% environment, so the error checking in \end will work.
+%
+% We must call \lisp last in the definition, since it reads the
+% return following the @example (or whatever) command.
+%
+\def\example{\begingroup \def\Eexample{\nonfillfinish\endgroup}\lisp}
+\def\smallexample{\begingroup \def\Esmallexample{\nonfillfinish\endgroup}\lisp}
+\def\smalllisp{\begingroup \def\Esmalllisp{\nonfillfinish\endgroup}\lisp}
+
+% @smallexample and @smalllisp.  This is not used unless the @smallbook
+% command is given.  Originally contributed by Pavel@xerox.
+%
+\def\smalllispx{\begingroup
+  \nonfillstart
+  \let\Esmalllisp = \nonfillfinish
+  \let\Esmallexample = \nonfillfinish
+  %
+  % Smaller interline space and fonts for small examples.
+  \setleading{10pt}%
+  \indexfonts \tt
+  \rawbackslash % make \ output the \ character from the current font (tt)
+  \gobble
+}
+
+% This is @display; same as @lisp except use roman font.
+%
+\def\display{\begingroup
+  \nonfillstart
+  \let\Edisplay = \nonfillfinish
+  \gobble
+}
+
+% This is @format; same as @display except don't narrow margins.
+%
+\def\format{\begingroup
+  \let\nonarrowing = t
+  \nonfillstart
+  \let\Eformat = \nonfillfinish
+  \gobble
+}
+
+% @flushleft (same as @format) and @flushright.
+%
+\def\flushleft{\begingroup
+  \let\nonarrowing = t
+  \nonfillstart
+  \let\Eflushleft = \nonfillfinish
+  \gobble
+}
+\def\flushright{\begingroup
+  \let\nonarrowing = t
+  \nonfillstart
+  \let\Eflushright = \nonfillfinish
+  \advance\leftskip by 0pt plus 1fill
+  \gobble}
+
+% @quotation does normal linebreaking (hence we can't use \nonfillstart)
+% and narrows the margins.
+%
+\def\quotation{%
+  \begingroup\inENV %This group ends at the end of the @quotation body
+  {\parskip=0pt \aboveenvbreak}% because \aboveenvbreak inserts \parskip
+  \singlespace
+  \parindent=0pt
+  % We have retained a nonzero parskip for the environment, since we're
+  % doing normal filling. So to avoid extra space below the environment...
+  \def\Equotation{\parskip = 0pt \nonfillfinish}%
+  %
+  % @cartouche defines \nonarrowing to inhibit narrowing at next level down.
+  \ifx\nonarrowing\relax
+    \advance\leftskip by \lispnarrowing
+    \advance\rightskip by \lispnarrowing
+    \exdentamount = \lispnarrowing
+    \let\nonarrowing = \relax
+  \fi
+}
+
+\message{defuns,}
+% Define formatter for defuns
+% First, allow user to change definition object font (\df) internally
+\def\setdeffont #1 {\csname DEF#1\endcsname}
+
+\newskip\defbodyindent \defbodyindent=.4in
+\newskip\defargsindent \defargsindent=50pt
+\newskip\deftypemargin \deftypemargin=12pt
+\newskip\deflastargmargin \deflastargmargin=18pt
+
+\newcount\parencount
+% define \functionparens, which makes ( and ) and & do special things.
+% \functionparens affects the group it is contained in.
+\def\activeparens{%
+\catcode`\(=\active \catcode`\)=\active \catcode`\&=\active
+\catcode`\[=\active \catcode`\]=\active}
+
+% Make control sequences which act like normal parenthesis chars.
+\let\lparen = ( \let\rparen = )
+
+{\activeparens % Now, smart parens don't turn on until &foo (see \amprm)
+
+% Be sure that we always have a definition for `(', etc.  For example,
+% if the fn name has parens in it, \boldbrax will not be in effect yet,
+% so TeX would otherwise complain about undefined control sequence.
+\global\let(=\lparen \global\let)=\rparen
+\global\let[=\lbrack \global\let]=\rbrack
+
+\gdef\functionparens{\boldbrax\let&=\amprm\parencount=0 }
+\gdef\boldbrax{\let(=\opnr\let)=\clnr\let[=\lbrb\let]=\rbrb}
+% This is used to turn on special parens
+% but make & act ordinary (given that it's active).
+\gdef\boldbraxnoamp{\let(=\opnr\let)=\clnr\let[=\lbrb\let]=\rbrb\let&=\ampnr}
+
+% Definitions of (, ) and & used in args for functions.
+% This is the definition of ( outside of all parentheses.
+\gdef\oprm#1 {{\rm\char`\(}#1 \bf \let(=\opnested %
+\global\advance\parencount by 1 }
+%
+% This is the definition of ( when already inside a level of parens.
+\gdef\opnested{\char`\(\global\advance\parencount by 1 }
+%
+\gdef\clrm{% Print a paren in roman if it is taking us back to depth of 0.
+% also in that case restore the outer-level definition of (.
+\ifnum \parencount=1 {\rm \char `\)}\sl \let(=\oprm \else \char `\) \fi
+\global\advance \parencount by -1 }
+% If we encounter &foo, then turn on ()-hacking afterwards
+\gdef\amprm#1 {{\rm\&#1}\let(=\oprm \let)=\clrm\ }
+%
+\gdef\normalparens{\boldbrax\let&=\ampnr}
+} % End of definition inside \activeparens
+%% These parens (in \boldbrax) actually are a little bolder than the
+%% contained text.  This is especially needed for [ and ]
+\def\opnr{{\sf\char`\(}} \def\clnr{{\sf\char`\)}} \def\ampnr{\&}
+\def\lbrb{{\bf\char`\[}} \def\rbrb{{\bf\char`\]}}
+
+% First, defname, which formats the header line itself.
+% #1 should be the function name.
+% #2 should be the type of definition, such as "Function".
+
+\def\defname #1#2{%
+% Get the values of \leftskip and \rightskip as they were
+% outside the @def...
+\dimen2=\leftskip
+\advance\dimen2 by -\defbodyindent
+\dimen3=\rightskip
+\advance\dimen3 by -\defbodyindent
+\noindent        %
+\setbox0=\hbox{\hskip \deflastargmargin{\rm #2}\hskip \deftypemargin}%
+\dimen0=\hsize \advance \dimen0 by -\wd0 % compute size for first line
+\dimen1=\hsize \advance \dimen1 by -\defargsindent %size for continuations
+\parshape 2 0in \dimen0 \defargsindent \dimen1     %
+% Now output arg 2 ("Function" or some such)
+% ending at \deftypemargin from the right margin,
+% but stuck inside a box of width 0 so it does not interfere with linebreaking
+{% Adjust \hsize to exclude the ambient margins,
+% so that \rightline will obey them.
+\advance \hsize by -\dimen2 \advance \hsize by -\dimen3
+\rlap{\rightline{{\rm #2}\hskip \deftypemargin}}}%
+% Make all lines underfull and no complaints:
+\tolerance=10000 \hbadness=10000
+\advance\leftskip by -\defbodyindent
+\exdentamount=\defbodyindent
+{\df #1}\enskip        % Generate function name
+}
+
+% Actually process the body of a definition
+% #1 should be the terminating control sequence, such as \Edefun.
+% #2 should be the "another name" control sequence, such as \defunx.
+% #3 should be the control sequence that actually processes the header,
+%    such as \defunheader.
+
+\def\defparsebody #1#2#3{\begingroup\inENV% Environment for definitionbody
+\medbreak %
+% Define the end token that this defining construct specifies
+% so that it will exit this group.
+\def#1{\endgraf\endgroup\medbreak}%
+\def#2{\begingroup\obeylines\activeparens\spacesplit#3}%
+\parindent=0in
+\advance\leftskip by \defbodyindent \advance \rightskip by \defbodyindent
+\exdentamount=\defbodyindent
+\begingroup %
+\catcode 61=\active % 61 is `='
+\obeylines\activeparens\spacesplit#3}
+
+\def\defmethparsebody #1#2#3#4 {\begingroup\inENV %
+\medbreak %
+% Define the end token that this defining construct specifies
+% so that it will exit this group.
+\def#1{\endgraf\endgroup\medbreak}%
+\def#2##1 {\begingroup\obeylines\activeparens\spacesplit{#3{##1}}}%
+\parindent=0in
+\advance\leftskip by \defbodyindent \advance \rightskip by \defbodyindent
+\exdentamount=\defbodyindent
+\begingroup\obeylines\activeparens\spacesplit{#3{#4}}}
+
+\def\defopparsebody #1#2#3#4#5 {\begingroup\inENV %
+\medbreak %
+% Define the end token that this defining construct specifies
+% so that it will exit this group.
+\def#1{\endgraf\endgroup\medbreak}%
+\def#2##1 ##2 {\def#4{##1}%
+\begingroup\obeylines\activeparens\spacesplit{#3{##2}}}%
+\parindent=0in
+\advance\leftskip by \defbodyindent \advance \rightskip by \defbodyindent
+\exdentamount=\defbodyindent
+\begingroup\obeylines\activeparens\spacesplit{#3{#5}}}
+
+% These parsing functions are similar to the preceding ones
+% except that they do not make parens into active characters.
+% These are used for "variables" since they have no arguments.
+
+\def\defvarparsebody #1#2#3{\begingroup\inENV% Environment for definitionbody
+\medbreak %
+% Define the end token that this defining construct specifies
+% so that it will exit this group.
+\def#1{\endgraf\endgroup\medbreak}%
+\def#2{\begingroup\obeylines\spacesplit#3}%
+\parindent=0in
+\advance\leftskip by \defbodyindent \advance \rightskip by \defbodyindent
+\exdentamount=\defbodyindent
+\begingroup %
+\catcode 61=\active %
+\obeylines\spacesplit#3}
+
+% This is used for \def{tp,vr}parsebody.  It could probably be used for
+% some of the others, too, with some judicious conditionals.
+% 
+\def\parsebodycommon#1#2#3{%
+  \begingroup\inENV %
+  \medbreak %
+  % Define the end token that this defining construct specifies
+  % so that it will exit this group.
+  \def#1{\endgraf\endgroup\medbreak}%
+  \def#2##1 {\begingroup\obeylines\spacesplit{#3{##1}}}%
+  \parindent=0in
+  \advance\leftskip by \defbodyindent \advance \rightskip by \defbodyindent
+  \exdentamount=\defbodyindent
+  \begingroup\obeylines
+}
+
+\def\defvrparsebody#1#2#3#4 {%
+  \parsebodycommon{#1}{#2}{#3}%
+  \spacesplit{#3{#4}}%
+}
+
+% This loses on `@deftp {Data Type} {struct termios}' -- it thinks the
+% type is just `struct', because we lose the braces in `{struct
+% termios}' when \spacesplit reads its undelimited argument.  Sigh.
+% \let\deftpparsebody=\defvrparsebody
+%
+% So, to get around this, we put \empty in with the type name.  That
+% way, TeX won't find exactly `{...}' as an undelimited argument, and
+% won't strip off the braces.
+%
+\def\deftpparsebody #1#2#3#4 {%
+  \parsebodycommon{#1}{#2}{#3}%
+  \spacesplit{\parsetpheaderline{#3{#4}}}\empty
+}
+
+% Fine, but then we have to eventually remove the \empty *and* the
+% braces (if any).  That's what this does, putting the result in \tptemp.
+% 
+\def\removeemptybraces\empty#1\relax{\def\tptemp{#1}}%
+
+% After \spacesplit has done its work, this is called -- #1 is the final
+% thing to call, #2 the type name (which starts with \empty), and #3
+% (which might be empty) the arguments.
+% 
+\def\parsetpheaderline#1#2#3{%
+  \removeemptybraces#2\relax
+  #1{\tptemp}{#3}%
+}%
+
+\def\defopvarparsebody #1#2#3#4#5 {\begingroup\inENV %
+\medbreak %
+% Define the end token that this defining construct specifies
+% so that it will exit this group.
+\def#1{\endgraf\endgroup\medbreak}%
+\def#2##1 ##2 {\def#4{##1}%
+\begingroup\obeylines\spacesplit{#3{##2}}}%
+\parindent=0in
+\advance\leftskip by \defbodyindent \advance \rightskip by \defbodyindent
+\exdentamount=\defbodyindent
+\begingroup\obeylines\spacesplit{#3{#5}}}
+
+% Split up #2 at the first space token.
+% call #1 with two arguments:
+%  the first is all of #2 before the space token,
+%  the second is all of #2 after that space token.
+% If #2 contains no space token, all of it is passed as the first arg
+% and the second is passed as empty.
+
+{\obeylines
+\gdef\spacesplit#1#2^^M{\endgroup\spacesplitfoo{#1}#2 \relax\spacesplitfoo}%
+\long\gdef\spacesplitfoo#1#2 #3#4\spacesplitfoo{%
+\ifx\relax #3%
+#1{#2}{}\else #1{#2}{#3#4}\fi}}
+
+% So much for the things common to all kinds of definitions.
+
+% Define @defun.
+
+% First, define the processing that is wanted for arguments of \defun
+% Use this to expand the args and terminate the paragraph they make up
+
+\def\defunargs #1{\functionparens \sl
+% Expand, preventing hyphenation at `-' chars.
+% Note that groups don't affect changes in \hyphenchar.
+\hyphenchar\tensl=0
+#1%
+\hyphenchar\tensl=45
+\ifnum\parencount=0 \else \errmessage{unbalanced parens in @def arguments}\fi%
+\interlinepenalty=10000
+\advance\rightskip by 0pt plus 1fil
+\endgraf\penalty 10000\vskip -\parskip\penalty 10000%
+}
+
+\def\deftypefunargs #1{%
+% Expand, preventing hyphenation at `-' chars.
+% Note that groups don't affect changes in \hyphenchar.
+% Use \boldbraxnoamp, not \functionparens, so that & is not special.
+\boldbraxnoamp
+\tclose{#1}% avoid \code because of side effects on active chars
+\interlinepenalty=10000
+\advance\rightskip by 0pt plus 1fil
+\endgraf\penalty 10000\vskip -\parskip\penalty 10000%
+}
+
+% Do complete processing of one @defun or @defunx line already parsed.
+
+% @deffn Command forward-char nchars
+
+\def\deffn{\defmethparsebody\Edeffn\deffnx\deffnheader}
+
+\def\deffnheader #1#2#3{\doind {fn}{\code{#2}}%
+\begingroup\defname {#2}{#1}\defunargs{#3}\endgroup %
+\catcode 61=\other % Turn off change made in \defparsebody
+}
+
+% @defun == @deffn Function
+
+\def\defun{\defparsebody\Edefun\defunx\defunheader}
+
+\def\defunheader #1#2{\doind {fn}{\code{#1}}% Make entry in function index
+\begingroup\defname {#1}{Function}%
+\defunargs {#2}\endgroup %
+\catcode 61=\other % Turn off change made in \defparsebody
+}
+
+% @deftypefun int foobar (int @var{foo}, float @var{bar})
+
+\def\deftypefun{\defparsebody\Edeftypefun\deftypefunx\deftypefunheader}
+
+% #1 is the data type.  #2 is the name and args.
+\def\deftypefunheader #1#2{\deftypefunheaderx{#1}#2 \relax}
+% #1 is the data type, #2 the name, #3 the args.
+\def\deftypefunheaderx #1#2 #3\relax{%
+\doind {fn}{\code{#2}}% Make entry in function index
+\begingroup\defname {\defheaderxcond#1\relax$$$#2}{Function}%
+\deftypefunargs {#3}\endgroup %
+\catcode 61=\other % Turn off change made in \defparsebody
+}
+
+% @deftypefn {Library Function} int foobar (int @var{foo}, float @var{bar})
+
+\def\deftypefn{\defmethparsebody\Edeftypefn\deftypefnx\deftypefnheader}
+
+% \defheaderxcond#1\relax$$$
+% puts #1 in @code, followed by a space, but does nothing if #1 is null.
+\def\defheaderxcond#1#2$$${\ifx#1\relax\else\code{#1#2} \fi}
+
+% #1 is the classification.  #2 is the data type.  #3 is the name and args.
+\def\deftypefnheader #1#2#3{\deftypefnheaderx{#1}{#2}#3 \relax}
+% #1 is the classification, #2 the data type, #3 the name, #4 the args.
+\def\deftypefnheaderx #1#2#3 #4\relax{%
+\doind {fn}{\code{#3}}% Make entry in function index
+\begingroup
+\normalparens % notably, turn off `&' magic, which prevents
+%               at least some C++ text from working
+\defname {\defheaderxcond#2\relax$$$#3}{#1}%
+\deftypefunargs {#4}\endgroup %
+\catcode 61=\other % Turn off change made in \defparsebody
+}
+
+% @defmac == @deffn Macro
+
+\def\defmac{\defparsebody\Edefmac\defmacx\defmacheader}
+
+\def\defmacheader #1#2{\doind {fn}{\code{#1}}% Make entry in function index
+\begingroup\defname {#1}{Macro}%
+\defunargs {#2}\endgroup %
+\catcode 61=\other % Turn off change made in \defparsebody
+}
+
+% @defspec == @deffn Special Form
+
+\def\defspec{\defparsebody\Edefspec\defspecx\defspecheader}
+
+\def\defspecheader #1#2{\doind {fn}{\code{#1}}% Make entry in function index
+\begingroup\defname {#1}{Special Form}%
+\defunargs {#2}\endgroup %
+\catcode 61=\other % Turn off change made in \defparsebody
+}
+
+% This definition is run if you use @defunx
+% anywhere other than immediately after a @defun or @defunx.
+
+\def\deffnx #1 {\errmessage{@deffnx in invalid context}}
+\def\defunx #1 {\errmessage{@defunx in invalid context}}
+\def\defmacx #1 {\errmessage{@defmacx in invalid context}}
+\def\defspecx #1 {\errmessage{@defspecx in invalid context}}
+\def\deftypefnx #1 {\errmessage{@deftypefnx in invalid context}}
+\def\deftypeunx #1 {\errmessage{@deftypeunx in invalid context}}
+
+% @defmethod, and so on
+
+% @defop {Funny Method} foo-class frobnicate argument
+
+\def\defop #1 {\def\defoptype{#1}%
+\defopparsebody\Edefop\defopx\defopheader\defoptype}
+
+\def\defopheader #1#2#3{%
+\dosubind {fn}{\code{#2}}{on #1}% Make entry in function index
+\begingroup\defname {#2}{\defoptype{} on #1}%
+\defunargs {#3}\endgroup %
+}
+
+% @defmethod == @defop Method
+
+\def\defmethod{\defmethparsebody\Edefmethod\defmethodx\defmethodheader}
+
+\def\defmethodheader #1#2#3{%
+\dosubind {fn}{\code{#2}}{on #1}% entry in function index
+\begingroup\defname {#2}{Method on #1}%
+\defunargs {#3}\endgroup %
+}
+
+% @defcv {Class Option} foo-class foo-flag
+
+\def\defcv #1 {\def\defcvtype{#1}%
+\defopvarparsebody\Edefcv\defcvx\defcvarheader\defcvtype}
+
+\def\defcvarheader #1#2#3{%
+\dosubind {vr}{\code{#2}}{of #1}% Make entry in var index
+\begingroup\defname {#2}{\defcvtype{} of #1}%
+\defvarargs {#3}\endgroup %
+}
+
+% @defivar == @defcv {Instance Variable}
+
+\def\defivar{\defvrparsebody\Edefivar\defivarx\defivarheader}
+
+\def\defivarheader #1#2#3{%
+\dosubind {vr}{\code{#2}}{of #1}% Make entry in var index
+\begingroup\defname {#2}{Instance Variable of #1}%
+\defvarargs {#3}\endgroup %
+}
+
+% These definitions are run if you use @defmethodx, etc.,
+% anywhere other than immediately after a @defmethod, etc.
+
+\def\defopx #1 {\errmessage{@defopx in invalid context}}
+\def\defmethodx #1 {\errmessage{@defmethodx in invalid context}}
+\def\defcvx #1 {\errmessage{@defcvx in invalid context}}
+\def\defivarx #1 {\errmessage{@defivarx in invalid context}}
+
+% Now @defvar
+
+% First, define the processing that is wanted for arguments of @defvar.
+% This is actually simple: just print them in roman.
+% This must expand the args and terminate the paragraph they make up
+\def\defvarargs #1{\normalparens #1%
+\interlinepenalty=10000
+\endgraf\penalty 10000\vskip -\parskip\penalty 10000}
+
+% @defvr Counter foo-count
+
+\def\defvr{\defvrparsebody\Edefvr\defvrx\defvrheader}
+
+\def\defvrheader #1#2#3{\doind {vr}{\code{#2}}%
+\begingroup\defname {#2}{#1}\defvarargs{#3}\endgroup}
+
+% @defvar == @defvr Variable
+
+\def\defvar{\defvarparsebody\Edefvar\defvarx\defvarheader}
+
+\def\defvarheader #1#2{\doind {vr}{\code{#1}}% Make entry in var index
+\begingroup\defname {#1}{Variable}%
+\defvarargs {#2}\endgroup %
+}
+
+% @defopt == @defvr {User Option}
+
+\def\defopt{\defvarparsebody\Edefopt\defoptx\defoptheader}
+
+\def\defoptheader #1#2{\doind {vr}{\code{#1}}% Make entry in var index
+\begingroup\defname {#1}{User Option}%
+\defvarargs {#2}\endgroup %
+}
+
+% @deftypevar int foobar
+
+\def\deftypevar{\defvarparsebody\Edeftypevar\deftypevarx\deftypevarheader}
+
+% #1 is the data type.  #2 is the name.
+\def\deftypevarheader #1#2{%
+\doind {vr}{\code{#2}}% Make entry in variables index
+\begingroup\defname {\defheaderxcond#1\relax$$$#2}{Variable}%
+\interlinepenalty=10000
+\endgraf\penalty 10000\vskip -\parskip\penalty 10000
+\endgroup}
+
+% @deftypevr {Global Flag} int enable
+
+\def\deftypevr{\defvrparsebody\Edeftypevr\deftypevrx\deftypevrheader}
+
+\def\deftypevrheader #1#2#3{\doind {vr}{\code{#3}}%
+\begingroup\defname {\defheaderxcond#2\relax$$$#3}{#1}
+\interlinepenalty=10000
+\endgraf\penalty 10000\vskip -\parskip\penalty 10000
+\endgroup}
+
+% This definition is run if you use @defvarx
+% anywhere other than immediately after a @defvar or @defvarx.
+
+\def\defvrx #1 {\errmessage{@defvrx in invalid context}}
+\def\defvarx #1 {\errmessage{@defvarx in invalid context}}
+\def\defoptx #1 {\errmessage{@defoptx in invalid context}}
+\def\deftypevarx #1 {\errmessage{@deftypevarx in invalid context}}
+\def\deftypevrx #1 {\errmessage{@deftypevrx in invalid context}}
+
+% Now define @deftp
+% Args are printed in bold, a slight difference from @defvar.
+
+\def\deftpargs #1{\bf \defvarargs{#1}}
+
+% @deftp Class window height width ...
+
+\def\deftp{\deftpparsebody\Edeftp\deftpx\deftpheader}
+
+\def\deftpheader #1#2#3{\doind {tp}{\code{#2}}%
+\begingroup\defname {#2}{#1}\deftpargs{#3}\endgroup}
+
+% This definition is run if you use @deftpx, etc
+% anywhere other than immediately after a @deftp, etc.
+
+\def\deftpx #1 {\errmessage{@deftpx in invalid context}}
+
+\message{cross reference,}
+% Define cross-reference macros
+\newwrite \auxfile
+
+\newif\ifhavexrefs  % True if xref values are known.
+\newif\ifwarnedxrefs  % True if we warned once that they aren't known.
+
+% \setref{foo} defines a cross-reference point named foo.
+
+\def\setref#1{%
+\dosetq{#1-title}{Ytitle}%
+\dosetq{#1-pg}{Ypagenumber}%
+\dosetq{#1-snt}{Ysectionnumberandtype}}
+
+\def\unnumbsetref#1{%
+\dosetq{#1-title}{Ytitle}%
+\dosetq{#1-pg}{Ypagenumber}%
+\dosetq{#1-snt}{Ynothing}}
+
+\def\appendixsetref#1{%
+\dosetq{#1-title}{Ytitle}%
+\dosetq{#1-pg}{Ypagenumber}%
+\dosetq{#1-snt}{Yappendixletterandtype}}
+
+% \xref, \pxref, and \ref generate cross-references to specified points.
+% For \xrefX, #1 is the node name, #2 the name of the Info
+% cross-reference, #3 the printed node name, #4 the name of the Info
+% file, #5 the name of the printed manual.  All but the node name can be
+% omitted.
+%
+\def\pxref#1{\putwordsee{} \xrefX[#1,,,,,,,]}
+\def\xref#1{\putwordSee{} \xrefX[#1,,,,,,,]}
+\def\ref#1{\xrefX[#1,,,,,,,]}
+\def\xrefX[#1,#2,#3,#4,#5,#6]{\begingroup
+  \def\printedmanual{\ignorespaces #5}%
+  \def\printednodename{\ignorespaces #3}%
+  \setbox1=\hbox{\printedmanual}%
+  \setbox0=\hbox{\printednodename}%
+  \ifdim \wd0 = 0pt
+    % No printed node name was explicitly given.
+    \ifx\SETxref-automatic-section-title\relax %
+      % Use the actual chapter/section title appear inside
+      % the square brackets.  Use the real section title if we have it.
+      \ifdim \wd1>0pt%
+        % It is in another manual, so we don't have it.
+        \def\printednodename{\ignorespaces #1}%
+      \else
+        \ifhavexrefs
+          % We know the real title if we have the xref values.
+          \def\printednodename{\refx{#1-title}}%
+        \else
+          % Otherwise just copy the Info node name.
+          \def\printednodename{\ignorespaces #1}%
+        \fi%
+      \fi
+      \def\printednodename{#1-title}%
+    \else
+      % Use the node name inside the square brackets.
+      \def\printednodename{\ignorespaces #1}%
+    \fi
+  \fi
+  %
+  % If we use \unhbox0 and \unhbox1 to print the node names, TeX does not
+  % insert empty discretionaries after hyphens, which means that it will
+  % not find a line break at a hyphen in a node names.  Since some manuals
+  % are best written with fairly long node names, containing hyphens, this
+  % is a loss.  Therefore, we give the text of the node name again, so it
+  % is as if TeX is seeing it for the first time.
+  \ifdim \wd1 > 0pt
+    \putwordsection{} ``\printednodename'' in \cite{\printedmanual}%
+  \else
+    % _ (for example) has to be the character _ for the purposes of the
+    % control sequence corresponding to the node, but it has to expand
+    % into the usual \leavevmode...\vrule stuff for purposes of
+    % printing. So we \turnoffactive for the \refx-snt, back on for the
+    % printing, back off for the \refx-pg.
+    {\turnoffactive \refx{#1-snt}{}}%
+    \space [\printednodename],\space
+    \turnoffactive \putwordpage\tie\refx{#1-pg}{}%
+  \fi
+\endgroup}
+
+% \dosetq is the interface for calls from other macros
+
+% Use \turnoffactive so that punctuation chars such as underscore
+% work in node names.
+\def\dosetq #1#2{{\let\folio=0 \turnoffactive \auxhat%
+\edef\next{\write\auxfile{\internalsetq {#1}{#2}}}%
+\next}}
+
+% \internalsetq {foo}{page} expands into
+% CHARACTERS 'xrdef {foo}{...expansion of \Ypage...}
+% When the aux file is read, ' is the escape character
+
+\def\internalsetq #1#2{'xrdef {#1}{\csname #2\endcsname}}
+
+% Things to be expanded by \internalsetq
+
+\def\Ypagenumber{\folio}
+
+\def\Ytitle{\thissection}
+
+\def\Ynothing{}
+
+\def\Ysectionnumberandtype{%
+\ifnum\secno=0 \putwordChapter\xreftie\the\chapno %
+\else \ifnum \subsecno=0 \putwordSection\xreftie\the\chapno.\the\secno %
+\else \ifnum \subsubsecno=0 %
+\putwordSection\xreftie\the\chapno.\the\secno.\the\subsecno %
+\else %
+\putwordSection\xreftie\the\chapno.\the\secno.\the\subsecno.\the\subsubsecno %
+\fi \fi \fi }
+
+\def\Yappendixletterandtype{%
+\ifnum\secno=0 \putwordAppendix\xreftie'char\the\appendixno{}%
+\else \ifnum \subsecno=0 \putwordSection\xreftie'char\the\appendixno.\the\secno %
+\else \ifnum \subsubsecno=0 %
+\putwordSection\xreftie'char\the\appendixno.\the\secno.\the\subsecno %
+\else %
+\putwordSection\xreftie'char\the\appendixno.\the\secno.\the\subsecno.\the\subsubsecno %
+\fi \fi \fi }
+
+\gdef\xreftie{'tie}
+
+% Use TeX 3.0's \inputlineno to get the line number, for better error
+% messages, but if we're using an old version of TeX, don't do anything.
+%
+\ifx\inputlineno\thisisundefined
+  \let\linenumber = \empty % Non-3.0.
+\else
+  \def\linenumber{\the\inputlineno:\space}
+\fi
+
+% Define \refx{NAME}{SUFFIX} to reference a cross-reference string named NAME.
+% If its value is nonempty, SUFFIX is output afterward.
+
+\def\refx#1#2{%
+  \expandafter\ifx\csname X#1\endcsname\relax
+    % If not defined, say something at least.
+    $\langle$un\-de\-fined$\rangle$%
+    \ifhavexrefs
+      \message{\linenumber Undefined cross reference `#1'.}%
+    \else
+      \ifwarnedxrefs\else
+        \global\warnedxrefstrue
+        \message{Cross reference values unknown; you must run TeX again.}%
+      \fi
+    \fi
+  \else
+    % It's defined, so just use it.
+    \csname X#1\endcsname
+  \fi
+  #2% Output the suffix in any case.
+}
+
+% Read the last existing aux file, if any.  No error if none exists.
+
+% This is the macro invoked by entries in the aux file.
+\def\xrdef #1#2{
+{\catcode`\'=\other\expandafter \gdef \csname X#1\endcsname {#2}}}
+
+\def\readauxfile{%
+\begingroup
+\catcode `\^^@=\other
+\catcode `\\ 1=\other
+\catcode `\\ 2=\other
+\catcode `\^^C=\other
+\catcode `\^^D=\other
+\catcode `\^^E=\other
+\catcode `\^^F=\other
+\catcode `\^^G=\other
+\catcode `\^^H=\other
+\catcode `\\v=\other
+\catcode `\^^L=\other
+\catcode `\\ e=\other
+\catcode `\\ f=\other
+\catcode `\\10=\other
+\catcode `\\11=\other
+\catcode `\\12=\other
+\catcode `\\13=\other
+\catcode `\\14=\other
+\catcode `\\15=\other
+\catcode `\\16=\other
+\catcode `\\17=\other
+\catcode `\\18=\other
+\catcode `\\19=\other
+\catcode 26=\other
+\catcode `\^^[=\other
+\catcode `\^^\=\other
+\catcode `\^^]=\other
+\catcode `\^^^=\other
+\catcode `\^^_=\other
+\catcode `\@=\other
+\catcode `\^=\other
+\catcode `\~=\other
+\catcode `\[=\other
+\catcode `\]=\other
+\catcode`\"=\other
+\catcode`\_=\other
+\catcode`\|=\other
+\catcode`\<=\other
+\catcode`\>=\other
+\catcode `\$=\other
+\catcode `\#=\other
+\catcode `\&=\other
+% `\+ does not work, so use 43.
+\catcode 43=\other
+% Make the characters 128-255 be printing characters
+{%
+  \count 1=128
+  \def\loop{%
+    \catcode\count 1=\other
+    \advance\count 1 by 1
+    \ifnum \count 1<256 \loop \fi
+  }%
+}%
+% the aux file uses ' as the escape.
+% Turn off \ as an escape so we do not lose on
+% entries which were dumped with control sequences in their names.
+% For example, 'xrdef {$\leq $-fun}{page ...} made by @defun ^^
+% Reference to such entries still does not work the way one would wish,
+% but at least they do not bomb out when the aux file is read in.
+\catcode `\{=1 \catcode `\}=2
+\catcode `\%=\other
+\catcode `\'=0
+\catcode`\^=7 % to make ^^e4 etc usable in xref tags 
+\catcode `\\=\other
+\openin 1 \jobname.aux
+\ifeof 1 \else \closein 1 \input \jobname.aux \global\havexrefstrue
+\global\warnedobstrue
+\fi
+% Open the new aux file.  Tex will close it automatically at exit.
+\openout \auxfile=\jobname.aux
+\endgroup}
+
+
+% Footnotes.
+
+\newcount \footnoteno
+
+% The trailing space in the following definition for supereject is
+% vital for proper filling; pages come out unaligned when you do a
+% pagealignmacro call if that space before the closing brace is
+% removed.
+\def\supereject{\par\penalty -20000\footnoteno =0 }
+
+% @footnotestyle is meaningful for info output only..
+\let\footnotestyle=\comment
+
+\let\ptexfootnote=\footnote
+
+{\catcode `\@=11
+%
+% Auto-number footnotes.  Otherwise like plain.
+\gdef\footnote{%
+  \global\advance\footnoteno by \@ne
+  \edef\thisfootno{$^{\the\footnoteno}$}%
+  %
+  % In case the footnote comes at the end of a sentence, preserve the
+  % extra spacing after we do the footnote number.
+  \let\@sf\empty
+  \ifhmode\edef\@sf{\spacefactor\the\spacefactor}\/\fi
+  %
+  % Remove inadvertent blank space before typesetting the footnote number.
+  \unskip
+  \thisfootno\@sf
+  \footnotezzz
+}%
+
+% Don't bother with the trickery in plain.tex to not require the
+% footnote text as a parameter.  Our footnotes don't need to be so general.
+%
+\long\gdef\footnotezzz#1{\insert\footins{%
+  % We want to typeset this text as a normal paragraph, even if the
+  % footnote reference occurs in (for example) a display environment.
+  % So reset some parameters.
+  \interlinepenalty\interfootnotelinepenalty
+  \splittopskip\ht\strutbox % top baseline for broken footnotes
+  \splitmaxdepth\dp\strutbox
+  \floatingpenalty\@MM
+  \leftskip\z@skip
+  \rightskip\z@skip
+  \spaceskip\z@skip
+  \xspaceskip\z@skip
+  \parindent\defaultparindent
+  %
+  % Hang the footnote text off the number.
+  \hang
+  \textindent{\thisfootno}%
+  %
+  % Don't crash into the line above the footnote text.  Since this
+  % expands into a box, it must come within the paragraph, lest it
+  % provide a place where TeX can split the footnote.
+  \footstrut
+  #1\strut}%
+}
+
+}%end \catcode `\@=11
+
+% Set the baselineskip to #1, and the lineskip and strut size
+% correspondingly.  There is no deep meaning behind these magic numbers
+% used as factors; they just match (closely enough) what Knuth defined.
+%
+\def\lineskipfactor{.08333}
+\def\strutheightpercent{.70833}
+\def\strutdepthpercent {.29167}
+%
+\def\setleading#1{%
+  \normalbaselineskip = #1\relax
+  \normallineskip = \lineskipfactor\normalbaselineskip
+  \normalbaselines
+  \setbox\strutbox =\hbox{%
+    \vrule width0pt height\strutheightpercent\baselineskip
+                    depth \strutdepthpercent \baselineskip
+  }%
+}
+
+% @| inserts a changebar to the left of the current line.  It should
+% surround any changed text.  This approach does *not* work if the
+% change spans more than two lines of output.  To handle that, we would
+% have adopt a much more difficult approach (putting marks into the main
+% vertical list for the beginning and end of each change).
+%
+\def\|{%
+  % \vadjust can only be used in horizontal mode.
+  \leavevmode
+  %
+  % Append this vertical mode material after the current line in the output.
+  \vadjust{%
+    % We want to insert a rule with the height and depth of the current
+    % leading; that is exactly what \strutbox is supposed to record.
+    \vskip-\baselineskip
+    %
+    % \vadjust-items are inserted at the left edge of the type.  So
+    % the \llap here moves out into the left-hand margin.
+    \llap{%
+      %
+      % For a thicker or thinner bar, change the `1pt'.
+      \vrule height\baselineskip width1pt
+      %
+      % This is the space between the bar and the text.
+      \hskip 12pt
+    }%
+  }%
+}
+
+% For a final copy, take out the rectangles
+% that mark overfull boxes (in case you have decided
+% that the text looks ok even though it passes the margin).
+%
+\def\finalout{\overfullrule=0pt}
+
+
+% End of control word definitions.
+
+\message{and turning on texinfo input format.}
+
+\def\openindices{%
+   \newindex{cp}%
+   \newcodeindex{fn}%
+   \newcodeindex{vr}%
+   \newcodeindex{tp}%
+   \newcodeindex{ky}%
+   \newcodeindex{pg}%
+}
+
+% Set some numeric style parameters, for 8.5 x 11 format.
+
+%\hsize = 6.5in
+\newdimen\defaultparindent \defaultparindent = 15pt
+\parindent = \defaultparindent
+\parskip 18pt plus 1pt
+\setleading{15pt}
+\advance\topskip by 1.2cm
+
+% Prevent underfull vbox error messages.
+\vbadness=10000
+
+% Following George Bush, just get rid of widows and orphans.
+\widowpenalty=10000
+\clubpenalty=10000
+
+% Use TeX 3.0's \emergencystretch to help line breaking, but if we're
+% using an old version of TeX, don't do anything.  We want the amount of
+% stretch added to depend on the line length, hence the dependence on
+% \hsize.  This makes it come to about 9pt for the 8.5x11 format.
+%
+\ifx\emergencystretch\thisisundefined
+  % Allow us to assign to \emergencystretch anyway.
+  \def\emergencystretch{\dimen0}%
+\else
+  \emergencystretch = \hsize
+  \divide\emergencystretch by 45
+\fi
+
+% Use @smallbook to reset parameters for 7x9.5 format  (or else 7x9.25)
+\def\smallbook{
+
+% These values for secheadingskip and subsecheadingskip are
+% experiments.  RJC 7 Aug 1992
+% was 17pt and 14pt plus 6pt minus 3pt
+\global\secheadingskip = 0pt plus 4pt minus 4pt
+\global\subsecheadingskip = 0pt plus 4pt minus 4pt
+
+\global\lispnarrowing = 0.3in
+\setleading{12pt}
+\advance\topskip by -1cm
+\global\parskip 3pt plus 1pt
+\global\hsize = 5in
+\global\vsize=7.5in
+\global\tolerance=700
+\global\hfuzz=1pt
+\global\contentsrightmargin=0pt
+\global\deftypemargin=0pt
+\global\defbodyindent=.5cm
+
+\global\pagewidth=\hsize
+\global\pageheight=\vsize
+
+\global\let\smalllisp=\smalllispx
+\global\let\smallexample=\smalllispx
+\global\def\Esmallexample{\Esmalllisp}
+}
+
+% Use @afourpaper to print on European A4 paper.
+\def\afourpaper{
+\global\tolerance=700
+\global\hfuzz=1pt
+\setleading{12pt}
+\global\parskip 15pt plus 1pt
+
+\global\vsize= 53\baselineskip
+\advance\vsize by \topskip
+%\global\hsize=   5.85in     % A4 wide 10pt
+\global\hsize=  6.5in
+\global\outerhsize=\hsize
+\global\advance\outerhsize by 0.5in
+\global\outervsize=\vsize
+\global\advance\outervsize by 0.6in
+
+\global\pagewidth=\hsize
+\global\pageheight=\vsize
+}
+
+% Allow control of the text dimensions.  Parameters in order: textheight;
+% textwidth; \voffset; \hoffset (!); binding offset.  All require a dimension;
+% header is additional; added length extends the bottom of the page.
+
+\def\changepagesizes#1#2#3#4#5{
+ \global\vsize= #1
+ \advance\vsize by \topskip
+ \global\voffset= #3
+ \global\hsize= #2
+ \global\outerhsize=\hsize
+ \global\advance\outerhsize by 0.5in
+ \global\outervsize=\vsize
+ \global\advance\outervsize by 0.6in
+ \global\pagewidth=\hsize
+ \global\pageheight=\vsize
+ \global\normaloffset= #4
+ \global\bindingoffset= #5}
+
+% This layout is compatible with Latex on A4 paper.
+
+\def\afourlatex{\changepagesizes{22cm}{15cm}{7mm}{4.6mm}{5mm}}
+
+% Use @afourwide to print on European A4 paper in wide format.
+\def\afourwide{\afourpaper
+\changepagesizes{9.5in}{6.5in}{\hoffset}{\normaloffset}{\bindingoffset}}
+
+% Define macros to output various characters with catcode for normal text.
+\catcode`\"=\other
+\catcode`\~=\other
+\catcode`\^=\other
+\catcode`\_=\other
+\catcode`\|=\other
+\catcode`\<=\other
+\catcode`\>=\other
+\catcode`\+=\other
+\def\normaldoublequote{"}
+\def\normaltilde{~}
+\def\normalcaret{^}
+\def\normalunderscore{_}
+\def\normalverticalbar{|}
+\def\normalless{<}
+\def\normalgreater{>}
+\def\normalplus{+}
+
+% This macro is used to make a character print one way in ttfont
+% where it can probably just be output, and another way in other fonts,
+% where something hairier probably needs to be done.
+%
+% #1 is what to print if we are indeed using \tt; #2 is what to print
+% otherwise.  Since all the Computer Modern typewriter fonts have zero
+% interword stretch (and shrink), and it is reasonable to expect all
+% typewriter fonts to have this, we can check that font parameter.
+%
+\def\ifusingtt#1#2{\ifdim \fontdimen3\the\font=0pt #1\else #2\fi}
+
+% Turn off all special characters except @
+% (and those which the user can use as if they were ordinary).
+% Most of these we simply print from the \tt font, but for some, we can
+% use math or other variants that look better in normal text.
+
+\catcode`\"=\active
+\def\activedoublequote{{\tt \char '042}}
+\let"=\activedoublequote
+\catcode`\~=\active
+\def~{{\tt \char '176}}
+\chardef\hat=`\^
+\catcode`\^=\active
+\def\auxhat{\def^{'hat}}
+\def^{{\tt \hat}}
+
+\catcode`\_=\active
+\def_{\ifusingtt\normalunderscore\_}
+% Subroutine for the previous macro.
+\def\_{\lvvmode \kern.06em \vbox{\hrule width.3em height.1ex}}
+
+% \lvvmode is equivalent in function to \leavevmode.
+% Using \leavevmode runs into trouble when written out to
+% an index file due to the expansion of \leavevmode into ``\unhbox
+% \voidb@x'' ---which looks to TeX like ``\unhbox \voidb\x'' due to our
+% magic tricks with @.
+\def\lvvmode{\vbox to 0pt{}}
+
+\catcode`\|=\active
+\def|{{\tt \char '174}}
+\chardef \less=`\<
+\catcode`\<=\active
+\def<{{\tt \less}}
+\chardef \gtr=`\>
+\catcode`\>=\active
+\def>{{\tt \gtr}}
+\catcode`\+=\active
+\def+{{\tt \char 43}}
+%\catcode 27=\active
+%\def^^[{$\diamondsuit$}
+
+% Set up an active definition for =, but don't enable it most of the time.
+{\catcode`\==\active
+\global\def={{\tt \char 61}}}
+
+\catcode`+=\active
+\catcode`\_=\active
+
+% If a .fmt file is being used, characters that might appear in a file
+% name cannot be active until we have parsed the command line.
+% So turn them off again, and have \everyjob (or @setfilename) turn them on.
+% \otherifyactive is called near the end of this file.
+\def\otherifyactive{\catcode`+=\other \catcode`\_=\other}
+
+\catcode`\@=0
+
+% \rawbackslashxx output one backslash character in current font
+\global\chardef\rawbackslashxx=`\\
+%{\catcode`\\=\other
+%@gdef@rawbackslashxx{\}}
+
+% \rawbackslash redefines \ as input to do \rawbackslashxx.
+{\catcode`\\=\active
+@gdef@rawbackslash{@let\=@rawbackslashxx }}
+
+% \normalbackslash outputs one backslash in fixed width font.
+\def\normalbackslash{{\tt\rawbackslashxx}}
+
+% Say @foo, not \foo, in error messages.
+\escapechar=`\@
+
+% \catcode 17=0   % Define control-q
+\catcode`\\=\active
+
+% Used sometimes to turn off (effectively) the active characters
+% even after parsing them.
+@def@turnoffactive{@let"=@normaldoublequote
+@let\=@realbackslash
+@let~=@normaltilde
+@let^=@normalcaret
+@let_=@normalunderscore
+@let|=@normalverticalbar
+@let<=@normalless
+@let>=@normalgreater
+@let+=@normalplus}
+
+@def@normalturnoffactive{@let"=@normaldoublequote
+@let\=@normalbackslash
+@let~=@normaltilde
+@let^=@normalcaret
+@let_=@normalunderscore
+@let|=@normalverticalbar
+@let<=@normalless
+@let>=@normalgreater
+@let+=@normalplus}
+
+% Make _ and + \other characters, temporarily.
+% This is canceled by @fixbackslash.
+@otherifyactive
+
+% If a .fmt file is being used, we don't want the `\input texinfo' to show up.
+% That is what \eatinput is for; after that, the `\' should revert to printing
+% a backslash.
+%
+@gdef@eatinput input texinfo{@fixbackslash}
+@global@let\ = @eatinput
+
+% On the other hand, perhaps the file did not have a `\input texinfo'. Then
+% the first `\{ in the file would cause an error. This macro tries to fix
+% that, assuming it is called before the first `\' could plausibly occur.
+% Also back turn on active characters that might appear in the input
+% file name, in case not using a pre-dumped format.
+%
+@gdef@fixbackslash{@ifx\@eatinput @let\ = @normalbackslash @fi
+  @catcode`+=@active @catcode`@_=@active}
+
+%% These look ok in all fonts, so just make them not special.  The @rm below
+%% makes sure that the current font starts out as the newly loaded cmr10
+@catcode`@$=@other @catcode`@%=@other @catcode`@&=@other @catcode`@#=@other
+
+@textfonts
+@rm
+
+@c Local variables:
+@c page-delimiter: "^\\\\message"
+@c End:
diff --git a/examples/ChangeLog b/examples/ChangeLog
new file mode 100644 (file)
index 0000000..c41d21b
--- /dev/null
@@ -0,0 +1,15 @@
+Sun Aug  9 11:16:13 1998  Ben Pfaff  <blp@gnu.org>
+
+       * descriptives.stat: Renamed descript.stat.
+
+Sat Aug  8 00:28:24 1998  Ben Pfaff  <blp@gnu.org>
+
+       * New directory.
+
+       * descriptives.stat: New file.
+
+----------------------------------------------------------------------
+Local Variables:
+mode: change-log
+version-control: never
+End:
diff --git a/examples/descript.stat b/examples/descript.stat
new file mode 100644 (file)
index 0000000..2f2ad56
--- /dev/null
@@ -0,0 +1,29 @@
+title 'Demonstrate DESCRIPTIVES procedure'.
+
+remark EOF
+                        Sample syntax file for PSPP.
+                (This comment will appear in the output.)
+EOF
+
+/*      run this syntax file with the command:
+/*                 pspp example.stat
+/*
+/*      Output is written to the file "pspp.list".
+/*
+/*      (This comment will not appear in the output.)
+
+data list / v0 to v2 1-9.
+begin data.
+ 12 12 89
+ 34 12 80
+ 56 12 77
+ 78 12 73
+ 90 91
+ 37 97 85
+ 52    82
+ 12    79
+ 26 78 76
+ 29 13 71
+end data.
+
+descript all/stat=all/format=serial.
diff --git a/intl/ChangeLog b/intl/ChangeLog
new file mode 100644 (file)
index 0000000..743b3f5
--- /dev/null
@@ -0,0 +1,1026 @@
+Thu Oct  9 13:41:22 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.in: (INCLUDES) Add -I$(top_srcdir)/src.
+
+1997-09-06 02:10  Ulrich Drepper  <drepper@cygnus.com>
+
+       * intlh.inst.in: Reformat copyright.
+
+1997-08-19 15:22  Ulrich Drepper  <drepper@cygnus.com>
+
+       * dcgettext.c (DCGETTEXT): Remove wrong comment.
+
+1997-08-16 00:13  Ulrich Drepper  <drepper@cygnus.com>
+
+       * Makefile.in (install-data): Don't change directory to install.
+
+1997-08-01 14:30  Ulrich Drepper  <drepper@cygnus.com>
+
+       * cat-compat.c: Fix copyright.
+
+       * localealias.c: Don't define strchr unless !HAVE_STRCHR.
+
+       * loadmsgcat.c: Update copyright.  Fix typos.
+
+       * l10nflist.c: Don't define strchr unless !HAVE_STRCHR.
+       (_nl_make_l10nflist): Handle sponsor and revision correctly.
+
+       * gettext.c: Update copyright.
+       * gettext.h: Likewise.
+       * hash-string.h: Likewise.
+
+       * finddomain.c: Remoave dead code.  Define strchr only if
+       !HAVE_STRCHR.
+
+       * explodename.c: Include <sys/types.h>.
+
+       * explodename.c: Reformat copyright text.
+       (_nl_explode_name): Fix typo.
+
+       * dcgettext.c: Define and use __set_errno.
+       (guess_category_value): Don't use setlocale if HAVE_LC_MESSAGES is
+       not defined.
+
+       * bindtextdom.c: Pretty printing.
+
+1997-05-01 02:25  Ulrich Drepper  <drepper@cygnus.com>
+
+       * dcgettext.c (guess_category_value): Don't depend on
+       HAVE_LC_MESSAGES.  We don't need the macro here.
+       Patch by Bruno Haible <haible@ilog.fr>.
+
+       * cat-compat.c (textdomain): DoN't refer to HAVE_SETLOCALE_NULL
+       macro.  Instead use HAVE_LOCALE_NULL and define it when using
+       glibc, as in dcgettext.c.
+       Patch by Bruno Haible <haible@ilog.fr>.
+
+       * Makefile.in (CPPFLAGS): New variable.  Reported by Franc,ois
+       Pinard.
+
+Mon Mar 10 06:51:17 1997  Ulrich Drepper  <drepper@cygnus.com>
+
+       * Makefile.in: Implement handling of libtool.
+
+       * gettextP.h: Change data structures for use of generic lowlevel
+       i18n file handling.
+
+Wed Dec  4 20:21:18 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * textdomain.c: Put parentheses around arguments of memcpy macro
+       definition.
+       * localealias.c: Likewise.
+       * l10nflist.c: Likewise.
+       * finddomain.c: Likewise.
+       * bindtextdom.c: Likewise.
+       Reported by Thomas Esken.
+
+Mon Nov 25 22:57:51 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * textdomain.c: Move definition of `memcpy` macro to right
+       position.
+
+Fri Nov 22 04:01:58 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * finddomain.c [!HAVE_STRING_H && !_LIBC]: Define memcpy using
+       bcopy if not already defined.  Reported by Thomas Esken.
+       * bindtextdom.c: Likewise.
+       * l10nflist.c: Likewise.
+       * localealias.c: Likewise.
+       * textdomain.c: Likewise.
+
+Tue Oct 29 11:10:27 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * Makefile.in (libdir): Change to use exec_prefix instead of
+       prefix.  Reported by Knut-HÃ¥vardAksnes <etokna@eto.ericsson.se>.
+
+Sat Aug 31 03:07:09 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * l10nflist.c (_nl_normalize_codeset): We convert to lower case,
+       so don't prepend uppercase `ISO' for only numeric arg.
+
+Fri Jul 19 00:15:46 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * l10nflist.c: Move inclusion of argz.h, ctype.h, stdlib.h after
+       definition of _GNU_SOURCE.  Patch by Roland McGrath.
+
+       * Makefile.in (uninstall): Fix another bug with `for' loop and
+       empty arguments.  Patch by Jim Meyering.  Correct name os
+       uninstalled files: no intl- prefix anymore.
+
+       * Makefile.in (install-data): Again work around shells which
+       cannot handle mpty for list.  Reported by Jim Meyering.
+
+Sat Jul 13 18:11:35 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * Makefile.in (install): Split goal.  Now depend on install-exec
+        and install-data.
+       (install-exec, install-data): New goals.  Created from former
+       install goal.
+       Reported by Karl Berry.
+
+Sat Jun 22 04:58:14 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * Makefile.in (MKINSTALLDIRS): New variable.  Path to
+        mkinstalldirs script.
+       (install): use MKINSTALLDIRS variable or if the script is not present
+       try to find it in the $top_scrdir).
+
+Wed Jun 19 02:56:56 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * l10nflist.c: Linux libc *partly* includes the argz_* functions.
+       Grr.  Work around by renaming the static version and use macros
+       for renaming.
+
+Tue Jun 18 20:11:17 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * l10nflist.c: Correct presence test macros of __argz_* functions.
+
+       * l10nflist.c: Include <argz.h> based on test of it instead when
+       __argz_* functions are available.
+       Reported by Andreas Schwab.
+
+Thu Jun 13 15:17:44 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * explodename.c, l10nflist.c: Define NULL for dumb systems.
+
+Tue Jun 11 17:05:13 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * intlh.inst.in, libgettext.h (dcgettext): Rename local variable
+       result to __result to prevent name clash.
+
+       * l10nflist.c, localealias.c, dcgettext.c: Define _GNU_SOURCE to
+        get prototype for stpcpy and strcasecmp.
+
+       * intlh.inst.in, libgettext.h: Move declaration of
+       `_nl_msg_cat_cntr' outside __extension__ block to prevent warning
+       from gcc's -Wnested-extern option.
+
+Fri Jun  7 01:58:00 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * Makefile.in (install): Remove comment.
+
+Thu Jun  6 17:28:17 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * Makefile.in (install): Work around for another Buglix stupidity.
+       Always use an `else' close for `if's.  Reported by Nelson Beebe.
+
+       * Makefile.in (intlh.inst): Correct typo in phony rule.
+       Reported by Nelson Beebe.
+
+Thu Jun  6 01:49:52 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * dcgettext.c (read_alias_file): Rename variable alloca_list to
+       block_list as the macro calls assume.
+       Patch by Eric Backus.
+
+       * localealias.c [!HAVE_ALLOCA]: Define alloca as macro using
+        malloc.
+       (read_alias_file): Rename varriabe alloca_list to block_list as the
+       macro calls assume.
+       Patch by Eric Backus.
+
+       * l10nflist.c: Correct conditional for <argz.h> inclusion.
+       Reported by Roland McGrath.
+
+       * Makefile.in (all): Depend on all-@USE_INCLUDED_LIBINTL@, not
+        all-@USE_NLS@.
+
+       * Makefile.in (install): intlh.inst comes from local dir, not
+        $(srcdir).
+
+       * Makefile.in (intlh.inst): Special handling of this goal.  If
+       used in gettext, this is really a rul to construct this file.  If
+       used in any other package it is defined as a .PHONY rule with
+       empty body.
+
+       * finddomain.c: Extract locale file information handling into
+       l10nfile.c.  Rename local stpcpy__ function to stpcpy.
+
+       * dcgettext.c (stpcpy): Add local definition.
+
+       * l10nflist.c: Solve some portability problems.  Patches partly by
+       Thomas Esken.  Add local definition of stpcpy.
+
+Tue Jun  4 02:47:49 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * intlh.inst.in: Don't depend including <locale.h> on
+       HAVE_LOCALE_H.  Instead configure must rewrite this fiile
+       depending on the result of the configure run.
+
+       * Makefile.in (install): libintl.inst is now called intlh.inst.
+       Add rules for updating intlh.inst from intlh.inst.in.
+
+       * libintl.inst: Renamed to intlh.inst.in.
+
+       * localealias.c, dcgettext.c [__GNUC__]: Define HAVE_ALLOCA to 1
+        because gcc has __buitlin_alloca.
+       Reported by Roland McGrath.
+
+Mon Jun  3 00:32:16 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * Makefile.in (installcheck): New goal to fulfill needs of
+        automake's distcheck.
+
+       * Makefile.in (install): Reorder commands so that VERSION is
+        found.
+
+       * Makefile.in (gettextsrcdir): Now use subdirectory intl/ in
+        @datadir@/gettext.
+       (COMSRCS): Add l10nfile.c.
+       (OBJECTS): Add l10nfile.o.
+       (DISTFILES): Rename to DISTFILE.normal.  Remove $(DISTFILES.common).
+       (DISTFILE.gettext): Remove $(DISTFILES.common).
+       (all-gettext): Remove goal.
+       (install): If $(PACKAGE) = gettext install, otherwose do nothing.  No
+       package but gettext itself should install libintl.h + headers.
+       (dist): Extend goal to work for gettext, too.
+       (dist-gettext): Remove goal.
+
+       * dcgettext.c [!HAVE_ALLOCA]: Define macro alloca by using malloc.
+
+Sun Jun  2 17:33:06 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * loadmsgcat.c (_nl_load_domain): Parameter is now comes from
+        find_l10nfile.
+
+Sat Jun  1 02:23:03 1996  Ulrich Drepper  <drepper@cygnus.com>
+
+       * l10nflist.c (__argz_next): Add definition.
+
+       * dcgettext.c [!HAVE_ALLOCA]: Add code for handling missing alloca
+       code.  Use new l10nfile handling.
+
+       * localealias.c [!HAVE_ALLOCA]: Add code for handling missing
+        alloca code.
+
+       * l10nflist.c: Initial revision.
+
+Tue Apr  2 18:51:18 1996  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (all-gettext): New goal.  Same as all-yes.
+
+Thu Mar 28 23:01:22 1996  Karl Eichwalder  <ke@ke.central.de>
+
+       * Makefile.in (gettextsrcdir): Define using @datadir@.
+
+Tue Mar 26 12:39:14 1996  Ulrich Drepper  <drepper@myware>
+
+       * finddomain.c: Include <ctype.h>.  Reported by Roland McGrath.
+
+Sat Mar 23 02:00:35 1996  Ulrich Drepper  <drepper@myware>
+
+       * finddomain.c (stpcpy): Rename to stpcpy__ to prevent clashing
+        with external declaration.
+
+Sat Mar  2 00:47:09 1996  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (all-no): Rename from all_no.
+
+Sat Feb 17 00:25:59 1996  Ulrich Drepper  <drepper@myware>
+
+       * gettextP.h [loaded_domain]: Array `successor' must now contain up
+        to 63 elements (because of codeset name normalization).
+
+       * finddomain.c: Implement codeset name normalization.
+
+Thu Feb 15 04:39:09 1996  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (all): Define to `all-@USE_NLS@'.
+       (all-yes, all_no): New goals.  `all-no' is noop, `all-yes'
+       is former all.
+
+Mon Jan 15 21:46:01 1996  Howard Gayle  <howard@hal.com>
+
+       * localealias.c (alias_compare): Increment string pointers in loop
+        of strcasecmp replacement.
+
+Fri Dec 29 21:16:34 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (install-src): Who commented this goal out ? :-)
+
+Fri Dec 29 15:08:16 1995  Ulrich Drepper  <drepper@myware>
+
+       * dcgettext.c (DCGETTEXT): Save `errno'.  Failing system calls
+       should not effect it because a missing catalog is no error.
+       Reported by Harald K<o:>nig <koenig@tat.physik.uni-tuebingen.de>.
+
+Tue Dec 19 22:09:13 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (Makefile): Explicitly use $(SHELL) for running
+        shell scripts.
+
+Fri Dec 15 17:34:59 1995  Andreas Schwab  <schwab@issan.informatik.uni-dortmund.de>
+
+       * Makefile.in (install-src): Only install library and header when
+       we use the own implementation.  Don't do it when using the
+       system's gettext or catgets functions.
+
+       * dcgettext.c (find_msg): Must not swap domain->hash_size here.
+
+Sat Dec  9 16:24:37 1995  Ulrich Drepper  <drepper@myware>
+
+       * localealias.c, libintl.inst, libgettext.h, hash-string.h,
+       gettextP.h, finddomain.c, dcgettext.c, cat-compat.c:
+       Use PARAMS instead of __P.  Suggested by Roland McGrath.
+
+Tue Dec  5 11:39:14 1995  Larry Schwimmer  <rosebud@cyclone.stanford.edu>
+
+       * libgettext.h: Use `#if !defined (_LIBINTL_H)' instead of `#if
+       !_LIBINTL_H' because Solaris defines _LIBINTL_H as empty.
+
+Mon Dec  4 15:42:07 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (install-src):
+       Install libintl.inst instead of libintl.h.install.
+
+Sat Dec  2 22:51:38 1995  Marcus Daniels  <marcus@sysc.pdx.edu>
+
+       * cat-compat.c (textdomain):
+       Reverse order in which files are tried you load.  First
+       try local file, when this failed absolute path.
+
+Wed Nov 29 02:03:53 1995  Nelson H. F. Beebe  <beebe@math.utah.edu>
+
+       * cat-compat.c (bindtextdomain): Add missing { }.
+
+Sun Nov 26 18:21:41 1995  Ulrich Drepper  <drepper@myware>
+
+       * libintl.inst: Add missing __P definition.  Reported by Nelson Beebe.
+
+       * Makefile.in:
+       Add dummy `all' and `dvi' goals.  Reported by Tom Tromey.
+
+Sat Nov 25 16:12:01 1995  Franc,ois Pinard  <pinard@iro.umontreal.ca>
+
+       * hash-string.h: Capitalize arguments of macros.
+
+Sat Nov 25 12:01:36 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (DISTFILES): Prevent files names longer than 13
+       characters.  libintl.h.glibc->libintl.glibc,
+       libintl.h.install->libintl.inst.  Reported by Joshua R. Poulson.
+
+Sat Nov 25 11:31:12 1995  Eric Backus  <ericb@lsid.hp.com>
+
+       * dcgettext.c: Fix bug in preprocessor conditionals.
+
+Sat Nov 25 02:35:27 1995  Nelson H. F. Beebe  <beebe@math.utah.edu>
+
+       * libgettext.h: Solaris cc does not understand
+        #if !SYMBOL1 && !SYMBOL2.  Sad but true.
+
+Thu Nov 23 16:22:14 1995  Ulrich Drepper  <drepper@myware>
+
+       * hash-string.h (hash_string):
+       Fix for machine with >32 bit `unsigned long's.
+
+       * dcgettext.c (DCGETTEXT):
+       Fix horrible bug in loop for alternative translation.
+
+Thu Nov 23 01:45:29 1995  Ulrich Drepper  <drepper@myware>
+
+       * po2tbl.sed.in, linux-msg.sed, xopen-msg.sed:
+       Some further simplifications in message number generation.
+
+Mon Nov 20 21:08:43 1995  Ulrich Drepper  <drepper@myware>
+
+       * libintl.h.glibc: Use __const instead of const in prototypes.
+
+       * Makefile.in (install-src):
+       Install libintl.h.install instead of libintl.h.  This
+       is a stripped-down version.  Suggested by Peter Miller.
+
+       * libintl.h.install, libintl.h.glibc: Initial revision.
+
+       * localealias.c (_nl_expand_alias, read_alias_file):
+       Protect prototypes in type casts by __P.
+
+Tue Nov 14 16:43:58 1995  Ulrich Drepper  <drepper@myware>
+
+       * hash-string.h: Correct prototype for hash_string.
+
+Sun Nov 12 12:42:30 1995  Ulrich Drepper  <drepper@myware>
+
+       * hash-string.h (hash_string): Add prototype.
+
+       * gettextP.h: Fix copyright.
+       (SWAP): Add prototype.
+
+Wed Nov  8 22:56:33 1995  Ulrich Drepper  <drepper@myware>
+
+       * localealias.c (read_alias_file): Forgot sizeof.
+       Avoid calling *printf function.  This introduces a big overhead.
+       Patch by Roland McGrath.
+
+Tue Nov  7 14:21:08 1995  Ulrich Drepper  <drepper@myware>
+
+       * finddomain.c, cat-compat.c: Wrong indentation in #if for stpcpy.
+
+       * finddomain.c (stpcpy):
+       Define substitution function local.  The macro was to flaky.
+
+       * cat-compat.c: Fix typo.
+
+       * xopen-msg.sed, linux-msg.sed:
+       While bringing message number to right place only accept digits.
+
+       * linux-msg.sed, xopen-msg.sed: Now that the counter does not have
+       leading 0s we don't need to remove them.  Reported by Marcus
+       Daniels.
+
+       * Makefile.in (../po/cat-id-tbl.o): Use $(top_srdir) in
+       dependency.  Reported by Marcus Daniels.
+
+       * cat-compat.c: (stpcpy) [!_LIBC && !HAVE_STPCPY]: Define replacement.
+       Generally cleanup using #if instead of #ifndef.
+
+       * Makefile.in: Correct typos in comment.  By Franc,ois Pinard.
+
+Mon Nov  6 00:27:02 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (install-src): Don't install libintl.h and libintl.a
+       if we use an available gettext implementation.
+
+Sun Nov  5 22:02:08 1995  Ulrich Drepper  <drepper@myware>
+
+       * libgettext.h: Fix typo: HAVE_CATGETTS -> HAVE_CATGETS.  Reported
+       by Franc,ois Pinard.
+
+       * libgettext.h: Use #if instead of #ifdef/#ifndef.
+
+       * finddomain.c:
+       Comments describing what has to be done should start with FIXME.
+
+Sun Nov  5 19:38:01 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (DISTFILES): Split.  Use DISTFILES with normal meaning.
+       DISTFILES.common names the files common to both dist goals.
+       DISTFILES.gettext are the files only distributed in GNU gettext.
+
+Sun Nov  5 17:32:54 1995  Ulrich Drepper  <drepper@myware>
+
+       * dcgettext.c (DCGETTEXT): Correct searching in derived locales.
+       This was necessary since a change in _nl_find_msg several weeks
+       ago.  I really don't know this is still not fixed.
+
+Sun Nov  5 12:43:12 1995  Ulrich Drepper  <drepper@myware>
+
+       * loadmsgcat.c (_nl_load_domain): Test for FILENAME == NULL.  This
+       might mark a special condition.
+
+       * finddomain.c (make_entry_rec): Don't make illegal entry as decided.
+
+       * Makefile.in (dist): Suppress error message when ln failed.
+       Get files from $(srcdir) explicitly.
+
+       * libgettext.h (gettext_const): Rename to gettext_noop.
+
+Fri Nov  3 07:36:50 1995  Ulrich Drepper  <drepper@myware>
+
+       * finddomain.c (make_entry_rec):
+       Protect against wrong locale names by testing mask.
+
+       * libgettext.h (gettext_const): Add macro definition.
+       Capitalize macro arguments.
+
+Thu Nov  2 23:15:51 1995  Ulrich Drepper  <drepper@myware>
+
+       * finddomain.c (_nl_find_domain):
+       Test for pointer != NULL before accessing value.
+       Reported by Tom Tromey.
+
+       * gettext.c (NULL):
+       Define as (void*)0 instad of 0.  Reported by Franc,ois Pinard.
+
+Mon Oct 30 21:28:52 1995  Ulrich Drepper  <drepper@myware>
+
+       * po2tbl.sed.in: Serious typo bug fixed by Jim Meyering.
+
+Sat Oct 28 23:20:47 1995  Ulrich Drepper  <drepper@myware>
+
+       * libgettext.h: Disable dcgettext optimization for Solaris 2.3.
+
+       * localealias.c (alias_compare):
+       Peter Miller reported that tolower in some systems is
+       even dumber than I thought.  Protect call by `isupper'.
+
+Fri Oct 27 22:22:51 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (libdir, includedir): New variables.
+       (install-src): Install libintl.a and libintl.h in correct dirs.
+
+Fri Oct 27 22:07:29 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (SOURCES): Fix typo: intrl.compat.c -> intl-compat.c.
+
+       * po2tbl.sed.in: Patch for buggy SEDs by Christian von Roques.
+
+       * localealias.c:
+       Fix typo and superflous test.  Reported by Christian von Roques.
+
+Fri Oct  6 11:52:05 1995  Ulrich Drepper  <drepper@myware>
+
+       * finddomain.c (_nl_find_domain):
+       Correct some remainder from the pre-CEN syntax.  Now
+       we don't have a constant number of successors anymore.
+
+Wed Sep 27 21:41:13 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (DISTFILES): Add libintl.h.glibc.
+
+       * Makefile.in (dist-libc): Add goal for packing sources for glibc.
+       (COMSRCS, COMHDRS): Splitted to separate sources shared with glibc.
+
+       * loadmsgcat.c: Forget to continue #if line.
+
+       * localealias.c:
+       [_LIBC]: Rename strcasecmp to __strcasecmp to keep ANSI C name
+       space clean.
+
+       * dcgettext.c, finddomain.c: Better comment to last change.
+
+       * loadmsgcat.c:
+       [_LIBC]: Rename fstat, open, close, read, mmap, and munmap to
+       __fstat, __open, __close, __read, __mmap, and __munmap resp
+       to keep ANSI C name space clean.
+
+       * finddomain.c:
+       [_LIBC]: Rename stpcpy to __stpcpy to keep ANSI C name space clean.
+
+       * dcgettext.c:
+       [_LIBC]: Rename getced and stpcpy to __getcwd and __stpcpy resp to
+       keep ANSI C name space clean.
+
+       * libgettext.h:
+       Include sys/types.h for those old SysV systems out there.
+       Reported by Francesco Potorti`.
+
+       * loadmsgcat.c (use_mmap): Define if compiled for glibc.
+
+       * bindtextdom.c: Include all those standard headers
+       unconditionally if _LIBC is defined.
+
+       * finddomain.c: Fix 2 times defiend -> defined.
+
+       * textdomain.c: Include libintl.h instead of libgettext.h when
+       compiling for glibc.  Include all those standard headers
+       unconditionally if _LIBC is defined.
+
+       * localealias.c, loadmsgcat.c: Prepare to be compiled in glibc.
+
+       * gettext.c:
+       Include libintl.h instead of libgettext.h when compiling for glibc.
+       Get NULL from stddef.h if we compile for glibc.
+
+       * finddomain.c: Include libintl.h instead of libgettext.h when
+       compiling for glibc.  Include all those standard headers
+       unconditionally if _LIBC is defined.
+
+       * dcgettext.c: Include all those standard headers unconditionally
+       if _LIBC is defined.
+
+       * dgettext.c: If compiled in glibc include libintl.h instead of
+       libgettext.h.
+       (locale.h): Don't rely on HAVE_LOCALE_H when compiling for glibc.
+
+       * dcgettext.c: If compiled in glibc include libintl.h instead of
+       libgettext.h.
+       (getcwd): Don't rely on HAVE_GETCWD when compiling for glibc.
+
+       * bindtextdom.c:
+       If compiled in glibc include libintl.h instead of libgettext.h.
+
+Mon Sep 25 22:23:06 1995  Ulrich Drepper  <drepper@myware>
+
+       * localealias.c (_nl_expand_alias): Don't call bsearch if NMAP <= 0.
+       Reported by Marcus Daniels.
+
+       * cat-compat.c (bindtextdomain):
+       String used in putenv must not be recycled.
+       Reported by Marcus Daniels.
+
+       * libgettext.h (__USE_GNU_GETTEXT):
+       Additional symbol to signal that we use GNU gettext
+       library.
+
+       * cat-compat.c (bindtextdomain):
+       Fix bug with the strange stpcpy replacement.
+       Reported by Nelson Beebe.
+
+Sat Sep 23 08:23:51 1995  Ulrich Drepper  <drepper@myware>
+
+       * cat-compat.c: Include <string.h> for stpcpy prototype.
+
+       * localealias.c (read_alias_file):
+       While expand strdup code temporary variable `cp' hided
+       higher level variable with same name.  Rename to `tp'.
+
+       * textdomain.c (textdomain):
+       Avoid warning by using temporary variable in strdup code.
+
+       * finddomain.c (_nl_find_domain): Remove unused variable `application'.
+
+Thu Sep 21 15:51:44 1995  Ulrich Drepper  <drepper@myware>
+
+       * localealias.c (alias_compare):
+       Use strcasecmp() only if available.  Else use
+       implementation in place.
+
+       * intl-compat.c:
+       Wrapper functions now call *__ functions instead of __*.
+
+       * libgettext.h: Declare prototypes for *__ functions instead for __*.
+
+       * cat-compat.c, loadmsgcat.c:
+       Don't use xmalloc, xstrdup, and stpcpy.  These functions are not part
+       of the standard libc and so prevent libintl.a from being used
+       standalone.
+
+       * bindtextdom.c:
+       Don't use xmalloc, xstrdup, and stpcpy.  These functions are not part
+       of the standard libc and so prevent libintl.a from being used
+       standalone.
+       Rename to bindtextdomain__ if not used in GNU C Library.
+
+       * dgettext.c:
+       Rename function to dgettext__ if not used in GNU C Library.
+
+       * gettext.c:
+       Don't use xmalloc, xstrdup, and stpcpy.  These functions are not part
+       of the standard libc and so prevent libintl.a from being used
+       standalone.
+       Functions now called gettext__ if not used in GNU C Library.
+
+       * dcgettext.c, localealias.c, textdomain.c, finddomain.c:
+       Don't use xmalloc, xstrdup, and stpcpy.  These functions are not part
+       of the standard libc and so prevent libintl.a from being used
+       standalone.
+
+Sun Sep 17 23:14:49 1995  Ulrich Drepper  <drepper@myware>
+
+       * finddomain.c: Correct some bugs in handling of CEN standard
+       locale definitions.
+
+Thu Sep  7 01:49:28 1995  Ulrich Drepper  <drepper@myware>
+
+       * finddomain.c: Implement CEN syntax.
+
+       * gettextP.h (loaded_domain): Extend number of successors to 31.
+
+Sat Aug 19 19:25:29 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (aliaspath): Remove path to X11 locale dir.
+
+       * Makefile.in: Make install-src depend on install.  This helps
+       gettext to install the sources and other packages can use the
+       install goal.
+
+Sat Aug 19 15:19:33 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (uninstall): Remove stuff installed by install-src.
+
+Tue Aug 15 13:13:53 1995  Ulrich Drepper  <drepper@myware>
+
+       * VERSION.in: Initial revision.
+
+       * Makefile.in (DISTFILES):
+       Add VERSION file.  This is not necessary for gettext, but
+       for other packages using this library.
+
+Tue Aug 15 06:16:44 1995  Ulrich Drepper  <drepper@myware>
+
+       * gettextP.h (_nl_find_domain):
+       New prototype after changing search strategy.
+
+       * finddomain.c (_nl_find_domain):
+       We now try only to find a specified catalog.  Fall back to other
+       catalogs listed in the locale list is now done in __dcgettext.
+
+       * dcgettext.c (__dcgettext):
+       Now we provide message fall back even to different languages.
+       I.e. if a message is not available in one language all the other
+       in the locale list a tried.  Formerly fall back was only possible
+       within one language.  Implemented by moving one loop from
+       _nl_find_domain to here.
+
+Mon Aug 14 23:45:50 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (gettextsrcdir):
+       Directory where source of GNU gettext library are made
+       available.
+       (INSTALL, INSTALL_DATA): Programs used for installing sources.
+       (gettext-src): New.  Rule to install GNU gettext sources for use in
+       gettextize shell script.
+
+Sun Aug 13 14:40:48 1995  Ulrich Drepper  <drepper@myware>
+
+       * loadmsgcat.c (_nl_load_domain):
+       Use mmap for loading only when munmap function is
+       also available.
+
+       * Makefile.in (install): Depend on `all' goal.
+
+Wed Aug  9 11:04:33 1995  Ulrich Drepper  <drepper@myware>
+
+       * localealias.c (read_alias_file):
+       Do not overwrite '\n' when terminating alias value string.
+
+       * localealias.c (read_alias_file):
+       Handle long lines.  Ignore the rest not fitting in
+       the buffer after the initial `fgets' call.
+
+Wed Aug  9 00:54:29 1995  Ulrich Drepper  <drepper@myware>
+
+       * gettextP.h (_nl_load_domain):
+       Add prototype, replacing prototype for _nl_load_msg_cat.
+
+       * finddomain.c (_nl_find_domain):
+       Remove unneeded variable filename and filename_len.
+       (expand_alias): Remove prototype because functions does not
+       exist anymore.
+
+       * localealias.c (read_alias_file):
+       Change type of fname_len parameter to int.
+       (xmalloc): Add prototype.
+
+       * loadmsgcat.c: Better prototypes for xmalloc.
+
+Tue Aug  8 22:30:39 1995  Ulrich Drepper  <drepper@myware>
+
+       * finddomain.c (_nl_find_domain):
+       Allow alias name to be constructed from the four components.
+
+       * Makefile.in (aliaspath): New variable.  Set to preliminary value.
+       (SOURCES): Add localealias.c.
+       (OBJECTS): Add localealias.o.
+
+       * gettextP.h: Add prototype for _nl_expand_alias.
+
+       * finddomain.c: Aliasing handled in intl/localealias.c.
+
+       * localealias.c: Aliasing for locale names.
+
+       * bindtextdom.c: Better prototypes for xmalloc and xstrdup.
+
+Mon Aug  7 23:47:42 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (DISTFILES): gettext.perl is now found in misc/.
+
+       * cat-compat.c (bindtextdomain):
+       Correct implementation.  dirname parameter was not used.
+       Reported by Marcus Daniels.
+
+       * gettextP.h (loaded_domain):
+       New fields `successor' and `decided' for oo, lazy
+       message handling implementation.
+
+       * dcgettext.c:
+       Adopt for oo, lazy message handliing.
+       Now we can inherit translations from less specific locales.
+       (find_msg): New function.
+
+       * loadmsgcat.c, finddomain.c:
+       Complete rewrite.  Implement oo, lazy message handling :-).
+       We now have an additional environment variable `LANGUAGE' with
+       a higher priority than LC_ALL for the LC_MESSAGE locale.
+       Here we can set a colon separated list of specifications each
+       of the form `language[_territory[.codeset]][@modifier]'.
+
+Sat Aug  5 09:55:42 1995  Ulrich Drepper  <drepper@myware>
+
+       * finddomain.c (unistd.h):
+       Include to get _PC_PATH_MAX defined on system having it.
+
+Fri Aug  4 22:42:00 1995  Ulrich Drepper  <drepper@myware>
+
+       * finddomain.c (stpcpy): Include prototype.
+
+       * Makefile.in (dist): Remove `copying instead' message.
+
+Wed Aug  2 18:52:03 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (ID, TAGS): Do not use $^.
+
+Tue Aug  1 20:07:11 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (TAGS, ID): Use $^ as command argument.
+       (TAGS): Give etags -o option t write to current directory,
+       not $(srcdir).
+       (ID): Use $(srcdir) instead os $(top_srcdir)/src.
+       (distclean): Remove ID.
+
+Sun Jul 30 11:51:46 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (gnulocaledir):
+       New variable, always using share/ for data directory.
+       (DEFS): Add GNULOCALEDIR, used in finddomain.c.
+
+       * finddomain.c (_nl_default_dirname):
+       Set to GNULOCALEDIR, because it always has to point
+       to the directory where GNU gettext Library writes it to.
+
+       * intl-compat.c (textdomain, bindtextdomain):
+       Undefine macros before function definition.
+
+Sat Jul 22 01:10:02 1995  Ulrich Drepper  <drepper@myware>
+
+       * libgettext.h (_LIBINTL_H):
+       Protect definition in case where this file is included as
+       libgettext.h on Solaris machines.  Add comment about this.
+
+Wed Jul 19 02:36:42 1995  Ulrich Drepper  <drepper@myware>
+
+       * intl-compat.c (textdomain): Correct typo.
+
+Wed Jul 19 01:51:35 1995  Ulrich Drepper  <drepper@myware>
+
+       * dcgettext.c (dcgettext): Function now called __dcgettext.
+
+       * dgettext.c (dgettext): Now called __dgettext and calls
+       __dcgettext.
+
+       * gettext.c (gettext):
+       Function now called __gettext and calls __dgettext.
+
+       * textdomain.c (textdomain): Function now called __textdomain.
+
+       * bindtextdom.c (bindtextdomain): Function now called
+       __bindtextdomain.
+
+       * intl-compat.c: Initial revision.
+
+       * Makefile.in (SOURCES): Add intl-compat.c.
+       (OBJECTS): We always compile the GNU gettext library functions.
+       OBJECTS contains all objects but cat-compat.o, ../po/cat-if-tbl.o,
+       and intl-compat.o.
+       (GETTOBJS): Contains now only intl-compat.o.
+
+       * libgettext.h:
+       Re-include protection matches dualistic character of libgettext.h.
+       For all functions in GNU gettext library define __ counter part.
+
+       * finddomain.c (strchr): Define as index if not found in C library.
+       (_nl_find_domain): For relative paths paste / in between.
+
+Tue Jul 18 16:37:45 1995  Ulrich Drepper  <drepper@myware>
+
+       * loadmsgcat.c, finddomain.c: Add inclusion of sys/types.h.
+
+       * xopen-msg.sed: Fix bug with `msgstr ""' lines.
+       A little bit better comments.
+
+Tue Jul 18 01:18:27 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in:
+       po-mode.el, makelinks, combine-sh are now found in ../misc.
+
+       * po-mode.el, makelinks, combine-sh, elisp-comp:
+       Moved to ../misc/.
+
+       * libgettext.h, gettextP.h, gettext.h: Uniform test for __STDC__.
+
+Sun Jul 16 22:33:02 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (INSTALL, INSTALL_DATA): New variables.
+       (install-data, uninstall): Install/uninstall .elc file.
+
+       * po-mode.el (Installation comment):
+       Add .pox as possible extension of .po files.
+
+Sun Jul 16 13:23:27 1995  Ulrich Drepper  <drepper@myware>
+
+       * elisp-comp: Complete new version by Franc,ois: This does not
+       fail when not compiling in the source directory.
+
+Sun Jul 16 00:12:17 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (../po/cat-id-tbl.o):
+       Use $(MAKE) instead of make for recursive make.
+
+       * Makefile.in (.el.elc): Use $(SHELL) instead of /bin/sh.
+       (install-exec): Add missing dummy goal.
+       (install-data, uninstall): @ in multi-line shell command at
+       beginning, not in front of echo.  Reported by Eric Backus.
+
+Sat Jul 15 00:21:28 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (DISTFILES):
+       Rename libgettext.perl to gettext.perl to fit in 14 chars
+       file systems.
+
+       * gettext.perl:
+       Rename to gettext.perl to fit in 14 chars file systems.
+
+Thu Jul 13 23:17:20 1995  Ulrich Drepper  <drepper@myware>
+
+       * cat-compat.c: If !STDC_HEADERS try to include malloc.h.
+
+Thu Jul 13 20:55:02 1995  Ulrich Drepper  <drepper@myware>
+
+       * po2tbl.sed.in: Pretty printing.
+
+       * linux-msg.sed, xopen-msg.sed:
+       Correct bugs with handling substitute flags in branches.
+
+       * hash-string.h (hash_string):
+       Old K&R compilers don't under stand `unsigned char'.
+
+       * gettext.h (nls_uint32):
+       Some old K&R compilers (eg HP) don't understand `unsigned int'.
+
+       * cat-compat.c (msg_to_cat_id): De-ANSI-fy prototypes.
+
+Thu Jul 13 01:34:33 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (ELCFILES): New variable.
+       (DISTFILES): Add elisp-comp.
+       Add implicit rule for .el -> .elc compilation.
+       (install-data): install $ELCFILES
+       (clean): renamed po-to-tbl and po-to-msg to po2tbl and po2msg resp.
+
+       * elisp-comp: Initial revision
+
+Wed Jul 12 16:14:52 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in:
+       cat-id-tbl.c is now found in po/.  This enables us to use an identical
+       intl/ directory in all packages.
+
+       * dcgettext.c (dcgettext): hashing does not work for table size <= 2.
+
+       * textdomain.c: fix typo (#if def -> #if defined)
+
+Tue Jul 11 18:44:43 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in (stamp-cat-id): use top_srcdir to address source files
+       (DISTFILES,distclean): move tupdate.perl to src/
+
+       * po-to-tbl.sed.in:
+       add additional jump to clear change flag to recognize multiline strings
+
+Tue Jul 11 01:32:50 1995  Ulrich Drepper  <drepper@myware>
+
+       * textdomain.c: Protect inclusion of stdlib.h and string.h.
+
+       * loadmsgcat.c: Protect inclusion of stdlib.h.
+
+       * libgettext.h: Protect inclusion of locale.h.
+       Allow use in C++ programs.
+       Define NULL is not happened already.
+
+       * Makefile.in (DISTFILES): ship po-to-tbl.sed.in instead of
+       po-to-tbl.sed.
+       (distclean): remove po-to-tbl.sed and tupdate.perl.
+
+       * tupdate.perl.in: Substitute Perl path even in exec line.
+       Don't include entries without translation from old .po file.
+
+Tue Jul  4 00:41:51 1995  Ulrich Drepper  <drepper@myware>
+
+       * tupdate.perl.in: use "Updated: " in msgid "".
+
+       * cat-compat.c: Fix typo (LOCALDIR -> LOCALEDIR).
+       Define getenv if !__STDC__.
+
+       * bindtextdom.c: Protect stdlib.h and string.h inclusion.
+       Define free if !__STDC__.
+
+       * finddomain.c: Change DEF_MSG_DOM_DIR to LOCALEDIR.
+       Define free if !__STDC__.
+
+       * cat-compat.c: Change DEF_MSG_DOM_DIR to LOCALEDIR.
+
+Mon Jul  3 23:56:30 1995  Ulrich Drepper  <drepper@myware>
+
+       * Makefile.in: Use LOCALEDIR instead of DEF_MSG_DOM_DIR.
+       Remove unneeded $(srcdir) from Makefile.in dependency.
+
+       * makelinks: Add copyright and short description.
+
+       * po-mode.el: Last version for 0.7.
+
+       * tupdate.perl.in: Fix die message.
+
+       * dcgettext.c: Protect include of string.h.
+
+       * gettext.c: Protect include of stdlib.h and further tries to get NULL.
+
+       * finddomain.c: Some corrections in includes.
+
+       * Makefile.in (INCLUDES): Prune list correct path to Makefile.in.
+
+       * po-to-tbl.sed: Adopt for new .po file format.
+
+       * linux-msg.sed, xopen-msg.sed: Adopt for new .po file format.
+
+Sun Jul  2 23:55:03 1995  Ulrich Drepper  <drepper@myware>
+
+       * tupdate.perl.in: Complete rewrite for new .po file format.
+
+Sun Jul  2 02:06:50 1995  Ulrich Drepper  <drepper@myware>
+
+       * First official release.  This directory contains all the code
+       needed to internationalize own packages.  It provides functions
+       which allow to use the X/Open catgets function with an interface
+       like the Uniforum gettext function.  For system which does not
+       have neither of those a complete implementation is provided.
diff --git a/intl/Makefile.in b/intl/Makefile.in
new file mode 100644 (file)
index 0000000..c5e0ca9
--- /dev/null
@@ -0,0 +1,214 @@
+# Makefile for directory with message catalog handling in GNU NLS Utilities.
+# Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+PACKAGE = @PACKAGE@
+VERSION = @VERSION@
+
+SHELL = /bin/sh
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+top_builddir = ..
+VPATH = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+transform = @program_transform_name@
+libdir = $(exec_prefix)/lib
+includedir = $(prefix)/include
+datadir = $(prefix)/@DATADIRNAME@
+localedir = $(datadir)/locale
+gnulocaledir = $(prefix)/share/locale
+gettextsrcdir = @datadir@/gettext/intl
+aliaspath = $(localedir):.
+subdir = intl
+
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+
+l = @l@
+
+AR = ar
+CC = @CC@
+LIBTOOL = @LIBTOOL@
+RANLIB = @RANLIB@
+
+DEFS = -DLOCALEDIR=\"$(localedir)\" -DGNULOCALEDIR=\"$(gnulocaledir)\" \
+-DLOCALE_ALIAS_PATH=\"$(aliaspath)\" @DEFS@
+CPPFLAGS = @CPPFLAGS@
+CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+
+COMPILE = $(CC) -c $(DEFS) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) $(XCFLAGS)
+
+HEADERS = $(COMHDRS) libgettext.h loadinfo.h
+COMHDRS = gettext.h gettextP.h hash-string.h
+SOURCES = $(COMSRCS) intl-compat.c cat-compat.c
+COMSRCS = bindtextdom.c dcgettext.c dgettext.c gettext.c \
+finddomain.c loadmsgcat.c localealias.c textdomain.c l10nflist.c \
+explodename.c
+OBJECTS = @INTLOBJS@ bindtextdom.$lo dcgettext.$lo dgettext.$lo gettext.$lo \
+finddomain.$lo loadmsgcat.$lo localealias.$lo textdomain.$lo l10nflist.$lo \
+explodename.$lo
+CATOBJS = cat-compat.$lo ../po/cat-id-tbl.$lo
+GETTOBJS = intl-compat.$lo
+DISTFILES.common = ChangeLog Makefile.in linux-msg.sed po2tbl.sed.in \
+xopen-msg.sed $(HEADERS) $(SOURCES)
+DISTFILES.normal = VERSION
+DISTFILES.gettext = libintl.glibc intlh.inst.in
+
+.SUFFIXES:
+.SUFFIXES: .c .o .lo
+.c.o:
+       $(COMPILE) $<
+.c.lo:
+       $(LIBTOOL) --mode=compile $(COMPILE) $<
+
+INCLUDES = -I.. -I. -I$(top_srcdir)/intl -I$(top_srcdir)/lib -I$(top_srcdir)/src
+
+all: all-@USE_INCLUDED_LIBINTL@
+
+all-yes: libintl.$la intlh.inst
+all-no:
+
+libintl.a: $(OBJECTS)
+       rm -f $@
+       $(AR) cru $@ $(OBJECTS)
+       $(RANLIB) $@
+
+libintl.la: $(OBJECTS)
+       $(LIBTOOL) --mode=link $(CC) $(LDFLAGS) -o $@ $(OBJECTS) \
+                  -version-info 1:0 -rpath $(libdir)
+
+../po/cat-id-tbl.$lo: ../po/cat-id-tbl.c $(top_srcdir)/po/$(PACKAGE).pot
+       cd ../po && $(MAKE) cat-id-tbl.$lo
+
+check: all
+
+# This installation goal is only used in GNU gettext.  Packages which
+# only use the library should use install instead.
+
+# We must not install the libintl.h/libintl.a files if we are on a
+# system which has the gettext() function in its C library or in a
+# separate library or use the catgets interface.  A special case is
+# where configure found a previously installed GNU gettext library.
+# If you want to use the one which comes with this version of the
+# package, you have to use `configure --with-included-gettext'.
+install: install-exec install-data
+install-exec: all
+       if test "$(PACKAGE)" = "gettext" \
+          && test '@INTLOBJS@' = '$(GETTOBJS)'; then \
+         if test -r $(MKINSTALLDIRS); then \
+           $(MKINSTALLDIRS) $(libdir) $(includedir); \
+         else \
+           $(top_srcdir)/mkinstalldirs $(libdir) $(includedir); \
+         fi; \
+         $(INSTALL_DATA) intlh.inst $(includedir)/libintl.h; \
+         $(INSTALL_DATA) libintl.a $(libdir)/libintl.a; \
+       else \
+         : ; \
+       fi
+install-data: all
+       if test "$(PACKAGE)" = "gettext"; then \
+         if test -r $(MKINSTALLDIRS); then \
+           $(MKINSTALLDIRS) $(gettextsrcdir); \
+         else \
+           $(top_srcdir)/mkinstalldirs $(gettextsrcdir); \
+         fi; \
+         $(INSTALL_DATA) VERSION $(gettextsrcdir)/VERSION; \
+         dists="$(DISTFILES.common)"; \
+         for file in $$dists; do \
+           $(INSTALL_DATA) $(srcdir)/$$file $(gettextsrcdir)/$$file; \
+         done; \
+       else \
+         : ; \
+       fi
+
+# Define this as empty until I found a useful application.
+installcheck:
+
+uninstall:
+       dists="$(DISTFILES.common)"; \
+       for file in $$dists; do \
+         rm -f $(gettextsrcdir)/$$file; \
+       done
+
+info dvi:
+
+$(OBJECTS): ../config.h libgettext.h
+bindtextdom.$lo finddomain.$lo loadmsgcat.$lo: gettextP.h gettext.h loadinfo.h
+dcgettext.$lo: gettextP.h gettext.h hash-string.h loadinfo.h
+
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES)
+       here=`pwd`; cd $(srcdir) && etags -o $$here/TAGS $(HEADERS) $(SOURCES)
+
+id: ID
+
+ID: $(HEADERS) $(SOURCES)
+       here=`pwd`; cd $(srcdir) && mkid -f$$here/ID $(HEADERS) $(SOURCES)
+
+
+mostlyclean:
+       rm -f *.a *.o *.lo core core.*
+
+clean: mostlyclean
+
+distclean: clean
+       rm -f Makefile ID TAGS po2msg.sed po2tbl.sed libintl.h
+
+maintainer-clean: distclean
+       @echo "This command is intended for maintainers to use;"
+       @echo "it deletes files that may require special tools to rebuild."
+
+
+# GNU gettext needs not contain the file `VERSION' but contains some
+# other files which should not be distributed in other packages.
+distdir = ../$(PACKAGE)-$(VERSION)/$(subdir)
+dist distdir: Makefile $(DISTFILES)
+       if test "$(PACKAGE)" = gettext; then \
+         additional="$(DISTFILES.gettext)"; \
+       else \
+         additional="$(DISTFILES.normal)"; \
+       fi; \
+       for file in $(DISTFILES.common) $$additional; do \
+         ln $(srcdir)/$$file $(distdir) 2> /dev/null \
+           || cp -p $(srcdir)/$$file $(distdir); \
+       done
+
+dist-libc:
+       tar zcvf intl-glibc.tar.gz $(COMSRCS) $(COMHDRS) libintl.h.glibc
+
+Makefile: Makefile.in ../config.status
+       cd .. \
+         && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= $(SHELL) ./config.status
+
+# The dependency for intlh.inst is different in gettext and all other
+# packages.  Because we cannot you GNU make features we have to solve
+# the problem while rewriting Makefile.in.
+@GT_YES@intlh.inst: intlh.inst.in ../config.status
+@GT_YES@       cd .. \
+@GT_YES@       && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= \
+@GT_YES@         $(SHELL) ./config.status
+@GT_NO@.PHONY: intlh.inst
+@GT_NO@intlh.inst:
+
+# Tell versions [3.59,3.63) of GNU make not to export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/intl/VERSION b/intl/VERSION
new file mode 100644 (file)
index 0000000..d0e8c69
--- /dev/null
@@ -0,0 +1 @@
+0.10.32
diff --git a/intl/bindtextdom.c b/intl/bindtextdom.c
new file mode 100644 (file)
index 0000000..9fcb8d9
--- /dev/null
@@ -0,0 +1,199 @@
+/* Implementation of the bindtextdomain(3) function
+   Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if defined STDC_HEADERS || defined _LIBC
+# include <stdlib.h>
+#else
+# ifdef HAVE_MALLOC_H
+#  include <malloc.h>
+# else
+void free ();
+# endif
+#endif
+
+#if defined HAVE_STRING_H || defined _LIBC
+# include <string.h>
+#else
+# include <strings.h>
+# ifndef memcpy
+#  define memcpy(Dst, Src, Num) bcopy (Src, Dst, Num)
+# endif
+#endif
+
+#ifdef _LIBC
+# include <libintl.h>
+#else
+# include "libgettext.h"
+#endif
+#include "gettext.h"
+#include "gettextP.h"
+
+/* @@ end of prolog @@ */
+
+/* Contains the default location of the message catalogs.  */
+extern const char _nl_default_dirname[];
+
+/* List with bindings of specific domains.  */
+extern struct binding *_nl_domain_bindings;
+
+
+/* Names for the libintl functions are a problem.  They must not clash
+   with existing names and they should follow ANSI C.  But this source
+   code is also used in GNU C Library where the names have a __
+   prefix.  So we have to make a difference here.  */
+#ifdef _LIBC
+# define BINDTEXTDOMAIN __bindtextdomain
+# define strdup(str) __strdup (str)
+#else
+# define BINDTEXTDOMAIN bindtextdomain__
+#endif
+
+/* Specify that the DOMAINNAME message catalog will be found
+   in DIRNAME rather than in the system locale data base.  */
+char *
+BINDTEXTDOMAIN (domainname, dirname)
+     const char *domainname;
+     const char *dirname;
+{
+  struct binding *binding;
+
+  /* Some sanity checks.  */
+  if (domainname == NULL || domainname[0] == '\0')
+    return NULL;
+
+  for (binding = _nl_domain_bindings; binding != NULL; binding = binding->next)
+    {
+      int compare = strcmp (domainname, binding->domainname);
+      if (compare == 0)
+       /* We found it!  */
+       break;
+      if (compare < 0)
+       {
+         /* It is not in the list.  */
+         binding = NULL;
+         break;
+       }
+    }
+
+  if (dirname == NULL)
+    /* The current binding has be to returned.  */
+    return binding == NULL ? (char *) _nl_default_dirname : binding->dirname;
+
+  if (binding != NULL)
+    {
+      /* The domain is already bound.  If the new value and the old
+        one are equal we simply do nothing.  Otherwise replace the
+        old binding.  */
+      if (strcmp (dirname, binding->dirname) != 0)
+       {
+         char *new_dirname;
+
+         if (strcmp (dirname, _nl_default_dirname) == 0)
+           new_dirname = (char *) _nl_default_dirname;
+         else
+           {
+#if defined _LIBC || defined HAVE_STRDUP
+             new_dirname = strdup (dirname);
+             if (new_dirname == NULL)
+               return NULL;
+#else
+             size_t len = strlen (dirname) + 1;
+             new_dirname = (char *) malloc (len);
+             if (new_dirname == NULL)
+               return NULL;
+
+             memcpy (new_dirname, dirname, len);
+#endif
+           }
+
+         if (binding->dirname != _nl_default_dirname)
+           free (binding->dirname);
+
+         binding->dirname = new_dirname;
+       }
+    }
+  else
+    {
+      /* We have to create a new binding.  */
+      size_t len;
+      struct binding *new_binding =
+       (struct binding *) malloc (sizeof (*new_binding));
+
+      if (new_binding == NULL)
+       return NULL;
+
+#if defined _LIBC || defined HAVE_STRDUP
+      new_binding->domainname = strdup (domainname);
+      if (new_binding->domainname == NULL)
+       return NULL;
+#else
+      len = strlen (domainname) + 1;
+      new_binding->domainname = (char *) malloc (len);
+      if (new_binding->domainname == NULL)
+       return NULL;
+      memcpy (new_binding->domainname, domainname, len);
+#endif
+
+      if (strcmp (dirname, _nl_default_dirname) == 0)
+       new_binding->dirname = (char *) _nl_default_dirname;
+      else
+       {
+#if defined _LIBC || defined HAVE_STRDUP
+         new_binding->dirname = strdup (dirname);
+         if (new_binding->dirname == NULL)
+           return NULL;
+#else
+         len = strlen (dirname) + 1;
+         new_binding->dirname = (char *) malloc (len);
+         if (new_binding->dirname == NULL)
+           return NULL;
+         memcpy (new_binding->dirname, dirname, len);
+#endif
+       }
+
+      /* Now enqueue it.  */
+      if (_nl_domain_bindings == NULL
+         || strcmp (domainname, _nl_domain_bindings->domainname) < 0)
+       {
+         new_binding->next = _nl_domain_bindings;
+         _nl_domain_bindings = new_binding;
+       }
+      else
+       {
+         binding = _nl_domain_bindings;
+         while (binding->next != NULL
+                && strcmp (domainname, binding->next->domainname) > 0)
+           binding = binding->next;
+
+         new_binding->next = binding->next;
+         binding->next = new_binding;
+       }
+
+      binding = new_binding;
+    }
+
+  return binding->dirname;
+}
+
+#ifdef _LIBC
+/* Alias for function name in GNU C Library.  */
+weak_alias (__bindtextdomain, bindtextdomain);
+#endif
diff --git a/intl/cat-compat.c b/intl/cat-compat.c
new file mode 100644 (file)
index 0000000..867d901
--- /dev/null
@@ -0,0 +1,262 @@
+/* Compatibility code for gettext-using-catgets interface.
+   Copyright (C) 1995, 1997 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <string.h>
+#else
+char *getenv ();
+# ifdef HAVE_MALLOC_H
+#  include <malloc.h>
+# endif
+#endif
+
+#ifdef HAVE_NL_TYPES_H
+# include <nl_types.h>
+#endif
+
+#include "libgettext.h"
+
+/* @@ end of prolog @@ */
+
+/* XPG3 defines the result of `setlocale (category, NULL)' as:
+   ``Directs `setlocale()' to query `category' and return the current
+     setting of `local'.''
+   However it does not specify the exact format.  And even worse: POSIX
+   defines this not at all.  So we can use this feature only on selected
+   system (e.g. those using GNU C Library).  */
+#ifdef _LIBC
+# define HAVE_LOCALE_NULL
+#endif
+
+/* The catalog descriptor.  */
+static nl_catd catalog = (nl_catd) -1;
+
+/* Name of the default catalog.  */
+static const char default_catalog_name[] = "messages";
+
+/* Name of currently used catalog.  */
+static const char *catalog_name = default_catalog_name;
+
+/* Get ID for given string.  If not found return -1.  */
+static int msg_to_cat_id PARAMS ((const char *msg));
+
+/* Substitution for systems lacking this function in their C library.  */
+#if !_LIBC && !HAVE_STPCPY
+static char *stpcpy PARAMS ((char *dest, const char *src));
+#endif
+
+
+/* Set currently used domain/catalog.  */
+char *
+textdomain (domainname)
+     const char *domainname;
+{
+  nl_catd new_catalog;
+  char *new_name;
+  size_t new_name_len;
+  char *lang;
+
+#if defined HAVE_SETLOCALE && defined HAVE_LC_MESSAGES \
+    && defined HAVE_LOCALE_NULL
+  lang = setlocale (LC_MESSAGES, NULL);
+#else
+  lang = getenv ("LC_ALL");
+  if (lang == NULL || lang[0] == '\0')
+    {
+      lang = getenv ("LC_MESSAGES");
+      if (lang == NULL || lang[0] == '\0')
+       lang = getenv ("LANG");
+    }
+#endif
+  if (lang == NULL || lang[0] == '\0')
+    lang = "C";
+
+  /* See whether name of currently used domain is asked.  */
+  if (domainname == NULL)
+    return (char *) catalog_name;
+
+  if (domainname[0] == '\0')
+    domainname = default_catalog_name;
+
+  /* Compute length of added path element.  */
+  new_name_len = sizeof (LOCALEDIR) - 1 + 1 + strlen (lang)
+                + sizeof ("/LC_MESSAGES/") - 1 + sizeof (PACKAGE) - 1
+                + sizeof (".cat");
+
+  new_name = (char *) malloc (new_name_len);
+  if (new_name == NULL)
+    return NULL;
+
+  strcpy (new_name, PACKAGE);
+  new_catalog = catopen (new_name, 0);
+
+  if (new_catalog == (nl_catd) -1)
+    {
+      /* NLSPATH search didn't work, try absolute path */
+      sprintf (new_name, "%s/%s/LC_MESSAGES/%s.cat", LOCALEDIR, lang,
+              PACKAGE);
+      new_catalog = catopen (new_name, 0);
+
+      if (new_catalog == (nl_catd) -1)
+       {
+         free (new_name);
+         return (char *) catalog_name;
+       }
+    }
+
+  /* Close old catalog.  */
+  if (catalog != (nl_catd) -1)
+    catclose (catalog);
+  if (catalog_name != default_catalog_name)
+    free ((char *) catalog_name);
+
+  catalog = new_catalog;
+  catalog_name = new_name;
+
+  return (char *) catalog_name;
+}
+
+char *
+bindtextdomain (domainname, dirname)
+     const char *domainname;
+     const char *dirname;
+{
+#if HAVE_SETENV || HAVE_PUTENV
+  char *old_val, *new_val, *cp;
+  size_t new_val_len;
+
+  /* This does not make much sense here but to be compatible do it.  */
+  if (domainname == NULL)
+    return NULL;
+
+  /* Compute length of added path element.  If we use setenv we don't need
+     the first byts for NLSPATH=, but why complicate the code for this
+     peanuts.  */
+  new_val_len = sizeof ("NLSPATH=") - 1 + strlen (dirname)
+               + sizeof ("/%L/LC_MESSAGES/%N.cat");
+
+  old_val = getenv ("NLSPATH");
+  if (old_val == NULL || old_val[0] == '\0')
+    {
+      old_val = NULL;
+      new_val_len += 1 + sizeof (LOCALEDIR) - 1
+                    + sizeof ("/%L/LC_MESSAGES/%N.cat");
+    }
+  else
+    new_val_len += strlen (old_val);
+
+  new_val = (char *) malloc (new_val_len);
+  if (new_val == NULL)
+    return NULL;
+
+# if HAVE_SETENV
+  cp = new_val;
+# else
+  cp = stpcpy (new_val, "NLSPATH=");
+# endif
+
+  cp = stpcpy (cp, dirname);
+  cp = stpcpy (cp, "/%L/LC_MESSAGES/%N.cat:");
+
+  if (old_val == NULL)
+    {
+# if __STDC__
+      stpcpy (cp, LOCALEDIR "/%L/LC_MESSAGES/%N.cat");
+# else
+
+      cp = stpcpy (cp, LOCALEDIR);
+      stpcpy (cp, "/%L/LC_MESSAGES/%N.cat");
+# endif
+    }
+  else
+    stpcpy (cp, old_val);
+
+# if HAVE_SETENV
+  setenv ("NLSPATH", new_val, 1);
+  free (new_val);
+# else
+  putenv (new_val);
+  /* Do *not* free the environment entry we just entered.  It is used
+     from now on.   */
+# endif
+
+#endif
+
+  return (char *) domainname;
+}
+
+#undef gettext
+char *
+gettext (msg)
+     const char *msg;
+{
+  int msgid;
+
+  if (msg == NULL || catalog == (nl_catd) -1)
+    return (char *) msg;
+
+  /* Get the message from the catalog.  We always use set number 1.
+     The message ID is computed by the function `msg_to_cat_id'
+     which works on the table generated by `po-to-tbl'.  */
+  msgid = msg_to_cat_id (msg);
+  if (msgid == -1)
+    return (char *) msg;
+
+  return catgets (catalog, 1, msgid, (char *) msg);
+}
+
+/* Look through the table `_msg_tbl' which has `_msg_tbl_length' entries
+   for the one equal to msg.  If it is found return the ID.  In case when
+   the string is not found return -1.  */
+static int
+msg_to_cat_id (msg)
+     const char *msg;
+{
+  int cnt;
+
+  for (cnt = 0; cnt < _msg_tbl_length; ++cnt)
+    if (strcmp (msg, _msg_tbl[cnt]._msg) == 0)
+      return _msg_tbl[cnt]._msg_number;
+
+  return -1;
+}
+
+
+/* @@ begin of epilog @@ */
+
+/* We don't want libintl.a to depend on any other library.  So we
+   avoid the non-standard function stpcpy.  In GNU C Library this
+   function is available, though.  Also allow the symbol HAVE_STPCPY
+   to be defined.  */
+#if !_LIBC && !HAVE_STPCPY
+static char *
+stpcpy (dest, src)
+     char *dest;
+     const char *src;
+{
+  while ((*dest++ = *src++) != '\0')
+    /* Do nothing. */ ;
+  return dest - 1;
+}
+#endif
diff --git a/intl/dcgettext.c b/intl/dcgettext.c
new file mode 100644 (file)
index 0000000..a316bfd
--- /dev/null
@@ -0,0 +1,593 @@
+/* Implementation of the dcgettext(3) function
+   Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <sys/types.h>
+
+#ifdef __GNUC__
+# define alloca __builtin_alloca
+# define HAVE_ALLOCA 1
+#else
+# if defined HAVE_ALLOCA_H || defined _LIBC
+#  include <alloca.h>
+# else
+#  ifdef _AIX
+ #pragma alloca
+#  else
+#   ifndef alloca
+char *alloca ();
+#   endif
+#  endif
+# endif
+#endif
+
+#include <errno.h>
+#ifndef errno
+extern int errno;
+#endif
+#ifndef __set_errno
+# define __set_errno(val) errno = (val)
+#endif
+
+#if defined STDC_HEADERS || defined _LIBC
+# include <stdlib.h>
+#else
+char *getenv ();
+# ifdef HAVE_MALLOC_H
+#  include <malloc.h>
+# else
+void free ();
+# endif
+#endif
+
+#if defined HAVE_STRING_H || defined _LIBC
+# ifndef _GNU_SOURCE
+#  define _GNU_SOURCE  1
+# endif
+# include <string.h>
+#else
+# include <strings.h>
+#endif
+#if !HAVE_STRCHR && !defined _LIBC
+# ifndef strchr
+#  define strchr index
+# endif
+#endif
+
+#if defined HAVE_UNISTD_H || defined _LIBC
+# include <unistd.h>
+#endif
+
+#include "gettext.h"
+#include "gettextP.h"
+#ifdef _LIBC
+# include <libintl.h>
+#else
+# include "libgettext.h"
+#endif
+#include "hash-string.h"
+
+/* @@ end of prolog @@ */
+
+#ifdef _LIBC
+/* Rename the non ANSI C functions.  This is required by the standard
+   because some ANSI C functions will require linking with this object
+   file and the name space must not be polluted.  */
+# define getcwd __getcwd
+# define stpcpy __stpcpy
+#else
+# if !defined HAVE_GETCWD
+char *getwd ();
+#  define getcwd(buf, max) getwd (buf)
+# else
+char *getcwd ();
+# endif
+# ifndef HAVE_STPCPY
+static char *stpcpy PARAMS ((char *dest, const char *src));
+# endif
+#endif
+
+/* Amount to increase buffer size by in each try.  */
+#define PATH_INCR 32
+
+/* The following is from pathmax.h.  */
+/* Non-POSIX BSD systems might have gcc's limits.h, which doesn't define
+   PATH_MAX but might cause redefinition warnings when sys/param.h is
+   later included (as on MORE/BSD 4.3).  */
+#if defined(_POSIX_VERSION) || (defined(HAVE_LIMITS_H) && !defined(__GNUC__))
+# include <limits.h>
+#endif
+
+#ifndef _POSIX_PATH_MAX
+# define _POSIX_PATH_MAX 255
+#endif
+
+#if !defined(PATH_MAX) && defined(_PC_PATH_MAX)
+# define PATH_MAX (pathconf ("/", _PC_PATH_MAX) < 1 ? 1024 : pathconf ("/", _PC_PATH_MAX))
+#endif
+
+/* Don't include sys/param.h if it already has been.  */
+#if defined(HAVE_SYS_PARAM_H) && !defined(PATH_MAX) && !defined(MAXPATHLEN)
+# include <sys/param.h>
+#endif
+
+#if !defined(PATH_MAX) && defined(MAXPATHLEN)
+# define PATH_MAX MAXPATHLEN
+#endif
+
+#ifndef PATH_MAX
+# define PATH_MAX _POSIX_PATH_MAX
+#endif
+
+/* XPG3 defines the result of `setlocale (category, NULL)' as:
+   ``Directs `setlocale()' to query `category' and return the current
+     setting of `local'.''
+   However it does not specify the exact format.  And even worse: POSIX
+   defines this not at all.  So we can use this feature only on selected
+   system (e.g. those using GNU C Library).  */
+#ifdef _LIBC
+# define HAVE_LOCALE_NULL
+#endif
+
+/* Name of the default domain used for gettext(3) prior any call to
+   textdomain(3).  The default value for this is "messages".  */
+const char _nl_default_default_domain[] = "messages";
+
+/* Value used as the default domain for gettext(3).  */
+const char *_nl_current_default_domain = _nl_default_default_domain;
+
+/* Contains the default location of the message catalogs.  */
+const char _nl_default_dirname[] = GNULOCALEDIR;
+
+/* List with bindings of specific domains created by bindtextdomain()
+   calls.  */
+struct binding *_nl_domain_bindings;
+
+/* Prototypes for local functions.  */
+static char *find_msg PARAMS ((struct loaded_l10nfile *domain_file,
+                              const char *msgid));
+static const char *category_to_name PARAMS ((int category));
+static const char *guess_category_value PARAMS ((int category,
+                                                const char *categoryname));
+
+
+/* For those loosing systems which don't have `alloca' we have to add
+   some additional code emulating it.  */
+#ifdef HAVE_ALLOCA
+/* Nothing has to be done.  */
+# define ADD_BLOCK(list, address) /* nothing */
+# define FREE_BLOCKS(list) /* nothing */
+#else
+struct block_list
+{
+  void *address;
+  struct block_list *next;
+};
+# define ADD_BLOCK(list, addr)                                               \
+  do {                                                                       \
+    struct block_list *newp = (struct block_list *) malloc (sizeof (*newp));  \
+    /* If we cannot get a free block we cannot add the new element to        \
+       the list.  */                                                         \
+    if (newp != NULL) {                                                              \
+      newp->address = (addr);                                                \
+      newp->next = (list);                                                   \
+      (list) = newp;                                                         \
+    }                                                                        \
+  } while (0)
+# define FREE_BLOCKS(list)                                                   \
+  do {                                                                       \
+    while (list != NULL) {                                                   \
+      struct block_list *old = list;                                         \
+      list = list->next;                                                     \
+      free (old);                                                            \
+    }                                                                        \
+  } while (0)
+# undef alloca
+# define alloca(size) (malloc (size))
+#endif /* have alloca */
+
+
+/* Names for the libintl functions are a problem.  They must not clash
+   with existing names and they should follow ANSI C.  But this source
+   code is also used in GNU C Library where the names have a __
+   prefix.  So we have to make a difference here.  */
+#ifdef _LIBC
+# define DCGETTEXT __dcgettext
+#else
+# define DCGETTEXT dcgettext__
+#endif
+
+/* Look up MSGID in the DOMAINNAME message catalog for the current CATEGORY
+   locale.  */
+char *
+DCGETTEXT (domainname, msgid, category)
+     const char *domainname;
+     const char *msgid;
+     int category;
+{
+#ifndef HAVE_ALLOCA
+  struct block_list *block_list = NULL;
+#endif
+  struct loaded_l10nfile *domain;
+  struct binding *binding;
+  const char *categoryname;
+  const char *categoryvalue;
+  char *dirname, *xdomainname;
+  char *single_locale;
+  char *retval;
+  int saved_errno = errno;
+
+  /* If no real MSGID is given return NULL.  */
+  if (msgid == NULL)
+    return NULL;
+
+  /* If DOMAINNAME is NULL, we are interested in the default domain.  If
+     CATEGORY is not LC_MESSAGES this might not make much sense but the
+     defintion left this undefined.  */
+  if (domainname == NULL)
+    domainname = _nl_current_default_domain;
+
+  /* First find matching binding.  */
+  for (binding = _nl_domain_bindings; binding != NULL; binding = binding->next)
+    {
+      int compare = strcmp (domainname, binding->domainname);
+      if (compare == 0)
+       /* We found it!  */
+       break;
+      if (compare < 0)
+       {
+         /* It is not in the list.  */
+         binding = NULL;
+         break;
+       }
+    }
+
+  if (binding == NULL)
+    dirname = (char *) _nl_default_dirname;
+  else if (binding->dirname[0] == '/')
+    dirname = binding->dirname;
+  else
+    {
+      /* We have a relative path.  Make it absolute now.  */
+      size_t dirname_len = strlen (binding->dirname) + 1;
+      size_t path_max;
+      char *ret;
+
+      path_max = (unsigned) PATH_MAX;
+      path_max += 2;           /* The getcwd docs say to do this.  */
+
+      dirname = (char *) alloca (path_max + dirname_len);
+      ADD_BLOCK (block_list, dirname);
+
+      __set_errno (0);
+      while ((ret = getcwd (dirname, path_max)) == NULL && errno == ERANGE)
+       {
+         path_max += PATH_INCR;
+         dirname = (char *) alloca (path_max + dirname_len);
+         ADD_BLOCK (block_list, dirname);
+         __set_errno (0);
+       }
+
+      if (ret == NULL)
+       {
+         /* We cannot get the current working directory.  Don't signal an
+            error but simply return the default string.  */
+         FREE_BLOCKS (block_list);
+         __set_errno (saved_errno);
+         return (char *) msgid;
+       }
+
+      stpcpy (stpcpy (strchr (dirname, '\0'), "/"), binding->dirname);
+    }
+
+  /* Now determine the symbolic name of CATEGORY and its value.  */
+  categoryname = category_to_name (category);
+  categoryvalue = guess_category_value (category, categoryname);
+
+  xdomainname = (char *) alloca (strlen (categoryname)
+                                + strlen (domainname) + 5);
+  ADD_BLOCK (block_list, xdomainname);
+
+  stpcpy (stpcpy (stpcpy (stpcpy (xdomainname, categoryname), "/"),
+                 domainname),
+         ".mo");
+
+  /* Creating working area.  */
+  single_locale = (char *) alloca (strlen (categoryvalue) + 1);
+  ADD_BLOCK (block_list, single_locale);
+
+
+  /* Search for the given string.  This is a loop because we perhaps
+     got an ordered list of languages to consider for th translation.  */
+  while (1)
+    {
+      /* Make CATEGORYVALUE point to the next element of the list.  */
+      while (categoryvalue[0] != '\0' && categoryvalue[0] == ':')
+       ++categoryvalue;
+      if (categoryvalue[0] == '\0')
+       {
+         /* The whole contents of CATEGORYVALUE has been searched but
+            no valid entry has been found.  We solve this situation
+            by implicitly appending a "C" entry, i.e. no translation
+            will take place.  */
+         single_locale[0] = 'C';
+         single_locale[1] = '\0';
+       }
+      else
+       {
+         char *cp = single_locale;
+         while (categoryvalue[0] != '\0' && categoryvalue[0] != ':')
+           *cp++ = *categoryvalue++;
+         *cp = '\0';
+       }
+
+      /* If the current locale value is C (or POSIX) we don't load a
+        domain.  Return the MSGID.  */
+      if (strcmp (single_locale, "C") == 0
+         || strcmp (single_locale, "POSIX") == 0)
+       {
+         FREE_BLOCKS (block_list);
+         __set_errno (saved_errno);
+         return (char *) msgid;
+       }
+
+
+      /* Find structure describing the message catalog matching the
+        DOMAINNAME and CATEGORY.  */
+      domain = _nl_find_domain (dirname, single_locale, xdomainname);
+
+      if (domain != NULL)
+       {
+         retval = find_msg (domain, msgid);
+
+         if (retval == NULL)
+           {
+             int cnt;
+
+             for (cnt = 0; domain->successor[cnt] != NULL; ++cnt)
+               {
+                 retval = find_msg (domain->successor[cnt], msgid);
+
+                 if (retval != NULL)
+                   break;
+               }
+           }
+
+         if (retval != NULL)
+           {
+             FREE_BLOCKS (block_list);
+             __set_errno (saved_errno);
+             return retval;
+           }
+       }
+    }
+  /* NOTREACHED */
+}
+
+#ifdef _LIBC
+/* Alias for function name in GNU C Library.  */
+weak_alias (__dcgettext, dcgettext);
+#endif
+
+
+static char *
+find_msg (domain_file, msgid)
+     struct loaded_l10nfile *domain_file;
+     const char *msgid;
+{
+  size_t top, act, bottom;
+  struct loaded_domain *domain;
+
+  if (domain_file->decided == 0)
+    _nl_load_domain (domain_file);
+
+  if (domain_file->data == NULL)
+    return NULL;
+
+  domain = (struct loaded_domain *) domain_file->data;
+
+  /* Locate the MSGID and its translation.  */
+  if (domain->hash_size > 2 && domain->hash_tab != NULL)
+    {
+      /* Use the hashing table.  */
+      nls_uint32 len = strlen (msgid);
+      nls_uint32 hash_val = hash_string (msgid);
+      nls_uint32 idx = hash_val % domain->hash_size;
+      nls_uint32 incr = 1 + (hash_val % (domain->hash_size - 2));
+      nls_uint32 nstr = W (domain->must_swap, domain->hash_tab[idx]);
+
+      if (nstr == 0)
+       /* Hash table entry is empty.  */
+       return NULL;
+
+      if (W (domain->must_swap, domain->orig_tab[nstr - 1].length) == len
+         && strcmp (msgid,
+                    domain->data + W (domain->must_swap,
+                                      domain->orig_tab[nstr - 1].offset)) == 0)
+       return (char *) domain->data + W (domain->must_swap,
+                                         domain->trans_tab[nstr - 1].offset);
+
+      while (1)
+       {
+         if (idx >= domain->hash_size - incr)
+           idx -= domain->hash_size - incr;
+         else
+           idx += incr;
+
+         nstr = W (domain->must_swap, domain->hash_tab[idx]);
+         if (nstr == 0)
+           /* Hash table entry is empty.  */
+           return NULL;
+
+         if (W (domain->must_swap, domain->orig_tab[nstr - 1].length) == len
+             && strcmp (msgid,
+                        domain->data + W (domain->must_swap,
+                                          domain->orig_tab[nstr - 1].offset))
+                == 0)
+           return (char *) domain->data
+             + W (domain->must_swap, domain->trans_tab[nstr - 1].offset);
+       }
+      /* NOTREACHED */
+    }
+
+  /* Now we try the default method:  binary search in the sorted
+     array of messages.  */
+  bottom = 0;
+  top = domain->nstrings;
+  while (bottom < top)
+    {
+      int cmp_val;
+
+      act = (bottom + top) / 2;
+      cmp_val = strcmp (msgid, domain->data
+                              + W (domain->must_swap,
+                                   domain->orig_tab[act].offset));
+      if (cmp_val < 0)
+       top = act;
+      else if (cmp_val > 0)
+       bottom = act + 1;
+      else
+       break;
+    }
+
+  /* If an translation is found return this.  */
+  return bottom >= top ? NULL : (char *) domain->data
+                                + W (domain->must_swap,
+                                    domain->trans_tab[act].offset);
+}
+
+
+/* Return string representation of locale CATEGORY.  */
+static const char *
+category_to_name (category)
+     int category;
+{
+  const char *retval;
+
+  switch (category)
+  {
+#ifdef LC_COLLATE
+  case LC_COLLATE:
+    retval = "LC_COLLATE";
+    break;
+#endif
+#ifdef LC_CTYPE
+  case LC_CTYPE:
+    retval = "LC_CTYPE";
+    break;
+#endif
+#ifdef LC_MONETARY
+  case LC_MONETARY:
+    retval = "LC_MONETARY";
+    break;
+#endif
+#ifdef LC_NUMERIC
+  case LC_NUMERIC:
+    retval = "LC_NUMERIC";
+    break;
+#endif
+#ifdef LC_TIME
+  case LC_TIME:
+    retval = "LC_TIME";
+    break;
+#endif
+#ifdef LC_MESSAGES
+  case LC_MESSAGES:
+    retval = "LC_MESSAGES";
+    break;
+#endif
+#ifdef LC_RESPONSE
+  case LC_RESPONSE:
+    retval = "LC_RESPONSE";
+    break;
+#endif
+#ifdef LC_ALL
+  case LC_ALL:
+    /* This might not make sense but is perhaps better than any other
+       value.  */
+    retval = "LC_ALL";
+    break;
+#endif
+  default:
+    /* If you have a better idea for a default value let me know.  */
+    retval = "LC_XXX";
+  }
+
+  return retval;
+}
+
+/* Guess value of current locale from value of the environment variables.  */
+static const char *
+guess_category_value (category, categoryname)
+     int category;
+     const char *categoryname;
+{
+  const char *retval;
+
+  /* The highest priority value is the `LANGUAGE' environment
+     variable.  This is a GNU extension.  */
+  retval = getenv ("LANGUAGE");
+  if (retval != NULL && retval[0] != '\0')
+    return retval;
+
+  /* `LANGUAGE' is not set.  So we have to proceed with the POSIX
+     methods of looking to `LC_ALL', `LC_xxx', and `LANG'.  On some
+     systems this can be done by the `setlocale' function itself.  */
+#if defined HAVE_SETLOCALE && defined HAVE_LC_MESSAGES && defined HAVE_LOCALE_NULL
+  return setlocale (category, NULL);
+#else
+  /* Setting of LC_ALL overwrites all other.  */
+  retval = getenv ("LC_ALL");
+  if (retval != NULL && retval[0] != '\0')
+    return retval;
+
+  /* Next comes the name of the desired category.  */
+  retval = getenv (categoryname);
+  if (retval != NULL && retval[0] != '\0')
+    return retval;
+
+  /* Last possibility is the LANG environment variable.  */
+  retval = getenv ("LANG");
+  if (retval != NULL && retval[0] != '\0')
+    return retval;
+
+  /* We use C as the default domain.  POSIX says this is implementation
+     defined.  */
+  return "C";
+#endif
+}
+
+/* @@ begin of epilog @@ */
+
+/* We don't want libintl.a to depend on any other library.  So we
+   avoid the non-standard function stpcpy.  In GNU C Library this
+   function is available, though.  Also allow the symbol HAVE_STPCPY
+   to be defined.  */
+#if !_LIBC && !HAVE_STPCPY
+static char *
+stpcpy (dest, src)
+     char *dest;
+     const char *src;
+{
+  while ((*dest++ = *src++) != '\0')
+    /* Do nothing. */ ;
+  return dest - 1;
+}
+#endif
diff --git a/intl/dgettext.c b/intl/dgettext.c
new file mode 100644 (file)
index 0000000..2fde677
--- /dev/null
@@ -0,0 +1,59 @@
+/* dgettext.c -- implementation of the dgettext(3) function
+   Copyright (C) 1995 Software Foundation, Inc.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if defined HAVE_LOCALE_H || defined _LIBC
+# include <locale.h>
+#endif
+
+#ifdef _LIBC
+# include <libintl.h>
+#else
+# include "libgettext.h"
+#endif
+
+/* @@ end of prolog @@ */
+
+/* Names for the libintl functions are a problem.  They must not clash
+   with existing names and they should follow ANSI C.  But this source
+   code is also used in GNU C Library where the names have a __
+   prefix.  So we have to make a difference here.  */
+#ifdef _LIBC
+# define DGETTEXT __dgettext
+# define DCGETTEXT __dcgettext
+#else
+# define DGETTEXT dgettext__
+# define DCGETTEXT dcgettext__
+#endif
+
+/* Look up MSGID in the DOMAINNAME message catalog of the current
+   LC_MESSAGES locale.  */
+char *
+DGETTEXT (domainname, msgid)
+     const char *domainname;
+     const char *msgid;
+{
+  return DCGETTEXT (domainname, msgid, LC_MESSAGES);
+}
+
+#ifdef _LIBC
+/* Alias for function name in GNU C Library.  */
+weak_alias (__dgettext, dgettext);
+#endif
diff --git a/intl/explodename.c b/intl/explodename.c
new file mode 100644 (file)
index 0000000..e45b2a2
--- /dev/null
@@ -0,0 +1,181 @@
+/* Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+   Contributed by Ulrich Drepper <drepper@gnu.org>, 1995.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "loadinfo.h"
+
+/* On some strange systems still no definition of NULL is found.  Sigh!  */
+#ifndef NULL
+# if defined __STDC__ && __STDC__
+#  define NULL ((void *) 0)
+# else
+#  define NULL 0
+# endif
+#endif
+
+/* @@ end of prolog @@ */
+
+int
+_nl_explode_name (name, language, modifier, territory, codeset,
+                 normalized_codeset, special, sponsor, revision)
+     char *name;
+     const char **language;
+     const char **modifier;
+     const char **territory;
+     const char **codeset;
+     const char **normalized_codeset;
+     const char **special;
+     const char **sponsor;
+     const char **revision;
+{
+  enum { undecided, xpg, cen } syntax;
+  char *cp;
+  int mask;
+
+  *modifier = NULL;
+  *territory = NULL;
+  *codeset = NULL;
+  *normalized_codeset = NULL;
+  *special = NULL;
+  *sponsor = NULL;
+  *revision = NULL;
+
+  /* Now we determine the single parts of the locale name.  First
+     look for the language.  Termination symbols are `_' and `@' if
+     we use XPG4 style, and `_', `+', and `,' if we use CEN syntax.  */
+  mask = 0;
+  syntax = undecided;
+  *language = cp = name;
+  while (cp[0] != '\0' && cp[0] != '_' && cp[0] != '@'
+        && cp[0] != '+' && cp[0] != ',')
+    ++cp;
+
+  if (*language == cp)
+    /* This does not make sense: language has to be specified.  Use
+       this entry as it is without exploding.  Perhaps it is an alias.  */
+    cp = strchr (*language, '\0');
+  else if (cp[0] == '_')
+    {
+      /* Next is the territory.  */
+      cp[0] = '\0';
+      *territory = ++cp;
+
+      while (cp[0] != '\0' && cp[0] != '.' && cp[0] != '@'
+            && cp[0] != '+' && cp[0] != ',' && cp[0] != '_')
+       ++cp;
+
+      mask |= TERRITORY;
+
+      if (cp[0] == '.')
+       {
+         /* Next is the codeset.  */
+         syntax = xpg;
+         cp[0] = '\0';
+         *codeset = ++cp;
+
+         while (cp[0] != '\0' && cp[0] != '@')
+           ++cp;
+
+         mask |= XPG_CODESET;
+
+         if (*codeset != cp && (*codeset)[0] != '\0')
+           {
+             *normalized_codeset = _nl_normalize_codeset (*codeset,
+                                                          cp - *codeset);
+             if (strcmp (*codeset, *normalized_codeset) == 0)
+               free ((char *) *normalized_codeset);
+             else
+               mask |= XPG_NORM_CODESET;
+           }
+       }
+    }
+
+  if (cp[0] == '@' || (syntax != xpg && cp[0] == '+'))
+    {
+      /* Next is the modifier.  */
+      syntax = cp[0] == '@' ? xpg : cen;
+      cp[0] = '\0';
+      *modifier = ++cp;
+
+      while (syntax == cen && cp[0] != '\0' && cp[0] != '+'
+            && cp[0] != ',' && cp[0] != '_')
+       ++cp;
+
+      mask |= XPG_MODIFIER | CEN_AUDIENCE;
+    }
+
+  if (syntax != xpg && (cp[0] == '+' || cp[0] == ',' || cp[0] == '_'))
+    {
+      syntax = cen;
+
+      if (cp[0] == '+')
+       {
+         /* Next is special application (CEN syntax).  */
+         cp[0] = '\0';
+         *special = ++cp;
+
+         while (cp[0] != '\0' && cp[0] != ',' && cp[0] != '_')
+           ++cp;
+
+         mask |= CEN_SPECIAL;
+       }
+
+      if (cp[0] == ',')
+       {
+         /* Next is sponsor (CEN syntax).  */
+         cp[0] = '\0';
+         *sponsor = ++cp;
+
+         while (cp[0] != '\0' && cp[0] != '_')
+           ++cp;
+
+         mask |= CEN_SPONSOR;
+       }
+
+      if (cp[0] == '_')
+       {
+         /* Next is revision (CEN syntax).  */
+         cp[0] = '\0';
+         *revision = ++cp;
+
+         mask |= CEN_REVISION;
+       }
+    }
+
+  /* For CEN syntax values it might be important to have the
+     separator character in the file name, not for XPG syntax.  */
+  if (syntax == xpg)
+    {
+      if (*territory != NULL && (*territory)[0] == '\0')
+       mask &= ~TERRITORY;
+
+      if (*codeset != NULL && (*codeset)[0] == '\0')
+       mask &= ~XPG_CODESET;
+
+      if (*modifier != NULL && (*modifier)[0] == '\0')
+       mask &= ~XPG_MODIFIER;
+    }
+
+  return mask;
+}
diff --git a/intl/finddomain.c b/intl/finddomain.c
new file mode 100644 (file)
index 0000000..fd27f6f
--- /dev/null
@@ -0,0 +1,189 @@
+/* Handle list of needed message catalogs
+   Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+   Written by Ulrich Drepper <drepper@gnu.org>, 1995.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#if defined STDC_HEADERS || defined _LIBC
+# include <stdlib.h>
+#else
+# ifdef HAVE_MALLOC_H
+#  include <malloc.h>
+# else
+void free ();
+# endif
+#endif
+
+#if defined HAVE_STRING_H || defined _LIBC
+# include <string.h>
+#else
+# include <strings.h>
+# ifndef memcpy
+#  define memcpy(Dst, Src, Num) bcopy (Src, Dst, Num)
+# endif
+#endif
+#if !HAVE_STRCHR && !defined _LIBC
+# ifndef strchr
+#  define strchr index
+# endif
+#endif
+
+#if defined HAVE_UNISTD_H || defined _LIBC
+# include <unistd.h>
+#endif
+
+#include "gettext.h"
+#include "gettextP.h"
+#ifdef _LIBC
+# include <libintl.h>
+#else
+# include "libgettext.h"
+#endif
+
+/* @@ end of prolog @@ */
+/* List of already loaded domains.  */
+static struct loaded_l10nfile *_nl_loaded_domains;
+
+
+/* Return a data structure describing the message catalog described by
+   the DOMAINNAME and CATEGORY parameters with respect to the currently
+   established bindings.  */
+struct loaded_l10nfile *
+_nl_find_domain (dirname, locale, domainname)
+     const char *dirname;
+     char *locale;
+     const char *domainname;
+{
+  struct loaded_l10nfile *retval;
+  const char *language;
+  const char *modifier;
+  const char *territory;
+  const char *codeset;
+  const char *normalized_codeset;
+  const char *special;
+  const char *sponsor;
+  const char *revision;
+  const char *alias_value;
+  int mask;
+
+  /* LOCALE can consist of up to four recognized parts for the XPG syntax:
+
+               language[_territory[.codeset]][@modifier]
+
+     and six parts for the CEN syntax:
+
+       language[_territory][+audience][+special][,[sponsor][_revision]]
+
+     Beside the first all of them are allowed to be missing.  If the
+     full specified locale is not found, the less specific one are
+     looked for.  The various part will be stripped of according to
+     the following order:
+               (1) revision
+               (2) sponsor
+               (3) special
+               (4) codeset
+               (5) normalized codeset
+               (6) territory
+               (7) audience/modifier
+   */
+
+  /* If we have already tested for this locale entry there has to
+     be one data set in the list of loaded domains.  */
+  retval = _nl_make_l10nflist (&_nl_loaded_domains, dirname,
+                              strlen (dirname) + 1, 0, locale, NULL, NULL,
+                              NULL, NULL, NULL, NULL, NULL, domainname, 0);
+  if (retval != NULL)
+    {
+      /* We know something about this locale.  */
+      int cnt;
+
+      if (retval->decided == 0)
+       _nl_load_domain (retval);
+
+      if (retval->data != NULL)
+       return retval;
+
+      for (cnt = 0; retval->successor[cnt] != NULL; ++cnt)
+       {
+         if (retval->successor[cnt]->decided == 0)
+           _nl_load_domain (retval->successor[cnt]);
+
+         if (retval->successor[cnt]->data != NULL)
+           break;
+       }
+      return cnt >= 0 ? retval : NULL;
+      /* NOTREACHED */
+    }
+
+  /* See whether the locale value is an alias.  If yes its value
+     *overwrites* the alias name.  No test for the original value is
+     done.  */
+  alias_value = _nl_expand_alias (locale);
+  if (alias_value != NULL)
+    {
+      size_t len = strlen (alias_value) + 1;
+      locale = (char *) malloc (len);
+      if (locale == NULL)
+       return NULL;
+
+      memcpy (locale, alias_value, len);
+    }
+
+  /* Now we determine the single parts of the locale name.  First
+     look for the language.  Termination symbols are `_' and `@' if
+     we use XPG4 style, and `_', `+', and `,' if we use CEN syntax.  */
+  mask = _nl_explode_name (locale, &language, &modifier, &territory,
+                          &codeset, &normalized_codeset, &special,
+                          &sponsor, &revision);
+
+  /* Create all possible locale entries which might be interested in
+     generalization.  */
+  retval = _nl_make_l10nflist (&_nl_loaded_domains, dirname,
+                              strlen (dirname) + 1, mask, language, territory,
+                              codeset, normalized_codeset, modifier, special,
+                              sponsor, revision, domainname, 1);
+  if (retval == NULL)
+    /* This means we are out of core.  */
+    return NULL;
+
+  if (retval->decided == 0)
+    _nl_load_domain (retval);
+  if (retval->data == NULL)
+    {
+      int cnt;
+      for (cnt = 0; retval->successor[cnt] != NULL; ++cnt)
+       {
+         if (retval->successor[cnt]->decided == 0)
+           _nl_load_domain (retval->successor[cnt]);
+         if (retval->successor[cnt]->data != NULL)
+           break;
+       }
+    }
+
+  /* The room for an alias was dynamically allocated.  Free it now.  */
+  if (alias_value != NULL)
+    free (locale);
+
+  return retval;
+}
diff --git a/intl/gettext.c b/intl/gettext.c
new file mode 100644 (file)
index 0000000..1336d21
--- /dev/null
@@ -0,0 +1,70 @@
+/* Implementation of gettext(3) function
+   Copyright (C) 1995, 1997 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef _LIBC
+# define __need_NULL
+# include <stddef.h>
+#else
+# ifdef STDC_HEADERS
+#  include <stdlib.h>          /* Just for NULL.  */
+# else
+#  ifdef HAVE_STRING_H
+#   include <string.h>
+#  else
+#   define NULL ((void *) 0)
+#  endif
+# endif
+#endif
+
+#ifdef _LIBC
+# include <libintl.h>
+#else
+# include "libgettext.h"
+#endif
+
+/* @@ end of prolog @@ */
+
+/* Names for the libintl functions are a problem.  They must not clash
+   with existing names and they should follow ANSI C.  But this source
+   code is also used in GNU C Library where the names have a __
+   prefix.  So we have to make a difference here.  */
+#ifdef _LIBC
+# define GETTEXT __gettext
+# define DGETTEXT __dgettext
+#else
+# define GETTEXT gettext__
+# define DGETTEXT dgettext__
+#endif
+
+/* Look up MSGID in the current default message catalog for the current
+   LC_MESSAGES locale.  If not found, returns MSGID itself (the default
+   text).  */
+char *
+GETTEXT (msgid)
+     const char *msgid;
+{
+  return DGETTEXT (NULL, msgid);
+}
+
+#ifdef _LIBC
+/* Alias for function name in GNU C Library.  */
+weak_alias (__gettext, gettext);
+#endif
diff --git a/intl/gettext.h b/intl/gettext.h
new file mode 100644 (file)
index 0000000..6b4b9e3
--- /dev/null
@@ -0,0 +1,105 @@
+/* Internal header for GNU gettext internationalization functions
+   Copyright (C) 1995, 1997 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#ifndef _GETTEXT_H
+#define _GETTEXT_H 1
+
+#include <stdio.h>
+
+#if HAVE_LIMITS_H || _LIBC
+# include <limits.h>
+#endif
+
+/* @@ end of prolog @@ */
+
+/* The magic number of the GNU message catalog format.  */
+#define _MAGIC 0x950412de
+#define _MAGIC_SWAPPED 0xde120495
+
+/* Revision number of the currently used .mo (binary) file format.  */
+#define MO_REVISION_NUMBER 0
+
+/* The following contortions are an attempt to use the C preprocessor
+   to determine an unsigned integral type that is 32 bits wide.  An
+   alternative approach is to use autoconf's AC_CHECK_SIZEOF macro, but
+   doing that would require that the configure script compile and *run*
+   the resulting executable.  Locally running cross-compiled executables
+   is usually not possible.  */
+
+#if __STDC__
+# define UINT_MAX_32_BITS 4294967295U
+#else
+# define UINT_MAX_32_BITS 0xFFFFFFFF
+#endif
+
+/* If UINT_MAX isn't defined, assume it's a 32-bit type.
+   This should be valid for all systems GNU cares about because
+   that doesn't include 16-bit systems, and only modern systems
+   (that certainly have <limits.h>) have 64+-bit integral types.  */
+
+#ifndef UINT_MAX
+# define UINT_MAX UINT_MAX_32_BITS
+#endif
+
+#if UINT_MAX == UINT_MAX_32_BITS
+typedef unsigned nls_uint32;
+#else
+# if USHRT_MAX == UINT_MAX_32_BITS
+typedef unsigned short nls_uint32;
+# else
+#  if ULONG_MAX == UINT_MAX_32_BITS
+typedef unsigned long nls_uint32;
+#  else
+  /* The following line is intended to throw an error.  Using #error is
+     not portable enough.  */
+  "Cannot determine unsigned 32-bit data type."
+#  endif
+# endif
+#endif
+
+
+/* Header for binary .mo file format.  */
+struct mo_file_header
+{
+  /* The magic number.  */
+  nls_uint32 magic;
+  /* The revision number of the file format.  */
+  nls_uint32 revision;
+  /* The number of strings pairs.  */
+  nls_uint32 nstrings;
+  /* Offset of table with start offsets of original strings.  */
+  nls_uint32 orig_tab_offset;
+  /* Offset of table with start offsets of translation strings.  */
+  nls_uint32 trans_tab_offset;
+  /* Size of hashing table.  */
+  nls_uint32 hash_tab_size;
+  /* Offset of first hashing entry.  */
+  nls_uint32 hash_tab_offset;
+};
+
+struct string_desc
+{
+  /* Length of addressed string.  */
+  nls_uint32 length;
+  /* Offset of string in file.  */
+  nls_uint32 offset;
+};
+
+/* @@ begin of epilog @@ */
+
+#endif /* gettext.h  */
diff --git a/intl/gettextP.h b/intl/gettextP.h
new file mode 100644 (file)
index 0000000..bb8d552
--- /dev/null
@@ -0,0 +1,73 @@
+/* Header describing internals of gettext library
+   Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifndef _GETTEXTP_H
+#define _GETTEXTP_H
+
+#include "loadinfo.h"
+
+/* @@ end of prolog @@ */
+
+#ifndef PARAMS
+# if __STDC__
+#  define PARAMS(args) args
+# else
+#  define PARAMS(args) ()
+# endif
+#endif
+
+#ifndef W
+# define W(flag, data) ((flag) ? SWAP (data) : (data))
+#endif
+
+
+static nls_uint32 SWAP PARAMS ((nls_uint32 i));
+
+static inline nls_uint32
+SWAP (i)
+     nls_uint32 i;
+{
+  return (i << 24) | ((i & 0xff00) << 8) | ((i >> 8) & 0xff00) | (i >> 24);
+}
+
+
+struct loaded_domain
+{
+  const char *data;
+  int must_swap;
+  nls_uint32 nstrings;
+  struct string_desc *orig_tab;
+  struct string_desc *trans_tab;
+  nls_uint32 hash_size;
+  nls_uint32 *hash_tab;
+};
+
+struct binding
+{
+  struct binding *next;
+  char *domainname;
+  char *dirname;
+};
+
+struct loaded_l10nfile *_nl_find_domain PARAMS ((const char *__dirname,
+                                                char *__locale,
+                                                const char *__domainname));
+void _nl_load_domain PARAMS ((struct loaded_l10nfile *__domain));
+
+/* @@ begin of epilog @@ */
+
+#endif /* gettextP.h  */
diff --git a/intl/hash-string.h b/intl/hash-string.h
new file mode 100644 (file)
index 0000000..e66e841
--- /dev/null
@@ -0,0 +1,63 @@
+/* Implements a string hashing function.
+   Copyright (C) 1995, 1997 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_VALUES_H
+# include <values.h>
+#endif
+
+/* @@ end of prolog @@ */
+
+#ifndef PARAMS
+# if __STDC__
+#  define PARAMS(Args) Args
+# else
+#  define PARAMS(Args) ()
+# endif
+#endif
+
+/* We assume to have `unsigned long int' value with at least 32 bits.  */
+#define HASHWORDBITS 32
+
+
+/* Defines the so called `hashpjw' function by P.J. Weinberger
+   [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
+   1986, 1987 Bell Telephone Laboratories, Inc.]  */
+static unsigned long hash_string PARAMS ((const char *__str_param));
+
+static inline unsigned long
+hash_string (str_param)
+     const char *str_param;
+{
+  unsigned long int hval, g;
+  const char *str = str_param;
+
+  /* Compute the hash value for the given string.  */
+  hval = 0;
+  while (*str != '\0')
+    {
+      hval <<= 4;
+      hval += (unsigned long) *str++;
+      g = hval & ((unsigned long) 0xf << (HASHWORDBITS - 4));
+      if (g != 0)
+       {
+         hval ^= g >> (HASHWORDBITS - 8);
+         hval ^= g;
+       }
+    }
+  return hval;
+}
diff --git a/intl/intl-compat.c b/intl/intl-compat.c
new file mode 100644 (file)
index 0000000..503efa0
--- /dev/null
@@ -0,0 +1,76 @@
+/* intl-compat.c - Stub functions to call gettext functions from GNU gettext
+   Library.
+   Copyright (C) 1995 Software Foundation, Inc.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "libgettext.h"
+
+/* @@ end of prolog @@ */
+
+
+#undef gettext
+#undef dgettext
+#undef dcgettext
+#undef textdomain
+#undef bindtextdomain
+
+
+char *
+bindtextdomain (domainname, dirname)
+     const char *domainname;
+     const char *dirname;
+{
+  return bindtextdomain__ (domainname, dirname);
+}
+
+
+char *
+dcgettext (domainname, msgid, category)
+     const char *domainname;
+     const char *msgid;
+     int category;
+{
+  return dcgettext__ (domainname, msgid, category);
+}
+
+
+char *
+dgettext (domainname, msgid)
+     const char *domainname;
+     const char *msgid;
+{
+  return dgettext__ (domainname, msgid);
+}
+
+
+char *
+gettext (msgid)
+     const char *msgid;
+{
+  return gettext__ (msgid);
+}
+
+
+char *
+textdomain (domainname)
+     const char *domainname;
+{
+  return textdomain__ (domainname);
+}
diff --git a/intl/l10nflist.c b/intl/l10nflist.c
new file mode 100644 (file)
index 0000000..1b1da1f
--- /dev/null
@@ -0,0 +1,409 @@
+/* Handle list of needed message catalogs
+   Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+   Written by Ulrich Drepper <drepper@gnu.org>, 1995.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+
+#if defined HAVE_STRING_H || defined _LIBC
+# ifndef _GNU_SOURCE
+#  define _GNU_SOURCE  1
+# endif
+# include <string.h>
+#else
+# include <strings.h>
+# ifndef memcpy
+#  define memcpy(Dst, Src, Num) bcopy (Src, Dst, Num)
+# endif
+#endif
+#if !HAVE_STRCHR && !defined _LIBC
+# ifndef strchr
+#  define strchr index
+# endif
+#endif
+
+#if defined _LIBC || defined HAVE_ARGZ_H
+# include <argz.h>
+#endif
+#include <ctype.h>
+#include <sys/types.h>
+
+#if defined STDC_HEADERS || defined _LIBC
+# include <stdlib.h>
+#endif
+
+#include "loadinfo.h"
+
+/* On some strange systems still no definition of NULL is found.  Sigh!  */
+#ifndef NULL
+# if defined __STDC__ && __STDC__
+#  define NULL ((void *) 0)
+# else
+#  define NULL 0
+# endif
+#endif
+
+/* @@ end of prolog @@ */
+
+#ifdef _LIBC
+/* Rename the non ANSI C functions.  This is required by the standard
+   because some ANSI C functions will require linking with this object
+   file and the name space must not be polluted.  */
+# define stpcpy(dest, src) __stpcpy(dest, src)
+#else
+# ifndef HAVE_STPCPY
+static char *stpcpy PARAMS ((char *dest, const char *src));
+# endif
+#endif
+
+/* Define function which are usually not available.  */
+
+#if !defined _LIBC && !defined HAVE___ARGZ_COUNT
+/* Returns the number of strings in ARGZ.  */
+static size_t argz_count__ PARAMS ((const char *argz, size_t len));
+
+static size_t
+argz_count__ (argz, len)
+     const char *argz;
+     size_t len;
+{
+  size_t count = 0;
+  while (len > 0)
+    {
+      size_t part_len = strlen (argz);
+      argz += part_len + 1;
+      len -= part_len + 1;
+      count++;
+    }
+  return count;
+}
+# undef __argz_count
+# define __argz_count(argz, len) argz_count__ (argz, len)
+#endif /* !_LIBC && !HAVE___ARGZ_COUNT */
+
+#if !defined _LIBC && !defined HAVE___ARGZ_STRINGIFY
+/* Make '\0' separated arg vector ARGZ printable by converting all the '\0's
+   except the last into the character SEP.  */
+static void argz_stringify__ PARAMS ((char *argz, size_t len, int sep));
+
+static void
+argz_stringify__ (argz, len, sep)
+     char *argz;
+     size_t len;
+     int sep;
+{
+  while (len > 0)
+    {
+      size_t part_len = strlen (argz);
+      argz += part_len;
+      len -= part_len + 1;
+      if (len > 0)
+       *argz++ = sep;
+    }
+}
+# undef __argz_stringify
+# define __argz_stringify(argz, len, sep) argz_stringify__ (argz, len, sep)
+#endif /* !_LIBC && !HAVE___ARGZ_STRINGIFY */
+
+#if !defined _LIBC && !defined HAVE___ARGZ_NEXT
+static char *argz_next__ PARAMS ((char *argz, size_t argz_len,
+                                 const char *entry));
+
+static char *
+argz_next__ (argz, argz_len, entry)
+     char *argz;
+     size_t argz_len;
+     const char *entry;
+{
+  if (entry)
+    {
+      if (entry < argz + argz_len)
+        entry = strchr (entry, '\0') + 1;
+
+      return entry >= argz + argz_len ? NULL : (char *) entry;
+    }
+  else
+    if (argz_len > 0)
+      return argz;
+    else
+      return 0;
+}
+# undef __argz_next
+# define __argz_next(argz, len, entry) argz_next__ (argz, len, entry)
+#endif /* !_LIBC && !HAVE___ARGZ_NEXT */
+
+
+/* Return number of bits set in X.  */
+static int pop PARAMS ((int x));
+
+static inline int
+pop (x)
+     int x;
+{
+  /* We assume that no more than 16 bits are used.  */
+  x = ((x & ~0x5555) >> 1) + (x & 0x5555);
+  x = ((x & ~0x3333) >> 2) + (x & 0x3333);
+  x = ((x >> 4) + x) & 0x0f0f;
+  x = ((x >> 8) + x) & 0xff;
+
+  return x;
+}
+
+\f
+struct loaded_l10nfile *
+_nl_make_l10nflist (l10nfile_list, dirlist, dirlist_len, mask, language,
+                   territory, codeset, normalized_codeset, modifier, special,
+                   sponsor, revision, filename, do_allocate)
+     struct loaded_l10nfile **l10nfile_list;
+     const char *dirlist;
+     size_t dirlist_len;
+     int mask;
+     const char *language;
+     const char *territory;
+     const char *codeset;
+     const char *normalized_codeset;
+     const char *modifier;
+     const char *special;
+     const char *sponsor;
+     const char *revision;
+     const char *filename;
+     int do_allocate;
+{
+  char *abs_filename;
+  struct loaded_l10nfile *last = NULL;
+  struct loaded_l10nfile *retval;
+  char *cp;
+  size_t entries;
+  int cnt;
+
+  /* Allocate room for the full file name.  */
+  abs_filename = (char *) malloc (dirlist_len
+                                 + strlen (language)
+                                 + ((mask & TERRITORY) != 0
+                                    ? strlen (territory) + 1 : 0)
+                                 + ((mask & XPG_CODESET) != 0
+                                    ? strlen (codeset) + 1 : 0)
+                                 + ((mask & XPG_NORM_CODESET) != 0
+                                    ? strlen (normalized_codeset) + 1 : 0)
+                                 + (((mask & XPG_MODIFIER) != 0
+                                     || (mask & CEN_AUDIENCE) != 0)
+                                    ? strlen (modifier) + 1 : 0)
+                                 + ((mask & CEN_SPECIAL) != 0
+                                    ? strlen (special) + 1 : 0)
+                                 + (((mask & CEN_SPONSOR) != 0
+                                     || (mask & CEN_REVISION) != 0)
+                                    ? (1 + ((mask & CEN_SPONSOR) != 0
+                                            ? strlen (sponsor) + 1 : 0)
+                                       + ((mask & CEN_REVISION) != 0
+                                          ? strlen (revision) + 1 : 0)) : 0)
+                                 + 1 + strlen (filename) + 1);
+
+  if (abs_filename == NULL)
+    return NULL;
+
+  retval = NULL;
+  last = NULL;
+
+  /* Construct file name.  */
+  memcpy (abs_filename, dirlist, dirlist_len);
+  __argz_stringify (abs_filename, dirlist_len, ':');
+  cp = abs_filename + (dirlist_len - 1);
+  *cp++ = '/';
+  cp = stpcpy (cp, language);
+
+  if ((mask & TERRITORY) != 0)
+    {
+      *cp++ = '_';
+      cp = stpcpy (cp, territory);
+    }
+  if ((mask & XPG_CODESET) != 0)
+    {
+      *cp++ = '.';
+      cp = stpcpy (cp, codeset);
+    }
+  if ((mask & XPG_NORM_CODESET) != 0)
+    {
+      *cp++ = '.';
+      cp = stpcpy (cp, normalized_codeset);
+    }
+  if ((mask & (XPG_MODIFIER | CEN_AUDIENCE)) != 0)
+    {
+      /* This component can be part of both syntaces but has different
+        leading characters.  For CEN we use `+', else `@'.  */
+      *cp++ = (mask & CEN_AUDIENCE) != 0 ? '+' : '@';
+      cp = stpcpy (cp, modifier);
+    }
+  if ((mask & CEN_SPECIAL) != 0)
+    {
+      *cp++ = '+';
+      cp = stpcpy (cp, special);
+    }
+  if ((mask & (CEN_SPONSOR | CEN_REVISION)) != 0)
+    {
+      *cp++ = ',';
+      if ((mask & CEN_SPONSOR) != 0)
+       cp = stpcpy (cp, sponsor);
+      if ((mask & CEN_REVISION) != 0)
+       {
+         *cp++ = '_';
+         cp = stpcpy (cp, revision);
+       }
+    }
+
+  *cp++ = '/';
+  stpcpy (cp, filename);
+
+  /* Look in list of already loaded domains whether it is already
+     available.  */
+  last = NULL;
+  for (retval = *l10nfile_list; retval != NULL; retval = retval->next)
+    if (retval->filename != NULL)
+      {
+       int compare = strcmp (retval->filename, abs_filename);
+       if (compare == 0)
+         /* We found it!  */
+         break;
+       if (compare < 0)
+         {
+           /* It's not in the list.  */
+           retval = NULL;
+           break;
+         }
+
+       last = retval;
+      }
+
+  if (retval != NULL || do_allocate == 0)
+    {
+      free (abs_filename);
+      return retval;
+    }
+
+  retval = (struct loaded_l10nfile *)
+    malloc (sizeof (*retval) + (__argz_count (dirlist, dirlist_len)
+                               * (1 << pop (mask))
+                               * sizeof (struct loaded_l10nfile *)));
+  if (retval == NULL)
+    return NULL;
+
+  retval->filename = abs_filename;
+  retval->decided = (__argz_count (dirlist, dirlist_len) != 1
+                    || ((mask & XPG_CODESET) != 0
+                        && (mask & XPG_NORM_CODESET) != 0));
+  retval->data = NULL;
+
+  if (last == NULL)
+    {
+      retval->next = *l10nfile_list;
+      *l10nfile_list = retval;
+    }
+  else
+    {
+      retval->next = last->next;
+      last->next = retval;
+    }
+
+  entries = 0;
+  /* If the DIRLIST is a real list the RETVAL entry corresponds not to
+     a real file.  So we have to use the DIRLIST separation mechanism
+     of the inner loop.  */
+  cnt = __argz_count (dirlist, dirlist_len) == 1 ? mask - 1 : mask;
+  for (; cnt >= 0; --cnt)
+    if ((cnt & ~mask) == 0
+       && ((cnt & CEN_SPECIFIC) == 0 || (cnt & XPG_SPECIFIC) == 0)
+       && ((cnt & XPG_CODESET) == 0 || (cnt & XPG_NORM_CODESET) == 0))
+      {
+       /* Iterate over all elements of the DIRLIST.  */
+       char *dir = NULL;
+
+       while ((dir = __argz_next ((char *) dirlist, dirlist_len, dir))
+              != NULL)
+         retval->successor[entries++]
+           = _nl_make_l10nflist (l10nfile_list, dir, strlen (dir) + 1, cnt,
+                                 language, territory, codeset,
+                                 normalized_codeset, modifier, special,
+                                 sponsor, revision, filename, 1);
+      }
+  retval->successor[entries] = NULL;
+
+  return retval;
+}
+\f
+/* Normalize codeset name.  There is no standard for the codeset
+   names.  Normalization allows the user to use any of the common
+   names.  */
+const char *
+_nl_normalize_codeset (codeset, name_len)
+     const char *codeset;
+     size_t name_len;
+{
+  int len = 0;
+  int only_digit = 1;
+  char *retval;
+  char *wp;
+  size_t cnt;
+
+  for (cnt = 0; cnt < name_len; ++cnt)
+    if (isalnum (codeset[cnt]))
+      {
+       ++len;
+
+       if (isalpha (codeset[cnt]))
+         only_digit = 0;
+      }
+
+  retval = (char *) malloc ((only_digit ? 3 : 0) + len + 1);
+
+  if (retval != NULL)
+    {
+      if (only_digit)
+       wp = stpcpy (retval, "iso");
+      else
+       wp = retval;
+
+      for (cnt = 0; cnt < name_len; ++cnt)
+       if (isalpha (codeset[cnt]))
+         *wp++ = tolower (codeset[cnt]);
+       else if (isdigit (codeset[cnt]))
+         *wp++ = codeset[cnt];
+
+      *wp = '\0';
+    }
+
+  return (const char *) retval;
+}
+
+
+/* @@ begin of epilog @@ */
+
+/* We don't want libintl.a to depend on any other library.  So we
+   avoid the non-standard function stpcpy.  In GNU C Library this
+   function is available, though.  Also allow the symbol HAVE_STPCPY
+   to be defined.  */
+#if !_LIBC && !HAVE_STPCPY
+static char *
+stpcpy (dest, src)
+     char *dest;
+     const char *src;
+{
+  while ((*dest++ = *src++) != '\0')
+    /* Do nothing. */ ;
+  return dest - 1;
+}
+#endif
diff --git a/intl/libgettext.h b/intl/libgettext.h
new file mode 100644 (file)
index 0000000..0d4de4d
--- /dev/null
@@ -0,0 +1,182 @@
+/* Message catalogs for internationalization.
+   Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+/* Because on some systems (e.g. Solaris) we sometimes have to include
+   the systems libintl.h as well as this file we have more complex
+   include protection above.  But the systems header might perhaps also
+   define _LIBINTL_H and therefore we have to protect the definition here.  */
+
+#if !defined (_LIBINTL_H) || !defined (_LIBGETTEXT_H)
+#if !defined (_LIBINTL_H)
+# define _LIBINTL_H    1
+#endif
+#define _LIBGETTEXT_H  1
+
+/* We define an additional symbol to signal that we use the GNU
+   implementation of gettext.  */
+#define __USE_GNU_GETTEXT 1
+
+#include <sys/types.h>
+
+#if HAVE_LOCALE_H
+# include <locale.h>
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* @@ end of prolog @@ */
+
+#ifndef PARAMS
+# if __STDC__
+#  define PARAMS(args) args
+# else
+#  define PARAMS(args) ()
+# endif
+#endif
+
+#ifndef NULL
+# if !defined __cplusplus || defined __GNUC__
+#  define NULL ((void *) 0)
+# else
+#  define NULL (0)
+# endif
+#endif
+
+#if !HAVE_LC_MESSAGES
+/* This value determines the behaviour of the gettext() and dgettext()
+   function.  But some system does not have this defined.  Define it
+   to a default value.  */
+# define LC_MESSAGES (-1)
+#endif
+
+
+/* Declarations for gettext-using-catgets interface.  Derived from
+   Jim Meyering's libintl.h.  */
+struct _msg_ent
+{
+  const char *_msg;
+  int _msg_number;
+};
+
+
+#if HAVE_CATGETS
+/* These two variables are defined in the automatically by po-to-tbl.sed
+   generated file `cat-id-tbl.c'.  */
+extern const struct _msg_ent _msg_tbl[];
+extern int _msg_tbl_length;
+#endif
+
+
+/* For automatical extraction of messages sometimes no real
+   translation is needed.  Instead the string itself is the result.  */
+#define gettext_noop(Str) (Str)
+
+/* Look up MSGID in the current default message catalog for the current
+   LC_MESSAGES locale.  If not found, returns MSGID itself (the default
+   text).  */
+extern char *gettext PARAMS ((const char *__msgid));
+extern char *gettext__ PARAMS ((const char *__msgid));
+
+/* Look up MSGID in the DOMAINNAME message catalog for the current
+   LC_MESSAGES locale.  */
+extern char *dgettext PARAMS ((const char *__domainname, const char *__msgid));
+extern char *dgettext__ PARAMS ((const char *__domainname,
+                                const char *__msgid));
+
+/* Look up MSGID in the DOMAINNAME message catalog for the current CATEGORY
+   locale.  */
+extern char *dcgettext PARAMS ((const char *__domainname, const char *__msgid,
+                               int __category));
+extern char *dcgettext__ PARAMS ((const char *__domainname,
+                                 const char *__msgid, int __category));
+
+
+/* Set the current default message catalog to DOMAINNAME.
+   If DOMAINNAME is null, return the current default.
+   If DOMAINNAME is "", reset to the default of "messages".  */
+extern char *textdomain PARAMS ((const char *__domainname));
+extern char *textdomain__ PARAMS ((const char *__domainname));
+
+/* Specify that the DOMAINNAME message catalog will be found
+   in DIRNAME rather than in the system locale data base.  */
+extern char *bindtextdomain PARAMS ((const char *__domainname,
+                                 const char *__dirname));
+extern char *bindtextdomain__ PARAMS ((const char *__domainname,
+                                   const char *__dirname));
+
+#if ENABLE_NLS
+
+/* Solaris 2.3 has the gettext function but dcgettext is missing.
+   So we omit this optimization for Solaris 2.3.  BTW, Solaris 2.4
+   has dcgettext.  */
+# if !HAVE_CATGETS && (!HAVE_GETTEXT || HAVE_DCGETTEXT)
+
+#  define gettext(Msgid)                                                     \
+     dgettext (NULL, Msgid)
+
+#  define dgettext(Domainname, Msgid)                                        \
+     dcgettext (Domainname, Msgid, LC_MESSAGES)
+
+#  if defined __GNUC__ && __GNUC__ == 2 && __GNUC_MINOR__ >= 7
+/* This global variable is defined in loadmsgcat.c.  We need a sign,
+   whether a new catalog was loaded, which can be associated with all
+   translations.  */
+extern int _nl_msg_cat_cntr;
+
+#   define dcgettext(Domainname, Msgid, Category)                            \
+  (__extension__                                                             \
+   ({                                                                        \
+     char *__result;                                                         \
+     if (__builtin_constant_p (Msgid))                                       \
+       {                                                                     \
+        static char *__translation__;                                        \
+        static int __catalog_counter__;                                      \
+        if (! __translation__ || __catalog_counter__ != _nl_msg_cat_cntr)    \
+          {                                                                  \
+            __translation__ =                                                \
+              dcgettext__ (Domainname, Msgid, Category);                     \
+            __catalog_counter__ = _nl_msg_cat_cntr;                          \
+          }                                                                  \
+        __result = __translation__;                                          \
+       }                                                                     \
+     else                                                                    \
+       __result = dcgettext__ (Domainname, Msgid, Category);                 \
+     __result;                                                               \
+    }))
+#  endif
+# endif
+
+#else
+
+# define gettext(Msgid) (Msgid)
+# define dgettext(Domainname, Msgid) (Msgid)
+# define dcgettext(Domainname, Msgid, Category) (Msgid)
+# define textdomain(Domainname) while (0) /* nothing */
+# define bindtextdomain(Domainname, Dirname) while (0) /* nothing */
+
+#endif
+
+/* @@ begin of epilog @@ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/intl/linux-msg.sed b/intl/linux-msg.sed
new file mode 100644 (file)
index 0000000..7feb38d
--- /dev/null
@@ -0,0 +1,100 @@
+# po2msg.sed - Convert Uniforum style .po file to Linux style .msg file
+# Copyright (C) 1995 Free Software Foundation, Inc.
+# Ulrich Drepper <drepper@gnu.org>, 1995.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+#
+# The first directive in the .msg should be the definition of the
+# message set number.  We use always set number 1.
+#
+1 {
+  i\
+$set 1 # Automatically created by po2msg.sed
+  h
+  s/.*/0/
+  x
+}
+#
+# Mitch's old catalog format does not allow comments.
+#
+# We copy the original message as a comment into the .msg file.
+#
+/^msgid/ {
+  s/msgid[     ]*"//
+#
+# This does not work now with the new format.
+# /"$/! {
+#   s/\\$//
+#   s/$/ ... (more lines following)"/
+# }
+  x
+# The following nice solution is by
+# Bruno <Haible@ma2s2.mathematik.uni-karlsruhe.de>
+  td
+# Increment a decimal number in pattern space.
+# First hide trailing `9' digits.
+  :d
+  s/9\(_*\)$/_\1/
+  td
+# Assure at least one digit is available.
+  s/^\(_*\)$/0\1/
+# Increment the last digit.
+  s/8\(_*\)$/9\1/
+  s/7\(_*\)$/8\1/
+  s/6\(_*\)$/7\1/
+  s/5\(_*\)$/6\1/
+  s/4\(_*\)$/5\1/
+  s/3\(_*\)$/4\1/
+  s/2\(_*\)$/3\1/
+  s/1\(_*\)$/2\1/
+  s/0\(_*\)$/1\1/
+# Convert the hidden `9' digits to `0's.
+  s/_/0/g
+  x
+  G
+  s/\(.*\)"\n\([0-9]*\)/$ #\2 Original Message:(\1)/p
+}
+#
+# The .msg file contains, other then the .po file, only the translations
+# but each given a unique ID.  Starting from 1 and incrementing by 1 for
+# each message we assign them to the messages.
+# It is important that the .po file used to generate the cat-id-tbl.c file
+# (with po-to-tbl) is the same as the one used here.  (At least the order
+# of declarations must not be changed.)
+#
+/^msgstr/ {
+  s/msgstr[    ]*"\(.*\)"/# \1/
+# Clear substitution flag.
+  tb
+# Append the next line.
+  :b
+  N
+# Look whether second part is continuation line.
+  s/\(.*\n\)"\(.*\)"/\1\2/
+# Yes, then branch.
+  ta
+  P
+  D
+# Note that D includes a jump to the start!!
+# We found a continuation line.  But before printing insert '\'.
+  :a
+  s/\(.*\)\(\n.*\)/\1\\\2/
+  P
+# We cannot use D here.
+  s/.*\n\(.*\)/\1/
+  tb
+}
+d
diff --git a/intl/loadinfo.h b/intl/loadinfo.h
new file mode 100644 (file)
index 0000000..c67c2eb
--- /dev/null
@@ -0,0 +1,58 @@
+#ifndef PARAMS
+# if __STDC__
+#  define PARAMS(args) args
+# else
+#  define PARAMS(args) ()
+# endif
+#endif
+
+/* Encoding of locale name parts.  */
+#define CEN_REVISION           1
+#define CEN_SPONSOR            2
+#define CEN_SPECIAL            4
+#define XPG_NORM_CODESET       8
+#define XPG_CODESET            16
+#define TERRITORY              32
+#define CEN_AUDIENCE           64
+#define XPG_MODIFIER           128
+
+#define CEN_SPECIFIC   (CEN_REVISION|CEN_SPONSOR|CEN_SPECIAL|CEN_AUDIENCE)
+#define XPG_SPECIFIC   (XPG_CODESET|XPG_NORM_CODESET|XPG_MODIFIER)
+
+
+struct loaded_l10nfile
+{
+  const char *filename;
+  int decided;
+
+  const void *data;
+
+  struct loaded_l10nfile *next;
+  struct loaded_l10nfile *successor[1];
+};
+
+
+extern const char *_nl_normalize_codeset PARAMS ((const char *codeset,
+                                                 size_t name_len));
+
+extern struct loaded_l10nfile *
+_nl_make_l10nflist PARAMS ((struct loaded_l10nfile **l10nfile_list,
+                           const char *dirlist, size_t dirlist_len, int mask,
+                           const char *language, const char *territory,
+                           const char *codeset,
+                           const char *normalized_codeset,
+                           const char *modifier, const char *special,
+                           const char *sponsor, const char *revision,
+                           const char *filename, int do_allocate));
+
+
+extern const char *_nl_expand_alias PARAMS ((const char *name));
+
+extern int _nl_explode_name PARAMS ((char *name, const char **language,
+                                    const char **modifier,
+                                    const char **territory,
+                                    const char **codeset,
+                                    const char **normalized_codeset,
+                                    const char **special,
+                                    const char **sponsor,
+                                    const char **revision));
diff --git a/intl/loadmsgcat.c b/intl/loadmsgcat.c
new file mode 100644 (file)
index 0000000..73e90a9
--- /dev/null
@@ -0,0 +1,199 @@
+/* Load needed message catalogs
+   Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined STDC_HEADERS || defined _LIBC
+# include <stdlib.h>
+#endif
+
+#if defined HAVE_UNISTD_H || defined _LIBC
+# include <unistd.h>
+#endif
+
+#if (defined HAVE_MMAP && defined HAVE_MUNMAP) || defined _LIBC
+# include <sys/mman.h>
+#endif
+
+#include "gettext.h"
+#include "gettextP.h"
+
+/* @@ end of prolog @@ */
+
+#ifdef _LIBC
+/* Rename the non ISO C functions.  This is required by the standard
+   because some ISO C functions will require linking with this object
+   file and the name space must not be polluted.  */
+# define fstat  __fstat
+# define open   __open
+# define close  __close
+# define read   __read
+# define mmap   __mmap
+# define munmap __munmap
+#endif
+
+/* We need a sign, whether a new catalog was loaded, which can be associated
+   with all translations.  This is important if the translations are
+   cached by one of GCC's features.  */
+int _nl_msg_cat_cntr = 0;
+
+
+/* Load the message catalogs specified by FILENAME.  If it is no valid
+   message catalog do nothing.  */
+void
+_nl_load_domain (domain_file)
+     struct loaded_l10nfile *domain_file;
+{
+  int fd;
+  struct stat st;
+  struct mo_file_header *data = (struct mo_file_header *) -1;
+#if (defined HAVE_MMAP && defined HAVE_MUNMAP && !defined DISALLOW_MMAP) \
+    || defined _LIBC
+  int use_mmap = 0;
+#endif
+  struct loaded_domain *domain;
+
+  domain_file->decided = 1;
+  domain_file->data = NULL;
+
+  /* If the record does not represent a valid locale the FILENAME
+     might be NULL.  This can happen when according to the given
+     specification the locale file name is different for XPG and CEN
+     syntax.  */
+  if (domain_file->filename == NULL)
+    return;
+
+  /* Try to open the addressed file.  */
+  fd = open (domain_file->filename, O_RDONLY);
+  if (fd == -1)
+    return;
+
+  /* We must know about the size of the file.  */
+  if (fstat (fd, &st) != 0
+      && st.st_size < (off_t) sizeof (struct mo_file_header))
+    {
+      /* Something went wrong.  */
+      close (fd);
+      return;
+    }
+
+#if (defined HAVE_MMAP && defined HAVE_MUNMAP && !defined DISALLOW_MMAP) \
+    || defined _LIBC
+  /* Now we are ready to load the file.  If mmap() is available we try
+     this first.  If not available or it failed we try to load it.  */
+  data = (struct mo_file_header *) mmap (NULL, st.st_size, PROT_READ,
+                                        MAP_PRIVATE, fd, 0);
+
+  if (data != (struct mo_file_header *) -1)
+    {
+      /* mmap() call was successful.  */
+      close (fd);
+      use_mmap = 1;
+    }
+#endif
+
+  /* If the data is not yet available (i.e. mmap'ed) we try to load
+     it manually.  */
+  if (data == (struct mo_file_header *) -1)
+    {
+      off_t to_read;
+      char *read_ptr;
+
+      data = (struct mo_file_header *) malloc (st.st_size);
+      if (data == NULL)
+       return;
+
+      to_read = st.st_size;
+      read_ptr = (char *) data;
+      do
+       {
+         long int nb = (long int) read (fd, read_ptr, to_read);
+         if (nb == -1)
+           {
+             close (fd);
+             return;
+           }
+
+         read_ptr += nb;
+         to_read -= nb;
+       }
+      while (to_read > 0);
+
+      close (fd);
+    }
+
+  /* Using the magic number we can test whether it really is a message
+     catalog file.  */
+  if (data->magic != _MAGIC && data->magic != _MAGIC_SWAPPED)
+    {
+      /* The magic number is wrong: not a message catalog file.  */
+#if (defined HAVE_MMAP && defined HAVE_MUNMAP && !defined DISALLOW_MMAP) \
+    || defined _LIBC
+      if (use_mmap)
+       munmap ((caddr_t) data, st.st_size);
+      else
+#endif
+       free (data);
+      return;
+    }
+
+  domain_file->data
+    = (struct loaded_domain *) malloc (sizeof (struct loaded_domain));
+  if (domain_file->data == NULL)
+    return;
+
+  domain = (struct loaded_domain *) domain_file->data;
+  domain->data = (char *) data;
+  domain->must_swap = data->magic != _MAGIC;
+
+  /* Fill in the information about the available tables.  */
+  switch (W (domain->must_swap, data->revision))
+    {
+    case 0:
+      domain->nstrings = W (domain->must_swap, data->nstrings);
+      domain->orig_tab = (struct string_desc *)
+       ((char *) data + W (domain->must_swap, data->orig_tab_offset));
+      domain->trans_tab = (struct string_desc *)
+       ((char *) data + W (domain->must_swap, data->trans_tab_offset));
+      domain->hash_size = W (domain->must_swap, data->hash_tab_size);
+      domain->hash_tab = (nls_uint32 *)
+       ((char *) data + W (domain->must_swap, data->hash_tab_offset));
+      break;
+    default:
+      /* This is an illegal revision.  */
+#if (defined HAVE_MMAP && defined HAVE_MUNMAP && !defined DISALLOW_MMAP) \
+    || defined _LIBC
+      if (use_mmap)
+       munmap ((caddr_t) data, st.st_size);
+      else
+#endif
+       free (data);
+      free (domain);
+      domain_file->data = NULL;
+      return;
+    }
+
+  /* Show that one domain is changed.  This might make some cached
+     translations invalid.  */
+  ++_nl_msg_cat_cntr;
+}
diff --git a/intl/localealias.c b/intl/localealias.c
new file mode 100644 (file)
index 0000000..64e8ca7
--- /dev/null
@@ -0,0 +1,378 @@
+/* Handle aliases for locale names
+   Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+   Written by Ulrich Drepper <drepper@gnu.org>, 1995.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <ctype.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#ifdef __GNUC__
+# define alloca __builtin_alloca
+# define HAVE_ALLOCA 1
+#else
+# if defined HAVE_ALLOCA_H || defined _LIBC
+#  include <alloca.h>
+# else
+#  ifdef _AIX
+ #pragma alloca
+#  else
+#   ifndef alloca
+char *alloca ();
+#   endif
+#  endif
+# endif
+#endif
+
+#if defined STDC_HEADERS || defined _LIBC
+# include <stdlib.h>
+#else
+char *getenv ();
+# ifdef HAVE_MALLOC_H
+#  include <malloc.h>
+# else
+void free ();
+# endif
+#endif
+
+#if defined HAVE_STRING_H || defined _LIBC
+# ifndef _GNU_SOURCE
+#  define _GNU_SOURCE  1
+# endif
+# include <string.h>
+#else
+# include <strings.h>
+# ifndef memcpy
+#  define memcpy(Dst, Src, Num) bcopy (Src, Dst, Num)
+# endif
+#endif
+#if !HAVE_STRCHR && !defined _LIBC
+# ifndef strchr
+#  define strchr index
+# endif
+#endif
+
+#include "gettext.h"
+#include "gettextP.h"
+
+/* @@ end of prolog @@ */
+
+#ifdef _LIBC
+/* Rename the non ANSI C functions.  This is required by the standard
+   because some ANSI C functions will require linking with this object
+   file and the name space must not be polluted.  */
+# define strcasecmp __strcasecmp
+#endif
+
+
+/* For those loosing systems which don't have `alloca' we have to add
+   some additional code emulating it.  */
+#ifdef HAVE_ALLOCA
+/* Nothing has to be done.  */
+# define ADD_BLOCK(list, address) /* nothing */
+# define FREE_BLOCKS(list) /* nothing */
+#else
+struct block_list
+{
+  void *address;
+  struct block_list *next;
+};
+# define ADD_BLOCK(list, addr)                                               \
+  do {                                                                       \
+    struct block_list *newp = (struct block_list *) malloc (sizeof (*newp));  \
+    /* If we cannot get a free block we cannot add the new element to        \
+       the list.  */                                                         \
+    if (newp != NULL) {                                                              \
+      newp->address = (addr);                                                \
+      newp->next = (list);                                                   \
+      (list) = newp;                                                         \
+    }                                                                        \
+  } while (0)
+# define FREE_BLOCKS(list)                                                   \
+  do {                                                                       \
+    while (list != NULL) {                                                   \
+      struct block_list *old = list;                                         \
+      list = list->next;                                                     \
+      free (old);                                                            \
+    }                                                                        \
+  } while (0)
+# undef alloca
+# define alloca(size) (malloc (size))
+#endif /* have alloca */
+
+
+struct alias_map
+{
+  const char *alias;
+  const char *value;
+};
+
+
+static struct alias_map *map;
+static size_t nmap = 0;
+static size_t maxmap = 0;
+
+
+/* Prototypes for local functions.  */
+static size_t read_alias_file PARAMS ((const char *fname, int fname_len));
+static void extend_alias_table PARAMS ((void));
+static int alias_compare PARAMS ((const struct alias_map *map1,
+                                 const struct alias_map *map2));
+
+
+const char *
+_nl_expand_alias (name)
+    const char *name;
+{
+  static const char *locale_alias_path = LOCALE_ALIAS_PATH;
+  struct alias_map *retval;
+  size_t added;
+
+  do
+    {
+      struct alias_map item;
+
+      item.alias = name;
+
+      if (nmap > 0)
+       retval = (struct alias_map *) bsearch (&item, map, nmap,
+                                              sizeof (struct alias_map),
+                                              (int (*) PARAMS ((const void *,
+                                                                const void *))
+                                               ) alias_compare);
+      else
+       retval = NULL;
+
+      /* We really found an alias.  Return the value.  */
+      if (retval != NULL)
+       return retval->value;
+
+      /* Perhaps we can find another alias file.  */
+      added = 0;
+      while (added == 0 && locale_alias_path[0] != '\0')
+       {
+         const char *start;
+
+         while (locale_alias_path[0] == ':')
+           ++locale_alias_path;
+         start = locale_alias_path;
+
+         while (locale_alias_path[0] != '\0' && locale_alias_path[0] != ':')
+           ++locale_alias_path;
+
+         if (start < locale_alias_path)
+           added = read_alias_file (start, locale_alias_path - start);
+       }
+    }
+  while (added != 0);
+
+  return NULL;
+}
+
+
+static size_t
+read_alias_file (fname, fname_len)
+     const char *fname;
+     int fname_len;
+{
+#ifndef HAVE_ALLOCA
+  struct block_list *block_list = NULL;
+#endif
+  FILE *fp;
+  char *full_fname;
+  size_t added;
+  static const char aliasfile[] = "/locale.alias";
+
+  full_fname = (char *) alloca (fname_len + sizeof aliasfile);
+  ADD_BLOCK (block_list, full_fname);
+  memcpy (full_fname, fname, fname_len);
+  memcpy (&full_fname[fname_len], aliasfile, sizeof aliasfile);
+
+  fp = fopen (full_fname, "r");
+  if (fp == NULL)
+    {
+      FREE_BLOCKS (block_list);
+      return 0;
+    }
+
+  added = 0;
+  while (!feof (fp))
+    {
+      /* It is a reasonable approach to use a fix buffer here because
+        a) we are only interested in the first two fields
+        b) these fields must be usable as file names and so must not
+           be that long
+       */
+      char buf[BUFSIZ];
+      char *alias;
+      char *value;
+      char *cp;
+
+      if (fgets (buf, BUFSIZ, fp) == NULL)
+       /* EOF reached.  */
+       break;
+
+      cp = buf;
+      /* Ignore leading white space.  */
+      while (isspace (cp[0]))
+       ++cp;
+
+      /* A leading '#' signals a comment line.  */
+      if (cp[0] != '\0' && cp[0] != '#')
+       {
+         alias = cp++;
+         while (cp[0] != '\0' && !isspace (cp[0]))
+           ++cp;
+         /* Terminate alias name.  */
+         if (cp[0] != '\0')
+           *cp++ = '\0';
+
+         /* Now look for the beginning of the value.  */
+         while (isspace (cp[0]))
+           ++cp;
+
+         if (cp[0] != '\0')
+           {
+             char *tp;
+             size_t len;
+
+             value = cp++;
+             while (cp[0] != '\0' && !isspace (cp[0]))
+               ++cp;
+             /* Terminate value.  */
+             if (cp[0] == '\n')
+               {
+                 /* This has to be done to make the following test
+                    for the end of line possible.  We are looking for
+                    the terminating '\n' which do not overwrite here.  */
+                 *cp++ = '\0';
+                 *cp = '\n';
+               }
+             else if (cp[0] != '\0')
+               *cp++ = '\0';
+
+             if (nmap >= maxmap)
+               extend_alias_table ();
+
+             /* We cannot depend on strdup available in the libc.  Sigh!  */
+             len = strlen (alias) + 1;
+             tp = (char *) malloc (len);
+             if (tp == NULL)
+               {
+                 FREE_BLOCKS (block_list);
+                 return added;
+               }
+             memcpy (tp, alias, len);
+             map[nmap].alias = tp;
+
+             len = strlen (value) + 1;
+             tp = (char *) malloc (len);
+             if (tp == NULL)
+               {
+                 FREE_BLOCKS (block_list);
+                 return added;
+               }
+             memcpy (tp, value, len);
+             map[nmap].value = tp;
+
+             ++nmap;
+             ++added;
+           }
+       }
+
+      /* Possibly not the whole line fits into the buffer.  Ignore
+        the rest of the line.  */
+      while (strchr (cp, '\n') == NULL)
+       {
+         cp = buf;
+         if (fgets (buf, BUFSIZ, fp) == NULL)
+           /* Make sure the inner loop will be left.  The outer loop
+              will exit at the `feof' test.  */
+           *cp = '\n';
+       }
+    }
+
+  /* Should we test for ferror()?  I think we have to silently ignore
+     errors.  --drepper  */
+  fclose (fp);
+
+  if (added > 0)
+    qsort (map, nmap, sizeof (struct alias_map),
+          (int (*) PARAMS ((const void *, const void *))) alias_compare);
+
+  FREE_BLOCKS (block_list);
+  return added;
+}
+
+
+static void
+extend_alias_table ()
+{
+  size_t new_size;
+  struct alias_map *new_map;
+
+  new_size = maxmap == 0 ? 100 : 2 * maxmap;
+  new_map = (struct alias_map *) malloc (new_size
+                                        * sizeof (struct alias_map));
+  if (new_map == NULL)
+    /* Simply don't extend: we don't have any more core.  */
+    return;
+
+  memcpy (new_map, map, nmap * sizeof (struct alias_map));
+
+  if (maxmap != 0)
+    free (map);
+
+  map = new_map;
+  maxmap = new_size;
+}
+
+
+static int
+alias_compare (map1, map2)
+     const struct alias_map *map1;
+     const struct alias_map *map2;
+{
+#if defined _LIBC || defined HAVE_STRCASECMP
+  return strcasecmp (map1->alias, map2->alias);
+#else
+  const unsigned char *p1 = (const unsigned char *) map1->alias;
+  const unsigned char *p2 = (const unsigned char *) map2->alias;
+  unsigned char c1, c2;
+
+  if (p1 == p2)
+    return 0;
+
+  do
+    {
+      /* I know this seems to be odd but the tolower() function in
+        some systems libc cannot handle nonalpha characters.  */
+      c1 = isupper (*p1) ? tolower (*p1) : *p1;
+      c2 = isupper (*p2) ? tolower (*p2) : *p2;
+      if (c1 == '\0')
+       break;
+      ++p1;
+      ++p2;
+    }
+  while (c1 == c2);
+
+  return c1 - c2;
+#endif
+}
diff --git a/intl/po2tbl.sed.in b/intl/po2tbl.sed.in
new file mode 100644 (file)
index 0000000..247b668
--- /dev/null
@@ -0,0 +1,102 @@
+# po2tbl.sed - Convert Uniforum style .po file to lookup table for catgets
+# Copyright (C) 1995 Free Software Foundation, Inc.
+# Ulrich Drepper <drepper@gnu.org>, 1995.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+1 {
+  i\
+/* Automatically generated by po2tbl.sed from @PACKAGE NAME@.pot.  */\
+\
+#if HAVE_CONFIG_H\
+# include <config.h>\
+#endif\
+\
+#include "libgettext.h"\
+\
+const struct _msg_ent _msg_tbl[] = {
+  h
+  s/.*/0/
+  x
+}
+#
+# Write msgid entries in C array form.
+#
+/^msgid/ {
+  s/msgid[     ]*\(".*"\)/  {\1/
+  tb
+# Append the next line
+  :b
+  N
+# Look whether second part is continuation line.
+  s/\(.*\)"\(\n\)"\(.*"\)/\1\2\3/
+# Yes, then branch.
+  ta
+# Because we assume that the input file correctly formed the line
+# just read cannot be again be a msgid line.  So it's safe to ignore
+# it.
+  s/\(.*\)\n.*/\1/
+  bc
+# We found a continuation line.  But before printing insert '\'.
+  :a
+  s/\(.*\)\(\n.*\)/\1\\\2/
+  P
+# We cannot use D here.
+  s/.*\n\(.*\)/\1/
+# Some buggy seds do not clear the `successful substitution since last ``t'''
+# flag on `N', so we do a `t' here to clear it.
+  tb
+# Not reached
+  :c
+  x
+# The following nice solution is by
+# Bruno <Haible@ma2s2.mathematik.uni-karlsruhe.de>
+  td
+# Increment a decimal number in pattern space.
+# First hide trailing `9' digits.
+  :d
+  s/9\(_*\)$/_\1/
+  td
+# Assure at least one digit is available.
+  s/^\(_*\)$/0\1/
+# Increment the last digit.
+  s/8\(_*\)$/9\1/
+  s/7\(_*\)$/8\1/
+  s/6\(_*\)$/7\1/
+  s/5\(_*\)$/6\1/
+  s/4\(_*\)$/5\1/
+  s/3\(_*\)$/4\1/
+  s/2\(_*\)$/3\1/
+  s/1\(_*\)$/2\1/
+  s/0\(_*\)$/1\1/
+# Convert the hidden `9' digits to `0's.
+  s/_/0/g
+  x
+  G
+  s/\(.*\)\n\([0-9]*\)/\1, \2},/
+  s/\(.*\)"$/\1/
+  p
+}
+#
+# Last line.
+#
+$ {
+  i\
+};\
+
+  g
+  s/0*\(.*\)/int _msg_tbl_length = \1;/p
+}
+d
diff --git a/intl/textdomain.c b/intl/textdomain.c
new file mode 100644 (file)
index 0000000..beb1f06
--- /dev/null
@@ -0,0 +1,106 @@
+/* Implementation of the textdomain(3) function
+   Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+   Written by Ulrich Drepper <drepper@gnu.org>, 1995.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if defined STDC_HEADERS || defined _LIBC
+# include <stdlib.h>
+#endif
+
+#if defined STDC_HEADERS || defined HAVE_STRING_H || defined _LIBC
+# include <string.h>
+#else
+# include <strings.h>
+# ifndef memcpy
+#  define memcpy(Dst, Src, Num) bcopy (Src, Dst, Num)
+# endif
+#endif
+
+#ifdef _LIBC
+# include <libintl.h>
+#else
+# include "libgettext.h"
+#endif
+
+/* @@ end of prolog @@ */
+
+/* Name of the default text domain.  */
+extern const char _nl_default_default_domain[];
+
+/* Default text domain in which entries for gettext(3) are to be found.  */
+extern const char *_nl_current_default_domain;
+
+
+/* Names for the libintl functions are a problem.  They must not clash
+   with existing names and they should follow ANSI C.  But this source
+   code is also used in GNU C Library where the names have a __
+   prefix.  So we have to make a difference here.  */
+#ifdef _LIBC
+# define TEXTDOMAIN __textdomain
+# define strdup(str) __strdup (str)
+#else
+# define TEXTDOMAIN textdomain__
+#endif
+
+/* Set the current default message catalog to DOMAINNAME.
+   If DOMAINNAME is null, return the current default.
+   If DOMAINNAME is "", reset to the default of "messages".  */
+char *
+TEXTDOMAIN (domainname)
+     const char *domainname;
+{
+  char *old;
+
+  /* A NULL pointer requests the current setting.  */
+  if (domainname == NULL)
+    return (char *) _nl_current_default_domain;
+
+  old = (char *) _nl_current_default_domain;
+
+  /* If domain name is the null string set to default domain "messages".  */
+  if (domainname[0] == '\0'
+      || strcmp (domainname, _nl_default_default_domain) == 0)
+    _nl_current_default_domain = _nl_default_default_domain;
+  else
+    {
+      /* If the following malloc fails `_nl_current_default_domain'
+        will be NULL.  This value will be returned and so signals we
+        are out of core.  */
+#if defined _LIBC || defined HAVE_STRDUP
+      _nl_current_default_domain = strdup (domainname);
+#else
+      size_t len = strlen (domainname) + 1;
+      char *cp = (char *) malloc (len);
+      if (cp != NULL)
+       memcpy (cp, domainname, len);
+      _nl_current_default_domain = cp;
+#endif
+    }
+
+  if (old != _nl_default_default_domain)
+    free (old);
+
+  return (char *) _nl_current_default_domain;
+}
+
+#ifdef _LIBC
+/* Alias for function name in GNU C Library.  */
+weak_alias (__textdomain, textdomain);
+#endif
diff --git a/intl/xopen-msg.sed b/intl/xopen-msg.sed
new file mode 100644 (file)
index 0000000..b35588f
--- /dev/null
@@ -0,0 +1,104 @@
+# po2msg.sed - Convert Uniforum style .po file to X/Open style .msg file
+# Copyright (C) 1995 Free Software Foundation, Inc.
+# Ulrich Drepper <drepper@gnu.org>, 1995.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+#
+# The first directive in the .msg should be the definition of the
+# message set number.  We use always set number 1.
+#
+1 {
+  i\
+$set 1 # Automatically created by po2msg.sed
+  h
+  s/.*/0/
+  x
+}
+#
+# We copy all comments into the .msg file.  Perhaps they can help.
+#
+/^#/ s/^#[     ]*/$ /p
+#
+# We copy the original message as a comment into the .msg file.
+#
+/^msgid/ {
+# Does not work now
+#  /"$/! {
+#    s/\\$//
+#    s/$/ ... (more lines following)"/
+#  }
+  s/^msgid[    ]*"\(.*\)"$/$ Original Message: \1/
+  p
+}
+#
+# The .msg file contains, other then the .po file, only the translations
+# but each given a unique ID.  Starting from 1 and incrementing by 1 for
+# each message we assign them to the messages.
+# It is important that the .po file used to generate the cat-id-tbl.c file
+# (with po-to-tbl) is the same as the one used here.  (At least the order
+# of declarations must not be changed.)
+#
+/^msgstr/ {
+  s/msgstr[    ]*"\(.*\)"/\1/
+  x
+# The following nice solution is by
+# Bruno <Haible@ma2s2.mathematik.uni-karlsruhe.de>
+  td
+# Increment a decimal number in pattern space.
+# First hide trailing `9' digits.
+  :d
+  s/9\(_*\)$/_\1/
+  td
+# Assure at least one digit is available.
+  s/^\(_*\)$/0\1/
+# Increment the last digit.
+  s/8\(_*\)$/9\1/
+  s/7\(_*\)$/8\1/
+  s/6\(_*\)$/7\1/
+  s/5\(_*\)$/6\1/
+  s/4\(_*\)$/5\1/
+  s/3\(_*\)$/4\1/
+  s/2\(_*\)$/3\1/
+  s/1\(_*\)$/2\1/
+  s/0\(_*\)$/1\1/
+# Convert the hidden `9' digits to `0's.
+  s/_/0/g
+  x
+# Bring the line in the format `<number> <message>'
+  G
+  s/^[^\n]*$/& /
+  s/\(.*\)\n\([0-9]*\)/\2 \1/
+# Clear flag from last substitution.
+  tb
+# Append the next line.
+  :b
+  N
+# Look whether second part is a continuation line.
+  s/\(.*\n\)"\(.*\)"/\1\2/
+# Yes, then branch.
+  ta
+  P
+  D
+# Note that `D' includes a jump to the start!!
+# We found a continuation line.  But before printing insert '\'.
+  :a
+  s/\(.*\)\(\n.*\)/\1\\\2/
+  P
+# We cannot use the sed command `D' here
+  s/.*\n\(.*\)/\1/
+  tb
+}
+d
diff --git a/lib/ChangeLog b/lib/ChangeLog
new file mode 100644 (file)
index 0000000..211133d
--- /dev/null
@@ -0,0 +1,30 @@
+Sun Jan  2 21:31:48 2000  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (SUBDIRS) Only include gmp if libgmp not installed
+       on this system already.
+
+Sun May 31 00:55:51 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (SUBDIRS) Add gmp.
+
+       * gmp/: New subdirectory, containing a subset of GNU libgmp2 just
+       big enough to support mpf_init_set_d(), mpf_get_str(), and
+       mpf_clear().
+
+Fri Apr 24 12:52:07 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (SUBDIRS) Remove avllib.
+
+       * avllib/: Removed.
+
+Wed Dec 24 22:36:50 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (SUBDIRS) Add dcdflib.
+
+       * dcdflib: New subdirectory.
+
+----------------------------------------------------------------------
+Local Variables:
+mode: change-log
+version-control: never
+End:
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644 (file)
index 0000000..9918437
--- /dev/null
@@ -0,0 +1,6 @@
+## Process this file with automake to produce Makefile.in  -*- makefile -*-
+
+SUBDIRS = julcal @GMP_SUBDIRS@ misc dcdflib
+DIST_SUBDIRS = julcal gmp misc dcdflib
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/lib/dcdflib/COPYING b/lib/dcdflib/COPYING
new file mode 100644 (file)
index 0000000..173ab1a
--- /dev/null
@@ -0,0 +1,41 @@
+The following license applies *only* to the files cdflib.h, dcdflib.c,
+and ipmpar.c. -blp
+
+                              LEGALITIES
+
+Code that appeared  in an    ACM  publication  is subject  to    their
+algorithms policy:
+
+     Submittal of  an  algorithm    for publication  in   one of   the  ACM
+     Transactions implies that unrestricted use  of the algorithm within  a
+     computer is permissible.   General permission  to copy and  distribute
+     the algorithm without fee is granted provided that the copies  are not
+     made  or   distributed for  direct   commercial  advantage.    The ACM
+     copyright notice and the title of the publication and its date appear,
+     and  notice is given that copying  is by permission of the Association
+     for Computing Machinery.  To copy otherwise, or to republish, requires
+     a fee and/or specific permission.
+
+     Krogh, F.  Algorithms  Policy.  ACM  Tran.   Math.  Softw.   13(1987),
+     183-186.
+
+We place the DCDFLIB code that we have written in the public domain.  
+
+                                 NO WARRANTY
+     
+     WE PROVIDE ABSOLUTELY  NO WARRANTY  OF ANY  KIND  EITHER  EXPRESSED OR
+     IMPLIED,  INCLUDING BUT   NOT LIMITED TO,  THE  IMPLIED  WARRANTIES OF
+     MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK
+     AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS  WITH YOU.  SHOULD
+     THIS PROGRAM PROVE  DEFECTIVE, YOU ASSUME  THE COST  OF  ALL NECESSARY
+     SERVICING, REPAIR OR CORRECTION.
+     
+     IN NO  EVENT  SHALL THE UNIVERSITY  OF TEXAS OR  ANY  OF ITS COMPONENT
+     INSTITUTIONS INCLUDING M. D.   ANDERSON HOSPITAL BE LIABLE  TO YOU FOR
+     DAMAGES, INCLUDING ANY  LOST PROFITS, LOST MONIES,   OR OTHER SPECIAL,
+     INCIDENTAL   OR  CONSEQUENTIAL DAMAGES   ARISING   OUT  OF  THE USE OR
+     INABILITY TO USE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA OR
+     ITS ANALYSIS BEING  RENDERED INACCURATE OR  LOSSES SUSTAINED  BY THIRD
+     PARTIES) THE PROGRAM.
+     
+     (Above NO WARRANTY modified from the GNU NO WARRANTY statement.)
diff --git a/lib/dcdflib/ChangeLog b/lib/dcdflib/ChangeLog
new file mode 100644 (file)
index 0000000..6cef35f
--- /dev/null
@@ -0,0 +1,32 @@
+Sun Aug  9 11:16:26 1998  Ben Pfaff  <blp@gnu.org>
+
+       * dcdflib.COPYING: Renamed COPYING.
+
+Sun Jul  5 00:14:51 1998  Ben Pfaff  <blp@gnu.org>
+
+       * cdflib.h: Move E0000, E0001 prototypes into dcdflib.c.
+
+Thu May  7 22:56:48 1998  Ben Pfaff  <blp@gnu.org>
+
+       * dcdflib.c: (E0000) Explicitly constant string to char * in call
+       to ftnstop() in order to alleviate warning from gcc.
+
+Sat Jan  3 17:08:58 1998  Ben Pfaff  <blp@gnu.org>
+
+       * README: New file.
+
+Wed Dec 24 22:37:21 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: New file.
+
+       * cdflib.h: New file from dcdflib.c-1.1.
+       
+       * dcdflib.COPYING: New file extracted from dcdflib.c-1.1 README.
+
+       * dcdflib.c: New file from dcdflib.c-1.1.  Minor changes
+       (parenthesization) to placate gcc warnings.
+       
+       * ipmpar.c: New file from dcdflib.c-1.1.  Largely rewritten for
+       autoconf.
+
+
diff --git a/lib/dcdflib/Makefile.am b/lib/dcdflib/Makefile.am
new file mode 100644 (file)
index 0000000..e2ee995
--- /dev/null
@@ -0,0 +1,13 @@
+## Process this file with automake to produce Makefile.in  -*- makefile -*-
+
+noinst_LIBRARIES = libdcdflib.a
+
+INCLUDES = -I$(srcdir) -I$(top_srcdir)/src -I$(top_srcdir) \
+-I$(top_srcdir)/intl
+
+libdcdflib_a_SOURCES = dcdflib.c ipmpar.c
+noinst_HEADERS = cdflib.h
+
+MAINTAINERCLEANFILES = Makefile.in
+
+EXTRA_DIST = COPYING
diff --git a/lib/dcdflib/README b/lib/dcdflib/README
new file mode 100644 (file)
index 0000000..cfafc4d
--- /dev/null
@@ -0,0 +1,5 @@
+Please note that dcdflib is not part of PSPP.  Instead, it is a
+separate library that is included in the PSPP distribution for
+convenience in compiling.
+
+       -blp
diff --git a/lib/dcdflib/cdflib.h b/lib/dcdflib/cdflib.h
new file mode 100644 (file)
index 0000000..5f1ce3c
--- /dev/null
@@ -0,0 +1,80 @@
+double algdiv(double*,double*);
+double alngam(double*);
+double alnrel(double*);
+double apser(double*,double*,double*,double*);
+double basym(double*,double*,double*,double*);
+double bcorr(double*,double*);
+double betaln(double*,double*);
+double bfrac(double*,double*,double*,double*,double*,double*);
+void bgrat(double*,double*,double*,double*,double*,double*,int*i);
+double bpser(double*,double*,double*,double*);
+void bratio(double*,double*,double*,double*,double*,double*,int*);
+double brcmp1(int*,double*,double*,double*,double*);
+double brcomp(double*,double*,double*,double*);
+double bup(double*,double*,double*,double*,int*,double*);
+void cdfbet(int*,double*,double*,double*,double*,double*,double*,
+            int*,double*);
+void cdfbin(int*,double*,double*,double*,double*,double*,double*,
+            int*,double*);
+void cdfchi(int*,double*,double*,double*,double*,int*,double*);
+void cdfchn(int*,double*,double*,double*,double*,double*,int*,double*);
+void cdff(int*,double*,double*,double*,double*,double*,int*,double*);
+void cdffnc(int*,double*,double*,double*,double*,double*,double*,
+            int*s,double*);
+void cdfgam(int*,double*,double*,double*,double*,double*,int*,double*);
+void cdfnbn(int*,double*,double*,double*,double*,double*,double*,
+            int*,double*);
+void cdfnor(int*,double*,double*,double*,double*,double*,int*,double*);
+void cdfpoi(int*,double*,double*,double*,double*,int*,double*);
+void cdft(int*,double*,double*,double*,double*,int*,double*);
+void cdftnc(int*,double*,double*,double*,double*,double*,int*,double*);
+void cumbet(double*,double*,double*,double*,double*,double*);
+void cumbin(double*,double*,double*,double*,double*,double*);
+void cumchi(double*,double*,double*,double*);
+void cumchn(double*,double*,double*,double*,double*);
+void cumf(double*,double*,double*,double*,double*);
+void cumfnc(double*,double*,double*,double*,double*,double*);
+void cumgam(double*,double*,double*,double*);
+void cumnbn(double*,double*,double*,double*,double*,double*);
+void cumnor(double*,double*,double*);
+void cumpoi(double*,double*,double*,double*);
+void cumt(double*,double*,double*,double*);
+void cumtnc(double*,double*,double*,double*,double*);
+double devlpl(double [],int*,double*);
+double dinvnr(double *p,double *q);
+void dinvr(int*,double*,double*,unsigned long*,unsigned long*);
+void dstinv(double*,double*,double*,double*,double*,double*,
+            double*);
+double dt1(double*,double*,double*);
+void dzror(int*,double*,double*,double*,double *,
+           unsigned long*,unsigned long*);
+void dstzr(double *zxlo,double *zxhi,double *zabstl,double *zreltl);
+double erf1(double*);
+double erfc1(int*,double*);
+double esum(int*,double*);
+double exparg(int*);
+double fpser(double*,double*,double*,double*);
+double gam1(double*);
+void gaminv(double*,double*,double*,double*,double*,int*);
+double gamln(double*);
+double gamln1(double*);
+double Xgamm(double*);
+void grat1(double*,double*,double*,double*,double*,double*);
+void gratio(double*,double*,double*,double*,int*);
+double gsumln(double*,double*);
+double psi(double*);
+double rcomp(double*,double*);
+double rexp(double*);
+double rlog(double*);
+double rlog1(double*);
+double spmpar(int*);
+double stvaln(double*);
+double fifdint(double);
+double fifdmax1(double,double);
+double fifdmin1(double,double);
+double fifdsign(double,double);
+long fifidint(double);
+long fifmod(long,long);
+void ftnstop(char*);
+extern int ipmpar(int*);
+
diff --git a/lib/dcdflib/dcdflib.c b/lib/dcdflib/dcdflib.c
new file mode 100644 (file)
index 0000000..91f606b
--- /dev/null
@@ -0,0 +1,9093 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "cdflib.h"
+
+static void E0000(int,int*,double*,double*,unsigned long*,
+                  unsigned long*,double*,double*,double*,
+                  double*,double*,double*,double*);
+static void E0001(int,int*,double*,double*,double*,double*,
+                  unsigned long*,unsigned long*,double*,double*,
+                  double*,double*);
+
+/*
+ * A comment about ints and longs - whether ints or longs are used should
+ * make no difference, but where double r-values are assigned to ints the
+ * r-value is cast converted to a long, which is then assigned to the int
+ * to be compatible with the operation of fifidint.
+ */
+/*
+-----------------------------------------------------------------------
+     COMPUTATION OF LN(GAMMA(B)/GAMMA(A+B)) WHEN B .GE. 8
+                         --------
+     IN THIS ALGORITHM, DEL(X) IS THE FUNCTION DEFINED BY
+     LN(GAMMA(X)) = (X - 0.5)*LN(X) - X + 0.5*LN(2*PI) + DEL(X).
+-----------------------------------------------------------------------
+*/
+double algdiv(double *a,double *b)
+{
+static double c0 = .833333333333333e-01;
+static double c1 = -.277777777760991e-02;
+static double c2 = .793650666825390e-03;
+static double c3 = -.595202931351870e-03;
+static double c4 = .837308034031215e-03;
+static double c5 = -.165322962780713e-02;
+static double algdiv,c,d,h,s11,s3,s5,s7,s9,t,u,v,w,x,x2,T1;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(*a <= *b) goto S10;
+    h = *b/ *a;
+    c = 1.0e0/(1.0e0+h);
+    x = h/(1.0e0+h);
+    d = *a+(*b-0.5e0);
+    goto S20;
+S10:
+    h = *a/ *b;
+    c = h/(1.0e0+h);
+    x = 1.0e0/(1.0e0+h);
+    d = *b+(*a-0.5e0);
+S20:
+/*
+                SET SN = (1 - X**N)/(1 - X)
+*/
+    x2 = x*x;
+    s3 = 1.0e0+(x+x2);
+    s5 = 1.0e0+(x+x2*s3);
+    s7 = 1.0e0+(x+x2*s5);
+    s9 = 1.0e0+(x+x2*s7);
+    s11 = 1.0e0+(x+x2*s9);
+/*
+                SET W = DEL(B) - DEL(A + B)
+*/
+    t = pow(1.0e0/ *b,2.0);
+    w = ((((c5*s11*t+c4*s9)*t+c3*s7)*t+c2*s5)*t+c1*s3)*t+c0;
+    w *= (c/ *b);
+/*
+                    COMBINE THE RESULTS
+*/
+    T1 = *a/ *b;
+    u = d*alnrel(&T1);
+    v = *a*(log(*b)-1.0e0);
+    if(u <= v) goto S30;
+    algdiv = w-v-u;
+    return algdiv;
+S30:
+    algdiv = w-u-v;
+    return algdiv;
+}
+double alngam(double *x)
+/*
+**********************************************************************
+     double alngam(double *x)
+                 double precision LN of the GAMma function
+                              Function
+     Returns the natural logarithm of GAMMA(X).
+                              Arguments
+     X --> value at which scaled log gamma is to be returned
+                    X is DOUBLE PRECISION
+                              Method
+     If X .le. 6.0, then use recursion to get X below 3
+     then apply rational approximation number 5236 of
+     Hart et al, Computer Approximations, John Wiley and
+     Sons, NY, 1968.
+     If X .gt. 6.0, then use recursion to get X to at least 12 and
+     then use formula 5423 of the same source.
+**********************************************************************
+*/
+{
+#define hln2pi 0.91893853320467274178e0
+static double coef[5] = {
+    0.83333333333333023564e-1,-0.27777777768818808e-2,0.79365006754279e-3,
+    -0.594997310889e-3,0.8065880899e-3
+};
+static double scoefd[4] = {
+    0.62003838007126989331e2,0.9822521104713994894e1,-0.8906016659497461257e1,
+    0.1000000000000000000e1
+};
+static double scoefn[9] = {
+    0.62003838007127258804e2,0.36036772530024836321e2,0.20782472531792126786e2,
+    0.6338067999387272343e1,0.215994312846059073e1,0.3980671310203570498e0,
+    0.1093115956710439502e0,0.92381945590275995e-2,0.29737866448101651e-2
+};
+static int K1 = 9;
+static int K3 = 4;
+static int K5 = 5;
+static double alngam,offset,prod,xx;
+static int i,n;
+static double T2,T4,T6;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(!(*x <= 6.0e0)) goto S70;
+    prod = 1.0e0;
+    xx = *x;
+    if(!(*x > 3.0e0)) goto S30;
+S10:
+    if(!(xx > 3.0e0)) goto S20;
+    xx -= 1.0e0;
+    prod *= xx;
+    goto S10;
+S30:
+S20:
+    if(!(*x < 2.0e0)) goto S60;
+S40:
+    if(!(xx < 2.0e0)) goto S50;
+    prod /= xx;
+    xx += 1.0e0;
+    goto S40;
+S60:
+S50:
+    T2 = xx-2.0e0;
+    T4 = xx-2.0e0;
+    alngam = devlpl(scoefn,&K1,&T2)/devlpl(scoefd,&K3,&T4);
+/*
+     COMPUTE RATIONAL APPROXIMATION TO GAMMA(X)
+*/
+    alngam *= prod;
+    alngam = log(alngam);
+    goto S110;
+S70:
+    offset = hln2pi;
+/*
+     IF NECESSARY MAKE X AT LEAST 12 AND CARRY CORRECTION IN OFFSET
+*/
+    n = fifidint(12.0e0-*x);
+    if(!(n > 0)) goto S90;
+    prod = 1.0e0;
+    for(i=1; i<=n; i++) prod *= (*x+(double)(i-1));
+    offset -= log(prod);
+    xx = *x+(double)n;
+    goto S100;
+S90:
+    xx = *x;
+S100:
+/*
+     COMPUTE POWER SERIES
+*/
+    T6 = 1.0e0/pow(xx,2.0);
+    alngam = devlpl(coef,&K5,&T6)/xx;
+    alngam += (offset+(xx-0.5e0)*log(xx)-xx);
+S110:
+    return alngam;
+#undef hln2pi
+}
+double alnrel(double *a)
+/*
+-----------------------------------------------------------------------
+            EVALUATION OF THE FUNCTION LN(1 + A)
+-----------------------------------------------------------------------
+*/
+{
+static double p1 = -.129418923021993e+01;
+static double p2 = .405303492862024e+00;
+static double p3 = -.178874546012214e-01;
+static double q1 = -.162752256355323e+01;
+static double q2 = .747811014037616e+00;
+static double q3 = -.845104217945565e-01;
+static double alnrel,t,t2,w,x;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(fabs(*a) > 0.375e0) goto S10;
+    t = *a/(*a+2.0e0);
+    t2 = t*t;
+    w = (((p3*t2+p2)*t2+p1)*t2+1.0e0)/(((q3*t2+q2)*t2+q1)*t2+1.0e0);
+    alnrel = 2.0e0*t*w;
+    return alnrel;
+S10:
+    x = 1.e0+*a;
+    alnrel = log(x);
+    return alnrel;
+}
+double apser(double *a,double *b,double *x,double *eps)
+/*
+-----------------------------------------------------------------------
+     APSER YIELDS THE INCOMPLETE BETA RATIO I(SUB(1-X))(B,A) FOR
+     A .LE. MIN(EPS,EPS*B), B*X .LE. 1, AND X .LE. 0.5. USED WHEN
+     A IS VERY SMALL. USE ONLY IF ABOVE INEQUALITIES ARE SATISFIED.
+-----------------------------------------------------------------------
+*/
+{
+static double g = .577215664901533e0;
+static double apser,aj,bx,c,j,s,t,tol;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    bx = *b**x;
+    t = *x-bx;
+    if(*b**eps > 2.e-2) goto S10;
+    c = log(*x)+psi(b)+g+t;
+    goto S20;
+S10:
+    c = log(bx)+g+t;
+S20:
+    tol = 5.0e0**eps*fabs(c);
+    j = 1.0e0;
+    s = 0.0e0;
+S30:
+    j += 1.0e0;
+    t *= (*x-bx/j);
+    aj = t/j;
+    s += aj;
+    if(fabs(aj) > tol) goto S30;
+    apser = -(*a*(c+s));
+    return apser;
+}
+double basym(double *a,double *b,double *lambda,double *eps)
+/*
+-----------------------------------------------------------------------
+     ASYMPTOTIC EXPANSION FOR IX(A,B) FOR LARGE A AND B.
+     LAMBDA = (A + B)*Y - B  AND EPS IS THE TOLERANCE USED.
+     IT IS ASSUMED THAT LAMBDA IS NONNEGATIVE AND THAT
+     A AND B ARE GREATER THAN OR EQUAL TO 15.
+-----------------------------------------------------------------------
+*/
+{
+static double e0 = 1.12837916709551e0;
+static double e1 = .353553390593274e0;
+static int num = 20;
+/*
+------------------------
+     ****** NUM IS THE MAXIMUM VALUE THAT N CAN TAKE IN THE DO LOOP
+            ENDING AT STATEMENT 50. IT IS REQUIRED THAT NUM BE EVEN.
+            THE ARRAYS A0, B0, C, D HAVE DIMENSION NUM + 1.
+------------------------
+     E0 = 2/SQRT(PI)
+     E1 = 2**(-3/2)
+------------------------
+*/
+static int K3 = 1;
+static double basym,bsum,dsum,f,h,h2,hn,j0,j1,r,r0,r1,s,sum,t,t0,t1,u,w,w0,z,z0,
+    z2,zn,znm1;
+static int i,im1,imj,j,m,mm1,mmj,n,np1;
+static double a0[21],b0[21],c[21],d[21],T1,T2;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    basym = 0.0e0;
+    if(*a >= *b) goto S10;
+    h = *a/ *b;
+    r0 = 1.0e0/(1.0e0+h);
+    r1 = (*b-*a)/ *b;
+    w0 = 1.0e0/sqrt(*a*(1.0e0+h));
+    goto S20;
+S10:
+    h = *b/ *a;
+    r0 = 1.0e0/(1.0e0+h);
+    r1 = (*b-*a)/ *a;
+    w0 = 1.0e0/sqrt(*b*(1.0e0+h));
+S20:
+    T1 = -(*lambda/ *a);
+    T2 = *lambda/ *b;
+    f = *a*rlog1(&T1)+*b*rlog1(&T2);
+    t = exp(-f);
+    if(t == 0.0e0) return basym;
+    z0 = sqrt(f);
+    z = 0.5e0*(z0/e1);
+    z2 = f+f;
+    a0[0] = 2.0e0/3.0e0*r1;
+    c[0] = -(0.5e0*a0[0]);
+    d[0] = -c[0];
+    j0 = 0.5e0/e0*erfc1(&K3,&z0);
+    j1 = e1;
+    sum = j0+d[0]*w0*j1;
+    s = 1.0e0;
+    h2 = h*h;
+    hn = 1.0e0;
+    w = w0;
+    znm1 = z;
+    zn = z2;
+    for(n=2; n<=num; n+=2) {
+        hn = h2*hn;
+        a0[n-1] = 2.0e0*r0*(1.0e0+h*hn)/((double)n+2.0e0);
+        np1 = n+1;
+        s += hn;
+        a0[np1-1] = 2.0e0*r1*s/((double)n+3.0e0);
+        for(i=n; i<=np1; i++) {
+            r = -(0.5e0*((double)i+1.0e0));
+            b0[0] = r*a0[0];
+            for(m=2; m<=i; m++) {
+                bsum = 0.0e0;
+                mm1 = m-1;
+                for(j=1; j<=mm1; j++) {
+                    mmj = m-j;
+                    bsum += (((double)j*r-(double)mmj)*a0[j-1]*b0[mmj-1]);
+                }
+                b0[m-1] = r*a0[m-1]+bsum/(double)m;
+            }
+            c[i-1] = b0[i-1]/((double)i+1.0e0);
+            dsum = 0.0e0;
+            im1 = i-1;
+            for(j=1; j<=im1; j++) {
+                imj = i-j;
+                dsum += (d[imj-1]*c[j-1]);
+            }
+            d[i-1] = -(dsum+c[i-1]);
+        }
+        j0 = e1*znm1+((double)n-1.0e0)*j0;
+        j1 = e1*zn+(double)n*j1;
+        znm1 = z2*znm1;
+        zn = z2*zn;
+        w = w0*w;
+        t0 = d[n-1]*w*j0;
+        w = w0*w;
+        t1 = d[np1-1]*w*j1;
+        sum += (t0+t1);
+        if(fabs(t0)+fabs(t1) <= *eps*sum) goto S80;
+    }
+S80:
+    u = exp(-bcorr(a,b));
+    basym = e0*t*u*sum;
+    return basym;
+}
+double bcorr(double *a0,double *b0)
+/*
+-----------------------------------------------------------------------
+     EVALUATION OF  DEL(A0) + DEL(B0) - DEL(A0 + B0)  WHERE
+     LN(GAMMA(A)) = (A - 0.5)*LN(A) - A + 0.5*LN(2*PI) + DEL(A).
+     IT IS ASSUMED THAT A0 .GE. 8 AND B0 .GE. 8.
+-----------------------------------------------------------------------
+*/
+{
+static double c0 = .833333333333333e-01;
+static double c1 = -.277777777760991e-02;
+static double c2 = .793650666825390e-03;
+static double c3 = -.595202931351870e-03;
+static double c4 = .837308034031215e-03;
+static double c5 = -.165322962780713e-02;
+static double bcorr,a,b,c,h,s11,s3,s5,s7,s9,t,w,x,x2;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    a = fifdmin1(*a0,*b0);
+    b = fifdmax1(*a0,*b0);
+    h = a/b;
+    c = h/(1.0e0+h);
+    x = 1.0e0/(1.0e0+h);
+    x2 = x*x;
+/*
+                SET SN = (1 - X**N)/(1 - X)
+*/
+    s3 = 1.0e0+(x+x2);
+    s5 = 1.0e0+(x+x2*s3);
+    s7 = 1.0e0+(x+x2*s5);
+    s9 = 1.0e0+(x+x2*s7);
+    s11 = 1.0e0+(x+x2*s9);
+/*
+                SET W = DEL(B) - DEL(A + B)
+*/
+    t = pow(1.0e0/b,2.0);
+    w = ((((c5*s11*t+c4*s9)*t+c3*s7)*t+c2*s5)*t+c1*s3)*t+c0;
+    w *= (c/b);
+/*
+                   COMPUTE  DEL(A) + W
+*/
+    t = pow(1.0e0/a,2.0);
+    bcorr = (((((c5*t+c4)*t+c3)*t+c2)*t+c1)*t+c0)/a+w;
+    return bcorr;
+}
+double betaln(double *a0,double *b0)
+/*
+-----------------------------------------------------------------------
+     EVALUATION OF THE LOGARITHM OF THE BETA FUNCTION
+-----------------------------------------------------------------------
+     E = 0.5*LN(2*PI)
+--------------------------
+*/
+{
+static double e = .918938533204673e0;
+static double betaln,a,b,c,h,u,v,w,z;
+static int i,n;
+static double T1;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    a = fifdmin1(*a0,*b0);
+    b = fifdmax1(*a0,*b0);
+    if(a >= 8.0e0) goto S100;
+    if(a >= 1.0e0) goto S20;
+/*
+-----------------------------------------------------------------------
+                   PROCEDURE WHEN A .LT. 1
+-----------------------------------------------------------------------
+*/
+    if(b >= 8.0e0) goto S10;
+    T1 = a+b;
+    betaln = gamln(&a)+(gamln(&b)-gamln(&T1));
+    return betaln;
+S10:
+    betaln = gamln(&a)+algdiv(&a,&b);
+    return betaln;
+S20:
+/*
+-----------------------------------------------------------------------
+                PROCEDURE WHEN 1 .LE. A .LT. 8
+-----------------------------------------------------------------------
+*/
+    if(a > 2.0e0) goto S40;
+    if(b > 2.0e0) goto S30;
+    betaln = gamln(&a)+gamln(&b)-gsumln(&a,&b);
+    return betaln;
+S30:
+    w = 0.0e0;
+    if(b < 8.0e0) goto S60;
+    betaln = gamln(&a)+algdiv(&a,&b);
+    return betaln;
+S40:
+/*
+                REDUCTION OF A WHEN B .LE. 1000
+*/
+    if(b > 1000.0e0) goto S80;
+    n = (long)(a - 1.0e0);
+    w = 1.0e0;
+    for(i=1; i<=n; i++) {
+        a -= 1.0e0;
+        h = a/b;
+        w *= (h/(1.0e0+h));
+    }
+    w = log(w);
+    if(b < 8.0e0) goto S60;
+    betaln = w+gamln(&a)+algdiv(&a,&b);
+    return betaln;
+S60:
+/*
+                 REDUCTION OF B WHEN B .LT. 8
+*/
+    n = (long)(b - 1.0e0);
+    z = 1.0e0;
+    for(i=1; i<=n; i++) {
+        b -= 1.0e0;
+        z *= (b/(a+b));
+    }
+    betaln = w+log(z)+(gamln(&a)+(gamln(&b)-gsumln(&a,&b)));
+    return betaln;
+S80:
+/*
+                REDUCTION OF A WHEN B .GT. 1000
+*/
+    n = (long)(a - 1.0e0);
+    w = 1.0e0;
+    for(i=1; i<=n; i++) {
+        a -= 1.0e0;
+        w *= (a/(1.0e0+a/b));
+    }
+    betaln = log(w)-(double)n*log(b)+(gamln(&a)+algdiv(&a,&b));
+    return betaln;
+S100:
+/*
+-----------------------------------------------------------------------
+                   PROCEDURE WHEN A .GE. 8
+-----------------------------------------------------------------------
+*/
+    w = bcorr(&a,&b);
+    h = a/b;
+    c = h/(1.0e0+h);
+    u = -((a-0.5e0)*log(c));
+    v = b*alnrel(&h);
+    if(u <= v) goto S110;
+    betaln = -(0.5e0*log(b))+e+w-v-u;
+    return betaln;
+S110:
+    betaln = -(0.5e0*log(b))+e+w-u-v;
+    return betaln;
+}
+double bfrac(double *a,double *b,double *x,double *y,double *lambda,
+            double *eps)
+/*
+-----------------------------------------------------------------------
+     CONTINUED FRACTION EXPANSION FOR IX(A,B) WHEN A,B .GT. 1.
+     IT IS ASSUMED THAT  LAMBDA = (A + B)*Y - B.
+-----------------------------------------------------------------------
+*/
+{
+static double bfrac,alpha,an,anp1,beta,bn,bnp1,c,c0,c1,e,n,p,r,r0,s,t,w,yp1;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    bfrac = brcomp(a,b,x,y);
+    if(bfrac == 0.0e0) return bfrac;
+    c = 1.0e0+*lambda;
+    c0 = *b/ *a;
+    c1 = 1.0e0+1.0e0/ *a;
+    yp1 = *y+1.0e0;
+    n = 0.0e0;
+    p = 1.0e0;
+    s = *a+1.0e0;
+    an = 0.0e0;
+    bn = anp1 = 1.0e0;
+    bnp1 = c/c1;
+    r = c1/c;
+S10:
+/*
+        CONTINUED FRACTION CALCULATION
+*/
+    n += 1.0e0;
+    t = n/ *a;
+    w = n*(*b-n)**x;
+    e = *a/s;
+    alpha = p*(p+c0)*e*e*(w**x);
+    e = (1.0e0+t)/(c1+t+t);
+    beta = n+w/s+e*(c+n*yp1);
+    p = 1.0e0+t;
+    s += 2.0e0;
+/*
+        UPDATE AN, BN, ANP1, AND BNP1
+*/
+    t = alpha*an+beta*anp1;
+    an = anp1;
+    anp1 = t;
+    t = alpha*bn+beta*bnp1;
+    bn = bnp1;
+    bnp1 = t;
+    r0 = r;
+    r = anp1/bnp1;
+    if(fabs(r-r0) <= *eps*r) goto S20;
+/*
+        RESCALE AN, BN, ANP1, AND BNP1
+*/
+    an /= bnp1;
+    bn /= bnp1;
+    anp1 = r;
+    bnp1 = 1.0e0;
+    goto S10;
+S20:
+/*
+                 TERMINATION
+*/
+    bfrac *= r;
+    return bfrac;
+}
+void bgrat(double *a,double *b,double *x,double *y,double *w,
+          double *eps,int *ierr)
+/*
+-----------------------------------------------------------------------
+     ASYMPTOTIC EXPANSION FOR IX(A,B) WHEN A IS LARGER THAN B.
+     THE RESULT OF THE EXPANSION IS ADDED TO W. IT IS ASSUMED
+     THAT A .GE. 15 AND B .LE. 1.  EPS IS THE TOLERANCE USED.
+     IERR IS A VARIABLE THAT REPORTS THE STATUS OF THE RESULTS.
+-----------------------------------------------------------------------
+*/
+{
+static double bm1,bp2n,cn,coef,dj,j,l,lnx,n2,nu,p,q,r,s,sum,t,t2,u,v,z;
+static int i,n,nm1;
+static double c[30],d[30],T1;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    bm1 = *b-0.5e0-0.5e0;
+    nu = *a+0.5e0*bm1;
+    if(*y > 0.375e0) goto S10;
+    T1 = -*y;
+    lnx = alnrel(&T1);
+    goto S20;
+S10:
+    lnx = log(*x);
+S20:
+    z = -(nu*lnx);
+    if(*b*z == 0.0e0) goto S70;
+/*
+                 COMPUTATION OF THE EXPANSION
+                 SET R = EXP(-Z)*Z**B/GAMMA(B)
+*/
+    r = *b*(1.0e0+gam1(b))*exp(*b*log(z));
+    r *= (exp(*a*lnx)*exp(0.5e0*bm1*lnx));
+    u = algdiv(b,a)+*b*log(nu);
+    u = r*exp(-u);
+    if(u == 0.0e0) goto S70;
+    grat1(b,&z,&r,&p,&q,eps);
+    v = 0.25e0*pow(1.0e0/nu,2.0);
+    t2 = 0.25e0*lnx*lnx;
+    l = *w/u;
+    j = q/r;
+    sum = j;
+    t = cn = 1.0e0;
+    n2 = 0.0e0;
+    for(n=1; n<=30; n++) {
+        bp2n = *b+n2;
+        j = (bp2n*(bp2n+1.0e0)*j+(z+bp2n+1.0e0)*t)*v;
+        n2 += 2.0e0;
+        t *= t2;
+        cn /= (n2*(n2+1.0e0));
+        c[n-1] = cn;
+        s = 0.0e0;
+        if(n == 1) goto S40;
+        nm1 = n-1;
+        coef = *b-(double)n;
+        for(i=1; i<=nm1; i++) {
+            s += (coef*c[i-1]*d[n-i-1]);
+            coef += *b;
+        }
+S40:
+        d[n-1] = bm1*cn+s/(double)n;
+        dj = d[n-1]*j;
+        sum += dj;
+        if(sum <= 0.0e0) goto S70;
+        if(fabs(dj) <= *eps*(sum+l)) goto S60;
+    }
+S60:
+/*
+                    ADD THE RESULTS TO W
+*/
+    *ierr = 0;
+    *w += (u*sum);
+    return;
+S70:
+/*
+               THE EXPANSION CANNOT BE COMPUTED
+*/
+    *ierr = 1;
+    return;
+}
+double bpser(double *a,double *b,double *x,double *eps)
+/*
+-----------------------------------------------------------------------
+     POWER SERIES EXPANSION FOR EVALUATING IX(A,B) WHEN B .LE. 1
+     OR B*X .LE. 0.7.  EPS IS THE TOLERANCE USED.
+-----------------------------------------------------------------------
+*/
+{
+static double bpser,a0,apb,b0,c,n,sum,t,tol,u,w,z;
+static int i,m;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    bpser = 0.0e0;
+    if(*x == 0.0e0) return bpser;
+/*
+-----------------------------------------------------------------------
+            COMPUTE THE FACTOR X**A/(A*BETA(A,B))
+-----------------------------------------------------------------------
+*/
+    a0 = fifdmin1(*a,*b);
+    if(a0 < 1.0e0) goto S10;
+    z = *a*log(*x)-betaln(a,b);
+    bpser = exp(z)/ *a;
+    goto S100;
+S10:
+    b0 = fifdmax1(*a,*b);
+    if(b0 >= 8.0e0) goto S90;
+    if(b0 > 1.0e0) goto S40;
+/*
+            PROCEDURE FOR A0 .LT. 1 AND B0 .LE. 1
+*/
+    bpser = pow(*x,*a);
+    if(bpser == 0.0e0) return bpser;
+    apb = *a+*b;
+    if(apb > 1.0e0) goto S20;
+    z = 1.0e0+gam1(&apb);
+    goto S30;
+S20:
+    u = *a+*b-1.e0;
+    z = (1.0e0+gam1(&u))/apb;
+S30:
+    c = (1.0e0+gam1(a))*(1.0e0+gam1(b))/z;
+    bpser *= (c*(*b/apb));
+    goto S100;
+S40:
+/*
+         PROCEDURE FOR A0 .LT. 1 AND 1 .LT. B0 .LT. 8
+*/
+    u = gamln1(&a0);
+    m = (long)(b0 - 1.0e0);
+    if(m < 1) goto S60;
+    c = 1.0e0;
+    for(i=1; i<=m; i++) {
+        b0 -= 1.0e0;
+        c *= (b0/(a0+b0));
+    }
+    u = log(c)+u;
+S60:
+    z = *a*log(*x)-u;
+    b0 -= 1.0e0;
+    apb = a0+b0;
+    if(apb > 1.0e0) goto S70;
+    t = 1.0e0+gam1(&apb);
+    goto S80;
+S70:
+    u = a0+b0-1.e0;
+    t = (1.0e0+gam1(&u))/apb;
+S80:
+    bpser = exp(z)*(a0/ *a)*(1.0e0+gam1(&b0))/t;
+    goto S100;
+S90:
+/*
+            PROCEDURE FOR A0 .LT. 1 AND B0 .GE. 8
+*/
+    u = gamln1(&a0)+algdiv(&a0,&b0);
+    z = *a*log(*x)-u;
+    bpser = a0/ *a*exp(z);
+S100:
+    if(bpser == 0.0e0 || *a <= 0.1e0**eps) return bpser;
+/*
+-----------------------------------------------------------------------
+                     COMPUTE THE SERIES
+-----------------------------------------------------------------------
+*/
+    sum = n = 0.0e0;
+    c = 1.0e0;
+    tol = *eps/ *a;
+S110:
+    n += 1.0e0;
+    c *= ((0.5e0+(0.5e0-*b/n))**x);
+    w = c/(*a+n);
+    sum += w;
+    if(fabs(w) > tol) goto S110;
+    bpser *= (1.0e0+*a*sum);
+    return bpser;
+}
+void bratio(double *a,double *b,double *x,double *y,double *w,
+           double *w1,int *ierr)
+/*
+-----------------------------------------------------------------------
+            EVALUATION OF THE INCOMPLETE BETA FUNCTION IX(A,B)
+                     --------------------
+     IT IS ASSUMED THAT A AND B ARE NONNEGATIVE, AND THAT X .LE. 1
+     AND Y = 1 - X.  BRATIO ASSIGNS W AND W1 THE VALUES
+                      W  = IX(A,B)
+                      W1 = 1 - IX(A,B)
+     IERR IS A VARIABLE THAT REPORTS THE STATUS OF THE RESULTS.
+     IF NO INPUT ERRORS ARE DETECTED THEN IERR IS SET TO 0 AND
+     W AND W1 ARE COMPUTED. OTHERWISE, IF AN ERROR IS DETECTED,
+     THEN W AND W1 ARE ASSIGNED THE VALUE 0 AND IERR IS SET TO
+     ONE OF THE FOLLOWING VALUES ...
+        IERR = 1  IF A OR B IS NEGATIVE
+        IERR = 2  IF A = B = 0
+        IERR = 3  IF X .LT. 0 OR X .GT. 1
+        IERR = 4  IF Y .LT. 0 OR Y .GT. 1
+        IERR = 5  IF X + Y .NE. 1
+        IERR = 6  IF X = A = 0
+        IERR = 7  IF Y = B = 0
+--------------------
+     WRITTEN BY ALFRED H. MORRIS, JR.
+        NAVAL SURFACE WARFARE CENTER
+        DAHLGREN, VIRGINIA
+     REVISED ... NOV 1991
+-----------------------------------------------------------------------
+*/
+{
+static int K1 = 1;
+static double a0,b0,eps,lambda,t,x0,y0,z;
+static int ierr1,ind,n;
+static double T2,T3,T4,T5;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     ****** EPS IS A MACHINE DEPENDENT CONSTANT. EPS IS THE SMALLEST
+            FLOATING POINT NUMBER FOR WHICH 1.0 + EPS .GT. 1.0
+*/
+    eps = spmpar(&K1);
+    *w = *w1 = 0.0e0;
+    if(*a < 0.0e0 || *b < 0.0e0) goto S270;
+    if(*a == 0.0e0 && *b == 0.0e0) goto S280;
+    if(*x < 0.0e0 || *x > 1.0e0) goto S290;
+    if(*y < 0.0e0 || *y > 1.0e0) goto S300;
+    z = *x+*y-0.5e0-0.5e0;
+    if(fabs(z) > 3.0e0*eps) goto S310;
+    *ierr = 0;
+    if(*x == 0.0e0) goto S210;
+    if(*y == 0.0e0) goto S230;
+    if(*a == 0.0e0) goto S240;
+    if(*b == 0.0e0) goto S220;
+    eps = fifdmax1(eps,1.e-15);
+    if(fifdmax1(*a,*b) < 1.e-3*eps) goto S260;
+    ind = 0;
+    a0 = *a;
+    b0 = *b;
+    x0 = *x;
+    y0 = *y;
+    if(fifdmin1(a0,b0) > 1.0e0) goto S40;
+/*
+             PROCEDURE FOR A0 .LE. 1 OR B0 .LE. 1
+*/
+    if(*x <= 0.5e0) goto S10;
+    ind = 1;
+    a0 = *b;
+    b0 = *a;
+    x0 = *y;
+    y0 = *x;
+S10:
+    if(b0 < fifdmin1(eps,eps*a0)) goto S90;
+    if(a0 < fifdmin1(eps,eps*b0) && b0*x0 <= 1.0e0) goto S100;
+    if(fifdmax1(a0,b0) > 1.0e0) goto S20;
+    if(a0 >= fifdmin1(0.2e0,b0)) goto S110;
+    if(pow(x0,a0) <= 0.9e0) goto S110;
+    if(x0 >= 0.3e0) goto S120;
+    n = 20;
+    goto S140;
+S20:
+    if(b0 <= 1.0e0) goto S110;
+    if(x0 >= 0.3e0) goto S120;
+    if(x0 >= 0.1e0) goto S30;
+    if(pow(x0*b0,a0) <= 0.7e0) goto S110;
+S30:
+    if(b0 > 15.0e0) goto S150;
+    n = 20;
+    goto S140;
+S40:
+/*
+             PROCEDURE FOR A0 .GT. 1 AND B0 .GT. 1
+*/
+    if(*a > *b) goto S50;
+    lambda = *a-(*a+*b)**x;
+    goto S60;
+S50:
+    lambda = (*a+*b)**y-*b;
+S60:
+    if(lambda >= 0.0e0) goto S70;
+    ind = 1;
+    a0 = *b;
+    b0 = *a;
+    x0 = *y;
+    y0 = *x;
+    lambda = fabs(lambda);
+S70:
+    if(b0 < 40.0e0 && b0*x0 <= 0.7e0) goto S110;
+    if(b0 < 40.0e0) goto S160;
+    if(a0 > b0) goto S80;
+    if(a0 <= 100.0e0) goto S130;
+    if(lambda > 0.03e0*a0) goto S130;
+    goto S200;
+S80:
+    if(b0 <= 100.0e0) goto S130;
+    if(lambda > 0.03e0*b0) goto S130;
+    goto S200;
+S90:
+/*
+            EVALUATION OF THE APPROPRIATE ALGORITHM
+*/
+    *w = fpser(&a0,&b0,&x0,&eps);
+    *w1 = 0.5e0+(0.5e0-*w);
+    goto S250;
+S100:
+    *w1 = apser(&a0,&b0,&x0,&eps);
+    *w = 0.5e0+(0.5e0-*w1);
+    goto S250;
+S110:
+    *w = bpser(&a0,&b0,&x0,&eps);
+    *w1 = 0.5e0+(0.5e0-*w);
+    goto S250;
+S120:
+    *w1 = bpser(&b0,&a0,&y0,&eps);
+    *w = 0.5e0+(0.5e0-*w1);
+    goto S250;
+S130:
+    T2 = 15.0e0*eps;
+    *w = bfrac(&a0,&b0,&x0,&y0,&lambda,&T2);
+    *w1 = 0.5e0+(0.5e0-*w);
+    goto S250;
+S140:
+    *w1 = bup(&b0,&a0,&y0,&x0,&n,&eps);
+    b0 += (double)n;
+S150:
+    T3 = 15.0e0*eps;
+    bgrat(&b0,&a0,&y0,&x0,w1,&T3,&ierr1);
+    *w = 0.5e0+(0.5e0-*w1);
+    goto S250;
+S160:
+    n = (long)(b0);
+    b0 -= (double)n;
+    if(b0 != 0.0e0) goto S170;
+    n -= 1;
+    b0 = 1.0e0;
+S170:
+    *w = bup(&b0,&a0,&y0,&x0,&n,&eps);
+    if(x0 > 0.7e0) goto S180;
+    *w += bpser(&a0,&b0,&x0,&eps);
+    *w1 = 0.5e0+(0.5e0-*w);
+    goto S250;
+S180:
+    if(a0 > 15.0e0) goto S190;
+    n = 20;
+    *w += bup(&a0,&b0,&x0,&y0,&n,&eps);
+    a0 += (double)n;
+S190:
+    T4 = 15.0e0*eps;
+    bgrat(&a0,&b0,&x0,&y0,w,&T4,&ierr1);
+    *w1 = 0.5e0+(0.5e0-*w);
+    goto S250;
+S200:
+    T5 = 100.0e0*eps;
+    *w = basym(&a0,&b0,&lambda,&T5);
+    *w1 = 0.5e0+(0.5e0-*w);
+    goto S250;
+S210:
+/*
+               TERMINATION OF THE PROCEDURE
+*/
+    if(*a == 0.0e0) goto S320;
+S220:
+    *w = 0.0e0;
+    *w1 = 1.0e0;
+    return;
+S230:
+    if(*b == 0.0e0) goto S330;
+S240:
+    *w = 1.0e0;
+    *w1 = 0.0e0;
+    return;
+S250:
+    if(ind == 0) return;
+    t = *w;
+    *w = *w1;
+    *w1 = t;
+    return;
+S260:
+/*
+           PROCEDURE FOR A AND B .LT. 1.E-3*EPS
+*/
+    *w = *b/(*a+*b);
+    *w1 = *a/(*a+*b);
+    return;
+S270:
+/*
+                       ERROR RETURN
+*/
+    *ierr = 1;
+    return;
+S280:
+    *ierr = 2;
+    return;
+S290:
+    *ierr = 3;
+    return;
+S300:
+    *ierr = 4;
+    return;
+S310:
+    *ierr = 5;
+    return;
+S320:
+    *ierr = 6;
+    return;
+S330:
+    *ierr = 7;
+    return;
+}
+double brcmp1(int *mu,double *a,double *b,double *x,double *y)
+/*
+-----------------------------------------------------------------------
+          EVALUATION OF  EXP(MU) * (X**A*Y**B/BETA(A,B))
+-----------------------------------------------------------------------
+*/
+{
+static double Const = .398942280401433e0;
+static double brcmp1,a0,apb,b0,c,e,h,lambda,lnx,lny,t,u,v,x0,y0,z;
+static int i,n;
+/*
+-----------------
+     CONST = 1/SQRT(2*PI)
+-----------------
+*/
+static double T1,T2,T3,T4;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    a0 = fifdmin1(*a,*b);
+    if(a0 >= 8.0e0) goto S130;
+    if(*x > 0.375e0) goto S10;
+    lnx = log(*x);
+    T1 = -*x;
+    lny = alnrel(&T1);
+    goto S30;
+S10:
+    if(*y > 0.375e0) goto S20;
+    T2 = -*y;
+    lnx = alnrel(&T2);
+    lny = log(*y);
+    goto S30;
+S20:
+    lnx = log(*x);
+    lny = log(*y);
+S30:
+    z = *a*lnx+*b*lny;
+    if(a0 < 1.0e0) goto S40;
+    z -= betaln(a,b);
+    brcmp1 = esum(mu,&z);
+    return brcmp1;
+S40:
+/*
+-----------------------------------------------------------------------
+              PROCEDURE FOR A .LT. 1 OR B .LT. 1
+-----------------------------------------------------------------------
+*/
+    b0 = fifdmax1(*a,*b);
+    if(b0 >= 8.0e0) goto S120;
+    if(b0 > 1.0e0) goto S70;
+/*
+                   ALGORITHM FOR B0 .LE. 1
+*/
+    brcmp1 = esum(mu,&z);
+    if(brcmp1 == 0.0e0) return brcmp1;
+    apb = *a+*b;
+    if(apb > 1.0e0) goto S50;
+    z = 1.0e0+gam1(&apb);
+    goto S60;
+S50:
+    u = *a+*b-1.e0;
+    z = (1.0e0+gam1(&u))/apb;
+S60:
+    c = (1.0e0+gam1(a))*(1.0e0+gam1(b))/z;
+    brcmp1 = brcmp1*(a0*c)/(1.0e0+a0/b0);
+    return brcmp1;
+S70:
+/*
+                ALGORITHM FOR 1 .LT. B0 .LT. 8
+*/
+    u = gamln1(&a0);
+    n = (long)(b0 - 1.0e0);
+    if(n < 1) goto S90;
+    c = 1.0e0;
+    for(i=1; i<=n; i++) {
+        b0 -= 1.0e0;
+        c *= (b0/(a0+b0));
+    }
+    u = log(c)+u;
+S90:
+    z -= u;
+    b0 -= 1.0e0;
+    apb = a0+b0;
+    if(apb > 1.0e0) goto S100;
+    t = 1.0e0+gam1(&apb);
+    goto S110;
+S100:
+    u = a0+b0-1.e0;
+    t = (1.0e0+gam1(&u))/apb;
+S110:
+    brcmp1 = a0*esum(mu,&z)*(1.0e0+gam1(&b0))/t;
+    return brcmp1;
+S120:
+/*
+                   ALGORITHM FOR B0 .GE. 8
+*/
+    u = gamln1(&a0)+algdiv(&a0,&b0);
+    T3 = z-u;
+    brcmp1 = a0*esum(mu,&T3);
+    return brcmp1;
+S130:
+/*
+-----------------------------------------------------------------------
+              PROCEDURE FOR A .GE. 8 AND B .GE. 8
+-----------------------------------------------------------------------
+*/
+    if(*a > *b) goto S140;
+    h = *a/ *b;
+    x0 = h/(1.0e0+h);
+    y0 = 1.0e0/(1.0e0+h);
+    lambda = *a-(*a+*b)**x;
+    goto S150;
+S140:
+    h = *b/ *a;
+    x0 = 1.0e0/(1.0e0+h);
+    y0 = h/(1.0e0+h);
+    lambda = (*a+*b)**y-*b;
+S150:
+    e = -(lambda/ *a);
+    if(fabs(e) > 0.6e0) goto S160;
+    u = rlog1(&e);
+    goto S170;
+S160:
+    u = e-log(*x/x0);
+S170:
+    e = lambda/ *b;
+    if(fabs(e) > 0.6e0) goto S180;
+    v = rlog1(&e);
+    goto S190;
+S180:
+    v = e-log(*y/y0);
+S190:
+    T4 = -(*a*u+*b*v);
+    z = esum(mu,&T4);
+    brcmp1 = Const*sqrt(*b*x0)*z*exp(-bcorr(a,b));
+    return brcmp1;
+}
+double brcomp(double *a,double *b,double *x,double *y)
+/*
+-----------------------------------------------------------------------
+               EVALUATION OF X**A*Y**B/BETA(A,B)
+-----------------------------------------------------------------------
+*/
+{
+static double Const = .398942280401433e0;
+static double brcomp,a0,apb,b0,c,e,h,lambda,lnx,lny,t,u,v,x0,y0,z;
+static int i,n;
+/*
+-----------------
+     CONST = 1/SQRT(2*PI)
+-----------------
+*/
+static double T1,T2;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    brcomp = 0.0e0;
+    if(*x == 0.0e0 || *y == 0.0e0) return brcomp;
+    a0 = fifdmin1(*a,*b);
+    if(a0 >= 8.0e0) goto S130;
+    if(*x > 0.375e0) goto S10;
+    lnx = log(*x);
+    T1 = -*x;
+    lny = alnrel(&T1);
+    goto S30;
+S10:
+    if(*y > 0.375e0) goto S20;
+    T2 = -*y;
+    lnx = alnrel(&T2);
+    lny = log(*y);
+    goto S30;
+S20:
+    lnx = log(*x);
+    lny = log(*y);
+S30:
+    z = *a*lnx+*b*lny;
+    if(a0 < 1.0e0) goto S40;
+    z -= betaln(a,b);
+    brcomp = exp(z);
+    return brcomp;
+S40:
+/*
+-----------------------------------------------------------------------
+              PROCEDURE FOR A .LT. 1 OR B .LT. 1
+-----------------------------------------------------------------------
+*/
+    b0 = fifdmax1(*a,*b);
+    if(b0 >= 8.0e0) goto S120;
+    if(b0 > 1.0e0) goto S70;
+/*
+                   ALGORITHM FOR B0 .LE. 1
+*/
+    brcomp = exp(z);
+    if(brcomp == 0.0e0) return brcomp;
+    apb = *a+*b;
+    if(apb > 1.0e0) goto S50;
+    z = 1.0e0+gam1(&apb);
+    goto S60;
+S50:
+    u = *a+*b-1.e0;
+    z = (1.0e0+gam1(&u))/apb;
+S60:
+    c = (1.0e0+gam1(a))*(1.0e0+gam1(b))/z;
+    brcomp = brcomp*(a0*c)/(1.0e0+a0/b0);
+    return brcomp;
+S70:
+/*
+                ALGORITHM FOR 1 .LT. B0 .LT. 8
+*/
+    u = gamln1(&a0);
+    n = (long)(b0 - 1.0e0);
+    if(n < 1) goto S90;
+    c = 1.0e0;
+    for(i=1; i<=n; i++) {
+        b0 -= 1.0e0;
+        c *= (b0/(a0+b0));
+    }
+    u = log(c)+u;
+S90:
+    z -= u;
+    b0 -= 1.0e0;
+    apb = a0+b0;
+    if(apb > 1.0e0) goto S100;
+    t = 1.0e0+gam1(&apb);
+    goto S110;
+S100:
+    u = a0+b0-1.e0;
+    t = (1.0e0+gam1(&u))/apb;
+S110:
+    brcomp = a0*exp(z)*(1.0e0+gam1(&b0))/t;
+    return brcomp;
+S120:
+/*
+                   ALGORITHM FOR B0 .GE. 8
+*/
+    u = gamln1(&a0)+algdiv(&a0,&b0);
+    brcomp = a0*exp(z-u);
+    return brcomp;
+S130:
+/*
+-----------------------------------------------------------------------
+              PROCEDURE FOR A .GE. 8 AND B .GE. 8
+-----------------------------------------------------------------------
+*/
+    if(*a > *b) goto S140;
+    h = *a/ *b;
+    x0 = h/(1.0e0+h);
+    y0 = 1.0e0/(1.0e0+h);
+    lambda = *a-(*a+*b)**x;
+    goto S150;
+S140:
+    h = *b/ *a;
+    x0 = 1.0e0/(1.0e0+h);
+    y0 = h/(1.0e0+h);
+    lambda = (*a+*b)**y-*b;
+S150:
+    e = -(lambda/ *a);
+    if(fabs(e) > 0.6e0) goto S160;
+    u = rlog1(&e);
+    goto S170;
+S160:
+    u = e-log(*x/x0);
+S170:
+    e = lambda/ *b;
+    if(fabs(e) > 0.6e0) goto S180;
+    v = rlog1(&e);
+    goto S190;
+S180:
+    v = e-log(*y/y0);
+S190:
+    z = exp(-(*a*u+*b*v));
+    brcomp = Const*sqrt(*b*x0)*z*exp(-bcorr(a,b));
+    return brcomp;
+}
+double bup(double *a,double *b,double *x,double *y,int *n,double *eps)
+/*
+-----------------------------------------------------------------------
+     EVALUATION OF IX(A,B) - IX(A+N,B) WHERE N IS A POSITIVE INTEGER.
+     EPS IS THE TOLERANCE USED.
+-----------------------------------------------------------------------
+*/
+{
+static int K1 = 1;
+static int K2 = 0;
+static double bup,ap1,apb,d,l,r,t,w;
+static int i,k,kp1,mu,nm1;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+          OBTAIN THE SCALING FACTOR EXP(-MU) AND
+             EXP(MU)*(X**A*Y**B/BETA(A,B))/A
+*/
+    apb = *a+*b;
+    ap1 = *a+1.0e0;
+    mu = 0;
+    d = 1.0e0;
+    if(*n == 1 || *a < 1.0e0) goto S10;
+    if(apb < 1.1e0*ap1) goto S10;
+    mu = (long)(fabs(exparg(&K1)));
+    k = (long)(exparg(&K2));
+    if(k < mu) mu = k;
+    t = mu;
+    d = exp(-t);
+S10:
+    bup = brcmp1(&mu,a,b,x,y)/ *a;
+    if(*n == 1 || bup == 0.0e0) return bup;
+    nm1 = *n-1;
+    w = d;
+/*
+          LET K BE THE INDEX OF THE MAXIMUM TERM
+*/
+    k = 0;
+    if(*b <= 1.0e0) goto S50;
+    if(*y > 1.e-4) goto S20;
+    k = nm1;
+    goto S30;
+S20:
+    r = (*b-1.0e0)**x/ *y-*a;
+    if(r < 1.0e0) goto S50;
+    t = nm1;
+    k = (long)(t);
+    if(r < t) k = (long)(r);
+S30:
+/*
+          ADD THE INCREASING TERMS OF THE SERIES
+*/
+    for(i=1; i<=k; i++) {
+        l = i-1;
+        d = (apb+l)/(ap1+l)**x*d;
+        w += d;
+    }
+    if(k == nm1) goto S70;
+S50:
+/*
+          ADD THE REMAINING TERMS OF THE SERIES
+*/
+    kp1 = k+1;
+    for(i=kp1; i<=nm1; i++) {
+        l = i-1;
+        d = (apb+l)/(ap1+l)**x*d;
+        w += d;
+        if(d <= *eps*w) goto S70;
+    }
+S70:
+/*
+               TERMINATE THE PROCEDURE
+*/
+    bup *= w;
+    return bup;
+}
+void cdfbet(int *which,double *p,double *q,double *x,double *y,
+           double *a,double *b,int *status,double *bound)
+/**********************************************************************
+
+      void cdfbet(int *which,double *p,double *q,double *x,double *y,
+            double *a,double *b,int *status,double *bound)
+
+               Cumulative Distribution Function
+                         BETa Distribution
+
+
+                              Function
+
+
+     Calculates any one parameter of the beta distribution given
+     values for the others.
+
+
+                              Arguments
+
+
+     WHICH --> Integer indicating which of the next four argument
+               values is to be calculated from the others.
+               Legal range: 1..4
+               iwhich = 1 : Calculate P and Q from X,Y,A and B
+               iwhich = 2 : Calculate X and Y from P,Q,A and B
+               iwhich = 3 : Calculate A from P,Q,X,Y and B
+               iwhich = 4 : Calculate B from P,Q,X,Y and A
+
+     P <--> The integral from 0 to X of the chi-square
+            distribution.
+            Input range: [0, 1].
+
+     Q <--> 1-P.
+            Input range: [0, 1].
+            P + Q = 1.0.
+
+     X <--> Upper limit of integration of beta density.
+            Input range: [0,1].
+            Search range: [0,1]
+
+     Y <--> 1-X.
+            Input range: [0,1].
+            Search range: [0,1]
+            X + Y = 1.0.
+
+     A <--> The first parameter of the beta density.
+            Input range: (0, +infinity).
+            Search range: [1D-100,1D100]
+
+     B <--> The second parameter of the beta density.
+            Input range: (0, +infinity).
+            Search range: [1D-100,1D100]
+
+     STATUS <-- 0 if calculation completed correctly
+               -I if input parameter number I is out of range
+                1 if answer appears to be lower than lowest
+                  search bound
+                2 if answer appears to be higher than greatest
+                  search bound
+                3 if P + Q .ne. 1
+                4 if X + Y .ne. 1
+
+     BOUND <-- Undefined if STATUS is 0
+
+               Bound exceeded by parameter number I if STATUS
+               is negative.
+
+               Lower search bound if STATUS is 1.
+
+               Upper search bound if STATUS is 2.
+
+
+                              Method
+
+
+     Cumulative distribution function  (P)  is calculated directly by
+     code associated with the following reference.
+
+     DiDinato, A. R. and Morris,  A.   H.  Algorithm 708: Significant
+     Digit Computation of the Incomplete  Beta  Function Ratios.  ACM
+     Trans. Math.  Softw. 18 (1993), 360-373.
+
+     Computation of other parameters involve a seach for a value that
+     produces  the desired  value  of P.   The search relies  on  the
+     monotinicity of P with the other parameter.
+
+
+                              Note
+
+
+     The beta density is proportional to
+               t^(A-1) * (1-t)^(B-1)
+
+**********************************************************************/
+{
+#define tol 1.0e-8
+#define atol 1.0e-50
+#define zero 1.0e-100
+#define inf 1.0e100
+#define one 1.0e0
+static int K1 = 1;
+static double K2 = 0.0e0;
+static double K3 = 1.0e0;
+static double K8 = 0.5e0;
+static double K9 = 5.0e0;
+static double fx,xhi,xlo,cum,ccum,xy,pq;
+static unsigned long qhi,qleft,qporq;
+static double T4,T5,T6,T7,T10,T11,T12,T13,T14,T15;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     Check arguments
+*/
+    if(!(*which < 1 || *which > 4)) goto S30;
+    if(!(*which < 1)) goto S10;
+    *bound = 1.0e0;
+    goto S20;
+S10:
+    *bound = 4.0e0;
+S20:
+    *status = -1;
+    return;
+S30:
+    if(*which == 1) goto S70;
+/*
+     P
+*/
+    if(!(*p < 0.0e0 || *p > 1.0e0)) goto S60;
+    if(!(*p < 0.0e0)) goto S40;
+    *bound = 0.0e0;
+    goto S50;
+S40:
+    *bound = 1.0e0;
+S50:
+    *status = -2;
+    return;
+S70:
+S60:
+    if(*which == 1) goto S110;
+/*
+     Q
+*/
+    if(!(*q < 0.0e0 || *q > 1.0e0)) goto S100;
+    if(!(*q < 0.0e0)) goto S80;
+    *bound = 0.0e0;
+    goto S90;
+S80:
+    *bound = 1.0e0;
+S90:
+    *status = -3;
+    return;
+S110:
+S100:
+    if(*which == 2) goto S150;
+/*
+     X
+*/
+    if(!(*x < 0.0e0 || *x > 1.0e0)) goto S140;
+    if(!(*x < 0.0e0)) goto S120;
+    *bound = 0.0e0;
+    goto S130;
+S120:
+    *bound = 1.0e0;
+S130:
+    *status = -4;
+    return;
+S150:
+S140:
+    if(*which == 2) goto S190;
+/*
+     Y
+*/
+    if(!(*y < 0.0e0 || *y > 1.0e0)) goto S180;
+    if(!(*y < 0.0e0)) goto S160;
+    *bound = 0.0e0;
+    goto S170;
+S160:
+    *bound = 1.0e0;
+S170:
+    *status = -5;
+    return;
+S190:
+S180:
+    if(*which == 3) goto S210;
+/*
+     A
+*/
+    if(!(*a <= 0.0e0)) goto S200;
+    *bound = 0.0e0;
+    *status = -6;
+    return;
+S210:
+S200:
+    if(*which == 4) goto S230;
+/*
+     B
+*/
+    if(!(*b <= 0.0e0)) goto S220;
+    *bound = 0.0e0;
+    *status = -7;
+    return;
+S230:
+S220:
+    if(*which == 1) goto S270;
+/*
+     P + Q
+*/
+    pq = *p+*q;
+    if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S260;
+    if(!(pq < 0.0e0)) goto S240;
+    *bound = 0.0e0;
+    goto S250;
+S240:
+    *bound = 1.0e0;
+S250:
+    *status = 3;
+    return;
+S270:
+S260:
+    if(*which == 2) goto S310;
+/*
+     X + Y
+*/
+    xy = *x+*y;
+    if(!(fabs(xy-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S300;
+    if(!(xy < 0.0e0)) goto S280;
+    *bound = 0.0e0;
+    goto S290;
+S280:
+    *bound = 1.0e0;
+S290:
+    *status = 4;
+    return;
+S310:
+S300:
+    if(!(*which == 1)) qporq = *p <= *q;
+/*
+     Select the minimum of P or Q
+     Calculate ANSWERS
+*/
+    if(1 == *which) {
+/*
+     Calculating P and Q
+*/
+        cumbet(x,y,a,b,p,q);
+        *status = 0;
+    }
+    else if(2 == *which) {
+/*
+     Calculating X and Y
+*/
+        T4 = atol;
+        T5 = tol;
+        dstzr(&K2,&K3,&T4,&T5);
+        if(!qporq) goto S340;
+        *status = 0;
+        dzror(status,x,&fx,&xlo,&xhi,&qleft,&qhi);
+        *y = one-*x;
+S320:
+        if(!(*status == 1)) goto S330;
+        cumbet(x,y,a,b,&cum,&ccum);
+        fx = cum-*p;
+        dzror(status,x,&fx,&xlo,&xhi,&qleft,&qhi);
+        *y = one-*x;
+        goto S320;
+S330:
+        goto S370;
+S340:
+        *status = 0;
+        dzror(status,y,&fx,&xlo,&xhi,&qleft,&qhi);
+        *x = one-*y;
+S350:
+        if(!(*status == 1)) goto S360;
+        cumbet(x,y,a,b,&cum,&ccum);
+        fx = ccum-*q;
+        dzror(status,y,&fx,&xlo,&xhi,&qleft,&qhi);
+        *x = one-*y;
+        goto S350;
+S370:
+S360:
+        if(!(*status == -1)) goto S400;
+        if(!qleft) goto S380;
+        *status = 1;
+        *bound = 0.0e0;
+        goto S390;
+S380:
+        *status = 2;
+        *bound = 1.0e0;
+S400:
+S390:
+        ;
+    }
+    else if(3 == *which) {
+/*
+     Computing A
+*/
+        *a = 5.0e0;
+        T6 = zero;
+        T7 = inf;
+        T10 = atol;
+        T11 = tol;
+        dstinv(&T6,&T7,&K8,&K8,&K9,&T10,&T11);
+        *status = 0;
+        dinvr(status,a,&fx,&qleft,&qhi);
+S410:
+        if(!(*status == 1)) goto S440;
+        cumbet(x,y,a,b,&cum,&ccum);
+        if(!qporq) goto S420;
+        fx = cum-*p;
+        goto S430;
+S420:
+        fx = ccum-*q;
+S430:
+        dinvr(status,a,&fx,&qleft,&qhi);
+        goto S410;
+S440:
+        if(!(*status == -1)) goto S470;
+        if(!qleft) goto S450;
+        *status = 1;
+        *bound = zero;
+        goto S460;
+S450:
+        *status = 2;
+        *bound = inf;
+S470:
+S460:
+        ;
+    }
+    else if(4 == *which) {
+/*
+     Computing B
+*/
+        *b = 5.0e0;
+        T12 = zero;
+        T13 = inf;
+        T14 = atol;
+        T15 = tol;
+        dstinv(&T12,&T13,&K8,&K8,&K9,&T14,&T15);
+        *status = 0;
+        dinvr(status,b,&fx,&qleft,&qhi);
+S480:
+        if(!(*status == 1)) goto S510;
+        cumbet(x,y,a,b,&cum,&ccum);
+        if(!qporq) goto S490;
+        fx = cum-*p;
+        goto S500;
+S490:
+        fx = ccum-*q;
+S500:
+        dinvr(status,b,&fx,&qleft,&qhi);
+        goto S480;
+S510:
+        if(!(*status == -1)) goto S540;
+        if(!qleft) goto S520;
+        *status = 1;
+        *bound = zero;
+        goto S530;
+S520:
+        *status = 2;
+        *bound = inf;
+S530:
+        ;
+    }
+S540:
+    return;
+#undef tol
+#undef atol
+#undef zero
+#undef inf
+#undef one
+}
+void cdfbin(int *which,double *p,double *q,double *s,double *xn,
+           double *pr,double *ompr,int *status,double *bound)
+/**********************************************************************
+
+      void cdfbin(int *which,double *p,double *q,double *s,double *xn,
+            double *pr,double *ompr,int *status,double *bound)
+
+               Cumulative Distribution Function
+                         BINomial distribution
+
+
+                              Function
+
+
+     Calculates any one parameter of the binomial
+     distribution given values for the others.
+
+
+                              Arguments
+
+
+     WHICH --> Integer indicating which of the next four argument
+               values is to be calculated from the others.
+               Legal range: 1..4
+               iwhich = 1 : Calculate P and Q from S,XN,PR and OMPR
+               iwhich = 2 : Calculate S from P,Q,XN,PR and OMPR
+               iwhich = 3 : Calculate XN from P,Q,S,PR and OMPR
+               iwhich = 4 : Calculate PR and OMPR from P,Q,S and XN
+
+     P <--> The cumulation from 0 to S of the binomial distribution.
+            (Probablility of S or fewer successes in XN trials each
+            with probability of success PR.)
+            Input range: [0,1].
+
+     Q <--> 1-P.
+            Input range: [0, 1].
+            P + Q = 1.0.
+
+     S <--> The number of successes observed.
+            Input range: [0, XN]
+            Search range: [0, XN]
+
+     XN  <--> The number of binomial trials.
+              Input range: (0, +infinity).
+              Search range: [1E-100, 1E100]
+
+     PR  <--> The probability of success in each binomial trial.
+              Input range: [0,1].
+              Search range: [0,1]
+
+     OMPR  <--> 1-PR
+              Input range: [0,1].
+              Search range: [0,1]
+              PR + OMPR = 1.0
+
+     STATUS <-- 0 if calculation completed correctly
+               -I if input parameter number I is out of range
+                1 if answer appears to be lower than lowest
+                  search bound
+                2 if answer appears to be higher than greatest
+                  search bound
+                3 if P + Q .ne. 1
+                4 if PR + OMPR .ne. 1
+
+     BOUND <-- Undefined if STATUS is 0
+
+               Bound exceeded by parameter number I if STATUS
+               is negative.
+
+               Lower search bound if STATUS is 1.
+
+               Upper search bound if STATUS is 2.
+
+
+                              Method
+
+
+     Formula  26.5.24    of   Abramowitz  and    Stegun,  Handbook   of
+     Mathematical   Functions (1966) is   used  to reduce the  binomial
+     distribution  to  the  cumulative incomplete    beta distribution.
+
+     Computation of other parameters involve a seach for a value that
+     produces  the desired  value  of P.   The search relies  on  the
+     monotinicity of P with the other parameter.
+
+
+**********************************************************************/
+{
+#define atol 1.0e-50
+#define tol 1.0e-8
+#define zero 1.0e-100
+#define inf 1.0e100
+#define one 1.0e0
+static int K1 = 1;
+static double K2 = 0.0e0;
+static double K3 = 0.5e0;
+static double K4 = 5.0e0;
+static double K11 = 1.0e0;
+static double fx,xhi,xlo,cum,ccum,pq,prompr;
+static unsigned long qhi,qleft,qporq;
+static double T5,T6,T7,T8,T9,T10,T12,T13;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     Check arguments
+*/
+    if(!(*which < 1 && *which > 4)) goto S30;
+    if(!(*which < 1)) goto S10;
+    *bound = 1.0e0;
+    goto S20;
+S10:
+    *bound = 4.0e0;
+S20:
+    *status = -1;
+    return;
+S30:
+    if(*which == 1) goto S70;
+/*
+     P
+*/
+    if(!(*p < 0.0e0 || *p > 1.0e0)) goto S60;
+    if(!(*p < 0.0e0)) goto S40;
+    *bound = 0.0e0;
+    goto S50;
+S40:
+    *bound = 1.0e0;
+S50:
+    *status = -2;
+    return;
+S70:
+S60:
+    if(*which == 1) goto S110;
+/*
+     Q
+*/
+    if(!(*q < 0.0e0 || *q > 1.0e0)) goto S100;
+    if(!(*q < 0.0e0)) goto S80;
+    *bound = 0.0e0;
+    goto S90;
+S80:
+    *bound = 1.0e0;
+S90:
+    *status = -3;
+    return;
+S110:
+S100:
+    if(*which == 3) goto S130;
+/*
+     XN
+*/
+    if(!(*xn <= 0.0e0)) goto S120;
+    *bound = 0.0e0;
+    *status = -5;
+    return;
+S130:
+S120:
+    if(*which == 2) goto S170;
+/*
+     S
+*/
+    if(!(*s < 0.0e0 || (*which != 3 && *s > *xn))) goto S160;
+    if(!(*s < 0.0e0)) goto S140;
+    *bound = 0.0e0;
+    goto S150;
+S140:
+    *bound = *xn;
+S150:
+    *status = -4;
+    return;
+S170:
+S160:
+    if(*which == 4) goto S210;
+/*
+     PR
+*/
+    if(!(*pr < 0.0e0 || *pr > 1.0e0)) goto S200;
+    if(!(*pr < 0.0e0)) goto S180;
+    *bound = 0.0e0;
+    goto S190;
+S180:
+    *bound = 1.0e0;
+S190:
+    *status = -6;
+    return;
+S210:
+S200:
+    if(*which == 4) goto S250;
+/*
+     OMPR
+*/
+    if(!(*ompr < 0.0e0 || *ompr > 1.0e0)) goto S240;
+    if(!(*ompr < 0.0e0)) goto S220;
+    *bound = 0.0e0;
+    goto S230;
+S220:
+    *bound = 1.0e0;
+S230:
+    *status = -7;
+    return;
+S250:
+S240:
+    if(*which == 1) goto S290;
+/*
+     P + Q
+*/
+    pq = *p+*q;
+    if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S280;
+    if(!(pq < 0.0e0)) goto S260;
+    *bound = 0.0e0;
+    goto S270;
+S260:
+    *bound = 1.0e0;
+S270:
+    *status = 3;
+    return;
+S290:
+S280:
+    if(*which == 4) goto S330;
+/*
+     PR + OMPR
+*/
+    prompr = *pr+*ompr;
+    if(!(fabs(prompr-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S320;
+    if(!(prompr < 0.0e0)) goto S300;
+    *bound = 0.0e0;
+    goto S310;
+S300:
+    *bound = 1.0e0;
+S310:
+    *status = 4;
+    return;
+S330:
+S320:
+    if(!(*which == 1)) qporq = *p <= *q;
+/*
+     Select the minimum of P or Q
+     Calculate ANSWERS
+*/
+    if(1 == *which) {
+/*
+     Calculating P
+*/
+        cumbin(s,xn,pr,ompr,p,q);
+        *status = 0;
+    }
+    else if(2 == *which) {
+/*
+     Calculating S
+*/
+        *s = 5.0e0;
+        T5 = atol;
+        T6 = tol;
+        dstinv(&K2,xn,&K3,&K3,&K4,&T5,&T6);
+        *status = 0;
+        dinvr(status,s,&fx,&qleft,&qhi);
+S340:
+        if(!(*status == 1)) goto S370;
+        cumbin(s,xn,pr,ompr,&cum,&ccum);
+        if(!qporq) goto S350;
+        fx = cum-*p;
+        goto S360;
+S350:
+        fx = ccum-*q;
+S360:
+        dinvr(status,s,&fx,&qleft,&qhi);
+        goto S340;
+S370:
+        if(!(*status == -1)) goto S400;
+        if(!qleft) goto S380;
+        *status = 1;
+        *bound = 0.0e0;
+        goto S390;
+S380:
+        *status = 2;
+        *bound = *xn;
+S400:
+S390:
+        ;
+    }
+    else if(3 == *which) {
+/*
+     Calculating XN
+*/
+        *xn = 5.0e0;
+        T7 = zero;
+        T8 = inf;
+        T9 = atol;
+        T10 = tol;
+        dstinv(&T7,&T8,&K3,&K3,&K4,&T9,&T10);
+        *status = 0;
+        dinvr(status,xn,&fx,&qleft,&qhi);
+S410:
+        if(!(*status == 1)) goto S440;
+        cumbin(s,xn,pr,ompr,&cum,&ccum);
+        if(!qporq) goto S420;
+        fx = cum-*p;
+        goto S430;
+S420:
+        fx = ccum-*q;
+S430:
+        dinvr(status,xn,&fx,&qleft,&qhi);
+        goto S410;
+S440:
+        if(!(*status == -1)) goto S470;
+        if(!qleft) goto S450;
+        *status = 1;
+        *bound = zero;
+        goto S460;
+S450:
+        *status = 2;
+        *bound = inf;
+S470:
+S460:
+        ;
+    }
+    else if(4 == *which) {
+/*
+     Calculating PR and OMPR
+*/
+        T12 = atol;
+        T13 = tol;
+        dstzr(&K2,&K11,&T12,&T13);
+        if(!qporq) goto S500;
+        *status = 0;
+        dzror(status,pr,&fx,&xlo,&xhi,&qleft,&qhi);
+        *ompr = one-*pr;
+S480:
+        if(!(*status == 1)) goto S490;
+        cumbin(s,xn,pr,ompr,&cum,&ccum);
+        fx = cum-*p;
+        dzror(status,pr,&fx,&xlo,&xhi,&qleft,&qhi);
+        *ompr = one-*pr;
+        goto S480;
+S490:
+        goto S530;
+S500:
+        *status = 0;
+        dzror(status,ompr,&fx,&xlo,&xhi,&qleft,&qhi);
+        *pr = one-*ompr;
+S510:
+        if(!(*status == 1)) goto S520;
+        cumbin(s,xn,pr,ompr,&cum,&ccum);
+        fx = ccum-*q;
+        dzror(status,ompr,&fx,&xlo,&xhi,&qleft,&qhi);
+        *pr = one-*ompr;
+        goto S510;
+S530:
+S520:
+        if(!(*status == -1)) goto S560;
+        if(!qleft) goto S540;
+        *status = 1;
+        *bound = 0.0e0;
+        goto S550;
+S540:
+        *status = 2;
+        *bound = 1.0e0;
+S550:
+        ;
+    }
+S560:
+    return;
+#undef atol
+#undef tol
+#undef zero
+#undef inf
+#undef one
+}
+void cdfchi(int *which,double *p,double *q,double *x,double *df,
+           int *status,double *bound)
+/**********************************************************************
+
+      void cdfchi(int *which,double *p,double *q,double *x,double *df,
+            int *status,double *bound)
+
+               Cumulative Distribution Function
+               CHI-Square distribution
+
+
+                              Function
+
+
+     Calculates any one parameter of the chi-square
+     distribution given values for the others.
+
+
+                              Arguments
+
+
+     WHICH --> Integer indicating which of the next three argument
+               values is to be calculated from the others.
+               Legal range: 1..3
+               iwhich = 1 : Calculate P and Q from X and DF
+               iwhich = 2 : Calculate X from P,Q and DF
+               iwhich = 3 : Calculate DF from P,Q and X
+
+     P <--> The integral from 0 to X of the chi-square
+            distribution.
+            Input range: [0, 1].
+
+     Q <--> 1-P.
+            Input range: (0, 1].
+            P + Q = 1.0.
+
+     X <--> Upper limit of integration of the non-central
+            chi-square distribution.
+            Input range: [0, +infinity).
+            Search range: [0,1E100]
+
+     DF <--> Degrees of freedom of the
+             chi-square distribution.
+             Input range: (0, +infinity).
+             Search range: [ 1E-100, 1E100]
+
+     STATUS <-- 0 if calculation completed correctly
+               -I if input parameter number I is out of range
+                1 if answer appears to be lower than lowest
+                  search bound
+                2 if answer appears to be higher than greatest
+                  search bound
+                3 if P + Q .ne. 1
+               10 indicates error returned from cumgam.  See
+                  references in cdfgam
+
+     BOUND <-- Undefined if STATUS is 0
+
+               Bound exceeded by parameter number I if STATUS
+               is negative.
+
+               Lower search bound if STATUS is 1.
+
+               Upper search bound if STATUS is 2.
+
+
+                              Method
+
+
+     Formula    26.4.19   of Abramowitz  and     Stegun, Handbook  of
+     Mathematical Functions   (1966) is used   to reduce the chisqure
+     distribution to the incomplete distribution.
+
+     Computation of other parameters involve a seach for a value that
+     produces  the desired  value  of P.   The search relies  on  the
+     monotinicity of P with the other parameter.
+
+**********************************************************************/
+{
+#define tol 1.0e-8
+#define atol 1.0e-50
+#define zero 1.0e-100
+#define inf 1.0e100
+static int K1 = 1;
+static double K2 = 0.0e0;
+static double K4 = 0.5e0;
+static double K5 = 5.0e0;
+static double fx,cum,ccum,pq,porq;
+static unsigned long qhi,qleft,qporq;
+static double T3,T6,T7,T8,T9,T10,T11;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     Check arguments
+*/
+    if(!(*which < 1 || *which > 3)) goto S30;
+    if(!(*which < 1)) goto S10;
+    *bound = 1.0e0;
+    goto S20;
+S10:
+    *bound = 3.0e0;
+S20:
+    *status = -1;
+    return;
+S30:
+    if(*which == 1) goto S70;
+/*
+     P
+*/
+    if(!(*p < 0.0e0 || *p > 1.0e0)) goto S60;
+    if(!(*p < 0.0e0)) goto S40;
+    *bound = 0.0e0;
+    goto S50;
+S40:
+    *bound = 1.0e0;
+S50:
+    *status = -2;
+    return;
+S70:
+S60:
+    if(*which == 1) goto S110;
+/*
+     Q
+*/
+    if(!(*q <= 0.0e0 || *q > 1.0e0)) goto S100;
+    if(!(*q <= 0.0e0)) goto S80;
+    *bound = 0.0e0;
+    goto S90;
+S80:
+    *bound = 1.0e0;
+S90:
+    *status = -3;
+    return;
+S110:
+S100:
+    if(*which == 2) goto S130;
+/*
+     X
+*/
+    if(!(*x < 0.0e0)) goto S120;
+    *bound = 0.0e0;
+    *status = -4;
+    return;
+S130:
+S120:
+    if(*which == 3) goto S150;
+/*
+     DF
+*/
+    if(!(*df <= 0.0e0)) goto S140;
+    *bound = 0.0e0;
+    *status = -5;
+    return;
+S150:
+S140:
+    if(*which == 1) goto S190;
+/*
+     P + Q
+*/
+    pq = *p+*q;
+    if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S180;
+    if(!(pq < 0.0e0)) goto S160;
+    *bound = 0.0e0;
+    goto S170;
+S160:
+    *bound = 1.0e0;
+S170:
+    *status = 3;
+    return;
+S190:
+S180:
+    if(*which == 1) goto S220;
+/*
+     Select the minimum of P or Q
+*/
+    qporq = *p <= *q;
+    if(!qporq) goto S200;
+    porq = *p;
+    goto S210;
+S200:
+    porq = *q;
+S220:
+S210:
+/*
+     Calculate ANSWERS
+*/
+    if(1 == *which) {
+/*
+     Calculating P and Q
+*/
+        *status = 0;
+        cumchi(x,df,p,q);
+        if(porq > 1.5e0) {
+            *status = 10;
+            return;
+        }
+    }
+    else if(2 == *which) {
+/*
+     Calculating X
+*/
+        *x = 5.0e0;
+        T3 = inf;
+        T6 = atol;
+        T7 = tol;
+        dstinv(&K2,&T3,&K4,&K4,&K5,&T6,&T7);
+        *status = 0;
+        dinvr(status,x,&fx,&qleft,&qhi);
+S230:
+        if(!(*status == 1)) goto S270;
+        cumchi(x,df,&cum,&ccum);
+        if(!qporq) goto S240;
+        fx = cum-*p;
+        goto S250;
+S240:
+        fx = ccum-*q;
+S250:
+        if(!(fx+porq > 1.5e0)) goto S260;
+        *status = 10;
+        return;
+S260:
+        dinvr(status,x,&fx,&qleft,&qhi);
+        goto S230;
+S270:
+        if(!(*status == -1)) goto S300;
+        if(!qleft) goto S280;
+        *status = 1;
+        *bound = 0.0e0;
+        goto S290;
+S280:
+        *status = 2;
+        *bound = inf;
+S300:
+S290:
+        ;
+    }
+    else if(3 == *which) {
+/*
+     Calculating DF
+*/
+        *df = 5.0e0;
+        T8 = zero;
+        T9 = inf;
+        T10 = atol;
+        T11 = tol;
+        dstinv(&T8,&T9,&K4,&K4,&K5,&T10,&T11);
+        *status = 0;
+        dinvr(status,df,&fx,&qleft,&qhi);
+S310:
+        if(!(*status == 1)) goto S350;
+        cumchi(x,df,&cum,&ccum);
+        if(!qporq) goto S320;
+        fx = cum-*p;
+        goto S330;
+S320:
+        fx = ccum-*q;
+S330:
+        if(!(fx+porq > 1.5e0)) goto S340;
+        *status = 10;
+        return;
+S340:
+        dinvr(status,df,&fx,&qleft,&qhi);
+        goto S310;
+S350:
+        if(!(*status == -1)) goto S380;
+        if(!qleft) goto S360;
+        *status = 1;
+        *bound = zero;
+        goto S370;
+S360:
+        *status = 2;
+        *bound = inf;
+S370:
+        ;
+    }
+S380:
+    return;
+#undef tol
+#undef atol
+#undef zero
+#undef inf
+}
+void cdfchn(int *which,double *p,double *q,double *x,double *df,
+           double *pnonc,int *status,double *bound)
+/**********************************************************************
+
+      void cdfchn(int *which,double *p,double *q,double *x,double *df,
+            double *pnonc,int *status,double *bound)
+
+               Cumulative Distribution Function
+               Non-central Chi-Square
+
+
+                              Function
+
+
+     Calculates any one parameter of the non-central chi-square
+     distribution given values for the others.
+
+
+                              Arguments
+
+
+     WHICH --> Integer indicating which of the next three argument
+               values is to be calculated from the others.
+               Input range: 1..4
+               iwhich = 1 : Calculate P and Q from X and DF
+               iwhich = 2 : Calculate X from P,DF and PNONC
+               iwhich = 3 : Calculate DF from P,X and PNONC
+               iwhich = 3 : Calculate PNONC from P,X and DF
+
+     P <--> The integral from 0 to X of the non-central chi-square
+            distribution.
+            Input range: [0, 1-1E-16).
+
+     Q <--> 1-P.
+            Q is not used by this subroutine and is only included
+            for similarity with other cdf* routines.
+
+     X <--> Upper limit of integration of the non-central
+            chi-square distribution.
+            Input range: [0, +infinity).
+            Search range: [0,1E100]
+
+     DF <--> Degrees of freedom of the non-central
+             chi-square distribution.
+             Input range: (0, +infinity).
+             Search range: [ 1E-100, 1E100]
+
+     PNONC <--> Non-centrality parameter of the non-central
+                chi-square distribution.
+                Input range: [0, +infinity).
+                Search range: [0,1E4]
+
+     STATUS <-- 0 if calculation completed correctly
+               -I if input parameter number I is out of range
+                1 if answer appears to be lower than lowest
+                  search bound
+                2 if answer appears to be higher than greatest
+                  search bound
+
+     BOUND <-- Undefined if STATUS is 0
+
+               Bound exceeded by parameter number I if STATUS
+               is negative.
+
+               Lower search bound if STATUS is 1.
+
+               Upper search bound if STATUS is 2.
+
+
+                              Method
+
+
+     Formula  26.4.25   of   Abramowitz   and   Stegun,  Handbook  of
+     Mathematical  Functions (1966) is used to compute the cumulative
+     distribution function.
+
+     Computation of other parameters involve a seach for a value that
+     produces  the desired  value  of P.   The search relies  on  the
+     monotinicity of P with the other parameter.
+
+
+                            WARNING
+
+     The computation time  required for this  routine is proportional
+     to the noncentrality  parameter  (PNONC).  Very large  values of
+     this parameter can consume immense  computer resources.  This is
+     why the search range is bounded by 10,000.
+
+**********************************************************************/
+{
+#define tent4 1.0e4
+#define tol 1.0e-8
+#define atol 1.0e-50
+#define zero 1.0e-100
+#define one ( 1.0e0 - 1.0e-16 )
+#define inf 1.0e100
+static double K1 = 0.0e0;
+static double K3 = 0.5e0;
+static double K4 = 5.0e0;
+static double fx,cum,ccum;
+static unsigned long qhi,qleft;
+static double T2,T5,T6,T7,T8,T9,T10,T11,T12,T13;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     Check arguments
+*/
+    if(!(*which < 1 || *which > 4)) goto S30;
+    if(!(*which < 1)) goto S10;
+    *bound = 1.0e0;
+    goto S20;
+S10:
+    *bound = 4.0e0;
+S20:
+    *status = -1;
+    return;
+S30:
+    if(*which == 1) goto S70;
+/*
+     P
+*/
+    if(!(*p < 0.0e0 || *p > one)) goto S60;
+    if(!(*p < 0.0e0)) goto S40;
+    *bound = 0.0e0;
+    goto S50;
+S40:
+    *bound = one;
+S50:
+    *status = -2;
+    return;
+S70:
+S60:
+    if(*which == 2) goto S90;
+/*
+     X
+*/
+    if(!(*x < 0.0e0)) goto S80;
+    *bound = 0.0e0;
+    *status = -4;
+    return;
+S90:
+S80:
+    if(*which == 3) goto S110;
+/*
+     DF
+*/
+    if(!(*df <= 0.0e0)) goto S100;
+    *bound = 0.0e0;
+    *status = -5;
+    return;
+S110:
+S100:
+    if(*which == 4) goto S130;
+/*
+     PNONC
+*/
+    if(!(*pnonc < 0.0e0)) goto S120;
+    *bound = 0.0e0;
+    *status = -6;
+    return;
+S130:
+S120:
+/*
+     Calculate ANSWERS
+*/
+    if(1 == *which) {
+/*
+     Calculating P and Q
+*/
+        cumchn(x,df,pnonc,p,q);
+        *status = 0;
+    }
+    else if(2 == *which) {
+/*
+     Calculating X
+*/
+        *x = 5.0e0;
+        T2 = inf;
+        T5 = atol;
+        T6 = tol;
+        dstinv(&K1,&T2,&K3,&K3,&K4,&T5,&T6);
+        *status = 0;
+        dinvr(status,x,&fx,&qleft,&qhi);
+S140:
+        if(!(*status == 1)) goto S150;
+        cumchn(x,df,pnonc,&cum,&ccum);
+        fx = cum-*p;
+        dinvr(status,x,&fx,&qleft,&qhi);
+        goto S140;
+S150:
+        if(!(*status == -1)) goto S180;
+        if(!qleft) goto S160;
+        *status = 1;
+        *bound = 0.0e0;
+        goto S170;
+S160:
+        *status = 2;
+        *bound = inf;
+S180:
+S170:
+        ;
+    }
+    else if(3 == *which) {
+/*
+     Calculating DF
+*/
+        *df = 5.0e0;
+        T7 = zero;
+        T8 = inf;
+        T9 = atol;
+        T10 = tol;
+        dstinv(&T7,&T8,&K3,&K3,&K4,&T9,&T10);
+        *status = 0;
+        dinvr(status,df,&fx,&qleft,&qhi);
+S190:
+        if(!(*status == 1)) goto S200;
+        cumchn(x,df,pnonc,&cum,&ccum);
+        fx = cum-*p;
+        dinvr(status,df,&fx,&qleft,&qhi);
+        goto S190;
+S200:
+        if(!(*status == -1)) goto S230;
+        if(!qleft) goto S210;
+        *status = 1;
+        *bound = zero;
+        goto S220;
+S210:
+        *status = 2;
+        *bound = inf;
+S230:
+S220:
+        ;
+    }
+    else if(4 == *which) {
+/*
+     Calculating PNONC
+*/
+        *pnonc = 5.0e0;
+        T11 = tent4;
+        T12 = atol;
+        T13 = tol;
+        dstinv(&K1,&T11,&K3,&K3,&K4,&T12,&T13);
+        *status = 0;
+        dinvr(status,pnonc,&fx,&qleft,&qhi);
+S240:
+        if(!(*status == 1)) goto S250;
+        cumchn(x,df,pnonc,&cum,&ccum);
+        fx = cum-*p;
+        dinvr(status,pnonc,&fx,&qleft,&qhi);
+        goto S240;
+S250:
+        if(!(*status == -1)) goto S280;
+        if(!qleft) goto S260;
+        *status = 1;
+        *bound = zero;
+        goto S270;
+S260:
+        *status = 2;
+        *bound = tent4;
+S270:
+        ;
+    }
+S280:
+    return;
+#undef tent4
+#undef tol
+#undef atol
+#undef zero
+#undef one
+#undef inf
+}
+void cdff(int *which,double *p,double *q,double *f,double *dfn,
+         double *dfd,int *status,double *bound)
+/**********************************************************************
+
+      void cdff(int *which,double *p,double *q,double *f,double *dfn,
+          double *dfd,int *status,double *bound)
+
+               Cumulative Distribution Function
+               F distribution
+
+
+                              Function
+
+
+     Calculates any one parameter of the F distribution
+     given values for the others.
+
+
+                              Arguments
+
+
+     WHICH --> Integer indicating which of the next four argument
+               values is to be calculated from the others.
+               Legal range: 1..4
+               iwhich = 1 : Calculate P and Q from F,DFN and DFD
+               iwhich = 2 : Calculate F from P,Q,DFN and DFD
+               iwhich = 3 : Calculate DFN from P,Q,F and DFD
+               iwhich = 4 : Calculate DFD from P,Q,F and DFN
+
+       P <--> The integral from 0 to F of the f-density.
+              Input range: [0,1].
+
+       Q <--> 1-P.
+              Input range: (0, 1].
+              P + Q = 1.0.
+
+       F <--> Upper limit of integration of the f-density.
+              Input range: [0, +infinity).
+              Search range: [0,1E100]
+
+     DFN < --> Degrees of freedom of the numerator sum of squares.
+               Input range: (0, +infinity).
+               Search range: [ 1E-100, 1E100]
+
+     DFD < --> Degrees of freedom of the denominator sum of squares.
+               Input range: (0, +infinity).
+               Search range: [ 1E-100, 1E100]
+
+     STATUS <-- 0 if calculation completed correctly
+               -I if input parameter number I is out of range
+                1 if answer appears to be lower than lowest
+                  search bound
+                2 if answer appears to be higher than greatest
+                  search bound
+                3 if P + Q .ne. 1
+
+     BOUND <-- Undefined if STATUS is 0
+
+               Bound exceeded by parameter number I if STATUS
+               is negative.
+
+               Lower search bound if STATUS is 1.
+
+               Upper search bound if STATUS is 2.
+
+
+                              Method
+
+
+     Formula   26.6.2   of   Abramowitz   and   Stegun,  Handbook  of
+     Mathematical  Functions (1966) is used to reduce the computation
+     of the  cumulative  distribution function for the  F  variate to
+     that of an incomplete beta.
+
+     Computation of other parameters involve a seach for a value that
+     produces  the desired  value  of P.   The search relies  on  the
+     monotinicity of P with the other parameter.
+
+                              WARNING
+
+     The value of the  cumulative  F distribution is  not necessarily
+     monotone in  either degrees of freedom.  There  thus may  be two
+     values  that  provide a given CDF  value.   This routine assumes
+     monotonicity and will find an arbitrary one of the two values.
+
+**********************************************************************/
+{
+#define tol 1.0e-8
+#define atol 1.0e-50
+#define zero 1.0e-100
+#define inf 1.0e100
+static int K1 = 1;
+static double K2 = 0.0e0;
+static double K4 = 0.5e0;
+static double K5 = 5.0e0;
+static double pq,fx,cum,ccum;
+static unsigned long qhi,qleft,qporq;
+static double T3,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     Check arguments
+*/
+    if(!(*which < 1 || *which > 4)) goto S30;
+    if(!(*which < 1)) goto S10;
+    *bound = 1.0e0;
+    goto S20;
+S10:
+    *bound = 4.0e0;
+S20:
+    *status = -1;
+    return;
+S30:
+    if(*which == 1) goto S70;
+/*
+     P
+*/
+    if(!(*p < 0.0e0 || *p > 1.0e0)) goto S60;
+    if(!(*p < 0.0e0)) goto S40;
+    *bound = 0.0e0;
+    goto S50;
+S40:
+    *bound = 1.0e0;
+S50:
+    *status = -2;
+    return;
+S70:
+S60:
+    if(*which == 1) goto S110;
+/*
+     Q
+*/
+    if(!(*q <= 0.0e0 || *q > 1.0e0)) goto S100;
+    if(!(*q <= 0.0e0)) goto S80;
+    *bound = 0.0e0;
+    goto S90;
+S80:
+    *bound = 1.0e0;
+S90:
+    *status = -3;
+    return;
+S110:
+S100:
+    if(*which == 2) goto S130;
+/*
+     F
+*/
+    if(!(*f < 0.0e0)) goto S120;
+    *bound = 0.0e0;
+    *status = -4;
+    return;
+S130:
+S120:
+    if(*which == 3) goto S150;
+/*
+     DFN
+*/
+    if(!(*dfn <= 0.0e0)) goto S140;
+    *bound = 0.0e0;
+    *status = -5;
+    return;
+S150:
+S140:
+    if(*which == 4) goto S170;
+/*
+     DFD
+*/
+    if(!(*dfd <= 0.0e0)) goto S160;
+    *bound = 0.0e0;
+    *status = -6;
+    return;
+S170:
+S160:
+    if(*which == 1) goto S210;
+/*
+     P + Q
+*/
+    pq = *p+*q;
+    if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S200;
+    if(!(pq < 0.0e0)) goto S180;
+    *bound = 0.0e0;
+    goto S190;
+S180:
+    *bound = 1.0e0;
+S190:
+    *status = 3;
+    return;
+S210:
+S200:
+    if(!(*which == 1)) qporq = *p <= *q;
+/*
+     Select the minimum of P or Q
+     Calculate ANSWERS
+*/
+    if(1 == *which) {
+/*
+     Calculating P
+*/
+        cumf(f,dfn,dfd,p,q);
+        *status = 0;
+    }
+    else if(2 == *which) {
+/*
+     Calculating F
+*/
+        *f = 5.0e0;
+        T3 = inf;
+        T6 = atol;
+        T7 = tol;
+        dstinv(&K2,&T3,&K4,&K4,&K5,&T6,&T7);
+        *status = 0;
+        dinvr(status,f,&fx,&qleft,&qhi);
+S220:
+        if(!(*status == 1)) goto S250;
+        cumf(f,dfn,dfd,&cum,&ccum);
+        if(!qporq) goto S230;
+        fx = cum-*p;
+        goto S240;
+S230:
+        fx = ccum-*q;
+S240:
+        dinvr(status,f,&fx,&qleft,&qhi);
+        goto S220;
+S250:
+        if(!(*status == -1)) goto S280;
+        if(!qleft) goto S260;
+        *status = 1;
+        *bound = 0.0e0;
+        goto S270;
+S260:
+        *status = 2;
+        *bound = inf;
+S280:
+S270:
+        ;
+    }
+    else if(3 == *which) {
+/*
+     Calculating DFN
+*/
+        *dfn = 5.0e0;
+        T8 = zero;
+        T9 = inf;
+        T10 = atol;
+        T11 = tol;
+        dstinv(&T8,&T9,&K4,&K4,&K5,&T10,&T11);
+        *status = 0;
+        dinvr(status,dfn,&fx,&qleft,&qhi);
+S290:
+        if(!(*status == 1)) goto S320;
+        cumf(f,dfn,dfd,&cum,&ccum);
+        if(!qporq) goto S300;
+        fx = cum-*p;
+        goto S310;
+S300:
+        fx = ccum-*q;
+S310:
+        dinvr(status,dfn,&fx,&qleft,&qhi);
+        goto S290;
+S320:
+        if(!(*status == -1)) goto S350;
+        if(!qleft) goto S330;
+        *status = 1;
+        *bound = zero;
+        goto S340;
+S330:
+        *status = 2;
+        *bound = inf;
+S350:
+S340:
+        ;
+    }
+    else if(4 == *which) {
+/*
+     Calculating DFD
+*/
+        *dfd = 5.0e0;
+        T12 = zero;
+        T13 = inf;
+        T14 = atol;
+        T15 = tol;
+        dstinv(&T12,&T13,&K4,&K4,&K5,&T14,&T15);
+        *status = 0;
+        dinvr(status,dfd,&fx,&qleft,&qhi);
+S360:
+        if(!(*status == 1)) goto S390;
+        cumf(f,dfn,dfd,&cum,&ccum);
+        if(!qporq) goto S370;
+        fx = cum-*p;
+        goto S380;
+S370:
+        fx = ccum-*q;
+S380:
+        dinvr(status,dfd,&fx,&qleft,&qhi);
+        goto S360;
+S390:
+        if(!(*status == -1)) goto S420;
+        if(!qleft) goto S400;
+        *status = 1;
+        *bound = zero;
+        goto S410;
+S400:
+        *status = 2;
+        *bound = inf;
+S410:
+        ;
+    }
+S420:
+    return;
+#undef tol
+#undef atol
+#undef zero
+#undef inf
+}
+void cdffnc(int *which,double *p,double *q,double *f,double *dfn,
+           double *dfd,double *phonc,int *status,double *bound)
+/**********************************************************************
+
+      void cdffnc(int *which,double *p,double *q,double *f,double *dfn,
+            double *dfd,double *phonc,int *status,double *bound)
+
+               Cumulative Distribution Function
+               Non-central F distribution
+
+
+                              Function
+
+
+     Calculates any one parameter of the Non-central F
+     distribution given values for the others.
+
+
+                              Arguments
+
+
+     WHICH --> Integer indicating which of the next five argument
+               values is to be calculated from the others.
+               Legal range: 1..5
+               iwhich = 1 : Calculate P and Q from F,DFN,DFD and PNONC
+               iwhich = 2 : Calculate F from P,Q,DFN,DFD and PNONC
+               iwhich = 3 : Calculate DFN from P,Q,F,DFD and PNONC
+               iwhich = 4 : Calculate DFD from P,Q,F,DFN and PNONC
+               iwhich = 5 : Calculate PNONC from P,Q,F,DFN and DFD
+
+       P <--> The integral from 0 to F of the non-central f-density.
+              Input range: [0,1-1E-16).
+
+       Q <--> 1-P.
+              Q is not used by this subroutine and is only included
+              for similarity with other cdf* routines.
+
+       F <--> Upper limit of integration of the non-central f-density.
+              Input range: [0, +infinity).
+              Search range: [0,1E100]
+
+     DFN < --> Degrees of freedom of the numerator sum of squares.
+               Input range: (0, +infinity).
+               Search range: [ 1E-100, 1E100]
+
+     DFD < --> Degrees of freedom of the denominator sum of squares.
+               Must be in range: (0, +infinity).
+               Input range: (0, +infinity).
+               Search range: [ 1E-100, 1E100]
+
+     PNONC <-> The non-centrality parameter
+               Input range: [0,infinity)
+               Search range: [0,1E4]
+
+     STATUS <-- 0 if calculation completed correctly
+               -I if input parameter number I is out of range
+                1 if answer appears to be lower than lowest
+                  search bound
+                2 if answer appears to be higher than greatest
+                  search bound
+                3 if P + Q .ne. 1
+
+     BOUND <-- Undefined if STATUS is 0
+
+               Bound exceeded by parameter number I if STATUS
+               is negative.
+
+               Lower search bound if STATUS is 1.
+
+               Upper search bound if STATUS is 2.
+
+
+                              Method
+
+
+     Formula  26.6.20   of   Abramowitz   and   Stegun,  Handbook  of
+     Mathematical  Functions (1966) is used to compute the cumulative
+     distribution function.
+
+     Computation of other parameters involve a seach for a value that
+     produces  the desired  value  of P.   The search relies  on  the
+     monotinicity of P with the other parameter.
+
+                            WARNING
+
+     The computation time  required for this  routine is proportional
+     to the noncentrality  parameter  (PNONC).  Very large  values of
+     this parameter can consume immense  computer resources.  This is
+     why the search range is bounded by 10,000.
+
+                              WARNING
+
+     The  value  of the  cumulative  noncentral F distribution is not
+     necessarily monotone in either degrees  of freedom.  There  thus
+     may be two values that provide a given  CDF value.  This routine
+     assumes monotonicity  and will find  an arbitrary one of the two
+     values.
+
+**********************************************************************/
+{
+#define tent4 1.0e4
+#define tol 1.0e-8
+#define atol 1.0e-50
+#define zero 1.0e-100
+#define one ( 1.0e0 - 1.0e-16 )
+#define inf 1.0e100
+static double K1 = 0.0e0;
+static double K3 = 0.5e0;
+static double K4 = 5.0e0;
+static double fx,cum,ccum;
+static unsigned long qhi,qleft;
+static double T2,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,T17;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     Check arguments
+*/
+    if(!(*which < 1 || *which > 5)) goto S30;
+    if(!(*which < 1)) goto S10;
+    *bound = 1.0e0;
+    goto S20;
+S10:
+    *bound = 5.0e0;
+S20:
+    *status = -1;
+    return;
+S30:
+    if(*which == 1) goto S70;
+/*
+     P
+*/
+    if(!(*p < 0.0e0 || *p > one)) goto S60;
+    if(!(*p < 0.0e0)) goto S40;
+    *bound = 0.0e0;
+    goto S50;
+S40:
+    *bound = one;
+S50:
+    *status = -2;
+    return;
+S70:
+S60:
+    if(*which == 2) goto S90;
+/*
+     F
+*/
+    if(!(*f < 0.0e0)) goto S80;
+    *bound = 0.0e0;
+    *status = -4;
+    return;
+S90:
+S80:
+    if(*which == 3) goto S110;
+/*
+     DFN
+*/
+    if(!(*dfn <= 0.0e0)) goto S100;
+    *bound = 0.0e0;
+    *status = -5;
+    return;
+S110:
+S100:
+    if(*which == 4) goto S130;
+/*
+     DFD
+*/
+    if(!(*dfd <= 0.0e0)) goto S120;
+    *bound = 0.0e0;
+    *status = -6;
+    return;
+S130:
+S120:
+    if(*which == 5) goto S150;
+/*
+     PHONC
+*/
+    if(!(*phonc < 0.0e0)) goto S140;
+    *bound = 0.0e0;
+    *status = -7;
+    return;
+S150:
+S140:
+/*
+     Calculate ANSWERS
+*/
+    if(1 == *which) {
+/*
+     Calculating P
+*/
+        cumfnc(f,dfn,dfd,phonc,p,q);
+        *status = 0;
+    }
+    else if(2 == *which) {
+/*
+     Calculating F
+*/
+        *f = 5.0e0;
+        T2 = inf;
+        T5 = atol;
+        T6 = tol;
+        dstinv(&K1,&T2,&K3,&K3,&K4,&T5,&T6);
+        *status = 0;
+        dinvr(status,f,&fx,&qleft,&qhi);
+S160:
+        if(!(*status == 1)) goto S170;
+        cumfnc(f,dfn,dfd,phonc,&cum,&ccum);
+        fx = cum-*p;
+        dinvr(status,f,&fx,&qleft,&qhi);
+        goto S160;
+S170:
+        if(!(*status == -1)) goto S200;
+        if(!qleft) goto S180;
+        *status = 1;
+        *bound = 0.0e0;
+        goto S190;
+S180:
+        *status = 2;
+        *bound = inf;
+S200:
+S190:
+        ;
+    }
+    else if(3 == *which) {
+/*
+     Calculating DFN
+*/
+        *dfn = 5.0e0;
+        T7 = zero;
+        T8 = inf;
+        T9 = atol;
+        T10 = tol;
+        dstinv(&T7,&T8,&K3,&K3,&K4,&T9,&T10);
+        *status = 0;
+        dinvr(status,dfn,&fx,&qleft,&qhi);
+S210:
+        if(!(*status == 1)) goto S220;
+        cumfnc(f,dfn,dfd,phonc,&cum,&ccum);
+        fx = cum-*p;
+        dinvr(status,dfn,&fx,&qleft,&qhi);
+        goto S210;
+S220:
+        if(!(*status == -1)) goto S250;
+        if(!qleft) goto S230;
+        *status = 1;
+        *bound = zero;
+        goto S240;
+S230:
+        *status = 2;
+        *bound = inf;
+S250:
+S240:
+        ;
+    }
+    else if(4 == *which) {
+/*
+     Calculating DFD
+*/
+        *dfd = 5.0e0;
+        T11 = zero;
+        T12 = inf;
+        T13 = atol;
+        T14 = tol;
+        dstinv(&T11,&T12,&K3,&K3,&K4,&T13,&T14);
+        *status = 0;
+        dinvr(status,dfd,&fx,&qleft,&qhi);
+S260:
+        if(!(*status == 1)) goto S270;
+        cumfnc(f,dfn,dfd,phonc,&cum,&ccum);
+        fx = cum-*p;
+        dinvr(status,dfd,&fx,&qleft,&qhi);
+        goto S260;
+S270:
+        if(!(*status == -1)) goto S300;
+        if(!qleft) goto S280;
+        *status = 1;
+        *bound = zero;
+        goto S290;
+S280:
+        *status = 2;
+        *bound = inf;
+S300:
+S290:
+        ;
+    }
+    else if(5 == *which) {
+/*
+     Calculating PHONC
+*/
+        *phonc = 5.0e0;
+        T15 = tent4;
+        T16 = atol;
+        T17 = tol;
+        dstinv(&K1,&T15,&K3,&K3,&K4,&T16,&T17);
+        *status = 0;
+        dinvr(status,phonc,&fx,&qleft,&qhi);
+S310:
+        if(!(*status == 1)) goto S320;
+        cumfnc(f,dfn,dfd,phonc,&cum,&ccum);
+        fx = cum-*p;
+        dinvr(status,phonc,&fx,&qleft,&qhi);
+        goto S310;
+S320:
+        if(!(*status == -1)) goto S350;
+        if(!qleft) goto S330;
+        *status = 1;
+        *bound = 0.0e0;
+        goto S340;
+S330:
+        *status = 2;
+        *bound = tent4;
+S340:
+        ;
+    }
+S350:
+    return;
+#undef tent4
+#undef tol
+#undef atol
+#undef zero
+#undef one
+#undef inf
+}
+void cdfgam(int *which,double *p,double *q,double *x,double *shape,
+           double *scale,int *status,double *bound)
+/**********************************************************************
+
+      void cdfgam(int *which,double *p,double *q,double *x,double *shape,
+            double *scale,int *status,double *bound)
+
+               Cumulative Distribution Function
+                         GAMma Distribution
+
+
+                              Function
+
+
+     Calculates any one parameter of the gamma
+     distribution given values for the others.
+
+
+                              Arguments
+
+
+     WHICH --> Integer indicating which of the next four argument
+               values is to be calculated from the others.
+               Legal range: 1..4
+               iwhich = 1 : Calculate P and Q from X,SHAPE and SCALE
+               iwhich = 2 : Calculate X from P,Q,SHAPE and SCALE
+               iwhich = 3 : Calculate SHAPE from P,Q,X and SCALE
+               iwhich = 4 : Calculate SCALE from P,Q,X and SHAPE
+
+     P <--> The integral from 0 to X of the gamma density.
+            Input range: [0,1].
+
+     Q <--> 1-P.
+            Input range: (0, 1].
+            P + Q = 1.0.
+
+     X <--> The upper limit of integration of the gamma density.
+            Input range: [0, +infinity).
+            Search range: [0,1E100]
+
+     SHAPE <--> The shape parameter of the gamma density.
+                Input range: (0, +infinity).
+                Search range: [1E-100,1E100]
+
+     SCALE <--> The scale parameter of the gamma density.
+                Input range: (0, +infinity).
+                Search range: (1E-100,1E100]
+
+     STATUS <-- 0 if calculation completed correctly
+               -I if input parameter number I is out of range
+                1 if answer appears to be lower than lowest
+                  search bound
+                2 if answer appears to be higher than greatest
+                  search bound
+                3 if P + Q .ne. 1
+                10 if the gamma or inverse gamma routine cannot
+                   compute the answer.  Usually happens only for
+                   X and SHAPE very large (gt 1E10 or more)
+
+     BOUND <-- Undefined if STATUS is 0
+
+               Bound exceeded by parameter number I if STATUS
+               is negative.
+
+               Lower search bound if STATUS is 1.
+
+               Upper search bound if STATUS is 2.
+
+
+                              Method
+
+
+     Cumulative distribution function (P) is calculated directly by
+     the code associated with:
+
+     DiDinato, A. R. and Morris, A. H. Computation of the  incomplete
+     gamma function  ratios  and their  inverse.   ACM  Trans.  Math.
+     Softw. 12 (1986), 377-393.
+
+     Computation of other parameters involve a seach for a value that
+     produces  the desired  value  of P.   The search relies  on  the
+     monotinicity of P with the other parameter.
+
+
+                              Note
+
+
+
+     The gamma density is proportional to
+       T**(SHAPE - 1) * EXP(- SCALE * T)
+
+**********************************************************************/
+{
+#define tol 1.0e-8
+#define atol 1.0e-50
+#define zero 1.0e-100
+#define inf 1.0e100
+static int K1 = 1;
+static double K5 = 0.5e0;
+static double K6 = 5.0e0;
+static double xx,fx,xscale,cum,ccum,pq,porq;
+static int ierr;
+static unsigned long qhi,qleft,qporq;
+static double T2,T3,T4,T7,T8,T9;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     Check arguments
+*/
+    if(!(*which < 1 || *which > 4)) goto S30;
+    if(!(*which < 1)) goto S10;
+    *bound = 1.0e0;
+    goto S20;
+S10:
+    *bound = 4.0e0;
+S20:
+    *status = -1;
+    return;
+S30:
+    if(*which == 1) goto S70;
+/*
+     P
+*/
+    if(!(*p < 0.0e0 || *p > 1.0e0)) goto S60;
+    if(!(*p < 0.0e0)) goto S40;
+    *bound = 0.0e0;
+    goto S50;
+S40:
+    *bound = 1.0e0;
+S50:
+    *status = -2;
+    return;
+S70:
+S60:
+    if(*which == 1) goto S110;
+/*
+     Q
+*/
+    if(!(*q <= 0.0e0 || *q > 1.0e0)) goto S100;
+    if(!(*q <= 0.0e0)) goto S80;
+    *bound = 0.0e0;
+    goto S90;
+S80:
+    *bound = 1.0e0;
+S90:
+    *status = -3;
+    return;
+S110:
+S100:
+    if(*which == 2) goto S130;
+/*
+     X
+*/
+    if(!(*x < 0.0e0)) goto S120;
+    *bound = 0.0e0;
+    *status = -4;
+    return;
+S130:
+S120:
+    if(*which == 3) goto S150;
+/*
+     SHAPE
+*/
+    if(!(*shape <= 0.0e0)) goto S140;
+    *bound = 0.0e0;
+    *status = -5;
+    return;
+S150:
+S140:
+    if(*which == 4) goto S170;
+/*
+     SCALE
+*/
+    if(!(*scale <= 0.0e0)) goto S160;
+    *bound = 0.0e0;
+    *status = -6;
+    return;
+S170:
+S160:
+    if(*which == 1) goto S210;
+/*
+     P + Q
+*/
+    pq = *p+*q;
+    if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S200;
+    if(!(pq < 0.0e0)) goto S180;
+    *bound = 0.0e0;
+    goto S190;
+S180:
+    *bound = 1.0e0;
+S190:
+    *status = 3;
+    return;
+S210:
+S200:
+    if(*which == 1) goto S240;
+/*
+     Select the minimum of P or Q
+*/
+    qporq = *p <= *q;
+    if(!qporq) goto S220;
+    porq = *p;
+    goto S230;
+S220:
+    porq = *q;
+S240:
+S230:
+/*
+     Calculate ANSWERS
+*/
+    if(1 == *which) {
+/*
+     Calculating P
+*/
+        *status = 0;
+        xscale = *x**scale;
+        cumgam(&xscale,shape,p,q);
+        if(porq > 1.5e0) *status = 10;
+    }
+    else if(2 == *which) {
+/*
+     Computing X
+*/
+        T2 = -1.0e0;
+        gaminv(shape,&xx,&T2,p,q,&ierr);
+        if(ierr < 0.0e0) {
+            *status = 10;
+            return;
+        }
+        else  {
+            *x = xx/ *scale;
+            *status = 0;
+        }
+    }
+    else if(3 == *which) {
+/*
+     Computing SHAPE
+*/
+        *shape = 5.0e0;
+        xscale = *x**scale;
+        T3 = zero;
+        T4 = inf;
+        T7 = atol;
+        T8 = tol;
+        dstinv(&T3,&T4,&K5,&K5,&K6,&T7,&T8);
+        *status = 0;
+        dinvr(status,shape,&fx,&qleft,&qhi);
+S250:
+        if(!(*status == 1)) goto S290;
+        cumgam(&xscale,shape,&cum,&ccum);
+        if(!qporq) goto S260;
+        fx = cum-*p;
+        goto S270;
+S260:
+        fx = ccum-*q;
+S270:
+        if(!((qporq && cum > 1.5e0) || (!qporq && ccum > 1.5e0))) goto S280;
+        *status = 10;
+        return;
+S280:
+        dinvr(status,shape,&fx,&qleft,&qhi);
+        goto S250;
+S290:
+        if(!(*status == -1)) goto S320;
+        if(!qleft) goto S300;
+        *status = 1;
+        *bound = zero;
+        goto S310;
+S300:
+        *status = 2;
+        *bound = inf;
+S320:
+S310:
+        ;
+    }
+    else if(4 == *which) {
+/*
+     Computing SCALE
+*/
+        T9 = -1.0e0;
+        gaminv(shape,&xx,&T9,p,q,&ierr);
+        if(ierr < 0.0e0) {
+            *status = 10;
+            return;
+        }
+        else  {
+            *scale = xx/ *x;
+            *status = 0;
+        }
+    }
+    return;
+#undef tol
+#undef atol
+#undef zero
+#undef inf
+}
+void cdfnbn(int *which,double *p,double *q,double *s,double *xn,
+           double *pr,double *ompr,int *status,double *bound)
+/**********************************************************************
+
+      void cdfnbn(int *which,double *p,double *q,double *s,double *xn,
+            double *pr,double *ompr,int *status,double *bound)
+
+               Cumulative Distribution Function
+               Negative BiNomial distribution
+
+
+                              Function
+
+
+     Calculates any one parameter of the negative binomial
+     distribution given values for the others.
+
+     The  cumulative  negative   binomial  distribution  returns  the
+     probability that there  will be  F or fewer failures before  the
+     XNth success in binomial trials each of which has probability of
+     success PR.
+
+     The individual term of the negative binomial is the probability of
+     S failures before XN successes and is
+          Choose( S, XN+S-1 ) * PR^(XN) * (1-PR)^S
+
+
+                              Arguments
+
+
+     WHICH --> Integer indicating which of the next four argument
+               values is to be calculated from the others.
+               Legal range: 1..4
+               iwhich = 1 : Calculate P and Q from S,XN,PR and OMPR
+               iwhich = 2 : Calculate S from P,Q,XN,PR and OMPR
+               iwhich = 3 : Calculate XN from P,Q,S,PR and OMPR
+               iwhich = 4 : Calculate PR and OMPR from P,Q,S and XN
+
+     P <--> The cumulation from 0 to S of the  negative
+            binomial distribution.
+            Input range: [0,1].
+
+     Q <--> 1-P.
+            Input range: (0, 1].
+            P + Q = 1.0.
+
+     S <--> The upper limit of cumulation of the binomial distribution.
+            There are F or fewer failures before the XNth success.
+            Input range: [0, +infinity).
+            Search range: [0, 1E100]
+
+     XN  <--> The number of successes.
+              Input range: [0, +infinity).
+              Search range: [0, 1E100]
+
+     PR  <--> The probability of success in each binomial trial.
+              Input range: [0,1].
+              Search range: [0,1].
+
+     OMPR  <--> 1-PR
+              Input range: [0,1].
+              Search range: [0,1]
+              PR + OMPR = 1.0
+
+     STATUS <-- 0 if calculation completed correctly
+               -I if input parameter number I is out of range
+                1 if answer appears to be lower than lowest
+                  search bound
+                2 if answer appears to be higher than greatest
+                  search bound
+                3 if P + Q .ne. 1
+                4 if PR + OMPR .ne. 1
+
+     BOUND <-- Undefined if STATUS is 0
+
+               Bound exceeded by parameter number I if STATUS
+               is negative.
+
+               Lower search bound if STATUS is 1.
+
+               Upper search bound if STATUS is 2.
+
+
+                              Method
+
+
+     Formula   26.5.26   of   Abramowitz  and  Stegun,  Handbook   of
+     Mathematical Functions (1966) is used  to  reduce calculation of
+     the cumulative distribution  function to that of  an  incomplete
+     beta.
+
+     Computation of other parameters involve a seach for a value that
+     produces  the desired  value  of P.   The search relies  on  the
+     monotinicity of P with the other parameter.
+
+**********************************************************************/
+{
+#define tol 1.0e-8
+#define atol 1.0e-50
+#define inf 1.0e100
+#define one 1.0e0
+static int K1 = 1;
+static double K2 = 0.0e0;
+static double K4 = 0.5e0;
+static double K5 = 5.0e0;
+static double K11 = 1.0e0;
+static double fx,xhi,xlo,pq,prompr,cum,ccum;
+static unsigned long qhi,qleft,qporq;
+static double T3,T6,T7,T8,T9,T10,T12,T13;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     Check arguments
+*/
+    if(!(*which < 1 || *which > 4)) goto S30;
+    if(!(*which < 1)) goto S10;
+    *bound = 1.0e0;
+    goto S20;
+S10:
+    *bound = 4.0e0;
+S20:
+    *status = -1;
+    return;
+S30:
+    if(*which == 1) goto S70;
+/*
+     P
+*/
+    if(!(*p < 0.0e0 || *p > 1.0e0)) goto S60;
+    if(!(*p < 0.0e0)) goto S40;
+    *bound = 0.0e0;
+    goto S50;
+S40:
+    *bound = 1.0e0;
+S50:
+    *status = -2;
+    return;
+S70:
+S60:
+    if(*which == 1) goto S110;
+/*
+     Q
+*/
+    if(!(*q <= 0.0e0 || *q > 1.0e0)) goto S100;
+    if(!(*q <= 0.0e0)) goto S80;
+    *bound = 0.0e0;
+    goto S90;
+S80:
+    *bound = 1.0e0;
+S90:
+    *status = -3;
+    return;
+S110:
+S100:
+    if(*which == 2) goto S130;
+/*
+     S
+*/
+    if(!(*s < 0.0e0)) goto S120;
+    *bound = 0.0e0;
+    *status = -4;
+    return;
+S130:
+S120:
+    if(*which == 3) goto S150;
+/*
+     XN
+*/
+    if(!(*xn < 0.0e0)) goto S140;
+    *bound = 0.0e0;
+    *status = -5;
+    return;
+S150:
+S140:
+    if(*which == 4) goto S190;
+/*
+     PR
+*/
+    if(!(*pr < 0.0e0 || *pr > 1.0e0)) goto S180;
+    if(!(*pr < 0.0e0)) goto S160;
+    *bound = 0.0e0;
+    goto S170;
+S160:
+    *bound = 1.0e0;
+S170:
+    *status = -6;
+    return;
+S190:
+S180:
+    if(*which == 4) goto S230;
+/*
+     OMPR
+*/
+    if(!(*ompr < 0.0e0 || *ompr > 1.0e0)) goto S220;
+    if(!(*ompr < 0.0e0)) goto S200;
+    *bound = 0.0e0;
+    goto S210;
+S200:
+    *bound = 1.0e0;
+S210:
+    *status = -7;
+    return;
+S230:
+S220:
+    if(*which == 1) goto S270;
+/*
+     P + Q
+*/
+    pq = *p+*q;
+    if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S260;
+    if(!(pq < 0.0e0)) goto S240;
+    *bound = 0.0e0;
+    goto S250;
+S240:
+    *bound = 1.0e0;
+S250:
+    *status = 3;
+    return;
+S270:
+S260:
+    if(*which == 4) goto S310;
+/*
+     PR + OMPR
+*/
+    prompr = *pr+*ompr;
+    if(!(fabs(prompr-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S300;
+    if(!(prompr < 0.0e0)) goto S280;
+    *bound = 0.0e0;
+    goto S290;
+S280:
+    *bound = 1.0e0;
+S290:
+    *status = 4;
+    return;
+S310:
+S300:
+    if(!(*which == 1)) qporq = *p <= *q;
+/*
+     Select the minimum of P or Q
+     Calculate ANSWERS
+*/
+    if(1 == *which) {
+/*
+     Calculating P
+*/
+        cumnbn(s,xn,pr,ompr,p,q);
+        *status = 0;
+    }
+    else if(2 == *which) {
+/*
+     Calculating S
+*/
+        *s = 5.0e0;
+        T3 = inf;
+        T6 = atol;
+        T7 = tol;
+        dstinv(&K2,&T3,&K4,&K4,&K5,&T6,&T7);
+        *status = 0;
+        dinvr(status,s,&fx,&qleft,&qhi);
+S320:
+        if(!(*status == 1)) goto S350;
+        cumnbn(s,xn,pr,ompr,&cum,&ccum);
+        if(!qporq) goto S330;
+        fx = cum-*p;
+        goto S340;
+S330:
+        fx = ccum-*q;
+S340:
+        dinvr(status,s,&fx,&qleft,&qhi);
+        goto S320;
+S350:
+        if(!(*status == -1)) goto S380;
+        if(!qleft) goto S360;
+        *status = 1;
+        *bound = 0.0e0;
+        goto S370;
+S360:
+        *status = 2;
+        *bound = inf;
+S380:
+S370:
+        ;
+    }
+    else if(3 == *which) {
+/*
+     Calculating XN
+*/
+        *xn = 5.0e0;
+        T8 = inf;
+        T9 = atol;
+        T10 = tol;
+        dstinv(&K2,&T8,&K4,&K4,&K5,&T9,&T10);
+        *status = 0;
+        dinvr(status,xn,&fx,&qleft,&qhi);
+S390:
+        if(!(*status == 1)) goto S420;
+        cumnbn(s,xn,pr,ompr,&cum,&ccum);
+        if(!qporq) goto S400;
+        fx = cum-*p;
+        goto S410;
+S400:
+        fx = ccum-*q;
+S410:
+        dinvr(status,xn,&fx,&qleft,&qhi);
+        goto S390;
+S420:
+        if(!(*status == -1)) goto S450;
+        if(!qleft) goto S430;
+        *status = 1;
+        *bound = 0.0e0;
+        goto S440;
+S430:
+        *status = 2;
+        *bound = inf;
+S450:
+S440:
+        ;
+    }
+    else if(4 == *which) {
+/*
+     Calculating PR and OMPR
+*/
+        T12 = atol;
+        T13 = tol;
+        dstzr(&K2,&K11,&T12,&T13);
+        if(!qporq) goto S480;
+        *status = 0;
+        dzror(status,pr,&fx,&xlo,&xhi,&qleft,&qhi);
+        *ompr = one-*pr;
+S460:
+        if(!(*status == 1)) goto S470;
+        cumnbn(s,xn,pr,ompr,&cum,&ccum);
+        fx = cum-*p;
+        dzror(status,pr,&fx,&xlo,&xhi,&qleft,&qhi);
+        *ompr = one-*pr;
+        goto S460;
+S470:
+        goto S510;
+S480:
+        *status = 0;
+        dzror(status,ompr,&fx,&xlo,&xhi,&qleft,&qhi);
+        *pr = one-*ompr;
+S490:
+        if(!(*status == 1)) goto S500;
+        cumnbn(s,xn,pr,ompr,&cum,&ccum);
+        fx = ccum-*q;
+        dzror(status,ompr,&fx,&xlo,&xhi,&qleft,&qhi);
+        *pr = one-*ompr;
+        goto S490;
+S510:
+S500:
+        if(!(*status == -1)) goto S540;
+        if(!qleft) goto S520;
+        *status = 1;
+        *bound = 0.0e0;
+        goto S530;
+S520:
+        *status = 2;
+        *bound = 1.0e0;
+S530:
+        ;
+    }
+S540:
+    return;
+#undef tol
+#undef atol
+#undef inf
+#undef one
+}
+void cdfnor(int *which,double *p,double *q,double *x,double *mean,
+           double *sd,int *status,double *bound)
+/**********************************************************************
+
+      void cdfnor(int *which,double *p,double *q,double *x,double *mean,
+            double *sd,int *status,double *bound)
+
+               Cumulative Distribution Function
+               NORmal distribution
+
+
+                              Function
+
+
+     Calculates any one parameter of the normal
+     distribution given values for the others.
+
+
+                              Arguments
+
+
+     WHICH  --> Integer indicating  which of the  next  parameter
+     values is to be calculated using values  of the others.
+     Legal range: 1..4
+               iwhich = 1 : Calculate P and Q from X,MEAN and SD
+               iwhich = 2 : Calculate X from P,Q,MEAN and SD
+               iwhich = 3 : Calculate MEAN from P,Q,X and SD
+               iwhich = 4 : Calculate SD from P,Q,X and MEAN
+
+     P <--> The integral from -infinity to X of the normal density.
+            Input range: (0,1].
+
+     Q <--> 1-P.
+            Input range: (0, 1].
+            P + Q = 1.0.
+
+     X < --> Upper limit of integration of the normal-density.
+             Input range: ( -infinity, +infinity)
+
+     MEAN <--> The mean of the normal density.
+               Input range: (-infinity, +infinity)
+
+     SD <--> Standard Deviation of the normal density.
+             Input range: (0, +infinity).
+
+     STATUS <-- 0 if calculation completed correctly
+               -I if input parameter number I is out of range
+                1 if answer appears to be lower than lowest
+                  search bound
+                2 if answer appears to be higher than greatest
+                  search bound
+                3 if P + Q .ne. 1
+
+     BOUND <-- Undefined if STATUS is 0
+
+               Bound exceeded by parameter number I if STATUS
+               is negative.
+
+               Lower search bound if STATUS is 1.
+
+               Upper search bound if STATUS is 2.
+
+
+                              Method
+
+
+
+
+     A slightly modified version of ANORM from
+
+     Cody, W.D. (1993). "ALGORITHM 715: SPECFUN - A Portabel FORTRAN
+     Package of Special Function Routines and Test Drivers"
+     acm Transactions on Mathematical Software. 19, 22-32.
+
+     is used to calulate the  cumulative standard normal distribution.
+
+     The rational functions from pages  90-95  of Kennedy and Gentle,
+     Statistical  Computing,  Marcel  Dekker, NY,  1980 are  used  as
+     starting values to Newton's Iterations which compute the inverse
+     standard normal.  Therefore no  searches  are necessary for  any
+     parameter.
+
+     For X < -15, the asymptotic expansion for the normal is used  as
+     the starting value in finding the inverse standard normal.
+     This is formula 26.2.12 of Abramowitz and Stegun.
+
+
+                              Note
+
+
+      The normal density is proportional to
+      exp( - 0.5 * (( X - MEAN)/SD)**2)
+
+**********************************************************************/
+{
+static int K1 = 1;
+static double z,pq;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     Check arguments
+*/
+    *status = 0;
+    if(!(*which < 1 || *which > 4)) goto S30;
+    if(!(*which < 1)) goto S10;
+    *bound = 1.0e0;
+    goto S20;
+S10:
+    *bound = 4.0e0;
+S20:
+    *status = -1;
+    return;
+S30:
+    if(*which == 1) goto S70;
+/*
+     P
+*/
+    if(!(*p <= 0.0e0 || *p > 1.0e0)) goto S60;
+    if(!(*p <= 0.0e0)) goto S40;
+    *bound = 0.0e0;
+    goto S50;
+S40:
+    *bound = 1.0e0;
+S50:
+    *status = -2;
+    return;
+S70:
+S60:
+    if(*which == 1) goto S110;
+/*
+     Q
+*/
+    if(!(*q <= 0.0e0 || *q > 1.0e0)) goto S100;
+    if(!(*q <= 0.0e0)) goto S80;
+    *bound = 0.0e0;
+    goto S90;
+S80:
+    *bound = 1.0e0;
+S90:
+    *status = -3;
+    return;
+S110:
+S100:
+    if(*which == 1) goto S150;
+/*
+     P + Q
+*/
+    pq = *p+*q;
+    if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S140;
+    if(!(pq < 0.0e0)) goto S120;
+    *bound = 0.0e0;
+    goto S130;
+S120:
+    *bound = 1.0e0;
+S130:
+    *status = 3;
+    return;
+S150:
+S140:
+    if(*which == 4) goto S170;
+/*
+     SD
+*/
+    if(!(*sd <= 0.0e0)) goto S160;
+    *bound = 0.0e0;
+    *status = -6;
+    return;
+S170:
+S160:
+/*
+     Calculate ANSWERS
+*/
+    if(1 == *which) {
+/*
+     Computing P
+*/
+        z = (*x-*mean)/ *sd;
+        cumnor(&z,p,q);
+    }
+    else if(2 == *which) {
+/*
+     Computing X
+*/
+        z = dinvnr(p,q);
+        *x = *sd*z+*mean;
+    }
+    else if(3 == *which) {
+/*
+     Computing the MEAN
+*/
+        z = dinvnr(p,q);
+        *mean = *x-*sd*z;
+    }
+    else if(4 == *which) {
+/*
+     Computing SD
+*/
+        z = dinvnr(p,q);
+        *sd = (*x-*mean)/z;
+    }
+    return;
+}
+void cdfpoi(int *which,double *p,double *q,double *s,double *xlam,
+           int *status,double *bound)
+/**********************************************************************
+
+      void cdfpoi(int *which,double *p,double *q,double *s,double *xlam,
+            int *status,double *bound)
+
+               Cumulative Distribution Function
+               POIsson distribution
+
+
+                              Function
+
+
+     Calculates any one parameter of the Poisson
+     distribution given values for the others.
+
+
+                              Arguments
+
+
+     WHICH --> Integer indicating which  argument
+               value is to be calculated from the others.
+               Legal range: 1..3
+               iwhich = 1 : Calculate P and Q from S and XLAM
+               iwhich = 2 : Calculate A from P,Q and XLAM
+               iwhich = 3 : Calculate XLAM from P,Q and S
+
+        P <--> The cumulation from 0 to S of the poisson density.
+               Input range: [0,1].
+
+        Q <--> 1-P.
+               Input range: (0, 1].
+               P + Q = 1.0.
+
+        S <--> Upper limit of cumulation of the Poisson.
+               Input range: [0, +infinity).
+               Search range: [0,1E100]
+
+     XLAM <--> Mean of the Poisson distribution.
+               Input range: [0, +infinity).
+               Search range: [0,1E100]
+
+     STATUS <-- 0 if calculation completed correctly
+               -I if input parameter number I is out of range
+                1 if answer appears to be lower than lowest
+                  search bound
+                2 if answer appears to be higher than greatest
+                  search bound
+                3 if P + Q .ne. 1
+
+     BOUND <-- Undefined if STATUS is 0
+
+               Bound exceeded by parameter number I if STATUS
+               is negative.
+
+               Lower search bound if STATUS is 1.
+
+               Upper search bound if STATUS is 2.
+
+
+                              Method
+
+
+     Formula   26.4.21  of   Abramowitz  and   Stegun,   Handbook  of
+     Mathematical Functions (1966) is used  to reduce the computation
+     of  the cumulative distribution function to that  of computing a
+     chi-square, hence an incomplete gamma function.
+
+     Cumulative  distribution function  (P) is  calculated  directly.
+     Computation of other parameters involve a seach for a value that
+     produces  the desired value of  P.   The  search relies  on  the
+     monotinicity of P with the other parameter.
+
+**********************************************************************/
+{
+#define tol 1.0e-8
+#define atol 1.0e-50
+#define inf 1.0e100
+static int K1 = 1;
+static double K2 = 0.0e0;
+static double K4 = 0.5e0;
+static double K5 = 5.0e0;
+static double fx,cum,ccum,pq;
+static unsigned long qhi,qleft,qporq;
+static double T3,T6,T7,T8,T9,T10;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     Check arguments
+*/
+    if(!(*which < 1 || *which > 3)) goto S30;
+    if(!(*which < 1)) goto S10;
+    *bound = 1.0e0;
+    goto S20;
+S10:
+    *bound = 3.0e0;
+S20:
+    *status = -1;
+    return;
+S30:
+    if(*which == 1) goto S70;
+/*
+     P
+*/
+    if(!(*p < 0.0e0 || *p > 1.0e0)) goto S60;
+    if(!(*p < 0.0e0)) goto S40;
+    *bound = 0.0e0;
+    goto S50;
+S40:
+    *bound = 1.0e0;
+S50:
+    *status = -2;
+    return;
+S70:
+S60:
+    if(*which == 1) goto S110;
+/*
+     Q
+*/
+    if(!(*q <= 0.0e0 || *q > 1.0e0)) goto S100;
+    if(!(*q <= 0.0e0)) goto S80;
+    *bound = 0.0e0;
+    goto S90;
+S80:
+    *bound = 1.0e0;
+S90:
+    *status = -3;
+    return;
+S110:
+S100:
+    if(*which == 2) goto S130;
+/*
+     S
+*/
+    if(!(*s < 0.0e0)) goto S120;
+    *bound = 0.0e0;
+    *status = -4;
+    return;
+S130:
+S120:
+    if(*which == 3) goto S150;
+/*
+     XLAM
+*/
+    if(!(*xlam < 0.0e0)) goto S140;
+    *bound = 0.0e0;
+    *status = -5;
+    return;
+S150:
+S140:
+    if(*which == 1) goto S190;
+/*
+     P + Q
+*/
+    pq = *p+*q;
+    if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S180;
+    if(!(pq < 0.0e0)) goto S160;
+    *bound = 0.0e0;
+    goto S170;
+S160:
+    *bound = 1.0e0;
+S170:
+    *status = 3;
+    return;
+S190:
+S180:
+    if(!(*which == 1)) qporq = *p <= *q;
+/*
+     Select the minimum of P or Q
+     Calculate ANSWERS
+*/
+    if(1 == *which) {
+/*
+     Calculating P
+*/
+        cumpoi(s,xlam,p,q);
+        *status = 0;
+    }
+    else if(2 == *which) {
+/*
+     Calculating S
+*/
+        *s = 5.0e0;
+        T3 = inf;
+        T6 = atol;
+        T7 = tol;
+        dstinv(&K2,&T3,&K4,&K4,&K5,&T6,&T7);
+        *status = 0;
+        dinvr(status,s,&fx,&qleft,&qhi);
+S200:
+        if(!(*status == 1)) goto S230;
+        cumpoi(s,xlam,&cum,&ccum);
+        if(!qporq) goto S210;
+        fx = cum-*p;
+        goto S220;
+S210:
+        fx = ccum-*q;
+S220:
+        dinvr(status,s,&fx,&qleft,&qhi);
+        goto S200;
+S230:
+        if(!(*status == -1)) goto S260;
+        if(!qleft) goto S240;
+        *status = 1;
+        *bound = 0.0e0;
+        goto S250;
+S240:
+        *status = 2;
+        *bound = inf;
+S260:
+S250:
+        ;
+    }
+    else if(3 == *which) {
+/*
+     Calculating XLAM
+*/
+        *xlam = 5.0e0;
+        T8 = inf;
+        T9 = atol;
+        T10 = tol;
+        dstinv(&K2,&T8,&K4,&K4,&K5,&T9,&T10);
+        *status = 0;
+        dinvr(status,xlam,&fx,&qleft,&qhi);
+S270:
+        if(!(*status == 1)) goto S300;
+        cumpoi(s,xlam,&cum,&ccum);
+        if(!qporq) goto S280;
+        fx = cum-*p;
+        goto S290;
+S280:
+        fx = ccum-*q;
+S290:
+        dinvr(status,xlam,&fx,&qleft,&qhi);
+        goto S270;
+S300:
+        if(!(*status == -1)) goto S330;
+        if(!qleft) goto S310;
+        *status = 1;
+        *bound = 0.0e0;
+        goto S320;
+S310:
+        *status = 2;
+        *bound = inf;
+S320:
+        ;
+    }
+S330:
+    return;
+#undef tol
+#undef atol
+#undef inf
+}
+void cdft(int *which,double *p,double *q,double *t,double *df,
+         int *status,double *bound)
+/**********************************************************************
+
+      void cdft(int *which,double *p,double *q,double *t,double *df,
+          int *status,double *bound)
+
+               Cumulative Distribution Function
+                         T distribution
+
+
+                              Function
+
+
+     Calculates any one parameter of the t distribution given
+     values for the others.
+
+
+                              Arguments
+
+
+     WHICH --> Integer indicating which  argument
+               values is to be calculated from the others.
+               Legal range: 1..3
+               iwhich = 1 : Calculate P and Q from T and DF
+               iwhich = 2 : Calculate T from P,Q and DF
+               iwhich = 3 : Calculate DF from P,Q and T
+
+        P <--> The integral from -infinity to t of the t-density.
+               Input range: (0,1].
+
+        Q <--> 1-P.
+               Input range: (0, 1].
+               P + Q = 1.0.
+
+        T <--> Upper limit of integration of the t-density.
+               Input range: ( -infinity, +infinity).
+               Search range: [ -1E100, 1E100 ]
+
+        DF <--> Degrees of freedom of the t-distribution.
+                Input range: (0 , +infinity).
+                Search range: [1e-100, 1E10]
+
+     STATUS <-- 0 if calculation completed correctly
+               -I if input parameter number I is out of range
+                1 if answer appears to be lower than lowest
+                  search bound
+                2 if answer appears to be higher than greatest
+                  search bound
+                3 if P + Q .ne. 1
+
+     BOUND <-- Undefined if STATUS is 0
+
+               Bound exceeded by parameter number I if STATUS
+               is negative.
+
+               Lower search bound if STATUS is 1.
+
+               Upper search bound if STATUS is 2.
+
+
+                              Method
+
+
+     Formula  26.5.27  of   Abramowitz   and  Stegun,   Handbook   of
+     Mathematical Functions  (1966) is used to reduce the computation
+     of the cumulative distribution function to that of an incomplete
+     beta.
+
+     Computation of other parameters involve a seach for a value that
+     produces  the desired  value  of P.   The search relies  on  the
+     monotinicity of P with the other parameter.
+
+**********************************************************************/
+{
+#define tol 1.0e-8
+#define atol 1.0e-50
+#define zero 1.0e-100
+#define inf 1.0e100
+#define rtinf 1.0e100
+#define maxdf 1.0e10
+static int K1 = 1;
+static double K4 = 0.5e0;
+static double K5 = 5.0e0;
+static double fx,cum,ccum,pq;
+static unsigned long qhi,qleft,qporq;
+static double T2,T3,T6,T7,T8,T9,T10,T11;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     Check arguments
+*/
+    if(!(*which < 1 || *which > 3)) goto S30;
+    if(!(*which < 1)) goto S10;
+    *bound = 1.0e0;
+    goto S20;
+S10:
+    *bound = 3.0e0;
+S20:
+    *status = -1;
+    return;
+S30:
+    if(*which == 1) goto S70;
+/*
+     P
+*/
+    if(!(*p <= 0.0e0 || *p > 1.0e0)) goto S60;
+    if(!(*p <= 0.0e0)) goto S40;
+    *bound = 0.0e0;
+    goto S50;
+S40:
+    *bound = 1.0e0;
+S50:
+    *status = -2;
+    return;
+S70:
+S60:
+    if(*which == 1) goto S110;
+/*
+     Q
+*/
+    if(!(*q <= 0.0e0 || *q > 1.0e0)) goto S100;
+    if(!(*q <= 0.0e0)) goto S80;
+    *bound = 0.0e0;
+    goto S90;
+S80:
+    *bound = 1.0e0;
+S90:
+    *status = -3;
+    return;
+S110:
+S100:
+    if(*which == 3) goto S130;
+/*
+     DF
+*/
+    if(!(*df <= 0.0e0)) goto S120;
+    *bound = 0.0e0;
+    *status = -5;
+    return;
+S130:
+S120:
+    if(*which == 1) goto S170;
+/*
+     P + Q
+*/
+    pq = *p+*q;
+    if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S160;
+    if(!(pq < 0.0e0)) goto S140;
+    *bound = 0.0e0;
+    goto S150;
+S140:
+    *bound = 1.0e0;
+S150:
+    *status = 3;
+    return;
+S170:
+S160:
+    if(!(*which == 1)) qporq = *p <= *q;
+/*
+     Select the minimum of P or Q
+     Calculate ANSWERS
+*/
+    if(1 == *which) {
+/*
+     Computing P and Q
+*/
+        cumt(t,df,p,q);
+        *status = 0;
+    }
+    else if(2 == *which) {
+/*
+     Computing T
+     .. Get initial approximation for T
+*/
+        *t = dt1(p,q,df);
+        T2 = -rtinf;
+        T3 = rtinf;
+        T6 = atol;
+        T7 = tol;
+        dstinv(&T2,&T3,&K4,&K4,&K5,&T6,&T7);
+        *status = 0;
+        dinvr(status,t,&fx,&qleft,&qhi);
+S180:
+        if(!(*status == 1)) goto S210;
+        cumt(t,df,&cum,&ccum);
+        if(!qporq) goto S190;
+        fx = cum-*p;
+        goto S200;
+S190:
+        fx = ccum-*q;
+S200:
+        dinvr(status,t,&fx,&qleft,&qhi);
+        goto S180;
+S210:
+        if(!(*status == -1)) goto S240;
+        if(!qleft) goto S220;
+        *status = 1;
+        *bound = -rtinf;
+        goto S230;
+S220:
+        *status = 2;
+        *bound = rtinf;
+S240:
+S230:
+        ;
+    }
+    else if(3 == *which) {
+/*
+     Computing DF
+*/
+        *df = 5.0e0;
+        T8 = zero;
+        T9 = maxdf;
+        T10 = atol;
+        T11 = tol;
+        dstinv(&T8,&T9,&K4,&K4,&K5,&T10,&T11);
+        *status = 0;
+        dinvr(status,df,&fx,&qleft,&qhi);
+S250:
+        if(!(*status == 1)) goto S280;
+        cumt(t,df,&cum,&ccum);
+        if(!qporq) goto S260;
+        fx = cum-*p;
+        goto S270;
+S260:
+        fx = ccum-*q;
+S270:
+        dinvr(status,df,&fx,&qleft,&qhi);
+        goto S250;
+S280:
+        if(!(*status == -1)) goto S310;
+        if(!qleft) goto S290;
+        *status = 1;
+        *bound = zero;
+        goto S300;
+S290:
+        *status = 2;
+        *bound = maxdf;
+S300:
+        ;
+    }
+S310:
+    return;
+#undef tol
+#undef atol
+#undef zero
+#undef inf
+#undef rtinf
+#undef maxdf
+}
+void cdftnc(int *which,double *p,double *q,double *t,double *df,
+            double *pnonc,int *status,double *bound)
+/**********************************************************************
+   void cdftnc(int *which,double *p,double *q,double *t,double *df,
+               double *pnonc,int *status,double *bound)
+
+                Cumulative Distribution Function
+                   Non-Central T distribution
+                                Function
+      Calculates any one parameter of the noncentral t distribution give
+      values for the others.
+                                Arguments
+      WHICH --> Integer indicating which  argument
+                values is to be calculated from the others.
+                Legal range: 1..3
+                iwhich = 1 : Calculate P and Q from T,DF,PNONC
+                iwhich = 2 : Calculate T from P,Q,DF,PNONC
+                iwhich = 3 : Calculate DF from P,Q,T
+                iwhich = 4 : Calculate PNONC from P,Q,DF,T
+         P <--> The integral from -infinity to t of the noncentral t-den
+               Input range: (0,1].
+         Q <--> 1-P.
+               Input range: (0, 1].
+                P + Q = 1.0.
+         T <--> Upper limit of integration of the noncentral t-density.
+                Input range: ( -infinity, +infinity).
+                Search range: [ -1E100, 1E100 ]
+         DF <--> Degrees of freedom of the noncentral t-distribution.
+                 Input range: (0 , +infinity).
+                 Search range: [1e-100, 1E10]
+      PNONC <--> Noncentrality parameter of the noncentral t-distributio
+                 Input range: [-infinity , +infinity).
+                 Search range: [-1e4, 1E4]
+      STATUS <-- 0 if calculation completed correctly
+                -I if input parameter number I is out of range
+                 1 if answer appears to be lower than lowest
+                   search bound
+                 2 if answer appears to be higher than greatest
+                   search bound
+                 3 if P + Q .ne. 1
+      BOUND <-- Undefined if STATUS is 0
+                Bound exceeded by parameter number I if STATUS
+                is negative.
+                Lower search bound if STATUS is 1.
+                Upper search bound if STATUS is 2.
+                                 Method
+      Upper tail    of  the  cumulative  noncentral t is calculated usin
+      formulae  from page 532  of Johnson, Kotz,  Balakrishnan, Coninuou
+      Univariate Distributions, Vol 2, 2nd Edition.  Wiley (1995)
+      Computation of other parameters involve a seach for a value that
+      produces  the desired  value  of P.   The search relies  on  the
+      monotinicity of P with the other parameter.
+**********************************************************************/
+{
+#define tent4 1.0e4
+#define tol 1.0e-8
+#define atol 1.0e-50
+#define zero 1.0e-100
+#define one ( 1.0e0 - 1.0e-16 )
+#define inf 1.0e100
+static double K3 = 0.5e0;
+static double K4 = 5.0e0;
+static double ccum,cum,fx;
+static unsigned long qhi,qleft;
+static double T1,T2,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(!(*which < 1 || *which > 4)) goto S30;
+    if(!(*which < 1)) goto S10;
+    *bound = 1.0e0;
+    goto S20;
+S10:
+    *bound = 5.0e0;
+S20:
+    *status = -1;
+    return;
+S30:
+    if(*which == 1) goto S70;
+    if(!(*p < 0.0e0 || *p > one)) goto S60;
+    if(!(*p < 0.0e0)) goto S40;
+    *bound = 0.0e0;
+    goto S50;
+S40:
+    *bound = one;
+S50:
+    *status = -2;
+    return;
+S70:
+S60:
+    if(*which == 3) goto S90;
+    if(!(*df <= 0.0e0)) goto S80;
+    *bound = 0.0e0;
+    *status = -5;
+    return;
+S90:
+S80:
+    if(*which == 4) goto S100;
+S100:
+    if(1 == *which) {
+        cumtnc(t,df,pnonc,p,q);
+        *status = 0;
+    }
+    else if(2 == *which) {
+        *t = 5.0e0;
+        T1 = -inf;
+        T2 = inf;
+        T5 = atol;
+        T6 = tol;
+        dstinv(&T1,&T2,&K3,&K3,&K4,&T5,&T6);
+        *status = 0;
+        dinvr(status,t,&fx,&qleft,&qhi);
+S110:
+        if(!(*status == 1)) goto S120;
+        cumtnc(t,df,pnonc,&cum,&ccum);
+        fx = cum - *p;
+        dinvr(status,t,&fx,&qleft,&qhi);
+        goto S110;
+S120:
+        if(!(*status == -1)) goto S150;
+        if(!qleft) goto S130;
+        *status = 1;
+        *bound = -inf;
+        goto S140;
+S130:
+        *status = 2;
+        *bound = inf;
+S150:
+S140:
+        ;
+    }
+    else if(3 == *which) {
+        *df = 5.0e0;
+        T7 = zero;
+        T8 = tent4;
+        T9 = atol;
+        T10 = tol;
+        dstinv(&T7,&T8,&K3,&K3,&K4,&T9,&T10);
+        *status = 0;
+        dinvr(status,df,&fx,&qleft,&qhi);
+S160:
+        if(!(*status == 1)) goto S170;
+        cumtnc(t,df,pnonc,&cum,&ccum);
+        fx = cum - *p;
+        dinvr(status,df,&fx,&qleft,&qhi);
+        goto S160;
+S170:
+        if(!(*status == -1)) goto S200;
+        if(!qleft) goto S180;
+        *status = 1;
+        *bound = zero;
+        goto S190;
+S180:
+        *status = 2;
+        *bound = inf;
+S200:
+S190:
+        ;
+    }
+    else if(4 == *which) {
+        *pnonc = 5.0e0;
+        T11 = -tent4;
+        T12 = tent4;
+        T13 = atol;
+        T14 = tol;
+        dstinv(&T11,&T12,&K3,&K3,&K4,&T13,&T14);
+        *status = 0;
+        dinvr(status,pnonc,&fx,&qleft,&qhi);
+S210:
+        if(!(*status == 1)) goto S220;
+        cumtnc(t,df,pnonc,&cum,&ccum);
+        fx = cum - *p;
+        dinvr(status,pnonc,&fx,&qleft,&qhi);
+        goto S210;
+S220:
+        if(!(*status == -1)) goto S250;
+        if(!qleft) goto S230;
+        *status = 1;
+        *bound = 0.0e0;
+        goto S240;
+S230:
+        *status = 2;
+        *bound = tent4;
+S240:
+        ;
+    }
+S250:
+    return;
+#undef tent4
+#undef tol
+#undef atol
+#undef zero
+#undef one
+#undef inf
+}
+void cumbet(double *x,double *y,double *a,double *b,double *cum,
+           double *ccum)
+/*
+**********************************************************************
+     void cumbet(double *x,double *y,double *a,double *b,double *cum,
+            double *ccum)
+
+          Double precision cUMulative incomplete BETa distribution
+                              Function
+     Calculates the cdf to X of the incomplete beta distribution
+     with parameters a and b.  This is the integral from 0 to x
+     of (1/B(a,b))*f(t)) where f(t) = t**(a-1) * (1-t)**(b-1)
+                              Arguments
+     X --> Upper limit of integration.
+                                        X is DOUBLE PRECISION
+     Y --> 1 - X.
+                                        Y is DOUBLE PRECISION
+     A --> First parameter of the beta distribution.
+                                        A is DOUBLE PRECISION
+     B --> Second parameter of the beta distribution.
+                                        B is DOUBLE PRECISION
+     CUM <-- Cumulative incomplete beta distribution.
+                                        CUM is DOUBLE PRECISION
+     CCUM <-- Compliment of Cumulative incomplete beta distribution.
+                                        CCUM is DOUBLE PRECISION
+                              Method
+     Calls the routine BRATIO.
+                                   References
+     Didonato, Armido R. and Morris, Alfred H. Jr. (1992) Algorithim
+     708 Significant Digit Computation of the Incomplete Beta Function
+     Ratios. ACM ToMS, Vol.18, No. 3, Sept. 1992, 360-373.
+**********************************************************************
+*/
+{
+static int ierr;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(!(*x <= 0.0e0)) goto S10;
+    *cum = 0.0e0;
+    *ccum = 1.0e0;
+    return;
+S10:
+    if(!(*y <= 0.0e0)) goto S20;
+    *cum = 1.0e0;
+    *ccum = 0.0e0;
+    return;
+S20:
+    bratio(a,b,x,y,cum,ccum,&ierr);
+/*
+     Call bratio routine
+*/
+    return;
+}
+void cumbin(double *s,double *xn,double *pr,double *ompr,
+           double *cum,double *ccum)
+/*
+**********************************************************************
+     void cumbin(double *s,double *xn,double *pr,double *ompr,
+            double *cum,double *ccum)
+
+                    CUmulative BINomial distribution
+                              Function
+     Returns the probability   of 0  to  S  successes in  XN   binomial
+     trials, each of which has a probability of success, PBIN.
+                              Arguments
+     S --> The upper limit of cumulation of the binomial distribution.
+                                                  S is DOUBLE PRECISION
+     XN --> The number of binomial trials.
+                                                  XN is DOUBLE PRECISIO
+     PBIN --> The probability of success in each binomial trial.
+                                                  PBIN is DOUBLE PRECIS
+     OMPR --> 1 - PBIN
+                                                  OMPR is DOUBLE PRECIS
+     CUM <-- Cumulative binomial distribution.
+                                                  CUM is DOUBLE PRECISI
+     CCUM <-- Compliment of Cumulative binomial distribution.
+                                                  CCUM is DOUBLE PRECIS
+                              Method
+     Formula  26.5.24    of   Abramowitz  and    Stegun,  Handbook   of
+     Mathematical   Functions (1966) is   used  to reduce the  binomial
+     distribution  to  the  cumulative    beta distribution.
+**********************************************************************
+*/
+{
+static double T1,T2;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(!(*s < *xn)) goto S10;
+    T1 = *s+1.0e0;
+    T2 = *xn-*s;
+    cumbet(pr,ompr,&T1,&T2,ccum,cum);
+    goto S20;
+S10:
+    *cum = 1.0e0;
+    *ccum = 0.0e0;
+S20:
+    return;
+}
+void cumchi(double *x,double *df,double *cum,double *ccum)
+/*
+**********************************************************************
+     void cumchi(double *x,double *df,double *cum,double *ccum)
+             CUMulative of the CHi-square distribution
+                              Function
+     Calculates the cumulative chi-square distribution.
+                              Arguments
+     X       --> Upper limit of integration of the
+                 chi-square distribution.
+                                                 X is DOUBLE PRECISION
+     DF      --> Degrees of freedom of the
+                 chi-square distribution.
+                                                 DF is DOUBLE PRECISION
+     CUM <-- Cumulative chi-square distribution.
+                                                 CUM is DOUBLE PRECISIO
+     CCUM <-- Compliment of Cumulative chi-square distribution.
+                                                 CCUM is DOUBLE PRECISI
+                              Method
+     Calls incomplete gamma function (CUMGAM)
+**********************************************************************
+*/
+{
+static double a,xx;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    a = *df*0.5e0;
+    xx = *x*0.5e0;
+    cumgam(&xx,&a,cum,ccum);
+    return;
+}
+void cumchn(double *x,double *df,double *pnonc,double *cum,
+            double *ccum)
+/**********************************************************************
+     void cumchn(double *x,double *df,double *pnonc,double *cum,
+                 double *ccum)
+
+             CUMulative of the Non-central CHi-square distribution
+                               Function
+     Calculates     the       cumulative      non-central    chi-square
+     distribution, i.e.,  the probability   that  a   random   variable
+     which    follows  the  non-central chi-square  distribution,  with
+     non-centrality  parameter    PNONC  and   continuous  degrees   of
+     freedom DF, is less than or equal to X.
+                              Arguments
+     X       --> Upper limit of integration of the non-central
+                 chi-square distribution.
+     DF      --> Degrees of freedom of the non-central
+                 chi-square distribution.
+     PNONC   --> Non-centrality parameter of the non-central
+                 chi-square distribution.
+     CUM <-- Cumulative non-central chi-square distribution.
+     CCUM <-- Compliment of Cumulative non-central chi-square distribut
+                                Method
+     Uses  formula  26.4.25   of  Abramowitz  and  Stegun, Handbook  of
+     Mathematical    Functions,  US   NBS   (1966)    to calculate  the
+     non-central chi-square.
+                                Variables
+     EPS     --- Convergence criterion.  The sum stops when a
+                 term is less than EPS*SUM.
+     CCUM <-- Compliment of Cumulative non-central
+              chi-square distribution.
+**********************************************************************/
+{
+#define dg(i) (*df + 2.0e0 * (double)(i))
+#define qsmall(xx) (int)(sum < 1.0e-20 || (xx) < eps * sum)
+static double eps = 1.0e-5;
+static double adj,centaj,centwt,chid2,dfd2,lcntaj,lcntwt,lfact,pcent,pterm,sum,
+    sumadj,term,wt,xnonc;
+static int i,icent;
+static double T1,T2,T3;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(!(*x <= 0.0e0)) goto S10;
+    *cum = 0.0e0;
+    *ccum = 1.0e0;
+    return;
+S10:
+    if(!(*pnonc <= 1.0e-10 )) goto S20;
+/*
+     When non-centrality parameter is (essentially) zero,
+     use cumulative chi-square distribution
+*/
+    cumchi(x,df,cum,ccum);
+    return;
+S20:
+    xnonc = *pnonc / 2.0e0;
+/*
+***********************************************************************
+     The following code calcualtes the weight, chi-square, and
+     adjustment term for the central term in the infinite series.
+     The central term is the one in which the poisson weight is
+     greatest.  The adjustment term is the amount that must
+     be subtracted from the chi-square to move up two degrees
+     of freedom.
+***********************************************************************
+*/
+    icent = fifidint(xnonc);
+    if(icent == 0) icent = 1;
+    chid2 = *x / 2.0e0;
+/*
+     Calculate central weight term
+*/
+    T1 = (double)(icent + 1);
+    lfact = alngam(&T1);
+    lcntwt = -xnonc + (double)icent * log(xnonc) - lfact;
+    centwt = exp(lcntwt);
+/*
+     Calculate central chi-square
+*/
+    T2 = dg(icent);
+    cumchi(x,&T2,&pcent,ccum);
+/*
+     Calculate central adjustment term
+*/
+    dfd2 = dg(icent) / 2.0e0;
+    T3 = 1.0e0 + dfd2;
+    lfact = alngam(&T3);
+    lcntaj = dfd2 * log(chid2) - chid2 - lfact;
+    centaj = exp(lcntaj);
+    sum = centwt * pcent;
+/*
+***********************************************************************
+     Sum backwards from the central term towards zero.
+     Quit whenever either
+     (1) the zero term is reached, or
+     (2) the term gets small relative to the sum
+***********************************************************************
+*/
+    sumadj = 0.0e0;
+    adj = centaj;
+    wt = centwt;
+    i = icent;
+    goto S40;
+S30:
+    if(qsmall(term) || i == 0) goto S50;
+S40:
+    dfd2 = dg(i) / 2.0e0;
+/*
+     Adjust chi-square for two fewer degrees of freedom.
+     The adjusted value ends up in PTERM.
+*/
+    adj = adj * dfd2 / chid2;
+    sumadj += adj;
+    pterm = pcent + sumadj;
+/*
+     Adjust poisson weight for J decreased by one
+*/
+    wt *= ((double)i / xnonc);
+    term = wt * pterm;
+    sum += term;
+    i -= 1;
+    goto S30;
+S50:
+/*
+***********************************************************************
+     Now sum forward from the central term towards infinity.
+     Quit when either
+     (1) the term gets small relative to the sum, or
+***********************************************************************
+*/
+    sumadj = adj = centaj;
+    wt = centwt;
+    i = icent;
+    goto S70;
+S60:
+    if(qsmall(term)) goto S80;
+S70:
+/*
+     Update weights for next higher J
+*/
+    wt *= (xnonc / (double)(i + 1));
+/*
+     Calculate PTERM and add term to sum
+*/
+    pterm = pcent - sumadj;
+    term = wt * pterm;
+    sum += term;
+/*
+     Update adjustment term for DF for next iteration
+*/
+    i += 1;
+    dfd2 = dg(i) / 2.0e0;
+    adj = adj * chid2 / dfd2;
+    sumadj += adj;
+    goto S60;
+S80:
+    *cum = sum;
+    *ccum = 0.5e0 + (0.5e0 - *cum);
+    return;
+#undef dg
+#undef qsmall
+}
+void cumf(double *f,double *dfn,double *dfd,double *cum,double *ccum)
+/*
+**********************************************************************
+     void cumf(double *f,double *dfn,double *dfd,double *cum,double *ccum)
+                    CUMulative F distribution
+                              Function
+     Computes  the  integral from  0  to  F of  the f-density  with DFN
+     and DFD degrees of freedom.
+                              Arguments
+     F --> Upper limit of integration of the f-density.
+                                                  F is DOUBLE PRECISION
+     DFN --> Degrees of freedom of the numerator sum of squares.
+                                                  DFN is DOUBLE PRECISI
+     DFD --> Degrees of freedom of the denominator sum of squares.
+                                                  DFD is DOUBLE PRECISI
+     CUM <-- Cumulative f distribution.
+                                                  CUM is DOUBLE PRECISI
+     CCUM <-- Compliment of Cumulative f distribution.
+                                                  CCUM is DOUBLE PRECIS
+                              Method
+     Formula  26.5.28 of  Abramowitz and   Stegun   is  used to  reduce
+     the cumulative F to a cumulative beta distribution.
+                              Note
+     If F is less than or equal to 0, 0 is returned.
+**********************************************************************
+*/
+{
+#define half 0.5e0
+#define done 1.0e0
+static double dsum,prod,xx,yy;
+static int ierr;
+static double T1,T2;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(!(*f <= 0.0e0)) goto S10;
+    *cum = 0.0e0;
+    *ccum = 1.0e0;
+    return;
+S10:
+    prod = *dfn**f;
+/*
+     XX is such that the incomplete beta with parameters
+     DFD/2 and DFN/2 evaluated at XX is 1 - CUM or CCUM
+     YY is 1 - XX
+     Calculate the smaller of XX and YY accurately
+*/
+    dsum = *dfd+prod;
+    xx = *dfd/dsum;
+    if(xx > half) {
+        yy = prod/dsum;
+        xx = done-yy;
+    }
+    else  yy = done-xx;
+    T1 = *dfd*half;
+    T2 = *dfn*half;
+    bratio(&T1,&T2,&xx,&yy,ccum,cum,&ierr);
+    return;
+#undef half
+#undef done
+}
+void cumfnc(double *f,double *dfn,double *dfd,double *pnonc,
+           double *cum,double *ccum)
+/*
+**********************************************************************
+               F -NON- -C-ENTRAL F DISTRIBUTION
+                              Function
+     COMPUTES NONCENTRAL F DISTRIBUTION WITH DFN AND DFD
+     DEGREES OF FREEDOM AND NONCENTRALITY PARAMETER PNONC
+                              Arguments
+     X --> UPPER LIMIT OF INTEGRATION OF NONCENTRAL F IN EQUATION
+     DFN --> DEGREES OF FREEDOM OF NUMERATOR
+     DFD -->  DEGREES OF FREEDOM OF DENOMINATOR
+     PNONC --> NONCENTRALITY PARAMETER.
+     CUM <-- CUMULATIVE NONCENTRAL F DISTRIBUTION
+     CCUM <-- COMPLIMENT OF CUMMULATIVE
+                              Method
+     USES FORMULA 26.6.20 OF REFERENCE FOR INFINITE SERIES.
+     SERIES IS CALCULATED BACKWARD AND FORWARD FROM J = LAMBDA/2
+     (THIS IS THE TERM WITH THE LARGEST POISSON WEIGHT) UNTIL
+     THE CONVERGENCE CRITERION IS MET.
+     FOR SPEED, THE INCOMPLETE BETA FUNCTIONS ARE EVALUATED
+     BY FORMULA 26.5.16.
+               REFERENCE
+     HANDBOOD OF MATHEMATICAL FUNCTIONS
+     EDITED BY MILTON ABRAMOWITZ AND IRENE A. STEGUN
+     NATIONAL BUREAU OF STANDARDS APPLIED MATEMATICS SERIES - 55
+     MARCH 1965
+     P 947, EQUATIONS 26.6.17, 26.6.18
+                              Note
+     THE SUM CONTINUES UNTIL A SUCCEEDING TERM IS LESS THAN EPS
+     TIMES THE SUM (OR THE SUM IS LESS THAN 1.0E-20).  EPS IS
+     SET TO 1.0E-4 IN A DATA STATEMENT WHICH CAN BE CHANGED.
+**********************************************************************
+*/
+{
+#define qsmall(x) (int)(sum < 1.0e-20 || (x) < eps*sum)
+#define half 0.5e0
+#define done 1.0e0
+static double eps = 1.0e-4;
+static double dsum,dummy,prod,xx,yy,adn,aup,b,betdn,betup,centwt,dnterm,sum,
+    upterm,xmult,xnonc;
+static int i,icent,ierr;
+static double T1,T2,T3,T4,T5,T6;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(!(*f <= 0.0e0)) goto S10;
+    *cum = 0.0e0;
+    *ccum = 1.0e0;
+    return;
+S10:
+    if(!(*pnonc < 1.0e-10)) goto S20;
+/*
+     Handle case in which the non-centrality parameter is
+     (essentially) zero.
+*/
+    cumf(f,dfn,dfd,cum,ccum);
+    return;
+S20:
+    xnonc = *pnonc/2.0e0;
+/*
+     Calculate the central term of the poisson weighting factor.
+*/
+    icent = (long)(xnonc);
+    if(icent == 0) icent = 1;
+/*
+     Compute central weight term
+*/
+    T1 = (double)(icent+1);
+    centwt = exp(-xnonc+(double)icent*log(xnonc)-alngam(&T1));
+/*
+     Compute central incomplete beta term
+     Assure that minimum of arg to beta and 1 - arg is computed
+          accurately.
+*/
+    prod = *dfn**f;
+    dsum = *dfd+prod;
+    yy = *dfd/dsum;
+    if(yy > half) {
+        xx = prod/dsum;
+        yy = done-xx;
+    }
+    else  xx = done-yy;
+    T2 = *dfn*half+(double)icent;
+    T3 = *dfd*half;
+    bratio(&T2,&T3,&xx,&yy,&betdn,&dummy,&ierr);
+    adn = *dfn/2.0e0+(double)icent;
+    aup = adn;
+    b = *dfd/2.0e0;
+    betup = betdn;
+    sum = centwt*betdn;
+/*
+     Now sum terms backward from icent until convergence or all done
+*/
+    xmult = centwt;
+    i = icent;
+    T4 = adn+b;
+    T5 = adn+1.0e0;
+    dnterm = exp(alngam(&T4)-alngam(&T5)-alngam(&b)+adn*log(xx)+b*log(yy));
+S30:
+    if(qsmall(xmult*betdn) || i <= 0) goto S40;
+    xmult *= ((double)i/xnonc);
+    i -= 1;
+    adn -= 1.0;
+    dnterm = (adn+1.0)/((adn+b)*xx)*dnterm;
+    betdn += dnterm;
+    sum += (xmult*betdn);
+    goto S30;
+S40:
+    i = icent+1;
+/*
+     Now sum forwards until convergence
+*/
+    xmult = centwt;
+    if(aup-1.0+b == 0) upterm = exp(-alngam(&aup)-alngam(&b)+(aup-1.0)*log(xx)+
+      b*log(yy));
+    else  {
+        T6 = aup-1.0+b;
+        upterm = exp(alngam(&T6)-alngam(&aup)-alngam(&b)+(aup-1.0)*log(xx)+b*
+          log(yy));
+    }
+    goto S60;
+S50:
+    if(qsmall(xmult*betup)) goto S70;
+S60:
+    xmult *= (xnonc/(double)i);
+    i += 1;
+    aup += 1.0;
+    upterm = (aup+b-2.0e0)*xx/(aup-1.0)*upterm;
+    betup -= upterm;
+    sum += (xmult*betup);
+    goto S50;
+S70:
+    *cum = sum;
+    *ccum = 0.5e0+(0.5e0-*cum);
+    return;
+#undef qsmall
+#undef half
+#undef done
+}
+void cumgam(double *x,double *a,double *cum,double *ccum)
+/*
+**********************************************************************
+     void cumgam(double *x,double *a,double *cum,double *ccum)
+           Double precision cUMulative incomplete GAMma distribution
+                              Function
+     Computes   the  cumulative        of    the     incomplete   gamma
+     distribution, i.e., the integral from 0 to X of
+          (1/GAM(A))*EXP(-T)*T**(A-1) DT
+     where GAM(A) is the complete gamma function of A, i.e.,
+          GAM(A) = integral from 0 to infinity of
+                    EXP(-T)*T**(A-1) DT
+                              Arguments
+     X --> The upper limit of integration of the incomplete gamma.
+                                                X is DOUBLE PRECISION
+     A --> The shape parameter of the incomplete gamma.
+                                                A is DOUBLE PRECISION
+     CUM <-- Cumulative incomplete gamma distribution.
+                                        CUM is DOUBLE PRECISION
+     CCUM <-- Compliment of Cumulative incomplete gamma distribution.
+                                                CCUM is DOUBLE PRECISIO
+                              Method
+     Calls the routine GRATIO.
+**********************************************************************
+*/
+{
+static int K1 = 0;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(!(*x <= 0.0e0)) goto S10;
+    *cum = 0.0e0;
+    *ccum = 1.0e0;
+    return;
+S10:
+    gratio(a,x,cum,ccum,&K1);
+/*
+     Call gratio routine
+*/
+    return;
+}
+void cumnbn(double *s,double *xn,double *pr,double *ompr,
+           double *cum,double *ccum)
+/*
+**********************************************************************
+     void cumnbn(double *s,double *xn,double *pr,double *ompr,
+            double *cum,double *ccum)
+
+                    CUmulative Negative BINomial distribution
+                              Function
+     Returns the probability that it there will be S or fewer failures
+     before there are XN successes, with each binomial trial having
+     a probability of success PR.
+     Prob(# failures = S | XN successes, PR)  =
+                        ( XN + S - 1 )
+                        (            ) * PR^XN * (1-PR)^S
+                        (      S     )
+                              Arguments
+     S --> The number of failures
+                                                  S is DOUBLE PRECISION
+     XN --> The number of successes
+                                                  XN is DOUBLE PRECISIO
+     PR --> The probability of success in each binomial trial.
+                                                  PR is DOUBLE PRECISIO
+     OMPR --> 1 - PR
+                                                  OMPR is DOUBLE PRECIS
+     CUM <-- Cumulative negative binomial distribution.
+                                                  CUM is DOUBLE PRECISI
+     CCUM <-- Compliment of Cumulative negative binomial distribution.
+                                                  CCUM is DOUBLE PRECIS
+                              Method
+     Formula  26.5.26    of   Abramowitz  and    Stegun,  Handbook   of
+     Mathematical   Functions (1966) is   used  to reduce the  negative
+     binomial distribution to the cumulative beta distribution.
+**********************************************************************
+*/
+{
+static double T1;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    T1 = *s+1.e0;
+    cumbet(pr,ompr,xn,&T1,cum,ccum);
+    return;
+}
+void cumnor(double *arg,double *result,double *ccum)
+/*
+**********************************************************************
+     void cumnor(double *arg,double *result,double *ccum)
+                              Function
+     Computes the cumulative  of    the  normal   distribution,   i.e.,
+     the integral from -infinity to x of
+          (1/sqrt(2*pi)) exp(-u*u/2) du
+     X --> Upper limit of integration.
+                                        X is DOUBLE PRECISION
+     RESULT <-- Cumulative normal distribution.
+                                        RESULT is DOUBLE PRECISION
+     CCUM <-- Compliment of Cumulative normal distribution.
+                                        CCUM is DOUBLE PRECISION
+     Renaming of function ANORM from:
+
+     Cody, W.D. (1993). "ALGORITHM 715: SPECFUN - A Portabel FORTRAN
+     Package of Special Function Routines and Test Drivers"
+     acm Transactions on Mathematical Software. 19, 22-32.
+
+     with slight modifications to return ccum and to deal with
+     machine constants.
+**********************************************************************
+  Original Comments:
+------------------------------------------------------------------
+ This function evaluates the normal distribution function:
+                              / x
+                     1       |       -t*t/2
+          P(x) = ----------- |      e       dt
+                 sqrt(2 pi)  |
+                             /-oo
+   The main computation evaluates near-minimax approximations
+   derived from those in "Rational Chebyshev approximations for
+   the error function" by W. J. Cody, Math. Comp., 1969, 631-637.
+   This transportable program uses rational functions that
+   theoretically approximate the normal distribution function to
+   at least 18 significant decimal digits.  The accuracy achieved
+   depends on the arithmetic system, the compiler, the intrinsic
+   functions, and proper selection of the machine-dependent
+   constants.
+*******************************************************************
+*******************************************************************
+ Explanation of machine-dependent constants.
+   MIN   = smallest machine representable number.
+   EPS   = argument below which anorm(x) may be represented by
+           0.5  and above which  x*x  will not underflow.
+           A conservative value is the largest machine number X
+           such that   1.0 + X = 1.0   to machine precision.
+*******************************************************************
+*******************************************************************
+ Error returns
+  The program returns  ANORM = 0     for  ARG .LE. XLOW.
+ Intrinsic functions required are:
+     ABS, AINT, EXP
+  Author: W. J. Cody
+          Mathematics and Computer Science Division
+          Argonne National Laboratory
+          Argonne, IL 60439
+  Latest modification: March 15, 1992
+------------------------------------------------------------------
+*/
+{
+static double a[5] = {
+    2.2352520354606839287e00,1.6102823106855587881e02,1.0676894854603709582e03,
+    1.8154981253343561249e04,6.5682337918207449113e-2
+};
+static double b[4] = {
+    4.7202581904688241870e01,9.7609855173777669322e02,1.0260932208618978205e04,
+    4.5507789335026729956e04
+};
+static double c[9] = {
+    3.9894151208813466764e-1,8.8831497943883759412e00,9.3506656132177855979e01,
+    5.9727027639480026226e02,2.4945375852903726711e03,6.8481904505362823326e03,
+    1.1602651437647350124e04,9.8427148383839780218e03,1.0765576773720192317e-8
+};
+static double d[8] = {
+    2.2266688044328115691e01,2.3538790178262499861e02,1.5193775994075548050e03,
+    6.4855582982667607550e03,1.8615571640885098091e04,3.4900952721145977266e04,
+    3.8912003286093271411e04,1.9685429676859990727e04
+};
+static double half = 0.5e0;
+static double p[6] = {
+    2.1589853405795699e-1,1.274011611602473639e-1,2.2235277870649807e-2,
+    1.421619193227893466e-3,2.9112874951168792e-5,2.307344176494017303e-2
+};
+static double one = 1.0e0;
+static double q[5] = {
+    1.28426009614491121e00,4.68238212480865118e-1,6.59881378689285515e-2,
+    3.78239633202758244e-3,7.29751555083966205e-5
+};
+static double sixten = 1.60e0;
+static double sqrpi = 3.9894228040143267794e-1;
+static double thrsh = 0.66291e0;
+static double root32 = 5.656854248e0;
+static double zero = 0.0e0;
+static int K1 = 1;
+static int K2 = 2;
+static int i;
+static double del,eps,temp,x,xden,xnum,y,xsq,min;
+/*
+------------------------------------------------------------------
+  Machine dependent constants
+------------------------------------------------------------------
+*/
+    eps = spmpar(&K1)*0.5e0;
+    min = spmpar(&K2);
+    x = *arg;
+    y = fabs(x);
+    if(y <= thrsh) {
+/*
+------------------------------------------------------------------
+  Evaluate  anorm  for  |X| <= 0.66291
+------------------------------------------------------------------
+*/
+        xsq = zero;
+        if(y > eps) xsq = x*x;
+        xnum = a[4]*xsq;
+        xden = xsq;
+        for(i=0; i<3; i++) {
+            xnum = (xnum+a[i])*xsq;
+            xden = (xden+b[i])*xsq;
+        }
+        *result = x*(xnum+a[3])/(xden+b[3]);
+        temp = *result;
+        *result = half+temp;
+        *ccum = half-temp;
+    }
+/*
+------------------------------------------------------------------
+  Evaluate  anorm  for 0.66291 <= |X| <= sqrt(32)
+------------------------------------------------------------------
+*/
+    else if(y <= root32) {
+        xnum = c[8]*y;
+        xden = y;
+        for(i=0; i<7; i++) {
+            xnum = (xnum+c[i])*y;
+            xden = (xden+d[i])*y;
+        }
+        *result = (xnum+c[7])/(xden+d[7]);
+        xsq = fifdint(y*sixten)/sixten;
+        del = (y-xsq)*(y+xsq);
+        *result = exp(-(xsq*xsq*half))*exp(-(del*half))**result;
+        *ccum = one-*result;
+        if(x > zero) {
+            temp = *result;
+            *result = *ccum;
+            *ccum = temp;
+        }
+    }
+/*
+------------------------------------------------------------------
+  Evaluate  anorm  for |X| > sqrt(32)
+------------------------------------------------------------------
+*/
+    else  {
+        *result = zero;
+        xsq = one/(x*x);
+        xnum = p[5]*xsq;
+        xden = xsq;
+        for(i=0; i<4; i++) {
+            xnum = (xnum+p[i])*xsq;
+            xden = (xden+q[i])*xsq;
+        }
+        *result = xsq*(xnum+p[4])/(xden+q[4]);
+        *result = (sqrpi-*result)/y;
+        xsq = fifdint(x*sixten)/sixten;
+        del = (x-xsq)*(x+xsq);
+        *result = exp(-(xsq*xsq*half))*exp(-(del*half))**result;
+        *ccum = one-*result;
+        if(x > zero) {
+            temp = *result;
+            *result = *ccum;
+            *ccum = temp;
+        }
+    }
+    if(*result < min) *result = 0.0e0;
+/*
+------------------------------------------------------------------
+  Fix up for negative argument, erf, etc.
+------------------------------------------------------------------
+----------Last card of ANORM ----------
+*/
+    if(*ccum < min) *ccum = 0.0e0;
+}
+void cumpoi(double *s,double *xlam,double *cum,double *ccum)
+/*
+**********************************************************************
+     void cumpoi(double *s,double *xlam,double *cum,double *ccum)
+                    CUMulative POIsson distribution
+                              Function
+     Returns the  probability  of  S   or  fewer events in  a   Poisson
+     distribution with mean XLAM.
+                              Arguments
+     S --> Upper limit of cumulation of the Poisson.
+                                                  S is DOUBLE PRECISION
+     XLAM --> Mean of the Poisson distribution.
+                                                  XLAM is DOUBLE PRECIS
+     CUM <-- Cumulative poisson distribution.
+                                        CUM is DOUBLE PRECISION
+     CCUM <-- Compliment of Cumulative poisson distribution.
+                                                  CCUM is DOUBLE PRECIS
+                              Method
+     Uses formula  26.4.21   of   Abramowitz and  Stegun,  Handbook  of
+     Mathematical   Functions  to reduce   the   cumulative Poisson  to
+     the cumulative chi-square distribution.
+**********************************************************************
+*/
+{
+static double chi,df;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    df = 2.0e0*(*s+1.0e0);
+    chi = 2.0e0**xlam;
+    cumchi(&chi,&df,ccum,cum);
+    return;
+}
+void cumt(double *t,double *df,double *cum,double *ccum)
+/*
+**********************************************************************
+     void cumt(double *t,double *df,double *cum,double *ccum)
+                    CUMulative T-distribution
+                              Function
+     Computes the integral from -infinity to T of the t-density.
+                              Arguments
+     T --> Upper limit of integration of the t-density.
+                                                  T is DOUBLE PRECISION
+     DF --> Degrees of freedom of the t-distribution.
+                                                  DF is DOUBLE PRECISIO
+     CUM <-- Cumulative t-distribution.
+                                                  CCUM is DOUBLE PRECIS
+     CCUM <-- Compliment of Cumulative t-distribution.
+                                                  CCUM is DOUBLE PRECIS
+                              Method
+     Formula 26.5.27   of     Abramowitz  and   Stegun,    Handbook  of
+     Mathematical Functions  is   used   to  reduce the  t-distribution
+     to an incomplete beta.
+**********************************************************************
+*/
+{
+static double K2 = 0.5e0;
+static double xx,a,oma,tt,yy,dfptt,T1;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    tt = *t**t;
+    dfptt = *df+tt;
+    xx = *df/dfptt;
+    yy = tt/dfptt;
+    T1 = 0.5e0**df;
+    cumbet(&xx,&yy,&T1,&K2,&a,&oma);
+    if(!(*t <= 0.0e0)) goto S10;
+    *cum = 0.5e0*a;
+    *ccum = oma+*cum;
+    goto S20;
+S10:
+    *ccum = 0.5e0*a;
+    *cum = oma+*ccum;
+S20:
+    return;
+}
+void cumtnc(double *t,double *df,double *pnonc,double *cum,
+            double *ccum)
+/**********************************************************************
+     void cumtnc(double *t,double *df,double *pnonc,double *cum,
+                 double *ccum)
+                  CUMulative Non-Central T-distribution
+                               Function
+      Computes the integral from -infinity to T of the non-central
+      t-density.
+                               Arguments
+      T --> Upper limit of integration of the non-central t-density.
+      DF --> Degrees of freedom of the non-central t-distribution.
+      PNONC --> Non-centrality parameter of the non-central t distibutio
+      CUM <-- Cumulative t-distribution.
+      CCUM <-- Compliment of Cumulative t-distribution.
+                               Method
+      Upper tail    of  the  cumulative  noncentral t   using
+      formulae from page 532  of Johnson, Kotz,  Balakrishnan, Coninuous
+      Univariate Distributions, Vol 2, 2nd Edition.  Wiley (1995)
+      This implementation starts the calculation at i = lambda,
+      which is near the largest Di.  It then sums forward and backward.
+**********************************************************************/
+{
+#define one 1.0e0
+#define zero 0.0e0
+#define half 0.5e0
+#define two 2.0e0
+#define onep5 1.5e0
+#define conv 1.0e-7
+#define tiny 1.0e-10
+static double alghdf,b,bb,bbcent,bcent,cent,d,dcent,dpnonc,dum1,dum2,e,ecent,
+    halfdf,lambda,lnomx,lnx,omx,pnonc2,s,scent,ss,sscent,t2,term,tt,twoi,x,xi,
+    xlnd,xlne;
+static int ierr;
+static unsigned long qrevs;
+static double T1,T2,T3,T4,T5,T6,T7,T8,T9,T10;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     Case pnonc essentially zero
+*/
+    if(fabs(*pnonc) <= tiny) {
+        cumt(t,df,cum,ccum);
+        return;
+    }
+    qrevs = *t < zero;
+    if(qrevs) {
+        tt = -*t;
+        dpnonc = -*pnonc;
+    }
+    else  {
+        tt = *t;
+        dpnonc = *pnonc;
+    }
+    pnonc2 = dpnonc * dpnonc;
+    t2 = tt * tt;
+    if(fabs(tt) <= tiny) {
+        T1 = -*pnonc;
+        cumnor(&T1,cum,ccum);
+        return;
+    }
+    lambda = half * pnonc2;
+    x = *df / (*df + t2);
+    omx = one - x;
+    lnx = log(x);
+    lnomx = log(omx);
+    halfdf = half * *df;
+    alghdf = gamln(&halfdf);
+/*
+     ******************** Case i = lambda
+*/
+    cent = fifidint(lambda);
+    if(cent < one) cent = one;
+/*
+     Compute d=T(2i) in log space and offset by exp(-lambda)
+*/
+    T2 = cent + one;
+    xlnd = cent * log(lambda) - gamln(&T2) - lambda;
+    dcent = exp(xlnd);
+/*
+     Compute e=t(2i+1) in log space offset by exp(-lambda)
+*/
+    T3 = cent + onep5;
+    xlne = (cent + half) * log(lambda) - gamln(&T3) - lambda;
+    ecent = exp(xlne);
+    if(dpnonc < zero) ecent = -ecent;
+/*
+     Compute bcent=B(2*cent)
+*/
+    T4 = cent + half;
+    bratio(&halfdf,&T4,&x,&omx,&bcent,&dum1,&ierr);
+/*
+     compute bbcent=B(2*cent+1)
+*/
+    T5 = cent + one;
+    bratio(&halfdf,&T5,&x,&omx,&bbcent,&dum2,&ierr);
+/*
+     Case bcent and bbcent are essentially zero
+     Thus t is effectively infinite
+*/
+    if(bcent + bbcent < tiny) {
+        if(qrevs) {
+            *cum = zero;
+            *ccum = one;
+        }
+        else  {
+            *cum = one;
+            *ccum = zero;
+        }
+        return;
+    }
+/*
+     Case bcent and bbcent are essentially one
+     Thus t is effectively zero
+*/
+    if(dum1 + dum2 < tiny) {
+        T6 = -*pnonc;
+        cumnor(&T6,cum,ccum);
+        return;
+    }
+/*
+     First term in ccum is D*B + E*BB
+*/
+    *ccum = dcent * bcent + ecent * bbcent;
+/*
+     compute s(cent) = B(2*(cent+1)) - B(2*cent))
+*/
+    T7 = halfdf + cent + half;
+    T8 = cent + onep5;
+    scent = gamln(&T7) - gamln(&T8) - alghdf + halfdf * lnx + (cent + half) * 
+      lnomx;
+    scent = exp(scent);
+/*
+     compute ss(cent) = B(2*cent+3) - B(2*cent+1)
+*/
+    T9 = halfdf + cent + one;
+    T10 = cent + two;
+    sscent = gamln(&T9) - gamln(&T10) - alghdf + halfdf * lnx + (cent + one) * 
+      lnomx;
+    sscent = exp(sscent);
+/*
+     ******************** Sum Forward
+*/
+    xi = cent + one;
+    twoi = two * xi;
+    d = dcent;
+    e = ecent;
+    b = bcent;
+    bb = bbcent;
+    s = scent;
+    ss = sscent;
+S10:
+    b += s;
+    bb += ss;
+    d = lambda / xi * d;
+    e = lambda / (xi + half) * e;
+    term = d * b + e * bb;
+    *ccum += term;
+    s = s * omx * (*df + twoi - one) / (twoi + one);
+    ss = ss * omx * (*df + twoi) / (twoi + two);
+    xi += one;
+    twoi = two * xi;
+    if(fabs(term) > conv * *ccum) goto S10;
+/*
+     ******************** Sum Backward
+*/
+    xi = cent;
+    twoi = two * xi;
+    d = dcent;
+    e = ecent;
+    b = bcent;
+    bb = bbcent;
+    s = scent * (one + twoi) / ((*df + twoi - one) * omx);
+    ss = sscent * (two + twoi) / ((*df + twoi) * omx);
+S20:
+    b -= s;
+    bb -= ss;
+    d *= (xi / lambda);
+    e *= ((xi + half) / lambda);
+    term = d * b + e * bb;
+    *ccum += term;
+    xi -= one;
+    if(xi < half) goto S30;
+    twoi = two * xi;
+    s = s * (one + twoi) / ((*df + twoi - one) * omx);
+    ss = ss * (two + twoi) / ((*df + twoi) * omx);
+    if(fabs(term) > conv * *ccum) goto S20;
+S30:
+    if(qrevs) {
+        *cum = half * *ccum;
+        *ccum = one - *cum;
+    }
+    else  {
+        *ccum = half * *ccum;
+        *cum = one - *ccum;
+    }
+/*
+     Due to roundoff error the answer may not lie between zero and one
+     Force it to do so
+*/
+    *cum = fifdmax1(fifdmin1(*cum,one),zero);
+    *ccum = fifdmax1(fifdmin1(*ccum,one),zero);
+    return;
+#undef one
+#undef zero
+#undef half
+#undef two
+#undef onep5
+#undef conv
+#undef tiny
+}
+double devlpl(double a[],int *n,double *x)
+/*
+**********************************************************************
+     double devlpl(double a[],int *n,double *x)
+              Double precision EVALuate a PoLynomial at X
+                              Function
+     returns
+          A(1) + A(2)*X + ... + A(N)*X**(N-1)
+                              Arguments
+     A --> Array of coefficients of the polynomial.
+                                        A is DOUBLE PRECISION(N)
+     N --> Length of A, also degree of polynomial - 1.
+                                        N is INTEGER
+     X --> Point at which the polynomial is to be evaluated.
+                                        X is DOUBLE PRECISION
+**********************************************************************
+*/
+{
+static double devlpl,term;
+static int i;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    term = a[*n-1];
+    for(i= *n-1-1; i>=0; i--) term = a[i]+term**x;
+    devlpl = term;
+    return devlpl;
+}
+double dinvnr(double *p,double *q)
+/*
+**********************************************************************
+     double dinvnr(double *p,double *q)
+     Double precision NoRmal distribution INVerse
+                              Function
+     Returns X  such that CUMNOR(X)  =   P,  i.e., the  integral from -
+     infinity to X of (1/SQRT(2*PI)) EXP(-U*U/2) dU is P
+                              Arguments
+     P --> The probability whose normal deviate is sought.
+                    P is DOUBLE PRECISION
+     Q --> 1-P
+                    P is DOUBLE PRECISION
+                              Method
+     The  rational   function   on  page 95    of Kennedy  and  Gentle,
+     Statistical Computing, Marcel Dekker, NY , 1980 is used as a start
+     value for the Newton method of finding roots.
+                              Note
+     If P or Q .lt. machine EPS returns +/- DINVNR(EPS)
+**********************************************************************
+*/
+{
+#define maxit 100
+#define eps 1.0e-13
+#define r2pi 0.3989422804014326e0
+#define nhalf -0.5e0
+#define dennor(x) (r2pi*exp(nhalf*(x)*(x)))
+static double dinvnr,strtx,xcur,cum,ccum,pp,dx;
+static int i;
+static unsigned long qporq;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     FIND MINIMUM OF P AND Q
+*/
+    qporq = *p <= *q;
+    if(!qporq) goto S10;
+    pp = *p;
+    goto S20;
+S10:
+    pp = *q;
+S20:
+/*
+     INITIALIZATION STEP
+*/
+    strtx = stvaln(&pp);
+    xcur = strtx;
+/*
+     NEWTON INTERATIONS
+*/
+    for(i=1; i<=maxit; i++) {
+        cumnor(&xcur,&cum,&ccum);
+        dx = (cum-pp)/dennor(xcur);
+        xcur -= dx;
+        if(fabs(dx/xcur) < eps) goto S40;
+    }
+    dinvnr = strtx;
+/*
+     IF WE GET HERE, NEWTON HAS FAILED
+*/
+    if(!qporq) dinvnr = -dinvnr;
+    return dinvnr;
+S40:
+/*
+     IF WE GET HERE, NEWTON HAS SUCCEDED
+*/
+    dinvnr = xcur;
+    if(!qporq) dinvnr = -dinvnr;
+    return dinvnr;
+#undef maxit
+#undef eps
+#undef r2pi
+#undef nhalf
+#undef dennor
+}
+/* DEFINE DINVR */
+static void E0000(int IENTRY,int *status,double *x,double *fx,
+                 unsigned long *qleft,unsigned long *qhi,double *zabsst,
+                 double *zabsto,double *zbig,double *zrelst,
+                 double *zrelto,double *zsmall,double *zstpmu)
+{
+#define qxmon(zx,zy,zz) (int)((zx) <= (zy) && (zy) <= (zz))
+static double absstp,abstol,big,fbig,fsmall,relstp,reltol,small,step,stpmul,xhi,
+    xlb,xlo,xsave,xub,yy;
+static int i99999;
+static unsigned long qbdd,qcond,qdum1,qdum2,qincr,qlim,qok,qup;
+    switch(IENTRY){case 0: goto DINVR; case 1: goto DSTINV;}
+DINVR:
+    if(*status > 0) goto S310;
+    qcond = !qxmon(small,*x,big);
+    if(qcond) ftnstop((char *) " SMALL, X, BIG not monotone in INVR");
+    xsave = *x;
+/*
+     See that SMALL and BIG bound the zero and set QINCR
+*/
+    *x = small;
+/*
+     GET-FUNCTION-VALUE
+*/
+    i99999 = 1;
+    goto S300;
+S10:
+    fsmall = *fx;
+    *x = big;
+/*
+     GET-FUNCTION-VALUE
+*/
+    i99999 = 2;
+    goto S300;
+S20:
+    fbig = *fx;
+    qincr = fbig > fsmall;
+    if(!qincr) goto S50;
+    if(fsmall <= 0.0e0) goto S30;
+    *status = -1;
+    *qleft = *qhi = 1;
+    return;
+S30:
+    if(fbig >= 0.0e0) goto S40;
+    *status = -1;
+    *qleft = *qhi = 0;
+    return;
+S40:
+    goto S80;
+S50:
+    if(fsmall >= 0.0e0) goto S60;
+    *status = -1;
+    *qleft = 1;
+    *qhi = 0;
+    return;
+S60:
+    if(fbig <= 0.0e0) goto S70;
+    *status = -1;
+    *qleft = 0;
+    *qhi = 1;
+    return;
+S80:
+S70:
+    *x = xsave;
+    step = fifdmax1(absstp,relstp*fabs(*x));
+/*
+      YY = F(X) - Y
+     GET-FUNCTION-VALUE
+*/
+    i99999 = 3;
+    goto S300;
+S90:
+    yy = *fx;
+    if(!(yy == 0.0e0)) goto S100;
+    *status = 0;
+    qok = 1;
+    return;
+S100:
+    qup = (qincr && yy < 0.0e0) || (!qincr && yy > 0.0e0);
+/*
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+     HANDLE CASE IN WHICH WE MUST STEP HIGHER
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+*/
+    if(!qup) goto S170;
+    xlb = xsave;
+    xub = fifdmin1(xlb+step,big);
+    goto S120;
+S110:
+    if(qcond) goto S150;
+S120:
+/*
+      YY = F(XUB) - Y
+*/
+    *x = xub;
+/*
+     GET-FUNCTION-VALUE
+*/
+    i99999 = 4;
+    goto S300;
+S130:
+    yy = *fx;
+    qbdd = (qincr && yy >= 0.0e0) || (!qincr && yy <= 0.0e0);
+    qlim = xub >= big;
+    qcond = qbdd || qlim;
+    if(qcond) goto S140;
+    step = stpmul*step;
+    xlb = xub;
+    xub = fifdmin1(xlb+step,big);
+S140:
+    goto S110;
+S150:
+    if(!(qlim && !qbdd)) goto S160;
+    *status = -1;
+    *qleft = 0;
+    *qhi = !qincr;
+    *x = big;
+    return;
+S160:
+    goto S240;
+S170:
+/*
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+     HANDLE CASE IN WHICH WE MUST STEP LOWER
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+*/
+    xub = xsave;
+    xlb = fifdmax1(xub-step,small);
+    goto S190;
+S180:
+    if(qcond) goto S220;
+S190:
+/*
+      YY = F(XLB) - Y
+*/
+    *x = xlb;
+/*
+     GET-FUNCTION-VALUE
+*/
+    i99999 = 5;
+    goto S300;
+S200:
+    yy = *fx;
+    qbdd = (qincr && yy <= 0.0e0) || (!qincr && yy >= 0.0e0);
+    qlim = xlb <= small;
+    qcond = qbdd || qlim;
+    if(qcond) goto S210;
+    step = stpmul*step;
+    xub = xlb;
+    xlb = fifdmax1(xub-step,small);
+S210:
+    goto S180;
+S220:
+    if(!(qlim && !qbdd)) goto S230;
+    *status = -1;
+    *qleft = 1;
+    *qhi = qincr;
+    *x = small;
+    return;
+S240:
+S230:
+    dstzr(&xlb,&xub,&abstol,&reltol);
+/*
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+     IF WE REACH HERE, XLB AND XUB BOUND THE ZERO OF F.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+*/
+    *status = 0;
+    goto S260;
+S250:
+    if(!(*status == 1)) goto S290;
+S260:
+    dzror(status,x,fx,&xlo,&xhi,&qdum1,&qdum2);
+    if(!(*status == 1)) goto S280;
+/*
+     GET-FUNCTION-VALUE
+*/
+    i99999 = 6;
+    goto S300;
+S280:
+S270:
+    goto S250;
+S290:
+    *x = xlo;
+    *status = 0;
+    return;
+DSTINV:
+    small = *zsmall;
+    big = *zbig;
+    absstp = *zabsst;
+    relstp = *zrelst;
+    stpmul = *zstpmu;
+    abstol = *zabsto;
+    reltol = *zrelto;
+    return;
+S300:
+/*
+     TO GET-FUNCTION-VALUE
+*/
+    *status = 1;
+    return;
+S310:
+    switch((int)i99999){case 1: goto S10;case 2: goto S20;case 3: goto S90;case 
+      4: goto S130;case 5: goto S200;case 6: goto S270;default: break;}
+#undef qxmon
+}
+void dinvr(int *status,double *x,double *fx,
+          unsigned long *qleft,unsigned long *qhi)
+/*
+**********************************************************************
+     void dinvr(int *status,double *x,double *fx,
+           unsigned long *qleft,unsigned long *qhi)
+
+          Double precision
+          bounds the zero of the function and invokes zror
+                    Reverse Communication
+                              Function
+     Bounds the    function  and  invokes  ZROR   to perform the   zero
+     finding.  STINVR  must  have   been  called  before this   routine
+     in order to set its parameters.
+                              Arguments
+     STATUS <--> At the beginning of a zero finding problem, STATUS
+                 should be set to 0 and INVR invoked.  (The value
+                 of parameters other than X will be ignored on this cal
+                 When INVR needs the function evaluated, it will set
+                 STATUS to 1 and return.  The value of the function
+                 should be set in FX and INVR again called without
+                 changing any of its other parameters.
+                 When INVR has finished without error, it will return
+                 with STATUS 0.  In that case X is approximately a root
+                 of F(X).
+                 If INVR cannot bound the function, it returns status
+                 -1 and sets QLEFT and QHI.
+                         INTEGER STATUS
+     X <-- The value of X at which F(X) is to be evaluated.
+                         DOUBLE PRECISION X
+     FX --> The value of F(X) calculated when INVR returns with
+            STATUS = 1.
+                         DOUBLE PRECISION FX
+     QLEFT <-- Defined only if QMFINV returns .FALSE.  In that
+          case it is .TRUE. If the stepping search terminated
+          unsucessfully at SMALL.  If it is .FALSE. the search
+          terminated unsucessfully at BIG.
+                    QLEFT is LOGICAL
+     QHI <-- Defined only if QMFINV returns .FALSE.  In that
+          case it is .TRUE. if F(X) .GT. Y at the termination
+          of the search and .FALSE. if F(X) .LT. Y at the
+          termination of the search.
+                    QHI is LOGICAL
+**********************************************************************
+*/
+{
+    E0000(0,status,x,fx,qleft,qhi,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+}
+void dstinv(double *zsmall,double *zbig,double *zabsst,
+           double *zrelst,double *zstpmu,double *zabsto,
+           double *zrelto)
+/*
+**********************************************************************
+      void dstinv(double *zsmall,double *zbig,double *zabsst,
+            double *zrelst,double *zstpmu,double *zabsto,
+            double *zrelto)
+
+      Double Precision - SeT INverse finder - Reverse Communication
+                              Function
+     Concise Description - Given a monotone function F finds X
+     such that F(X) = Y.  Uses Reverse communication -- see invr.
+     This routine sets quantities needed by INVR.
+          More Precise Description of INVR -
+     F must be a monotone function, the results of QMFINV are
+     otherwise undefined.  QINCR must be .TRUE. if F is non-
+     decreasing and .FALSE. if F is non-increasing.
+     QMFINV will return .TRUE. if and only if F(SMALL) and
+     F(BIG) bracket Y, i. e.,
+          QINCR is .TRUE. and F(SMALL).LE.Y.LE.F(BIG) or
+          QINCR is .FALSE. and F(BIG).LE.Y.LE.F(SMALL)
+     if QMFINV returns .TRUE., then the X returned satisfies
+     the following condition.  let
+               TOL(X) = MAX(ABSTOL,RELTOL*ABS(X))
+     then if QINCR is .TRUE.,
+          F(X-TOL(X)) .LE. Y .LE. F(X+TOL(X))
+     and if QINCR is .FALSE.
+          F(X-TOL(X)) .GE. Y .GE. F(X+TOL(X))
+                              Arguments
+     SMALL --> The left endpoint of the interval to be
+          searched for a solution.
+                    SMALL is DOUBLE PRECISION
+     BIG --> The right endpoint of the interval to be
+          searched for a solution.
+                    BIG is DOUBLE PRECISION
+     ABSSTP, RELSTP --> The initial step size in the search
+          is MAX(ABSSTP,RELSTP*ABS(X)). See algorithm.
+                    ABSSTP is DOUBLE PRECISION
+                    RELSTP is DOUBLE PRECISION
+     STPMUL --> When a step doesn't bound the zero, the step
+                size is multiplied by STPMUL and another step
+                taken.  A popular value is 2.0
+                    DOUBLE PRECISION STPMUL
+     ABSTOL, RELTOL --> Two numbers that determine the accuracy
+          of the solution.  See function for a precise definition.
+                    ABSTOL is DOUBLE PRECISION
+                    RELTOL is DOUBLE PRECISION
+                              Method
+     Compares F(X) with Y for the input value of X then uses QINCR
+     to determine whether to step left or right to bound the
+     desired x.  the initial step size is
+          MAX(ABSSTP,RELSTP*ABS(S)) for the input value of X.
+     Iteratively steps right or left until it bounds X.
+     At each step which doesn't bound X, the step size is doubled.
+     The routine is careful never to step beyond SMALL or BIG.  If
+     it hasn't bounded X at SMALL or BIG, QMFINV returns .FALSE.
+     after setting QLEFT and QHI.
+     If X is successfully bounded then Algorithm R of the paper
+     'Two Efficient Algorithms with Guaranteed Convergence for
+     Finding a Zero of a Function' by J. C. P. Bus and
+     T. J. Dekker in ACM Transactions on Mathematical
+     Software, Volume 1, No. 4 page 330 (DEC. '75) is employed
+     to find the zero of the function F(X)-Y. This is routine
+     QRZERO.
+**********************************************************************
+*/
+{
+    E0000(1,NULL,NULL,NULL,NULL,NULL,zabsst,zabsto,zbig,zrelst,zrelto,zsmall,
+    zstpmu);
+}
+double dt1(double *p,double *q,double *df)
+/*
+**********************************************************************
+     double dt1(double *p,double *q,double *df)
+     Double precision Initalize Approximation to
+           INVerse of the cumulative T distribution
+                              Function
+     Returns  the  inverse   of  the T   distribution   function, i.e.,
+     the integral from 0 to INVT of the T density is P. This is an
+     initial approximation
+                              Arguments
+     P --> The p-value whose inverse from the T distribution is
+          desired.
+                    P is DOUBLE PRECISION
+     Q --> 1-P.
+                    Q is DOUBLE PRECISION
+     DF --> Degrees of freedom of the T distribution.
+                    DF is DOUBLE PRECISION
+**********************************************************************
+*/
+{
+static double coef[4][5] = {
+    {1.0e0,1.0e0,0.0e0,0.0e0,0.0e0},
+    {3.0e0,16.0e0,5.0e0,0.0e0,0.0e0},
+    {-15.0e0,17.0e0,19.0e0,3.0e0,0.0e0},
+    {-945.0e0,-1920.0e0,1482.0e0,776.0e0,79.0e0}
+};
+static double denom[4] = {
+    4.0e0,96.0e0,384.0e0,92160.0e0
+};
+static int ideg[4] = {
+    2,3,4,5
+};
+static double dt1,denpow,sum,term,x,xp,xx;
+static int i;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    x = fabs(dinvnr(p,q));
+    xx = x*x;
+    sum = x;
+    denpow = 1.0e0;
+    for(i=0; i<4; i++) {
+        term = devlpl(&coef[i][0],&ideg[i],&xx)*x;
+        denpow *= *df;
+        sum += (term/(denpow*denom[i]));
+    }
+    if(!(*p >= 0.5e0)) goto S20;
+    xp = sum;
+    goto S30;
+S20:
+    xp = -sum;
+S30:
+    dt1 = xp;
+    return dt1;
+}
+/* DEFINE DZROR */
+static void E0001(int IENTRY,int *status,double *x,double *fx,
+                 double *xlo,double *xhi,unsigned long *qleft,
+                 unsigned long *qhi,double *zabstl,double *zreltl,
+                 double *zxhi,double *zxlo)
+{
+#define ftol(zx) (0.5e0*fifdmax1(abstol,reltol*fabs((zx))))
+static double a,abstol,b,c,d,fa,fb,fc,fd,fda,fdb,m,mb,p,q,reltol,tol,w,xxhi,xxlo;
+static int ext,i99999;
+static unsigned long first,qrzero;
+    switch(IENTRY){case 0: goto DZROR; case 1: goto DSTZR;}
+DZROR:
+    if(*status > 0) goto S280;
+    *xlo = xxlo;
+    *xhi = xxhi;
+    b = *x = *xlo;
+/*
+     GET-FUNCTION-VALUE
+*/
+    i99999 = 1;
+    goto S270;
+S10:
+    fb = *fx;
+    *xlo = *xhi;
+    a = *x = *xlo;
+/*
+     GET-FUNCTION-VALUE
+*/
+    i99999 = 2;
+    goto S270;
+S20:
+/*
+     Check that F(ZXLO) < 0 < F(ZXHI)  or
+                F(ZXLO) > 0 > F(ZXHI)
+*/
+    if(!(fb < 0.0e0)) goto S40;
+    if(!(*fx < 0.0e0)) goto S30;
+    *status = -1;
+    *qleft = *fx < fb;
+    *qhi = 0;
+    return;
+S40:
+S30:
+    if(!(fb > 0.0e0)) goto S60;
+    if(!(*fx > 0.0e0)) goto S50;
+    *status = -1;
+    *qleft = *fx > fb;
+    *qhi = 1;
+    return;
+S60:
+S50:
+    fa = *fx;
+    first = 1;
+S70:
+    c = a;
+    fc = fa;
+    ext = 0;
+S80:
+    if(!(fabs(fc) < fabs(fb))) goto S100;
+    if(!(c != a)) goto S90;
+    d = a;
+    fd = fa;
+S90:
+    a = b;
+    fa = fb;
+    *xlo = c;
+    b = *xlo;
+    fb = fc;
+    c = a;
+    fc = fa;
+S100:
+    tol = ftol(*xlo);
+    m = (c+b)*.5e0;
+    mb = m-b;
+    if(!(fabs(mb) > tol)) goto S240;
+    if(!(ext > 3)) goto S110;
+    w = mb;
+    goto S190;
+S110:
+    tol = fifdsign(tol,mb);
+    p = (b-a)*fb;
+    if(!first) goto S120;
+    q = fa-fb;
+    first = 0;
+    goto S130;
+S120:
+    fdb = (fd-fb)/(d-b);
+    fda = (fd-fa)/(d-a);
+    p = fda*p;
+    q = fdb*fa-fda*fb;
+S130:
+    if(!(p < 0.0e0)) goto S140;
+    p = -p;
+    q = -q;
+S140:
+    if(ext == 3) p *= 2.0e0;
+    if(!(p*1.0e0 == 0.0e0 || p <= q*tol)) goto S150;
+    w = tol;
+    goto S180;
+S150:
+    if(!(p < mb*q)) goto S160;
+    w = p/q;
+    goto S170;
+S160:
+    w = mb;
+S190:
+S180:
+S170:
+    d = a;
+    fd = fa;
+    a = b;
+    fa = fb;
+    b += w;
+    *xlo = b;
+    *x = *xlo;
+/*
+     GET-FUNCTION-VALUE
+*/
+    i99999 = 3;
+    goto S270;
+S200:
+    fb = *fx;
+    if(!(fc*fb >= 0.0e0)) goto S210;
+    goto S70;
+S210:
+    if(!(w == mb)) goto S220;
+    ext = 0;
+    goto S230;
+S220:
+    ext += 1;
+S230:
+    goto S80;
+S240:
+    *xhi = c;
+    qrzero = (fc >= 0.0e0 && fb <= 0.0e0) || (fc < 0.0e0 && fb >= 0.0e0);
+    if(!qrzero) goto S250;
+    *status = 0;
+    goto S260;
+S250:
+    *status = -1;
+S260:
+    return;
+DSTZR:
+    xxlo = *zxlo;
+    xxhi = *zxhi;
+    abstol = *zabstl;
+    reltol = *zreltl;
+    return;
+S270:
+/*
+     TO GET-FUNCTION-VALUE
+*/
+    *status = 1;
+    return;
+S280:
+    switch((int)i99999){case 1: goto S10;case 2: goto S20;case 3: goto S200;
+      default: break;}
+#undef ftol
+}
+void dzror(int *status,double *x,double *fx,double *xlo,
+          double *xhi,unsigned long *qleft,unsigned long *qhi)
+/*
+**********************************************************************
+     void dzror(int *status,double *x,double *fx,double *xlo,
+           double *xhi,unsigned long *qleft,unsigned long *qhi)
+
+     Double precision ZeRo of a function -- Reverse Communication
+                              Function
+     Performs the zero finding.  STZROR must have been called before
+     this routine in order to set its parameters.
+                              Arguments
+     STATUS <--> At the beginning of a zero finding problem, STATUS
+                 should be set to 0 and ZROR invoked.  (The value
+                 of other parameters will be ignored on this call.)
+                 When ZROR needs the function evaluated, it will set
+                 STATUS to 1 and return.  The value of the function
+                 should be set in FX and ZROR again called without
+                 changing any of its other parameters.
+                 When ZROR has finished without error, it will return
+                 with STATUS 0.  In that case (XLO,XHI) bound the answe
+                 If ZROR finds an error (which implies that F(XLO)-Y an
+                 F(XHI)-Y have the same sign, it returns STATUS -1.  In
+                 this case, XLO and XHI are undefined.
+                         INTEGER STATUS
+     X <-- The value of X at which F(X) is to be evaluated.
+                         DOUBLE PRECISION X
+     FX --> The value of F(X) calculated when ZROR returns with
+            STATUS = 1.
+                         DOUBLE PRECISION FX
+     XLO <-- When ZROR returns with STATUS = 0, XLO bounds the
+             inverval in X containing the solution below.
+                         DOUBLE PRECISION XLO
+     XHI <-- When ZROR returns with STATUS = 0, XHI bounds the
+             inverval in X containing the solution above.
+                         DOUBLE PRECISION XHI
+     QLEFT <-- .TRUE. if the stepping search terminated unsucessfully
+                at XLO.  If it is .FALSE. the search terminated
+                unsucessfully at XHI.
+                    QLEFT is LOGICAL
+     QHI <-- .TRUE. if F(X) .GT. Y at the termination of the
+              search and .FALSE. if F(X) .LT. Y at the
+              termination of the search.
+                    QHI is LOGICAL
+**********************************************************************
+*/
+{
+    E0001(0,status,x,fx,xlo,xhi,qleft,qhi,NULL,NULL,NULL,NULL);
+}
+void dstzr(double *zxlo,double *zxhi,double *zabstl,double *zreltl)
+/*
+**********************************************************************
+     void dstzr(double *zxlo,double *zxhi,double *zabstl,double *zreltl)
+     Double precision SeT ZeRo finder - Reverse communication version
+                              Function
+     Sets quantities needed by ZROR.  The function of ZROR
+     and the quantities set is given here.
+     Concise Description - Given a function F
+     find XLO such that F(XLO) = 0.
+          More Precise Description -
+     Input condition. F is a double precision function of a single
+     double precision argument and XLO and XHI are such that
+          F(XLO)*F(XHI)  .LE.  0.0
+     If the input condition is met, QRZERO returns .TRUE.
+     and output values of XLO and XHI satisfy the following
+          F(XLO)*F(XHI)  .LE. 0.
+          ABS(F(XLO)  .LE. ABS(F(XHI)
+          ABS(XLO-XHI)  .LE. TOL(X)
+     where
+          TOL(X) = MAX(ABSTOL,RELTOL*ABS(X))
+     If this algorithm does not find XLO and XHI satisfying
+     these conditions then QRZERO returns .FALSE.  This
+     implies that the input condition was not met.
+                              Arguments
+     XLO --> The left endpoint of the interval to be
+           searched for a solution.
+                    XLO is DOUBLE PRECISION
+     XHI --> The right endpoint of the interval to be
+           for a solution.
+                    XHI is DOUBLE PRECISION
+     ABSTOL, RELTOL --> Two numbers that determine the accuracy
+                      of the solution.  See function for a
+                      precise definition.
+                    ABSTOL is DOUBLE PRECISION
+                    RELTOL is DOUBLE PRECISION
+                              Method
+     Algorithm R of the paper 'Two Efficient Algorithms with
+     Guaranteed Convergence for Finding a Zero of a Function'
+     by J. C. P. Bus and T. J. Dekker in ACM Transactions on
+     Mathematical Software, Volume 1, no. 4 page 330
+     (Dec. '75) is employed to find the zero of F(X)-Y.
+**********************************************************************
+*/
+{
+    E0001(1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,zabstl,zreltl,zxhi,zxlo);
+}
+double erf1(double *x)
+/*
+-----------------------------------------------------------------------
+             EVALUATION OF THE REAL ERROR FUNCTION
+-----------------------------------------------------------------------
+*/
+{
+static double c = .564189583547756e0;
+static double a[5] = {
+    .771058495001320e-04,-.133733772997339e-02,.323076579225834e-01,
+    .479137145607681e-01,.128379167095513e+00
+};
+static double b[3] = {
+    .301048631703895e-02,.538971687740286e-01,.375795757275549e+00
+};
+static double p[8] = {
+    -1.36864857382717e-07,5.64195517478974e-01,7.21175825088309e+00,
+    4.31622272220567e+01,1.52989285046940e+02,3.39320816734344e+02,
+    4.51918953711873e+02,3.00459261020162e+02
+};
+static double q[8] = {
+    1.00000000000000e+00,1.27827273196294e+01,7.70001529352295e+01,
+    2.77585444743988e+02,6.38980264465631e+02,9.31354094850610e+02,
+    7.90950925327898e+02,3.00459260956983e+02
+};
+static double r[5] = {
+    2.10144126479064e+00,2.62370141675169e+01,2.13688200555087e+01,
+    4.65807828718470e+00,2.82094791773523e-01
+};
+static double s[4] = {
+    9.41537750555460e+01,1.87114811799590e+02,9.90191814623914e+01,
+    1.80124575948747e+01
+};
+static double erf1,ax,bot,t,top,x2;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    ax = fabs(*x);
+    if(ax > 0.5e0) goto S10;
+    t = *x**x;
+    top = (((a[0]*t+a[1])*t+a[2])*t+a[3])*t+a[4]+1.0e0;
+    bot = ((b[0]*t+b[1])*t+b[2])*t+1.0e0;
+    erf1 = *x*(top/bot);
+    return erf1;
+S10:
+    if(ax > 4.0e0) goto S20;
+    top = ((((((p[0]*ax+p[1])*ax+p[2])*ax+p[3])*ax+p[4])*ax+p[5])*ax+p[6])*ax+p[
+      7];
+    bot = ((((((q[0]*ax+q[1])*ax+q[2])*ax+q[3])*ax+q[4])*ax+q[5])*ax+q[6])*ax+q[
+      7];
+    erf1 = 0.5e0+(0.5e0-exp(-(*x**x))*top/bot);
+    if(*x < 0.0e0) erf1 = -erf1;
+    return erf1;
+S20:
+    if(ax >= 5.8e0) goto S30;
+    x2 = *x**x;
+    t = 1.0e0/x2;
+    top = (((r[0]*t+r[1])*t+r[2])*t+r[3])*t+r[4];
+    bot = (((s[0]*t+s[1])*t+s[2])*t+s[3])*t+1.0e0;
+    erf1 = (c-top/(x2*bot))/ax;
+    erf1 = 0.5e0+(0.5e0-exp(-x2)*erf1);
+    if(*x < 0.0e0) erf1 = -erf1;
+    return erf1;
+S30:
+    erf1 = fifdsign(1.0e0,*x);
+    return erf1;
+}
+double erfc1(int *ind,double *x)
+/*
+-----------------------------------------------------------------------
+         EVALUATION OF THE COMPLEMENTARY ERROR FUNCTION
+          ERFC1(IND,X) = ERFC(X)            IF IND = 0
+          ERFC1(IND,X) = EXP(X*X)*ERFC(X)   OTHERWISE
+-----------------------------------------------------------------------
+*/
+{
+static double c = .564189583547756e0;
+static double a[5] = {
+    .771058495001320e-04,-.133733772997339e-02,.323076579225834e-01,
+    .479137145607681e-01,.128379167095513e+00
+};
+static double b[3] = {
+    .301048631703895e-02,.538971687740286e-01,.375795757275549e+00
+};
+static double p[8] = {
+    -1.36864857382717e-07,5.64195517478974e-01,7.21175825088309e+00,
+    4.31622272220567e+01,1.52989285046940e+02,3.39320816734344e+02,
+    4.51918953711873e+02,3.00459261020162e+02
+};
+static double q[8] = {
+    1.00000000000000e+00,1.27827273196294e+01,7.70001529352295e+01,
+    2.77585444743988e+02,6.38980264465631e+02,9.31354094850610e+02,
+    7.90950925327898e+02,3.00459260956983e+02
+};
+static double r[5] = {
+    2.10144126479064e+00,2.62370141675169e+01,2.13688200555087e+01,
+    4.65807828718470e+00,2.82094791773523e-01
+};
+static double s[4] = {
+    9.41537750555460e+01,1.87114811799590e+02,9.90191814623914e+01,
+    1.80124575948747e+01
+};
+static int K1 = 1;
+static double erfc1,ax,bot,e,t,top,w;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+                     ABS(X) .LE. 0.5
+*/
+    ax = fabs(*x);
+    if(ax > 0.5e0) goto S10;
+    t = *x**x;
+    top = (((a[0]*t+a[1])*t+a[2])*t+a[3])*t+a[4]+1.0e0;
+    bot = ((b[0]*t+b[1])*t+b[2])*t+1.0e0;
+    erfc1 = 0.5e0+(0.5e0-*x*(top/bot));
+    if(*ind != 0) erfc1 = exp(t)*erfc1;
+    return erfc1;
+S10:
+/*
+                  0.5 .LT. ABS(X) .LE. 4
+*/
+    if(ax > 4.0e0) goto S20;
+    top = ((((((p[0]*ax+p[1])*ax+p[2])*ax+p[3])*ax+p[4])*ax+p[5])*ax+p[6])*ax+p[
+      7];
+    bot = ((((((q[0]*ax+q[1])*ax+q[2])*ax+q[3])*ax+q[4])*ax+q[5])*ax+q[6])*ax+q[
+      7];
+    erfc1 = top/bot;
+    goto S40;
+S20:
+/*
+                      ABS(X) .GT. 4
+*/
+    if(*x <= -5.6e0) goto S60;
+    if(*ind != 0) goto S30;
+    if(*x > 100.0e0) goto S70;
+    if(*x**x > -exparg(&K1)) goto S70;
+S30:
+    t = pow(1.0e0/ *x,2.0);
+    top = (((r[0]*t+r[1])*t+r[2])*t+r[3])*t+r[4];
+    bot = (((s[0]*t+s[1])*t+s[2])*t+s[3])*t+1.0e0;
+    erfc1 = (c-t*top/bot)/ax;
+S40:
+/*
+                      FINAL ASSEMBLY
+*/
+    if(*ind == 0) goto S50;
+    if(*x < 0.0e0) erfc1 = 2.0e0*exp(*x**x)-erfc1;
+    return erfc1;
+S50:
+    w = *x**x;
+    t = w;
+    e = w-t;
+    erfc1 = (0.5e0+(0.5e0-e))*exp(-t)*erfc1;
+    if(*x < 0.0e0) erfc1 = 2.0e0-erfc1;
+    return erfc1;
+S60:
+/*
+             LIMIT VALUE FOR LARGE NEGATIVE X
+*/
+    erfc1 = 2.0e0;
+    if(*ind != 0) erfc1 = 2.0e0*exp(*x**x);
+    return erfc1;
+S70:
+/*
+             LIMIT VALUE FOR LARGE POSITIVE X
+                       WHEN IND = 0
+*/
+    erfc1 = 0.0e0;
+    return erfc1;
+}
+double esum(int *mu,double *x)
+/*
+-----------------------------------------------------------------------
+                    EVALUATION OF EXP(MU + X)
+-----------------------------------------------------------------------
+*/
+{
+static double esum,w;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(*x > 0.0e0) goto S10;
+    if(*mu < 0) goto S20;
+    w = (double)*mu+*x;
+    if(w > 0.0e0) goto S20;
+    esum = exp(w);
+    return esum;
+S10:
+    if(*mu > 0) goto S20;
+    w = (double)*mu+*x;
+    if(w < 0.0e0) goto S20;
+    esum = exp(w);
+    return esum;
+S20:
+    w = *mu;
+    esum = exp(w)*exp(*x);
+    return esum;
+}
+double exparg(int *l)
+/*
+--------------------------------------------------------------------
+     IF L = 0 THEN  EXPARG(L) = THE LARGEST POSITIVE W FOR WHICH
+     EXP(W) CAN BE COMPUTED.
+     IF L IS NONZERO THEN  EXPARG(L) = THE LARGEST NEGATIVE W FOR
+     WHICH THE COMPUTED VALUE OF EXP(W) IS NONZERO.
+     NOTE... ONLY AN APPROXIMATE VALUE FOR EXPARG(L) IS NEEDED.
+--------------------------------------------------------------------
+*/
+{
+static int K1 = 4;
+static int K2 = 9;
+static int K3 = 10;
+static double exparg,lnb;
+static int b,m;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    b = ipmpar(&K1);
+    if(b != 2) goto S10;
+    lnb = .69314718055995e0;
+    goto S40;
+S10:
+    if(b != 8) goto S20;
+    lnb = 2.0794415416798e0;
+    goto S40;
+S20:
+    if(b != 16) goto S30;
+    lnb = 2.7725887222398e0;
+    goto S40;
+S30:
+    lnb = log((double)b);
+S40:
+    if(*l == 0) goto S50;
+    m = ipmpar(&K2)-1;
+    exparg = 0.99999e0*((double)m*lnb);
+    return exparg;
+S50:
+    m = ipmpar(&K3);
+    exparg = 0.99999e0*((double)m*lnb);
+    return exparg;
+}
+double fpser(double *a,double *b,double *x,double *eps)
+/*
+-----------------------------------------------------------------------
+                 EVALUATION OF I (A,B)
+                                X
+          FOR B .LT. MIN(EPS,EPS*A) AND X .LE. 0.5.
+-----------------------------------------------------------------------
+                  SET  FPSER = X**A
+*/
+{
+static int K1 = 1;
+static double fpser,an,c,s,t,tol;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    fpser = 1.0e0;
+    if(*a <= 1.e-3**eps) goto S10;
+    fpser = 0.0e0;
+    t = *a*log(*x);
+    if(t < exparg(&K1)) return fpser;
+    fpser = exp(t);
+S10:
+/*
+                NOTE THAT 1/B(A,B) = B
+*/
+    fpser = *b/ *a*fpser;
+    tol = *eps/ *a;
+    an = *a+1.0e0;
+    t = *x;
+    s = t/an;
+S20:
+    an += 1.0e0;
+    t = *x*t;
+    c = t/an;
+    s += c;
+    if(fabs(c) > tol) goto S20;
+    fpser *= (1.0e0+*a*s);
+    return fpser;
+}
+double gam1(double *a)
+/*
+     ------------------------------------------------------------------
+     COMPUTATION OF 1/GAMMA(A+1) - 1  FOR -0.5 .LE. A .LE. 1.5
+     ------------------------------------------------------------------
+*/
+{
+static double s1 = .273076135303957e+00;
+static double s2 = .559398236957378e-01;
+static double p[7] = {
+    .577215664901533e+00,-.409078193005776e+00,-.230975380857675e+00,
+    .597275330452234e-01,.766968181649490e-02,-.514889771323592e-02,
+    .589597428611429e-03
+};
+static double q[5] = {
+    .100000000000000e+01,.427569613095214e+00,.158451672430138e+00,
+    .261132021441447e-01,.423244297896961e-02
+};
+static double r[9] = {
+    -.422784335098468e+00,-.771330383816272e+00,-.244757765222226e+00,
+    .118378989872749e+00,.930357293360349e-03,-.118290993445146e-01,
+    .223047661158249e-02,.266505979058923e-03,-.132674909766242e-03
+};
+static double gam1,bot,d,t,top,w,T1;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    t = *a;
+    d = *a-0.5e0;
+    if(d > 0.0e0) t = d-0.5e0;
+    T1 = t;
+    if(T1 < 0) goto S40;
+    else if(T1 == 0) goto S10;
+    else  goto S20;
+S10:
+    gam1 = 0.0e0;
+    return gam1;
+S20:
+    top = (((((p[6]*t+p[5])*t+p[4])*t+p[3])*t+p[2])*t+p[1])*t+p[0];
+    bot = (((q[4]*t+q[3])*t+q[2])*t+q[1])*t+1.0e0;
+    w = top/bot;
+    if(d > 0.0e0) goto S30;
+    gam1 = *a*w;
+    return gam1;
+S30:
+    gam1 = t/ *a*(w-0.5e0-0.5e0);
+    return gam1;
+S40:
+    top = (((((((r[8]*t+r[7])*t+r[6])*t+r[5])*t+r[4])*t+r[3])*t+r[2])*t+r[1])*t+
+      r[0];
+    bot = (s2*t+s1)*t+1.0e0;
+    w = top/bot;
+    if(d > 0.0e0) goto S50;
+    gam1 = *a*(w+0.5e0+0.5e0);
+    return gam1;
+S50:
+    gam1 = t*w/ *a;
+    return gam1;
+}
+void gaminv(double *a,double *x,double *x0,double *p,double *q,
+           int *ierr)
+/*
+ ----------------------------------------------------------------------
+            INVERSE INCOMPLETE GAMMA RATIO FUNCTION
+     GIVEN POSITIVE A, AND NONEGATIVE P AND Q WHERE P + Q = 1.
+     THEN X IS COMPUTED WHERE P(A,X) = P AND Q(A,X) = Q. SCHRODER
+     ITERATION IS EMPLOYED. THE ROUTINE ATTEMPTS TO COMPUTE X
+     TO 10 SIGNIFICANT DIGITS IF THIS IS POSSIBLE FOR THE
+     PARTICULAR COMPUTER ARITHMETIC BEING USED.
+                      ------------
+     X IS A VARIABLE. IF P = 0 THEN X IS ASSIGNED THE VALUE 0,
+     AND IF Q = 0 THEN X IS SET TO THE LARGEST FLOATING POINT
+     NUMBER AVAILABLE. OTHERWISE, GAMINV ATTEMPTS TO OBTAIN
+     A SOLUTION FOR P(A,X) = P AND Q(A,X) = Q. IF THE ROUTINE
+     IS SUCCESSFUL THEN THE SOLUTION IS STORED IN X.
+     X0 IS AN OPTIONAL INITIAL APPROXIMATION FOR X. IF THE USER
+     DOES NOT WISH TO SUPPLY AN INITIAL APPROXIMATION, THEN SET
+     X0 .LE. 0.
+     IERR IS A VARIABLE THAT REPORTS THE STATUS OF THE RESULTS.
+     WHEN THE ROUTINE TERMINATES, IERR HAS ONE OF THE FOLLOWING
+     VALUES ...
+       IERR =  0    THE SOLUTION WAS OBTAINED. ITERATION WAS
+                    NOT USED.
+       IERR.GT.0    THE SOLUTION WAS OBTAINED. IERR ITERATIONS
+                    WERE PERFORMED.
+       IERR = -2    (INPUT ERROR) A .LE. 0
+       IERR = -3    NO SOLUTION WAS OBTAINED. THE RATIO Q/A
+                    IS TOO LARGE.
+       IERR = -4    (INPUT ERROR) P + Q .NE. 1
+       IERR = -6    20 ITERATIONS WERE PERFORMED. THE MOST
+                    RECENT VALUE OBTAINED FOR X IS GIVEN.
+                    THIS CANNOT OCCUR IF X0 .LE. 0.
+       IERR = -7    ITERATION FAILED. NO VALUE IS GIVEN FOR X.
+                    THIS MAY OCCUR WHEN X IS APPROXIMATELY 0.
+       IERR = -8    A VALUE FOR X HAS BEEN OBTAINED, BUT THE
+                    ROUTINE IS NOT CERTAIN OF ITS ACCURACY.
+                    ITERATION CANNOT BE PERFORMED IN THIS
+                    CASE. IF X0 .LE. 0, THIS CAN OCCUR ONLY
+                    WHEN P OR Q IS APPROXIMATELY 0. IF X0 IS
+                    POSITIVE THEN THIS CAN OCCUR WHEN A IS
+                    EXCEEDINGLY CLOSE TO X AND A IS EXTREMELY
+                    LARGE (SAY A .GE. 1.E20).
+ ----------------------------------------------------------------------
+     WRITTEN BY ALFRED H. MORRIS, JR.
+        NAVAL SURFACE WEAPONS CENTER
+        DAHLGREN, VIRGINIA
+     -------------------
+*/
+{
+static double a0 = 3.31125922108741e0;
+static double a1 = 11.6616720288968e0;
+static double a2 = 4.28342155967104e0;
+static double a3 = .213623493715853e0;
+static double b1 = 6.61053765625462e0;
+static double b2 = 6.40691597760039e0;
+static double b3 = 1.27364489782223e0;
+static double b4 = .036117081018842e0;
+static double c = .577215664901533e0;
+static double ln10 = 2.302585e0;
+static double tol = 1.e-5;
+static double amin[2] = {
+    500.0e0,100.0e0
+};
+static double bmin[2] = {
+    1.e-28,1.e-13
+};
+static double dmin[2] = {
+    1.e-06,1.e-04
+};
+static double emin[2] = {
+    2.e-03,6.e-03
+};
+static double eps0[2] = {
+    1.e-10,1.e-08
+};
+static int K1 = 1;
+static int K2 = 2;
+static int K3 = 3;
+static int K8 = 0;
+static double am1,amax,ap1,ap2,ap3,apn,b,c1,c2,c3,c4,c5,d,e,e2,eps,g,h,pn,qg,qn,
+    r,rta,s,s2,sum,t,u,w,xmax,xmin,xn,y,z;
+static int iop;
+static double T4,T5,T6,T7,T9;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     ****** E, XMIN, AND XMAX ARE MACHINE DEPENDENT CONSTANTS.
+            E IS THE SMALLEST NUMBER FOR WHICH 1.0 + E .GT. 1.0.
+            XMIN IS THE SMALLEST POSITIVE NUMBER AND XMAX IS THE
+            LARGEST POSITIVE NUMBER.
+*/
+    e = spmpar(&K1);
+    xmin = spmpar(&K2);
+    xmax = spmpar(&K3);
+    *x = 0.0e0;
+    if(*a <= 0.0e0) goto S300;
+    t = *p+*q-1.e0;
+    if(fabs(t) > e) goto S320;
+    *ierr = 0;
+    if(*p == 0.0e0) return;
+    if(*q == 0.0e0) goto S270;
+    if(*a == 1.0e0) goto S280;
+    e2 = 2.0e0*e;
+    amax = 0.4e-10/(e*e);
+    iop = 1;
+    if(e > 1.e-10) iop = 2;
+    eps = eps0[iop-1];
+    xn = *x0;
+    if(*x0 > 0.0e0) goto S160;
+/*
+        SELECTION OF THE INITIAL APPROXIMATION XN OF X
+                       WHEN A .LT. 1
+*/
+    if(*a > 1.0e0) goto S80;
+    T4 = *a+1.0e0;
+    g = Xgamm(&T4);
+    qg = *q*g;
+    if(qg == 0.0e0) goto S360;
+    b = qg/ *a;
+    if(qg > 0.6e0**a) goto S40;
+    if(*a >= 0.30e0 || b < 0.35e0) goto S10;
+    t = exp(-(b+c));
+    u = t*exp(t);
+    xn = t*exp(u);
+    goto S160;
+S10:
+    if(b >= 0.45e0) goto S40;
+    if(b == 0.0e0) goto S360;
+    y = -log(b);
+    s = 0.5e0+(0.5e0-*a);
+    z = log(y);
+    t = y-s*z;
+    if(b < 0.15e0) goto S20;
+    xn = y-s*log(t)-log(1.0e0+s/(t+1.0e0));
+    goto S220;
+S20:
+    if(b <= 0.01e0) goto S30;
+    u = ((t+2.0e0*(3.0e0-*a))*t+(2.0e0-*a)*(3.0e0-*a))/((t+(5.0e0-*a))*t+2.0e0);
+    xn = y-s*log(t)-log(u);
+    goto S220;
+S30:
+    c1 = -(s*z);
+    c2 = -(s*(1.0e0+c1));
+    c3 = s*((0.5e0*c1+(2.0e0-*a))*c1+(2.5e0-1.5e0**a));
+    c4 = -(s*(((c1/3.0e0+(2.5e0-1.5e0**a))*c1+((*a-6.0e0)**a+7.0e0))*c1+(
+      (11.0e0**a-46.0)**a+47.0e0)/6.0e0));
+    c5 = -(s*((((-(c1/4.0e0)+(11.0e0**a-17.0e0)/6.0e0)*c1+((-(3.0e0**a)+13.0e0)*
+      *a-13.0e0))*c1+0.5e0*(((2.0e0**a-25.0e0)**a+72.0e0)**a-61.0e0))*c1+((
+      (25.0e0**a-195.0e0)**a+477.0e0)**a-379.0e0)/12.0e0));
+    xn = (((c5/y+c4)/y+c3)/y+c2)/y+c1+y;
+    if(*a > 1.0e0) goto S220;
+    if(b > bmin[iop-1]) goto S220;
+    *x = xn;
+    return;
+S40:
+    if(b**q > 1.e-8) goto S50;
+    xn = exp(-(*q/ *a+c));
+    goto S70;
+S50:
+    if(*p <= 0.9e0) goto S60;
+    T5 = -*q;
+    xn = exp((alnrel(&T5)+gamln1(a))/ *a);
+    goto S70;
+S60:
+    xn = exp(log(*p*g)/ *a);
+S70:
+    if(xn == 0.0e0) goto S310;
+    t = 0.5e0+(0.5e0-xn/(*a+1.0e0));
+    xn /= t;
+    goto S160;
+S80:
+/*
+        SELECTION OF THE INITIAL APPROXIMATION XN OF X
+                       WHEN A .GT. 1
+*/
+    if(*q <= 0.5e0) goto S90;
+    w = log(*p);
+    goto S100;
+S90:
+    w = log(*q);
+S100:
+    t = sqrt(-(2.0e0*w));
+    s = t-(((a3*t+a2)*t+a1)*t+a0)/((((b4*t+b3)*t+b2)*t+b1)*t+1.0e0);
+    if(*q > 0.5e0) s = -s;
+    rta = sqrt(*a);
+    s2 = s*s;
+    xn = *a+s*rta+(s2-1.0e0)/3.0e0+s*(s2-7.0e0)/(36.0e0*rta)-((3.0e0*s2+7.0e0)*
+      s2-16.0e0)/(810.0e0**a)+s*((9.0e0*s2+256.0e0)*s2-433.0e0)/(38880.0e0**a*
+      rta);
+    xn = fifdmax1(xn,0.0e0);
+    if(*a < amin[iop-1]) goto S110;
+    *x = xn;
+    d = 0.5e0+(0.5e0-*x/ *a);
+    if(fabs(d) <= dmin[iop-1]) return;
+S110:
+    if(*p <= 0.5e0) goto S130;
+    if(xn < 3.0e0**a) goto S220;
+    y = -(w+gamln(a));
+    d = fifdmax1(2.0e0,*a*(*a-1.0e0));
+    if(y < ln10*d) goto S120;
+    s = 1.0e0-*a;
+    z = log(y);
+    goto S30;
+S120:
+    t = *a-1.0e0;
+    T6 = -(t/(xn+1.0e0));
+    xn = y+t*log(xn)-alnrel(&T6);
+    T7 = -(t/(xn+1.0e0));
+    xn = y+t*log(xn)-alnrel(&T7);
+    goto S220;
+S130:
+    ap1 = *a+1.0e0;
+    if(xn > 0.70e0*ap1) goto S170;
+    w += gamln(&ap1);
+    if(xn > 0.15e0*ap1) goto S140;
+    ap2 = *a+2.0e0;
+    ap3 = *a+3.0e0;
+    *x = exp((w+*x)/ *a);
+    *x = exp((w+*x-log(1.0e0+*x/ap1*(1.0e0+*x/ap2)))/ *a);
+    *x = exp((w+*x-log(1.0e0+*x/ap1*(1.0e0+*x/ap2)))/ *a);
+    *x = exp((w+*x-log(1.0e0+*x/ap1*(1.0e0+*x/ap2*(1.0e0+*x/ap3))))/ *a);
+    xn = *x;
+    if(xn > 1.e-2*ap1) goto S140;
+    if(xn <= emin[iop-1]*ap1) return;
+    goto S170;
+S140:
+    apn = ap1;
+    t = xn/apn;
+    sum = 1.0e0+t;
+S150:
+    apn += 1.0e0;
+    t *= (xn/apn);
+    sum += t;
+    if(t > 1.e-4) goto S150;
+    t = w-log(sum);
+    xn = exp((xn+t)/ *a);
+    xn *= (1.0e0-(*a*log(xn)-xn-t)/(*a-xn));
+    goto S170;
+S160:
+/*
+                 SCHRODER ITERATION USING P
+*/
+    if(*p > 0.5e0) goto S220;
+S170:
+    if(*p <= 1.e10*xmin) goto S350;
+    am1 = *a-0.5e0-0.5e0;
+S180:
+    if(*a <= amax) goto S190;
+    d = 0.5e0+(0.5e0-xn/ *a);
+    if(fabs(d) <= e2) goto S350;
+S190:
+    if(*ierr >= 20) goto S330;
+    *ierr += 1;
+    gratio(a,&xn,&pn,&qn,&K8);
+    if(pn == 0.0e0 || qn == 0.0e0) goto S350;
+    r = rcomp(a,&xn);
+    if(r == 0.0e0) goto S350;
+    t = (pn-*p)/r;
+    w = 0.5e0*(am1-xn);
+    if(fabs(t) <= 0.1e0 && fabs(w*t) <= 0.1e0) goto S200;
+    *x = xn*(1.0e0-t);
+    if(*x <= 0.0e0) goto S340;
+    d = fabs(t);
+    goto S210;
+S200:
+    h = t*(1.0e0+w*t);
+    *x = xn*(1.0e0-h);
+    if(*x <= 0.0e0) goto S340;
+    if(fabs(w) >= 1.0e0 && fabs(w)*t*t <= eps) return;
+    d = fabs(h);
+S210:
+    xn = *x;
+    if(d > tol) goto S180;
+    if(d <= eps) return;
+    if(fabs(*p-pn) <= tol**p) return;
+    goto S180;
+S220:
+/*
+                 SCHRODER ITERATION USING Q
+*/
+    if(*q <= 1.e10*xmin) goto S350;
+    am1 = *a-0.5e0-0.5e0;
+S230:
+    if(*a <= amax) goto S240;
+    d = 0.5e0+(0.5e0-xn/ *a);
+    if(fabs(d) <= e2) goto S350;
+S240:
+    if(*ierr >= 20) goto S330;
+    *ierr += 1;
+    gratio(a,&xn,&pn,&qn,&K8);
+    if(pn == 0.0e0 || qn == 0.0e0) goto S350;
+    r = rcomp(a,&xn);
+    if(r == 0.0e0) goto S350;
+    t = (*q-qn)/r;
+    w = 0.5e0*(am1-xn);
+    if(fabs(t) <= 0.1e0 && fabs(w*t) <= 0.1e0) goto S250;
+    *x = xn*(1.0e0-t);
+    if(*x <= 0.0e0) goto S340;
+    d = fabs(t);
+    goto S260;
+S250:
+    h = t*(1.0e0+w*t);
+    *x = xn*(1.0e0-h);
+    if(*x <= 0.0e0) goto S340;
+    if(fabs(w) >= 1.0e0 && fabs(w)*t*t <= eps) return;
+    d = fabs(h);
+S260:
+    xn = *x;
+    if(d > tol) goto S230;
+    if(d <= eps) return;
+    if(fabs(*q-qn) <= tol**q) return;
+    goto S230;
+S270:
+/*
+                       SPECIAL CASES
+*/
+    *x = xmax;
+    return;
+S280:
+    if(*q < 0.9e0) goto S290;
+    T9 = -*p;
+    *x = -alnrel(&T9);
+    return;
+S290:
+    *x = -log(*q);
+    return;
+S300:
+/*
+                       ERROR RETURN
+*/
+    *ierr = -2;
+    return;
+S310:
+    *ierr = -3;
+    return;
+S320:
+    *ierr = -4;
+    return;
+S330:
+    *ierr = -6;
+    return;
+S340:
+    *ierr = -7;
+    return;
+S350:
+    *x = xn;
+    *ierr = -8;
+    return;
+S360:
+    *x = xmax;
+    *ierr = -8;
+    return;
+}
+double gamln(double *a)
+/*
+-----------------------------------------------------------------------
+            EVALUATION OF LN(GAMMA(A)) FOR POSITIVE A
+-----------------------------------------------------------------------
+     WRITTEN BY ALFRED H. MORRIS
+          NAVAL SURFACE WARFARE CENTER
+          DAHLGREN, VIRGINIA
+--------------------------
+     D = 0.5*(LN(2*PI) - 1)
+--------------------------
+*/
+{
+static double c0 = .833333333333333e-01;
+static double c1 = -.277777777760991e-02;
+static double c2 = .793650666825390e-03;
+static double c3 = -.595202931351870e-03;
+static double c4 = .837308034031215e-03;
+static double c5 = -.165322962780713e-02;
+static double d = .418938533204673e0;
+static double gamln,t,w;
+static int i,n;
+static double T1;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(*a > 0.8e0) goto S10;
+    gamln = gamln1(a)-log(*a);
+    return gamln;
+S10:
+    if(*a > 2.25e0) goto S20;
+    t = *a-0.5e0-0.5e0;
+    gamln = gamln1(&t);
+    return gamln;
+S20:
+    if(*a >= 10.0e0) goto S40;
+    n = (long)(*a - 1.25e0);
+    t = *a;
+    w = 1.0e0;
+    for(i=1; i<=n; i++) {
+        t -= 1.0e0;
+        w = t*w;
+    }
+    T1 = t-1.0e0;
+    gamln = gamln1(&T1)+log(w);
+    return gamln;
+S40:
+    t = pow(1.0e0/ *a,2.0);
+    w = (((((c5*t+c4)*t+c3)*t+c2)*t+c1)*t+c0)/ *a;
+    gamln = d+w+(*a-0.5e0)*(log(*a)-1.0e0);
+    return gamln;
+}
+double gamln1(double *a)
+/*
+-----------------------------------------------------------------------
+     EVALUATION OF LN(GAMMA(1 + A)) FOR -0.2 .LE. A .LE. 1.25
+-----------------------------------------------------------------------
+*/
+{
+static double p0 = .577215664901533e+00;
+static double p1 = .844203922187225e+00;
+static double p2 = -.168860593646662e+00;
+static double p3 = -.780427615533591e+00;
+static double p4 = -.402055799310489e+00;
+static double p5 = -.673562214325671e-01;
+static double p6 = -.271935708322958e-02;
+static double q1 = .288743195473681e+01;
+static double q2 = .312755088914843e+01;
+static double q3 = .156875193295039e+01;
+static double q4 = .361951990101499e+00;
+static double q5 = .325038868253937e-01;
+static double q6 = .667465618796164e-03;
+static double r0 = .422784335098467e+00;
+static double r1 = .848044614534529e+00;
+static double r2 = .565221050691933e+00;
+static double r3 = .156513060486551e+00;
+static double r4 = .170502484022650e-01;
+static double r5 = .497958207639485e-03;
+static double s1 = .124313399877507e+01;
+static double s2 = .548042109832463e+00;
+static double s3 = .101552187439830e+00;
+static double s4 = .713309612391000e-02;
+static double s5 = .116165475989616e-03;
+static double gamln1,w,x;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(*a >= 0.6e0) goto S10;
+    w = ((((((p6**a+p5)**a+p4)**a+p3)**a+p2)**a+p1)**a+p0)/((((((q6**a+q5)**a+
+      q4)**a+q3)**a+q2)**a+q1)**a+1.0e0);
+    gamln1 = -(*a*w);
+    return gamln1;
+S10:
+    x = *a-0.5e0-0.5e0;
+    w = (((((r5*x+r4)*x+r3)*x+r2)*x+r1)*x+r0)/(((((s5*x+s4)*x+s3)*x+s2)*x+s1)*x
+      +1.0e0);
+    gamln1 = x*w;
+    return gamln1;
+}
+double Xgamm(double *a)
+/*
+-----------------------------------------------------------------------
+         EVALUATION OF THE GAMMA FUNCTION FOR REAL ARGUMENTS
+                           -----------
+     GAMMA(A) IS ASSIGNED THE VALUE 0 WHEN THE GAMMA FUNCTION CANNOT
+     BE COMPUTED.
+-----------------------------------------------------------------------
+     WRITTEN BY ALFRED H. MORRIS, JR.
+          NAVAL SURFACE WEAPONS CENTER
+          DAHLGREN, VIRGINIA
+-----------------------------------------------------------------------
+*/
+{
+static double d = .41893853320467274178e0;
+static double pi = 3.1415926535898e0;
+static double r1 = .820756370353826e-03;
+static double r2 = -.595156336428591e-03;
+static double r3 = .793650663183693e-03;
+static double r4 = -.277777777770481e-02;
+static double r5 = .833333333333333e-01;
+static double p[7] = {
+    .539637273585445e-03,.261939260042690e-02,.204493667594920e-01,
+    .730981088720487e-01,.279648642639792e+00,.553413866010467e+00,1.0e0
+};
+static double q[7] = {
+    -.832979206704073e-03,.470059485860584e-02,.225211131035340e-01,
+    -.170458969313360e+00,-.567902761974940e-01,.113062953091122e+01,1.0e0
+};
+static int K2 = 3;
+static int K3 = 0;
+static double Xgamm,bot,g,lnx,s,t,top,w,x,z;
+static int i,j,m,n,T1;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    Xgamm = 0.0e0;
+    x = *a;
+    if(fabs(*a) >= 15.0e0) goto S110;
+/*
+-----------------------------------------------------------------------
+            EVALUATION OF GAMMA(A) FOR ABS(A) .LT. 15
+-----------------------------------------------------------------------
+*/
+    t = 1.0e0;
+    m = fifidint(*a)-1;
+/*
+     LET T BE THE PRODUCT OF A-J WHEN A .GE. 2
+*/
+    T1 = m;
+    if(T1 < 0) goto S40;
+    else if(T1 == 0) goto S30;
+    else  goto S10;
+S10:
+    for(j=1; j<=m; j++) {
+        x -= 1.0e0;
+        t = x*t;
+    }
+S30:
+    x -= 1.0e0;
+    goto S80;
+S40:
+/*
+     LET T BE THE PRODUCT OF A+J WHEN A .LT. 1
+*/
+    t = *a;
+    if(*a > 0.0e0) goto S70;
+    m = -m-1;
+    if(m == 0) goto S60;
+    for(j=1; j<=m; j++) {
+        x += 1.0e0;
+        t = x*t;
+    }
+S60:
+    x += (0.5e0+0.5e0);
+    t = x*t;
+    if(t == 0.0e0) return Xgamm;
+S70:
+/*
+     THE FOLLOWING CODE CHECKS IF 1/T CAN OVERFLOW. THIS
+     CODE MAY BE OMITTED IF DESIRED.
+*/
+    if(fabs(t) >= 1.e-30) goto S80;
+    if(fabs(t)*spmpar(&K2) <= 1.0001e0) return Xgamm;
+    Xgamm = 1.0e0/t;
+    return Xgamm;
+S80:
+/*
+     COMPUTE GAMMA(1 + X) FOR  0 .LE. X .LT. 1
+*/
+    top = p[0];
+    bot = q[0];
+    for(i=1; i<7; i++) {
+        top = p[i]+x*top;
+        bot = q[i]+x*bot;
+    }
+    Xgamm = top/bot;
+/*
+     TERMINATION
+*/
+    if(*a < 1.0e0) goto S100;
+    Xgamm *= t;
+    return Xgamm;
+S100:
+    Xgamm /= t;
+    return Xgamm;
+S110:
+/*
+-----------------------------------------------------------------------
+            EVALUATION OF GAMMA(A) FOR ABS(A) .GE. 15
+-----------------------------------------------------------------------
+*/
+    if(fabs(*a) >= 1.e3) return Xgamm;
+    if(*a > 0.0e0) goto S120;
+    x = -*a;
+    n = (long)(x);
+    t = x-(double)n;
+    if(t > 0.9e0) t = 1.0e0-t;
+    s = sin(pi*t)/pi;
+    if(fifmod(n,2) == 0) s = -s;
+    if(s == 0.0e0) return Xgamm;
+S120:
+/*
+     COMPUTE THE MODIFIED ASYMPTOTIC SUM
+*/
+    t = 1.0e0/(x*x);
+    g = ((((r1*t+r2)*t+r3)*t+r4)*t+r5)/x;
+/*
+     ONE MAY REPLACE THE NEXT STATEMENT WITH  LNX = ALOG(X)
+     BUT LESS ACCURACY WILL NORMALLY BE OBTAINED.
+*/
+    lnx = log(x);
+/*
+     FINAL ASSEMBLY
+*/
+    z = x;
+    g = d+g+(z-0.5e0)*(lnx-1.e0);
+    w = g;
+    t = g-w;
+    if(w > 0.99999e0*exparg(&K3)) return Xgamm;
+    Xgamm = exp(w)*(1.0e0+t);
+    if(*a < 0.0e0) Xgamm = 1.0e0/(Xgamm*s)/x;
+    return Xgamm;
+}
+void grat1(double *a,double *x,double *r,double *p,double *q,
+          double *eps)
+{
+static int K2 = 0;
+static double a2n,a2nm1,am0,an,an0,b2n,b2nm1,c,cma,g,h,j,l,sum,t,tol,w,z,T1,T3;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+-----------------------------------------------------------------------
+        EVALUATION OF THE INCOMPLETE GAMMA RATIO FUNCTIONS
+                      P(A,X) AND Q(A,X)
+     IT IS ASSUMED THAT A .LE. 1.  EPS IS THE TOLERANCE TO BE USED.
+     THE INPUT ARGUMENT R HAS THE VALUE E**(-X)*X**A/GAMMA(A).
+-----------------------------------------------------------------------
+*/
+    if(*a**x == 0.0e0) goto S120;
+    if(*a == 0.5e0) goto S100;
+    if(*x < 1.1e0) goto S10;
+    goto S60;
+S10:
+/*
+             TAYLOR SERIES FOR P(A,X)/X**A
+*/
+    an = 3.0e0;
+    c = *x;
+    sum = *x/(*a+3.0e0);
+    tol = 0.1e0**eps/(*a+1.0e0);
+S20:
+    an += 1.0e0;
+    c = -(c*(*x/an));
+    t = c/(*a+an);
+    sum += t;
+    if(fabs(t) > tol) goto S20;
+    j = *a**x*((sum/6.0e0-0.5e0/(*a+2.0e0))**x+1.0e0/(*a+1.0e0));
+    z = *a*log(*x);
+    h = gam1(a);
+    g = 1.0e0+h;
+    if(*x < 0.25e0) goto S30;
+    if(*a < *x/2.59e0) goto S50;
+    goto S40;
+S30:
+    if(z > -.13394e0) goto S50;
+S40:
+    w = exp(z);
+    *p = w*g*(0.5e0+(0.5e0-j));
+    *q = 0.5e0+(0.5e0-*p);
+    return;
+S50:
+    l = rexp(&z);
+    w = 0.5e0+(0.5e0+l);
+    *q = (w*j-l)*g-h;
+    if(*q < 0.0e0) goto S90;
+    *p = 0.5e0+(0.5e0-*q);
+    return;
+S60:
+/*
+              CONTINUED FRACTION EXPANSION
+*/
+    a2nm1 = a2n = 1.0e0;
+    b2nm1 = *x;
+    b2n = *x+(1.0e0-*a);
+    c = 1.0e0;
+S70:
+    a2nm1 = *x*a2n+c*a2nm1;
+    b2nm1 = *x*b2n+c*b2nm1;
+    am0 = a2nm1/b2nm1;
+    c += 1.0e0;
+    cma = c-*a;
+    a2n = a2nm1+cma*a2n;
+    b2n = b2nm1+cma*b2n;
+    an0 = a2n/b2n;
+    if(fabs(an0-am0) >= *eps*an0) goto S70;
+    *q = *r*an0;
+    *p = 0.5e0+(0.5e0-*q);
+    return;
+S80:
+/*
+                SPECIAL CASES
+*/
+    *p = 0.0e0;
+    *q = 1.0e0;
+    return;
+S90:
+    *p = 1.0e0;
+    *q = 0.0e0;
+    return;
+S100:
+    if(*x >= 0.25e0) goto S110;
+    T1 = sqrt(*x);
+    *p = erf1(&T1);
+    *q = 0.5e0+(0.5e0-*p);
+    return;
+S110:
+    T3 = sqrt(*x);
+    *q = erfc1(&K2,&T3);
+    *p = 0.5e0+(0.5e0-*q);
+    return;
+S120:
+    if(*x <= *a) goto S80;
+    goto S90;
+}
+void gratio(double *a,double *x,double *ans,double *qans,int *ind)
+/*
+ ----------------------------------------------------------------------
+        EVALUATION OF THE INCOMPLETE GAMMA RATIO FUNCTIONS
+                      P(A,X) AND Q(A,X)
+                        ----------
+     IT IS ASSUMED THAT A AND X ARE NONNEGATIVE, WHERE A AND X
+     ARE NOT BOTH 0.
+     ANS AND QANS ARE VARIABLES. GRATIO ASSIGNS ANS THE VALUE
+     P(A,X) AND QANS THE VALUE Q(A,X). IND MAY BE ANY INTEGER.
+     IF IND = 0 THEN THE USER IS REQUESTING AS MUCH ACCURACY AS
+     POSSIBLE (UP TO 14 SIGNIFICANT DIGITS). OTHERWISE, IF
+     IND = 1 THEN ACCURACY IS REQUESTED TO WITHIN 1 UNIT OF THE
+     6-TH SIGNIFICANT DIGIT, AND IF IND .NE. 0,1 THEN ACCURACY
+     IS REQUESTED TO WITHIN 1 UNIT OF THE 3RD SIGNIFICANT DIGIT.
+     ERROR RETURN ...
+        ANS IS ASSIGNED THE VALUE 2 WHEN A OR X IS NEGATIVE,
+     WHEN A*X = 0, OR WHEN P(A,X) AND Q(A,X) ARE INDETERMINANT.
+     P(A,X) AND Q(A,X) ARE COMPUTATIONALLY INDETERMINANT WHEN
+     X IS EXCEEDINGLY CLOSE TO A AND A IS EXTREMELY LARGE.
+ ----------------------------------------------------------------------
+     WRITTEN BY ALFRED H. MORRIS, JR.
+        NAVAL SURFACE WEAPONS CENTER
+        DAHLGREN, VIRGINIA
+     --------------------
+*/
+{
+static double alog10 = 2.30258509299405e0;
+static double d10 = -.185185185185185e-02;
+static double d20 = .413359788359788e-02;
+static double d30 = .649434156378601e-03;
+static double d40 = -.861888290916712e-03;
+static double d50 = -.336798553366358e-03;
+static double d60 = .531307936463992e-03;
+static double d70 = .344367606892378e-03;
+static double rt2pin = .398942280401433e0;
+static double rtpi = 1.77245385090552e0;
+static double third = .333333333333333e0;
+static double acc0[3] = {
+    5.e-15,5.e-7,5.e-4
+};
+static double big[3] = {
+    20.0e0,14.0e0,10.0e0
+};
+static double d0[13] = {
+    .833333333333333e-01,-.148148148148148e-01,.115740740740741e-02,
+    .352733686067019e-03,-.178755144032922e-03,.391926317852244e-04,
+    -.218544851067999e-05,-.185406221071516e-05,.829671134095309e-06,
+    -.176659527368261e-06,.670785354340150e-08,.102618097842403e-07,
+    -.438203601845335e-08
+};
+static double d1[12] = {
+    -.347222222222222e-02,.264550264550265e-02,-.990226337448560e-03,
+    .205761316872428e-03,-.401877572016461e-06,-.180985503344900e-04,
+    .764916091608111e-05,-.161209008945634e-05,.464712780280743e-08,
+    .137863344691572e-06,-.575254560351770e-07,.119516285997781e-07
+};
+static double d2[10] = {
+    -.268132716049383e-02,.771604938271605e-03,.200938786008230e-05,
+    -.107366532263652e-03,.529234488291201e-04,-.127606351886187e-04,
+    .342357873409614e-07,.137219573090629e-05,-.629899213838006e-06,
+    .142806142060642e-06
+};
+static double d3[8] = {
+    .229472093621399e-03,-.469189494395256e-03,.267720632062839e-03,
+    -.756180167188398e-04,-.239650511386730e-06,.110826541153473e-04,
+    -.567495282699160e-05,.142309007324359e-05
+};
+static double d4[6] = {
+    .784039221720067e-03,-.299072480303190e-03,-.146384525788434e-05,
+    .664149821546512e-04,-.396836504717943e-04,.113757269706784e-04
+};
+static double d5[4] = {
+    -.697281375836586e-04,.277275324495939e-03,-.199325705161888e-03,
+    .679778047793721e-04
+};
+static double d6[2] = {
+    -.592166437353694e-03,.270878209671804e-03
+};
+static double e00[3] = {
+    .25e-3,.25e-1,.14e0
+};
+static double x00[3] = {
+    31.0e0,17.0e0,9.7e0
+};
+static int K1 = 1;
+static int K2 = 0;
+static double a2n,a2nm1,acc,am0,amn,an,an0,apn,b2n,b2nm1,c,c0,c1,c2,c3,c4,c5,c6,
+    cma,e,e0,g,h,j,l,r,rta,rtx,s,sum,t,t1,tol,twoa,u,w,x0,y,z;
+static int i,iop,m,max,n;
+static double wk[20],T3;
+static int T4,T5;
+static double T6,T7;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+     --------------------
+     ****** E IS A MACHINE DEPENDENT CONSTANT. E IS THE SMALLEST
+            FLOATING POINT NUMBER FOR WHICH 1.0 + E .GT. 1.0 .
+*/
+    e = spmpar(&K1);
+    if(*a < 0.0e0 || *x < 0.0e0) goto S430;
+    if(*a == 0.0e0 && *x == 0.0e0) goto S430;
+    if(*a**x == 0.0e0) goto S420;
+    iop = *ind+1;
+    if(iop != 1 && iop != 2) iop = 3;
+    acc = fifdmax1(acc0[iop-1],e);
+    e0 = e00[iop-1];
+    x0 = x00[iop-1];
+/*
+            SELECT THE APPROPRIATE ALGORITHM
+*/
+    if(*a >= 1.0e0) goto S10;
+    if(*a == 0.5e0) goto S390;
+    if(*x < 1.1e0) goto S160;
+    t1 = *a*log(*x)-*x;
+    u = *a*exp(t1);
+    if(u == 0.0e0) goto S380;
+    r = u*(1.0e0+gam1(a));
+    goto S250;
+S10:
+    if(*a >= big[iop-1]) goto S30;
+    if(*a > *x || *x >= x0) goto S20;
+    twoa = *a+*a;
+    m = fifidint(twoa);
+    if(twoa != (double)m) goto S20;
+    i = m/2;
+    if(*a == (double)i) goto S210;
+    goto S220;
+S20:
+    t1 = *a*log(*x)-*x;
+    r = exp(t1)/Xgamm(a);
+    goto S40;
+S30:
+    l = *x/ *a;
+    if(l == 0.0e0) goto S370;
+    s = 0.5e0+(0.5e0-l);
+    z = rlog(&l);
+    if(z >= 700.0e0/ *a) goto S410;
+    y = *a*z;
+    rta = sqrt(*a);
+    if(fabs(s) <= e0/rta) goto S330;
+    if(fabs(s) <= 0.4e0) goto S270;
+    t = pow(1.0e0/ *a,2.0);
+    t1 = (((0.75e0*t-1.0e0)*t+3.5e0)*t-105.0e0)/(*a*1260.0e0);
+    t1 -= y;
+    r = rt2pin*rta*exp(t1);
+S40:
+    if(r == 0.0e0) goto S420;
+    if(*x <= fifdmax1(*a,alog10)) goto S50;
+    if(*x < x0) goto S250;
+    goto S100;
+S50:
+/*
+                 TAYLOR SERIES FOR P/R
+*/
+    apn = *a+1.0e0;
+    t = *x/apn;
+    wk[0] = t;
+    for(n=2; n<=20; n++) {
+        apn += 1.0e0;
+        t *= (*x/apn);
+        if(t <= 1.e-3) goto S70;
+        wk[n-1] = t;
+    }
+    n = 20;
+S70:
+    sum = t;
+    tol = 0.5e0*acc;
+S80:
+    apn += 1.0e0;
+    t *= (*x/apn);
+    sum += t;
+    if(t > tol) goto S80;
+    max = n-1;
+    for(m=1; m<=max; m++) {
+        n -= 1;
+        sum += wk[n-1];
+    }
+    *ans = r/ *a*(1.0e0+sum);
+    *qans = 0.5e0+(0.5e0-*ans);
+    return;
+S100:
+/*
+                 ASYMPTOTIC EXPANSION
+*/
+    amn = *a-1.0e0;
+    t = amn/ *x;
+    wk[0] = t;
+    for(n=2; n<=20; n++) {
+        amn -= 1.0e0;
+        t *= (amn/ *x);
+        if(fabs(t) <= 1.e-3) goto S120;
+        wk[n-1] = t;
+    }
+    n = 20;
+S120:
+    sum = t;
+S130:
+    if(fabs(t) <= acc) goto S140;
+    amn -= 1.0e0;
+    t *= (amn/ *x);
+    sum += t;
+    goto S130;
+S140:
+    max = n-1;
+    for(m=1; m<=max; m++) {
+        n -= 1;
+        sum += wk[n-1];
+    }
+    *qans = r/ *x*(1.0e0+sum);
+    *ans = 0.5e0+(0.5e0-*qans);
+    return;
+S160:
+/*
+             TAYLOR SERIES FOR P(A,X)/X**A
+*/
+    an = 3.0e0;
+    c = *x;
+    sum = *x/(*a+3.0e0);
+    tol = 3.0e0*acc/(*a+1.0e0);
+S170:
+    an += 1.0e0;
+    c = -(c*(*x/an));
+    t = c/(*a+an);
+    sum += t;
+    if(fabs(t) > tol) goto S170;
+    j = *a**x*((sum/6.0e0-0.5e0/(*a+2.0e0))**x+1.0e0/(*a+1.0e0));
+    z = *a*log(*x);
+    h = gam1(a);
+    g = 1.0e0+h;
+    if(*x < 0.25e0) goto S180;
+    if(*a < *x/2.59e0) goto S200;
+    goto S190;
+S180:
+    if(z > -.13394e0) goto S200;
+S190:
+    w = exp(z);
+    *ans = w*g*(0.5e0+(0.5e0-j));
+    *qans = 0.5e0+(0.5e0-*ans);
+    return;
+S200:
+    l = rexp(&z);
+    w = 0.5e0+(0.5e0+l);
+    *qans = (w*j-l)*g-h;
+    if(*qans < 0.0e0) goto S380;
+    *ans = 0.5e0+(0.5e0-*qans);
+    return;
+S210:
+/*
+             FINITE SUMS FOR Q WHEN A .GE. 1
+                 AND 2*A IS AN INTEGER
+*/
+    sum = exp(-*x);
+    t = sum;
+    n = 1;
+    c = 0.0e0;
+    goto S230;
+S220:
+    rtx = sqrt(*x);
+    sum = erfc1(&K2,&rtx);
+    t = exp(-*x)/(rtpi*rtx);
+    n = 0;
+    c = -0.5e0;
+S230:
+    if(n == i) goto S240;
+    n += 1;
+    c += 1.0e0;
+    t = *x*t/c;
+    sum += t;
+    goto S230;
+S240:
+    *qans = sum;
+    *ans = 0.5e0+(0.5e0-*qans);
+    return;
+S250:
+/*
+              CONTINUED FRACTION EXPANSION
+*/
+    tol = fifdmax1(5.0e0*e,acc);
+    a2nm1 = a2n = 1.0e0;
+    b2nm1 = *x;
+    b2n = *x+(1.0e0-*a);
+    c = 1.0e0;
+S260:
+    a2nm1 = *x*a2n+c*a2nm1;
+    b2nm1 = *x*b2n+c*b2nm1;
+    am0 = a2nm1/b2nm1;
+    c += 1.0e0;
+    cma = c-*a;
+    a2n = a2nm1+cma*a2n;
+    b2n = b2nm1+cma*b2n;
+    an0 = a2n/b2n;
+    if(fabs(an0-am0) >= tol*an0) goto S260;
+    *qans = r*an0;
+    *ans = 0.5e0+(0.5e0-*qans);
+    return;
+S270:
+/*
+                GENERAL TEMME EXPANSION
+*/
+    if(fabs(s) <= 2.0e0*e && *a*e*e > 3.28e-3) goto S430;
+    c = exp(-y);
+    T3 = sqrt(y);
+    w = 0.5e0*erfc1(&K1,&T3);
+    u = 1.0e0/ *a;
+    z = sqrt(z+z);
+    if(l < 1.0e0) z = -z;
+    T4 = iop-2;
+    if(T4 < 0) goto S280;
+    else if(T4 == 0) goto S290;
+    else  goto S300;
+S280:
+    if(fabs(s) <= 1.e-3) goto S340;
+    c0 = ((((((((((((d0[12]*z+d0[11])*z+d0[10])*z+d0[9])*z+d0[8])*z+d0[7])*z+d0[
+      6])*z+d0[5])*z+d0[4])*z+d0[3])*z+d0[2])*z+d0[1])*z+d0[0])*z-third;
+    c1 = (((((((((((d1[11]*z+d1[10])*z+d1[9])*z+d1[8])*z+d1[7])*z+d1[6])*z+d1[5]
+      )*z+d1[4])*z+d1[3])*z+d1[2])*z+d1[1])*z+d1[0])*z+d10;
+    c2 = (((((((((d2[9]*z+d2[8])*z+d2[7])*z+d2[6])*z+d2[5])*z+d2[4])*z+d2[3])*z+
+      d2[2])*z+d2[1])*z+d2[0])*z+d20;
+    c3 = (((((((d3[7]*z+d3[6])*z+d3[5])*z+d3[4])*z+d3[3])*z+d3[2])*z+d3[1])*z+
+      d3[0])*z+d30;
+    c4 = (((((d4[5]*z+d4[4])*z+d4[3])*z+d4[2])*z+d4[1])*z+d4[0])*z+d40;
+    c5 = (((d5[3]*z+d5[2])*z+d5[1])*z+d5[0])*z+d50;
+    c6 = (d6[1]*z+d6[0])*z+d60;
+    t = ((((((d70*u+c6)*u+c5)*u+c4)*u+c3)*u+c2)*u+c1)*u+c0;
+    goto S310;
+S290:
+    c0 = (((((d0[5]*z+d0[4])*z+d0[3])*z+d0[2])*z+d0[1])*z+d0[0])*z-third;
+    c1 = (((d1[3]*z+d1[2])*z+d1[1])*z+d1[0])*z+d10;
+    c2 = d2[0]*z+d20;
+    t = (c2*u+c1)*u+c0;
+    goto S310;
+S300:
+    t = ((d0[2]*z+d0[1])*z+d0[0])*z-third;
+S310:
+    if(l < 1.0e0) goto S320;
+    *qans = c*(w+rt2pin*t/rta);
+    *ans = 0.5e0+(0.5e0-*qans);
+    return;
+S320:
+    *ans = c*(w-rt2pin*t/rta);
+    *qans = 0.5e0+(0.5e0-*ans);
+    return;
+S330:
+/*
+               TEMME EXPANSION FOR L = 1
+*/
+    if(*a*e*e > 3.28e-3) goto S430;
+    c = 0.5e0+(0.5e0-y);
+    w = (0.5e0-sqrt(y)*(0.5e0+(0.5e0-y/3.0e0))/rtpi)/c;
+    u = 1.0e0/ *a;
+    z = sqrt(z+z);
+    if(l < 1.0e0) z = -z;
+    T5 = iop-2;
+    if(T5 < 0) goto S340;
+    else if(T5 == 0) goto S350;
+    else  goto S360;
+S340:
+    c0 = ((((((d0[6]*z+d0[5])*z+d0[4])*z+d0[3])*z+d0[2])*z+d0[1])*z+d0[0])*z-
+      third;
+    c1 = (((((d1[5]*z+d1[4])*z+d1[3])*z+d1[2])*z+d1[1])*z+d1[0])*z+d10;
+    c2 = ((((d2[4]*z+d2[3])*z+d2[2])*z+d2[1])*z+d2[0])*z+d20;
+    c3 = (((d3[3]*z+d3[2])*z+d3[1])*z+d3[0])*z+d30;
+    c4 = (d4[1]*z+d4[0])*z+d40;
+    c5 = (d5[1]*z+d5[0])*z+d50;
+    c6 = d6[0]*z+d60;
+    t = ((((((d70*u+c6)*u+c5)*u+c4)*u+c3)*u+c2)*u+c1)*u+c0;
+    goto S310;
+S350:
+    c0 = (d0[1]*z+d0[0])*z-third;
+    c1 = d1[0]*z+d10;
+    t = (d20*u+c1)*u+c0;
+    goto S310;
+S360:
+    t = d0[0]*z-third;
+    goto S310;
+S370:
+/*
+                     SPECIAL CASES
+*/
+    *ans = 0.0e0;
+    *qans = 1.0e0;
+    return;
+S380:
+    *ans = 1.0e0;
+    *qans = 0.0e0;
+    return;
+S390:
+    if(*x >= 0.25e0) goto S400;
+    T6 = sqrt(*x);
+    *ans = erf1(&T6);
+    *qans = 0.5e0+(0.5e0-*ans);
+    return;
+S400:
+    T7 = sqrt(*x);
+    *qans = erfc1(&K2,&T7);
+    *ans = 0.5e0+(0.5e0-*qans);
+    return;
+S410:
+    if(fabs(s) <= 2.0e0*e) goto S430;
+S420:
+    if(*x <= *a) goto S370;
+    goto S380;
+S430:
+/*
+                     ERROR RETURN
+*/
+    *ans = 2.0e0;
+    return;
+}
+double gsumln(double *a,double *b)
+/*
+-----------------------------------------------------------------------
+          EVALUATION OF THE FUNCTION LN(GAMMA(A + B))
+          FOR 1 .LE. A .LE. 2  AND  1 .LE. B .LE. 2
+-----------------------------------------------------------------------
+*/
+{
+static double gsumln,x,T1,T2;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    x = *a+*b-2.e0;
+    if(x > 0.25e0) goto S10;
+    T1 = 1.0e0+x;
+    gsumln = gamln1(&T1);
+    return gsumln;
+S10:
+    if(x > 1.25e0) goto S20;
+    gsumln = gamln1(&x)+alnrel(&x);
+    return gsumln;
+S20:
+    T2 = x-1.0e0;
+    gsumln = gamln1(&T2)+log(x*(1.0e0+x));
+    return gsumln;
+}
+double psi(double *xx)
+/*
+---------------------------------------------------------------------
+                 EVALUATION OF THE DIGAMMA FUNCTION
+                           -----------
+     PSI(XX) IS ASSIGNED THE VALUE 0 WHEN THE DIGAMMA FUNCTION CANNOT
+     BE COMPUTED.
+     THE MAIN COMPUTATION INVOLVES EVALUATION OF RATIONAL CHEBYSHEV
+     APPROXIMATIONS PUBLISHED IN MATH. COMP. 27, 123-127(1973) BY
+     CODY, STRECOK AND THACHER.
+---------------------------------------------------------------------
+     PSI WAS WRITTEN AT ARGONNE NATIONAL LABORATORY FOR THE FUNPACK
+     PACKAGE OF SPECIAL FUNCTION SUBROUTINES. PSI WAS MODIFIED BY
+     A.H. MORRIS (NSWC).
+---------------------------------------------------------------------
+*/
+{
+static double dx0 = 1.461632144968362341262659542325721325e0;
+static double piov4 = .785398163397448e0;
+static double p1[7] = {
+    .895385022981970e-02,.477762828042627e+01,.142441585084029e+03,
+    .118645200713425e+04,.363351846806499e+04,.413810161269013e+04,
+    .130560269827897e+04
+};
+static double p2[4] = {
+    -.212940445131011e+01,-.701677227766759e+01,-.448616543918019e+01,
+    -.648157123766197e+00
+};
+static double q1[6] = {
+    .448452573429826e+02,.520752771467162e+03,.221000799247830e+04,
+    .364127349079381e+04,.190831076596300e+04,.691091682714533e-05
+};
+static double q2[4] = {
+    .322703493791143e+02,.892920700481861e+02,.546117738103215e+02,
+    .777788548522962e+01
+};
+static int K1 = 3;
+static int K2 = 1;
+static double psi,aug,den,sgn,upper,w,x,xmax1,xmx0,xsmall,z;
+static int i,m,n,nq;
+/*
+     ..
+     .. Executable Statements ..
+*/
+/*
+---------------------------------------------------------------------
+     MACHINE DEPENDENT CONSTANTS ...
+        XMAX1  = THE SMALLEST POSITIVE FLOATING POINT CONSTANT
+                 WITH ENTIRELY INTEGER REPRESENTATION.  ALSO USED
+                 AS NEGATIVE OF LOWER BOUND ON ACCEPTABLE NEGATIVE
+                 ARGUMENTS AND AS THE POSITIVE ARGUMENT BEYOND WHICH
+                 PSI MAY BE REPRESENTED AS ALOG(X).
+        XSMALL = ABSOLUTE ARGUMENT BELOW WHICH PI*COTAN(PI*X)
+                 MAY BE REPRESENTED BY 1/X.
+---------------------------------------------------------------------
+*/
+    xmax1 = ipmpar(&K1);
+    xmax1 = fifdmin1(xmax1,1.0e0/spmpar(&K2));
+    xsmall = 1.e-9;
+    x = *xx;
+    aug = 0.0e0;
+    if(x >= 0.5e0) goto S50;
+/*
+---------------------------------------------------------------------
+     X .LT. 0.5,  USE REFLECTION FORMULA
+     PSI(1-X) = PSI(X) + PI * COTAN(PI*X)
+---------------------------------------------------------------------
+*/
+    if(fabs(x) > xsmall) goto S10;
+    if(x == 0.0e0) goto S100;
+/*
+---------------------------------------------------------------------
+     0 .LT. ABS(X) .LE. XSMALL.  USE 1/X AS A SUBSTITUTE
+     FOR  PI*COTAN(PI*X)
+---------------------------------------------------------------------
+*/
+    aug = -(1.0e0/x);
+    goto S40;
+S10:
+/*
+---------------------------------------------------------------------
+     REDUCTION OF ARGUMENT FOR COTAN
+---------------------------------------------------------------------
+*/
+    w = -x;
+    sgn = piov4;
+    if(w > 0.0e0) goto S20;
+    w = -w;
+    sgn = -sgn;
+S20:
+/*
+---------------------------------------------------------------------
+     MAKE AN ERROR EXIT IF X .LE. -XMAX1
+---------------------------------------------------------------------
+*/
+    if(w >= xmax1) goto S100;
+    nq = fifidint(w);
+    w -= (double)nq;
+    nq = fifidint(w*4.0e0);
+    w = 4.0e0*(w-(double)nq*.25e0);
+/*
+---------------------------------------------------------------------
+     W IS NOW RELATED TO THE FRACTIONAL PART OF  4.0 * X.
+     ADJUST ARGUMENT TO CORRESPOND TO VALUES IN FIRST
+     QUADRANT AND DETERMINE SIGN
+---------------------------------------------------------------------
+*/
+    n = nq/2;
+    if(n+n != nq) w = 1.0e0-w;
+    z = piov4*w;
+    m = n/2;
+    if(m+m != n) sgn = -sgn;
+/*
+---------------------------------------------------------------------
+     DETERMINE FINAL VALUE FOR  -PI*COTAN(PI*X)
+---------------------------------------------------------------------
+*/
+    n = (nq+1)/2;
+    m = n/2;
+    m += m;
+    if(m != n) goto S30;
+/*
+---------------------------------------------------------------------
+     CHECK FOR SINGULARITY
+---------------------------------------------------------------------
+*/
+    if(z == 0.0e0) goto S100;
+/*
+---------------------------------------------------------------------
+     USE COS/SIN AS A SUBSTITUTE FOR COTAN, AND
+     SIN/COS AS A SUBSTITUTE FOR TAN
+---------------------------------------------------------------------
+*/
+    aug = sgn*(cos(z)/sin(z)*4.0e0);
+    goto S40;
+S30:
+    aug = sgn*(sin(z)/cos(z)*4.0e0);
+S40:
+    x = 1.0e0-x;
+S50:
+    if(x > 3.0e0) goto S70;
+/*
+---------------------------------------------------------------------
+     0.5 .LE. X .LE. 3.0
+---------------------------------------------------------------------
+*/
+    den = x;
+    upper = p1[0]*x;
+    for(i=1; i<=5; i++) {
+        den = (den+q1[i-1])*x;
+        upper = (upper+p1[i+1-1])*x;
+    }
+    den = (upper+p1[6])/(den+q1[5]);
+    xmx0 = x-dx0;
+    psi = den*xmx0+aug;
+    return psi;
+S70:
+/*
+---------------------------------------------------------------------
+     IF X .GE. XMAX1, PSI = LN(X)
+---------------------------------------------------------------------
+*/
+    if(x >= xmax1) goto S90;
+/*
+---------------------------------------------------------------------
+     3.0 .LT. X .LT. XMAX1
+---------------------------------------------------------------------
+*/
+    w = 1.0e0/(x*x);
+    den = w;
+    upper = p2[0]*w;
+    for(i=1; i<=3; i++) {
+        den = (den+q2[i-1])*w;
+        upper = (upper+p2[i+1-1])*w;
+    }
+    aug = upper/(den+q2[3])-0.5e0/x+aug;
+S90:
+    psi = aug+log(x);
+    return psi;
+S100:
+/*
+---------------------------------------------------------------------
+     ERROR RETURN
+---------------------------------------------------------------------
+*/
+    psi = 0.0e0;
+    return psi;
+}
+double rcomp(double *a,double *x)
+/*
+     -------------------
+     EVALUATION OF EXP(-X)*X**A/GAMMA(A)
+     -------------------
+     RT2PIN = 1/SQRT(2*PI)
+     -------------------
+*/
+{
+static double rt2pin = .398942280401433e0;
+static double rcomp,t,t1,u;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    rcomp = 0.0e0;
+    if(*a >= 20.0e0) goto S20;
+    t = *a*log(*x)-*x;
+    if(*a >= 1.0e0) goto S10;
+    rcomp = *a*exp(t)*(1.0e0+gam1(a));
+    return rcomp;
+S10:
+    rcomp = exp(t)/Xgamm(a);
+    return rcomp;
+S20:
+    u = *x/ *a;
+    if(u == 0.0e0) return rcomp;
+    t = pow(1.0e0/ *a,2.0);
+    t1 = (((0.75e0*t-1.0e0)*t+3.5e0)*t-105.0e0)/(*a*1260.0e0);
+    t1 -= (*a*rlog(&u));
+    rcomp = rt2pin*sqrt(*a)*exp(t1);
+    return rcomp;
+}
+double rexp(double *x)
+/*
+-----------------------------------------------------------------------
+            EVALUATION OF THE FUNCTION EXP(X) - 1
+-----------------------------------------------------------------------
+*/
+{
+static double p1 = .914041914819518e-09;
+static double p2 = .238082361044469e-01;
+static double q1 = -.499999999085958e+00;
+static double q2 = .107141568980644e+00;
+static double q3 = -.119041179760821e-01;
+static double q4 = .595130811860248e-03;
+static double rexp,w;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(fabs(*x) > 0.15e0) goto S10;
+    rexp = *x*(((p2**x+p1)**x+1.0e0)/((((q4**x+q3)**x+q2)**x+q1)**x+1.0e0));
+    return rexp;
+S10:
+    w = exp(*x);
+    if(*x > 0.0e0) goto S20;
+    rexp = w-0.5e0-0.5e0;
+    return rexp;
+S20:
+    rexp = w*(0.5e0+(0.5e0-1.0e0/w));
+    return rexp;
+}
+double rlog(double *x)
+/*
+     -------------------
+     COMPUTATION OF  X - 1 - LN(X)
+     -------------------
+*/
+{
+static double a = .566749439387324e-01;
+static double b = .456512608815524e-01;
+static double p0 = .333333333333333e+00;
+static double p1 = -.224696413112536e+00;
+static double p2 = .620886815375787e-02;
+static double q1 = -.127408923933623e+01;
+static double q2 = .354508718369557e+00;
+static double rlog,r,t,u,w,w1;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(*x < 0.61e0 || *x > 1.57e0) goto S40;
+    if(*x < 0.82e0) goto S10;
+    if(*x > 1.18e0) goto S20;
+/*
+              ARGUMENT REDUCTION
+*/
+    u = *x-0.5e0-0.5e0;
+    w1 = 0.0e0;
+    goto S30;
+S10:
+    u = *x-0.7e0;
+    u /= 0.7e0;
+    w1 = a-u*0.3e0;
+    goto S30;
+S20:
+    u = 0.75e0**x-1.e0;
+    w1 = b+u/3.0e0;
+S30:
+/*
+               SERIES EXPANSION
+*/
+    r = u/(u+2.0e0);
+    t = r*r;
+    w = ((p2*t+p1)*t+p0)/((q2*t+q1)*t+1.0e0);
+    rlog = 2.0e0*t*(1.0e0/(1.0e0-r)-r*w)+w1;
+    return rlog;
+S40:
+    r = *x-0.5e0-0.5e0;
+    rlog = r-log(*x);
+    return rlog;
+}
+double rlog1(double *x)
+/*
+-----------------------------------------------------------------------
+             EVALUATION OF THE FUNCTION X - LN(1 + X)
+-----------------------------------------------------------------------
+*/
+{
+static double a = .566749439387324e-01;
+static double b = .456512608815524e-01;
+static double p0 = .333333333333333e+00;
+static double p1 = -.224696413112536e+00;
+static double p2 = .620886815375787e-02;
+static double q1 = -.127408923933623e+01;
+static double q2 = .354508718369557e+00;
+static double rlog1,h,r,t,w,w1;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(*x < -0.39e0 || *x > 0.57e0) goto S40;
+    if(*x < -0.18e0) goto S10;
+    if(*x > 0.18e0) goto S20;
+/*
+              ARGUMENT REDUCTION
+*/
+    h = *x;
+    w1 = 0.0e0;
+    goto S30;
+S10:
+    h = *x+0.3e0;
+    h /= 0.7e0;
+    w1 = a-h*0.3e0;
+    goto S30;
+S20:
+    h = 0.75e0**x-0.25e0;
+    w1 = b+h/3.0e0;
+S30:
+/*
+               SERIES EXPANSION
+*/
+    r = h/(h+2.0e0);
+    t = r*r;
+    w = ((p2*t+p1)*t+p0)/((q2*t+q1)*t+1.0e0);
+    rlog1 = 2.0e0*t*(1.0e0/(1.0e0-r)-r*w)+w1;
+    return rlog1;
+S40:
+    w = *x+0.5e0+0.5e0;
+    rlog1 = *x-log(w);
+    return rlog1;
+}
+double spmpar(int *i)
+/*
+-----------------------------------------------------------------------
+     SPMPAR PROVIDES THE SINGLE PRECISION MACHINE CONSTANTS FOR
+     THE COMPUTER BEING USED. IT IS ASSUMED THAT THE ARGUMENT
+     I IS AN INTEGER HAVING ONE OF THE VALUES 1, 2, OR 3. IF THE
+     SINGLE PRECISION ARITHMETIC BEING USED HAS M BASE B DIGITS AND
+     ITS SMALLEST AND LARGEST EXPONENTS ARE EMIN AND EMAX, THEN
+        SPMPAR(1) = B**(1 - M), THE MACHINE PRECISION,
+        SPMPAR(2) = B**(EMIN - 1), THE SMALLEST MAGNITUDE,
+        SPMPAR(3) = B**EMAX*(1 - B**(-M)), THE LARGEST MAGNITUDE.
+-----------------------------------------------------------------------
+     WRITTEN BY
+        ALFRED H. MORRIS, JR.
+        NAVAL SURFACE WARFARE CENTER
+        DAHLGREN VIRGINIA
+-----------------------------------------------------------------------
+-----------------------------------------------------------------------
+     MODIFIED BY BARRY W. BROWN TO RETURN DOUBLE PRECISION MACHINE
+     CONSTANTS FOR THE COMPUTER BEING USED.  THIS MODIFICATION WAS
+     MADE AS PART OF CONVERTING BRATIO TO DOUBLE PRECISION
+-----------------------------------------------------------------------
+*/
+{
+static int K1 = 4;
+static int K2 = 8;
+static int K3 = 9;
+static int K4 = 10;
+static double spmpar,b,binv,bm1,one,w,z;
+static int emax,emin,ibeta,m;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(*i > 1) goto S10;
+    b = ipmpar(&K1);
+    m = ipmpar(&K2);
+    spmpar = pow(b,(double)(1-m));
+    return spmpar;
+S10:
+    if(*i > 2) goto S20;
+    b = ipmpar(&K1);
+    emin = ipmpar(&K3);
+    one = 1.0;
+    binv = one/b;
+    w = pow(b,(double)(emin+2));
+    spmpar = w*binv*binv*binv;
+    return spmpar;
+S20:
+    ibeta = ipmpar(&K1);
+    m = ipmpar(&K2);
+    emax = ipmpar(&K4);
+    b = ibeta;
+    bm1 = ibeta-1;
+    one = 1.0;
+    z = pow(b,(double)(m-1));
+    w = ((z-one)*b+bm1)/(b*z);
+    z = pow(b,(double)(emax-2));
+    spmpar = w*z*b*b;
+    return spmpar;
+}
+double stvaln(double *p)
+/*
+**********************************************************************
+     double stvaln(double *p)
+                    STarting VALue for Neton-Raphon
+                calculation of Normal distribution Inverse
+                              Function
+     Returns X  such that CUMNOR(X)  =   P,  i.e., the  integral from -
+     infinity to X of (1/SQRT(2*PI)) EXP(-U*U/2) dU is P
+                              Arguments
+     P --> The probability whose normal deviate is sought.
+                    P is DOUBLE PRECISION
+                              Method
+     The  rational   function   on  page 95    of Kennedy  and  Gentle,
+     Statistical Computing, Marcel Dekker, NY , 1980.
+**********************************************************************
+*/
+{
+static double xden[5] = {
+    0.993484626060e-1,0.588581570495e0,0.531103462366e0,0.103537752850e0,
+    0.38560700634e-2
+};
+static double xnum[5] = {
+    -0.322232431088e0,-1.000000000000e0,-0.342242088547e0,-0.204231210245e-1,
+    -0.453642210148e-4
+};
+static int K1 = 5;
+static double stvaln,sign,y,z;
+/*
+     ..
+     .. Executable Statements ..
+*/
+    if(!(*p <= 0.5e0)) goto S10;
+    sign = -1.0e0;
+    z = *p;
+    goto S20;
+S10:
+    sign = 1.0e0;
+    z = 1.0e0-*p;
+S20:
+    y = sqrt(-(2.0e0*log(z)));
+    stvaln = y+devlpl(xnum,&K1,&y)/devlpl(xden,&K1,&y);
+    stvaln = sign*stvaln;
+    return stvaln;
+}
+/************************************************************************
+FIFDINT:
+Truncates a double precision number to an integer and returns the
+value in a double.
+************************************************************************/
+double fifdint(double a)
+/* a     -     number to be truncated */
+{
+  long temp;
+  temp = (long)(a);
+  return (double)(temp);
+}
+/************************************************************************
+FIFDMAX1:
+returns the maximum of two numbers a and b
+************************************************************************/
+double fifdmax1(double a,double b)
+/* a     -      first number */
+/* b     -      second number */
+{
+  if (a < b) return b;
+  else return a;
+}
+/************************************************************************
+FIFDMIN1:
+returns the minimum of two numbers a and b
+************************************************************************/
+double fifdmin1(double a,double b)
+/* a     -     first number */
+/* b     -     second number */
+{
+  if (a < b) return a;
+  else return b;
+}
+/************************************************************************
+FIFDSIGN:
+transfers the sign of the variable "sign" to the variable "mag"
+************************************************************************/
+double fifdsign(double mag,double sign)
+/* mag     -     magnitude */
+/* sign    -     sign to be transfered */
+{
+  if (mag < 0) mag = -mag;
+  if (sign < 0) mag = -mag;
+  return mag;
+
+}
+/************************************************************************
+FIFIDINT:
+Truncates a double precision number to a long integer
+************************************************************************/
+long fifidint(double a)
+/* a - number to be truncated */
+{
+  return (long)(a);
+}
+/************************************************************************
+FIFMOD:
+returns the modulo of a and b
+************************************************************************/
+long fifmod(long a,long b)
+/* a - numerator */
+/* b - denominator */
+{
+  return a % b;
+}
+/************************************************************************
+FTNSTOP:
+Prints msg to standard error and then exits
+************************************************************************/
+void ftnstop(char* msg)
+/* msg - error message */
+{
+  if (msg != NULL) fprintf(stderr,"%s\n",msg);
+  exit(EXIT_FAILURE); /* EXIT_FAILURE from stdlib.h, or use an int */
+}
diff --git a/lib/dcdflib/ipmpar.c b/lib/dcdflib/ipmpar.c
new file mode 100644 (file)
index 0000000..bdf42d9
--- /dev/null
@@ -0,0 +1,97 @@
+#include <config.h>
+
+#include "limits.h"
+
+/* Edited 12/22/97 by Ben Pfaff for PSPP. */
+
+int ipmpar(int*);
+/*
+-----------------------------------------------------------------------
+     IPMPAR PROVIDES THE INTEGER MACHINE CONSTANTS FOR THE COMPUTER
+     THAT IS USED. IT IS ASSUMED THAT THE ARGUMENT I IS AN INTEGER
+     HAVING ONE OF THE VALUES 1-10. IPMPAR(I) HAS THE VALUE ...
+  INTEGERS.
+     ASSUME INTEGERS ARE REPRESENTED IN THE N-DIGIT, BASE-A FORM
+               SIGN ( X(N-1)*A**(N-1) + ... + X(1)*A + X(0) )
+               WHERE 0 .LE. X(I) .LT. A FOR I=0,...,N-1.
+     IPMPAR(1) = A, THE BASE.
+     IPMPAR(2) = N, THE NUMBER OF BASE-A DIGITS.
+     IPMPAR(3) = A**N - 1, THE LARGEST MAGNITUDE.
+  FLOATING-POINT NUMBERS.
+     IT IS ASSUMED THAT THE SINGLE AND DOUBLE PRECISION FLOATING
+     POINT ARITHMETICS HAVE THE SAME BASE, SAY B, AND THAT THE
+     NONZERO NUMBERS ARE REPRESENTED IN THE FORM
+               SIGN (B**E) * (X(1)/B + ... + X(M)/B**M)
+               WHERE X(I) = 0,1,...,B-1 FOR I=1,...,M,
+               X(1) .GE. 1, AND EMIN .LE. E .LE. EMAX.
+     IPMPAR(4) = B, THE BASE.
+  SINGLE-PRECISION
+     IPMPAR(5) = M, THE NUMBER OF BASE-B DIGITS.
+     IPMPAR(6) = EMIN, THE SMALLEST EXPONENT E.
+     IPMPAR(7) = EMAX, THE LARGEST EXPONENT E.
+  DOUBLE-PRECISION
+     IPMPAR(8) = M, THE NUMBER OF BASE-B DIGITS.
+     IPMPAR(9) = EMIN, THE SMALLEST EXPONENT E.
+     IPMPAR(10) = EMAX, THE LARGEST EXPONENT E.
+-----------------------------------------------------------------------
+     TO DEFINE THIS FUNCTION FOR THE COMPUTER BEING USED REMOVE
+     THE COMMENT DELIMITORS FROM THE DEFINITIONS DIRECTLY BELOW THE NAME
+     OF THE MACHINE
+-----------------------------------------------------------------------
+     IPMPAR IS AN ADAPTATION OF THE FUNCTION I1MACH, WRITTEN BY
+     P.A. FOX, A.D. HALL, AND N.L. SCHRYER (BELL LABORATORIES).
+     IPMPAR WAS FORMED BY A.H. MORRIS (NSWC). THE CONSTANTS ARE
+     FROM BELL LABORATORIES, NSWC, AND OTHER SOURCES.
+-----------------------------------------------------------------------
+     .. Scalar Arguments ..
+*/
+int ipmpar(int *i)
+{
+  static int imach[11];
+  static int ipmpar;
+  
+  imach[1] = 2;
+  imach[2] = sizeof (long) * 8 - 1;
+  imach[3] = INT_MAX;
+
+#if FPREP==FPREP_IEEE754
+  imach[4] = 2;
+  imach[5] = 24;
+  imach[6] = -125;
+  imach[7] = 128;
+  imach[8] = 53;
+  imach[9] = -1021;
+  imach[10] = 1024;
+#else
+#error Please define machine-specific constants for your machine.
+#endif
+   
+  ipmpar = imach[*i];
+  return ipmpar;
+}
diff --git a/lib/gmp/COPYING.LIB b/lib/gmp/COPYING.LIB
new file mode 100644 (file)
index 0000000..92b8903
--- /dev/null
@@ -0,0 +1,481 @@
+                 GNU LIBRARY GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1991 Free Software Foundation, Inc.
+                   59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the library GPL.  It is
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Library General Public License, applies to some
+specially designated Free Software Foundation software, and to any
+other libraries whose authors decide to use it.  You can use it for
+your libraries, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if
+you distribute copies of the library, or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link a program with the library, you must provide
+complete object files to the recipients so that they can relink them
+with the library, after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  Our method of protecting your rights has two steps: (1) copyright
+the library, and (2) offer you this license which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  Also, for each distributor's protection, we want to make certain
+that everyone understands that there is no warranty for this free
+library.  If the library is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original
+version, so that any problems introduced by others will not reflect on
+the original authors' reputations.
+\f
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that companies distributing free
+software will individually obtain patent licenses, thus in effect
+transforming the program into proprietary software.  To prevent this,
+we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+  Most GNU software, including some libraries, is covered by the ordinary
+GNU General Public License, which was designed for utility programs.  This
+license, the GNU Library General Public License, applies to certain
+designated libraries.  This license is quite different from the ordinary
+one; be sure to read it in full, and don't assume that anything in it is
+the same as in the ordinary license.
+
+  The reason we have a separate public license for some libraries is that
+they blur the distinction we usually make between modifying or adding to a
+program and simply using it.  Linking a program with a library, without
+changing the library, is in some sense simply using the library, and is
+analogous to running a utility program or application program.  However, in
+a textual and legal sense, the linked executable is a combined work, a
+derivative of the original library, and the ordinary General Public License
+treats it as such.
+
+  Because of this blurred distinction, using the ordinary General
+Public License for libraries did not effectively promote software
+sharing, because most developers did not use the libraries.  We
+concluded that weaker conditions might promote sharing better.
+
+  However, unrestricted linking of non-free programs would deprive the
+users of those programs of all benefit from the free status of the
+libraries themselves.  This Library General Public License is intended to
+permit developers of non-free programs to use free libraries, while
+preserving your freedom as a user of such programs to change the free
+libraries that are incorporated in them.  (We have not seen how to achieve
+this as regards changes in header files, but we have achieved it as regards
+changes in the actual functions of the Library.)  The hope is that this
+will lead to faster development of free libraries.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, while the latter only
+works together with the library.
+
+  Note that it is possible for a library to be covered by the ordinary
+General Public License rather than by this special one.
+\f
+                 GNU LIBRARY GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library which
+contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Library
+General Public License (also called "this License").  Each licensee is
+addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+\f
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+\f
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+\f
+  6. As an exception to the Sections above, you may also compile or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    c) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    d) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the source code distributed need not include anything that is normally
+distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+\f
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+\f
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Library General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+\f
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                           NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public
+    License along with this library; if not, write to the Free
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/lib/gmp/ChangeLog b/lib/gmp/ChangeLog
new file mode 100644 (file)
index 0000000..e398ea6
--- /dev/null
@@ -0,0 +1,29 @@
+Mon Dec 14 11:52:05 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am, mpn/Makefile.am, mpf/Makefile.am: (INCLUDES) Add
+       -I$(top_srcdir)/intl.  Thanks to OKUJI Yoshinori
+       <okuji@kuicr.kyoto-u.ac.jp>.
+
+Thu Nov 19 12:35:13 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Thanks to Hans Olav Eggestad <olav@jordforsk.nlh.no> and others
+       for reporting bug fixed below.
+       
+       * Makefile.am: (libgmp_a_SOURCES) Add mp_clz_tab.c.
+
+       * longlong.h: Define LONGLONG_STANDALONE unconditionally since we
+       don't include architecture-specific directories.
+
+       * mp_clz_tab.c: New file.
+
+Sun Aug  9 11:17:02 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Fixed for renamed file.
+
+       * extract-double.c: Renamed extract-dbl.c.
+
+----------------------------------------------------------------------
+Local Variables:
+mode: change-log
+version-control: never
+End:
diff --git a/lib/gmp/INSTALL b/lib/gmp/INSTALL
new file mode 100644 (file)
index 0000000..7e4ae38
--- /dev/null
@@ -0,0 +1,43 @@
+(Other sections omitted because they're not useful in use along with
+PSPP. -blp)
+
+Known Build Problems
+--------------------
+
+Note that GCC 2.7.2 (as well as 2.6.3) for the RS/6000 and PowerPC can not
+be used to compile GMP, due to a bug in GCC.  If you want to use GCC, you
+need to apply the patch at the end of this file, or use a later version of
+the compiler.
+
+If you are on a Sequent Symmetry, use GAS instead of the system's assembler
+due to the latter's serious bugs.
+
+The system compiler on NeXT is a massacred and old gcc, even if the
+compiler calls itself cc.  This compiler cannot be used to build GMP.  You
+need to get a real gcc, and install that before you compile GMP.  (NeXT
+might have fixed this in newer releases of their system.)
+
+Please report other problems to bug-gmp@prep.ai.mit.edu.
+
+
+Patch to apply to GCC 2.6.3 and 2.7.2:
+
+*** config/rs6000/rs6000.md    Sun Feb 11 08:22:11 1996
+--- config/rs6000/rs6000.md.new        Sun Feb 18 03:33:37 1996
+***************
+*** 920,926 ****
+     (set (match_operand:SI 0 "gpc_reg_operand" "=r")
+       (not:SI (match_dup 1)))]
+    ""
+!   "nor. %0,%2,%1"
+    [(set_attr "type" "compare")])
+  
+  (define_insn ""
+--- 920,926 ----
+     (set (match_operand:SI 0 "gpc_reg_operand" "=r")
+       (not:SI (match_dup 1)))]
+    ""
+!   "nor. %0,%1,%1"
+    [(set_attr "type" "compare")])
+  
+  (define_insn ""
diff --git a/lib/gmp/Makefile.am b/lib/gmp/Makefile.am
new file mode 100644 (file)
index 0000000..b563f67
--- /dev/null
@@ -0,0 +1,11 @@
+## Process this file with automake to produce Makefile.in  -*- makefile -*-
+
+INCLUDES = -I$(srcdir) -I$(top_srcdir) -I$(top_srcdir)/src -I$(top_srcdir)/intl
+
+SUBDIRS = mpn mpf
+
+noinst_LIBRARIES = libgmp.a
+libgmp_a_SOURCES = extract-dbl.c gmp-mparam.h longlong.h gmp-impl.h    \
+gmp.h memory.c mp_clz_tab.c
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/lib/gmp/extract-dbl.c b/lib/gmp/extract-dbl.c
new file mode 100644 (file)
index 0000000..84bd661
--- /dev/null
@@ -0,0 +1,161 @@
+/* __gmp_extract_double -- convert from double to array of mp_limb_t.
+
+Copyright (C) 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+
+#ifdef XDEBUG
+#undef _GMP_IEEE_FLOATS
+#endif
+
+#ifndef _GMP_IEEE_FLOATS
+#define _GMP_IEEE_FLOATS 0
+#endif
+
+#define MP_BASE_AS_DOUBLE (2.0 * ((mp_limb_t) 1 << (BITS_PER_MP_LIMB - 1)))
+
+/* Extract a non-negative double in d.  */
+
+int
+#if __STDC__
+__gmp_extract_double (mp_ptr rp, double d)
+#else
+__gmp_extract_double (rp, d)
+     mp_ptr rp;
+     double d;
+#endif
+{
+  long exp;
+  unsigned sc;
+  mp_limb_t manh, manl;
+
+  /* BUGS
+
+     1. Should handle Inf and NaN in IEEE specific code.
+     2. Handle Inf and NaN also in default code, to avoid hangs.
+     3. Generalize to handle all BITS_PER_MP_LIMB >= 32.
+     4. This lits is incomplete and misspelled.
+   */
+
+  if (d == 0.0)
+    {
+      rp[0] = 0;
+      rp[1] = 0;
+#if BITS_PER_MP_LIMB == 32
+      rp[2] = 0;
+#endif
+      return 0;
+    }
+
+#if _GMP_IEEE_FLOATS
+  {
+    union ieee_double_extract x;
+    x.d = d;
+
+    exp = x.s.exp;
+    sc = (unsigned) (exp + 2) % BITS_PER_MP_LIMB;
+#if BITS_PER_MP_LIMB == 64
+    manl = (((mp_limb_t) 1 << 63)
+           | ((mp_limb_t) x.s.manh << 43) | ((mp_limb_t) x.s.manl << 11));
+#else
+    manh = ((mp_limb_t) 1 << 31) | (x.s.manh << 11) | (x.s.manl >> 21);
+    manl = x.s.manl << 11;
+#endif
+  }
+#else
+  {
+    /* Unknown (or known to be non-IEEE) double format.  */
+    exp = 0;
+    if (d >= 1.0)
+      {
+       if (d * 0.5 == d)
+         abort ();
+
+       while (d >= 32768.0)
+         {
+           d *= (1.0 / 65536.0);
+           exp += 16;
+         }
+       while (d >= 1.0)
+         {
+           d *= 0.5;
+           exp += 1;
+         }
+      }
+    else if (d < 0.5)
+      {
+       while (d < (1.0 / 65536.0))
+         {
+           d *=  65536.0;
+           exp -= 16;
+         }
+       while (d < 0.5)
+         {
+           d *= 2.0;
+           exp -= 1;
+         }
+      }
+
+    sc = (unsigned) exp % BITS_PER_MP_LIMB;
+
+    d *= MP_BASE_AS_DOUBLE;
+#if BITS_PER_MP_LIMB == 64
+    manl = d;
+#else
+    manh = d;
+    manl = (d - manh) * MP_BASE_AS_DOUBLE;
+#endif
+
+    exp += 1022;
+  }
+#endif
+
+  exp = (unsigned) (exp + 1) / BITS_PER_MP_LIMB - 1024 / BITS_PER_MP_LIMB + 1;
+
+#if BITS_PER_MP_LIMB == 64
+  if (sc != 0)
+    {
+      rp[1] = manl >> (BITS_PER_MP_LIMB - sc);
+      rp[0] = manl << sc;
+    }
+  else
+    {
+      rp[1] = manl;
+      rp[0] = 0;
+    }
+#else
+  if (sc != 0)
+    {
+      rp[2] = manh >> (BITS_PER_MP_LIMB - sc);
+      rp[1] = (manl >> (BITS_PER_MP_LIMB - sc)) | (manh << sc);
+      rp[0] = manl << sc;
+    }
+  else
+    {
+      rp[2] = manh;
+      rp[1] = manl;
+      rp[0] = 0;
+    }
+#endif
+
+  return exp;
+}
diff --git a/lib/gmp/gmp-impl.h b/lib/gmp/gmp-impl.h
new file mode 100644 (file)
index 0000000..a838ba6
--- /dev/null
@@ -0,0 +1,374 @@
+/* Include file for internal GNU MP types and definitions.
+
+Copyright (C) 1991, 1993, 1994, 1995, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#if 0 /* PSPP has its own alloca */
+/* When using gcc, make sure to use its builtin alloca.  */
+#if ! defined (alloca) && defined (__GNUC__)
+#define alloca __builtin_alloca
+#define HAVE_ALLOCA
+#endif
+
+/* When using cc, do whatever necessary to allow use of alloca.  For many
+   machines, this means including alloca.h.  IBM's compilers need a #pragma
+   in "each module that needs to use alloca".  */
+#if ! defined (alloca)
+/* We need lots of variants for MIPS, to cover all versions and perversions
+   of OSes for MIPS.  */
+#if defined (__mips) || defined (MIPSEL) || defined (MIPSEB) \
+ || defined (_MIPSEL) || defined (_MIPSEB) || defined (__sgi) \
+ || defined (__alpha) || defined (__sparc) || defined (sparc) \
+ || defined (__ksr__)
+#include <alloca.h>
+#define HAVE_ALLOCA
+#endif
+#if defined (_IBMR2)
+#pragma alloca
+#define HAVE_ALLOCA
+#endif
+#if defined (__DECC)
+#define alloca(x) __ALLOCA(x)
+#define HAVE_ALLOCA
+#endif
+#endif
+#endif /* 0 */
+
+#if ! defined (HAVE_ALLOCA) || USE_STACK_ALLOC
+#include "stack-alloc.h"
+#else
+#define TMP_DECL(m)
+#define TMP_ALLOC(x) alloca(x)
+#define TMP_MARK(m)
+#define TMP_FREE(m)
+#endif
+
+#ifndef NULL
+#define NULL ((void *) 0)
+#endif
+
+#if ! defined (__GNUC__)
+#define inline                 /* Empty */
+#endif
+
+#define ABS(x) (x >= 0 ? x : -x)
+#define MIN(l,o) ((l) < (o) ? (l) : (o))
+#define MAX(h,i) ((h) > (i) ? (h) : (i))
+
+/* Field access macros.  */
+#define SIZ(x) ((x)->_mp_size)
+#define ABSIZ(x) ABS (SIZ (x))
+#define PTR(x) ((x)->_mp_d)
+#define EXP(x) ((x)->_mp_exp)
+#define PREC(x) ((x)->_mp_prec)
+#define ALLOC(x) ((x)->_mp_alloc)
+
+#include "gmp-mparam.h"
+/* #include "longlong.h" */
+
+#if defined (__STDC__)  || defined (__cplusplus)
+void *malloc (size_t);
+void *realloc (void *, size_t);
+void free (void *);
+
+extern void *  (*_mp_allocate_func) (size_t);
+extern void *  (*_mp_reallocate_func) (void *, size_t, size_t);
+extern void    (*_mp_free_func) (void *, size_t);
+
+void *_mp_default_allocate (size_t);
+void *_mp_default_reallocate (void *, size_t, size_t);
+void _mp_default_free (void *, size_t);
+
+#else
+
+#define const                  /* Empty */
+#define signed                 /* Empty */
+
+void *malloc ();
+void *realloc ();
+void free ();
+
+extern void *  (*_mp_allocate_func) ();
+extern void *  (*_mp_reallocate_func) ();
+extern void    (*_mp_free_func) ();
+
+void *_mp_default_allocate ();
+void *_mp_default_reallocate ();
+void _mp_default_free ();
+#endif
+
+/* Copy NLIMBS *limbs* from SRC to DST.  */
+#define MPN_COPY_INCR(DST, SRC, NLIMBS) \
+  do {                                                                 \
+    mp_size_t __i;                                                     \
+    for (__i = 0; __i < (NLIMBS); __i++)                               \
+      (DST)[__i] = (SRC)[__i];                                         \
+  } while (0)
+#define MPN_COPY_DECR(DST, SRC, NLIMBS) \
+  do {                                                                 \
+    mp_size_t __i;                                                     \
+    for (__i = (NLIMBS) - 1; __i >= 0; __i--)                          \
+      (DST)[__i] = (SRC)[__i];                                         \
+  } while (0)
+#define MPN_COPY MPN_COPY_INCR
+
+/* Zero NLIMBS *limbs* AT DST.  */
+#define MPN_ZERO(DST, NLIMBS) \
+  do {                                                                 \
+    mp_size_t __i;                                                     \
+    for (__i = 0; __i < (NLIMBS); __i++)                               \
+      (DST)[__i] = 0;                                                  \
+  } while (0)
+
+#define MPN_NORMALIZE(DST, NLIMBS) \
+  do {                                                                 \
+    while (NLIMBS > 0)                                                 \
+      {                                                                        \
+       if ((DST)[(NLIMBS) - 1] != 0)                                   \
+         break;                                                        \
+       NLIMBS--;                                                       \
+      }                                                                        \
+  } while (0)
+#define MPN_NORMALIZE_NOT_ZERO(DST, NLIMBS) \
+  do {                                                                 \
+    while (1)                                                          \
+      {                                                                        \
+       if ((DST)[(NLIMBS) - 1] != 0)                                   \
+         break;                                                        \
+       NLIMBS--;                                                       \
+      }                                                                        \
+  } while (0)
+
+/* Initialize the MP_INT X with space for NLIMBS limbs.
+   X should be a temporary variable, and it will be automatically
+   cleared out when the running function returns.
+   We use __x here to make it possible to accept both mpz_ptr and mpz_t
+   arguments.  */
+#define MPZ_TMP_INIT(X, NLIMBS) \
+  do {                                                                 \
+    mpz_ptr __x = (X);                                                 \
+    __x->_mp_alloc = (NLIMBS);                                         \
+    __x->_mp_d = (mp_ptr) TMP_ALLOC ((NLIMBS) * BYTES_PER_MP_LIMB);    \
+  } while (0)
+
+#define MPN_MUL_N_RECURSE(prodp, up, vp, size, tspace) \
+  do {                                                                 \
+    if ((size) < KARATSUBA_THRESHOLD)                                  \
+      impn_mul_n_basecase (prodp, up, vp, size);                       \
+    else                                                               \
+      impn_mul_n (prodp, up, vp, size, tspace);                        \
+  } while (0);
+#define MPN_SQR_N_RECURSE(prodp, up, size, tspace) \
+  do {                                                                 \
+    if ((size) < KARATSUBA_THRESHOLD)                                  \
+      impn_sqr_n_basecase (prodp, up, size);                           \
+    else                                                               \
+      impn_sqr_n (prodp, up, size, tspace);                            \
+  } while (0);
+
+/* Structure for conversion between internal binary format and
+   strings in base 2..36.  */
+struct bases
+{
+  /* Number of digits in the conversion base that always fits in an mp_limb_t.
+     For example, for base 10 on a machine where a mp_limb_t has 32 bits this
+     is 9, since 10**9 is the largest number that fits into a mp_limb_t.  */
+  int chars_per_limb;
+
+  /* log(2)/log(conversion_base) */
+  float chars_per_bit_exactly;
+
+  /* base**chars_per_limb, i.e. the biggest number that fits a word, built by
+     factors of base.  Exception: For 2, 4, 8, etc, big_base is log2(base),
+     i.e. the number of bits used to represent each digit in the base.  */
+  mp_limb_t big_base;
+
+  /* A BITS_PER_MP_LIMB bit approximation to 1/big_base, represented as a
+     fixed-point number.  Instead of dividing by big_base an application can
+     choose to multiply by big_base_inverted.  */
+  mp_limb_t big_base_inverted;
+};
+
+extern const struct bases __mp_bases[];
+extern mp_size_t __gmp_default_fp_limb_precision;
+
+/* Divide the two-limb number in (NH,,NL) by D, with DI being the largest
+   limb not larger than (2**(2*BITS_PER_MP_LIMB))/D - (2**BITS_PER_MP_LIMB).
+   If this would yield overflow, DI should be the largest possible number
+   (i.e., only ones).  For correct operation, the most significant bit of D
+   has to be set.  Put the quotient in Q and the remainder in R.  */
+#define udiv_qrnnd_preinv(q, r, nh, nl, d, di) \
+  do {                                                                 \
+    mp_limb_t _q, _ql, _r;                                             \
+    mp_limb_t _xh, _xl;                                                        \
+    umul_ppmm (_q, _ql, (nh), (di));                                   \
+    _q += (nh);                        /* DI is 2**BITS_PER_MP_LIMB too small */\
+    umul_ppmm (_xh, _xl, _q, (d));                                     \
+    sub_ddmmss (_xh, _r, (nh), (nl), _xh, _xl);                                \
+    if (_xh != 0)                                                      \
+      {                                                                        \
+       sub_ddmmss (_xh, _r, _xh, _r, 0, (d));                          \
+       _q += 1;                                                        \
+       if (_xh != 0)                                                   \
+         {                                                             \
+           sub_ddmmss (_xh, _r, _xh, _r, 0, (d));                      \
+           _q += 1;                                                    \
+         }                                                             \
+      }                                                                        \
+    if (_r >= (d))                                                     \
+      {                                                                        \
+       _r -= (d);                                                      \
+       _q += 1;                                                        \
+      }                                                                        \
+    (r) = _r;                                                          \
+    (q) = _q;                                                          \
+  } while (0)
+/* Like udiv_qrnnd_preinv, but for for any value D.  DNORM is D shifted left
+   so that its most significant bit is set.  LGUP is ceil(log2(D)).  */
+#define udiv_qrnnd_preinv2gen(q, r, nh, nl, d, di, dnorm, lgup) \
+  do {                                                                 \
+    mp_limb_t n2, n10, n1, nadj, q1;                                   \
+    mp_limb_t _xh, _xl;                                                        \
+    n2 = ((nh) << (BITS_PER_MP_LIMB - (lgup))) + ((nl) >> 1 >> (l - 1));\
+    n10 = (nl) << (BITS_PER_MP_LIMB - (lgup));                         \
+    n1 = ((mp_limb_signed_t) n10 >> (BITS_PER_MP_LIMB - 1));           \
+    nadj = n10 + (n1 & (dnorm));                                       \
+    umul_ppmm (_xh, _xl, di, n2 - n1);                                 \
+    add_ssaaaa (_xh, _xl, _xh, _xl, 0, nadj);                          \
+    q1 = ~(n2 + _xh);                                                  \
+    umul_ppmm (_xh, _xl, q1, d);                                       \
+    add_ssaaaa (_xh, _xl, _xh, _xl, nh, nl);                           \
+    _xh -= (d);                                                                \
+    (r) = _xl + ((d) & _xh);                                           \
+    (q) = _xh - q1;                                                    \
+  } while (0)
+/* Exactly like udiv_qrnnd_preinv, but branch-free.  It is not clear which
+   version to use.  */
+#define udiv_qrnnd_preinv2norm(q, r, nh, nl, d, di) \
+  do {                                                                 \
+    mp_limb_t n2, n10, n1, nadj, q1;                                   \
+    mp_limb_t _xh, _xl;                                                        \
+    n2 = (nh);                                                         \
+    n10 = (nl);                                                                \
+    n1 = ((mp_limb_signed_t) n10 >> (BITS_PER_MP_LIMB - 1));           \
+    nadj = n10 + (n1 & (d));                                           \
+    umul_ppmm (_xh, _xl, di, n2 - n1);                                 \
+    add_ssaaaa (_xh, _xl, _xh, _xl, 0, nadj);                          \
+    q1 = ~(n2 + _xh);                                                  \
+    umul_ppmm (_xh, _xl, q1, d);                                       \
+    add_ssaaaa (_xh, _xl, _xh, _xl, nh, nl);                           \
+    _xh -= (d);                                                                \
+    (r) = _xl + ((d) & _xh);                                           \
+    (q) = _xh - q1;                                                    \
+  } while (0)
+
+#if defined (__GNUC__)
+/* Define stuff for longlong.h.  */
+typedef unsigned int UQItype   __attribute__ ((mode (QI)));
+typedef         int SItype     __attribute__ ((mode (SI)));
+typedef unsigned int USItype   __attribute__ ((mode (SI)));
+typedef                 int DItype     __attribute__ ((mode (DI)));
+typedef unsigned int UDItype   __attribute__ ((mode (DI)));
+#else
+typedef unsigned char UQItype;
+typedef         long SItype;
+typedef unsigned long USItype;
+#endif
+
+typedef mp_limb_t UWtype;
+typedef unsigned int UHWtype;
+#define W_TYPE_SIZE BITS_PER_MP_LIMB
+
+/* Internal mpn calls */
+#define impn_mul_n_basecase    __MPN(impn_mul_n_basecase)
+#define impn_mul_n             __MPN(impn_mul_n)
+#define impn_sqr_n_basecase    __MPN(impn_sqr_n_basecase)
+#define impn_sqr_n             __MPN(impn_sqr_n)
+
+void impn_mul_n_basecase (mp_ptr prodp, mp_srcptr up, mp_srcptr vp,
+                         mp_size_t size);
+void impn_mul_n (mp_ptr prodp, mp_srcptr up, mp_srcptr vp, mp_size_t size,
+                mp_ptr tspace);
+
+/* Define ieee_double_extract and _GMP_IEEE_FLOATS.  */
+
+#if defined (_LITTLE_ENDIAN) || defined (__LITTLE_ENDIAN__)            \
+ || defined (__alpha)                                                  \
+ || (defined (__arm__) && defined (__ARMWEL__))                                \
+ || defined (__clipper__)                                              \
+ || defined (__cris)                                                   \
+ || defined (__i386__)                                                 \
+ || defined (__i860__)                                                 \
+ || defined (__i960__)                                                 \
+ || defined (MIPSEL) || defined (_MIPSEL)                              \
+ || defined (__ns32000__)                                              \
+ || defined (__WINNT) || defined (_WIN32)
+#define _GMP_IEEE_FLOATS 1
+union ieee_double_extract
+{
+  struct
+    {
+      unsigned int manl:32;
+      unsigned int manh:20;
+      unsigned int exp:11;
+      unsigned int sig:1;
+    } s;
+  double d;
+};
+#else /* Need this as an #else since the tests aren't made exclusive.  */
+#if defined (_BIG_ENDIAN)                                              \
+ || defined (__a29k__) || defined (_AM29K)                             \
+ || defined (__arm__)                                                  \
+ || (defined (__convex__) && defined (_IEEE_FLOAT_))                   \
+ || defined (__i370__) || defined (__mvs__)                            \
+ || defined (__mc68000__) || defined (__mc68020__) || defined (__NeXT__)\
+    || defined(mc68020)                                                        \
+ || defined (__m88000__)                                               \
+ || defined (MIPSEB) || defined (_MIPSEB)                              \
+ || defined (__hppa)                                                   \
+ || defined (__pyr__)                                                  \
+ || defined (__ibm032__)                                               \
+ || defined (_IBMR2) || defined (_ARCH_PPC)                            \
+ || defined (__sh__)                                                   \
+ || defined (__sparc) || defined (sparc)                               \
+ || defined (__we32k__)
+#define _GMP_IEEE_FLOATS 1
+union ieee_double_extract
+{
+  struct
+    {
+      unsigned int sig:1;
+      unsigned int exp:11;
+      unsigned int manh:20;
+      unsigned int manl:32;
+    } s;
+  double d;
+};
+#endif
+#endif
+
+#define MP_BASE_AS_DOUBLE (2.0 * ((mp_limb_t) 1 << (BITS_PER_MP_LIMB - 1)))
+#if BITS_PER_MP_LIMB == 64
+#define LIMBS_PER_DOUBLE 2
+#else
+#define LIMBS_PER_DOUBLE 3
+#endif
+
+double __gmp_scale2 _PROTO ((double, int));
+int __gmp_extract_double _PROTO((mp_ptr, double));
diff --git a/lib/gmp/gmp-mparam.h b/lib/gmp/gmp-mparam.h
new file mode 100644 (file)
index 0000000..f3cbe78
--- /dev/null
@@ -0,0 +1,27 @@
+/* gmp-mparam.h -- Compiler/machine parameter header file.
+
+Copyright (C) 1991, 1993, 1994 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#define BITS_PER_MP_LIMB (8 * SIZEOF_LONG)
+#define BYTES_PER_MP_LIMB SIZEOF_LONG
+#define BITS_PER_LONGINT (8 * SIZEOF_LONG)
+#define BITS_PER_INT (8 * SIZEOF_INT)
+#define BITS_PER_SHORTINT (8 * SIZEOF_SHORT)
+#define BITS_PER_CHAR 8
diff --git a/lib/gmp/gmp.h b/lib/gmp/gmp.h
new file mode 100644 (file)
index 0000000..a1cc1ac
--- /dev/null
@@ -0,0 +1,632 @@
+/* gmp.h -- Definitions for GNU multiple precision functions.
+
+Copyright (C) 1991, 1993, 1994, 1995, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#ifndef __GMP_H__
+
+#ifndef __GNU_MP__
+#define __GNU_MP__ 2
+#define __need_size_t
+#include <stddef.h>
+#undef __need_size_t
+
+#if defined (__STDC__) || defined (__cplusplus)
+#define __gmp_const const
+#else
+#define __gmp_const
+#endif
+
+#if defined (__GNUC__)
+#define __gmp_inline __inline__
+#else
+#define __gmp_inline
+#endif
+
+#ifndef _EXTERN_INLINE
+#ifdef __GNUC__
+#define _EXTERN_INLINE extern __inline__
+#else
+#define _EXTERN_INLINE static
+#endif
+#endif
+
+#ifdef _SHORT_LIMB
+typedef unsigned int           mp_limb_t;
+typedef int                    mp_limb_signed_t;
+#else
+#ifdef _LONG_LONG_LIMB
+typedef unsigned long long int mp_limb_t;
+typedef long long int          mp_limb_signed_t;
+#else
+typedef unsigned long int      mp_limb_t;
+typedef long int               mp_limb_signed_t;
+#endif
+#endif
+
+typedef mp_limb_t *            mp_ptr;
+typedef __gmp_const mp_limb_t *        mp_srcptr;
+typedef long int               mp_size_t;
+typedef long int               mp_exp_t;
+
+#ifndef __MP_SMALL__
+typedef struct
+{
+  int _mp_alloc;               /* Number of *limbs* allocated and pointed
+                                  to by the D field.  */
+  int _mp_size;                        /* abs(SIZE) is the number of limbs
+                                  the last field points to.  If SIZE
+                                  is negative this is a negative
+                                  number.  */
+  mp_limb_t *_mp_d;            /* Pointer to the limbs.  */
+} __mpz_struct;
+#else
+typedef struct
+{
+  short int _mp_alloc;         /* Number of *limbs* allocated and pointed
+                                  to by the D field.  */
+  short int _mp_size;          /* abs(SIZE) is the number of limbs
+                                  the last field points to.  If SIZE
+                                  is negative this is a negative
+                                  number.  */
+  mp_limb_t *_mp_d;            /* Pointer to the limbs.  */
+} __mpz_struct;
+#endif
+#endif /* __GNU_MP__ */
+
+/* User-visible types.  */
+typedef __mpz_struct MP_INT;
+typedef __mpz_struct mpz_t[1];
+
+/* Structure for rational numbers.  Zero is represented as 0/any, i.e.
+   the denominator is ignored.  Negative numbers have the sign in
+   the numerator.  */
+typedef struct
+{
+  __mpz_struct _mp_num;
+  __mpz_struct _mp_den;
+#if 0
+  int _mp_num_alloc;           /* Number of limbs allocated
+                                  for the numerator.  */
+  int _mp_num_size;            /* The absolute value of this field is the
+                                  length of the numerator; the sign is the
+                                  sign of the entire rational number.  */
+  mp_ptr _mp_num;              /* Pointer to the numerator limbs.  */
+  int _mp_den_alloc;           /* Number of limbs allocated
+                                  for the denominator.  */
+  int _mp_den_size;            /* Length of the denominator.  (This field
+                                  should always be positive.) */
+  mp_ptr _mp_den;              /* Pointer to the denominator limbs.  */
+#endif
+} __mpq_struct;
+
+typedef __mpq_struct MP_RAT;
+typedef __mpq_struct mpq_t[1];
+
+typedef struct
+{
+  int _mp_prec;                        /* Max precision, in number of `mp_limb_t's.
+                                  Set by mpf_init and modified by
+                                  mpf_set_prec.  The area pointed to
+                                  by the `d' field contains `prec' + 1
+                                  limbs.  */
+  int _mp_size;                        /* abs(SIZE) is the number of limbs
+                                  the last field points to.  If SIZE
+                                  is negative this is a negative
+                                  number.  */
+  mp_exp_t _mp_exp;            /* Exponent, in the base of `mp_limb_t'.  */
+  mp_limb_t *_mp_d;            /* Pointer to the limbs.  */
+} __mpf_struct;
+
+/* typedef __mpf_struct MP_FLOAT; */
+typedef __mpf_struct mpf_t[1];
+
+/* Types for function declarations in gmp files.  */
+/* ??? Should not pollute user name space with these ??? */
+typedef __gmp_const __mpz_struct *mpz_srcptr;
+typedef __mpz_struct *mpz_ptr;
+typedef __gmp_const __mpf_struct *mpf_srcptr;
+typedef __mpf_struct *mpf_ptr;
+typedef __gmp_const __mpq_struct *mpq_srcptr;
+typedef __mpq_struct *mpq_ptr;
+
+#ifndef _PROTO
+#if defined (__STDC__) || defined (__cplusplus)
+#define _PROTO(x) x
+#else
+#define _PROTO(x) ()
+#endif
+#endif
+
+#ifndef __MPN
+#if defined (__STDC__) || defined (__cplusplus)
+#define __MPN(x) __mpn_##x
+#else
+#define __MPN(x) __mpn_/**/x
+#endif
+#endif
+
+#if defined (FILE) || defined (H_STDIO) || defined (_H_STDIO) \
+ || defined (_STDIO_H) || defined (_STDIO_H_) || defined (__STDIO_H__) \
+ || defined (_STDIO_INCLUDED)
+#define _GMP_H_HAVE_FILE 1
+#endif
+
+void mp_set_memory_functions _PROTO ((void *(*) (size_t),
+                                     void *(*) (void *, size_t, size_t),
+                                     void (*) (void *, size_t)));
+extern __gmp_const int mp_bits_per_limb;
+
+/**************** Integer (i.e. Z) routines.  ****************/
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+void *_mpz_realloc _PROTO ((mpz_ptr, mp_size_t));
+
+void mpz_abs _PROTO ((mpz_ptr, mpz_srcptr));
+void mpz_add _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+void mpz_add_ui _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_and _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+void mpz_array_init _PROTO ((mpz_ptr, mp_size_t, mp_size_t));
+void mpz_cdiv_q _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+unsigned long int mpz_cdiv_q_ui _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_cdiv_qr _PROTO ((mpz_ptr, mpz_ptr, mpz_srcptr, mpz_srcptr));
+unsigned long int mpz_cdiv_qr_ui _PROTO ((mpz_ptr, mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_cdiv_r _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+unsigned long int mpz_cdiv_r_ui _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+unsigned long int mpz_cdiv_ui _PROTO ((mpz_srcptr, unsigned long int));
+void mpz_clear _PROTO ((mpz_ptr));
+void mpz_clrbit _PROTO ((mpz_ptr, unsigned long int));
+int mpz_cmp _PROTO ((mpz_srcptr, mpz_srcptr));
+int mpz_cmp_si _PROTO ((mpz_srcptr, signed long int));
+int mpz_cmp_ui _PROTO ((mpz_srcptr, unsigned long int));
+void mpz_com _PROTO ((mpz_ptr, mpz_srcptr));
+void mpz_divexact _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+void mpz_fac_ui _PROTO ((mpz_ptr, unsigned long int));
+void mpz_fdiv_q _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+void mpz_fdiv_q_2exp _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+unsigned long int mpz_fdiv_q_ui _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_fdiv_qr _PROTO ((mpz_ptr, mpz_ptr, mpz_srcptr, mpz_srcptr));
+unsigned long int mpz_fdiv_qr_ui _PROTO ((mpz_ptr, mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_fdiv_r _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+void mpz_fdiv_r_2exp _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+unsigned long int mpz_fdiv_r_ui _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+unsigned long int mpz_fdiv_ui _PROTO ((mpz_srcptr, unsigned long int));
+void mpz_gcd _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+unsigned long int mpz_gcd_ui _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_gcdext _PROTO ((mpz_ptr, mpz_ptr, mpz_ptr, mpz_srcptr, mpz_srcptr));
+double mpz_get_d _PROTO ((mpz_srcptr));
+/* signed */ long int mpz_get_si _PROTO ((mpz_srcptr));
+char *mpz_get_str _PROTO ((char *, int, mpz_srcptr));
+unsigned long int mpz_get_ui _PROTO ((mpz_srcptr));
+mp_limb_t mpz_getlimbn _PROTO ((mpz_srcptr, mp_size_t));
+unsigned long int mpz_hamdist _PROTO ((mpz_srcptr, mpz_srcptr));
+void mpz_init _PROTO ((mpz_ptr));
+#ifdef _GMP_H_HAVE_FILE
+size_t mpz_inp_binary _PROTO ((mpz_ptr, FILE *));
+size_t mpz_inp_raw _PROTO ((mpz_ptr, FILE *));
+size_t mpz_inp_str _PROTO ((mpz_ptr, FILE *, int));
+#endif
+void mpz_init_set _PROTO ((mpz_ptr, mpz_srcptr));
+void mpz_init_set_d _PROTO ((mpz_ptr, double));
+void mpz_init_set_si _PROTO ((mpz_ptr, signed long int));
+int mpz_init_set_str _PROTO ((mpz_ptr, const char *, int));
+void mpz_init_set_ui _PROTO ((mpz_ptr, unsigned long int));
+int mpz_invert _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+void mpz_ior _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+int mpz_jacobi _PROTO ((mpz_srcptr, mpz_srcptr));
+int mpz_legendre _PROTO ((mpz_srcptr, mpz_srcptr));
+void mpz_mod _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+void mpz_mul _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+void mpz_mul_2exp _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_mul_ui _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_neg _PROTO ((mpz_ptr, mpz_srcptr));
+#ifdef _GMP_H_HAVE_FILE
+size_t mpz_out_binary _PROTO ((FILE *, mpz_srcptr));
+size_t mpz_out_raw _PROTO ((FILE *, mpz_srcptr));
+size_t mpz_out_str _PROTO ((FILE *, int, mpz_srcptr));
+#endif
+int mpz_perfect_square_p _PROTO ((mpz_srcptr));
+unsigned long int mpz_popcount _PROTO ((mpz_srcptr));
+void mpz_pow_ui _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_powm _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr, mpz_srcptr));
+void mpz_powm_ui _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int, mpz_srcptr));
+int mpz_probab_prime_p _PROTO ((mpz_srcptr, int));
+void mpz_random _PROTO ((mpz_ptr, mp_size_t));
+void mpz_random2 _PROTO ((mpz_ptr, mp_size_t));
+unsigned long int mpz_scan0 _PROTO ((mpz_srcptr, unsigned long int));
+unsigned long int mpz_scan1 _PROTO ((mpz_srcptr, unsigned long int));
+void mpz_set _PROTO ((mpz_ptr, mpz_srcptr));
+void mpz_set_d _PROTO ((mpz_ptr, double));
+void mpz_set_f _PROTO ((mpz_ptr, mpf_srcptr));
+void mpz_set_q _PROTO ((mpz_ptr, mpq_srcptr));
+void mpz_set_si _PROTO ((mpz_ptr, signed long int));
+int mpz_set_str _PROTO ((mpz_ptr, const char *, int));
+void mpz_set_ui _PROTO ((mpz_ptr, unsigned long int));
+void mpz_setbit _PROTO ((mpz_ptr, unsigned long int));
+size_t mpz_size _PROTO ((mpz_srcptr));
+size_t mpz_sizeinbase _PROTO ((mpz_srcptr, int));
+void mpz_sqrt _PROTO ((mpz_ptr, mpz_srcptr));
+void mpz_sqrtrem _PROTO ((mpz_ptr, mpz_ptr, mpz_srcptr));
+void mpz_sub _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+void mpz_sub_ui _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_tdiv_q _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+void mpz_tdiv_q_2exp _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_tdiv_q_ui _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_tdiv_qr _PROTO ((mpz_ptr, mpz_ptr, mpz_srcptr, mpz_srcptr));
+void mpz_tdiv_qr_ui _PROTO ((mpz_ptr, mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_tdiv_r _PROTO ((mpz_ptr, mpz_srcptr, mpz_srcptr));
+void mpz_tdiv_r_2exp _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_tdiv_r_ui _PROTO ((mpz_ptr, mpz_srcptr, unsigned long int));
+void mpz_ui_pow_ui _PROTO ((mpz_ptr, unsigned long int, unsigned long int));
+
+/**************** Rational (i.e. Q) routines.  ****************/
+
+void mpq_init _PROTO ((mpq_ptr));
+void mpq_clear _PROTO ((mpq_ptr));
+void mpq_set _PROTO ((mpq_ptr, mpq_srcptr));
+void mpq_set_ui _PROTO ((mpq_ptr, unsigned long int, unsigned long int));
+void mpq_set_si _PROTO ((mpq_ptr, signed long int, unsigned long int));
+void mpq_set_z _PROTO ((mpq_ptr, mpz_srcptr));
+void mpq_add _PROTO ((mpq_ptr, mpq_srcptr, mpq_srcptr));
+void mpq_sub _PROTO ((mpq_ptr, mpq_srcptr, mpq_srcptr));
+void mpq_mul _PROTO ((mpq_ptr, mpq_srcptr, mpq_srcptr));
+void mpq_div _PROTO ((mpq_ptr, mpq_srcptr, mpq_srcptr));
+void mpq_neg _PROTO ((mpq_ptr, mpq_srcptr));
+int mpq_cmp _PROTO ((mpq_srcptr, mpq_srcptr));
+int mpq_cmp_ui _PROTO ((mpq_srcptr, unsigned long int, unsigned long int));
+int mpq_equal _PROTO ((mpq_srcptr, mpq_srcptr));
+void mpq_inv _PROTO ((mpq_ptr, mpq_srcptr));
+void mpq_set_num _PROTO ((mpq_ptr, mpz_srcptr));
+void mpq_set_den _PROTO ((mpq_ptr, mpz_srcptr));
+void mpq_get_num _PROTO ((mpz_ptr, mpq_srcptr));
+void mpq_get_den _PROTO ((mpz_ptr, mpq_srcptr));
+double mpq_get_d _PROTO ((mpq_srcptr));
+void mpq_canonicalize _PROTO ((mpq_ptr));
+
+/**************** Float (i.e. F) routines.  ****************/
+
+void mpf_abs _PROTO ((mpf_ptr, mpf_srcptr));
+void mpf_add _PROTO ((mpf_ptr, mpf_srcptr, mpf_srcptr));
+void mpf_add_ui _PROTO ((mpf_ptr, mpf_srcptr, unsigned long int));
+void mpf_clear _PROTO ((mpf_ptr));
+int mpf_cmp _PROTO ((mpf_srcptr, mpf_srcptr));
+int mpf_cmp_si _PROTO ((mpf_srcptr, signed long int));
+int mpf_cmp_ui _PROTO ((mpf_srcptr, unsigned long int));
+void mpf_div _PROTO ((mpf_ptr, mpf_srcptr, mpf_srcptr));
+void mpf_div_2exp _PROTO ((mpf_ptr, mpf_srcptr, unsigned long int));
+void mpf_div_ui _PROTO ((mpf_ptr, mpf_srcptr, unsigned long int));
+void mpf_dump _PROTO ((mpf_srcptr));
+int mpf_eq _PROTO ((mpf_srcptr, mpf_srcptr, unsigned long int));
+double mpf_get_d _PROTO ((mpf_srcptr));
+unsigned long int mpf_get_prec _PROTO ((mpf_srcptr));
+char *mpf_get_str _PROTO ((char *, mp_exp_t *, int, size_t, mpf_srcptr));
+void mpf_init _PROTO ((mpf_ptr));
+void mpf_init2 _PROTO ((mpf_ptr, unsigned long int));
+#ifdef _GMP_H_HAVE_FILE
+size_t mpf_inp_str _PROTO ((mpf_ptr, FILE *, int));
+#endif
+void mpf_init_set _PROTO ((mpf_ptr, mpf_srcptr));
+void mpf_init_set_d _PROTO ((mpf_ptr, double));
+void mpf_init_set_si _PROTO ((mpf_ptr, signed long int));
+int mpf_init_set_str _PROTO ((mpf_ptr, const char *, int));
+void mpf_init_set_ui _PROTO ((mpf_ptr, unsigned long int));
+void mpf_mul _PROTO ((mpf_ptr, mpf_srcptr, mpf_srcptr));
+void mpf_mul_2exp _PROTO ((mpf_ptr, mpf_srcptr, unsigned long int));
+void mpf_mul_ui _PROTO ((mpf_ptr, mpf_srcptr, unsigned long int));
+void mpf_neg _PROTO ((mpf_ptr, mpf_srcptr));
+#ifdef _GMP_H_HAVE_FILE
+size_t mpf_out_str _PROTO ((FILE *, int, size_t, mpf_srcptr));
+#endif
+void mpf_random2 _PROTO ((mpf_ptr, mp_size_t, mp_exp_t));
+void mpf_reldiff _PROTO ((mpf_ptr, mpf_srcptr, mpf_srcptr));
+void mpf_set _PROTO ((mpf_ptr, mpf_srcptr));
+void mpf_set_d _PROTO ((mpf_ptr, double));
+void mpf_set_default_prec _PROTO ((unsigned long int));
+void mpf_set_prec _PROTO ((mpf_ptr, unsigned long int));
+void mpf_set_prec_raw _PROTO ((mpf_ptr, unsigned long int));
+void mpf_set_q _PROTO ((mpf_ptr, mpq_srcptr));
+void mpf_set_si _PROTO ((mpf_ptr, signed long int));
+int mpf_set_str _PROTO ((mpf_ptr, const char *, int));
+void mpf_set_ui _PROTO ((mpf_ptr, unsigned long int));
+void mpf_set_z _PROTO ((mpf_ptr, mpz_srcptr));
+size_t mpf_size _PROTO ((mpf_srcptr));
+void mpf_sqrt _PROTO ((mpf_ptr, mpf_srcptr));
+void mpf_sqrt_ui _PROTO ((mpf_ptr, unsigned long int));
+void mpf_sub _PROTO ((mpf_ptr, mpf_srcptr, mpf_srcptr));
+void mpf_sub_ui _PROTO ((mpf_ptr, mpf_srcptr, unsigned long int));
+void mpf_ui_div _PROTO ((mpf_ptr, unsigned long int, mpf_srcptr));
+void mpf_ui_sub _PROTO ((mpf_ptr, unsigned long int, mpf_srcptr));
+#if defined (__cplusplus)
+}
+#endif
+/************ Low level positive-integer (i.e. N) routines.  ************/
+
+/* This is ugly, but we need to make usr calls reach the prefixed function.  */
+#define mpn_add                        __MPN(add)
+#define mpn_add_1              __MPN(add_1)
+#define mpn_add_n              __MPN(add_n)
+#define mpn_addmul_1           __MPN(addmul_1)
+#define mpn_bdivmod            __MPN(bdivmod)
+#define mpn_cmp                        __MPN(cmp)
+#define mpn_divmod_1           __MPN(divmod_1)
+#define mpn_divrem             __MPN(divrem)
+#define mpn_divrem_1           __MPN(divrem_1)
+#define mpn_dump               __MPN(dump)
+#define mpn_gcd                        __MPN(gcd)
+#define mpn_gcd_1              __MPN(gcd_1)
+#define mpn_gcdext             __MPN(gcdext)
+#define mpn_get_str            __MPN(get_str)
+#define mpn_hamdist            __MPN(hamdist)
+#define mpn_lshift             __MPN(lshift)
+#define mpn_mod_1              __MPN(mod_1)
+#define mpn_mul                        __MPN(mul)
+#define mpn_mul_1              __MPN(mul_1)
+#define mpn_mul_n              __MPN(mul_n)
+#define mpn_perfect_square_p   __MPN(perfect_square_p)
+#define mpn_popcount           __MPN(popcount)
+#define mpn_preinv_mod_1       __MPN(preinv_mod_1)
+#define mpn_random2            __MPN(random2)
+#define mpn_rshift             __MPN(rshift)
+#define mpn_scan0              __MPN(scan0)
+#define mpn_scan1              __MPN(scan1)
+#define mpn_set_str            __MPN(set_str)
+#define mpn_sqrtrem            __MPN(sqrtrem)
+#define mpn_sub                        __MPN(sub)
+#define mpn_sub_1              __MPN(sub_1)
+#define mpn_sub_n              __MPN(sub_n)
+#define mpn_submul_1           __MPN(submul_1)
+#define mpn_udiv_w_sdiv                __MPN(udiv_w_sdiv)
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+mp_limb_t mpn_add _PROTO ((mp_ptr, mp_srcptr, mp_size_t, mp_srcptr,mp_size_t));
+mp_limb_t mpn_add_1 _PROTO ((mp_ptr, mp_srcptr, mp_size_t, mp_limb_t));
+mp_limb_t mpn_add_n _PROTO ((mp_ptr, mp_srcptr, mp_srcptr, mp_size_t));
+mp_limb_t mpn_addmul_1 _PROTO ((mp_ptr, mp_srcptr, mp_size_t, mp_limb_t));
+mp_limb_t mpn_bdivmod _PROTO ((mp_ptr, mp_ptr, mp_size_t, mp_srcptr, mp_size_t, unsigned long int));
+int mpn_cmp _PROTO ((mp_srcptr, mp_srcptr, mp_size_t));
+mp_limb_t mpn_divmod_1 _PROTO ((mp_ptr, mp_srcptr, mp_size_t, mp_limb_t));
+mp_limb_t mpn_divrem _PROTO ((mp_ptr, mp_size_t, mp_ptr, mp_size_t, mp_srcptr, mp_size_t));
+mp_limb_t mpn_divrem_1 _PROTO ((mp_ptr, mp_size_t, mp_srcptr, mp_size_t, mp_limb_t));
+void mpn_dump _PROTO ((mp_srcptr, mp_size_t));
+mp_size_t mpn_gcd _PROTO ((mp_ptr, mp_ptr, mp_size_t, mp_ptr, mp_size_t));
+mp_limb_t mpn_gcd_1 _PROTO ((mp_srcptr, mp_size_t, mp_limb_t));
+mp_size_t mpn_gcdext _PROTO ((mp_ptr, mp_ptr, mp_ptr, mp_size_t, mp_ptr, mp_size_t));
+size_t mpn_get_str _PROTO ((unsigned char *, int, mp_ptr, mp_size_t));
+unsigned long int mpn_hamdist _PROTO ((mp_srcptr, mp_srcptr, mp_size_t));
+mp_limb_t mpn_lshift _PROTO ((mp_ptr, mp_srcptr, mp_size_t, unsigned int));
+mp_limb_t mpn_mod_1 _PROTO ((mp_srcptr, mp_size_t, mp_limb_t));
+mp_limb_t mpn_mul _PROTO ((mp_ptr, mp_srcptr, mp_size_t, mp_srcptr, mp_size_t));
+mp_limb_t mpn_mul_1 _PROTO ((mp_ptr, mp_srcptr, mp_size_t, mp_limb_t));
+void mpn_mul_n _PROTO ((mp_ptr, mp_srcptr, mp_srcptr, mp_size_t));
+int mpn_perfect_square_p _PROTO ((mp_srcptr, mp_size_t));
+unsigned long int mpn_popcount _PROTO ((mp_srcptr, mp_size_t));
+mp_limb_t mpn_preinv_mod_1 _PROTO ((mp_srcptr, mp_size_t, mp_limb_t, mp_limb_t));
+void mpn_random2 _PROTO ((mp_ptr, mp_size_t));
+mp_limb_t mpn_rshift _PROTO ((mp_ptr, mp_srcptr, mp_size_t, unsigned int));
+unsigned long int mpn_scan0 _PROTO ((mp_srcptr, unsigned long int));
+unsigned long int mpn_scan1 _PROTO ((mp_srcptr, unsigned long int));
+mp_size_t mpn_set_str _PROTO ((mp_ptr, const unsigned char *, size_t, int));
+mp_size_t mpn_sqrtrem _PROTO ((mp_ptr, mp_ptr, mp_srcptr, mp_size_t));
+mp_limb_t mpn_sub _PROTO ((mp_ptr, mp_srcptr, mp_size_t, mp_srcptr,mp_size_t));
+mp_limb_t mpn_sub_1 _PROTO ((mp_ptr, mp_srcptr, mp_size_t, mp_limb_t));
+mp_limb_t mpn_sub_n _PROTO ((mp_ptr, mp_srcptr, mp_srcptr, mp_size_t));
+mp_limb_t mpn_submul_1 _PROTO ((mp_ptr, mp_srcptr, mp_size_t, mp_limb_t));
+#if defined (__cplusplus)
+}
+#endif
+
+#if defined (__GNUC__) || defined (_FORCE_INLINES)
+_EXTERN_INLINE mp_limb_t
+#if defined (__STDC__) || defined (__cplusplus)
+mpn_add_1 (register mp_ptr res_ptr,
+          register mp_srcptr s1_ptr,
+          register mp_size_t s1_size,
+          register mp_limb_t s2_limb)
+#else
+mpn_add_1 (res_ptr, s1_ptr, s1_size, s2_limb)
+     register mp_ptr res_ptr;
+     register mp_srcptr s1_ptr;
+     register mp_size_t s1_size;
+     register mp_limb_t s2_limb;
+#endif
+{
+  register mp_limb_t x;
+
+  x = *s1_ptr++;
+  s2_limb = x + s2_limb;
+  *res_ptr++ = s2_limb;
+  if (s2_limb < x)
+    {
+      while (--s1_size != 0)
+       {
+         x = *s1_ptr++ + 1;
+         *res_ptr++ = x;
+         if (x != 0)
+           goto fin;
+       }
+
+      return 1;
+    }
+
+ fin:
+  if (res_ptr != s1_ptr)
+    {
+      mp_size_t i;
+      for (i = 0; i < s1_size - 1; i++)
+       res_ptr[i] = s1_ptr[i];
+    }
+  return 0;
+}
+
+_EXTERN_INLINE mp_limb_t
+#if defined (__STDC__) || defined (__cplusplus)
+mpn_add (register mp_ptr res_ptr,
+        register mp_srcptr s1_ptr,
+        register mp_size_t s1_size,
+        register mp_srcptr s2_ptr,
+        register mp_size_t s2_size)
+#else
+mpn_add (res_ptr, s1_ptr, s1_size, s2_ptr, s2_size)
+     register mp_ptr res_ptr;
+     register mp_srcptr s1_ptr;
+     register mp_size_t s1_size;
+     register mp_srcptr s2_ptr;
+     register mp_size_t s2_size;
+#endif
+{
+  mp_limb_t cy_limb = 0;
+
+  if (s2_size != 0)
+    cy_limb = mpn_add_n (res_ptr, s1_ptr, s2_ptr, s2_size);
+
+  if (s1_size - s2_size != 0)
+    cy_limb = mpn_add_1 (res_ptr + s2_size,
+                        s1_ptr + s2_size,
+                        s1_size - s2_size,
+                        cy_limb);
+  return cy_limb;
+}
+
+_EXTERN_INLINE mp_limb_t
+#if defined (__STDC__) || defined (__cplusplus)
+mpn_sub_1 (register mp_ptr res_ptr,
+          register mp_srcptr s1_ptr,
+          register mp_size_t s1_size,
+          register mp_limb_t s2_limb)
+#else
+mpn_sub_1 (res_ptr, s1_ptr, s1_size, s2_limb)
+     register mp_ptr res_ptr;
+     register mp_srcptr s1_ptr;
+     register mp_size_t s1_size;
+     register mp_limb_t s2_limb;
+#endif
+{
+  register mp_limb_t x;
+
+  x = *s1_ptr++;
+  s2_limb = x - s2_limb;
+  *res_ptr++ = s2_limb;
+  if (s2_limb > x)
+    {
+      while (--s1_size != 0)
+       {
+         x = *s1_ptr++;
+         *res_ptr++ = x - 1;
+         if (x != 0)
+           goto fin;
+       }
+
+      return 1;
+    }
+
+ fin:
+  if (res_ptr != s1_ptr)
+    {
+      mp_size_t i;
+      for (i = 0; i < s1_size - 1; i++)
+       res_ptr[i] = s1_ptr[i];
+    }
+  return 0;
+}
+
+_EXTERN_INLINE mp_limb_t
+#if defined (__STDC__) || defined (__cplusplus)
+mpn_sub (register mp_ptr res_ptr,
+        register mp_srcptr s1_ptr,
+        register mp_size_t s1_size,
+        register mp_srcptr s2_ptr,
+        register mp_size_t s2_size)
+#else
+mpn_sub (res_ptr, s1_ptr, s1_size, s2_ptr, s2_size)
+     register mp_ptr res_ptr;
+     register mp_srcptr s1_ptr;
+     register mp_size_t s1_size;
+     register mp_srcptr s2_ptr;
+     register mp_size_t s2_size;
+#endif
+{
+  mp_limb_t cy_limb = 0;
+
+  if (s2_size != 0)
+    cy_limb = mpn_sub_n (res_ptr, s1_ptr, s2_ptr, s2_size);
+
+  if (s1_size - s2_size != 0)
+    cy_limb = mpn_sub_1 (res_ptr + s2_size,
+                        s1_ptr + s2_size,
+                        s1_size - s2_size,
+                        cy_limb);
+  return cy_limb;
+}
+#endif /* __GNUC__ */
+
+/* Allow faster testing for negative, zero, and positive.  */
+#define mpz_sgn(Z) ((Z)->_mp_size < 0 ? -1 : (Z)->_mp_size > 0)
+#define mpf_sgn(F) ((F)->_mp_size < 0 ? -1 : (F)->_mp_size > 0)
+#define mpq_sgn(Q) ((Q)->_mp_num._mp_size < 0 ? -1 : (Q)->_mp_num._mp_size > 0)
+
+/* Allow direct user access to numerator and denominator of a mpq_t object.  */
+#define mpq_numref(Q) (&((Q)->_mp_num))
+#define mpq_denref(Q) (&((Q)->_mp_den))
+
+/* When using GCC, optimize certain common comparisons.  */
+#if defined (__GNUC__)
+#define mpz_cmp_ui(Z,UI) \
+  (__builtin_constant_p (UI) && (UI) == 0                              \
+   ? mpz_sgn (Z) : mpz_cmp_ui (Z,UI))
+#define mpz_cmp_si(Z,UI) \
+  (__builtin_constant_p (UI) && (UI) == 0 ? mpz_sgn (Z)                        \
+   : __builtin_constant_p (UI) && (UI) > 0 ? mpz_cmp_ui (Z,UI)         \
+   : mpz_cmp_si (Z,UI))
+#define mpq_cmp_ui(Q,NUI,DUI) \
+  (__builtin_constant_p (NUI) && (NUI) == 0                            \
+   ? mpq_sgn (Q) : mpq_cmp_ui (Q,NUI,DUI))
+#endif
+
+#define mpn_divmod(qp,np,nsize,dp,dsize) mpn_divrem (qp,0,np,nsize,dp,dsize)
+#if 0
+#define mpn_divmod_1(qp,np,nsize,dlimb) mpn_divrem_1 (qp,0,np,nsize,dlimb)
+#endif
+
+/* Compatibility with GMP 1.  */
+#define mpz_mdiv       mpz_fdiv_q
+#define mpz_mdivmod    mpz_fdiv_qr
+#define mpz_mmod       mpz_fdiv_r
+#define mpz_mdiv_ui    mpz_fdiv_q_ui
+#define mpz_mdivmod_ui(q,r,n,d) \
+  ((r == 0) ? mpz_fdiv_q_ui (q,n,d) : mpz_fdiv_qr_ui (q,r,n,d))
+#define mpz_mmod_ui(r,n,d) \
+  ((r == 0) ? mpz_fdiv_ui (n,d) : mpz_fdiv_r_ui (r,n,d))
+
+/* Useful synonyms, but not quite compatible with GMP 1.  */
+#define mpz_div                mpz_fdiv_q
+#define mpz_divmod     mpz_fdiv_qr
+#define mpz_div_ui     mpz_fdiv_q_ui
+#define mpz_divmod_ui  mpz_fdiv_qr_ui
+#define mpz_mod_ui     mpz_fdiv_r_ui
+#define mpz_div_2exp   mpz_fdiv_q_2exp
+#define mpz_mod_2exp   mpz_fdiv_r_2exp
+
+#define __GNU_MP_VERSION 2
+#define __GNU_MP_VERSION_MINOR 0
+#define __GMP_H__
+#endif /* __GMP_H__ */
diff --git a/lib/gmp/longlong.h b/lib/gmp/longlong.h
new file mode 100644 (file)
index 0000000..e9c2521
--- /dev/null
@@ -0,0 +1,1410 @@
+/* longlong.h -- definitions for mixed size 32/64 bit arithmetic.
+
+Copyright (C) 1991, 1992, 1993, 1994, 1996 Free Software Foundation, Inc.
+
+This file is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This file is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with this file; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#define LONGLONG_STANDALONE 1 /* blp 1998/10/29 */
+
+/* You have to define the following before including this file:
+
+   UWtype -- An unsigned type, default type for operations (typically a "word")
+   UHWtype -- An unsigned type, at least half the size of UWtype.
+   UDWtype -- An unsigned type, at least twice as large a UWtype
+   W_TYPE_SIZE -- size in bits of UWtype
+
+   SItype, USItype -- Signed and unsigned 32 bit types.
+   DItype, UDItype -- Signed and unsigned 64 bit types.
+
+   On a 32 bit machine UWtype should typically be USItype;
+   on a 64 bit machine, UWtype should typically be UDItype.
+*/
+
+#define __BITS4 (W_TYPE_SIZE / 4)
+#define __ll_B ((UWtype) 1 << (W_TYPE_SIZE / 2))
+#define __ll_lowpart(t) ((UWtype) (t) & (__ll_B - 1))
+#define __ll_highpart(t) ((UWtype) (t) >> (W_TYPE_SIZE / 2))
+
+/* This is used to make sure no undesirable sharing between different libraries
+   that use this file takes place.  */
+#ifndef __MPN
+#define __MPN(x) __##x
+#endif
+
+/* Define auxiliary asm macros.
+
+   1) umul_ppmm(high_prod, low_prod, multipler, multiplicand) multiplies two
+   UWtype integers MULTIPLER and MULTIPLICAND, and generates a two UWtype
+   word product in HIGH_PROD and LOW_PROD.
+
+   2) __umulsidi3(a,b) multiplies two UWtype integers A and B, and returns a
+   UDWtype product.  This is just a variant of umul_ppmm.
+
+   3) udiv_qrnnd(quotient, remainder, high_numerator, low_numerator,
+   denominator) divides a UDWtype, composed by the UWtype integers
+   HIGH_NUMERATOR and LOW_NUMERATOR, by DENOMINATOR and places the quotient
+   in QUOTIENT and the remainder in REMAINDER.  HIGH_NUMERATOR must be less
+   than DENOMINATOR for correct operation.  If, in addition, the most
+   significant bit of DENOMINATOR must be 1, then the pre-processor symbol
+   UDIV_NEEDS_NORMALIZATION is defined to 1.
+
+   4) sdiv_qrnnd(quotient, remainder, high_numerator, low_numerator,
+   denominator).  Like udiv_qrnnd but the numbers are signed.  The quotient
+   is rounded towards 0.
+
+   5) count_leading_zeros(count, x) counts the number of zero-bits from the
+   msb to the first non-zero bit in the UWtype X.  This is the number of
+   steps X needs to be shifted left to set the msb.  Undefined for X == 0,
+   unless the symbol COUNT_LEADING_ZEROS_0 is defined to some value.
+
+   6) count_trailing_zeros(count, x) like count_leading_zeros, but counts
+   from the least significant end.
+
+   7) add_ssaaaa(high_sum, low_sum, high_addend_1, low_addend_1,
+   high_addend_2, low_addend_2) adds two UWtype integers, composed by
+   HIGH_ADDEND_1 and LOW_ADDEND_1, and HIGH_ADDEND_2 and LOW_ADDEND_2
+   respectively.  The result is placed in HIGH_SUM and LOW_SUM.  Overflow
+   (i.e. carry out) is not stored anywhere, and is lost.
+
+   8) sub_ddmmss(high_difference, low_difference, high_minuend, low_minuend,
+   high_subtrahend, low_subtrahend) subtracts two two-word UWtype integers,
+   composed by HIGH_MINUEND_1 and LOW_MINUEND_1, and HIGH_SUBTRAHEND_2 and
+   LOW_SUBTRAHEND_2 respectively.  The result is placed in HIGH_DIFFERENCE
+   and LOW_DIFFERENCE.  Overflow (i.e. carry out) is not stored anywhere,
+   and is lost.
+
+   If any of these macros are left undefined for a particular CPU,
+   C macros are used.  */
+
+/* The CPUs come in alphabetical order below.
+
+   Please add support for more CPUs here, or improve the current support
+   for the CPUs below!  */
+
+#if defined (__CHECKER__)
+#define NO_ASM
+#endif
+
+#if defined (__GNUC__) && !defined (NO_ASM)
+
+/* We sometimes need to clobber "cc" with gcc2, but that would not be
+   understood by gcc1.  Use cpp to avoid major code duplication.  */
+#if __GNUC__ < 2
+#define __CLOBBER_CC
+#define __AND_CLOBBER_CC
+#else /* __GNUC__ >= 2 */
+#define __CLOBBER_CC : "cc"
+#define __AND_CLOBBER_CC , "cc"
+#endif /* __GNUC__ < 2 */
+
+#if (defined (__a29k__) || defined (_AM29K)) && W_TYPE_SIZE == 32
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  __asm__ ("add %1,%4,%5
+       addc %0,%2,%3"                                                  \
+          : "=r" ((USItype)(sh)),                                      \
+           "=&r" ((USItype)(sl))                                       \
+          : "%r" ((USItype)(ah)),                                      \
+            "rI" ((USItype)(bh)),                                      \
+            "%r" ((USItype)(al)),                                      \
+            "rI" ((USItype)(bl)))
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  __asm__ ("sub %1,%4,%5
+       subc %0,%2,%3"                                                  \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "r" ((USItype)(ah)),                                       \
+            "rI" ((USItype)(bh)),                                      \
+            "r" ((USItype)(al)),                                       \
+            "rI" ((USItype)(bl)))
+#define umul_ppmm(xh, xl, m0, m1) \
+  do {                                                                 \
+    USItype __m0 = (m0), __m1 = (m1);                                  \
+    __asm__ ("multiplu %0,%1,%2"                                       \
+            : "=r" ((USItype)(xl))                                     \
+            : "r" (__m0),                                              \
+              "r" (__m1));                                             \
+    __asm__ ("multmu %0,%1,%2"                                         \
+            : "=r" ((USItype)(xh))                                     \
+            : "r" (__m0),                                              \
+              "r" (__m1));                                             \
+  } while (0)
+#define udiv_qrnnd(q, r, n1, n0, d) \
+  __asm__ ("dividu %0,%3,%4"                                           \
+          : "=r" ((USItype)(q)),                                       \
+            "=q" ((USItype)(r))                                        \
+          : "1" ((USItype)(n1)),                                       \
+            "r" ((USItype)(n0)),                                       \
+            "r" ((USItype)(d)))
+#define count_leading_zeros(count, x) \
+    __asm__ ("clz %0,%1"                                               \
+            : "=r" ((USItype)(count))                                  \
+            : "r" ((USItype)(x)))
+#define COUNT_LEADING_ZEROS_0 32
+#endif /* __a29k__ */
+
+#if defined (__alpha) && W_TYPE_SIZE == 64
+#define umul_ppmm(ph, pl, m0, m1) \
+  do {                                                                 \
+    UDItype __m0 = (m0), __m1 = (m1);                                  \
+    __asm__ ("umulh %r1,%2,%0"                                         \
+            : "=r" ((UDItype) ph)                                      \
+            : "%rJ" (__m0),                                            \
+              "rI" (__m1));                                            \
+    (pl) = __m0 * __m1;                                                        \
+  } while (0)
+#define UMUL_TIME 46
+#ifndef LONGLONG_STANDALONE
+#define udiv_qrnnd(q, r, n1, n0, d) \
+  do { UDItype __r;                                                    \
+    (q) = __udiv_qrnnd (&__r, (n1), (n0), (d));                                \
+    (r) = __r;                                                         \
+  } while (0)
+extern UDItype __udiv_qrnnd ();
+#define UDIV_TIME 220
+#endif /* LONGLONG_STANDALONE */
+#endif /* __alpha */
+
+#if defined (__arm__) && W_TYPE_SIZE == 32
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  __asm__ ("adds       %1, %4, %5
+       adc     %0, %2, %3"                                             \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "%r" ((USItype)(ah)),                                      \
+            "rI" ((USItype)(bh)),                                      \
+            "%r" ((USItype)(al)),                                      \
+            "rI" ((USItype)(bl)))
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  __asm__ ("subs       %1, %4, %5
+       sbc     %0, %2, %3"                                             \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "r" ((USItype)(ah)),                                       \
+            "rI" ((USItype)(bh)),                                      \
+            "r" ((USItype)(al)),                                       \
+            "rI" ((USItype)(bl)))
+#define umul_ppmm(xh, xl, a, b) \
+  __asm__ ("%@ Inlined umul_ppmm
+       mov     %|r0, %2, lsr #16
+       mov     %|r2, %3, lsr #16
+       bic     %|r1, %2, %|r0, lsl #16
+       bic     %|r2, %3, %|r2, lsl #16
+       mul     %1, %|r1, %|r2
+       mul     %|r2, %|r0, %|r2
+       mul     %|r1, %0, %|r1
+       mul     %0, %|r0, %0
+       adds    %|r1, %|r2, %|r1
+       addcs   %0, %0, #65536
+       adds    %1, %1, %|r1, lsl #16
+       adc     %0, %0, %|r1, lsr #16"                                  \
+          : "=&r" ((USItype)(xh)),                                     \
+            "=r" ((USItype)(xl))                                       \
+          : "r" ((USItype)(a)),                                        \
+            "r" ((USItype)(b))                                         \
+          : "r0", "r1", "r2")
+#define UMUL_TIME 20
+#define UDIV_TIME 100
+#endif /* __arm__ */
+
+#if defined (__clipper__) && W_TYPE_SIZE == 32
+#define umul_ppmm(w1, w0, u, v) \
+  ({union {UDItype __ll;                                               \
+          struct {USItype __l, __h;} __i;                              \
+         } __xx;                                                       \
+  __asm__ ("mulwux %2,%0"                                              \
+          : "=r" (__xx.__ll)                                           \
+          : "%0" ((USItype)(u)),                                       \
+            "r" ((USItype)(v)));                                       \
+  (w1) = __xx.__i.__h; (w0) = __xx.__i.__l;})
+#define smul_ppmm(w1, w0, u, v) \
+  ({union {DItype __ll;                                                        \
+          struct {SItype __l, __h;} __i;                               \
+         } __xx;                                                       \
+  __asm__ ("mulwx %2,%0"                                               \
+          : "=r" (__xx.__ll)                                           \
+          : "%0" ((SItype)(u)),                                        \
+            "r" ((SItype)(v)));                                        \
+  (w1) = __xx.__i.__h; (w0) = __xx.__i.__l;})
+#define __umulsidi3(u, v) \
+  ({UDItype __w;                                                       \
+    __asm__ ("mulwux %2,%0"                                            \
+            : "=r" (__w)                                               \
+            : "%0" ((USItype)(u)),                                     \
+              "r" ((USItype)(v)));                                     \
+    __w; })
+#endif /* __clipper__ */
+
+#if defined (__gmicro__) && W_TYPE_SIZE == 32
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  __asm__ ("add.w %5,%1
+       addx %3,%0"                                                     \
+          : "=g" ((USItype)(sh)),                                      \
+            "=&g" ((USItype)(sl))                                      \
+          : "%0" ((USItype)(ah)),                                      \
+            "g" ((USItype)(bh)),                                       \
+            "%1" ((USItype)(al)),                                      \
+            "g" ((USItype)(bl)))
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  __asm__ ("sub.w %5,%1
+       subx %3,%0"                                                     \
+          : "=g" ((USItype)(sh)),                                      \
+            "=&g" ((USItype)(sl))                                      \
+          : "0" ((USItype)(ah)),                                       \
+            "g" ((USItype)(bh)),                                       \
+            "1" ((USItype)(al)),                                       \
+            "g" ((USItype)(bl)))
+#define umul_ppmm(ph, pl, m0, m1) \
+  __asm__ ("mulx %3,%0,%1"                                             \
+          : "=g" ((USItype)(ph)),                                      \
+            "=r" ((USItype)(pl))                                       \
+          : "%0" ((USItype)(m0)),                                      \
+            "g" ((USItype)(m1)))
+#define udiv_qrnnd(q, r, nh, nl, d) \
+  __asm__ ("divx %4,%0,%1"                                             \
+          : "=g" ((USItype)(q)),                                       \
+            "=r" ((USItype)(r))                                        \
+          : "1" ((USItype)(nh)),                                       \
+            "0" ((USItype)(nl)),                                       \
+            "g" ((USItype)(d)))
+#define count_leading_zeros(count, x) \
+  __asm__ ("bsch/1 %1,%0"                                              \
+          : "=g" (count)                                               \
+          : "g" ((USItype)(x)),                                        \
+            "0" ((USItype)0))
+#endif
+
+#if defined (__hppa) && W_TYPE_SIZE == 32
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  __asm__ ("add %4,%5,%1
+       addc %2,%3,%0"                                                  \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "%rM" ((USItype)(ah)),                                     \
+            "rM" ((USItype)(bh)),                                      \
+            "%rM" ((USItype)(al)),                                     \
+            "rM" ((USItype)(bl)))
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  __asm__ ("sub %4,%5,%1
+       subb %2,%3,%0"                                                  \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "rM" ((USItype)(ah)),                                      \
+            "rM" ((USItype)(bh)),                                      \
+            "rM" ((USItype)(al)),                                      \
+            "rM" ((USItype)(bl)))
+#if defined (_PA_RISC1_1)
+#define umul_ppmm(wh, wl, u, v) \
+  do {                                                                 \
+    union {UDItype __ll;                                               \
+          struct {USItype __h, __l;} __i;                              \
+         } __xx;                                                       \
+    __asm__ ("xmpyu %1,%2,%0"                                          \
+            : "=*f" (__xx.__ll)                                        \
+            : "*f" ((USItype)(u)),                                     \
+              "*f" ((USItype)(v)));                                    \
+    (wh) = __xx.__i.__h;                                               \
+    (wl) = __xx.__i.__l;                                               \
+  } while (0)
+#define UMUL_TIME 8
+#define UDIV_TIME 60
+#else
+#define UMUL_TIME 40
+#define UDIV_TIME 80
+#endif
+#ifndef LONGLONG_STANDALONE
+#define udiv_qrnnd(q, r, n1, n0, d) \
+  do { USItype __r;                                                    \
+    (q) = __udiv_qrnnd (&__r, (n1), (n0), (d));                                \
+    (r) = __r;                                                         \
+  } while (0)
+extern USItype __udiv_qrnnd ();
+#endif /* LONGLONG_STANDALONE */
+#define count_leading_zeros(count, x) \
+  do {                                                                 \
+    USItype __tmp;                                                     \
+    __asm__ (                                                          \
+       "ldi            1,%0
+       extru,=         %1,15,16,%%r0           ; Bits 31..16 zero?
+       extru,tr        %1,15,16,%1             ; No.  Shift down, skip add.
+       ldo             16(%0),%0               ; Yes.  Perform add.
+       extru,=         %1,23,8,%%r0            ; Bits 15..8 zero?
+       extru,tr        %1,23,8,%1              ; No.  Shift down, skip add.
+       ldo             8(%0),%0                ; Yes.  Perform add.
+       extru,=         %1,27,4,%%r0            ; Bits 7..4 zero?
+       extru,tr        %1,27,4,%1              ; No.  Shift down, skip add.
+       ldo             4(%0),%0                ; Yes.  Perform add.
+       extru,=         %1,29,2,%%r0            ; Bits 3..2 zero?
+       extru,tr        %1,29,2,%1              ; No.  Shift down, skip add.
+       ldo             2(%0),%0                ; Yes.  Perform add.
+       extru           %1,30,1,%1              ; Extract bit 1.
+       sub             %0,%1,%0                ; Subtract it.
+       " : "=r" (count), "=r" (__tmp) : "1" (x));                      \
+  } while (0)
+#endif /* hppa */
+
+#if (defined (__i370__) || defined (__mvs__)) && W_TYPE_SIZE == 32
+#define umul_ppmm(xh, xl, m0, m1) \
+  do {                                                                 \
+    union {UDItype __ll;                                               \
+          struct {USItype __h, __l;} __i;                              \
+         } __xx;                                                       \
+    USItype __m0 = (m0), __m1 = (m1);                                  \
+    __asm__ ("mr %0,%3"                                                        \
+            : "=r" (__xx.__i.__h),                                     \
+              "=r" (__xx.__i.__l)                                      \
+            : "%1" (__m0),                                             \
+              "r" (__m1));                                             \
+    (xh) = __xx.__i.__h; (xl) = __xx.__i.__l;                          \
+    (xh) += ((((SItype) __m0 >> 31) & __m1)                            \
+            + (((SItype) __m1 >> 31) & __m0));                         \
+  } while (0)
+#define smul_ppmm(xh, xl, m0, m1) \
+  do {                                                                 \
+    union {DItype __ll;                                                        \
+          struct {USItype __h, __l;} __i;                              \
+         } __xx;                                                       \
+    __asm__ ("mr %0,%3"                                                        \
+            : "=r" (__xx.__i.__h),                                     \
+              "=r" (__xx.__i.__l)                                      \
+            : "%1" (m0),                                               \
+              "r" (m1));                                               \
+    (xh) = __xx.__i.__h; (xl) = __xx.__i.__l;                          \
+  } while (0)
+#define sdiv_qrnnd(q, r, n1, n0, d) \
+  do {                                                                 \
+    union {DItype __ll;                                                        \
+          struct {USItype __h, __l;} __i;                              \
+         } __xx;                                                       \
+    __xx.__i.__h = n1; __xx.__i.__l = n0;                              \
+    __asm__ ("dr %0,%2"                                                        \
+            : "=r" (__xx.__ll)                                         \
+            : "0" (__xx.__ll), "r" (d));                               \
+    (q) = __xx.__i.__l; (r) = __xx.__i.__h;                            \
+  } while (0)
+#endif
+
+#if (defined (__i386__) || defined (__i486__)) && W_TYPE_SIZE == 32
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  __asm__ ("addl %5,%1
+       adcl %3,%0"                                                     \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "%0" ((USItype)(ah)),                                      \
+            "g" ((USItype)(bh)),                                       \
+            "%1" ((USItype)(al)),                                      \
+            "g" ((USItype)(bl)))
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  __asm__ ("subl %5,%1
+       sbbl %3,%0"                                                     \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "0" ((USItype)(ah)),                                       \
+            "g" ((USItype)(bh)),                                       \
+            "1" ((USItype)(al)),                                       \
+            "g" ((USItype)(bl)))
+#define umul_ppmm(w1, w0, u, v) \
+  __asm__ ("mull %3"                                                   \
+          : "=a" ((USItype)(w0)),                                      \
+            "=d" ((USItype)(w1))                                       \
+          : "%0" ((USItype)(u)),                                       \
+            "rm" ((USItype)(v)))
+#define udiv_qrnnd(q, r, n1, n0, d) \
+  __asm__ ("divl %4"                                                   \
+          : "=a" ((USItype)(q)),                                       \
+            "=d" ((USItype)(r))                                        \
+          : "0" ((USItype)(n0)),                                       \
+            "1" ((USItype)(n1)),                                       \
+            "rm" ((USItype)(d)))
+#define count_leading_zeros(count, x) \
+  do {                                                                 \
+    USItype __cbtmp;                                                   \
+    __asm__ ("bsrl %1,%0"                                              \
+            : "=r" (__cbtmp) : "rm" ((USItype)(x)));                   \
+    (count) = __cbtmp ^ 31;                                            \
+  } while (0)
+#define count_trailing_zeros(count, x) \
+  __asm__ ("bsfl %1,%0" : "=r" (count) : "rm" ((USItype)(x)))
+#ifndef UMUL_TIME
+#define UMUL_TIME 40
+#endif
+#ifndef UDIV_TIME
+#define UDIV_TIME 40
+#endif
+#endif /* 80x86 */
+
+#if defined (__i860__) && W_TYPE_SIZE == 32
+#define rshift_rhlc(r,h,l,c) \
+  __asm__ ("shr %3,r0,r0\;shrd %1,%2,%0"                               \
+          "=r" (r) : "r" (h), "r" (l), "rn" (c))
+#endif /* i860 */
+
+#if defined (__i960__) && W_TYPE_SIZE == 32
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  __asm__ ("cmpo 1,0\;addc %5,%4,%1\;addc %3,%2,%0"                    \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "%dI" ((USItype)(ah)),                                     \
+            "dI" ((USItype)(bh)),                                      \
+            "%dI" ((USItype)(al)),                                     \
+            "dI" ((USItype)(bl)))
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  __asm__ ("cmpo 0,0\;subc %5,%4,%1\;subc %3,%2,%0"                    \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "dI" ((USItype)(ah)),                                      \
+            "dI" ((USItype)(bh)),                                      \
+            "dI" ((USItype)(al)),                                      \
+            "dI" ((USItype)(bl)))
+#define umul_ppmm(w1, w0, u, v) \
+  ({union {UDItype __ll;                                               \
+          struct {USItype __l, __h;} __i;                              \
+         } __xx;                                                       \
+  __asm__ ("emul       %2,%1,%0"                                       \
+          : "=d" (__xx.__ll)                                           \
+          : "%dI" ((USItype)(u)),                                      \
+            "dI" ((USItype)(v)));                                      \
+  (w1) = __xx.__i.__h; (w0) = __xx.__i.__l;})
+#define __umulsidi3(u, v) \
+  ({UDItype __w;                                                       \
+    __asm__ ("emul     %2,%1,%0"                                       \
+            : "=d" (__w)                                               \
+            : "%dI" ((USItype)(u)),                                    \
+              "dI" ((USItype)(v)));                                    \
+    __w; })
+#define udiv_qrnnd(q, r, nh, nl, d) \
+  do {                                                                 \
+    union {UDItype __ll;                                               \
+          struct {USItype __l, __h;} __i;                              \
+         } __nn;                                                       \
+    __nn.__i.__h = (nh); __nn.__i.__l = (nl);                          \
+    __asm__ ("ediv %d,%n,%0"                                           \
+          : "=d" (__rq.__ll)                                           \
+          : "dI" (__nn.__ll),                                          \
+            "dI" ((USItype)(d)));                                      \
+    (r) = __rq.__i.__l; (q) = __rq.__i.__h;                            \
+  } while (0)
+#define count_leading_zeros(count, x) \
+  do {                                                                 \
+    USItype __cbtmp;                                                   \
+    __asm__ ("scanbit %1,%0"                                           \
+            : "=r" (__cbtmp)                                           \
+            : "r" ((USItype)(x)));                                     \
+    (count) = __cbtmp ^ 31;                                            \
+  } while (0)
+#define COUNT_LEADING_ZEROS_0 (-32) /* sic */
+#if defined (__i960mx)         /* what is the proper symbol to test??? */
+#define rshift_rhlc(r,h,l,c) \
+  do {                                                                 \
+    union {UDItype __ll;                                               \
+          struct {USItype __l, __h;} __i;                              \
+         } __nn;                                                       \
+    __nn.__i.__h = (h); __nn.__i.__l = (l);                            \
+    __asm__ ("shre %2,%1,%0"                                           \
+            : "=d" (r) : "dI" (__nn.__ll), "dI" (c));                  \
+  }
+#endif /* i960mx */
+#endif /* i960 */
+
+#if (defined (__mc68000__) || defined (__mc68020__) || defined (__NeXT__) || defined(mc68020)) && W_TYPE_SIZE == 32
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  __asm__ ("add%.l %5,%1
+       addx%.l %3,%0"                                                  \
+          : "=d" ((USItype)(sh)),                                      \
+            "=&d" ((USItype)(sl))                                      \
+          : "%0" ((USItype)(ah)),                                      \
+            "d" ((USItype)(bh)),                                       \
+            "%1" ((USItype)(al)),                                      \
+            "g" ((USItype)(bl)))
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  __asm__ ("sub%.l %5,%1
+       subx%.l %3,%0"                                                  \
+          : "=d" ((USItype)(sh)),                                      \
+            "=&d" ((USItype)(sl))                                      \
+          : "0" ((USItype)(ah)),                                       \
+            "d" ((USItype)(bh)),                                       \
+            "1" ((USItype)(al)),                                       \
+            "g" ((USItype)(bl)))
+#if (defined (__mc68020__) || defined (__NeXT__) || defined(mc68020))
+#define umul_ppmm(w1, w0, u, v) \
+  __asm__ ("mulu%.l %3,%1:%0"                                          \
+          : "=d" ((USItype)(w0)),                                      \
+            "=d" ((USItype)(w1))                                       \
+          : "%0" ((USItype)(u)),                                       \
+            "dmi" ((USItype)(v)))
+#define UMUL_TIME 45
+#define udiv_qrnnd(q, r, n1, n0, d) \
+  __asm__ ("divu%.l %4,%1:%0"                                          \
+          : "=d" ((USItype)(q)),                                       \
+            "=d" ((USItype)(r))                                        \
+          : "0" ((USItype)(n0)),                                       \
+            "1" ((USItype)(n1)),                                       \
+            "dmi" ((USItype)(d)))
+#define UDIV_TIME 90
+#define sdiv_qrnnd(q, r, n1, n0, d) \
+  __asm__ ("divs%.l %4,%1:%0"                                          \
+          : "=d" ((USItype)(q)),                                       \
+            "=d" ((USItype)(r))                                        \
+          : "0" ((USItype)(n0)),                                       \
+            "1" ((USItype)(n1)),                                       \
+            "dmi" ((USItype)(d)))
+#define count_leading_zeros(count, x) \
+  __asm__ ("bfffo %1{%b2:%b2},%0"                                      \
+          : "=d" ((USItype)(count))                                    \
+          : "od" ((USItype)(x)), "n" (0))
+#define COUNT_LEADING_ZEROS_0 32
+#else /* not mc68020 */
+#define umul_ppmm(xh, xl, a, b) \
+  do { USItype __umul_tmp1, __umul_tmp2;                               \
+       __asm__ ("| Inlined umul_ppmm
+       move%.l %5,%3
+       move%.l %2,%0
+       move%.w %3,%1
+       swap    %3
+       swap    %0
+       mulu    %2,%1
+       mulu    %3,%0
+       mulu    %2,%3
+       swap    %2
+       mulu    %5,%2
+       add%.l  %3,%2
+       jcc     1f
+       add%.l  %#0x10000,%0
+1:     move%.l %2,%3
+       clr%.w  %2
+       swap    %2
+       swap    %3
+       clr%.w  %3
+       add%.l  %3,%1
+       addx%.l %2,%0
+       | End inlined umul_ppmm"                                        \
+             : "=&d" ((USItype)(xh)), "=&d" ((USItype)(xl)),           \
+               "=d" (__umul_tmp1), "=&d" (__umul_tmp2)                 \
+             : "%2" ((USItype)(a)), "d" ((USItype)(b)));               \
+  } while (0)
+#define UMUL_TIME 100
+#define UDIV_TIME 400
+#endif /* not mc68020 */
+#endif /* mc68000 */
+
+#if defined (__m88000__) && W_TYPE_SIZE == 32
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  __asm__ ("addu.co %1,%r4,%r5
+       addu.ci %0,%r2,%r3"                                             \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "%rJ" ((USItype)(ah)),                                     \
+            "rJ" ((USItype)(bh)),                                      \
+            "%rJ" ((USItype)(al)),                                     \
+            "rJ" ((USItype)(bl)))
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  __asm__ ("subu.co %1,%r4,%r5
+       subu.ci %0,%r2,%r3"                                             \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "rJ" ((USItype)(ah)),                                      \
+            "rJ" ((USItype)(bh)),                                      \
+            "rJ" ((USItype)(al)),                                      \
+            "rJ" ((USItype)(bl)))
+#define count_leading_zeros(count, x) \
+  do {                                                                 \
+    USItype __cbtmp;                                                   \
+    __asm__ ("ff1 %0,%1"                                               \
+            : "=r" (__cbtmp)                                           \
+            : "r" ((USItype)(x)));                                     \
+    (count) = __cbtmp ^ 31;                                            \
+  } while (0)
+#define COUNT_LEADING_ZEROS_0 63 /* sic */
+#if defined (__m88110__)
+#define umul_ppmm(wh, wl, u, v) \
+  do {                                                                 \
+    union {UDItype __ll;                                               \
+          struct {USItype __h, __l;} __i;                              \
+         } __xx;                                                       \
+    __asm__ ("mulu.d   %0,%1,%2"                                       \
+            : "=r" (__xx.__ll)                                         \
+            : "r" ((USItype)(u)),                                      \
+              "r" ((USItype)(v)));                                     \
+    (wh) = __xx.__i.__h;                                               \
+    (wl) = __xx.__i.__l;                                               \
+  } while (0)
+#define udiv_qrnnd(q, r, n1, n0, d) \
+  ({union {UDItype __ll;                                               \
+          struct {USItype __h, __l;} __i;                              \
+         } __xx;                                                       \
+  USItype __q;                                                         \
+  __xx.__i.__h = (n1); __xx.__i.__l = (n0);                            \
+  __asm__ ("divu.d %0,%1,%2"                                           \
+          : "=r" (__q)                                                 \
+          : "r" (__xx.__ll),                                           \
+            "r" ((USItype)(d)));                                       \
+  (r) = (n0) - __q * (d); (q) = __q; })
+#define UMUL_TIME 5
+#define UDIV_TIME 25
+#else
+#define UMUL_TIME 17
+#define UDIV_TIME 150
+#endif /* __m88110__ */
+#endif /* __m88000__ */
+
+#if defined (__mips__) && W_TYPE_SIZE == 32
+#if __GNUC__ > 2 || __GNUC_MINOR__ >= 7
+#define umul_ppmm(w1, w0, u, v) \
+  __asm__ ("multu %2,%3"                                               \
+          : "=l" ((USItype)(w0)),                                      \
+            "=h" ((USItype)(w1))                                       \
+          : "d" ((USItype)(u)),                                        \
+            "d" ((USItype)(v)))
+#else
+#define umul_ppmm(w1, w0, u, v) \
+  __asm__ ("multu %2,%3
+       mflo %0
+       mfhi %1"                                                        \
+          : "=d" ((USItype)(w0)),                                      \
+            "=d" ((USItype)(w1))                                       \
+          : "d" ((USItype)(u)),                                        \
+            "d" ((USItype)(v)))
+#endif
+#define UMUL_TIME 10
+#define UDIV_TIME 100
+#endif /* __mips__ */
+
+#if (defined (__mips) && __mips >= 3) && W_TYPE_SIZE == 64
+#if __GNUC__ > 2 || __GNUC_MINOR__ >= 7
+#define umul_ppmm(w1, w0, u, v) \
+  __asm__ ("dmultu %2,%3"                                              \
+          : "=l" ((UDItype)(w0)),                                      \
+            "=h" ((UDItype)(w1))                                       \
+          : "d" ((UDItype)(u)),                                        \
+            "d" ((UDItype)(v)))
+#else
+#define umul_ppmm(w1, w0, u, v) \
+  __asm__ ("dmultu %2,%3
+       mflo %0
+       mfhi %1"                                                        \
+          : "=d" ((UDItype)(w0)),                                      \
+            "=d" ((UDItype)(w1))                                       \
+          : "d" ((UDItype)(u)),                                        \
+            "d" ((UDItype)(v)))
+#endif
+#define UMUL_TIME 20
+#define UDIV_TIME 140
+#endif /* __mips__ */
+
+#if defined (__ns32000__) && W_TYPE_SIZE == 32
+#define umul_ppmm(w1, w0, u, v) \
+  ({union {UDItype __ll;                                               \
+          struct {USItype __l, __h;} __i;                              \
+         } __xx;                                                       \
+  __asm__ ("meid %2,%0"                                                        \
+          : "=g" (__xx.__ll)                                           \
+          : "%0" ((USItype)(u)),                                       \
+            "g" ((USItype)(v)));                                       \
+  (w1) = __xx.__i.__h; (w0) = __xx.__i.__l;})
+#define __umulsidi3(u, v) \
+  ({UDItype __w;                                                       \
+    __asm__ ("meid %2,%0"                                              \
+            : "=g" (__w)                                               \
+            : "%0" ((USItype)(u)),                                     \
+              "g" ((USItype)(v)));                                     \
+    __w; })
+#define udiv_qrnnd(q, r, n1, n0, d) \
+  ({union {UDItype __ll;                                               \
+          struct {USItype __l, __h;} __i;                              \
+         } __xx;                                                       \
+  __xx.__i.__h = (n1); __xx.__i.__l = (n0);                            \
+  __asm__ ("deid %2,%0"                                                        \
+          : "=g" (__xx.__ll)                                           \
+          : "0" (__xx.__ll),                                           \
+            "g" ((USItype)(d)));                                       \
+  (r) = __xx.__i.__l; (q) = __xx.__i.__h; })
+#define count_trailing_zeros(count,x) \
+  do {
+    __asm__ ("ffsd     %2,%0"                                          \
+            : "=r" ((USItype) (count))                                 \
+            : "0" ((USItype) 0),                                       \
+              "r" ((USItype) (x)));                                    \
+  } while (0)
+#endif /* __ns32000__ */
+
+#if (defined (_ARCH_PPC) || defined (_IBMR2)) && W_TYPE_SIZE == 32
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  do {                                                                 \
+    if (__builtin_constant_p (bh) && (bh) == 0)                                \
+      __asm__ ("{a%I4|add%I4c} %1,%3,%4\n\t{aze|addze} %0,%2"          \
+            : "=r" ((USItype)(sh)),                                    \
+              "=&r" ((USItype)(sl))                                    \
+            : "%r" ((USItype)(ah)),                                    \
+              "%r" ((USItype)(al)),                                    \
+              "rI" ((USItype)(bl)));                                   \
+    else if (__builtin_constant_p (bh) && (bh) ==~(USItype) 0)         \
+      __asm__ ("{a%I4|add%I4c} %1,%3,%4\n\t{ame|addme} %0,%2"          \
+            : "=r" ((USItype)(sh)),                                    \
+              "=&r" ((USItype)(sl))                                    \
+            : "%r" ((USItype)(ah)),                                    \
+              "%r" ((USItype)(al)),                                    \
+              "rI" ((USItype)(bl)));                                   \
+    else                                                               \
+      __asm__ ("{a%I5|add%I5c} %1,%4,%5\n\t{ae|adde} %0,%2,%3"         \
+            : "=r" ((USItype)(sh)),                                    \
+              "=&r" ((USItype)(sl))                                    \
+            : "%r" ((USItype)(ah)),                                    \
+              "r" ((USItype)(bh)),                                     \
+              "%r" ((USItype)(al)),                                    \
+              "rI" ((USItype)(bl)));                                   \
+  } while (0)
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  do {                                                                 \
+    if (__builtin_constant_p (ah) && (ah) == 0)                                \
+      __asm__ ("{sf%I3|subf%I3c} %1,%4,%3\n\t{sfze|subfze} %0,%2"      \
+              : "=r" ((USItype)(sh)),                                  \
+                "=&r" ((USItype)(sl))                                  \
+              : "r" ((USItype)(bh)),                                   \
+                "rI" ((USItype)(al)),                                  \
+                "r" ((USItype)(bl)));                                  \
+    else if (__builtin_constant_p (ah) && (ah) ==~(USItype) 0)         \
+      __asm__ ("{sf%I3|subf%I3c} %1,%4,%3\n\t{sfme|subfme} %0,%2"      \
+              : "=r" ((USItype)(sh)),                                  \
+                "=&r" ((USItype)(sl))                                  \
+              : "r" ((USItype)(bh)),                                   \
+                "rI" ((USItype)(al)),                                  \
+                "r" ((USItype)(bl)));                                  \
+    else if (__builtin_constant_p (bh) && (bh) == 0)                   \
+      __asm__ ("{sf%I3|subf%I3c} %1,%4,%3\n\t{ame|addme} %0,%2"                \
+              : "=r" ((USItype)(sh)),                                  \
+                "=&r" ((USItype)(sl))                                  \
+              : "r" ((USItype)(ah)),                                   \
+                "rI" ((USItype)(al)),                                  \
+                "r" ((USItype)(bl)));                                  \
+    else if (__builtin_constant_p (bh) && (bh) ==~(USItype) 0)         \
+      __asm__ ("{sf%I3|subf%I3c} %1,%4,%3\n\t{aze|addze} %0,%2"                \
+              : "=r" ((USItype)(sh)),                                  \
+                "=&r" ((USItype)(sl))                                  \
+              : "r" ((USItype)(ah)),                                   \
+                "rI" ((USItype)(al)),                                  \
+                "r" ((USItype)(bl)));                                  \
+    else                                                               \
+      __asm__ ("{sf%I4|subf%I4c} %1,%5,%4\n\t{sfe|subfe} %0,%3,%2"     \
+              : "=r" ((USItype)(sh)),                                  \
+                "=&r" ((USItype)(sl))                                  \
+              : "r" ((USItype)(ah)),                                   \
+                "r" ((USItype)(bh)),                                   \
+                "rI" ((USItype)(al)),                                  \
+                "r" ((USItype)(bl)));                                  \
+  } while (0)
+#define count_leading_zeros(count, x) \
+  __asm__ ("{cntlz|cntlzw} %0,%1"                                      \
+          : "=r" ((USItype)(count))                                    \
+          : "r" ((USItype)(x)))
+#define COUNT_LEADING_ZEROS_0 32
+#if defined (_ARCH_PPC)
+#define umul_ppmm(ph, pl, m0, m1) \
+  do {                                                                 \
+    USItype __m0 = (m0), __m1 = (m1);                                  \
+    __asm__ ("mulhwu %0,%1,%2"                                         \
+            : "=r" ((USItype) ph)                                      \
+            : "%r" (__m0),                                             \
+              "r" (__m1));                                             \
+    (pl) = __m0 * __m1;                                                        \
+  } while (0)
+#define UMUL_TIME 15
+#define smul_ppmm(ph, pl, m0, m1) \
+  do {                                                                 \
+    SItype __m0 = (m0), __m1 = (m1);                                   \
+    __asm__ ("mulhw %0,%1,%2"                                          \
+            : "=r" ((SItype) ph)                                       \
+            : "%r" (__m0),                                             \
+              "r" (__m1));                                             \
+    (pl) = __m0 * __m1;                                                        \
+  } while (0)
+#define SMUL_TIME 14
+#define UDIV_TIME 120
+#else
+#define umul_ppmm(xh, xl, m0, m1) \
+  do {                                                                 \
+    USItype __m0 = (m0), __m1 = (m1);                                  \
+    __asm__ ("mul %0,%2,%3"                                            \
+            : "=r" ((USItype)(xh)),                                    \
+              "=q" ((USItype)(xl))                                     \
+            : "r" (__m0),                                              \
+              "r" (__m1));                                             \
+    (xh) += ((((SItype) __m0 >> 31) & __m1)                            \
+            + (((SItype) __m1 >> 31) & __m0));                         \
+  } while (0)
+#define UMUL_TIME 8
+#define smul_ppmm(xh, xl, m0, m1) \
+  __asm__ ("mul %0,%2,%3"                                              \
+          : "=r" ((SItype)(xh)),                                       \
+            "=q" ((SItype)(xl))                                        \
+          : "r" (m0),                                                  \
+            "r" (m1))
+#define SMUL_TIME 4
+#define sdiv_qrnnd(q, r, nh, nl, d) \
+  __asm__ ("div %0,%2,%4"                                              \
+          : "=r" ((SItype)(q)), "=q" ((SItype)(r))                     \
+          : "r" ((SItype)(nh)), "1" ((SItype)(nl)), "r" ((SItype)(d)))
+#define UDIV_TIME 100
+#endif
+#endif /* Power architecture variants.  */
+
+#if defined (__pyr__) && W_TYPE_SIZE == 32
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  __asm__ ("addw       %5,%1
+       addwc   %3,%0"                                                  \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "%0" ((USItype)(ah)),                                      \
+            "g" ((USItype)(bh)),                                       \
+            "%1" ((USItype)(al)),                                      \
+            "g" ((USItype)(bl)))
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  __asm__ ("subw       %5,%1
+       subwb   %3,%0"                                                  \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "0" ((USItype)(ah)),                                       \
+            "g" ((USItype)(bh)),                                       \
+            "1" ((USItype)(al)),                                       \
+            "g" ((USItype)(bl)))
+/* This insn works on Pyramids with AP, XP, or MI CPUs, but not with SP.  */
+#define umul_ppmm(w1, w0, u, v) \
+  ({union {UDItype __ll;                                               \
+          struct {USItype __h, __l;} __i;                              \
+         } __xx;                                                       \
+  __asm__ ("movw %1,%R0
+       uemul %2,%0"                                                    \
+          : "=&r" (__xx.__ll)                                          \
+          : "g" ((USItype) (u)),                                       \
+            "g" ((USItype)(v)));                                       \
+  (w1) = __xx.__i.__h; (w0) = __xx.__i.__l;})
+#endif /* __pyr__ */
+
+#if defined (__ibm032__) /* RT/ROMP */  && W_TYPE_SIZE == 32
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  __asm__ ("a %1,%5
+       ae %0,%3"                                                       \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "%0" ((USItype)(ah)),                                      \
+            "r" ((USItype)(bh)),                                       \
+            "%1" ((USItype)(al)),                                      \
+            "r" ((USItype)(bl)))
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  __asm__ ("s %1,%5
+       se %0,%3"                                                       \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "0" ((USItype)(ah)),                                       \
+            "r" ((USItype)(bh)),                                       \
+            "1" ((USItype)(al)),                                       \
+            "r" ((USItype)(bl)))
+#define umul_ppmm(ph, pl, m0, m1) \
+  do {                                                                 \
+    USItype __m0 = (m0), __m1 = (m1);                                  \
+    __asm__ (                                                          \
+       "s      r2,r2
+       mts     r10,%2
+       m       r2,%3
+       m       r2,%3
+       m       r2,%3
+       m       r2,%3
+       m       r2,%3
+       m       r2,%3
+       m       r2,%3
+       m       r2,%3
+       m       r2,%3
+       m       r2,%3
+       m       r2,%3
+       m       r2,%3
+       m       r2,%3
+       m       r2,%3
+       m       r2,%3
+       m       r2,%3
+       cas     %0,r2,r0
+       mfs     r10,%1"                                                 \
+            : "=r" ((USItype)(ph)),                                    \
+              "=r" ((USItype)(pl))                                     \
+            : "%r" (__m0),                                             \
+               "r" (__m1)                                              \
+            : "r2");                                                   \
+    (ph) += ((((SItype) __m0 >> 31) & __m1)                            \
+            + (((SItype) __m1 >> 31) & __m0));                         \
+  } while (0)
+#define UMUL_TIME 20
+#define UDIV_TIME 200
+#define count_leading_zeros(count, x) \
+  do {                                                                 \
+    if ((x) >= 0x10000)                                                        \
+      __asm__ ("clz    %0,%1"                                          \
+              : "=r" ((USItype)(count))                                \
+              : "r" ((USItype)(x) >> 16));                             \
+    else                                                               \
+      {                                                                        \
+       __asm__ ("clz   %0,%1"                                          \
+                : "=r" ((USItype)(count))                              \
+                : "r" ((USItype)(x)));                                 \
+       (count) += 16;                                                  \
+      }                                                                        \
+  } while (0)
+#endif /* RT/ROMP */
+
+#if defined (__sh2__) && W_TYPE_SIZE == 32
+#define umul_ppmm(w1, w0, u, v) \
+  __asm__ (                                                            \
+       "dmulu.l        %2,%3
+       sts     macl,%1
+       sts     mach,%0"                                                \
+          : "=r" ((USItype)(w1)),                                      \
+            "=r" ((USItype)(w0))                                       \
+          : "r" ((USItype)(u)),                                        \
+            "r" ((USItype)(v))                                         \
+          : "macl", "mach")
+#define UMUL_TIME 5
+#endif
+
+#if defined (__sparc__) && W_TYPE_SIZE == 32
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  __asm__ ("addcc %r4,%5,%1
+       addx %r2,%3,%0"                                                 \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "%rJ" ((USItype)(ah)),                                     \
+            "rI" ((USItype)(bh)),                                      \
+            "%rJ" ((USItype)(al)),                                     \
+            "rI" ((USItype)(bl))                                       \
+          __CLOBBER_CC)
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  __asm__ ("subcc %r4,%5,%1
+       subx %r2,%3,%0"                                                 \
+          : "=r" ((USItype)(sh)),                                      \
+            "=&r" ((USItype)(sl))                                      \
+          : "rJ" ((USItype)(ah)),                                      \
+            "rI" ((USItype)(bh)),                                      \
+            "rJ" ((USItype)(al)),                                      \
+            "rI" ((USItype)(bl))                                       \
+          __CLOBBER_CC)
+#if defined (__sparc_v8__)
+/* Don't match immediate range because, 1) it is not often useful,
+   2) the 'I' flag thinks of the range as a 13 bit signed interval,
+   while we want to match a 13 bit interval, sign extended to 32 bits,
+   but INTERPRETED AS UNSIGNED.  */
+#define umul_ppmm(w1, w0, u, v) \
+  __asm__ ("umul %2,%3,%1;rd %%y,%0"                                   \
+          : "=r" ((USItype)(w1)),                                      \
+            "=r" ((USItype)(w0))                                       \
+          : "r" ((USItype)(u)),                                        \
+            "r" ((USItype)(v)))
+#define UMUL_TIME 5
+#ifndef SUPERSPARC     /* SuperSPARC's udiv only handles 53 bit dividends */
+#define udiv_qrnnd(q, r, n1, n0, d) \
+  do {                                                                 \
+    USItype __q;                                                       \
+    __asm__ ("mov %1,%%y;nop;nop;nop;udiv %2,%3,%0"                    \
+            : "=r" ((USItype)(__q))                                    \
+            : "r" ((USItype)(n1)),                                     \
+              "r" ((USItype)(n0)),                                     \
+              "r" ((USItype)(d)));                                     \
+    (r) = (n0) - __q * (d);                                            \
+    (q) = __q;                                                         \
+  } while (0)
+#define UDIV_TIME 25
+#endif /* SUPERSPARC */
+#else /* ! __sparc_v8__ */
+#if defined (__sparclite__)
+/* This has hardware multiply but not divide.  It also has two additional
+   instructions scan (ffs from high bit) and divscc.  */
+#define umul_ppmm(w1, w0, u, v) \
+  __asm__ ("umul %2,%3,%1;rd %%y,%0"                                   \
+          : "=r" ((USItype)(w1)),                                      \
+            "=r" ((USItype)(w0))                                       \
+          : "r" ((USItype)(u)),                                        \
+            "r" ((USItype)(v)))
+#define UMUL_TIME 5
+#define udiv_qrnnd(q, r, n1, n0, d) \
+  __asm__ ("! Inlined udiv_qrnnd
+       wr      %%g0,%2,%%y     ! Not a delayed write for sparclite
+       tst     %%g0
+       divscc  %3,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%%g1
+       divscc  %%g1,%4,%0
+       rd      %%y,%1
+       bl,a 1f
+       add     %1,%4,%1
+1:     ! End of inline udiv_qrnnd"                                     \
+          : "=r" ((USItype)(q)),                                       \
+            "=r" ((USItype)(r))                                        \
+          : "r" ((USItype)(n1)),                                       \
+            "r" ((USItype)(n0)),                                       \
+            "rI" ((USItype)(d))                                        \
+          : "%g1" __AND_CLOBBER_CC)
+#define UDIV_TIME 37
+#define count_leading_zeros(count, x) \
+  __asm__ ("scan %1,0,%0"                                              \
+          : "=r" ((USItype)(x))                                        \
+          : "r" ((USItype)(count)))
+/* Early sparclites return 63 for an argument of 0, but they warn that future
+   implementations might change this.  Therefore, leave COUNT_LEADING_ZEROS_0
+   undefined.  */
+#endif /* __sparclite__ */
+#endif /* __sparc_v8__ */
+/* Default to sparc v7 versions of umul_ppmm and udiv_qrnnd.  */
+#ifndef umul_ppmm
+#define umul_ppmm(w1, w0, u, v) \
+  __asm__ ("! Inlined umul_ppmm
+       wr      %%g0,%2,%%y     ! SPARC has 0-3 delay insn after a wr
+       sra     %3,31,%%g2      ! Don't move this insn
+       and     %2,%%g2,%%g2    ! Don't move this insn
+       andcc   %%g0,0,%%g1     ! Don't move this insn
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,%3,%%g1
+       mulscc  %%g1,0,%%g1
+       add     %%g1,%%g2,%0
+       rd      %%y,%1"                                                 \
+          : "=r" ((USItype)(w1)),                                      \
+            "=r" ((USItype)(w0))                                       \
+          : "%rI" ((USItype)(u)),                                      \
+            "r" ((USItype)(v))                                         \
+          : "%g1", "%g2" __AND_CLOBBER_CC)
+#define UMUL_TIME 39           /* 39 instructions */
+#endif
+#ifndef udiv_qrnnd
+#ifndef LONGLONG_STANDALONE
+#define udiv_qrnnd(q, r, n1, n0, d) \
+  do { USItype __r;                                                    \
+    (q) = __udiv_qrnnd (&__r, (n1), (n0), (d));                                \
+    (r) = __r;                                                         \
+  } while (0)
+extern USItype __udiv_qrnnd ();
+#define UDIV_TIME 140
+#endif /* LONGLONG_STANDALONE */
+#endif /* udiv_qrnnd */
+#endif /* __sparc__ */
+
+#if defined (__vax__) && W_TYPE_SIZE == 32
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  __asm__ ("addl2 %5,%1
+       adwc %3,%0"                                                     \
+          : "=g" ((USItype)(sh)),                                      \
+            "=&g" ((USItype)(sl))                                      \
+          : "%0" ((USItype)(ah)),                                      \
+            "g" ((USItype)(bh)),                                       \
+            "%1" ((USItype)(al)),                                      \
+            "g" ((USItype)(bl)))
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  __asm__ ("subl2 %5,%1
+       sbwc %3,%0"                                                     \
+          : "=g" ((USItype)(sh)),                                      \
+            "=&g" ((USItype)(sl))                                      \
+          : "0" ((USItype)(ah)),                                       \
+            "g" ((USItype)(bh)),                                       \
+            "1" ((USItype)(al)),                                       \
+            "g" ((USItype)(bl)))
+#define umul_ppmm(xh, xl, m0, m1) \
+  do {                                                                 \
+    union {UDItype __ll;                                               \
+          struct {USItype __l, __h;} __i;                              \
+         } __xx;                                                       \
+    USItype __m0 = (m0), __m1 = (m1);                                  \
+    __asm__ ("emul %1,%2,$0,%0"                                                \
+            : "=g" (__xx.__ll)                                         \
+            : "g" (__m0),                                              \
+              "g" (__m1));                                             \
+    (xh) = __xx.__i.__h; (xl) = __xx.__i.__l;                          \
+    (xh) += ((((SItype) __m0 >> 31) & __m1)                            \
+            + (((SItype) __m1 >> 31) & __m0));                         \
+  } while (0)
+#define sdiv_qrnnd(q, r, n1, n0, d) \
+  do {                                                                 \
+    union {DItype __ll;                                                        \
+          struct {SItype __l, __h;} __i;                               \
+         } __xx;                                                       \
+    __xx.__i.__h = n1; __xx.__i.__l = n0;                              \
+    __asm__ ("ediv %3,%2,%0,%1"                                                \
+            : "=g" (q), "=g" (r)                                       \
+            : "g" (__xx.ll), "g" (d));                                 \
+  } while (0)
+#endif /* __vax__ */
+
+#if defined (__z8000__) && W_TYPE_SIZE == 16
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  __asm__ ("add        %H1,%H5\n\tadc  %H0,%H3"                                \
+          : "=r" ((unsigned int)(sh)),                                 \
+            "=&r" ((unsigned int)(sl))                                 \
+          : "%0" ((unsigned int)(ah)),                                 \
+            "r" ((unsigned int)(bh)),                                  \
+            "%1" ((unsigned int)(al)),                                 \
+            "rQR" ((unsigned int)(bl)))
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  __asm__ ("sub        %H1,%H5\n\tsbc  %H0,%H3"                                \
+          : "=r" ((unsigned int)(sh)),                                 \
+            "=&r" ((unsigned int)(sl))                                 \
+          : "0" ((unsigned int)(ah)),                                  \
+            "r" ((unsigned int)(bh)),                                  \
+            "1" ((unsigned int)(al)),                                  \
+            "rQR" ((unsigned int)(bl)))
+#define umul_ppmm(xh, xl, m0, m1) \
+  do {                                                                 \
+    union {long int __ll;                                              \
+          struct {unsigned int __h, __l;} __i;                         \
+         } __xx;                                                       \
+    unsigned int __m0 = (m0), __m1 = (m1);                             \
+    __asm__ ("mult     %S0,%H3"                                        \
+            : "=r" (__xx.__i.__h),                                     \
+              "=r" (__xx.__i.__l)                                      \
+            : "%1" (__m0),                                             \
+              "rQR" (__m1));                                           \
+    (xh) = __xx.__i.__h; (xl) = __xx.__i.__l;                          \
+    (xh) += ((((signed int) __m0 >> 15) & __m1)                                \
+            + (((signed int) __m1 >> 15) & __m0));                     \
+  } while (0)
+#endif /* __z8000__ */
+
+#endif /* __GNUC__ */
+
+
+#if !defined (umul_ppmm) && defined (__umulsidi3)
+#define umul_ppmm(ph, pl, m0, m1) \
+  {                                                                    \
+    UDWtype __ll = __umulsidi3 (m0, m1);                               \
+    ph = (UWtype) (__ll >> W_TYPE_SIZE);                               \
+    pl = (UWtype) __ll;                                                        \
+  }
+#endif
+
+#if !defined (__umulsidi3)
+#define __umulsidi3(u, v) \
+  ({UWtype __hi, __lo;                                                 \
+    umul_ppmm (__hi, __lo, u, v);                                      \
+    ((UDWtype) __hi << W_TYPE_SIZE) | __lo; })
+#endif
+
+/* If this machine has no inline assembler, use C macros.  */
+
+#if !defined (add_ssaaaa)
+#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
+  do {                                                                 \
+    UWtype __x;                                                                \
+    __x = (al) + (bl);                                                 \
+    (sh) = (ah) + (bh) + (__x < (al));                                 \
+    (sl) = __x;                                                                \
+  } while (0)
+#endif
+
+#if !defined (sub_ddmmss)
+#define sub_ddmmss(sh, sl, ah, al, bh, bl) \
+  do {                                                                 \
+    UWtype __x;                                                                \
+    __x = (al) - (bl);                                                 \
+    (sh) = (ah) - (bh) - (__x > (al));                                 \
+    (sl) = __x;                                                                \
+  } while (0)
+#endif
+
+#if !defined (umul_ppmm)
+#define umul_ppmm(w1, w0, u, v)                                                \
+  do {                                                                 \
+    UWtype __x0, __x1, __x2, __x3;                                     \
+    UHWtype __ul, __vl, __uh, __vh;                                    \
+    UWtype __u = (u), __v = (v);                                       \
+                                                                       \
+    __ul = __ll_lowpart (__u);                                         \
+    __uh = __ll_highpart (__u);                                                \
+    __vl = __ll_lowpart (__v);                                         \
+    __vh = __ll_highpart (__v);                                                \
+                                                                       \
+    __x0 = (UWtype) __ul * __vl;                                       \
+    __x1 = (UWtype) __ul * __vh;                                       \
+    __x2 = (UWtype) __uh * __vl;                                       \
+    __x3 = (UWtype) __uh * __vh;                                       \
+                                                                       \
+    __x1 += __ll_highpart (__x0);/* this can't give carry */           \
+    __x1 += __x2;              /* but this indeed can */               \
+    if (__x1 < __x2)           /* did we get it? */                    \
+      __x3 += __ll_B;          /* yes, add it in the proper pos. */    \
+                                                                       \
+    (w1) = __x3 + __ll_highpart (__x1);                                        \
+    (w0) = (__ll_lowpart (__x1) << W_TYPE_SIZE/2) + __ll_lowpart (__x0);\
+  } while (0)
+#endif
+
+#if !defined (umul_ppmm)
+#define smul_ppmm(w1, w0, u, v)                                                \
+  do {                                                                 \
+    UWtype __w1;                                                       \
+    UWtype __m0 = (u), __m1 = (v);                                     \
+    umul_ppmm (__w1, w0, __m0, __m1);                                  \
+    (w1) = __w1 - (-(__m0 >> (W_TYPE_SIZE - 1)) & __m1)                        \
+               - (-(__m1 >> (W_TYPE_SIZE - 1)) & __m0);                \
+  } while (0)
+#endif
+
+/* Define this unconditionally, so it can be used for debugging.  */
+#define __udiv_qrnnd_c(q, r, n1, n0, d) \
+  do {                                                                 \
+    UWtype __d1, __d0, __q1, __q0, __r1, __r0, __m;                    \
+    __d1 = __ll_highpart (d);                                          \
+    __d0 = __ll_lowpart (d);                                           \
+                                                                       \
+    __r1 = (n1) % __d1;                                                        \
+    __q1 = (n1) / __d1;                                                        \
+    __m = (UWtype) __q1 * __d0;                                                \
+    __r1 = __r1 * __ll_B | __ll_highpart (n0);                         \
+    if (__r1 < __m)                                                    \
+      {                                                                        \
+       __q1--, __r1 += (d);                                            \
+       if (__r1 >= (d)) /* i.e. we didn't get carry when adding to __r1 */\
+         if (__r1 < __m)                                               \
+           __q1--, __r1 += (d);                                        \
+      }                                                                        \
+    __r1 -= __m;                                                       \
+                                                                       \
+    __r0 = __r1 % __d1;                                                        \
+    __q0 = __r1 / __d1;                                                        \
+    __m = (UWtype) __q0 * __d0;                                                \
+    __r0 = __r0 * __ll_B | __ll_lowpart (n0);                          \
+    if (__r0 < __m)                                                    \
+      {                                                                        \
+       __q0--, __r0 += (d);                                            \
+       if (__r0 >= (d))                                                \
+         if (__r0 < __m)                                               \
+           __q0--, __r0 += (d);                                        \
+      }                                                                        \
+    __r0 -= __m;                                                       \
+                                                                       \
+    (q) = (UWtype) __q1 * __ll_B | __q0;                               \
+    (r) = __r0;                                                                \
+  } while (0)
+
+/* If the processor has no udiv_qrnnd but sdiv_qrnnd, go through
+   __udiv_w_sdiv (defined in libgcc or elsewhere).  */
+#if !defined (udiv_qrnnd) && defined (sdiv_qrnnd)
+#define udiv_qrnnd(q, r, nh, nl, d) \
+  do {                                                                 \
+    UWtype __r;                                                                \
+    (q) = __MPN(udiv_w_sdiv) (&__r, nh, nl, d);                                \
+    (r) = __r;                                                         \
+  } while (0)
+#endif
+
+/* If udiv_qrnnd was not defined for this processor, use __udiv_qrnnd_c.  */
+#if !defined (udiv_qrnnd)
+#define UDIV_NEEDS_NORMALIZATION 1
+#define udiv_qrnnd __udiv_qrnnd_c
+#endif
+
+#if !defined (count_leading_zeros)
+extern
+#ifdef __STDC__
+const
+#endif
+unsigned char __clz_tab[];
+#define count_leading_zeros(count, x) \
+  do {                                                                 \
+    UWtype __xr = (x);                                                 \
+    UWtype __a;                                                                \
+                                                                       \
+    if (W_TYPE_SIZE <= 32)                                             \
+      {                                                                        \
+       __a = __xr < ((UWtype) 1 << 2*__BITS4)                          \
+         ? (__xr < ((UWtype) 1 << __BITS4) ? 0 : __BITS4)              \
+         : (__xr < ((UWtype) 1 << 3*__BITS4) ?  2*__BITS4 : 3*__BITS4);\
+      }                                                                        \
+    else                                                               \
+      {                                                                        \
+       for (__a = W_TYPE_SIZE - 8; __a > 0; __a -= 8)                  \
+         if (((__xr >> __a) & 0xff) != 0)                              \
+           break;                                                      \
+      }                                                                        \
+                                                                       \
+    (count) = W_TYPE_SIZE - (__clz_tab[__xr >> __a] + __a);            \
+  } while (0)
+/* This version gives a well-defined value for zero. */
+#define COUNT_LEADING_ZEROS_0 W_TYPE_SIZE
+#endif
+
+#if !defined (count_trailing_zeros)
+/* Define count_trailing_zeros using count_leading_zeros.  The latter might be
+   defined in asm, but if it is not, the C version above is good enough.  */
+#define count_trailing_zeros(count, x) \
+  do {                                                                 \
+    UWtype __ctz_x = (x);                                              \
+    UWtype __ctz_c;                                                    \
+    count_leading_zeros (__ctz_c, __ctz_x & -__ctz_x);                 \
+    (count) = W_TYPE_SIZE - 1 - __ctz_c;                               \
+  } while (0)
+#endif
+
+#ifndef UDIV_NEEDS_NORMALIZATION
+#define UDIV_NEEDS_NORMALIZATION 0
+#endif
diff --git a/lib/gmp/memory.c b/lib/gmp/memory.c
new file mode 100644 (file)
index 0000000..2cd64a1
--- /dev/null
@@ -0,0 +1,98 @@
+/* Memory allocation routines.
+
+Copyright (C) 1991, 1993, 1994 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include <stdio.h>
+
+#include "gmp.h"
+#include "gmp-impl.h"
+
+#ifdef __NeXT__
+#define static
+#endif
+
+#if __STDC__
+void * (*_mp_allocate_func) (size_t) = _mp_default_allocate;
+void * (*_mp_reallocate_func) (void *, size_t, size_t)
+     = _mp_default_reallocate;
+void   (*_mp_free_func) (void *, size_t) = _mp_default_free;
+#else
+void * (*_mp_allocate_func) () = _mp_default_allocate;
+void * (*_mp_reallocate_func) () = _mp_default_reallocate;
+void   (*_mp_free_func) () = _mp_default_free;
+#endif
+
+/* Default allocation functions.  In case of failure to allocate/reallocate
+   an error message is written to stderr and the program aborts.  */
+
+void *
+#if __STDC__
+_mp_default_allocate (size_t size)
+#else
+_mp_default_allocate (size)
+     size_t size;
+#endif
+{
+  void *ret;
+
+  ret = malloc (size);
+  if (ret == 0)
+    {
+      perror ("cannot allocate in gmp");
+      abort ();
+    }
+
+  return ret;
+}
+
+void *
+#if __STDC__
+_mp_default_reallocate (void *oldptr, unused size_t old_size, size_t new_size)
+#else
+_mp_default_reallocate (oldptr, old_size, new_size)
+     void *oldptr;
+     size_t old_size;
+     size_t new_size;
+#endif
+{
+  void *ret;
+
+  ret = realloc (oldptr, new_size);
+  if (ret == 0)
+    {
+      perror ("cannot allocate in gmp");
+      abort ();
+    }
+
+  return ret;
+}
+
+void
+#if __STDC__
+_mp_default_free (void *blk_ptr, unused size_t blk_size)
+#else
+_mp_default_free (blk_ptr, blk_size)
+     void *blk_ptr;
+     size_t blk_size;
+#endif
+{
+  free (blk_ptr);
+}
diff --git a/lib/gmp/mp_clz_tab.c b/lib/gmp/mp_clz_tab.c
new file mode 100644 (file)
index 0000000..6fd7e90
--- /dev/null
@@ -0,0 +1,40 @@
+/* __clz_tab -- support for longlong.h
+
+Copyright (C) 1991, 1993, 1994, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#if 0
+#include "gmp.h"
+#include "gmp-impl.h"
+#endif
+
+#if 0
+const
+#endif
+unsigned char __clz_tab[] =
+{
+  0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
+  6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
+  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+  8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+  8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+  8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+  8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+};
diff --git a/lib/gmp/mpf/Makefile.am b/lib/gmp/mpf/Makefile.am
new file mode 100644 (file)
index 0000000..c048998
--- /dev/null
@@ -0,0 +1,9 @@
+## Process this file with automake to produce Makefile.in  -*- makefile -*-
+
+INCLUDES = -I$(srcdir) -I$(srcdir)/.. -I$(top_srcdir) -I$(top_srcdir)/src \
+       -I$(top_srcdir)/intl
+
+noinst_LIBRARIES = libmpf.a
+libmpf_a_SOURCES = clear.c get_str.c iset_d.c set_d.c set_dfl_prec.c
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/lib/gmp/mpf/clear.c b/lib/gmp/mpf/clear.c
new file mode 100644 (file)
index 0000000..00284f5
--- /dev/null
@@ -0,0 +1,36 @@
+/* mpf_clear -- de-allocate the space occupied by the dynamic digit space of
+   an integer.
+
+Copyright (C) 1993, 1994, 1995 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+
+void
+#if __STDC__
+mpf_clear (mpf_ptr m)
+#else
+mpf_clear (m)
+     mpf_ptr m;
+#endif
+{
+  (*_mp_free_func) (m->_mp_d, (m->_mp_prec + 1) * BYTES_PER_MP_LIMB);
+}
diff --git a/lib/gmp/mpf/get_str.c b/lib/gmp/mpf/get_str.c
new file mode 100644 (file)
index 0000000..f6cf10d
--- /dev/null
@@ -0,0 +1,501 @@
+/* mpf_get_str (digit_ptr, exp, base, n_digits, a) -- Convert the floating
+  point number A to a base BASE number and store N_DIGITS raw digits at
+  DIGIT_PTR, and the base BASE exponent in the word pointed to by EXP.  For
+  example, the number 3.1416 would be returned as "31416" in DIGIT_PTR and
+  1 in EXP.
+
+Copyright (C) 1993, 1994, 1995, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+#include "longlong.h"
+
+/*
+   New algorithm for converting fractions (951019):
+   0. Call the fraction to convert F.
+   1. Compute [exp * log(2^BITS_PER_MP_LIMB)/log(B)], i.e.,
+      [exp * BITS_PER_MP_LIMB * __mp_bases[B].chars_per_bit_exactly].  Exp is
+      the number of limbs between the limb point and the most significant
+      non-zero limb.  Call this result n.
+   2. Compute B^n.
+   3. F*B^n will now be just below 1, which can be converted easily.  (Just
+      multiply by B repeatedly, and see the digits fall out as integers.)
+   We should interrupt the conversion process of F*B^n as soon as the number
+   of digits requested have been generated.
+
+   New algorithm for converting integers (951019):
+   0. Call the integer to convert I.
+   1. Compute [exp * log(2^BITS_PER_MP_LIMB)/log(B)], i.e.,
+      [exp BITS_PER_MP_LIMB * __mp_bases[B].chars_per_bit_exactly].  Exp is
+      the number of limbs between the limb point and the least significant
+      non-zero limb.  Call this result n.
+   2. Compute B^n.
+   3. I/B^n can be converted easily.  (Just divide by B repeatedly.  In GMP,
+      this is best done by calling mpn_get_str.)
+   Note that converting I/B^n could yield more digits than requested.  For
+   efficiency, the variable n above should be set larger in such cases, to
+   kill all undesired digits in the division in step 3.
+*/
+
+char *
+#if __STDC__
+mpf_get_str (char *digit_ptr, mp_exp_t *exp, int base, size_t n_digits, mpf_srcptr u)
+#else
+mpf_get_str (digit_ptr, exp, base, n_digits, u)
+     char *digit_ptr;
+     mp_exp_t *exp;
+     int base;
+     size_t n_digits;
+     mpf_srcptr u;
+#endif
+{
+  mp_size_t usize;
+  mp_exp_t uexp;
+  unsigned char *str;
+  size_t str_size;
+  char *num_to_text;
+  long i;                      /* should be size_t */
+  mp_ptr rp;
+  mp_limb_t big_base;
+  size_t digits_computed_so_far;
+  int dig_per_u;
+  mp_srcptr up;
+  unsigned char *tstr;
+  mp_exp_t exp_in_base;
+  TMP_DECL (marker);
+
+  TMP_MARK (marker);
+  usize = u->_mp_size;
+  uexp = u->_mp_exp;
+
+  if (base >= 0)
+    {
+      if (base == 0)
+       base = 10;
+      num_to_text = (char *) "0123456789abcdefghijklmnopqrstuvwxyz";
+    }
+  else
+    {
+      base = -base;
+      num_to_text = (char *) "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+    }
+
+  /* Don't compute more digits than U can accurately represent.
+     Also, if 0 digits were requested, give *exactly* as many digits
+     as can be accurately represented.  */
+  {
+    size_t max_digits = (((u->_mp_prec - 1) * BITS_PER_MP_LIMB)
+                        * __mp_bases[base].chars_per_bit_exactly);
+    if (n_digits == 0 || n_digits > max_digits)
+      n_digits = max_digits;
+  }
+
+  if (digit_ptr == 0)
+    {
+      /* We didn't get a string from the user.  Allocate one (and return
+        a pointer to it) with space for `-' and terminating null.  */
+      digit_ptr = (char *) (*_mp_allocate_func) (n_digits + 2);
+    }
+
+  if (usize == 0)
+    {
+      *exp = 0;
+      *digit_ptr = 0;
+      return digit_ptr;
+    }
+
+  str = (unsigned char *) digit_ptr;
+
+  /* Allocate temporary digit space.  We can't put digits directly in the user
+     area, since we almost always generate more digits than requested.  */
+  tstr = (unsigned char *) TMP_ALLOC (n_digits + 3 * BITS_PER_MP_LIMB);
+
+  if (usize < 0)
+    {
+      *digit_ptr = '-';
+      str++;
+      usize = -usize;
+    }
+
+  digits_computed_so_far = 0;
+
+  if (uexp > usize)
+    {
+      /* The number has just an integral part.  */
+      mp_size_t rsize;
+      mp_size_t exp_in_limbs;
+      mp_size_t msize;
+      mp_ptr tp, xp, mp;
+      int cnt;
+      mp_limb_t cy;
+      mp_size_t start_str;
+      mp_size_t n_limbs;
+
+      n_limbs = 2 + ((mp_size_t) (n_digits / __mp_bases[base].chars_per_bit_exactly)
+                    / BITS_PER_MP_LIMB);
+
+      /* Compute n such that [u/B^n] contains (somewhat) more than n_digits
+        digits.  (We compute less than that only if that is an exact number,
+        i.e., exp is small enough.)  */
+
+      exp_in_limbs = uexp;
+
+      if (n_limbs >= exp_in_limbs)
+       {
+         /* The number is so small that we convert the entire number.  */
+         exp_in_base = 0;
+         rp = (mp_ptr) TMP_ALLOC (exp_in_limbs * BYTES_PER_MP_LIMB);
+         MPN_ZERO (rp, exp_in_limbs - usize);
+         MPN_COPY (rp + (exp_in_limbs - usize), u->_mp_d, usize);
+         rsize = exp_in_limbs;
+       }
+      else
+       {
+         exp_in_limbs -= n_limbs;
+         exp_in_base = (((exp_in_limbs * BITS_PER_MP_LIMB - 1))
+                        * __mp_bases[base].chars_per_bit_exactly);
+
+         rsize = exp_in_limbs + 1;
+         rp = (mp_ptr) TMP_ALLOC (rsize * BYTES_PER_MP_LIMB);
+         tp = (mp_ptr) TMP_ALLOC (rsize * BYTES_PER_MP_LIMB);
+
+         rp[0] = base;
+         rsize = 1;
+
+         count_leading_zeros (cnt, exp_in_base);
+         for (i = BITS_PER_MP_LIMB - cnt - 2; i >= 0; i--)
+           {
+             mpn_mul_n (tp, rp, rp, rsize);
+             rsize = 2 * rsize;
+             rsize -= tp[rsize - 1] == 0;
+             xp = tp; tp = rp; rp = xp;
+
+             if (((exp_in_base >> i) & 1) != 0)
+               {
+                 cy = mpn_mul_1 (rp, rp, rsize, (mp_limb_t) base);
+                 rp[rsize] = cy;
+                 rsize += cy != 0;
+               }
+           }
+
+         mp = u->_mp_d;
+         msize = usize;
+
+         {
+           mp_ptr qp;
+           mp_limb_t qflag;
+           mp_size_t xtra;
+           if (msize < rsize)
+             {
+               mp_ptr tmp = (mp_ptr) TMP_ALLOC ((rsize+1)* BYTES_PER_MP_LIMB);
+               MPN_ZERO (tmp, rsize - msize);
+               MPN_COPY (tmp + rsize - msize, mp, msize);
+               mp = tmp;
+               msize = rsize;
+             }
+           else
+             {
+               mp_ptr tmp = (mp_ptr) TMP_ALLOC ((msize+1)* BYTES_PER_MP_LIMB);
+               MPN_COPY (tmp, mp, msize);
+               mp = tmp;
+             }
+           count_leading_zeros (cnt, rp[rsize - 1]);
+           cy = 0;
+           if (cnt != 0)
+             {
+               mpn_lshift (rp, rp, rsize, cnt);
+               cy = mpn_lshift (mp, mp, msize, cnt);
+               if (cy)
+                 mp[msize++] = cy;
+             }
+
+           {
+             mp_size_t qsize = n_limbs + (cy != 0);
+             qp = (mp_ptr) TMP_ALLOC ((qsize + 1) * BYTES_PER_MP_LIMB);
+             xtra = qsize - (msize - rsize);
+             qflag = mpn_divrem (qp, xtra, mp, msize, rp, rsize);
+             qp[qsize] = qflag;
+             rsize = qsize + qflag;
+             rp = qp;
+           }
+         }
+       }
+
+      str_size = mpn_get_str (tstr, base, rp, rsize);
+
+      if (str_size > n_digits + 3 * BITS_PER_MP_LIMB)
+       abort ();
+
+      start_str = 0;
+      while (tstr[start_str] == 0)
+       start_str++;
+
+      for (i = start_str; i < (int) str_size; i++)
+       {
+         tstr[digits_computed_so_far++] = tstr[i];
+         if (digits_computed_so_far > n_digits)
+           break;
+       }
+      exp_in_base = exp_in_base + str_size - start_str;
+      goto finish_up;
+    }
+
+  exp_in_base = 0;
+
+  if (uexp > 0)
+    {
+      /* The number has an integral part, convert that first.
+        If there is a fractional part too, it will be handled later.  */
+      mp_size_t start_str;
+
+      rp = (mp_ptr) TMP_ALLOC (uexp * BYTES_PER_MP_LIMB);
+      up = u->_mp_d + usize - uexp;
+      MPN_COPY (rp, up, uexp);
+
+      str_size = mpn_get_str (tstr, base, rp, uexp);
+
+      start_str = 0;
+      while (tstr[start_str] == 0)
+       start_str++;
+
+      for (i = start_str; i < (int) str_size; i++)
+       {
+         tstr[digits_computed_so_far++] = tstr[i];
+         if (digits_computed_so_far > n_digits)
+           {
+             exp_in_base = str_size - start_str;
+             goto finish_up;
+           }
+       }
+
+      exp_in_base = str_size - start_str;
+      /* Modify somewhat and fall out to convert fraction... */
+      usize -= uexp;
+      uexp = 0;
+    }
+
+  if (usize <= 0)
+    goto finish_up;
+
+  /* Convert the fraction.  */
+  {
+    mp_size_t rsize, msize;
+    mp_ptr rp, tp, xp, mp;
+    int cnt;
+    mp_limb_t cy;
+    mp_exp_t nexp;
+
+    big_base = __mp_bases[base].big_base;
+    dig_per_u = __mp_bases[base].chars_per_limb;
+
+    /* Hack for correctly (although not efficiently) converting to bases that
+       are powers of 2.  If we deem it important, we could handle powers of 2
+       by shifting and masking (just like mpn_get_str).  */
+    if (big_base < 10)         /* logarithm of base when power of two */
+      {
+       int logbase = big_base;
+       if (dig_per_u * logbase == BITS_PER_MP_LIMB)
+         dig_per_u--;
+       big_base = (mp_limb_t) 1 << (dig_per_u * logbase);
+       /* fall out to general code... */
+      }
+
+#if 0
+    if (0 && uexp == 0)
+      {
+       rp = (mp_ptr) TMP_ALLOC (usize * BYTES_PER_MP_LIMB);
+       up = u->_mp_d;
+       MPN_COPY (rp, up, usize);
+       rsize = usize;
+       nexp = 0;
+      }
+    else
+      {}
+#endif
+    uexp = -uexp;
+    if (u->_mp_d[usize - 1] == 0)
+      cnt = 0;
+    else
+      count_leading_zeros (cnt, u->_mp_d[usize - 1]);
+
+    nexp = ((uexp * BITS_PER_MP_LIMB) + cnt)
+      * __mp_bases[base].chars_per_bit_exactly;
+
+    if (nexp == 0)
+      {
+       rp = (mp_ptr) TMP_ALLOC (usize * BYTES_PER_MP_LIMB);
+       up = u->_mp_d;
+       MPN_COPY (rp, up, usize);
+       rsize = usize;
+      }
+    else
+      {
+       rsize = uexp + 2;
+       rp = (mp_ptr) TMP_ALLOC (rsize * BYTES_PER_MP_LIMB);
+       tp = (mp_ptr) TMP_ALLOC (rsize * BYTES_PER_MP_LIMB);
+
+       rp[0] = base;
+       rsize = 1;
+
+       count_leading_zeros (cnt, nexp);
+       for (i = BITS_PER_MP_LIMB - cnt - 2; i >= 0; i--)
+         {
+           mpn_mul_n (tp, rp, rp, rsize);
+           rsize = 2 * rsize;
+           rsize -= tp[rsize - 1] == 0;
+           xp = tp; tp = rp; rp = xp;
+
+           if (((nexp >> i) & 1) != 0)
+             {
+               cy = mpn_mul_1 (rp, rp, rsize, (mp_limb_t) base);
+               rp[rsize] = cy;
+               rsize += cy != 0;
+             }
+         }
+
+       /* Did our multiplier (base^nexp) cancel with uexp?  */
+#if 0
+       if (uexp != rsize)
+         {
+           do
+             {
+               cy = mpn_mul_1 (rp, rp, rsize, big_base);
+               nexp += dig_per_u;
+             }
+           while (cy == 0);
+           rp[rsize++] = cy;
+         }
+#endif
+       mp = u->_mp_d;
+       msize = usize;
+
+       tp = (mp_ptr) TMP_ALLOC ((rsize + msize) * BYTES_PER_MP_LIMB);
+       if (rsize > msize)
+         cy = mpn_mul (tp, rp, rsize, mp, msize);
+       else
+         cy = mpn_mul (tp, mp, msize, rp, rsize);
+       rsize += msize;
+       rsize -= cy == 0;
+       rp = tp;
+
+       /* If we already output digits (for an integral part) pad
+          leading zeros.  */
+       if (digits_computed_so_far != 0)
+         for (i = 0; i < nexp; i++)
+           tstr[digits_computed_so_far++] = 0;
+      }
+
+    while (digits_computed_so_far <= n_digits)
+      {
+       /* For speed: skip trailing zeroes.  */
+       if (rp[0] == 0)
+         {
+           rp++;
+           rsize--;
+           if (rsize == 0)
+             {
+               n_digits = digits_computed_so_far;
+               break;
+             }
+         }
+
+       cy = mpn_mul_1 (rp, rp, rsize, big_base);
+       if (digits_computed_so_far == 0 && cy == 0)
+         {
+           abort ();
+           nexp += dig_per_u;
+           continue;
+         }
+       /* Convert N1 from BIG_BASE to a string of digits in BASE
+          using single precision operations.  */
+       {
+         unsigned char *s = tstr + digits_computed_so_far + dig_per_u;
+         for (i = dig_per_u - 1; i >= 0; i--)
+           {
+             *--s = cy % base;
+             cy /= base;
+           }
+       }
+       digits_computed_so_far += dig_per_u;
+      }
+    if (exp_in_base == 0)
+      exp_in_base = -nexp;
+  }
+
+ finish_up:
+
+  /* We can have at most one leading 0.  Remove it.  */
+  if (tstr[0] == 0)
+    {
+      tstr++;
+      digits_computed_so_far--;
+      exp_in_base--;
+    }
+
+  /* We should normally have computed too many digits.  Round the result
+     at the point indicated by n_digits.  */
+  if (digits_computed_so_far > n_digits)
+    {
+      /* Round the result.  */
+      if (tstr[n_digits] * 2 >= base)
+       {
+         digits_computed_so_far = n_digits;
+         for (i = n_digits - 1; i >= 0; i--)
+           {
+             unsigned int x;
+             x = ++(tstr[i]);
+             if ((int) x < base)
+               goto rounded_ok;
+             digits_computed_so_far--;
+           }
+         tstr[0] = 1;
+         digits_computed_so_far = 1;
+         exp_in_base++;
+       rounded_ok:;
+       }
+    }
+
+  /* We might have fewer digits than requested as a result of rounding above,
+     (i.e. 0.999999 => 1.0) or because we have a number that simply doesn't
+     need many digits in this base (i.e., 0.125 in base 10).  */
+  if (n_digits > digits_computed_so_far)
+    n_digits = digits_computed_so_far;
+
+  /* Remove trailing 0.  There can be many zeros. */
+  while (n_digits != 0 && tstr[n_digits - 1] == 0)
+    n_digits--;
+
+  /* Translate to ascii and null-terminate.  */
+  for (i = 0; i < (int) n_digits; i++)
+    *str++ = num_to_text[tstr[i]];
+  *str = 0;
+  *exp = exp_in_base;
+  TMP_FREE (marker);
+  return digit_ptr;
+}
+
+#if COPY_THIS_TO_OTHER_PLACES
+      /* Use this expression in lots of places in the library instead of the
+        count_leading_zeros+expression that is used currently.  This expression
+        is much more accurate and will save odles of memory.  */
+      rsize = ((mp_size_t) (exp_in_base / __mp_bases[base].chars_per_bit_exactly)
+              + BITS_PER_MP_LIMB) / BITS_PER_MP_LIMB;
+#endif
diff --git a/lib/gmp/mpf/iset_d.c b/lib/gmp/mpf/iset_d.c
new file mode 100644 (file)
index 0000000..3d4427e
--- /dev/null
@@ -0,0 +1,40 @@
+/* mpf_init_set_d -- Initialize a float and assign it from a double.
+
+Copyright (C) 1993, 1994, 1995 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+
+void
+#if __STDC__
+mpf_init_set_d (mpf_ptr r, double val)
+#else
+mpf_init_set_d (r, val)
+     mpf_ptr r;
+     double val;
+#endif
+{
+  mp_size_t prec = __gmp_default_fp_limb_precision;
+  r->_mp_d = (mp_ptr) (*_mp_allocate_func) ((prec + 1) * BYTES_PER_MP_LIMB);
+  r->_mp_prec = prec;
+
+  mpf_set_d (r, val);
+}
diff --git a/lib/gmp/mpf/set_d.c b/lib/gmp/mpf/set_d.c
new file mode 100644 (file)
index 0000000..a9fcfed
--- /dev/null
@@ -0,0 +1,48 @@
+/* mpf_set_d -- Assign a float from a IEEE double.
+
+Copyright (C) 1993, 1994, 1995, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+
+void
+#if __STDC__
+mpf_set_d (mpf_ptr r, double d)
+#else
+mpf_set_d (r, d)
+     mpf_ptr r;
+     double d;
+#endif
+{
+  int negative;
+
+  if (d == 0)
+    {
+      SIZ(r) = 0;
+      EXP(r) = 0;
+      return;
+    }
+  negative = d < 0;
+  d = ABS (d);
+
+  EXP(r) = __gmp_extract_double (PTR(r), d);
+  SIZ(r) = negative ? -LIMBS_PER_DOUBLE : LIMBS_PER_DOUBLE;
+}
diff --git a/lib/gmp/mpf/set_dfl_prec.c b/lib/gmp/mpf/set_dfl_prec.c
new file mode 100644 (file)
index 0000000..c8db2d6
--- /dev/null
@@ -0,0 +1,41 @@
+/* mpf_set_default_prec --
+
+Copyright (C) 1993, 1994, 1995 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+
+mp_size_t __gmp_default_fp_limb_precision
+  = (53 + 2 * BITS_PER_MP_LIMB - 1) / BITS_PER_MP_LIMB;
+
+void
+#if __STDC__
+mpf_set_default_prec (unsigned long int prec_in_bits)
+#else
+mpf_set_default_prec (prec_in_bits)
+     unsigned long int prec_in_bits;
+#endif
+{
+  mp_size_t prec;
+
+  prec = (MAX (53, prec_in_bits) + 2 * BITS_PER_MP_LIMB - 1)/BITS_PER_MP_LIMB;
+  __gmp_default_fp_limb_precision = prec;
+}
diff --git a/lib/gmp/mpn/Makefile.am b/lib/gmp/mpn/Makefile.am
new file mode 100644 (file)
index 0000000..80b7c64
--- /dev/null
@@ -0,0 +1,10 @@
+## Process this file with automake to produce Makefile.in  -*- makefile -*-
+
+INCLUDES = -I$(srcdir) -I$(srcdir)/.. -I$(top_srcdir) -I$(top_srcdir)/src \
+       -I$(top_srcdir)/intl
+
+noinst_LIBRARIES = libmpn.a
+libmpn_a_SOURCES = add_n.c addmul_1.c cmp.c divrem.c get_str.c \
+inlines.c lshift.c mp_bases.c mul.c mul_1.c mul_n.c sub_n.c submul_1.c
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/lib/gmp/mpn/add_n.c b/lib/gmp/mpn/add_n.c
new file mode 100644 (file)
index 0000000..ecaec46
--- /dev/null
@@ -0,0 +1,63 @@
+/* mpn_add_n -- Add two limb vectors of equal, non-zero length.
+
+Copyright (C) 1992, 1993, 1994, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+
+mp_limb_t
+#if __STDC__
+mpn_add_n (mp_ptr res_ptr, mp_srcptr s1_ptr, mp_srcptr s2_ptr, mp_size_t size)
+#else
+mpn_add_n (res_ptr, s1_ptr, s2_ptr, size)
+     register mp_ptr res_ptr;
+     register mp_srcptr s1_ptr;
+     register mp_srcptr s2_ptr;
+     mp_size_t size;
+#endif
+{
+  register mp_limb_t x, y, cy;
+  register mp_size_t j;
+
+  /* The loop counter and index J goes from -SIZE to -1.  This way
+     the loop becomes faster.  */
+  j = -size;
+
+  /* Offset the base pointers to compensate for the negative indices.  */
+  s1_ptr -= j;
+  s2_ptr -= j;
+  res_ptr -= j;
+
+  cy = 0;
+  do
+    {
+      y = s2_ptr[j];
+      x = s1_ptr[j];
+      y += cy;                 /* add previous carry to one addend */
+      cy = (y < cy);           /* get out carry from that addition */
+      y = x + y;               /* add other addend */
+      cy = (y < x) + cy;       /* get out carry from that add, combine */
+      res_ptr[j] = y;
+    }
+  while (++j != 0);
+
+  return cy;
+}
diff --git a/lib/gmp/mpn/addmul_1.c b/lib/gmp/mpn/addmul_1.c
new file mode 100644 (file)
index 0000000..ec58091
--- /dev/null
@@ -0,0 +1,66 @@
+/* mpn_addmul_1 -- multiply the S1_SIZE long limb vector pointed to by S1_PTR
+   by S2_LIMB, add the S1_SIZE least significant limbs of the product to the
+   limb vector pointed to by RES_PTR.  Return the most significant limb of
+   the product, adjusted for carry-out from the addition.
+
+Copyright (C) 1992, 1993, 1994, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+#include "longlong.h"
+
+mp_limb_t
+mpn_addmul_1 (res_ptr, s1_ptr, s1_size, s2_limb)
+     register mp_ptr res_ptr;
+     register mp_srcptr s1_ptr;
+     mp_size_t s1_size;
+     register mp_limb_t s2_limb;
+{
+  register mp_limb_t cy_limb;
+  register mp_size_t j;
+  register mp_limb_t prod_high, prod_low;
+  register mp_limb_t x;
+
+  /* The loop counter and index J goes from -SIZE to -1.  This way
+     the loop becomes faster.  */
+  j = -s1_size;
+
+  /* Offset the base pointers to compensate for the negative indices.  */
+  res_ptr -= j;
+  s1_ptr -= j;
+
+  cy_limb = 0;
+  do
+    {
+      umul_ppmm (prod_high, prod_low, s1_ptr[j], s2_limb);
+
+      prod_low += cy_limb;
+      cy_limb = (prod_low < cy_limb) + prod_high;
+
+      x = res_ptr[j];
+      prod_low = x + prod_low;
+      cy_limb += (prod_low < x);
+      res_ptr[j] = prod_low;
+    }
+  while (++j != 0);
+
+  return cy_limb;
+}
diff --git a/lib/gmp/mpn/cmp.c b/lib/gmp/mpn/cmp.c
new file mode 100644 (file)
index 0000000..95d44f9
--- /dev/null
@@ -0,0 +1,57 @@
+/* mpn_cmp -- Compare two low-level natural-number integers.
+
+Copyright (C) 1991, 1993, 1994, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+
+/* Compare OP1_PTR/OP1_SIZE with OP2_PTR/OP2_SIZE.
+   There are no restrictions on the relative sizes of
+   the two arguments.
+   Return 1 if OP1 > OP2, 0 if they are equal, and -1 if OP1 < OP2.  */
+
+int
+#if __STDC__
+mpn_cmp (mp_srcptr op1_ptr, mp_srcptr op2_ptr, mp_size_t size)
+#else
+mpn_cmp (op1_ptr, op2_ptr, size)
+     mp_srcptr op1_ptr;
+     mp_srcptr op2_ptr;
+     mp_size_t size;
+#endif
+{
+  mp_size_t i;
+  mp_limb_t op1_word, op2_word;
+
+  for (i = size - 1; i >= 0; i--)
+    {
+      op1_word = op1_ptr[i];
+      op2_word = op2_ptr[i];
+      if (op1_word != op2_word)
+       goto diff;
+    }
+  return 0;
+ diff:
+  /* This can *not* be simplified to
+       op2_word - op2_word
+     since that expression might give signed overflow.  */
+  return (op1_word > op2_word) ? 1 : -1;
+}
diff --git a/lib/gmp/mpn/divrem.c b/lib/gmp/mpn/divrem.c
new file mode 100644 (file)
index 0000000..1b41f6c
--- /dev/null
@@ -0,0 +1,246 @@
+/* mpn_divrem -- Divide natural numbers, producing both remainder and
+   quotient.
+
+Copyright (C) 1993, 1994, 1995, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+#include "longlong.h"
+
+/* Divide num (NP/NSIZE) by den (DP/DSIZE) and write
+   the NSIZE-DSIZE least significant quotient limbs at QP
+   and the DSIZE long remainder at NP.  If QEXTRA_LIMBS is
+   non-zero, generate that many fraction bits and append them after the
+   other quotient limbs.
+   Return the most significant limb of the quotient, this is always 0 or 1.
+
+   Preconditions:
+   0. NSIZE >= DSIZE.
+   1. The most significant bit of the divisor must be set.
+   2. QP must either not overlap with the input operands at all, or
+      QP + DSIZE >= NP must hold true.  (This means that it's
+      possible to put the quotient in the high part of NUM, right after the
+      remainder in NUM.
+   3. NSIZE >= DSIZE, even if QEXTRA_LIMBS is non-zero.  */
+
+mp_limb_t
+#if __STDC__
+mpn_divrem (mp_ptr qp, mp_size_t qextra_limbs,
+           mp_ptr np, mp_size_t nsize,
+           mp_srcptr dp, mp_size_t dsize)
+#else
+mpn_divrem (qp, qextra_limbs, np, nsize, dp, dsize)
+     mp_ptr qp;
+     mp_size_t qextra_limbs;
+     mp_ptr np;
+     mp_size_t nsize;
+     mp_srcptr dp;
+     mp_size_t dsize;
+#endif
+{
+  mp_limb_t most_significant_q_limb = 0;
+
+  switch (dsize)
+    {
+    case 0:
+      /* We are asked to divide by zero, so go ahead and do it!  (To make
+        the compiler not remove this statement, return the value.)  */
+      return 1 / dsize;
+
+    case 1:
+      {
+       mp_size_t i;
+       mp_limb_t n1;
+       mp_limb_t d;
+
+       d = dp[0];
+       n1 = np[nsize - 1];
+
+       if (n1 >= d)
+         {
+           n1 -= d;
+           most_significant_q_limb = 1;
+         }
+
+       qp += qextra_limbs;
+       for (i = nsize - 2; i >= 0; i--)
+         udiv_qrnnd (qp[i], n1, n1, np[i], d);
+       qp -= qextra_limbs;
+
+       for (i = qextra_limbs - 1; i >= 0; i--)
+         udiv_qrnnd (qp[i], n1, n1, 0, d);
+
+       np[0] = n1;
+      }
+      break;
+
+    case 2:
+      {
+       mp_size_t i;
+       mp_limb_t n1, n0, n2;
+       mp_limb_t d1, d0;
+
+       np += nsize - 2;
+       d1 = dp[1];
+       d0 = dp[0];
+       n1 = np[1];
+       n0 = np[0];
+
+       if (n1 >= d1 && (n1 > d1 || n0 >= d0))
+         {
+           sub_ddmmss (n1, n0, n1, n0, d1, d0);
+           most_significant_q_limb = 1;
+         }
+
+       for (i = qextra_limbs + nsize - 2 - 1; i >= 0; i--)
+         {
+           mp_limb_t q;
+           mp_limb_t r;
+
+           if (i >= qextra_limbs)
+             np--;
+           else
+             np[0] = 0;
+
+           if (n1 == d1)
+             {
+               /* Q should be either 111..111 or 111..110.  Need special
+                  treatment of this rare case as normal division would
+                  give overflow.  */
+               q = ~(mp_limb_t) 0;
+
+               r = n0 + d1;
+               if (r < d1)     /* Carry in the addition? */
+                 {
+                   add_ssaaaa (n1, n0, r - d0, np[0], 0, d0);
+                   qp[i] = q;
+                   continue;
+                 }
+               n1 = d0 - (d0 != 0);
+               n0 = -d0;
+             }
+           else
+             {
+               udiv_qrnnd (q, r, n1, n0, d1);
+               umul_ppmm (n1, n0, d0, q);
+             }
+
+           n2 = np[0];
+         q_test:
+           if (n1 > r || (n1 == r && n0 > n2))
+             {
+               /* The estimated Q was too large.  */
+               q--;
+
+               sub_ddmmss (n1, n0, n1, n0, 0, d0);
+               r += d1;
+               if (r >= d1)    /* If not carry, test Q again.  */
+                 goto q_test;
+             }
+
+           qp[i] = q;
+           sub_ddmmss (n1, n0, r, n2, n1, n0);
+         }
+       np[1] = n1;
+       np[0] = n0;
+      }
+      break;
+
+    default:
+      {
+       mp_size_t i;
+       mp_limb_t dX, d1, n0;
+
+       np += nsize - dsize;
+       dX = dp[dsize - 1];
+       d1 = dp[dsize - 2];
+       n0 = np[dsize - 1];
+
+       if (n0 >= dX)
+         {
+           if (n0 > dX || mpn_cmp (np, dp, dsize - 1) >= 0)
+             {
+               mpn_sub_n (np, np, dp, dsize);
+               n0 = np[dsize - 1];
+               most_significant_q_limb = 1;
+             }
+         }
+
+       for (i = qextra_limbs + nsize - dsize - 1; i >= 0; i--)
+         {
+           mp_limb_t q;
+           mp_limb_t n1, n2;
+           mp_limb_t cy_limb;
+
+           if (i >= qextra_limbs)
+             {
+               np--;
+               n2 = np[dsize];
+             }
+           else
+             {
+               n2 = np[dsize - 1];
+               MPN_COPY_DECR (np + 1, np, dsize);
+               np[0] = 0;
+             }
+
+           if (n0 == dX)
+             /* This might over-estimate q, but it's probably not worth
+                the extra code here to find out.  */
+             q = ~(mp_limb_t) 0;
+           else
+             {
+               mp_limb_t r;
+
+               udiv_qrnnd (q, r, n0, np[dsize - 1], dX);
+               umul_ppmm (n1, n0, d1, q);
+
+               while (n1 > r || (n1 == r && n0 > np[dsize - 2]))
+                 {
+                   q--;
+                   r += dX;
+                   if (r < dX) /* I.e. "carry in previous addition?"  */
+                     break;
+                   n1 -= n0 < d1;
+                   n0 -= d1;
+                 }
+             }
+
+           /* Possible optimization: We already have (q * n0) and (1 * n1)
+              after the calculation of q.  Taking advantage of that, we
+              could make this loop make two iterations less.  */
+
+           cy_limb = mpn_submul_1 (np, dp, dsize, q);
+
+           if (n2 != cy_limb)
+             {
+               mpn_add_n (np, np, dp, dsize);
+               q--;
+             }
+
+           qp[i] = q;
+           n0 = np[dsize - 1];
+         }
+      }
+    }
+
+  return most_significant_q_limb;
+}
diff --git a/lib/gmp/mpn/get_str.c b/lib/gmp/mpn/get_str.c
new file mode 100644 (file)
index 0000000..77c1643
--- /dev/null
@@ -0,0 +1,212 @@
+/* mpn_get_str -- Convert a MSIZE long limb vector pointed to by MPTR
+   to a printable string in STR in base BASE.
+
+Copyright (C) 1991, 1992, 1993, 1994, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+#include "longlong.h"
+
+/* Convert the limb vector pointed to by MPTR and MSIZE long to a
+   char array, using base BASE for the result array.  Store the
+   result in the character array STR.  STR must point to an array with
+   space for the largest possible number represented by a MSIZE long
+   limb vector + 1 extra character.
+
+   The result is NOT in Ascii, to convert it to printable format, add
+   '0' or 'A' depending on the base and range.
+
+   Return the number of digits in the result string.
+   This may include some leading zeros.
+
+   The limb vector pointed to by MPTR is clobbered.  */
+
+size_t
+mpn_get_str (str, base, mptr, msize)
+     unsigned char *str;
+     int base;
+     mp_ptr mptr;
+     mp_size_t msize;
+{
+  mp_limb_t big_base;
+#if UDIV_NEEDS_NORMALIZATION || UDIV_TIME > 2 * UMUL_TIME
+  int normalization_steps;
+#endif
+#if UDIV_TIME > 2 * UMUL_TIME
+  mp_limb_t big_base_inverted;
+#endif
+  unsigned int dig_per_u;
+  mp_size_t out_len;
+  register unsigned char *s;
+
+  big_base = __mp_bases[base].big_base;
+
+  s = str;
+
+  /* Special case zero, as the code below doesn't handle it.  */
+  if (msize == 0)
+    {
+      s[0] = 0;
+      return 1;
+    }
+
+  if ((base & (base - 1)) == 0)
+    {
+      /* The base is a power of 2.  Make conversion from most
+        significant side.  */
+      mp_limb_t n1, n0;
+      register int bits_per_digit = big_base;
+      register int x;
+      register int bit_pos;
+      register int i;
+
+      n1 = mptr[msize - 1];
+      count_leading_zeros (x, n1);
+
+       /* BIT_POS should be R when input ends in least sign. nibble,
+          R + bits_per_digit * n when input ends in n:th least significant
+          nibble. */
+
+      {
+       int bits;
+
+       bits = BITS_PER_MP_LIMB * msize - x;
+       x = bits % bits_per_digit;
+       if (x != 0)
+         bits += bits_per_digit - x;
+       bit_pos = bits - (msize - 1) * BITS_PER_MP_LIMB;
+      }
+
+      /* Fast loop for bit output.  */
+      i = msize - 1;
+      for (;;)
+       {
+         bit_pos -= bits_per_digit;
+         while (bit_pos >= 0)
+           {
+             *s++ = (n1 >> bit_pos) & ((1 << bits_per_digit) - 1);
+             bit_pos -= bits_per_digit;
+           }
+         i--;
+         if (i < 0)
+           break;
+         n0 = (n1 << -bit_pos) & ((1 << bits_per_digit) - 1);
+         n1 = mptr[i];
+         bit_pos += BITS_PER_MP_LIMB;
+         *s++ = n0 | (n1 >> bit_pos);
+       }
+
+      *s = 0;
+
+      return s - str;
+    }
+  else
+    {
+      /* General case.  The base is not a power of 2.  Make conversion
+        from least significant end.  */
+
+      /* If udiv_qrnnd only handles divisors with the most significant bit
+        set, prepare BIG_BASE for being a divisor by shifting it to the
+        left exactly enough to set the most significant bit.  */
+#if UDIV_NEEDS_NORMALIZATION || UDIV_TIME > 2 * UMUL_TIME
+      count_leading_zeros (normalization_steps, big_base);
+      big_base <<= normalization_steps;
+#if UDIV_TIME > 2 * UMUL_TIME
+      /* Get the fixed-point approximation to 1/(BIG_BASE << NORMALIZATION_STEPS).  */
+      big_base_inverted = __mp_bases[base].big_base_inverted;
+#endif
+#endif
+
+      dig_per_u = __mp_bases[base].chars_per_limb;
+      out_len = ((size_t) msize * BITS_PER_MP_LIMB
+                * __mp_bases[base].chars_per_bit_exactly) + 1;
+      s += out_len;
+
+      while (msize != 0)
+       {
+         int i;
+         mp_limb_t n0, n1;
+
+#if UDIV_NEEDS_NORMALIZATION || UDIV_TIME > 2 * UMUL_TIME
+         /* If we shifted BIG_BASE above, shift the dividend too, to get
+            the right quotient.  We need to do this every loop,
+            since the intermediate quotients are OK, but the quotient from
+            one turn in the loop is going to be the dividend in the
+            next turn, and the dividend needs to be up-shifted.  */
+         if (normalization_steps != 0)
+           {
+             n0 = mpn_lshift (mptr, mptr, msize, normalization_steps);
+
+             /* If the shifting gave a carry out limb, store it and
+                increase the length.  */
+             if (n0 != 0)
+               {
+                 mptr[msize] = n0;
+                 msize++;
+               }
+           }
+#endif
+
+         /* Divide the number at TP with BIG_BASE to get a quotient and a
+            remainder.  The remainder is our new digit in base BIG_BASE.  */
+         i = msize - 1;
+         n1 = mptr[i];
+
+         if (n1 >= big_base)
+           n1 = 0;
+         else
+           {
+             msize--;
+             i--;
+           }
+
+         for (; i >= 0; i--)
+           {
+             n0 = mptr[i];
+#if UDIV_TIME > 2 * UMUL_TIME
+             udiv_qrnnd_preinv (mptr[i], n1, n1, n0, big_base, big_base_inverted);
+#else
+             udiv_qrnnd (mptr[i], n1, n1, n0, big_base);
+#endif
+           }
+
+#if UDIV_NEEDS_NORMALIZATION || UDIV_TIME > 2 * UMUL_TIME
+         /* If we shifted above (at previous UDIV_NEEDS_NORMALIZATION tests)
+            the remainder will be up-shifted here.  Compensate.  */
+         n1 >>= normalization_steps;
+#endif
+
+         /* Convert N1 from BIG_BASE to a string of digits in BASE
+            using single precision operations.  */
+         for (i = dig_per_u - 1; i >= 0; i--)
+           {
+             *--s = n1 % base;
+             n1 /= base;
+             if (n1 == 0 && msize == 0)
+               break;
+           }
+       }
+
+      while (s != str)
+       *--s = 0;
+      return out_len;
+    }
+}
diff --git a/lib/gmp/mpn/inlines.c b/lib/gmp/mpn/inlines.c
new file mode 100644 (file)
index 0000000..5c137d3
--- /dev/null
@@ -0,0 +1,4 @@
+#include <config.h>
+#define _FORCE_INLINES
+#define _EXTERN_INLINE /* empty */
+#include "gmp.h"
diff --git a/lib/gmp/mpn/lshift.c b/lib/gmp/mpn/lshift.c
new file mode 100644 (file)
index 0000000..1d73afb
--- /dev/null
@@ -0,0 +1,88 @@
+/* mpn_lshift -- Shift left low level.
+
+Copyright (C) 1991, 1993, 1994, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+
+/* Shift U (pointed to by UP and USIZE digits long) CNT bits to the left
+   and store the USIZE least significant digits of the result at WP.
+   Return the bits shifted out from the most significant digit.
+
+   Argument constraints:
+   1. 0 < CNT < BITS_PER_MP_LIMB
+   2. If the result is to be written over the input, WP must be >= UP.
+*/
+
+mp_limb_t
+#if __STDC__
+mpn_lshift (register mp_ptr wp,
+           register mp_srcptr up, mp_size_t usize,
+           register unsigned int cnt)
+#else
+mpn_lshift (wp, up, usize, cnt)
+     register mp_ptr wp;
+     register mp_srcptr up;
+     mp_size_t usize;
+     register unsigned int cnt;
+#endif
+{
+  register mp_limb_t high_limb, low_limb;
+  register unsigned sh_1, sh_2;
+  register mp_size_t i;
+  mp_limb_t retval;
+
+#ifdef DEBUG
+  if (usize == 0 || cnt == 0)
+    abort ();
+#endif
+
+  sh_1 = cnt;
+#if 0
+  if (sh_1 == 0)
+    {
+      if (wp != up)
+       {
+         /* Copy from high end to low end, to allow specified input/output
+            overlapping.  */
+         for (i = usize - 1; i >= 0; i--)
+           wp[i] = up[i];
+       }
+      return 0;
+    }
+#endif
+
+  wp += 1;
+  sh_2 = BITS_PER_MP_LIMB - sh_1;
+  i = usize - 1;
+  low_limb = up[i];
+  retval = low_limb >> sh_2;
+  high_limb = low_limb;
+  while (--i >= 0)
+    {
+      low_limb = up[i];
+      wp[i] = (high_limb << sh_1) | (low_limb >> sh_2);
+      high_limb = low_limb;
+    }
+  wp[i] = high_limb << sh_1;
+
+  return retval;
+}
diff --git a/lib/gmp/mpn/mp_bases.c b/lib/gmp/mpn/mp_bases.c
new file mode 100644 (file)
index 0000000..f2f6dae
--- /dev/null
@@ -0,0 +1,550 @@
+/* __mp_bases -- Structure for conversion between internal binary
+   format and strings in base 2..255.  The fields are explained in
+   gmp-impl.h.
+
+
+Copyright (C) 1991, 1993, 1994, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+
+#if BITS_PER_MP_LIMB == 32
+const struct bases __mp_bases[256] =
+{
+  /*  0 */ {0, 0.0, 0, 0},
+  /*  1 */ {0, 1e38, 0, 0},
+  /*  2 */ {32, 1.00000000, 0x1, 0x0},
+  /*  3 */ {20, 0.63092975, 0xcfd41b91, 0x3b563c24},
+  /*  4 */ {16, 0.50000000, 0x2, 0x0},
+  /*  5 */ {13, 0.43067656, 0x48c27395, 0xc25c2684},
+  /*  6 */ {12, 0.38685281, 0x81bf1000, 0xf91bd1b6},
+  /*  7 */ {11, 0.35620719, 0x75db9c97, 0x1607a2cb},
+  /*  8 */ {10, 0.33333333, 0x3, 0x0},
+  /*  9 */ {10, 0.31546488, 0xcfd41b91, 0x3b563c24},
+  /* 10 */ {9, 0.30103000, 0x3b9aca00, 0x12e0be82},
+  /* 11 */ {9, 0.28906483, 0x8c8b6d2b, 0xd24cde04},
+  /* 12 */ {8, 0.27894295, 0x19a10000, 0x3fa39ab5},
+  /* 13 */ {8, 0.27023815, 0x309f1021, 0x50f8ac5f},
+  /* 14 */ {8, 0.26264954, 0x57f6c100, 0x74843b1e},
+  /* 15 */ {8, 0.25595802, 0x98c29b81, 0xad0326c2},
+  /* 16 */ {8, 0.25000000, 0x4, 0x0},
+  /* 17 */ {7, 0.24465054, 0x18754571, 0x4ef0b6bd},
+  /* 18 */ {7, 0.23981247, 0x247dbc80, 0xc0fc48a1},
+  /* 19 */ {7, 0.23540891, 0x3547667b, 0x33838942},
+  /* 20 */ {7, 0.23137821, 0x4c4b4000, 0xad7f29ab},
+  /* 21 */ {7, 0.22767025, 0x6b5a6e1d, 0x313c3d15},
+  /* 22 */ {7, 0.22424382, 0x94ace180, 0xb8cca9e0},
+  /* 23 */ {7, 0.22106473, 0xcaf18367, 0x42ed6de9},
+  /* 24 */ {6, 0.21810429, 0xb640000, 0x67980e0b},
+  /* 25 */ {6, 0.21533828, 0xe8d4a51, 0x19799812},
+  /* 26 */ {6, 0.21274605, 0x1269ae40, 0xbce85396},
+  /* 27 */ {6, 0.21030992, 0x17179149, 0x62c103a9},
+  /* 28 */ {6, 0.20801460, 0x1cb91000, 0x1d353d43},
+  /* 29 */ {6, 0.20584683, 0x23744899, 0xce1decea},
+  /* 30 */ {6, 0.20379505, 0x2b73a840, 0x790fc511},
+  /* 31 */ {6, 0.20184909, 0x34e63b41, 0x35b865a0},
+  /* 32 */ {6, 0.20000000, 0x5, 0x0},
+  /* 33 */ {6, 0.19823986, 0x4cfa3cc1, 0xa9aed1b3},
+  /* 34 */ {6, 0.19656163, 0x5c13d840, 0x63dfc229},
+  /* 35 */ {6, 0.19495902, 0x6d91b519, 0x2b0fee30},
+  /* 36 */ {6, 0.19342640, 0x81bf1000, 0xf91bd1b6},
+  /* 37 */ {6, 0.19195872, 0x98ede0c9, 0xac89c3a9},
+  /* 38 */ {6, 0.19055141, 0xb3773e40, 0x6d2c32fe},
+  /* 39 */ {6, 0.18920036, 0xd1bbc4d1, 0x387907c9},
+  /* 40 */ {6, 0.18790182, 0xf4240000, 0xc6f7a0b},
+  /* 41 */ {5, 0.18665241, 0x6e7d349, 0x28928154},
+  /* 42 */ {5, 0.18544902, 0x7ca30a0, 0x6e8629d},
+  /* 43 */ {5, 0.18428883, 0x8c32bbb, 0xd373dca0},
+  /* 44 */ {5, 0.18316925, 0x9d46c00, 0xa0b17895},
+  /* 45 */ {5, 0.18208790, 0xaffacfd, 0x746811a5},
+  /* 46 */ {5, 0.18104260, 0xc46bee0, 0x4da6500f},
+  /* 47 */ {5, 0.18003133, 0xdab86ef, 0x2ba23582},
+  /* 48 */ {5, 0.17905223, 0xf300000, 0xdb20a88},
+  /* 49 */ {5, 0.17810359, 0x10d63af1, 0xe68d5ce4},
+  /* 50 */ {5, 0.17718382, 0x12a05f20, 0xb7cdfd9d},
+  /* 51 */ {5, 0.17629143, 0x1490aae3, 0x8e583933},
+  /* 52 */ {5, 0.17542506, 0x16a97400, 0x697cc3ea},
+  /* 53 */ {5, 0.17458343, 0x18ed2825, 0x48a5ca6c},
+  /* 54 */ {5, 0.17376534, 0x1b5e4d60, 0x2b52db16},
+  /* 55 */ {5, 0.17296969, 0x1dff8297, 0x111586a6},
+  /* 56 */ {5, 0.17219543, 0x20d38000, 0xf31d2b36},
+  /* 57 */ {5, 0.17144160, 0x23dd1799, 0xc8d76d19},
+  /* 58 */ {5, 0.17070728, 0x271f35a0, 0xa2cb1eb4},
+  /* 59 */ {5, 0.16999162, 0x2a9ce10b, 0x807c3ec3},
+  /* 60 */ {5, 0.16929381, 0x2e593c00, 0x617ec8bf},
+  /* 61 */ {5, 0.16861310, 0x3257844d, 0x45746cbe},
+  /* 62 */ {5, 0.16794878, 0x369b13e0, 0x2c0aa273},
+  /* 63 */ {5, 0.16730018, 0x3b27613f, 0x14f90805},
+  /* 64 */ {5, 0.16666667, 0x6, 0x0},
+  /* 65 */ {5, 0.16604765, 0x4528a141, 0xd9cf0829},
+  /* 66 */ {5, 0.16544255, 0x4aa51420, 0xb6fc4841},
+  /* 67 */ {5, 0.16485086, 0x50794633, 0x973054cb},
+  /* 68 */ {5, 0.16427205, 0x56a94400, 0x7a1dbe4b},
+  /* 69 */ {5, 0.16370566, 0x5d393975, 0x5f7fcd7f},
+  /* 70 */ {5, 0.16315122, 0x642d7260, 0x47196c84},
+  /* 71 */ {5, 0.16260831, 0x6b8a5ae7, 0x30b43635},
+  /* 72 */ {5, 0.16207652, 0x73548000, 0x1c1fa5f6},
+  /* 73 */ {5, 0.16155547, 0x7b908fe9, 0x930634a},
+  /* 74 */ {5, 0.16104477, 0x84435aa0, 0xef7f4a3c},
+  /* 75 */ {5, 0.16054409, 0x8d71d25b, 0xcf5552d2},
+  /* 76 */ {5, 0.16005307, 0x97210c00, 0xb1a47c8e},
+  /* 77 */ {5, 0.15957142, 0xa1563f9d, 0x9634b43e},
+  /* 78 */ {5, 0.15909881, 0xac16c8e0, 0x7cd3817d},
+  /* 79 */ {5, 0.15863496, 0xb768278f, 0x65536761},
+  /* 80 */ {5, 0.15817959, 0xc3500000, 0x4f8b588e},
+  /* 81 */ {5, 0.15773244, 0xcfd41b91, 0x3b563c24},
+  /* 82 */ {5, 0.15729325, 0xdcfa6920, 0x28928154},
+  /* 83 */ {5, 0.15686177, 0xeac8fd83, 0x1721bfb0},
+  /* 84 */ {5, 0.15643779, 0xf9461400, 0x6e8629d},
+  /* 85 */ {4, 0.15602107, 0x31c84b1, 0x491cc17c},
+  /* 86 */ {4, 0.15561139, 0x342ab10, 0x3a11d83b},
+  /* 87 */ {4, 0.15520856, 0x36a2c21, 0x2be074cd},
+  /* 88 */ {4, 0.15481238, 0x3931000, 0x1e7a02e7},
+  /* 89 */ {4, 0.15442266, 0x3bd5ee1, 0x11d10edd},
+  /* 90 */ {4, 0.15403922, 0x3e92110, 0x5d92c68},
+  /* 91 */ {4, 0.15366189, 0x4165ef1, 0xf50dbfb2},
+  /* 92 */ {4, 0.15329049, 0x4452100, 0xdf9f1316},
+  /* 93 */ {4, 0.15292487, 0x4756fd1, 0xcb52a684},
+  /* 94 */ {4, 0.15256487, 0x4a75410, 0xb8163e97},
+  /* 95 */ {4, 0.15221035, 0x4dad681, 0xa5d8f269},
+  /* 96 */ {4, 0.15186115, 0x5100000, 0x948b0fcd},
+  /* 97 */ {4, 0.15151715, 0x546d981, 0x841e0215},
+  /* 98 */ {4, 0.15117821, 0x57f6c10, 0x74843b1e},
+  /* 99 */ {4, 0.15084420, 0x5b9c0d1, 0x65b11e6e},
+  /* 100 */ {4, 0.15051500, 0x5f5e100, 0x5798ee23},
+  /* 101 */ {4, 0.15019048, 0x633d5f1, 0x4a30b99b},
+  /* 102 */ {4, 0.14987054, 0x673a910, 0x3d6e4d94},
+  /* 103 */ {4, 0.14955506, 0x6b563e1, 0x314825b0},
+  /* 104 */ {4, 0.14924394, 0x6f91000, 0x25b55f2e},
+  /* 105 */ {4, 0.14893706, 0x73eb721, 0x1aadaccb},
+  /* 106 */ {4, 0.14863434, 0x7866310, 0x10294ba2},
+  /* 107 */ {4, 0.14833567, 0x7d01db1, 0x620f8f6},
+  /* 108 */ {4, 0.14804096, 0x81bf100, 0xf91bd1b6},
+  /* 109 */ {4, 0.14775011, 0x869e711, 0xe6d37b2a},
+  /* 110 */ {4, 0.14746305, 0x8ba0a10, 0xd55cff6e},
+  /* 111 */ {4, 0.14717969, 0x90c6441, 0xc4ad2db2},
+  /* 112 */ {4, 0.14689994, 0x9610000, 0xb4b985cf},
+  /* 113 */ {4, 0.14662372, 0x9b7e7c1, 0xa5782bef},
+  /* 114 */ {4, 0.14635096, 0xa112610, 0x96dfdd2a},
+  /* 115 */ {4, 0.14608158, 0xa6cc591, 0x88e7e509},
+  /* 116 */ {4, 0.14581551, 0xacad100, 0x7b8813d3},
+  /* 117 */ {4, 0.14555268, 0xb2b5331, 0x6eb8b595},
+  /* 118 */ {4, 0.14529302, 0xb8e5710, 0x627289db},
+  /* 119 */ {4, 0.14503647, 0xbf3e7a1, 0x56aebc07},
+  /* 120 */ {4, 0.14478295, 0xc5c1000, 0x4b66dc33},
+  /* 121 */ {4, 0.14453241, 0xcc6db61, 0x4094d8a3},
+  /* 122 */ {4, 0.14428479, 0xd345510, 0x3632f7a5},
+  /* 123 */ {4, 0.14404003, 0xda48871, 0x2c3bd1f0},
+  /* 124 */ {4, 0.14379807, 0xe178100, 0x22aa4d5f},
+  /* 125 */ {4, 0.14355885, 0xe8d4a51, 0x19799812},
+  /* 126 */ {4, 0.14332233, 0xf05f010, 0x10a523e5},
+  /* 127 */ {4, 0.14308844, 0xf817e01, 0x828a237},
+  /* 128 */ {4, 0.14285714, 0x7, 0x0},
+  /* 129 */ {4, 0.14262838, 0x10818201, 0xf04ec452},
+  /* 130 */ {4, 0.14240211, 0x11061010, 0xe136444a},
+  /* 131 */ {4, 0.14217828, 0x118db651, 0xd2af9589},
+  /* 132 */ {4, 0.14195685, 0x12188100, 0xc4b42a83},
+  /* 133 */ {4, 0.14173777, 0x12a67c71, 0xb73dccf5},
+  /* 134 */ {4, 0.14152100, 0x1337b510, 0xaa4698c5},
+  /* 135 */ {4, 0.14130649, 0x13cc3761, 0x9dc8f729},
+  /* 136 */ {4, 0.14109421, 0x14641000, 0x91bf9a30},
+  /* 137 */ {4, 0.14088412, 0x14ff4ba1, 0x86257887},
+  /* 138 */ {4, 0.14067617, 0x159df710, 0x7af5c98c},
+  /* 139 */ {4, 0.14047033, 0x16401f31, 0x702c01a0},
+  /* 140 */ {4, 0.14026656, 0x16e5d100, 0x65c3ceb1},
+  /* 141 */ {4, 0.14006482, 0x178f1991, 0x5bb91502},
+  /* 142 */ {4, 0.13986509, 0x183c0610, 0x5207ec23},
+  /* 143 */ {4, 0.13966731, 0x18eca3c1, 0x48ac9c19},
+  /* 144 */ {4, 0.13947147, 0x19a10000, 0x3fa39ab5},
+  /* 145 */ {4, 0.13927753, 0x1a592841, 0x36e98912},
+  /* 146 */ {4, 0.13908545, 0x1b152a10, 0x2e7b3140},
+  /* 147 */ {4, 0.13889521, 0x1bd51311, 0x2655840b},
+  /* 148 */ {4, 0.13870677, 0x1c98f100, 0x1e7596ea},
+  /* 149 */ {4, 0.13852011, 0x1d60d1b1, 0x16d8a20d},
+  /* 150 */ {4, 0.13833519, 0x1e2cc310, 0xf7bfe87},
+  /* 151 */ {4, 0.13815199, 0x1efcd321, 0x85d2492},
+  /* 152 */ {4, 0.13797047, 0x1fd11000, 0x179a9f4},
+  /* 153 */ {4, 0.13779062, 0x20a987e1, 0xf59e80eb},
+  /* 154 */ {4, 0.13761241, 0x21864910, 0xe8b768db},
+  /* 155 */ {4, 0.13743580, 0x226761f1, 0xdc39d6d5},
+  /* 156 */ {4, 0.13726078, 0x234ce100, 0xd021c5d1},
+  /* 157 */ {4, 0.13708732, 0x2436d4d1, 0xc46b5e37},
+  /* 158 */ {4, 0.13691539, 0x25254c10, 0xb912f39c},
+  /* 159 */ {4, 0.13674498, 0x26185581, 0xae150294},
+  /* 160 */ {4, 0.13657605, 0x27100000, 0xa36e2eb1},
+  /* 161 */ {4, 0.13640859, 0x280c5a81, 0x991b4094},
+  /* 162 */ {4, 0.13624257, 0x290d7410, 0x8f19241e},
+  /* 163 */ {4, 0.13607797, 0x2a135bd1, 0x8564e6b7},
+  /* 164 */ {4, 0.13591477, 0x2b1e2100, 0x7bfbb5b4},
+  /* 165 */ {4, 0.13575295, 0x2c2dd2f1, 0x72dadcc8},
+  /* 166 */ {4, 0.13559250, 0x2d428110, 0x69ffc498},
+  /* 167 */ {4, 0.13543338, 0x2e5c3ae1, 0x6167f154},
+  /* 168 */ {4, 0.13527558, 0x2f7b1000, 0x5911016e},
+  /* 169 */ {4, 0.13511908, 0x309f1021, 0x50f8ac5f},
+  /* 170 */ {4, 0.13496386, 0x31c84b10, 0x491cc17c},
+  /* 171 */ {4, 0.13480991, 0x32f6d0b1, 0x417b26d8},
+  /* 172 */ {4, 0.13465720, 0x342ab100, 0x3a11d83b},
+  /* 173 */ {4, 0.13450572, 0x3563fc11, 0x32dee622},
+  /* 174 */ {4, 0.13435545, 0x36a2c210, 0x2be074cd},
+  /* 175 */ {4, 0.13420637, 0x37e71341, 0x2514bb58},
+  /* 176 */ {4, 0.13405847, 0x39310000, 0x1e7a02e7},
+  /* 177 */ {4, 0.13391173, 0x3a8098c1, 0x180ea5d0},
+  /* 178 */ {4, 0.13376614, 0x3bd5ee10, 0x11d10edd},
+  /* 179 */ {4, 0.13362168, 0x3d311091, 0xbbfb88e},
+  /* 180 */ {4, 0.13347832, 0x3e921100, 0x5d92c68},
+  /* 181 */ {4, 0.13333607, 0x3ff90031, 0x1c024c},
+  /* 182 */ {4, 0.13319491, 0x4165ef10, 0xf50dbfb2},
+  /* 183 */ {4, 0.13305481, 0x42d8eea1, 0xea30efa3},
+  /* 184 */ {4, 0.13291577, 0x44521000, 0xdf9f1316},
+  /* 185 */ {4, 0.13277777, 0x45d16461, 0xd555c0c9},
+  /* 186 */ {4, 0.13264079, 0x4756fd10, 0xcb52a684},
+  /* 187 */ {4, 0.13250483, 0x48e2eb71, 0xc193881f},
+  /* 188 */ {4, 0.13236988, 0x4a754100, 0xb8163e97},
+  /* 189 */ {4, 0.13223591, 0x4c0e0f51, 0xaed8b724},
+  /* 190 */ {4, 0.13210292, 0x4dad6810, 0xa5d8f269},
+  /* 191 */ {4, 0.13197089, 0x4f535d01, 0x9d15039d},
+  /* 192 */ {4, 0.13183981, 0x51000000, 0x948b0fcd},
+  /* 193 */ {4, 0.13170967, 0x52b36301, 0x8c394d1d},
+  /* 194 */ {4, 0.13158046, 0x546d9810, 0x841e0215},
+  /* 195 */ {4, 0.13145216, 0x562eb151, 0x7c3784f8},
+  /* 196 */ {4, 0.13132477, 0x57f6c100, 0x74843b1e},
+  /* 197 */ {4, 0.13119827, 0x59c5d971, 0x6d02985d},
+  /* 198 */ {4, 0.13107265, 0x5b9c0d10, 0x65b11e6e},
+  /* 199 */ {4, 0.13094791, 0x5d796e61, 0x5e8e5c64},
+  /* 200 */ {4, 0.13082402, 0x5f5e1000, 0x5798ee23},
+  /* 201 */ {4, 0.13070099, 0x614a04a1, 0x50cf7bde},
+  /* 202 */ {4, 0.13057879, 0x633d5f10, 0x4a30b99b},
+  /* 203 */ {4, 0.13045743, 0x65383231, 0x43bb66bd},
+  /* 204 */ {4, 0.13033688, 0x673a9100, 0x3d6e4d94},
+  /* 205 */ {4, 0.13021715, 0x69448e91, 0x374842ee},
+  /* 206 */ {4, 0.13009822, 0x6b563e10, 0x314825b0},
+  /* 207 */ {4, 0.12998007, 0x6d6fb2c1, 0x2b6cde75},
+  /* 208 */ {4, 0.12986271, 0x6f910000, 0x25b55f2e},
+  /* 209 */ {4, 0.12974613, 0x71ba3941, 0x2020a2c5},
+  /* 210 */ {4, 0.12963031, 0x73eb7210, 0x1aadaccb},
+  /* 211 */ {4, 0.12951524, 0x7624be11, 0x155b891f},
+  /* 212 */ {4, 0.12940092, 0x78663100, 0x10294ba2},
+  /* 213 */ {4, 0.12928734, 0x7aafdeb1, 0xb160fe9},
+  /* 214 */ {4, 0.12917448, 0x7d01db10, 0x620f8f6},
+  /* 215 */ {4, 0.12906235, 0x7f5c3a21, 0x14930ef},
+  /* 216 */ {4, 0.12895094, 0x81bf1000, 0xf91bd1b6},
+  /* 217 */ {4, 0.12884022, 0x842a70e1, 0xefdcb0c7},
+  /* 218 */ {4, 0.12873021, 0x869e7110, 0xe6d37b2a},
+  /* 219 */ {4, 0.12862089, 0x891b24f1, 0xddfeb94a},
+  /* 220 */ {4, 0.12851224, 0x8ba0a100, 0xd55cff6e},
+  /* 221 */ {4, 0.12840428, 0x8e2ef9d1, 0xcceced50},
+  /* 222 */ {4, 0.12829698, 0x90c64410, 0xc4ad2db2},
+  /* 223 */ {4, 0.12819034, 0x93669481, 0xbc9c75f9},
+  /* 224 */ {4, 0.12808435, 0x96100000, 0xb4b985cf},
+  /* 225 */ {4, 0.12797901, 0x98c29b81, 0xad0326c2},
+  /* 226 */ {4, 0.12787431, 0x9b7e7c10, 0xa5782bef},
+  /* 227 */ {4, 0.12777024, 0x9e43b6d1, 0x9e1771a9},
+  /* 228 */ {4, 0.12766680, 0xa1126100, 0x96dfdd2a},
+  /* 229 */ {4, 0.12756398, 0xa3ea8ff1, 0x8fd05c41},
+  /* 230 */ {4, 0.12746176, 0xa6cc5910, 0x88e7e509},
+  /* 231 */ {4, 0.12736016, 0xa9b7d1e1, 0x8225759d},
+  /* 232 */ {4, 0.12725915, 0xacad1000, 0x7b8813d3},
+  /* 233 */ {4, 0.12715874, 0xafac2921, 0x750eccf9},
+  /* 234 */ {4, 0.12705891, 0xb2b53310, 0x6eb8b595},
+  /* 235 */ {4, 0.12695967, 0xb5c843b1, 0x6884e923},
+  /* 236 */ {4, 0.12686100, 0xb8e57100, 0x627289db},
+  /* 237 */ {4, 0.12676290, 0xbc0cd111, 0x5c80c07b},
+  /* 238 */ {4, 0.12666537, 0xbf3e7a10, 0x56aebc07},
+  /* 239 */ {4, 0.12656839, 0xc27a8241, 0x50fbb19b},
+  /* 240 */ {4, 0.12647197, 0xc5c10000, 0x4b66dc33},
+  /* 241 */ {4, 0.12637609, 0xc91209c1, 0x45ef7c7c},
+  /* 242 */ {4, 0.12628075, 0xcc6db610, 0x4094d8a3},
+  /* 243 */ {4, 0.12618595, 0xcfd41b91, 0x3b563c24},
+  /* 244 */ {4, 0.12609168, 0xd3455100, 0x3632f7a5},
+  /* 245 */ {4, 0.12599794, 0xd6c16d31, 0x312a60c3},
+  /* 246 */ {4, 0.12590471, 0xda488710, 0x2c3bd1f0},
+  /* 247 */ {4, 0.12581200, 0xdddab5a1, 0x2766aa45},
+  /* 248 */ {4, 0.12571980, 0xe1781000, 0x22aa4d5f},
+  /* 249 */ {4, 0.12562811, 0xe520ad61, 0x1e06233c},
+  /* 250 */ {4, 0.12553692, 0xe8d4a510, 0x19799812},
+  /* 251 */ {4, 0.12544622, 0xec940e71, 0x15041c33},
+  /* 252 */ {4, 0.12535601, 0xf05f0100, 0x10a523e5},
+  /* 253 */ {4, 0.12526629, 0xf4359451, 0xc5c2749},
+  /* 254 */ {4, 0.12517705, 0xf817e010, 0x828a237},
+  /* 255 */ {4, 0.12508829, 0xfc05fc01, 0x40a1423},
+};
+#endif
+#if BITS_PER_MP_LIMB == 64
+const struct bases __mp_bases[256] =
+{
+  /*  0 */ {0, 0.0, 0, 0},
+  /*  1 */ {0, 1e38, 0, 0},
+  /*  2 */ {64, 1.00000000, 0x1, 0x0},
+  /*  3 */ {40, 0.63092975, 0xa8b8b452291fe821L, 0x846d550e37b5063dL},
+  /*  4 */ {32, 0.50000000, 0x2L, 0x0L},
+  /*  5 */ {27, 0.43067656, 0x6765c793fa10079dL, 0x3ce9a36f23c0fc90L},
+  /*  6 */ {24, 0.38685281, 0x41c21cb8e1000000L, 0xf24f62335024a295L},
+  /*  7 */ {22, 0.35620719, 0x3642798750226111L, 0x2df495ccaa57147bL},
+  /*  8 */ {21, 0.33333333, 0x3L, 0x0L},
+  /*  9 */ {20, 0.31546488, 0xa8b8b452291fe821L, 0x846d550e37b5063dL},
+  /* 10 */ {19, 0.30103000, 0x8ac7230489e80000L, 0xd83c94fb6d2ac34aL},
+  /* 11 */ {18, 0.28906483, 0x4d28cb56c33fa539L, 0xa8adf7ae45e7577bL},
+  /* 12 */ {17, 0.27894295, 0x1eca170c00000000L, 0xa10c2bec5da8f8fL},
+  /* 13 */ {17, 0.27023815, 0x780c7372621bd74dL, 0x10f4becafe412ec3L},
+  /* 14 */ {16, 0.26264954, 0x1e39a5057d810000L, 0xf08480f672b4e86L},
+  /* 15 */ {16, 0.25595802, 0x5b27ac993df97701L, 0x6779c7f90dc42f48L},
+  /* 16 */ {16, 0.25000000, 0x4L, 0x0L},
+  /* 17 */ {15, 0.24465054, 0x27b95e997e21d9f1L, 0x9c71e11bab279323L},
+  /* 18 */ {15, 0.23981247, 0x5da0e1e53c5c8000L, 0x5dfaa697ec6f6a1cL},
+  /* 19 */ {15, 0.23540891, 0xd2ae3299c1c4aedbL, 0x3711783f6be7e9ecL},
+  /* 20 */ {14, 0.23137821, 0x16bcc41e90000000L, 0x6849b86a12b9b01eL},
+  /* 21 */ {14, 0.22767025, 0x2d04b7fdd9c0ef49L, 0x6bf097ba5ca5e239L},
+  /* 22 */ {14, 0.22424382, 0x5658597bcaa24000L, 0x7b8015c8d7af8f08L},
+  /* 23 */ {14, 0.22106473, 0xa0e2073737609371L, 0x975a24b3a3151b38L},
+  /* 24 */ {13, 0.21810429, 0xc29e98000000000L, 0x50bd367972689db1L},
+  /* 25 */ {13, 0.21533828, 0x14adf4b7320334b9L, 0x8c240c4aecb13bb5L},
+  /* 26 */ {13, 0.21274605, 0x226ed36478bfa000L, 0xdbd2e56854e118c9L},
+  /* 27 */ {13, 0.21030992, 0x383d9170b85ff80bL, 0x2351ffcaa9c7c4aeL},
+  /* 28 */ {13, 0.20801460, 0x5a3c23e39c000000L, 0x6b24188ca33b0636L},
+  /* 29 */ {13, 0.20584683, 0x8e65137388122bcdL, 0xcc3dceaf2b8ba99dL},
+  /* 30 */ {13, 0.20379505, 0xdd41bb36d259e000L, 0x2832e835c6c7d6b6L},
+  /* 31 */ {12, 0.20184909, 0xaee5720ee830681L, 0x76b6aa272e1873c5L},
+  /* 32 */ {12, 0.20000000, 0x5L, 0x0L},
+  /* 33 */ {12, 0.19823986, 0x172588ad4f5f0981L, 0x61eaf5d402c7bf4fL},
+  /* 34 */ {12, 0.19656163, 0x211e44f7d02c1000L, 0xeeb658123ffb27ecL},
+  /* 35 */ {12, 0.19495902, 0x2ee56725f06e5c71L, 0x5d5e3762e6fdf509L},
+  /* 36 */ {12, 0.19342640, 0x41c21cb8e1000000L, 0xf24f62335024a295L},
+  /* 37 */ {12, 0.19195872, 0x5b5b57f8a98a5dd1L, 0x66ae7831762efb6fL},
+  /* 38 */ {12, 0.19055141, 0x7dcff8986ea31000L, 0x47388865a00f544L},
+  /* 39 */ {12, 0.18920036, 0xabd4211662a6b2a1L, 0x7d673c33a123b54cL},
+  /* 40 */ {12, 0.18790182, 0xe8d4a51000000000L, 0x19799812dea11197L},
+  /* 41 */ {11, 0.18665241, 0x7a32956ad081b79L, 0xc27e62e0686feaeL},
+  /* 42 */ {11, 0.18544902, 0x9f49aaff0e86800L, 0x9b6e7507064ce7c7L},
+  /* 43 */ {11, 0.18428883, 0xce583bb812d37b3L, 0x3d9ac2bf66cfed94L},
+  /* 44 */ {11, 0.18316925, 0x109b79a654c00000L, 0xed46bc50ce59712aL},
+  /* 45 */ {11, 0.18208790, 0x1543beff214c8b95L, 0x813d97e2c89b8d46L},
+  /* 46 */ {11, 0.18104260, 0x1b149a79459a3800L, 0x2e81751956af8083L},
+  /* 47 */ {11, 0.18003133, 0x224edfb5434a830fL, 0xdd8e0a95e30c0988L},
+  /* 48 */ {11, 0.17905223, 0x2b3fb00000000000L, 0x7ad4dd48a0b5b167L},
+  /* 49 */ {11, 0.17810359, 0x3642798750226111L, 0x2df495ccaa57147bL},
+  /* 50 */ {11, 0.17718382, 0x43c33c1937564800L, 0xe392010175ee5962L},
+  /* 51 */ {11, 0.17629143, 0x54411b2441c3cd8bL, 0x84eaf11b2fe7738eL},
+  /* 52 */ {11, 0.17542506, 0x6851455acd400000L, 0x3a1e3971e008995dL},
+  /* 53 */ {11, 0.17458343, 0x80a23b117c8feb6dL, 0xfd7a462344ffce25L},
+  /* 54 */ {11, 0.17376534, 0x9dff7d32d5dc1800L, 0x9eca40b40ebcef8aL},
+  /* 55 */ {11, 0.17296969, 0xc155af6faeffe6a7L, 0x52fa161a4a48e43dL},
+  /* 56 */ {11, 0.17219543, 0xebb7392e00000000L, 0x1607a2cbacf930c1L},
+  /* 57 */ {10, 0.17144160, 0x50633659656d971L, 0x97a014f8e3be55f1L},
+  /* 58 */ {10, 0.17070728, 0x5fa8624c7fba400L, 0x568df8b76cbf212cL},
+  /* 59 */ {10, 0.16999162, 0x717d9faa73c5679L, 0x20ba7c4b4e6ef492L},
+  /* 60 */ {10, 0.16929381, 0x86430aac6100000L, 0xe81ee46b9ef492f5L},
+  /* 61 */ {10, 0.16861310, 0x9e64d9944b57f29L, 0x9dc0d10d51940416L},
+  /* 62 */ {10, 0.16794878, 0xba5ca5392cb0400L, 0x5fa8ed2f450272a5L},
+  /* 63 */ {10, 0.16730018, 0xdab2ce1d022cd81L, 0x2ba9eb8c5e04e641L},
+  /* 64 */ {10, 0.16666667, 0x6L, 0x0L},
+  /* 65 */ {10, 0.16604765, 0x12aeed5fd3e2d281L, 0xb67759cc00287bf1L},
+  /* 66 */ {10, 0.16544255, 0x15c3da1572d50400L, 0x78621feeb7f4ed33L},
+  /* 67 */ {10, 0.16485086, 0x194c05534f75ee29L, 0x43d55b5f72943bc0L},
+  /* 68 */ {10, 0.16427205, 0x1d56299ada100000L, 0x173decb64d1d4409L},
+  /* 69 */ {10, 0.16370566, 0x21f2a089a4ff4f79L, 0xe29fb54fd6b6074fL},
+  /* 70 */ {10, 0.16315122, 0x2733896c68d9a400L, 0xa1f1f5c210d54e62L},
+  /* 71 */ {10, 0.16260831, 0x2d2cf2c33b533c71L, 0x6aac7f9bfafd57b2L},
+  /* 72 */ {10, 0.16207652, 0x33f506e440000000L, 0x3b563c2478b72ee2L},
+  /* 73 */ {10, 0.16155547, 0x3ba43bec1d062211L, 0x12b536b574e92d1bL},
+  /* 74 */ {10, 0.16104477, 0x4455872d8fd4e400L, 0xdf86c03020404fa5L},
+  /* 75 */ {10, 0.16054409, 0x4e2694539f2f6c59L, 0xa34adf02234eea8eL},
+  /* 76 */ {10, 0.16005307, 0x5938006c18900000L, 0x6f46eb8574eb59ddL},
+  /* 77 */ {10, 0.15957142, 0x65ad9912474aa649L, 0x42459b481df47cecL},
+  /* 78 */ {10, 0.15909881, 0x73ae9ff4241ec400L, 0x1b424b95d80ca505L},
+  /* 79 */ {10, 0.15863496, 0x836612ee9c4ce1e1L, 0xf2c1b982203a0dacL},
+  /* 80 */ {10, 0.15817959, 0x9502f90000000000L, 0xb7cdfd9d7bdbab7dL},
+  /* 81 */ {10, 0.15773244, 0xa8b8b452291fe821L, 0x846d550e37b5063dL},
+  /* 82 */ {10, 0.15729325, 0xbebf59a07dab4400L, 0x57931eeaf85cf64fL},
+  /* 83 */ {10, 0.15686177, 0xd7540d4093bc3109L, 0x305a944507c82f47L},
+  /* 84 */ {10, 0.15643779, 0xf2b96616f1900000L, 0xe007ccc9c22781aL},
+  /* 85 */ {9, 0.15602107, 0x336de62af2bca35L, 0x3e92c42e000eeed4L},
+  /* 86 */ {9, 0.15561139, 0x39235ec33d49600L, 0x1ebe59130db2795eL},
+  /* 87 */ {9, 0.15520856, 0x3f674e539585a17L, 0x268859e90f51b89L},
+  /* 88 */ {9, 0.15481238, 0x4645b6958000000L, 0xd24cde0463108cfaL},
+  /* 89 */ {9, 0.15442266, 0x4dcb74afbc49c19L, 0xa536009f37adc383L},
+  /* 90 */ {9, 0.15403922, 0x56064e1d18d9a00L, 0x7cea06ce1c9ace10L},
+  /* 91 */ {9, 0.15366189, 0x5f04fe2cd8a39fbL, 0x58db032e72e8ba43L},
+  /* 92 */ {9, 0.15329049, 0x68d74421f5c0000L, 0x388cc17cae105447L},
+  /* 93 */ {9, 0.15292487, 0x738df1f6ab4827dL, 0x1b92672857620ce0L},
+  /* 94 */ {9, 0.15256487, 0x7f3afbc9cfb5e00L, 0x18c6a9575c2ade4L},
+  /* 95 */ {9, 0.15221035, 0x8bf187fba88f35fL, 0xd44da7da8e44b24fL},
+  /* 96 */ {9, 0.15186115, 0x99c600000000000L, 0xaa2f78f1b4cc6794L},
+  /* 97 */ {9, 0.15151715, 0xa8ce21eb6531361L, 0x843c067d091ee4ccL},
+  /* 98 */ {9, 0.15117821, 0xb92112c1a0b6200L, 0x62005e1e913356e3L},
+  /* 99 */ {9, 0.15084420, 0xcad7718b8747c43L, 0x4316eed01dedd518L},
+  /* 100 */ {9, 0.15051500, 0xde0b6b3a7640000L, 0x2725dd1d243aba0eL},
+  /* 101 */ {9, 0.15019048, 0xf2d8cf5fe6d74c5L, 0xddd9057c24cb54fL},
+  /* 102 */ {9, 0.14987054, 0x1095d25bfa712600L, 0xedeee175a736d2a1L},
+  /* 103 */ {9, 0.14955506, 0x121b7c4c3698faa7L, 0xc4699f3df8b6b328L},
+  /* 104 */ {9, 0.14924394, 0x13c09e8d68000000L, 0x9ebbe7d859cb5a7cL},
+  /* 105 */ {9, 0.14893706, 0x15876ccb0b709ca9L, 0x7c828b9887eb2179L},
+  /* 106 */ {9, 0.14863434, 0x17723c2976da2a00L, 0x5d652ab99001adcfL},
+  /* 107 */ {9, 0.14833567, 0x198384e9c259048bL, 0x4114f1754e5d7b32L},
+  /* 108 */ {9, 0.14804096, 0x1bbde41dfeec0000L, 0x274b7c902f7e0188L},
+  /* 109 */ {9, 0.14775011, 0x1e241d6e3337910dL, 0xfc9e0fbb32e210cL},
+  /* 110 */ {9, 0.14746305, 0x20b91cee9901ee00L, 0xf4afa3e594f8ea1fL},
+  /* 111 */ {9, 0.14717969, 0x237ff9079863dfefL, 0xcd85c32e9e4437b0L},
+  /* 112 */ {9, 0.14689994, 0x267bf47000000000L, 0xa9bbb147e0dd92a8L},
+  /* 113 */ {9, 0.14662372, 0x29b08039fbeda7f1L, 0x8900447b70e8eb82L},
+  /* 114 */ {9, 0.14635096, 0x2d213df34f65f200L, 0x6b0a92adaad5848aL},
+  /* 115 */ {9, 0.14608158, 0x30d201d957a7c2d3L, 0x4f990ad8740f0ee5L},
+  /* 116 */ {9, 0.14581551, 0x34c6d52160f40000L, 0x3670a9663a8d3610L},
+  /* 117 */ {9, 0.14555268, 0x3903f855d8f4c755L, 0x1f5c44188057be3cL},
+  /* 118 */ {9, 0.14529302, 0x3d8de5c8ec59b600L, 0xa2bea956c4e4977L},
+  /* 119 */ {9, 0.14503647, 0x4269541d1ff01337L, 0xed68b23033c3637eL},
+  /* 120 */ {9, 0.14478295, 0x479b38e478000000L, 0xc99cf624e50549c5L},
+  /* 121 */ {9, 0.14453241, 0x4d28cb56c33fa539L, 0xa8adf7ae45e7577bL},
+  /* 122 */ {9, 0.14428479, 0x5317871fa13aba00L, 0x8a5bc740b1c113e5L},
+  /* 123 */ {9, 0.14404003, 0x596d2f44de9fa71bL, 0x6e6c7efb81cfbb9bL},
+  /* 124 */ {9, 0.14379807, 0x602fd125c47c0000L, 0x54aba5c5cada5f10L},
+  /* 125 */ {9, 0.14355885, 0x6765c793fa10079dL, 0x3ce9a36f23c0fc90L},
+  /* 126 */ {9, 0.14332233, 0x6f15be069b847e00L, 0x26fb43de2c8cd2a8L},
+  /* 127 */ {9, 0.14308844, 0x7746b3e82a77047fL, 0x12b94793db8486a1L},
+  /* 128 */ {9, 0.14285714, 0x7L, 0x0L},
+  /* 129 */ {9, 0.14262838, 0x894953f7ea890481L, 0xdd5deca404c0156dL},
+  /* 130 */ {9, 0.14240211, 0x932abffea4848200L, 0xbd51373330291de0L},
+  /* 131 */ {9, 0.14217828, 0x9dacb687d3d6a163L, 0x9fa4025d66f23085L},
+  /* 132 */ {9, 0.14195685, 0xa8d8102a44840000L, 0x842530ee2db4949dL},
+  /* 133 */ {9, 0.14173777, 0xb4b60f9d140541e5L, 0x6aa7f2766b03dc25L},
+  /* 134 */ {9, 0.14152100, 0xc15065d4856e4600L, 0x53035ba7ebf32e8dL},
+  /* 135 */ {9, 0.14130649, 0xceb1363f396d23c7L, 0x3d12091fc9fb4914L},
+  /* 136 */ {9, 0.14109421, 0xdce31b2488000000L, 0x28b1cb81b1ef1849L},
+  /* 137 */ {9, 0.14088412, 0xebf12a24bca135c9L, 0x15c35be67ae3e2c9L},
+  /* 138 */ {9, 0.14067617, 0xfbe6f8dbf88f4a00L, 0x42a17bd09be1ff0L},
+  /* 139 */ {8, 0.14047033, 0x1ef156c084ce761L, 0x8bf461f03cf0bbfL},
+  /* 140 */ {8, 0.14026656, 0x20c4e3b94a10000L, 0xf3fbb43f68a32d05L},
+  /* 141 */ {8, 0.14006482, 0x22b0695a08ba421L, 0xd84f44c48564dc19L},
+  /* 142 */ {8, 0.13986509, 0x24b4f35d7a4c100L, 0xbe58ebcce7956abeL},
+  /* 143 */ {8, 0.13966731, 0x26d397284975781L, 0xa5fac463c7c134b7L},
+  /* 144 */ {8, 0.13947147, 0x290d74100000000L, 0x8f19241e28c7d757L},
+  /* 145 */ {8, 0.13927753, 0x2b63b3a37866081L, 0x799a6d046c0ae1aeL},
+  /* 146 */ {8, 0.13908545, 0x2dd789f4d894100L, 0x6566e37d746a9e40L},
+  /* 147 */ {8, 0.13889521, 0x306a35e51b58721L, 0x526887dbfb5f788fL},
+  /* 148 */ {8, 0.13870677, 0x331d01712e10000L, 0x408af3382b8efd3dL},
+  /* 149 */ {8, 0.13852011, 0x35f14200a827c61L, 0x2fbb374806ec05f1L},
+  /* 150 */ {8, 0.13833519, 0x38e858b62216100L, 0x1fe7c0f0afce87feL},
+  /* 151 */ {8, 0.13815199, 0x3c03b2c13176a41L, 0x11003d517540d32eL},
+  /* 152 */ {8, 0.13797047, 0x3f44c9b21000000L, 0x2f5810f98eff0dcL},
+  /* 153 */ {8, 0.13779062, 0x42ad23cef3113c1L, 0xeb72e35e7840d910L},
+  /* 154 */ {8, 0.13761241, 0x463e546b19a2100L, 0xd27de19593dc3614L},
+  /* 155 */ {8, 0.13743580, 0x49f9fc3f96684e1L, 0xbaf391fd3e5e6fc2L},
+  /* 156 */ {8, 0.13726078, 0x4de1c9c5dc10000L, 0xa4bd38c55228c81dL},
+  /* 157 */ {8, 0.13708732, 0x51f77994116d2a1L, 0x8fc5a8de8e1de782L},
+  /* 158 */ {8, 0.13691539, 0x563cd6bb3398100L, 0x7bf9265bea9d3a3bL},
+  /* 159 */ {8, 0.13674498, 0x5ab3bb270beeb01L, 0x69454b325983dccdL},
+  /* 160 */ {8, 0.13657605, 0x5f5e10000000000L, 0x5798ee2308c39df9L},
+  /* 161 */ {8, 0.13640859, 0x643dce0ec16f501L, 0x46e40ba0fa66a753L},
+  /* 162 */ {8, 0.13624257, 0x6954fe21e3e8100L, 0x3717b0870b0db3a7L},
+  /* 163 */ {8, 0.13607797, 0x6ea5b9755f440a1L, 0x2825e6775d11cdebL},
+  /* 164 */ {8, 0.13591477, 0x74322a1c0410000L, 0x1a01a1c09d1b4dacL},
+  /* 165 */ {8, 0.13575295, 0x79fc8b6ae8a46e1L, 0xc9eb0a8bebc8f3eL},
+  /* 166 */ {8, 0.13559250, 0x80072a66d512100L, 0xffe357ff59e6a004L},
+  /* 167 */ {8, 0.13543338, 0x86546633b42b9c1L, 0xe7dfd1be05fa61a8L},
+  /* 168 */ {8, 0.13527558, 0x8ce6b0861000000L, 0xd11ed6fc78f760e5L},
+  /* 169 */ {8, 0.13511908, 0x93c08e16a022441L, 0xbb8db609dd29ebfeL},
+  /* 170 */ {8, 0.13496386, 0x9ae49717f026100L, 0xa71aec8d1813d532L},
+  /* 171 */ {8, 0.13480991, 0xa25577ae24c1a61L, 0x93b612a9f20fbc02L},
+  /* 172 */ {8, 0.13465720, 0xaa15f068e610000L, 0x814fc7b19a67d317L},
+  /* 173 */ {8, 0.13450572, 0xb228d6bf7577921L, 0x6fd9a03f2e0a4b7cL},
+  /* 174 */ {8, 0.13435545, 0xba91158ef5c4100L, 0x5f4615a38d0d316eL},
+  /* 175 */ {8, 0.13420637, 0xc351ad9aec0b681L, 0x4f8876863479a286L},
+  /* 176 */ {8, 0.13405847, 0xcc6db6100000000L, 0x4094d8a3041b60ebL},
+  /* 177 */ {8, 0.13391173, 0xd5e85d09025c181L, 0x32600b8ed883a09bL},
+  /* 178 */ {8, 0.13376614, 0xdfc4e816401c100L, 0x24df8c6eb4b6d1f1L},
+  /* 179 */ {8, 0.13362168, 0xea06b4c72947221L, 0x18097a8ee151acefL},
+  /* 180 */ {8, 0.13347832, 0xf4b139365210000L, 0xbd48cc8ec1cd8e3L},
+  /* 181 */ {8, 0.13333607, 0xffc80497d520961L, 0x3807a8d67485fbL},
+  /* 182 */ {8, 0.13319491, 0x10b4ebfca1dee100L, 0xea5768860b62e8d8L},
+  /* 183 */ {8, 0.13305481, 0x117492de921fc141L, 0xd54faf5b635c5005L},
+  /* 184 */ {8, 0.13291577, 0x123bb2ce41000000L, 0xc14a56233a377926L},
+  /* 185 */ {8, 0.13277777, 0x130a8b6157bdecc1L, 0xae39a88db7cd329fL},
+  /* 186 */ {8, 0.13264079, 0x13e15dede0e8a100L, 0x9c10bde69efa7ab6L},
+  /* 187 */ {8, 0.13250483, 0x14c06d941c0ca7e1L, 0x8ac36c42a2836497L},
+  /* 188 */ {8, 0.13236988, 0x15a7ff487a810000L, 0x7a463c8b84f5ef67L},
+  /* 189 */ {8, 0.13223591, 0x169859ddc5c697a1L, 0x6a8e5f5ad090fd4bL},
+  /* 190 */ {8, 0.13210292, 0x1791c60f6fed0100L, 0x5b91a2943596fc56L},
+  /* 191 */ {8, 0.13197089, 0x18948e8c0e6fba01L, 0x4d4667b1c468e8f0L},
+  /* 192 */ {8, 0.13183981, 0x19a1000000000000L, 0x3fa39ab547994dafL},
+  /* 193 */ {8, 0.13170967, 0x1ab769203dafc601L, 0x32a0a9b2faee1e2aL},
+  /* 194 */ {8, 0.13158046, 0x1bd81ab557f30100L, 0x26357ceac0e96962L},
+  /* 195 */ {8, 0.13145216, 0x1d0367a69fed1ba1L, 0x1a5a6f65caa5859eL},
+  /* 196 */ {8, 0.13132477, 0x1e39a5057d810000L, 0xf08480f672b4e86L},
+  /* 197 */ {8, 0.13119827, 0x1f7b2a18f29ac3e1L, 0x4383340615612caL},
+  /* 198 */ {8, 0.13107265, 0x20c850694c2aa100L, 0xf3c77969ee4be5a2L},
+  /* 199 */ {8, 0.13094791, 0x222173cc014980c1L, 0xe00993cc187c5ec9L},
+  /* 200 */ {8, 0.13082402, 0x2386f26fc1000000L, 0xcd2b297d889bc2b6L},
+  /* 201 */ {8, 0.13070099, 0x24f92ce8af296d41L, 0xbb214d5064862b22L},
+  /* 202 */ {8, 0.13057879, 0x2678863cd0ece100L, 0xa9e1a7ca7ea10e20L},
+  /* 203 */ {8, 0.13045743, 0x280563f0a9472d61L, 0x99626e72b39ea0cfL},
+  /* 204 */ {8, 0.13033688, 0x29a02e1406210000L, 0x899a5ba9c13fafd9L},
+  /* 205 */ {8, 0.13021715, 0x2b494f4efe6d2e21L, 0x7a80a705391e96ffL},
+  /* 206 */ {8, 0.13009822, 0x2d0134ef21cbc100L, 0x6c0cfe23de23042aL},
+  /* 207 */ {8, 0.12998007, 0x2ec84ef4da2ef581L, 0x5e377df359c944ddL},
+  /* 208 */ {8, 0.12986271, 0x309f102100000000L, 0x50f8ac5fc8f53985L},
+  /* 209 */ {8, 0.12974613, 0x3285ee02a1420281L, 0x44497266278e35b7L},
+  /* 210 */ {8, 0.12963031, 0x347d6104fc324100L, 0x382316831f7ee175L},
+  /* 211 */ {8, 0.12951524, 0x3685e47dade53d21L, 0x2c7f377833b8946eL},
+  /* 212 */ {8, 0.12940092, 0x389ff6bb15610000L, 0x2157c761ab4163efL},
+  /* 213 */ {8, 0.12928734, 0x3acc1912ebb57661L, 0x16a7071803cc49a9L},
+  /* 214 */ {8, 0.12917448, 0x3d0acff111946100L, 0xc6781d80f8224fcL},
+  /* 215 */ {8, 0.12906235, 0x3f5ca2e692eaf841L, 0x294092d370a900bL},
+  /* 216 */ {8, 0.12895094, 0x41c21cb8e1000000L, 0xf24f62335024a295L},
+  /* 217 */ {8, 0.12884022, 0x443bcb714399a5c1L, 0xe03b98f103fad6d2L},
+  /* 218 */ {8, 0.12873021, 0x46ca406c81af2100L, 0xcee3d32cad2a9049L},
+  /* 219 */ {8, 0.12862089, 0x496e106ac22aaae1L, 0xbe3f9df9277fdadaL},
+  /* 220 */ {8, 0.12851224, 0x4c27d39fa5410000L, 0xae46f0d94c05e933L},
+  /* 221 */ {8, 0.12840428, 0x4ef825c296e43ca1L, 0x9ef2280fb437a33dL},
+  /* 222 */ {8, 0.12829698, 0x51dfa61f5ad88100L, 0x9039ff426d3f284bL},
+  /* 223 */ {8, 0.12819034, 0x54def7a6d2f16901L, 0x82178c6d6b51f8f4L},
+  /* 224 */ {8, 0.12808435, 0x57f6c10000000000L, 0x74843b1ee4c1e053L},
+  /* 225 */ {8, 0.12797901, 0x5b27ac993df97701L, 0x6779c7f90dc42f48L},
+  /* 226 */ {8, 0.12787431, 0x5e7268b9bbdf8100L, 0x5af23c74f9ad9fe9L},
+  /* 227 */ {8, 0.12777024, 0x61d7a7932ff3d6a1L, 0x4ee7eae2acdc617eL},
+  /* 228 */ {8, 0.12766680, 0x65581f53c8c10000L, 0x43556aa2ac262a0bL},
+  /* 229 */ {8, 0.12756398, 0x68f48a385b8320e1L, 0x3835949593b8ddd1L},
+  /* 230 */ {8, 0.12746176, 0x6cada69ed07c2100L, 0x2d837fbe78458762L},
+  /* 231 */ {8, 0.12736016, 0x70843718cdbf27c1L, 0x233a7e150a54a555L},
+  /* 232 */ {8, 0.12725915, 0x7479027ea1000000L, 0x19561984a50ff8feL},
+  /* 233 */ {8, 0.12715874, 0x788cd40268f39641L, 0xfd211159fe3490fL},
+  /* 234 */ {8, 0.12705891, 0x7cc07b437ecf6100L, 0x6aa563e655033e3L},
+  /* 235 */ {8, 0.12695967, 0x8114cc6220762061L, 0xfbb614b3f2d3b14cL},
+  /* 236 */ {8, 0.12686100, 0x858aa0135be10000L, 0xeac0f8837fb05773L},
+  /* 237 */ {8, 0.12676290, 0x8a22d3b53c54c321L, 0xda6e4c10e8615ca5L},
+  /* 238 */ {8, 0.12666537, 0x8ede496339f34100L, 0xcab755a8d01fa67fL},
+  /* 239 */ {8, 0.12656839, 0x93bde80aec3a1481L, 0xbb95a9ae71aa3e0cL},
+  /* 240 */ {8, 0.12647197, 0x98c29b8100000000L, 0xad0326c296b4f529L},
+  /* 241 */ {8, 0.12637609, 0x9ded549671832381L, 0x9ef9f21eed31b7c1L},
+  /* 242 */ {8, 0.12628075, 0xa33f092e0b1ac100L, 0x91747422be14b0b2L},
+  /* 243 */ {8, 0.12618595, 0xa8b8b452291fe821L, 0x846d550e37b5063dL},
+  /* 244 */ {8, 0.12609168, 0xae5b564ac3a10000L, 0x77df79e9a96c06f6L},
+  /* 245 */ {8, 0.12599794, 0xb427f4b3be74c361L, 0x6bc6019636c7d0c2L},
+  /* 246 */ {8, 0.12590471, 0xba1f9a938041e100L, 0x601c4205aebd9e47L},
+  /* 247 */ {8, 0.12581200, 0xc0435871d1110f41L, 0x54ddc59756f05016L},
+  /* 248 */ {8, 0.12571980, 0xc694446f01000000L, 0x4a0648979c838c18L},
+  /* 249 */ {8, 0.12562811, 0xcd137a5b57ac3ec1L, 0x3f91b6e0bb3a053dL},
+  /* 250 */ {8, 0.12553692, 0xd3c21bcecceda100L, 0x357c299a88ea76a5L},
+  /* 251 */ {8, 0.12544622, 0xdaa150410b788de1L, 0x2bc1e517aecc56e3L},
+  /* 252 */ {8, 0.12535601, 0xe1b24521be010000L, 0x225f56ceb3da9f5dL},
+  /* 253 */ {8, 0.12526629, 0xe8f62df12777c1a1L, 0x1951136d53ad63acL},
+  /* 254 */ {8, 0.12517705, 0xf06e445906fc0100L, 0x1093d504b3cd7d93L},
+  /* 255 */ {8, 0.12508829, 0xf81bc845c81bf801L, 0x824794d1ec1814fL},
+};
+#endif
diff --git a/lib/gmp/mpn/mul.c b/lib/gmp/mpn/mul.c
new file mode 100644 (file)
index 0000000..960eb94
--- /dev/null
@@ -0,0 +1,153 @@
+/* mpn_mul -- Multiply two natural numbers.
+
+Copyright (C) 1991, 1993, 1994, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+
+/* Multiply the natural numbers u (pointed to by UP, with USIZE limbs)
+   and v (pointed to by VP, with VSIZE limbs), and store the result at
+   PRODP.  USIZE + VSIZE limbs are always stored, but if the input
+   operands are normalized.  Return the most significant limb of the
+   result.
+
+   NOTE: The space pointed to by PRODP is overwritten before finished
+   with U and V, so overlap is an error.
+
+   Argument constraints:
+   1. USIZE >= VSIZE.
+   2. PRODP != UP and PRODP != VP, i.e. the destination
+      must be distinct from the multiplier and the multiplicand.  */
+
+/* If KARATSUBA_THRESHOLD is not already defined, define it to a
+   value which is good on most machines.  */
+#ifndef KARATSUBA_THRESHOLD
+#define KARATSUBA_THRESHOLD 32
+#endif
+
+mp_limb_t
+#if __STDC__
+mpn_mul (mp_ptr prodp,
+        mp_srcptr up, mp_size_t usize,
+        mp_srcptr vp, mp_size_t vsize)
+#else
+mpn_mul (prodp, up, usize, vp, vsize)
+     mp_ptr prodp;
+     mp_srcptr up;
+     mp_size_t usize;
+     mp_srcptr vp;
+     mp_size_t vsize;
+#endif
+{
+  mp_ptr prod_endp = prodp + usize + vsize - 1;
+  mp_limb_t cy;
+  mp_ptr tspace;
+  TMP_DECL (marker);
+
+  if (vsize < KARATSUBA_THRESHOLD)
+    {
+      /* Handle simple cases with traditional multiplication.
+
+        This is the most critical code of the entire function.  All
+        multiplies rely on this, both small and huge.  Small ones arrive
+        here immediately.  Huge ones arrive here as this is the base case
+        for Karatsuba's recursive algorithm below.  */
+      mp_size_t i;
+      mp_limb_t cy_limb;
+      mp_limb_t v_limb;
+
+      if (vsize == 0)
+       return 0;
+
+      /* Multiply by the first limb in V separately, as the result can be
+        stored (not added) to PROD.  We also avoid a loop for zeroing.  */
+      v_limb = vp[0];
+      if (v_limb <= 1)
+       {
+         if (v_limb == 1)
+           MPN_COPY (prodp, up, usize);
+         else
+           MPN_ZERO (prodp, usize);
+         cy_limb = 0;
+       }
+      else
+       cy_limb = mpn_mul_1 (prodp, up, usize, v_limb);
+
+      prodp[usize] = cy_limb;
+      prodp++;
+
+      /* For each iteration in the outer loop, multiply one limb from
+        U with one limb from V, and add it to PROD.  */
+      for (i = 1; i < vsize; i++)
+       {
+         v_limb = vp[i];
+         if (v_limb <= 1)
+           {
+             cy_limb = 0;
+             if (v_limb == 1)
+               cy_limb = mpn_add_n (prodp, prodp, up, usize);
+           }
+         else
+           cy_limb = mpn_addmul_1 (prodp, up, usize, v_limb);
+
+         prodp[usize] = cy_limb;
+         prodp++;
+       }
+      return cy_limb;
+    }
+
+  TMP_MARK (marker);
+
+  tspace = (mp_ptr) TMP_ALLOC (2 * vsize * BYTES_PER_MP_LIMB);
+  MPN_MUL_N_RECURSE (prodp, up, vp, vsize, tspace);
+
+  prodp += vsize;
+  up += vsize;
+  usize -= vsize;
+  if (usize >= vsize)
+    {
+      mp_ptr tp = (mp_ptr) TMP_ALLOC (2 * vsize * BYTES_PER_MP_LIMB);
+      do
+       {
+         MPN_MUL_N_RECURSE (tp, up, vp, vsize, tspace);
+         cy = mpn_add_n (prodp, prodp, tp, vsize);
+         mpn_add_1 (prodp + vsize, tp + vsize, vsize, cy);
+         prodp += vsize;
+         up += vsize;
+         usize -= vsize;
+       }
+      while (usize >= vsize);
+    }
+
+  /* True: usize < vsize.  */
+
+  /* Make life simple: Recurse.  */
+
+  if (usize != 0)
+    {
+      mpn_mul (tspace, vp, vsize, up, usize);
+      cy = mpn_add_n (prodp, prodp, tspace, vsize);
+      mpn_add_1 (prodp + vsize, tspace + vsize, usize, cy);
+    }
+
+  TMP_FREE (marker);
+  return *prod_endp;
+}
diff --git a/lib/gmp/mpn/mul_1.c b/lib/gmp/mpn/mul_1.c
new file mode 100644 (file)
index 0000000..21aa951
--- /dev/null
@@ -0,0 +1,60 @@
+/* mpn_mul_1 -- Multiply a limb vector with a single limb and
+   store the product in a second limb vector.
+
+Copyright (C) 1991, 1992, 1993, 1994, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+#include "longlong.h"
+
+mp_limb_t
+mpn_mul_1 (res_ptr, s1_ptr, s1_size, s2_limb)
+     register mp_ptr res_ptr;
+     register mp_srcptr s1_ptr;
+     mp_size_t s1_size;
+     register mp_limb_t s2_limb;
+{
+  register mp_limb_t cy_limb;
+  register mp_size_t j;
+  register mp_limb_t prod_high, prod_low;
+
+  /* The loop counter and index J goes from -S1_SIZE to -1.  This way
+     the loop becomes faster.  */
+  j = -s1_size;
+
+  /* Offset the base pointers to compensate for the negative indices.  */
+  s1_ptr -= j;
+  res_ptr -= j;
+
+  cy_limb = 0;
+  do
+    {
+      umul_ppmm (prod_high, prod_low, s1_ptr[j], s2_limb);
+
+      prod_low += cy_limb;
+      cy_limb = (prod_low < cy_limb) + prod_high;
+
+      res_ptr[j] = prod_low;
+    }
+  while (++j != 0);
+
+  return cy_limb;
+}
diff --git a/lib/gmp/mpn/mul_n.c b/lib/gmp/mpn/mul_n.c
new file mode 100644 (file)
index 0000000..104d332
--- /dev/null
@@ -0,0 +1,402 @@
+/* mpn_mul_n -- Multiply two natural numbers of length n.
+
+Copyright (C) 1991, 1992, 1993, 1994, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+
+/* Multiply the natural numbers u (pointed to by UP) and v (pointed to by VP),
+   both with SIZE limbs, and store the result at PRODP.  2 * SIZE limbs are
+   always stored.  Return the most significant limb.
+
+   Argument constraints:
+   1. PRODP != UP and PRODP != VP, i.e. the destination
+      must be distinct from the multiplier and the multiplicand.  */
+
+/* If KARATSUBA_THRESHOLD is not already defined, define it to a
+   value which is good on most machines.  */
+#ifndef KARATSUBA_THRESHOLD
+#define KARATSUBA_THRESHOLD 32
+#endif
+
+/* The code can't handle KARATSUBA_THRESHOLD smaller than 2.  */
+#if KARATSUBA_THRESHOLD < 2
+#undef KARATSUBA_THRESHOLD
+#define KARATSUBA_THRESHOLD 2
+#endif
+
+/* Handle simple cases with traditional multiplication.
+
+   This is the most critical code of multiplication.  All multiplies rely
+   on this, both small and huge.  Small ones arrive here immediately.  Huge
+   ones arrive here as this is the base case for Karatsuba's recursive
+   algorithm below.  */
+
+void
+#if __STDC__
+impn_mul_n_basecase (mp_ptr prodp, mp_srcptr up, mp_srcptr vp, mp_size_t size)
+#else
+impn_mul_n_basecase (prodp, up, vp, size)
+     mp_ptr prodp;
+     mp_srcptr up;
+     mp_srcptr vp;
+     mp_size_t size;
+#endif
+{
+  mp_size_t i;
+  mp_limb_t cy_limb;
+  mp_limb_t v_limb;
+
+  /* Multiply by the first limb in V separately, as the result can be
+     stored (not added) to PROD.  We also avoid a loop for zeroing.  */
+  v_limb = vp[0];
+  if (v_limb <= 1)
+    {
+      if (v_limb == 1)
+       MPN_COPY (prodp, up, size);
+      else
+       MPN_ZERO (prodp, size);
+      cy_limb = 0;
+    }
+  else
+    cy_limb = mpn_mul_1 (prodp, up, size, v_limb);
+
+  prodp[size] = cy_limb;
+  prodp++;
+
+  /* For each iteration in the outer loop, multiply one limb from
+     U with one limb from V, and add it to PROD.  */
+  for (i = 1; i < size; i++)
+    {
+      v_limb = vp[i];
+      if (v_limb <= 1)
+       {
+         cy_limb = 0;
+         if (v_limb == 1)
+           cy_limb = mpn_add_n (prodp, prodp, up, size);
+       }
+      else
+       cy_limb = mpn_addmul_1 (prodp, up, size, v_limb);
+
+      prodp[size] = cy_limb;
+      prodp++;
+    }
+}
+
+void
+#if __STDC__
+impn_mul_n (mp_ptr prodp,
+            mp_srcptr up, mp_srcptr vp, mp_size_t size, mp_ptr tspace)
+#else
+impn_mul_n (prodp, up, vp, size, tspace)
+     mp_ptr prodp;
+     mp_srcptr up;
+     mp_srcptr vp;
+     mp_size_t size;
+     mp_ptr tspace;
+#endif
+{
+  if ((size & 1) != 0)
+    {
+      /* The size is odd, the code code below doesn't handle that.
+        Multiply the least significant (size - 1) limbs with a recursive
+        call, and handle the most significant limb of S1 and S2
+        separately.  */
+      /* A slightly faster way to do this would be to make the Karatsuba
+        code below behave as if the size were even, and let it check for
+        odd size in the end.  I.e., in essence move this code to the end.
+        Doing so would save us a recursive call, and potentially make the
+        stack grow a lot less.  */
+
+      mp_size_t esize = size - 1;      /* even size */
+      mp_limb_t cy_limb;
+
+      MPN_MUL_N_RECURSE (prodp, up, vp, esize, tspace);
+      cy_limb = mpn_addmul_1 (prodp + esize, up, esize, vp[esize]);
+      prodp[esize + esize] = cy_limb;
+      cy_limb = mpn_addmul_1 (prodp + esize, vp, size, up[esize]);
+
+      prodp[esize + size] = cy_limb;
+    }
+  else
+    {
+      /* Anatolij Alekseevich Karatsuba's divide-and-conquer algorithm.
+
+        Split U in two pieces, U1 and U0, such that
+        U = U0 + U1*(B**n),
+        and V in V1 and V0, such that
+        V = V0 + V1*(B**n).
+
+        UV is then computed recursively using the identity
+
+               2n   n          n                     n
+        UV = (B  + B )U V  +  B (U -U )(V -V )  +  (B + 1)U V
+                       1 1        1  0   0  1              0 0
+
+        Where B = 2**BITS_PER_MP_LIMB.  */
+
+      mp_size_t hsize = size >> 1;
+      mp_limb_t cy;
+      int negflg;
+
+      /*** Product H.   ________________  ________________
+                       |_____U1 x V1____||____U0 x V0_____|  */
+      /* Put result in upper part of PROD and pass low part of TSPACE
+        as new TSPACE.  */
+      MPN_MUL_N_RECURSE (prodp + size, up + hsize, vp + hsize, hsize, tspace);
+
+      /*** Product M.   ________________
+                       |_(U1-U0)(V0-V1)_|  */
+      if (mpn_cmp (up + hsize, up, hsize) >= 0)
+       {
+         mpn_sub_n (prodp, up + hsize, up, hsize);
+         negflg = 0;
+       }
+      else
+       {
+         mpn_sub_n (prodp, up, up + hsize, hsize);
+         negflg = 1;
+       }
+      if (mpn_cmp (vp + hsize, vp, hsize) >= 0)
+       {
+         mpn_sub_n (prodp + hsize, vp + hsize, vp, hsize);
+         negflg ^= 1;
+       }
+      else
+       {
+         mpn_sub_n (prodp + hsize, vp, vp + hsize, hsize);
+         /* No change of NEGFLG.  */
+       }
+      /* Read temporary operands from low part of PROD.
+        Put result in low part of TSPACE using upper part of TSPACE
+        as new TSPACE.  */
+      MPN_MUL_N_RECURSE (tspace, prodp, prodp + hsize, hsize, tspace + size);
+
+      /*** Add/copy product H.  */
+      MPN_COPY (prodp + hsize, prodp + size, hsize);
+      cy = mpn_add_n (prodp + size, prodp + size, prodp + size + hsize, hsize);
+
+      /*** Add product M (if NEGFLG M is a negative number).  */
+      if (negflg)
+       cy -= mpn_sub_n (prodp + hsize, prodp + hsize, tspace, size);
+      else
+       cy += mpn_add_n (prodp + hsize, prodp + hsize, tspace, size);
+
+      /*** Product L.   ________________  ________________
+                       |________________||____U0 x V0_____|  */
+      /* Read temporary operands from low part of PROD.
+        Put result in low part of TSPACE using upper part of TSPACE
+        as new TSPACE.  */
+      MPN_MUL_N_RECURSE (tspace, up, vp, hsize, tspace + size);
+
+      /*** Add/copy Product L (twice).  */
+
+      cy += mpn_add_n (prodp + hsize, prodp + hsize, tspace, size);
+      if (cy)
+       mpn_add_1 (prodp + hsize + size, prodp + hsize + size, hsize, cy);
+
+      MPN_COPY (prodp, tspace, hsize);
+      cy = mpn_add_n (prodp + hsize, prodp + hsize, tspace + hsize, hsize);
+      if (cy)
+       mpn_add_1 (prodp + size, prodp + size, size, 1);
+    }
+}
+
+void
+#if __STDC__
+impn_sqr_n_basecase (mp_ptr prodp, mp_srcptr up, mp_size_t size)
+#else
+impn_sqr_n_basecase (prodp, up, size)
+     mp_ptr prodp;
+     mp_srcptr up;
+     mp_size_t size;
+#endif
+{
+  mp_size_t i;
+  mp_limb_t cy_limb;
+  mp_limb_t v_limb;
+
+  /* Multiply by the first limb in V separately, as the result can be
+     stored (not added) to PROD.  We also avoid a loop for zeroing.  */
+  v_limb = up[0];
+  if (v_limb <= 1)
+    {
+      if (v_limb == 1)
+       MPN_COPY (prodp, up, size);
+      else
+       MPN_ZERO (prodp, size);
+      cy_limb = 0;
+    }
+  else
+    cy_limb = mpn_mul_1 (prodp, up, size, v_limb);
+
+  prodp[size] = cy_limb;
+  prodp++;
+
+  /* For each iteration in the outer loop, multiply one limb from
+     U with one limb from V, and add it to PROD.  */
+  for (i = 1; i < size; i++)
+    {
+      v_limb = up[i];
+      if (v_limb <= 1)
+       {
+         cy_limb = 0;
+         if (v_limb == 1)
+           cy_limb = mpn_add_n (prodp, prodp, up, size);
+       }
+      else
+       cy_limb = mpn_addmul_1 (prodp, up, size, v_limb);
+
+      prodp[size] = cy_limb;
+      prodp++;
+    }
+}
+
+void
+#if __STDC__
+impn_sqr_n (mp_ptr prodp,
+            mp_srcptr up, mp_size_t size, mp_ptr tspace)
+#else
+impn_sqr_n (prodp, up, size, tspace)
+     mp_ptr prodp;
+     mp_srcptr up;
+     mp_size_t size;
+     mp_ptr tspace;
+#endif
+{
+  if ((size & 1) != 0)
+    {
+      /* The size is odd, the code code below doesn't handle that.
+        Multiply the least significant (size - 1) limbs with a recursive
+        call, and handle the most significant limb of S1 and S2
+        separately.  */
+      /* A slightly faster way to do this would be to make the Karatsuba
+        code below behave as if the size were even, and let it check for
+        odd size in the end.  I.e., in essence move this code to the end.
+        Doing so would save us a recursive call, and potentially make the
+        stack grow a lot less.  */
+
+      mp_size_t esize = size - 1;      /* even size */
+      mp_limb_t cy_limb;
+
+      MPN_SQR_N_RECURSE (prodp, up, esize, tspace);
+      cy_limb = mpn_addmul_1 (prodp + esize, up, esize, up[esize]);
+      prodp[esize + esize] = cy_limb;
+      cy_limb = mpn_addmul_1 (prodp + esize, up, size, up[esize]);
+
+      prodp[esize + size] = cy_limb;
+    }
+  else
+    {
+      mp_size_t hsize = size >> 1;
+      mp_limb_t cy;
+
+      /*** Product H.   ________________  ________________
+                       |_____U1 x U1____||____U0 x U0_____|  */
+      /* Put result in upper part of PROD and pass low part of TSPACE
+        as new TSPACE.  */
+      MPN_SQR_N_RECURSE (prodp + size, up + hsize, hsize, tspace);
+
+      /*** Product M.   ________________
+                       |_(U1-U0)(U0-U1)_|  */
+      if (mpn_cmp (up + hsize, up, hsize) >= 0)
+       {
+         mpn_sub_n (prodp, up + hsize, up, hsize);
+       }
+      else
+       {
+         mpn_sub_n (prodp, up, up + hsize, hsize);
+       }
+
+      /* Read temporary operands from low part of PROD.
+        Put result in low part of TSPACE using upper part of TSPACE
+        as new TSPACE.  */
+      MPN_SQR_N_RECURSE (tspace, prodp, hsize, tspace + size);
+
+      /*** Add/copy product H.  */
+      MPN_COPY (prodp + hsize, prodp + size, hsize);
+      cy = mpn_add_n (prodp + size, prodp + size, prodp + size + hsize, hsize);
+
+      /*** Add product M (if NEGFLG M is a negative number).  */
+      cy -= mpn_sub_n (prodp + hsize, prodp + hsize, tspace, size);
+
+      /*** Product L.   ________________  ________________
+                       |________________||____U0 x U0_____|  */
+      /* Read temporary operands from low part of PROD.
+        Put result in low part of TSPACE using upper part of TSPACE
+        as new TSPACE.  */
+      MPN_SQR_N_RECURSE (tspace, up, hsize, tspace + size);
+
+      /*** Add/copy Product L (twice).  */
+
+      cy += mpn_add_n (prodp + hsize, prodp + hsize, tspace, size);
+      if (cy)
+       mpn_add_1 (prodp + hsize + size, prodp + hsize + size, hsize, cy);
+
+      MPN_COPY (prodp, tspace, hsize);
+      cy = mpn_add_n (prodp + hsize, prodp + hsize, tspace + hsize, hsize);
+      if (cy)
+       mpn_add_1 (prodp + size, prodp + size, size, 1);
+    }
+}
+
+/* This should be made into an inline function in gmp.h.  */
+inline void
+#if __STDC__
+mpn_mul_n (mp_ptr prodp, mp_srcptr up, mp_srcptr vp, mp_size_t size)
+#else
+mpn_mul_n (prodp, up, vp, size)
+     mp_ptr prodp;
+     mp_srcptr up;
+     mp_srcptr vp;
+     mp_size_t size;
+#endif
+{
+  TMP_DECL (marker);
+  TMP_MARK (marker);
+  if (up == vp)
+    {
+      if (size < KARATSUBA_THRESHOLD)
+       {
+         impn_sqr_n_basecase (prodp, up, size);
+       }
+      else
+       {
+         mp_ptr tspace;
+         tspace = (mp_ptr) TMP_ALLOC (2 * size * BYTES_PER_MP_LIMB);
+         impn_sqr_n (prodp, up, size, tspace);
+       }
+    }
+  else
+    {
+      if (size < KARATSUBA_THRESHOLD)
+       {
+         impn_mul_n_basecase (prodp, up, vp, size);
+       }
+      else
+       {
+         mp_ptr tspace;
+         tspace = (mp_ptr) TMP_ALLOC (2 * size * BYTES_PER_MP_LIMB);
+         impn_mul_n (prodp, up, vp, size, tspace);
+       }
+    }
+  TMP_FREE (marker);
+}
diff --git a/lib/gmp/mpn/sub_n.c b/lib/gmp/mpn/sub_n.c
new file mode 100644 (file)
index 0000000..0947857
--- /dev/null
@@ -0,0 +1,63 @@
+/* mpn_sub_n -- Subtract two limb vectors of equal, non-zero length.
+
+Copyright (C) 1992, 1993, 1994, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+
+mp_limb_t
+#if __STDC__
+mpn_sub_n (mp_ptr res_ptr, mp_srcptr s1_ptr, mp_srcptr s2_ptr, mp_size_t size)
+#else
+mpn_sub_n (res_ptr, s1_ptr, s2_ptr, size)
+     register mp_ptr res_ptr;
+     register mp_srcptr s1_ptr;
+     register mp_srcptr s2_ptr;
+     mp_size_t size;
+#endif
+{
+  register mp_limb_t x, y, cy;
+  register mp_size_t j;
+
+  /* The loop counter and index J goes from -SIZE to -1.  This way
+     the loop becomes faster.  */
+  j = -size;
+
+  /* Offset the base pointers to compensate for the negative indices.  */
+  s1_ptr -= j;
+  s2_ptr -= j;
+  res_ptr -= j;
+
+  cy = 0;
+  do
+    {
+      y = s2_ptr[j];
+      x = s1_ptr[j];
+      y += cy;                 /* add previous carry to subtrahend */
+      cy = (y < cy);           /* get out carry from that addition */
+      y = x - y;               /* main subtract */
+      cy = (y > x) + cy;       /* get out carry from the subtract, combine */
+      res_ptr[j] = y;
+    }
+  while (++j != 0);
+
+  return cy;
+}
diff --git a/lib/gmp/mpn/submul_1.c b/lib/gmp/mpn/submul_1.c
new file mode 100644 (file)
index 0000000..8af60a7
--- /dev/null
@@ -0,0 +1,66 @@
+/* mpn_submul_1 -- multiply the S1_SIZE long limb vector pointed to by S1_PTR
+   by S2_LIMB, subtract the S1_SIZE least significant limbs of the product
+   from the limb vector pointed to by RES_PTR.  Return the most significant
+   limb of the product, adjusted for carry-out from the subtraction.
+
+Copyright (C) 1992, 1993, 1994, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include <config.h>
+#include "gmp.h"
+#include "gmp-impl.h"
+#include "longlong.h"
+
+mp_limb_t
+mpn_submul_1 (res_ptr, s1_ptr, s1_size, s2_limb)
+     register mp_ptr res_ptr;
+     register mp_srcptr s1_ptr;
+     mp_size_t s1_size;
+     register mp_limb_t s2_limb;
+{
+  register mp_limb_t cy_limb;
+  register mp_size_t j;
+  register mp_limb_t prod_high, prod_low;
+  register mp_limb_t x;
+
+  /* The loop counter and index J goes from -SIZE to -1.  This way
+     the loop becomes faster.  */
+  j = -s1_size;
+
+  /* Offset the base pointers to compensate for the negative indices.  */
+  res_ptr -= j;
+  s1_ptr -= j;
+
+  cy_limb = 0;
+  do
+    {
+      umul_ppmm (prod_high, prod_low, s1_ptr[j], s2_limb);
+
+      prod_low += cy_limb;
+      cy_limb = (prod_low < cy_limb) + prod_high;
+
+      x = res_ptr[j];
+      prod_low = x - prod_low;
+      cy_limb += (prod_low > x);
+      res_ptr[j] = prod_low;
+    }
+  while (++j != 0);
+
+  return cy_limb;
+}
diff --git a/lib/julcal/ChangeLog b/lib/julcal/ChangeLog
new file mode 100644 (file)
index 0000000..2981049
--- /dev/null
@@ -0,0 +1,49 @@
+Sun Jan  2 21:32:13 2000  Ben Pfaff  <blp@gnu.org>
+
+       * julcal.c: Comment fixes.  Most of the code was rewritten.
+       (_juldnj) Renamed calendar_to_julian.  Interface changed.
+       (juldnj) Removed.
+       (juldnd) Renamed julian_to_calendar.  Interface changed.
+       (julcd) Removed.
+       (julcdd) Removed.
+       (julian_to_mday) New function.
+       (julian_to_wday) New function.
+       [STANDALONE] (main) New test routines.
+
+       * julcal.h: Replaced.
+
+Sat Jan  3 17:09:07 1998  Ben Pfaff  <blp@gnu.org>
+
+       * README: New file.
+
+Fri Dec 26 15:43:57 1997  Ben Pfaff  <blp@gnu.org>
+
+       * julcal.c: (julian_offset) Move glob var definition here.
+
+Sun Jul  6 19:12:18 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Fixed INCLUDES to include intl; fixed directories.
+
+Sun Jun  1 17:27:17 1997  Ben Pfaff  <blp@gnu.org>
+
+       * julcal.h: Made the declaration of macros with arguments a lot
+       nicer looking.
+
+Fri Apr 18 16:48:41 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Refers to src/ as include directory instead of
+       include/.
+
+Fri Apr 18 15:42:22 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Maintainer-clean Makefile.in.
+       
+Thu Oct 24 17:47:14 1996  Ben Pfaff  <blp@gnu.org>
+
+       * julcal.h: Comment fix.
+
+----------------------------------------------------------------------
+Local Variables:
+mode: change-log
+version-control: never
+End:
diff --git a/lib/julcal/Makefile.am b/lib/julcal/Makefile.am
new file mode 100644 (file)
index 0000000..9067458
--- /dev/null
@@ -0,0 +1,11 @@
+## Process this file with automake to produce Makefile.in  -*- makefile -*-
+
+noinst_LIBRARIES = libjulcal.a
+
+INCLUDES = -I$(srcdir) -I$(top_srcdir)/src -I$(top_srcdir) \
+-I$(top_srcdir)/intl
+
+libjulcal_a_SOURCES = julcal.c
+noinst_HEADERS = julcal.h
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/lib/julcal/README b/lib/julcal/README
new file mode 100644 (file)
index 0000000..7cc95aa
--- /dev/null
@@ -0,0 +1,5 @@
+Please note that julcal is not part of PSPP.  Instead, it is a
+separate library that is included in the PSPP distribution for
+convenience in compiling.
+
+       -blp
diff --git a/lib/julcal/julcal.c b/lib/julcal/julcal.c
new file mode 100644 (file)
index 0000000..46b0765
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+   Modified BLP 8/28/95, 12/15/99 for PSPP.
+
+   Original sources for julcal.c and julcal.h can be found at
+   ftp.cdrom.com in /pub/algorithms/c/julcal10/{julcal.c,julcal.h}.
+ */
+
+/*
+   Translated from Pascal to C by Jim Van Zandt, July 1992.
+
+   Error-free translation based on error-free PL/I source
+
+   Based on Pascal code copyright 1985 by Michael A. Covington,
+   published in P.C. Tech Journal, December 1985, based on formulae
+   appearing in Astronomical Formulae for Calculators by Jean Meeus
+ */
+
+/*#include <config.h>*/
+#include <time.h>
+#include <assert.h>
+#include "julcal.h"
+
+#define JUL_OFFSET 2299160L
+
+/* Takes Y, M, and D, and returns the corresponding Julian date as an
+   offset in days from the midnight separating 8 Oct 1582 and 9 Oct
+   1582.  (Y,M,D) = (1999,10,1) corresponds to 1 Oct 1999. */
+long
+calendar_to_julian (int y, int m, int d)
+{
+  m--;
+  y += m / 12;
+  m -= m / 12 * 12;
+
+  assert (m > -12 && m < 12);
+  if (m < 0)
+    {
+      m += 12;
+      y--;
+    }
+
+  assert (m >= 0 && m < 12);
+  if (m < 2)
+    {
+      m += 13;
+      y--;
+    }
+  else
+    m++;
+    
+  return ((1461 * (y + 4716L) / 4)
+         + (153 * (m + 1) / 5)
+         + (d - 1)
+         - 1524
+         + 3
+         - y / 100
+         + y / 400
+         - y / 4000
+         - JUL_OFFSET);
+}
+
+/* Takes a Julian date JD and sets *Y0, *M0, and *D0 to the
+   corresponding year, month, and day, respectively, where
+   (*Y0,*M0,*D0) = (1999,10,1) would be 1 Oct 1999. */
+void
+julian_to_calendar (long jd, int *y0, int *m0, int *d0)
+{
+  int a, ay, em;
+
+  jd += JUL_OFFSET;
+  
+  {
+    long aa, ab;
+    
+    aa = jd - 1721120L;
+    ab = 31 * (aa / 1460969L);
+    aa %= 1460969L;
+    ab += 3 * (aa / 146097L);
+    aa = aa % 146097L;
+    if (aa == 146096L)
+      ab += 3;
+    else
+      ab += aa / 36524L;
+    a = jd + (ab - 2);
+  }
+  
+  {
+    long ee, b, d;
+    
+    b = a + 1524;
+    ay = (20 * b - 2442) / 7305;
+    d = 1461L * ay / 4;
+    ee = b - d;
+    em = 10000 * ee / 306001;
+    if (d0 != NULL)
+      *d0 = ee - 306001L * em / 10000L;
+  }
+
+  if (y0 != NULL || m0 != NULL)
+    {
+      int m = em - 1;
+      if (m > 12)
+       m -= 12;
+      if (m0 != NULL)
+       *m0 = m;
+
+      if (y0 != NULL)
+       {
+         if (m > 2)
+           *y0 = ay - 4716;
+         else
+           *y0 = ay - 4715;
+       }
+      
+    }
+}
+
+/* Takes a julian date JD and returns the corresponding year-relative
+   Julian date, with 1=Jan 1. */
+int
+julian_to_jday (long jd)
+{
+  int year;
+
+  julian_to_calendar (jd, &year, NULL, NULL);
+  return jd - calendar_to_julian (year, 1, 1) + 1;
+}
+
+
+/* Takes a julian date JD and returns the corresponding weekday 1...7,
+   with 1=Sunday. */
+int
+julian_to_wday (long jd)
+{
+  return (jd - 3) % 7 + 1;
+}
+
+#if STANDALONE
+#include <stdio.h>
+
+int
+main (void)
+{
+  {
+    long julian[] = 
+      {
+       1, 50000, 102, 1157, 14288, 87365, 109623, 153211, 152371, 144623,
+      };
+    size_t j;
+
+    for (j = 0; j < sizeof julian / sizeof *julian; j++)
+      {
+       int y, m, d;
+       long jd;
+       julian_to_calendar (julian[j], &y, &m, &d);
+       jd = calendar_to_julian (y, m, d);
+       printf ("%ld => %d/%d/%d => %ld\n", julian[j], y, m, d, jd);
+      }
+  }
+  
+  {
+    int date[][3] = 
+      {
+       {1582,10,15}, {1719,9,6}, {1583,1,24}, {1585,12,14},
+       {1621,11,26}, {1821,12,25}, {1882,12,3}, {2002,4,6},
+       {1999,12,19}, {1978,10,1},
+      };
+    size_t j;
+
+    for (j = 0; j < sizeof date / sizeof *date; j++)
+      {
+       int y = date[j][0], m = date[j][1], d = date[j][2];
+       long jd = calendar_to_julian (y, m, d);
+       int y2, m2, d2;
+       julian_to_calendar (jd, &y2, &m2, &d2);
+       printf ("%d/%d/%d => %ld => %d/%d/%d\n",
+               y, m, d, jd, y2, m2, d2);
+      }
+  }
+    
+  return 0;
+}
+#endif
diff --git a/lib/julcal/julcal.h b/lib/julcal/julcal.h
new file mode 100644 (file)
index 0000000..e1a4415
--- /dev/null
@@ -0,0 +1,15 @@
+/* 
+   Declarations for Julian date routines.
+
+   Modified BLP 8/28/95, 9/26/95, 12/15/99 for PSPP.
+ */
+
+#if !julcal_h
+#define julcal_h 1
+
+long calendar_to_julian (int y, int m, int d);
+void julian_to_calendar (long jd, int *y, int *m, int *d);
+int julian_to_wday (long jd);
+int julian_to_jday (long jd);
+
+#endif /* !julcal_h */
diff --git a/lib/misc/ChangeLog b/lib/misc/ChangeLog
new file mode 100644 (file)
index 0000000..9bcae51
--- /dev/null
@@ -0,0 +1,148 @@
+Sun Jan  2 21:35:47 2000  Ben Pfaff  <blp@gnu.org>
+
+       * qsort.c: Change headers.  Fix __attribute__ for gcc 2.7.2.
+
+       * strcasecmp.c: Remove duplicate inclusion of ctype.h.
+
+       * strncasecmp.c: (strncasecmp) Must cast argument of tolower() to
+       unsigned char.
+
+Sat Jan 23 12:50:16 1999  Ben Pfaff  <blp@gnu.org>
+
+       * strcasecmp.c: (strcasecmp) Fix behavior for zero-length strings.
+       
+Sun Jul  5 00:15:44 1998  Ben Pfaff  <blp@gnu.org>
+
+       * qsort.c: (blp_quicksort) Add unused qualifier to temp_buf when
+       alloca is in use.
+
+1998-02-23  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (EXTRA_DIST) Add strtok_r.c.
+       
+       * strtok_r.c: New file.
+
+1998-02-16  Ben Pfaff  <blp@gnu.org>
+
+       * memmem.c: Cast void * to char * before dereferencing, in a
+       different place.
+
+Fri Feb 13 15:35:55 1998  Ben Pfaff  <blp@gnu.org>
+
+       * memmem.c: Cast void * to char * before dereferencing.  Reported
+       by palme@uni-wuppertal.de (Hubert Palme).
+
+Sun Jan 18 00:30:38 1998  Ben Pfaff  <blp@gnu.org>
+
+       * memmem.c: Fix argument types.
+
+Sun Oct  5 15:54:37 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (EXTRA_DIST) Add strerror.c.  From Alexandre Oliva
+       <oliva@dcc.unicamp.br>.
+
+       * strerror.c: New file.  From Alexandre Oliva
+       <oliva@dcc.unicamp.br>.
+
+Thu Sep 18 21:34:07 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (libmisc_a_SOURCES) Added getopt.c, getopt1.c.
+       (EXTRA_DIST) Removed getopt.c, getopt1.c.
+
+Thu Jul 17 01:50:43 1997  Ben Pfaff  <blp@gnu.org>
+
+       * strncasecmp.c: (strncasecmp) Rewritten to fix undefined
+       behavior.
+
+Fri Jul 11 14:06:04 1997  Ben Pfaff  <blp@gnu.org>
+
+       * getdelim.c: Added in some necessary #include's.
+
+       * getline.c: #include's <config.h>.  Added getdelim() prototype.
+
+       * memmem.c: #include's <stddef.h>.
+       (memmem) `i' now a size_t.  Avoid subtraction of unsigned's.
+
+Sun Jul  6 19:12:35 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Fixed INCLUDES to include intl; fixed directories.
+
+Mon Jun  2 14:22:24 1997  Ben Pfaff  <blp@gnu.org>
+
+       * getopt.c: Marked strings for gettext.
+
+Fri Apr 18 16:48:41 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Refers to src/ as include directory instead of
+       include/.
+
+Fri Apr 18 15:42:22 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Maintainer-clean Makefile.in.
+       
+Thu Mar 27 01:11:29 1997  Ben Pfaff  <blp@gnu.org>
+
+       * alloca.c: Only compiled if necessary. 
+
+       * getdelim.c: New file.
+       * getline.c: New file.
+       * memchr.c: New file.
+       * memcpy.c: New file.
+       * memmem.c: New file.
+       * memmove.c: New file.
+       * memset.c: New file.
+       * strcasecmp.c: New file.
+       * strncasecmp.c: New file.
+       * strpbrk.c: New file.
+       * strstr.c: New file.
+       * strtol.c: New file.
+       * strtoul.c: New file.
+       
+Sun Dec 15 15:32:16 1996  Ben Pfaff  <blp@gnu.org>
+
+       * qsort.c: New file, essentially unchanged from the glibc-1.09
+       distribution.
+
+Mon Nov 11 15:34:09 1996  Ben Pfaff  <blp@gnu.org>
+
+       * avl.c: (destroy) Format fix.
+       (avl_destroy) Only calls destroy() if the tree has a non-NULL
+       root.
+
+Thu Nov  7 20:52:28 1996  Ben Pfaff  <blp@gnu.org>
+
+       * avl.h: (force_avl_insert, force_avl_delete) New macros/functions
+       that assure that a node was successfully added/deleted, active
+       only when GLOBAL_DEBUGGING.  Most occurrences of
+       avl_insert/avl_delete changed to use these instead.
+
+       * avl.c: [GLOBAL_DEBUGGING] (force_avl_delete) New function.
+
+Tue Sep 24 18:39:09 1996  Ben Pfaff  <blp@gnu.org>
+
+       * stpcpy.c: Comment fix.
+
+Fri Sep 20 22:52:28 1996  Ben Pfaff  <blp@gnu.org>
+
+       * alloca.c: Changed conditions for inclusion.
+
+Tue Jul 23 21:48:36 1996  Ben Pfaff  <blp@gnu.org>
+
+       * avl.c: Formatting fixes.
+       (balance) Fixed bug introduced in last patchlevel that reversed
+       the truth value returned by final statement.
+       (find) Always returns NULL if end of function reached.
+       (avl_walk) Split into two functions, walk_inorder and
+       walk_preorder.  All callers and callees changed.
+       (avl_sort) New function.
+
+Fri Jul 19 19:11:13 1996  Ben Pfaff  <blp@gnu.org>
+
+       * avl.h, avl.c: Completely reworked, might as well be considered
+       new files.  All callers, all references to AVL trees changed.
+
+----------------------------------------------------------------------
+Local Variables:
+mode: change-log
+version-control: never
+End:
diff --git a/lib/misc/Makefile.am b/lib/misc/Makefile.am
new file mode 100644 (file)
index 0000000..bedc884
--- /dev/null
@@ -0,0 +1,16 @@
+## Process this file with automake to produce Makefile.in  -*- makefile -*-
+
+noinst_LIBRARIES = libmisc.a
+
+INCLUDES = -I$(srcdir) -I$(top_srcdir)/src -I$(top_srcdir) \
+-I$(top_srcdir)/intl
+
+libmisc_a_SOURCES = qsort.c getopt.c getopt1.c
+libmisc_a_LIBADD = @ALLOCA@ @LIBOBJS@
+libmisc_a_DEPENDENCIES = @ALLOCA@ @LIBOBJS@
+
+EXTRA_DIST = alloca.c getdelim.c getline.c memchr.c memcmp.c memcpy.c \
+memmem.c memmove.c memset.c stpcpy.c strcasecmp.c strerror.c \
+strncasecmp.c strpbrk.c strstr.c strtok_r.c strtol.c strtoul.c
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/lib/misc/alloca.c b/lib/misc/alloca.c
new file mode 100644 (file)
index 0000000..be1f0c4
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+   alloca -- (mostly) portable public-domain implementation -- D A Gwyn
+
+   edited 8/22/95, 2/28/96, 3/28/96 by BLP for PSPP
+
+   edited 86/05/30      rms
+   include config.h, since on VMS it renames some symbols.
+   Use xmalloc instead of malloc.
+
+   This implementation of the PWB library alloca() function,
+   which is used to allocate space off the run-time stack so
+   that it is automatically reclaimed upon procedure exit, 
+   was inspired by discussions with J. Q. Johnson of Cornell.
+
+   It should work under any C implementation that uses an
+   actual procedure stack (as opposed to a linked list of
+   frames).  There are some preprocessor constants that can
+   be defined when compiling for your specific system, for
+   improved efficiency; however, the defaults should be okay.
+
+   The general concept of this implementation is to keep
+   track of all alloca()-allocated blocks, and reclaim any
+   that are found to be deeper in the stack than the current
+   invocation.  This heuristic does not reclaim storage as
+   soon as it becomes invalid, but it will do so eventually.
+
+   As a special case, alloca(0) reclaims storage without
+   allocating any.  It is a good idea to use alloca(0) in
+   your main control loop, etc. to force garbage collection.
+ */
+
+#if C_ALLOCA
+
+#include <config.h>
+#include <stdlib.h>
+#include "common.h"
+
+typedef void *pointer;         /* generic pointer type */
+#define        NULL    0               /* null pointer constant */
+
+extern void free ();
+extern pointer xmalloc ();
+
+/*
+   Define STACK_DIRECTION if you know the direction of stack
+   growth for your system; otherwise it will be automatically
+   deduced at run-time.
+
+   STACK_DIRECTION > 0 => grows toward higher addresses
+   STACK_DIRECTION < 0 => grows toward lower addresses
+   STACK_DIRECTION = 0 => direction of growth unknown
+ */
+
+#ifndef STACK_DIRECTION
+#define        STACK_DIRECTION 0       /* direction unknown */
+#endif
+
+#if STACK_DIRECTION != 0
+
+#define        STACK_DIR       STACK_DIRECTION         /* known at compile-time */
+
+#else /* STACK_DIRECTION == 0; need run-time code */
+
+static int stack_dir;          /* 1 or -1 once known */
+#define        STACK_DIR       stack_dir
+
+static void
+find_stack_direction (void)
+{
+  static char *addr = NULL;    /* address of first
+                                  `dummy', once known */
+  auto char dummy;             /* to get stack address */
+
+  if (addr == NULL)
+    {                          /* initial entry */
+      addr = &dummy;
+
+      find_stack_direction (); /* recurse once */
+    }
+  else
+    /* second entry */ if (&dummy > addr)
+    stack_dir = 1;             /* stack grew upward */
+  else
+    stack_dir = -1;            /* stack grew downward */
+}
+
+#endif /* STACK_DIRECTION == 0 */
+
+/*
+   An "alloca header" is used to:
+   (a) chain together all alloca()ed blocks;
+   (b) keep track of stack depth.
+
+   PORTME: It is very important that sizeof(header) agree with
+   malloc() alignment chunk size.  The following default should
+   work okay.  */
+
+#ifndef        ALIGN_SIZE
+#define        ALIGN_SIZE      sizeof(double)
+#endif
+
+typedef union hdr
+{
+  char align[ALIGN_SIZE];      /* to force sizeof(header) */
+  struct
+    {
+      union hdr *next;         /* for chaining headers */
+      char *deep;              /* for stack depth measure */
+    }
+  h;
+}
+header;
+
+/*
+   alloca( size ) returns a pointer to at least `size' bytes of
+   storage which will be automatically reclaimed upon exit from
+   the procedure that called alloca().  Originally, this space
+   was supposed to be taken from the current stack frame of the
+   caller, but that method cannot be made to work for some
+   implementations of C, for example under Gould's UTX/32.
+ */
+
+static header *last_alloca_header = NULL;      /* -> last alloca header */
+
+pointer
+alloca (unsigned size)         /* returns pointer to storage */
+{
+  auto char probe;             /* probes stack depth: */
+  register char *depth = &probe;
+
+#if STACK_DIRECTION == 0
+  if (STACK_DIR == 0)          /* unknown growth direction */
+    find_stack_direction ();
+#endif
+
+  /* Reclaim garbage, defined as all alloca()ed storage that
+     was allocated from deeper in the stack than currently. */
+
+  {
+    register header *hp;       /* traverses linked list */
+
+    for (hp = last_alloca_header; hp != NULL;)
+      if (STACK_DIR > 0 && hp->h.deep > depth
+         || STACK_DIR < 0 && hp->h.deep < depth)
+       {
+         register header *np = hp->h.next;
+
+         free ((pointer) hp);  /* collect garbage */
+
+         hp = np;              /* -> next header */
+       }
+      else
+       break;                  /* rest are not deeper */
+
+    last_alloca_header = hp;   /* -> last valid storage */
+  }
+
+  if (size == 0)
+    return NULL;               /* no allocation required */
+
+  /* Allocate combined header + user data storage. */
+
+  {
+    register pointer new = xmalloc (sizeof (header) + size);
+    /* address of header */
+
+    ((header *) new)->h.next = last_alloca_header;
+    ((header *) new)->h.deep = depth;
+
+    last_alloca_header = (header *) new;
+
+    /* User storage begins just after header. */
+
+    return (pointer) ((char *) new + sizeof (header));
+  }
+}
+
+#endif /* !__GNUC__ && !__BORLANDC__ */
diff --git a/lib/misc/getdelim.c b/lib/misc/getdelim.c
new file mode 100644 (file)
index 0000000..3e74f94
--- /dev/null
@@ -0,0 +1,72 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997, 1998 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stddef.h>
+#include <stdio.h>
+#include "common.h"
+
+/* Reads a DELIMITER-separated field of any length from file STREAM.
+   *LINEPTR is a malloc'd string of size N; if *LINEPTR is NULL, it is
+   allocated.  *LINEPTR is allocated/enlarged as necessary.  Returns
+   -1 if at eof when entered; otherwise eof causes return of string
+   without a terminating DELIMITER.  Normally DELIMITER is the last
+   character in *LINEPTR on return (besides the null character which
+   is always present).  Returns number of characters read, including
+   terminating field delimiter if present. */
+long
+getdelim (char **lineptr, size_t *n, int delimiter, FILE *stream)
+{
+  /* Number of characters stored in *lineptr so far. */
+  size_t len;
+
+  /* Last character read. */
+  int c;
+
+  if (*lineptr == NULL || *n < 2)
+    {
+      *lineptr = xrealloc (*lineptr, 128);
+      *n = 128;
+    }
+  assert (*n > 0);
+
+  len = 0;
+  c = getc (stream);
+  if (c == EOF)
+    return -1;
+  while (1)
+    {
+      if (len + 1 >= *n)
+       {
+         *n *= 2;
+         *lineptr = xrealloc (*lineptr, *n);
+       }
+      (*lineptr)[len++] = c;
+
+      if (c == delimiter)
+       break;
+
+      c = getc (stream);
+      if (c == EOF)
+       break;
+    }
+  (*lineptr)[len] = '\0';
+  return len;
+}
diff --git a/lib/misc/getline.c b/lib/misc/getline.c
new file mode 100644 (file)
index 0000000..5da0e0b
--- /dev/null
@@ -0,0 +1,32 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997, 1998 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#if !HAVE_GETDELIM
+long getdelim (char **lineptr, size_t *n, int delimiter, FILE *stream);
+#endif
+
+long
+getline (char **lineptr, size_t *n, FILE *stream)
+{
+  return getdelim (lineptr, n, '\n', stream);
+}
diff --git a/lib/misc/getopt.c b/lib/misc/getopt.c
new file mode 100644 (file)
index 0000000..fa21517
--- /dev/null
@@ -0,0 +1,754 @@
+/* Getopt for GNU.
+   NOTE: getopt is now part of the C library, so if you don't know what
+   "Keep this file name-space clean" means, talk to roland@gnu.org
+   before changing it!
+
+   Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94
+   Free Software Foundation, Inc.
+
+   This file is part of the GNU C Library.  Its master source is NOT part of
+   the C library, however.  The master source lives in /gd/gnu/lib.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If
+   not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+   Cambridge, MA 02139, USA.  */
+\f
+/* This file has been modified from the GNU libc distribution. */
+
+/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>.
+   Ditto for AIX 3.2 and <stdlib.h>.  */
+#ifndef _NO_PROTO
+#define _NO_PROTO
+#endif
+
+#include <config.h>
+
+#if !defined (__STDC__) || !__STDC__
+/* This is a separate conditional since some stdc systems
+   reject `defined (const)'.  */
+#ifndef const
+#define const
+#endif
+#endif
+
+#include <stdio.h>
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+   actually compiling the library itself.  This code is part of the GNU C
+   Library, but also included in many other GNU distributions.  Compiling
+   and linking in this code is a waste when using the GNU C library
+   (especially if it is a shared library).  Rather than having every GNU
+   program understand `configure --with-gnu-libc' and omit the object files,
+   it is simpler to just do this in the source for each such file.  */
+
+#if defined (_LIBC) || !defined (__GNU_LIBRARY__)
+
+
+/* This needs to come after some library #include
+   to get __GNU_LIBRARY__ defined.  */
+#ifdef __GNU_LIBRARY__
+/* Don't include stdlib.h for non-GNU C libraries because some of them
+   contain conflicting prototypes for getopt.  */
+#include <stdlib.h>
+#endif /* GNU C library.  */
+
+/* This version of `getopt' appears to the caller like standard Unix `getopt'
+   but it behaves differently for the user, since it allows the user
+   to intersperse the options with the other arguments.
+
+   As `getopt' works, it permutes the elements of ARGV so that,
+   when it is done, all the options precede everything else.  Thus
+   all application programs are extended to handle flexible argument order.
+
+   Setting the environment variable POSIXLY_CORRECT disables permutation.
+   Then the behavior is completely standard.
+
+   GNU application programs can use a third alternative mode in which
+   they can distinguish the relative order of options and other arguments.  */
+
+#include "getopt.h"
+
+/* For communication from `getopt' to the caller.
+   When `getopt' finds an option that takes an argument,
+   the argument value is returned here.
+   Also, when `ordering' is RETURN_IN_ORDER,
+   each non-option ARGV-element is returned here.  */
+
+char *optarg = NULL;
+
+/* Index in ARGV of the next element to be scanned.
+   This is used for communication to and from the caller
+   and for communication between successive calls to `getopt'.
+
+   On entry to `getopt', zero means this is the first call; initialize.
+
+   When `getopt' returns EOF, this is the index of the first of the
+   non-option elements that the caller should itself scan.
+
+   Otherwise, `optind' communicates from one call to the next
+   how much of ARGV has been scanned so far.  */
+
+/* XXX 1003.2 says this must be 1 before any call.  */
+int optind = 0;
+
+/* The next char to be scanned in the option-element
+   in which the last option character we returned was found.
+   This allows us to pick up the scan where we left off.
+
+   If this is zero, or a null string, it means resume the scan
+   by advancing to the next ARGV-element.  */
+
+static char *nextchar;
+
+/* Callers store zero here to inhibit the error message
+   for unrecognized options.  */
+
+int opterr = 1;
+
+/* Set to an option character which was unrecognized.
+   This must be initialized on some systems to avoid linking in the
+   system's own getopt implementation.  */
+
+int optopt = '?';
+
+/* Describe how to deal with options that follow non-option ARGV-elements.
+
+   If the caller did not specify anything,
+   the default is REQUIRE_ORDER if the environment variable
+   POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+   REQUIRE_ORDER means don't recognize them as options;
+   stop option processing when the first non-option is seen.
+   This is what Unix does.
+   This mode of operation is selected by either setting the environment
+   variable POSIXLY_CORRECT, or using `+' as the first character
+   of the list of option characters.
+
+   PERMUTE is the default.  We permute the contents of ARGV as we scan,
+   so that eventually all the non-options are at the end.  This allows options
+   to be given in any order, even with programs that were not written to
+   expect this.
+
+   RETURN_IN_ORDER is an option available to programs that were written
+   to expect options and other ARGV-elements in any order and that care about
+   the ordering of the two.  We describe each non-option ARGV-element
+   as if it were the argument of an option with character code 1.
+   Using `-' as the first character of the list of option characters
+   selects this mode of operation.
+
+   The special argument `--' forces an end of option-scanning regardless
+   of the value of `ordering'.  In the case of RETURN_IN_ORDER, only
+   `--' can cause `getopt' to return EOF with `optind' != ARGC.  */
+
+static enum
+  {
+    REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+  }
+ordering;
+
+/* Value of POSIXLY_CORRECT environment variable.  */
+static char *posixly_correct;
+\f
+#ifdef __GNU_LIBRARY__
+/* We want to avoid inclusion of string.h with non-GNU libraries
+   because there are many ways it can cause trouble.
+   On some systems, it contains special magic macros that don't work
+   in GCC.  */
+#include <string.h>
+#define        my_index        strchr
+#else
+
+/* Avoid depending on library functions or files
+   whose names are inconsistent.  */
+
+char *getenv ();
+
+static char *
+my_index (str, chr)
+     const char *str;
+     int chr;
+{
+  while (*str)
+    {
+      if (*str == chr)
+       return (char *) str;
+      str++;
+    }
+  return 0;
+}
+
+/* If using GCC, we can safely declare strlen this way.
+   If not using GCC, it is ok not to declare it.  */
+#ifdef __GNUC__
+/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h.
+   That was relevant to code that was here before.  */
+#if !defined (__STDC__) || !__STDC__
+/* gcc with -traditional declares the built-in strlen to return int,
+   and has done so at least since version 2.4.5. -- rms.  */
+extern int strlen (const char *);
+#endif /* not __STDC__ */
+#endif /* __GNUC__ */
+
+#endif /* not __GNU_LIBRARY__ */
+\f
+/* Handle permutation of arguments.  */
+
+/* Describe the part of ARGV that contains non-options that have
+   been skipped.  `first_nonopt' is the index in ARGV of the first of them;
+   `last_nonopt' is the index after the last of them.  */
+
+static int first_nonopt;
+static int last_nonopt;
+
+/* Exchange two adjacent subsequences of ARGV.
+   One subsequence is elements [first_nonopt,last_nonopt)
+   which contains all the non-options that have been skipped so far.
+   The other is elements [last_nonopt,optind), which contains all
+   the options processed since those non-options were skipped.
+
+   `first_nonopt' and `last_nonopt' are relocated so that they describe
+   the new indices of the non-options in ARGV after they are moved.  */
+
+static void
+exchange (argv)
+     char **argv;
+{
+  int bottom = first_nonopt;
+  int middle = last_nonopt;
+  int top = optind;
+  char *tem;
+
+  /* Exchange the shorter segment with the far end of the longer segment.
+     That puts the shorter segment into the right place.
+     It leaves the longer segment in the right place overall,
+     but it consists of two parts that need to be swapped next.  */
+
+  while (top > middle && middle > bottom)
+    {
+      if (top - middle > middle - bottom)
+       {
+         /* Bottom segment is the short one.  */
+         int len = middle - bottom;
+         register int i;
+
+         /* Swap it with the top part of the top segment.  */
+         for (i = 0; i < len; i++)
+           {
+             tem = argv[bottom + i];
+             argv[bottom + i] = argv[top - (middle - bottom) + i];
+             argv[top - (middle - bottom) + i] = tem;
+           }
+         /* Exclude the moved bottom segment from further swapping.  */
+         top -= len;
+       }
+      else
+       {
+         /* Top segment is the short one.  */
+         int len = top - middle;
+         register int i;
+
+         /* Swap it with the bottom part of the bottom segment.  */
+         for (i = 0; i < len; i++)
+           {
+             tem = argv[bottom + i];
+             argv[bottom + i] = argv[middle + i];
+             argv[middle + i] = tem;
+           }
+         /* Exclude the moved top segment from further swapping.  */
+         bottom += len;
+       }
+    }
+
+  /* Update records for the slots the non-options now occupy.  */
+
+  first_nonopt += (optind - last_nonopt);
+  last_nonopt = optind;
+}
+
+/* Initialize the internal data when the first call is made.  */
+
+static const char *
+_getopt_initialize (optstring)
+     const char *optstring;
+{
+  /* Start processing options with ARGV-element 1 (since ARGV-element 0
+     is the program name); the sequence of previously skipped
+     non-option ARGV-elements is empty.  */
+
+  first_nonopt = last_nonopt = optind = 1;
+
+  nextchar = NULL;
+
+  posixly_correct = getenv ("POSIXLY_CORRECT");
+
+  /* Determine how to handle the ordering of options and nonoptions.  */
+
+  if (optstring[0] == '-')
+    {
+      ordering = RETURN_IN_ORDER;
+      ++optstring;
+    }
+  else if (optstring[0] == '+')
+    {
+      ordering = REQUIRE_ORDER;
+      ++optstring;
+    }
+  else if (posixly_correct != NULL)
+    ordering = REQUIRE_ORDER;
+  else
+    ordering = PERMUTE;
+
+  return optstring;
+}
+\f
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+   given in OPTSTRING.
+
+   If an element of ARGV starts with '-', and is not exactly "-" or "--",
+   then it is an option element.  The characters of this element
+   (aside from the initial '-') are option characters.  If `getopt'
+   is called repeatedly, it returns successively each of the option characters
+   from each of the option elements.
+
+   If `getopt' finds another option character, it returns that character,
+   updating `optind' and `nextchar' so that the next call to `getopt' can
+   resume the scan with the following option character or ARGV-element.
+
+   If there are no more option characters, `getopt' returns `EOF'.
+   Then `optind' is the index in ARGV of the first ARGV-element
+   that is not an option.  (The ARGV-elements have been permuted
+   so that those that are not options now come last.)
+
+   OPTSTRING is a string containing the legitimate option characters.
+   If an option character is seen that is not listed in OPTSTRING,
+   return '?' after printing an error message.  If you set `opterr' to
+   zero, the error message is suppressed but we still return '?'.
+
+   If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+   so the following text in the same ARGV-element, or the text of the following
+   ARGV-element, is returned in `optarg'.  Two colons mean an option that
+   wants an optional arg; if there is text in the current ARGV-element,
+   it is returned in `optarg', otherwise `optarg' is set to zero.
+
+   If OPTSTRING starts with `-' or `+', it requests different methods of
+   handling the non-option ARGV-elements.
+   See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+   Long-named options begin with `--' instead of `-'.
+   Their names may be abbreviated as long as the abbreviation is unique
+   or is an exact match for some defined option.  If they have an
+   argument, it follows the option name in the same ARGV-element, separated
+   from the option name by a `=', or else the in next ARGV-element.
+   When `getopt' finds a long-named option, it returns 0 if that option's
+   `flag' field is nonzero, the value of the option's `val' field
+   if the `flag' field is zero.
+
+   The elements of ARGV aren't really const, because we permute them.
+   But we pretend they're const in the prototype to be compatible
+   with other systems.
+
+   LONGOPTS is a vector of `struct option' terminated by an
+   element containing a name which is zero.
+
+   LONGIND returns the index in LONGOPT of the long-named option found.
+   It is only valid when a long-named option has been found by the most
+   recent call.
+
+   If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+   long-named options.  */
+
+int
+_getopt_internal (argc, argv, optstring, longopts, longind, long_only)
+     int argc;
+     char *const *argv;
+     const char *optstring;
+     const struct option *longopts;
+     int *longind;
+     int long_only;
+{
+  optarg = NULL;
+
+  if (optind == 0)
+    optstring = _getopt_initialize (optstring);
+
+  if (nextchar == NULL || *nextchar == '\0')
+    {
+      /* Advance to the next ARGV-element.  */
+
+      if (ordering == PERMUTE)
+       {
+         /* If we have just processed some options following some non-options,
+            exchange them so that the options come first.  */
+
+         if (first_nonopt != last_nonopt && last_nonopt != optind)
+           exchange ((char **) argv);
+         else if (last_nonopt != optind)
+           first_nonopt = optind;
+
+         /* Skip any additional non-options
+            and extend the range of non-options previously skipped.  */
+
+         while (optind < argc
+                && (argv[optind][0] != '-' || argv[optind][1] == '\0'))
+           optind++;
+         last_nonopt = optind;
+       }
+
+      /* The special ARGV-element `--' means premature end of options.
+         Skip it like a null option,
+         then exchange with previous non-options as if it were an option,
+         then skip everything else like a non-option.  */
+
+      if (optind != argc && !strcmp (argv[optind], "--"))
+       {
+         optind++;
+
+         if (first_nonopt != last_nonopt && last_nonopt != optind)
+           exchange ((char **) argv);
+         else if (first_nonopt == last_nonopt)
+           first_nonopt = optind;
+         last_nonopt = argc;
+
+         optind = argc;
+       }
+
+      /* If we have done all the ARGV-elements, stop the scan
+         and back over any non-options that we skipped and permuted.  */
+
+      if (optind == argc)
+       {
+         /* Set the next-arg-index to point at the non-options
+            that we previously skipped, so the caller will digest them.  */
+         if (first_nonopt != last_nonopt)
+           optind = first_nonopt;
+         return EOF;
+       }
+
+      /* If we have come to a non-option and did not permute it,
+         either stop the scan or describe it to the caller and pass it by.  */
+
+      if ((argv[optind][0] != '-' || argv[optind][1] == '\0'))
+       {
+         if (ordering == REQUIRE_ORDER)
+           return EOF;
+         optarg = argv[optind++];
+         return 1;
+       }
+
+      /* We have found another option-ARGV-element.
+         Skip the initial punctuation.  */
+
+      nextchar = (argv[optind] + 1
+                 + (longopts != NULL && argv[optind][1] == '-'));
+    }
+
+  /* Decode the current option-ARGV-element.  */
+
+  /* Check whether the ARGV-element is a long option.
+
+     If long_only and the ARGV-element has the form "-f", where f is
+     a valid short option, don't consider it an abbreviated form of
+     a long option that starts with f.  Otherwise there would be no
+     way to give the -f short option.
+
+     On the other hand, if there's a long option "fubar" and
+     the ARGV-element is "-fu", do consider that an abbreviation of
+     the long option, just like "--fu", and not "-f" with arg "u".
+
+     This distinction seems to be the most useful approach.  */
+
+  if (longopts != NULL
+      && (argv[optind][1] == '-'
+         || (long_only && (argv[optind][2]
+                           || !my_index (optstring, argv[optind][1])))))
+    {
+      char *nameend;
+      const struct option *p;
+      const struct option *pfound = NULL;
+      int exact = 0;
+      int ambig = 0;
+      int indfound;
+      int option_index;
+
+      for (nameend = nextchar; *nameend && *nameend != '='; nameend++)
+       /* Do nothing.  */ ;
+
+      /* Test all long options for either exact match
+         or abbreviated matches.  */
+      for (p = longopts, option_index = 0; p->name; p++, option_index++)
+       if (!strncmp (p->name, nextchar, nameend - nextchar))
+         {
+           if (nameend - nextchar == strlen (p->name))
+             {
+               /* Exact match found.  */
+               pfound = p;
+               indfound = option_index;
+               exact = 1;
+               break;
+             }
+           else if (pfound == NULL)
+             {
+               /* First nonexact match found.  */
+               pfound = p;
+               indfound = option_index;
+             }
+           else
+             /* Second or later nonexact match found.  */
+             ambig = 1;
+         }
+
+      if (ambig && !exact)
+       {
+         if (opterr)
+           fprintf (stderr, _("%s: option `%s' is ambiguous\n"),
+                    argv[0], argv[optind]);
+         nextchar += strlen (nextchar);
+         optind++;
+         return '?';
+       }
+
+      if (pfound != NULL)
+       {
+         option_index = indfound;
+         optind++;
+         if (*nameend)
+           {
+             /* Don't test has_arg with >, because some C compilers don't
+                allow it to be used on enums.  */
+             if (pfound->has_arg)
+               optarg = nameend + 1;
+             else
+               {
+                 if (opterr)
+                   {
+                     if (argv[optind - 1][1] == '-')
+                       /* --option */
+                       fprintf (stderr,
+                           _("%s: option `--%s' doesn't allow an argument\n"),
+                                argv[0], pfound->name);
+                     else
+                       /* +option or -option */
+                       fprintf (stderr,
+                           _("%s: option `%c%s' doesn't allow an argument\n"),
+                                argv[0], argv[optind - 1][0], pfound->name);
+                   }
+                 nextchar += strlen (nextchar);
+                 return '?';
+               }
+           }
+         else if (pfound->has_arg == 1)
+           {
+             if (optind < argc)
+               optarg = argv[optind++];
+             else
+               {
+                 if (opterr)
+                   fprintf (stderr, _("%s: option `%s' requires an argument\n"),
+                            argv[0], argv[optind - 1]);
+                 nextchar += strlen (nextchar);
+                 return optstring[0] == ':' ? ':' : '?';
+               }
+           }
+         nextchar += strlen (nextchar);
+         if (longind != NULL)
+           *longind = option_index;
+         if (pfound->flag)
+           {
+             *(pfound->flag) = pfound->val;
+             return 0;
+           }
+         return pfound->val;
+       }
+
+      /* Can't find it as a long option.  If this is not getopt_long_only,
+         or the option starts with '--' or is not a valid short
+         option, then it's an error.
+         Otherwise interpret it as a short option.  */
+      if (!long_only || argv[optind][1] == '-'
+         || my_index (optstring, *nextchar) == NULL)
+       {
+         if (opterr)
+           {
+             if (argv[optind][1] == '-')
+               /* --option */
+               fprintf (stderr, _("%s: unrecognized option `--%s'\n"),
+                        argv[0], nextchar);
+             else
+               /* +option or -option */
+               fprintf (stderr, _("%s: unrecognized option `%c%s'\n"),
+                        argv[0], argv[optind][0], nextchar);
+           }
+         nextchar = (char *) "";
+         optind++;
+         return '?';
+       }
+    }
+
+  /* Look at and handle the next short option-character.  */
+
+  {
+    char c = *nextchar++;
+    char *temp = my_index (optstring, c);
+
+    /* Increment `optind' when we start to process its last character.  */
+    if (*nextchar == '\0')
+      ++optind;
+
+    if (temp == NULL || c == ':')
+      {
+       if (opterr)
+         {
+           if (posixly_correct)
+             /* 1003.2 specifies the format of this message.  */
+             fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], c);
+           else
+             fprintf (stderr, _("%s: invalid option -- %c\n"), argv[0], c);
+         }
+       optopt = c;
+       return '?';
+      }
+    if (temp[1] == ':')
+      {
+       if (temp[2] == ':')
+         {
+           /* This is an option that accepts an argument optionally.  */
+           if (*nextchar != '\0')
+             {
+               optarg = nextchar;
+               optind++;
+             }
+           else
+             optarg = NULL;
+           nextchar = NULL;
+         }
+       else
+         {
+           /* This is an option that requires an argument.  */
+           if (*nextchar != '\0')
+             {
+               optarg = nextchar;
+               /* If we end this ARGV-element by taking the rest as an arg,
+                  we must advance to the next element now.  */
+               optind++;
+             }
+           else if (optind == argc)
+             {
+               if (opterr)
+                 {
+                   /* 1003.2 specifies the format of this message.  */
+                   fprintf (stderr, _("%s: option requires an argument -- %c\n"),
+                            argv[0], c);
+                 }
+               optopt = c;
+               if (optstring[0] == ':')
+                 c = ':';
+               else
+                 c = '?';
+             }
+           else
+             /* We already incremented `optind' once;
+                increment it again when taking next ARGV-elt as argument.  */
+             optarg = argv[optind++];
+           nextchar = NULL;
+         }
+      }
+    return c;
+  }
+}
+
+int
+getopt (argc, argv, optstring)
+     int argc;
+     char *const *argv;
+     const char *optstring;
+{
+  return _getopt_internal (argc, argv, optstring,
+                          (const struct option *) 0,
+                          (int *) 0,
+                          0);
+}
+
+#endif /* _LIBC or not __GNU_LIBRARY__.  */
+\f
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+   the above definition of `getopt'.  */
+
+int
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int c;
+  int digit_optind = 0;
+
+  while (1)
+    {
+      int this_option_optind = optind ? optind : 1;
+
+      c = getopt (argc, argv, "abc:d:0123456789");
+      if (c == EOF)
+       break;
+
+      switch (c)
+       {
+       case '0':
+       case '1':
+       case '2':
+       case '3':
+       case '4':
+       case '5':
+       case '6':
+       case '7':
+       case '8':
+       case '9':
+         if (digit_optind != 0 && digit_optind != this_option_optind)
+           printf ("digits occur in two different argv-elements.\n");
+         digit_optind = this_option_optind;
+         printf ("option %c\n", c);
+         break;
+
+       case 'a':
+         printf ("option a\n");
+         break;
+
+       case 'b':
+         printf ("option b\n");
+         break;
+
+       case 'c':
+         printf ("option c with value `%s'\n", optarg);
+         break;
+
+       case '?':
+         break;
+
+       default:
+         printf ("?? getopt returned character code 0%o ??\n", c);
+       }
+    }
+
+  if (optind < argc)
+    {
+      printf ("non-option ARGV-elements: ");
+      while (optind < argc)
+       printf ("%s ", argv[optind++]);
+      printf ("\n");
+    }
+
+  exit (0);
+}
+
+#endif /* TEST */
diff --git a/lib/misc/getopt1.c b/lib/misc/getopt1.c
new file mode 100644 (file)
index 0000000..361872a
--- /dev/null
@@ -0,0 +1,183 @@
+/* getopt_long and getopt_long_only entry points for GNU getopt.
+   Copyright (C) 1987, 88, 89, 90, 91, 92, 1993, 1994
+   Free Software Foundation, Inc.
+
+   This file is part of the GNU C Library.  Its master source is NOT part of
+   the C library, however.  The master source lives in /gd/gnu/lib.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If
+   not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+   Cambridge, MA 02139, USA.  */
+\f
+/* This file has been modified from the GNU libc distribution. */
+#include <config.h>
+
+#include "getopt.h"
+
+#if !defined (__STDC__) || !__STDC__
+/* This is a separate conditional since some stdc systems
+   reject `defined (const)'.  */
+#ifndef const
+#define const
+#endif
+#endif
+
+#include <stdio.h>
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+   actually compiling the library itself.  This code is part of the GNU C
+   Library, but also included in many other GNU distributions.  Compiling
+   and linking in this code is a waste when using the GNU C library
+   (especially if it is a shared library).  Rather than having every GNU
+   program understand `configure --with-gnu-libc' and omit the object files,
+   it is simpler to just do this in the source for each such file.  */
+
+#if defined (_LIBC) || !defined (__GNU_LIBRARY__)
+
+
+/* This needs to come after some library #include
+   to get __GNU_LIBRARY__ defined.  */
+#ifdef __GNU_LIBRARY__
+#include <stdlib.h>
+#else
+char *getenv ();
+#endif
+
+#ifndef        NULL
+#define NULL 0
+#endif
+
+int
+getopt_long (argc, argv, options, long_options, opt_index)
+     int argc;
+     char *const *argv;
+     const char *options;
+     const struct option *long_options;
+     int *opt_index;
+{
+  return _getopt_internal (argc, argv, options, long_options, opt_index, 0);
+}
+
+/* Like getopt_long, but '-' as well as '--' can indicate a long option.
+   If an option that starts with '-' (not '--') doesn't match a long option,
+   but does match a short option, it is parsed as a short option
+   instead.  */
+
+int
+getopt_long_only (argc, argv, options, long_options, opt_index)
+     int argc;
+     char *const *argv;
+     const char *options;
+     const struct option *long_options;
+     int *opt_index;
+{
+  return _getopt_internal (argc, argv, options, long_options, opt_index, 1);
+}
+
+
+#endif /* _LIBC or not __GNU_LIBRARY__.  */
+\f
+#ifdef TEST
+
+#include <stdio.h>
+
+int
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int c;
+  int digit_optind = 0;
+
+  while (1)
+    {
+      int this_option_optind = optind ? optind : 1;
+      int option_index = 0;
+      static struct option long_options[] =
+      {
+       {"add", 1, 0, 0},
+       {"append", 0, 0, 0},
+       {"delete", 1, 0, 0},
+       {"verbose", 0, 0, 0},
+       {"create", 0, 0, 0},
+       {"file", 1, 0, 0},
+       {0, 0, 0, 0}
+      };
+
+      c = getopt_long (argc, argv, "abc:d:0123456789",
+                      long_options, &option_index);
+      if (c == EOF)
+       break;
+
+      switch (c)
+       {
+       case 0:
+         printf ("option %s", long_options[option_index].name);
+         if (optarg)
+           printf (" with arg %s", optarg);
+         printf ("\n");
+         break;
+
+       case '0':
+       case '1':
+       case '2':
+       case '3':
+       case '4':
+       case '5':
+       case '6':
+       case '7':
+       case '8':
+       case '9':
+         if (digit_optind != 0 && digit_optind != this_option_optind)
+           printf ("digits occur in two different argv-elements.\n");
+         digit_optind = this_option_optind;
+         printf ("option %c\n", c);
+         break;
+
+       case 'a':
+         printf ("option a\n");
+         break;
+
+       case 'b':
+         printf ("option b\n");
+         break;
+
+       case 'c':
+         printf ("option c with value `%s'\n", optarg);
+         break;
+
+       case 'd':
+         printf ("option d with value `%s'\n", optarg);
+         break;
+
+       case '?':
+         break;
+
+       default:
+         printf ("?? getopt returned character code 0%o ??\n", c);
+       }
+    }
+
+  if (optind < argc)
+    {
+      printf ("non-option ARGV-elements: ");
+      while (optind < argc)
+       printf ("%s ", argv[optind++]);
+      printf ("\n");
+    }
+
+  exit (0);
+}
+
+#endif /* TEST */
diff --git a/lib/misc/memchr.c b/lib/misc/memchr.c
new file mode 100644 (file)
index 0000000..4448248
--- /dev/null
@@ -0,0 +1,199 @@
+/* Copyright (C) 1991, 1993 Free Software Foundation, Inc.
+   Based on strlen implemention by Torbjorn Granlund (tege@sics.se),
+   with help from Dan Sahlin (dan@sics.se) and
+   commentary by Jim Blandy (jimb@ai.mit.edu);
+   adaptation to memchr suggested by Dick Karpinski (dick@cca.ucsf.edu),
+   and implemented by Roland McGrath (roland@ai.mit.edu).
+
+NOTE: The canonical source of this file is maintained with the GNU C Library.
+Bugs can be reported to bug-glibc@prep.ai.mit.edu.
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2, or (at your option) any
+later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+USA.  */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#undef __ptr_t
+#if defined (__cplusplus) || (defined (__STDC__) && __STDC__)
+# define __ptr_t void *
+#else /* Not C++ or ANSI C.  */
+# define __ptr_t char *
+#endif /* C++ or ANSI C.  */
+
+#if defined (_LIBC)
+# include <string.h>
+#endif
+
+#if defined (HAVE_LIMITS_H) || defined (_LIBC)
+# include <limits.h>
+#endif
+
+#define LONG_MAX_32_BITS 2147483647
+
+#ifndef LONG_MAX
+#define LONG_MAX LONG_MAX_32_BITS
+#endif
+
+#include <sys/types.h>
+
+
+/* Search no more than N bytes of S for C.  */
+
+__ptr_t
+memchr (s, c, n)
+     const __ptr_t s;
+     int c;
+     size_t n;
+{
+  const unsigned char *char_ptr;
+  const unsigned long int *longword_ptr;
+  unsigned long int longword, magic_bits, charmask;
+
+  c = (unsigned char) c;
+
+  /* Handle the first few characters by reading one character at a time.
+     Do this until CHAR_PTR is aligned on a longword boundary.  */
+  for (char_ptr = (const unsigned char *) s;
+       n > 0 && ((unsigned long int) char_ptr
+                & (sizeof (longword) - 1)) != 0;
+       --n, ++char_ptr)
+    if (*char_ptr == c)
+      return (__ptr_t) char_ptr;
+
+  /* All these elucidatory comments refer to 4-byte longwords,
+     but the theory applies equally well to 8-byte longwords.  */
+
+  longword_ptr = (unsigned long int *) char_ptr;
+
+  /* Bits 31, 24, 16, and 8 of this number are zero.  Call these bits
+     the "holes."  Note that there is a hole just to the left of
+     each byte, with an extra at the end:
+
+     bits:  01111110 11111110 11111110 11111111
+     bytes: AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD
+
+     The 1-bits make sure that carries propagate to the next 0-bit.
+     The 0-bits provide holes for carries to fall into.  */
+
+  if (sizeof (longword) != 4 && sizeof (longword) != 8)
+    abort ();
+
+#if LONG_MAX <= LONG_MAX_32_BITS
+  magic_bits = 0x7efefeff;
+#else
+  magic_bits = ((unsigned long int) 0x7efefefe << 32) | 0xfefefeff;
+#endif
+
+  /* Set up a longword, each of whose bytes is C.  */
+  charmask = c | (c << 8);
+  charmask |= charmask << 16;
+#if LONG_MAX > LONG_MAX_32_BITS
+  charmask |= charmask << 32;
+#endif
+
+  /* Instead of the traditional loop which tests each character,
+     we will test a longword at a time.  The tricky part is testing
+     if *any of the four* bytes in the longword in question are zero.  */
+  while (n >= sizeof (longword))
+    {
+      /* We tentatively exit the loop if adding MAGIC_BITS to
+        LONGWORD fails to change any of the hole bits of LONGWORD.
+
+        1) Is this safe?  Will it catch all the zero bytes?
+        Suppose there is a byte with all zeros.  Any carry bits
+        propagating from its left will fall into the hole at its
+        least significant bit and stop.  Since there will be no
+        carry from its most significant bit, the LSB of the
+        byte to the left will be unchanged, and the zero will be
+        detected.
+
+        2) Is this worthwhile?  Will it ignore everything except
+        zero bytes?  Suppose every byte of LONGWORD has a bit set
+        somewhere.  There will be a carry into bit 8.  If bit 8
+        is set, this will carry into bit 16.  If bit 8 is clear,
+        one of bits 9-15 must be set, so there will be a carry
+        into bit 16.  Similarly, there will be a carry into bit
+        24.  If one of bits 24-30 is set, there will be a carry
+        into bit 31, so all of the hole bits will be changed.
+
+        The one misfire occurs when bits 24-30 are clear and bit
+        31 is set; in this case, the hole at bit 31 is not
+        changed.  If we had access to the processor carry flag,
+        we could close this loophole by putting the fourth hole
+        at bit 32!
+
+        So it ignores everything except 128's, when they're aligned
+        properly.
+
+        3) But wait!  Aren't we looking for C, not zero?
+        Good point.  So what we do is XOR LONGWORD with a longword,
+        each of whose bytes is C.  This turns each byte that is C
+        into a zero.  */
+
+      longword = *longword_ptr++ ^ charmask;
+
+      /* Add MAGIC_BITS to LONGWORD.  */
+      if ((((longword + magic_bits)
+
+           /* Set those bits that were unchanged by the addition.  */
+           ^ ~longword)
+
+          /* Look at only the hole bits.  If any of the hole bits
+             are unchanged, most likely one of the bytes was a
+             zero.  */
+          & ~magic_bits) != 0)
+       {
+         /* Which of the bytes was C?  If none of them were, it was
+            a misfire; continue the search.  */
+
+         const unsigned char *cp = (const unsigned char *) (longword_ptr - 1);
+
+         if (cp[0] == c)
+           return (__ptr_t) cp;
+         if (cp[1] == c)
+           return (__ptr_t) &cp[1];
+         if (cp[2] == c)
+           return (__ptr_t) &cp[2];
+         if (cp[3] == c)
+           return (__ptr_t) &cp[3];
+#if LONG_MAX > 2147483647
+         if (cp[4] == c)
+           return (__ptr_t) &cp[4];
+         if (cp[5] == c)
+           return (__ptr_t) &cp[5];
+         if (cp[6] == c)
+           return (__ptr_t) &cp[6];
+         if (cp[7] == c)
+           return (__ptr_t) &cp[7];
+#endif
+       }
+
+      n -= sizeof (longword);
+    }
+
+  char_ptr = (const unsigned char *) longword_ptr;
+
+  while (n-- > 0)
+    {
+      if (*char_ptr == c)
+       return (__ptr_t) char_ptr;
+      else
+       ++char_ptr;
+    }
+
+  return 0;
+}
diff --git a/lib/misc/memcmp.c b/lib/misc/memcmp.c
new file mode 100644 (file)
index 0000000..ae4644e
--- /dev/null
@@ -0,0 +1,364 @@
+/* Copyright (C) 1991, 1993 Free Software Foundation, Inc.
+   Contributed by Torbjorn Granlund (tege@sics.se).
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If
+   not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+   Cambridge, MA 02139, USA.  */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#undef __ptr_t
+#if defined (__cplusplus) || (defined (__STDC__) && __STDC__)
+#define        __ptr_t void *
+#else /* Not C++ or ANSI C.  */
+#undef const
+#define        const
+#define        __ptr_t char *
+#endif /* C++ or ANSI C.  */
+
+#if defined (HAVE_STRING_H) || defined (_LIBC)
+#include <string.h>
+#endif
+
+#ifdef _LIBC
+
+#include <memcopy.h>
+
+#else /* Not in the GNU C library.  */
+
+#include <sys/types.h>
+
+/* Type to use for aligned memory operations.
+   This should normally be the biggest type supported by a single load
+   and store.  Must be an unsigned type.  */
+#define        op_t    unsigned long int
+#define OPSIZ  (sizeof(op_t))
+
+/* Threshold value for when to enter the unrolled loops.  */
+#define        OP_T_THRES      16
+
+/* Type to use for unaligned operations.  */
+typedef unsigned char byte;
+
+#ifndef WORDS_BIGENDIAN
+#define MERGE(w0, sh_1, w1, sh_2) (((w0) >> (sh_1)) | ((w1) << (sh_2)))
+#else
+#define MERGE(w0, sh_1, w1, sh_2) (((w0) << (sh_1)) | ((w1) >> (sh_2)))
+#endif
+
+#endif /* In the GNU C library.  */
+
+#ifdef WORDS_BIGENDIAN
+#define CMP_LT_OR_GT(a, b) ((a) > (b) ? 1 : -1)
+#else
+#define CMP_LT_OR_GT(a, b) memcmp_bytes ((a), (b))
+#endif
+
+/* BE VERY CAREFUL IF YOU CHANGE THIS CODE!  */
+
+/* The strategy of this memcmp is:
+
+   1. Compare bytes until one of the block pointers is aligned.
+
+   2. Compare using memcmp_common_alignment or
+   memcmp_not_common_alignment, regarding the alignment of the other
+   block after the initial byte operations.  The maximum number of
+   full words (of type op_t) are compared in this way.
+
+   3. Compare the few remaining bytes.  */
+
+#ifndef WORDS_BIGENDIAN
+/* memcmp_bytes -- Compare A and B bytewise in the byte order of the machine.
+   A and B are known to be different.
+   This is needed only on little-endian machines.  */
+#ifdef  __GNUC__
+__inline
+#endif
+static int
+memcmp_bytes (a, b)
+     op_t a, b;
+{
+  long int srcp1 = (long int) &a;
+  long int srcp2 = (long int) &b;
+  op_t a0, b0;
+
+  do
+    {
+      a0 = ((byte *) srcp1)[0];
+      b0 = ((byte *) srcp2)[0];
+      srcp1 += 1;
+      srcp2 += 1;
+    }
+  while (a0 == b0);
+  return a0 - b0;
+}
+#endif
+
+/* memcmp_common_alignment -- Compare blocks at SRCP1 and SRCP2 with LEN `op_t'
+   objects (not LEN bytes!).  Both SRCP1 and SRCP2 should be aligned for
+   memory operations on `op_t's.  */
+#ifdef __GNUC__
+__inline
+#endif
+static int
+memcmp_common_alignment (srcp1, srcp2, len)
+     long int srcp1;
+     long int srcp2;
+     size_t len;
+{
+  op_t a0, a1;
+  op_t b0, b1;
+
+  switch (len % 4)
+    {
+    case 2:
+      a0 = ((op_t *) srcp1)[0];
+      b0 = ((op_t *) srcp2)[0];
+      srcp1 -= 2 * OPSIZ;
+      srcp2 -= 2 * OPSIZ;
+      len += 2;
+      goto do1;
+    case 3:
+      a1 = ((op_t *) srcp1)[0];
+      b1 = ((op_t *) srcp2)[0];
+      srcp1 -= OPSIZ;
+      srcp2 -= OPSIZ;
+      len += 1;
+      goto do2;
+    case 0:
+      if (OP_T_THRES <= 3 * OPSIZ && len == 0)
+       return 0;
+      a0 = ((op_t *) srcp1)[0];
+      b0 = ((op_t *) srcp2)[0];
+      goto do3;
+    case 1:
+      a1 = ((op_t *) srcp1)[0];
+      b1 = ((op_t *) srcp2)[0];
+      srcp1 += OPSIZ;
+      srcp2 += OPSIZ;
+      len -= 1;
+      if (OP_T_THRES <= 3 * OPSIZ && len == 0)
+       goto do0;
+      /* Fall through.  */
+    }
+
+  do
+    {
+      a0 = ((op_t *) srcp1)[0];
+      b0 = ((op_t *) srcp2)[0];
+      if (a1 != b1)
+       return CMP_LT_OR_GT (a1, b1);
+
+    do3:
+      a1 = ((op_t *) srcp1)[1];
+      b1 = ((op_t *) srcp2)[1];
+      if (a0 != b0)
+       return CMP_LT_OR_GT (a0, b0);
+
+    do2:
+      a0 = ((op_t *) srcp1)[2];
+      b0 = ((op_t *) srcp2)[2];
+      if (a1 != b1)
+       return CMP_LT_OR_GT (a1, b1);
+
+    do1:
+      a1 = ((op_t *) srcp1)[3];
+      b1 = ((op_t *) srcp2)[3];
+      if (a0 != b0)
+       return CMP_LT_OR_GT (a0, b0);
+
+      srcp1 += 4 * OPSIZ;
+      srcp2 += 4 * OPSIZ;
+      len -= 4;
+    }
+  while (len != 0);
+
+  /* This is the right position for do0.  Please don't move
+     it into the loop.  */
+do0:
+  if (a1 != b1)
+    return CMP_LT_OR_GT (a1, b1);
+  return 0;
+}
+
+/* memcmp_not_common_alignment -- Compare blocks at SRCP1 and SRCP2 with LEN
+   `op_t' objects (not LEN bytes!).  SRCP2 should be aligned for memory
+   operations on `op_t', but SRCP1 *should be unaligned*.  */
+#ifdef __GNUC__
+__inline
+#endif
+static int
+memcmp_not_common_alignment (srcp1, srcp2, len)
+     long int srcp1;
+     long int srcp2;
+     size_t len;
+{
+  op_t a0, a1, a2, a3;
+  op_t b0, b1, b2, b3;
+  op_t x;
+  int shl, shr;
+
+  /* Calculate how to shift a word read at the memory operation
+     aligned srcp1 to make it aligned for comparison.  */
+
+  shl = 8 * (srcp1 % OPSIZ);
+  shr = 8 * OPSIZ - shl;
+
+  /* Make SRCP1 aligned by rounding it down to the beginning of the `op_t'
+     it points in the middle of.  */
+  srcp1 &= -OPSIZ;
+
+  switch (len % 4)
+    {
+    case 2:
+      a1 = ((op_t *) srcp1)[0];
+      a2 = ((op_t *) srcp1)[1];
+      b2 = ((op_t *) srcp2)[0];
+      srcp1 -= 1 * OPSIZ;
+      srcp2 -= 2 * OPSIZ;
+      len += 2;
+      goto do1;
+    case 3:
+      a0 = ((op_t *) srcp1)[0];
+      a1 = ((op_t *) srcp1)[1];
+      b1 = ((op_t *) srcp2)[0];
+      srcp2 -= 1 * OPSIZ;
+      len += 1;
+      goto do2;
+    case 0:
+      if (OP_T_THRES <= 3 * OPSIZ && len == 0)
+       return 0;
+      a3 = ((op_t *) srcp1)[0];
+      a0 = ((op_t *) srcp1)[1];
+      b0 = ((op_t *) srcp2)[0];
+      srcp1 += 1 * OPSIZ;
+      goto do3;
+    case 1:
+      a2 = ((op_t *) srcp1)[0];
+      a3 = ((op_t *) srcp1)[1];
+      b3 = ((op_t *) srcp2)[0];
+      srcp1 += 2 * OPSIZ;
+      srcp2 += 1 * OPSIZ;
+      len -= 1;
+      if (OP_T_THRES <= 3 * OPSIZ && len == 0)
+       goto do0;
+      /* Fall through.  */
+    }
+
+  do
+    {
+      a0 = ((op_t *) srcp1)[0];
+      b0 = ((op_t *) srcp2)[0];
+      x = MERGE (a2, shl, a3, shr);
+      if (x != b3)
+       return CMP_LT_OR_GT (x, b3);
+
+    do3:
+      a1 = ((op_t *) srcp1)[1];
+      b1 = ((op_t *) srcp2)[1];
+      x = MERGE (a3, shl, a0, shr);
+      if (x != b0)
+       return CMP_LT_OR_GT (x, b0);
+
+    do2:
+      a2 = ((op_t *) srcp1)[2];
+      b2 = ((op_t *) srcp2)[2];
+      x = MERGE (a0, shl, a1, shr);
+      if (x != b1)
+       return CMP_LT_OR_GT (x, b1);
+
+    do1:
+      a3 = ((op_t *) srcp1)[3];
+      b3 = ((op_t *) srcp2)[3];
+      x = MERGE (a1, shl, a2, shr);
+      if (x != b2)
+       return CMP_LT_OR_GT (x, b2);
+
+      srcp1 += 4 * OPSIZ;
+      srcp2 += 4 * OPSIZ;
+      len -= 4;
+    }
+  while (len != 0);
+
+  /* This is the right position for do0.  Please don't move
+     it into the loop.  */
+do0:
+  x = MERGE (a2, shl, a3, shr);
+  if (x != b3)
+    return CMP_LT_OR_GT (x, b3);
+  return 0;
+}
+
+int
+memcmp (s1, s2, len)
+     const __ptr_t s1;
+     const __ptr_t s2;
+     size_t len;
+{
+  op_t a0;
+  op_t b0;
+  long int srcp1 = (long int) s1;
+  long int srcp2 = (long int) s2;
+  op_t res;
+
+  if (len >= OP_T_THRES)
+    {
+      /* There are at least some bytes to compare.  No need to test
+         for LEN == 0 in this alignment loop.  */
+      while (srcp2 % OPSIZ != 0)
+       {
+         a0 = ((byte *) srcp1)[0];
+         b0 = ((byte *) srcp2)[0];
+         srcp1 += 1;
+         srcp2 += 1;
+         res = a0 - b0;
+         if (res != 0)
+           return res;
+         len -= 1;
+       }
+
+      /* SRCP2 is now aligned for memory operations on `op_t'.
+         SRCP1 alignment determines if we can do a simple,
+         aligned compare or need to shuffle bits.  */
+
+      if (srcp1 % OPSIZ == 0)
+       res = memcmp_common_alignment (srcp1, srcp2, len / OPSIZ);
+      else
+       res = memcmp_not_common_alignment (srcp1, srcp2, len / OPSIZ);
+      if (res != 0)
+       return res;
+
+      /* Number of bytes remaining in the interval [0..OPSIZ-1].  */
+      srcp1 += len & -OPSIZ;
+      srcp2 += len & -OPSIZ;
+      len %= OPSIZ;
+    }
+
+  /* There are just a few bytes to compare.  Use byte memory operations.  */
+  while (len != 0)
+    {
+      a0 = ((byte *) srcp1)[0];
+      b0 = ((byte *) srcp2)[0];
+      srcp1 += 1;
+      srcp2 += 1;
+      res = a0 - b0;
+      if (res != 0)
+       return res;
+      len -= 1;
+    }
+
+  return 0;
+}
diff --git a/lib/misc/memcpy.c b/lib/misc/memcpy.c
new file mode 100644 (file)
index 0000000..d36cef2
--- /dev/null
@@ -0,0 +1,20 @@
+/* Copy LEN bytes starting at SRCADDR to DESTADDR.  Result undefined
+   if the source overlaps with the destination.
+   Return DESTADDR. */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+char *
+memcpy (destaddr, srcaddr, len)
+     char *destaddr;
+     const char *srcaddr;
+     int len;
+{
+  char *dest = destaddr;
+
+  while (len-- > 0)
+    *destaddr++ = *srcaddr++;
+  return dest;
+}
diff --git a/lib/misc/memmem.c b/lib/misc/memmem.c
new file mode 100644 (file)
index 0000000..1ffbc29
--- /dev/null
@@ -0,0 +1,42 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997, 1998 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <stddef.h>
+
+int memcmp ();
+
+/* Finds the first NEEDLE of length NEEDLE_LEN in a HAYSTACK of length
+   HAYSTACK_LEN.  Returns a pointer to the match or NULL on
+   failure. */
+void *
+memmem (const void *haystack, size_t haystack_len,
+       const void *needle, size_t needle_len)
+{
+  size_t i;
+  
+  if (needle_len > haystack_len)
+    return NULL;
+  
+  for (i = 0; i <= haystack_len - needle_len; i++)
+    if (!memcmp (needle, &((const char *) haystack)[i], needle_len))
+      return (void *) (&((const char *) haystack)[i]);
+  
+  return NULL;
+}
+
diff --git a/lib/misc/memmove.c b/lib/misc/memmove.c
new file mode 100644 (file)
index 0000000..d83cad3
--- /dev/null
@@ -0,0 +1,36 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997, 1998 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* Written by Tristan Gingold <gingold@email.enst.fr>. */
+
+void
+memmove (const char *src, char *dest, int len)
+{
+  if (dest < src)
+    while (len--)
+      *dest++ = *src++;
+  else
+    {
+      char *lasts = (char *)src + (len-1);
+      char *lastd = dest + (len-1);
+      while (len--)
+        *(char *)lastd-- = *(char *)lasts--;
+    }
+}
+
diff --git a/lib/misc/memset.c b/lib/misc/memset.c
new file mode 100644 (file)
index 0000000..a0db560
--- /dev/null
@@ -0,0 +1,29 @@
+/* memset.c -- set an area of memory to a given value
+   Copyright (C) 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+char *
+memset (str, c, len)
+     char *str;
+     int c;
+     unsigned len;
+{
+  register char *st = str;
+
+  while (len-- > 0)
+    *st++ = c;
+  return str;
+}
diff --git a/lib/misc/qsort.c b/lib/misc/qsort.c
new file mode 100644 (file)
index 0000000..23f47c5
--- /dev/null
@@ -0,0 +1,257 @@
+/* Copyright (C) 1991, 1992 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Written by Douglas C. Schmidt (schmidt@ics.uci.edu).
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If
+   not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+   Cambridge, MA 02139, USA.  */
+
+/* Modified 12/15/96 by Ben Pfaff for PSPP. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include "alloc.h"
+
+/* Byte-wise swap two items of size SIZE. */
+#define SWAP(a, b, size)                                                     \
+  do                                                                         \
+    {                                                                        \
+      register size_t __size = (size);                                       \
+      register char *__a = (a), *__b = (b);                                  \
+      do                                                                     \
+       {                                                                     \
+         char __tmp = *__a;                                                  \
+         *__a++ = *__b;                                                      \
+         *__b++ = __tmp;                                                     \
+       } while (--__size > 0);                                               \
+    } while (0)
+
+/* Discontinue quicksort algorithm when partition gets below this size.
+   This particular magic number was chosen to work best on a Sun 4/260. */
+#define MAX_THRESH 4
+
+/* Stack node declarations used to store unfulfilled partition obligations. */
+typedef struct
+  {
+    char *lo;
+    char *hi;
+  }
+stack_node;
+
+/* The next 4 #defines implement a very fast in-line stack abstraction. */
+#define STACK_SIZE     (8 * sizeof(unsigned long int))
+#define PUSH(low, high)        ((void) ((top->lo = (low)), (top->hi = (high)), ++top))
+#define        POP(low, high)  ((void) (--top, (low = top->lo), (high = top->hi)))
+#define        STACK_NOT_EMPTY (stack < top)
+
+
+/* Order size using quicksort.  This implementation incorporates
+   four optimizations discussed in Sedgewick:
+
+   1. Non-recursive, using an explicit stack of pointer that store the 
+   next array partition to sort.  To save time, this maximum amount 
+   of space required to store an array of MAX_INT is allocated on the 
+   stack.  Assuming a 32-bit integer, this needs only 32 * 
+   sizeof(stack_node) == 136 bits.  Pretty cheap, actually.
+
+   2. Chose the pivot element using a median-of-three decision tree.
+   This reduces the probability of selecting a bad pivot value and 
+   eliminates certain extraneous comparisons.
+
+   3. Only quicksorts TOTAL_ELEMS / MAX_THRESH partitions, leaving
+   insertion sort to order the MAX_THRESH items within each partition.  
+   This is a big win, since insertion sort is faster for small, mostly
+   sorted array segements.
+
+   4. The larger of the two sub-partitions is always pushed onto the
+   stack first, with the algorithm then concentrating on the
+   smaller partition.  This *guarantees* no more than log (n)
+   stack size is needed (actually O(1) in this case)!  */
+
+void
+blp_quicksort (void *pbase, size_t total_elems, size_t size,
+              int (*cmp) (const void *, const void *),
+              void *temp_buf
+#if HAVE_ALLOCA
+              unused
+#endif
+              )
+{
+  register char *base_ptr = (char *) pbase;
+
+  /* Allocating SIZE bytes for a pivot buffer facilitates a better
+     algorithm below since we can do comparisons directly on the pivot. */
+#if HAVE_ALLOCA
+  char *pivot_buffer = (char *) local_alloc (size);
+#else
+  char *pivot_buffer = temp_buf;
+#endif
+  const size_t max_thresh = MAX_THRESH * size;
+
+  if (total_elems == 0)
+    {
+      /* Avoid lossage with unsigned arithmetic below.  */
+      local_free (pivot_buffer);
+      return;
+    }
+
+  if (total_elems > MAX_THRESH)
+    {
+      char *lo = base_ptr;
+      char *hi = &lo[size * (total_elems - 1)];
+      /* Largest size needed for 32-bit int!!! */
+      stack_node stack[STACK_SIZE];
+      stack_node *top = stack + 1;
+
+      while (STACK_NOT_EMPTY)
+       {
+         char *left_ptr;
+         char *right_ptr;
+
+         char *pivot = pivot_buffer;
+
+         /* Select median value from among LO, MID, and HI. Rearrange
+            LO and HI so the three values are sorted. This lowers the 
+            probability of picking a pathological pivot value and 
+            skips a comparison for both the LEFT_PTR and RIGHT_PTR. */
+
+         char *mid = lo + size * ((hi - lo) / size >> 1);
+
+         if ((*cmp) ((void *) mid, (void *) lo) < 0)
+           SWAP (mid, lo, size);
+         if ((*cmp) ((void *) hi, (void *) mid) < 0)
+           SWAP (mid, hi, size);
+         else
+           goto jump_over;
+         if ((*cmp) ((void *) mid, (void *) lo) < 0)
+           SWAP (mid, lo, size);
+       jump_over:;
+         memcpy (pivot, mid, size);
+         pivot = pivot_buffer;
+
+         left_ptr = lo + size;
+         right_ptr = hi - size;
+
+         /* Here's the famous ``collapse the walls'' section of quicksort.  
+            Gotta like those tight inner loops!  They are the main reason 
+            that this algorithm runs much faster than others. */
+         do
+           {
+             while ((*cmp) ((void *) left_ptr, (void *) pivot) < 0)
+               left_ptr += size;
+
+             while ((*cmp) ((void *) pivot, (void *) right_ptr) < 0)
+               right_ptr -= size;
+
+             if (left_ptr < right_ptr)
+               {
+                 SWAP (left_ptr, right_ptr, size);
+                 left_ptr += size;
+                 right_ptr -= size;
+               }
+             else if (left_ptr == right_ptr)
+               {
+                 left_ptr += size;
+                 right_ptr -= size;
+                 break;
+               }
+           }
+         while (left_ptr <= right_ptr);
+
+         /* Set up pointers for next iteration.  First determine whether
+            left and right partitions are below the threshold size.  If so, 
+            ignore one or both.  Otherwise, push the larger partition's
+            bounds on the stack and continue sorting the smaller one. */
+
+         if ((size_t) (right_ptr - lo) <= max_thresh)
+           {
+             if ((size_t) (hi - left_ptr) <= max_thresh)
+               /* Ignore both small partitions. */
+               POP (lo, hi);
+             else
+               /* Ignore small left partition. */
+               lo = left_ptr;
+           }
+         else if ((size_t) (hi - left_ptr) <= max_thresh)
+           /* Ignore small right partition. */
+           hi = right_ptr;
+         else if ((right_ptr - lo) > (hi - left_ptr))
+           {
+             /* Push larger left partition indices. */
+             PUSH (lo, right_ptr);
+             lo = left_ptr;
+           }
+         else
+           {
+             /* Push larger right partition indices. */
+             PUSH (left_ptr, hi);
+             hi = right_ptr;
+           }
+       }
+    }
+
+  /* Once the BASE_PTR array is partially sorted by quicksort the rest
+     is completely sorted using insertion sort, since this is efficient 
+     for partitions below MAX_THRESH size. BASE_PTR points to the beginning 
+     of the array to sort, and END_PTR points at the very last element in
+     the array (*not* one beyond it!). */
+
+#define min(x, y) ((x) < (y) ? (x) : (y))
+
+  {
+    char *const end_ptr = &base_ptr[size * (total_elems - 1)];
+    char *tmp_ptr = base_ptr;
+    char *thresh = min (end_ptr, base_ptr + max_thresh);
+    register char *run_ptr;
+
+    /* Find smallest element in first threshold and place it at the
+       array's beginning.  This is the smallest array element,
+       and the operation speeds up insertion sort's inner loop. */
+
+    for (run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size)
+      if ((*cmp) ((void *) run_ptr, (void *) tmp_ptr) < 0)
+       tmp_ptr = run_ptr;
+
+    if (tmp_ptr != base_ptr)
+      SWAP (tmp_ptr, base_ptr, size);
+
+    /* Insertion sort, running from left-hand-side up to right-hand-side.  */
+
+    run_ptr = base_ptr + size;
+    while ((run_ptr += size) <= end_ptr)
+      {
+       tmp_ptr = run_ptr - size;
+       while ((*cmp) ((void *) run_ptr, (void *) tmp_ptr) < 0)
+         tmp_ptr -= size;
+
+       tmp_ptr += size;
+       if (tmp_ptr != run_ptr)
+         {
+           char *trav;
+
+           trav = run_ptr + size;
+           while (--trav >= run_ptr)
+             {
+               char c = *trav;
+               char *hi, *lo;
+
+               for (hi = lo = trav; (lo -= size) >= tmp_ptr; hi = lo)
+                 *hi = *lo;
+               *hi = c;
+             }
+         }
+      }
+  }
+}
diff --git a/lib/misc/stpcpy.c b/lib/misc/stpcpy.c
new file mode 100644 (file)
index 0000000..b9df297
--- /dev/null
@@ -0,0 +1,47 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997, 1998 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <stddef.h>
+
+/* Some old versions of Linux libc prototype stpcpy() in string.h but
+   fail to include it in their C library.  By not including string.h
+   on these systems we can avoid conflicting prototypes.  Of course,
+   in theory this might be dangerous, if the prototype specifies some
+   weird calling convention, but for GNU/Linux at least it shouldn't
+   cause problems.
+
+   This might be needed for systems other than GNU/Linux; let me
+   know. */
+
+#ifdef __linux__
+void *memcpy (void *, const void *, size_t);
+size_t strlen (const char *);
+#else
+#include "str.h"
+#endif
+
+/* Copies SRC to DEST, returning the address of the terminating '\0'
+   in DEST. */
+char *
+stpcpy (char *dest, const char *src)
+{
+  int len = strlen (src);
+  memcpy (dest, src, len + 1);
+  return &dest[len];
+}
diff --git a/lib/misc/strcasecmp.c b/lib/misc/strcasecmp.c
new file mode 100644 (file)
index 0000000..c71940b
--- /dev/null
@@ -0,0 +1,33 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997, 1998 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <ctype.h>
+
+int
+strcasecmp (const char *s1, const char *s2)
+{
+  for (;;) 
+    {
+      if (!*s1 || !*s2
+         || tolower ((unsigned char) *s1) != tolower ((unsigned char) *s2))
+       return (unsigned char) *s1 - (unsigned char) *s2;
+      s1++;
+      s2++;
+    }
+}
diff --git a/lib/misc/strerror.c b/lib/misc/strerror.c
new file mode 100644 (file)
index 0000000..f2ed4d7
--- /dev/null
@@ -0,0 +1,48 @@
+/* A replacement version of strerror
+
+   Copyright (C) 1996 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2, or (at
+   your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+
+#if defined (HAVE_SYS_ERRLIST) && !defined (HAVE_SYS_ERRLIST_DECL)
+extern int sys_nerr;
+extern char *sys_errlist[];
+#endif
+
+/* Return a string describing the system error code ERR.  The returned value
+   may be in a static buffer (and in any case shouldn't be written to).  */
+const char *
+strerror (int err)
+{
+#ifdef HAVE_SYS_ERRLIST
+  if (err >= 0 && err < sys_nerr && sys_errlist[err])
+    return sys_errlist[err];
+  else
+#endif
+    {
+      static char buf[100];
+      sprintf (buf, "Error %d", err);
+      return buf;
+    }
+}
diff --git a/lib/misc/strncasecmp.c b/lib/misc/strncasecmp.c
new file mode 100644 (file)
index 0000000..ad14c2f
--- /dev/null
@@ -0,0 +1,37 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997, 1998 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <ctype.h>
+
+int
+strncasecmp (const char *s1, const char *s2, size_t n)
+{
+  size_t index;
+
+  for (index = 0; index < n; index++)
+    {
+      if (tolower ((unsigned char) s1[index])
+          != tolower ((unsigned char) s2[index]))
+       return (((unsigned const char *)s1)[index] 
+               - ((unsigned const char *)s2)[index]);
+      if (s1[index] == 0)
+       return 0;
+    }
+  return 0;
+}
diff --git a/lib/misc/strpbrk.c b/lib/misc/strpbrk.c
new file mode 100644 (file)
index 0000000..75b2ed1
--- /dev/null
@@ -0,0 +1,38 @@
+/* Copyright (C) 1991, 1994, 1996 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#include <string.h>
+
+
+/* Find the first occurrence in S of any character in ACCEPT.  */
+char *
+strpbrk (s, accept)
+     const char *s;
+     const char *accept;
+{
+  while (*s != '\0')
+    {
+      const char *a = accept;
+      while (*a != '\0')
+       if (*a++ == *s)
+         return (char *) s;
+      ++s;
+    }
+
+  return NULL;
+}
diff --git a/lib/misc/strstr.c b/lib/misc/strstr.c
new file mode 100644 (file)
index 0000000..990cae5
--- /dev/null
@@ -0,0 +1,24 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997, 1998 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+char *
+strstr (const char *haystack, const char *needle)
+{
+  return memmem (haystack, strlen (haystack), needle, strlen (needle));
+}
diff --git a/lib/misc/strtok_r.c b/lib/misc/strtok_r.c
new file mode 100644 (file)
index 0000000..fb68ad8
--- /dev/null
@@ -0,0 +1,62 @@
+/* Reentrant string tokenizer.  Generic version.
+Copyright (C) 1991, 1996 Free Software Foundation, Inc.
+This file is part of the GNU C Library.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the GNU C Library; see the file COPYING.LIB.  If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.  */
+
+#include <string.h>
+
+
+/* Parse S into tokens separated by characters in DELIM.
+   If S is NULL, the saved pointer in SAVE_PTR is used as
+   the next starting point.  For example:
+       char s[] = "-abc-=-def";
+       char *sp;
+       x = strtok_r(s, "-", &sp);      // x = "abc", sp = "=-def"
+       x = strtok_r(NULL, "-=", &sp);  // x = "def", sp = NULL
+       x = strtok_r(NULL, "=", &sp);   // x = NULL
+               // s = "abc\0-def\0"
+*/
+char *
+strtok_r (s, delim, save_ptr)
+     char *s;
+     const char *delim;
+     char **save_ptr;
+{
+  char *token;
+
+  if (s == NULL)
+    s = *save_ptr;
+
+  /* Scan leading delimiters.  */
+  s += strspn (s, delim);
+  if (*s == '\0')
+    return NULL;
+
+  /* Find the end of the token.  */
+  token = s;
+  s = strpbrk (token, delim);
+  if (s == NULL)
+    /* This token finishes the string.  */
+    *save_ptr = strchr (token, '\0');
+  else
+    {
+      /* Terminate the token and make *SAVE_PTR point past it.  */
+      *s = '\0';
+      *save_ptr = s + 1;
+    }
+  return token;
+}
diff --git a/lib/misc/strtol.c b/lib/misc/strtol.c
new file mode 100644 (file)
index 0000000..025287a
--- /dev/null
@@ -0,0 +1,368 @@
+/* strtol - Convert string representation of a number into an integer value.
+   Copyright (C) 1991, 92, 94, 95, 96 Free Software Foundation, Inc.
+   NOTE: The canonical source of this file is maintained with the GNU C
+   Library.  Bugs can be reported to bug-glibc@prep.ai.mit.edu.
+
+   This program is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by the
+   Free Software Foundation; either version 2, or (at your option) any
+   later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef _LIBC
+# define USE_NUMBER_GROUPING
+# define STDC_HEADERS
+# define HAVE_LIMITS_H
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#ifndef errno
+extern int errno;
+#endif
+#ifndef __set_errno
+# define __set_errno(Val) errno = (Val)
+#endif
+
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif
+
+#ifdef STDC_HEADERS
+# include <stddef.h>
+# include <stdlib.h>
+# include <string.h>
+#else
+# ifndef NULL
+#  define NULL 0
+# endif
+#endif
+
+#ifdef USE_NUMBER_GROUPING
+# include "../locale/localeinfo.h"
+#endif
+
+/* Nonzero if we are defining `strtoul' or `strtouq', operating on
+   unsigned integers.  */
+#ifndef UNSIGNED
+# define UNSIGNED 0
+# define INT LONG int
+#else
+# define INT unsigned LONG int
+#endif
+
+/* Determine the name.  */
+#if UNSIGNED
+# ifdef USE_WIDE_CHAR
+#  ifdef QUAD
+#   define strtol wcstouq
+#  else
+#   define strtol wcstoul
+#  endif
+# else
+#  ifdef QUAD
+#   define strtol strtouq
+#  else
+#   define strtol strtoul
+#  endif
+# endif
+#else
+# ifdef USE_WIDE_CHAR
+#  ifdef QUAD
+#   define strtol wcstoq
+#  else
+#   define strtol wcstol
+#  endif
+# else
+#  ifdef QUAD
+#   define strtol strtoq
+#  endif
+# endif
+#endif
+
+/* If QUAD is defined, we are defining `strtoq' or `strtouq',
+   operating on `long long int's.  */
+#ifdef QUAD
+# define LONG long long
+# undef LONG_MIN
+# define LONG_MIN LONG_LONG_MIN
+# undef LONG_MAX
+# define LONG_MAX LONG_LONG_MAX
+# undef ULONG_MAX
+# define ULONG_MAX ULONG_LONG_MAX
+# if __GNUC__ == 2 && __GNUC_MINOR__ < 7
+   /* Work around gcc bug with using this constant.  */
+   static const unsigned long long int maxquad = ULONG_LONG_MAX;
+#  undef ULONG_MAX
+#  define ULONG_MAX maxquad
+# endif
+#else
+# define LONG long
+
+#ifndef ULONG_MAX
+# define ULONG_MAX ((unsigned long) ~(unsigned long) 0)
+#endif
+#ifndef LONG_MAX
+# define LONG_MAX ((long int) (ULONG_MAX >> 1))
+#endif
+#endif
+
+#ifdef USE_WIDE_CHAR
+# include <wchar.h>
+# include <wctype.h>
+# define L_(Ch) L##Ch
+# define UCHAR_TYPE wint_t
+# define STRING_TYPE wchar_t
+# define ISSPACE(Ch) iswspace (Ch)
+# define ISALPHA(Ch) iswalpha (Ch)
+# define TOUPPER(Ch) towupper (Ch)
+#else
+# define L_(Ch) Ch
+# define UCHAR_TYPE unsigned char
+# define STRING_TYPE char
+# define ISSPACE(Ch) isspace (Ch)
+# define ISALPHA(Ch) isalpha (Ch)
+# define TOUPPER(Ch) toupper (Ch)
+#endif
+
+#ifdef __STDC__
+# define INTERNAL(X) INTERNAL1(X)
+# define INTERNAL1(X) __##X##_internal
+# define WEAKNAME(X) WEAKNAME1(X)
+#else
+# define INTERNAL(X) __/**/X/**/_internal
+#endif
+
+#ifdef USE_NUMBER_GROUPING
+/* This file defines a function to check for correct grouping.  */
+# include "grouping.h"
+#endif
+
+
+/* Convert NPTR to an `unsigned long int' or `long int' in base BASE.
+   If BASE is 0 the base is determined by the presence of a leading
+   zero, indicating octal or a leading "0x" or "0X", indicating hexadecimal.
+   If BASE is < 2 or > 36, it is reset to 10.
+   If ENDPTR is not NULL, a pointer to the character after the last
+   one converted is stored in *ENDPTR.  */
+
+INT
+INTERNAL (strtol) (nptr, endptr, base, group)
+     const STRING_TYPE *nptr;
+     STRING_TYPE **endptr;
+     int base;
+     int group;
+{
+  int negative;
+  register unsigned LONG int cutoff;
+  register unsigned int cutlim;
+  register unsigned LONG int i;
+  register const STRING_TYPE *s;
+  register UCHAR_TYPE c;
+  const STRING_TYPE *save, *end;
+  int overflow;
+
+#ifdef USE_NUMBER_GROUPING
+  /* The thousands character of the current locale.  */
+  wchar_t thousands;
+  /* The numeric grouping specification of the current locale,
+     in the format described in <locale.h>.  */
+  const char *grouping;
+
+  if (group)
+    {
+      grouping = _NL_CURRENT (LC_NUMERIC, GROUPING);
+      if (*grouping <= 0 || *grouping == CHAR_MAX)
+       grouping = NULL;
+      else
+       {
+         /* Figure out the thousands separator character.  */
+         if (mbtowc (&thousands, _NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP),
+                     strlen (_NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP))) <= 0)
+           thousands = (wchar_t) *_NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP);
+         if (thousands == L'\0')
+           grouping = NULL;
+       }
+    }
+  else
+    grouping = NULL;
+#endif
+
+  if (base < 0 || base == 1 || base > 36)
+    base = 10;
+
+  save = s = nptr;
+
+  /* Skip white space.  */
+  while (ISSPACE (*s))
+    ++s;
+  if (*s == L_('\0'))
+    goto noconv;
+
+  /* Check for a sign.  */
+  if (*s == L_('-'))
+    {
+      negative = 1;
+      ++s;
+    }
+  else if (*s == L_('+'))
+    {
+      negative = 0;
+      ++s;
+    }
+  else
+    negative = 0;
+
+  if (base == 16 && s[0] == L_('0') && TOUPPER (s[1]) == L_('X'))
+    s += 2;
+
+  /* If BASE is zero, figure it out ourselves.  */
+  if (base == 0)
+    if (*s == L_('0'))
+      {
+       if (TOUPPER (s[1]) == L_('X'))
+         {
+           s += 2;
+           base = 16;
+         }
+       else
+         base = 8;
+      }
+    else
+      base = 10;
+
+  /* Save the pointer so we can check later if anything happened.  */
+  save = s;
+
+#ifdef USE_NUMBER_GROUPING
+  if (group)
+    {
+      /* Find the end of the digit string and check its grouping.  */
+      end = s;
+      for (c = *end; c != L_('\0'); c = *++end)
+       if ((wchar_t) c != thousands
+           && ((wchar_t) c < L_('0') || (wchar_t) c > L_('9'))
+           && (!ISALPHA (c) || (int) (TOUPPER (c) - L_('A') + 10) >= base))
+         break;
+      if (*s == thousands)
+       end = s;
+      else
+       end = correctly_grouped_prefix (s, end, thousands, grouping);
+    }
+  else
+#endif
+    end = NULL;
+
+  cutoff = ULONG_MAX / (unsigned LONG int) base;
+  cutlim = ULONG_MAX % (unsigned LONG int) base;
+
+  overflow = 0;
+  i = 0;
+  for (c = *s; c != L_('\0'); c = *++s)
+    {
+      if (s == end)
+       break;
+      if (c >= L_('0') && c <= L_('9'))
+       c -= L_('0');
+      else if (ISALPHA (c))
+       c = TOUPPER (c) - L_('A') + 10;
+      else
+       break;
+      if ((int) c >= base)
+       break;
+      /* Check for overflow.  */
+      if (i > cutoff || (i == cutoff && c > cutlim))
+       overflow = 1;
+      else
+       {
+         i *= (unsigned LONG int) base;
+         i += c;
+       }
+    }
+
+  /* Check if anything actually happened.  */
+  if (s == save)
+    goto noconv;
+
+  /* Store in ENDPTR the address of one character
+     past the last character we converted.  */
+  if (endptr != NULL)
+    *endptr = (STRING_TYPE *) s;
+
+#if !UNSIGNED
+  /* Check for a value that is within the range of
+     `unsigned LONG int', but outside the range of `LONG int'.  */
+  if (overflow == 0
+      && i > (negative
+             ? -((unsigned LONG int) (LONG_MIN + 1)) + 1
+             : (unsigned LONG int) LONG_MAX))
+    overflow = 1;
+#endif
+
+  if (overflow)
+    {
+      __set_errno (ERANGE);
+#if UNSIGNED
+      return ULONG_MAX;
+#else
+      return negative ? LONG_MIN : LONG_MAX;
+#endif
+    }
+
+  /* Return the result of the appropriate sign.  */
+  return (negative ? -i : i);
+
+noconv:
+  /* We must handle a special case here: the base is 0 or 16 and the
+     first two characters are '0' and 'x', but the rest are no
+     hexadecimal digits.  This is no error case.  We return 0 and
+     ENDPTR points to the `x`.  */
+  if (endptr != NULL)
+    if (save - nptr >= 2 && TOUPPER (save[-1]) == L_('X')
+       && save[-2] == L_('0'))
+      *endptr = (STRING_TYPE *) &save[-1];
+    else
+      /*  There was no number to convert.  */
+      *endptr = (STRING_TYPE *) nptr;
+
+  return 0L;
+}
+\f
+/* External user entry point.  */
+
+#if _LIBC - 0 == 0
+# undef PARAMS
+# if defined (__STDC__) && __STDC__
+#  define PARAMS(Args) Args
+# else
+#  define PARAMS(Args) ()
+# endif
+
+/* Prototype.  */
+INT strtol PARAMS ((const STRING_TYPE *nptr, STRING_TYPE **endptr, int base));
+#endif
+
+
+INT
+#ifdef weak_function
+weak_function
+#endif
+strtol (nptr, endptr, base)
+     const STRING_TYPE *nptr;
+     STRING_TYPE **endptr;
+     int base;
+{
+  return INTERNAL (strtol) (nptr, endptr, base, 0);
+}
diff --git a/lib/misc/strtoul.c b/lib/misc/strtoul.c
new file mode 100644 (file)
index 0000000..715ba30
--- /dev/null
@@ -0,0 +1,22 @@
+/* Copyright (C) 1991 Free Software Foundation, Inc.
+
+NOTE: The canonical source of this file is maintained with the GNU C Library.
+Bugs can be reported to bug-glibc@prep.ai.mit.edu.
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2, or (at your option) any
+later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#define        UNSIGNED        1
+
+#include <strtol.c>
diff --git a/po/ChangeLog b/po/ChangeLog
new file mode 100644 (file)
index 0000000..b7fcbbb
--- /dev/null
@@ -0,0 +1,53 @@
+Sat Jan  1 23:27:03 2000  Ben Pfaff  <blp@gnu.org>
+
+       * POTFILES.in: Update.
+
+Thu Jan  8 22:27:38 1998  Ben Pfaff  <blp@gnu.org>
+
+       * POTFILES.in: Recreate.
+
+       * Makefile.in.in: Upcase `pspp' within maintainer-clean target.
+
+Tue Dec  2 14:35:47 1997  Ben Pfaff  <blp@gnu.org>
+
+       * POTFILES.in: Add src/aggregate.c; alphabetize.
+
+Wed Oct  8 15:53:13 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.in.in: Updated to gettext-0.10.32 while retaining local
+       fixes.
+
+Tue Oct  7 20:22:25 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.in.in: Maintainer-cleans Makefile.
+
+Thu Jul 17 01:51:23 1997  Ben Pfaff  <blp@gnu.org>
+
+       * POTFILES.in: Remove src/display.c.
+
+Sat Jul  5 23:44:30 1997  Ben Pfaff  <blp@gnu.org>
+
+       * POTFILES.in: Fix file list.
+
+Tue Jun  3 23:29:57 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.in.in: Maintainer-cleans fiasco.pot.
+
+Mon Jun  2 14:22:59 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.in.in: Maintainer-cleans cat-id-tbl.c, stamp-cat-id.
+
+       * POTFILES.in: Added all the files that have internationalized
+       strings; basically this is `grep -l `find . -name \*.[qc]`'.
+       
+Sun Jun  1 23:36:32 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.in.in: New file, taken from gettext-0.10.27.
+
+       * POTFILES.in: New file (empty).
+
+----------------------------------------------------------------------
+Local Variables:
+mode: change-log
+version-control: never
+End:
diff --git a/po/Makefile.in.in b/po/Makefile.in.in
new file mode 100644 (file)
index 0000000..280fae2
--- /dev/null
@@ -0,0 +1,247 @@
+# Makefile for program source directory in GNU NLS utilities package.
+# Copyright (C) 1995, 1996, 1997 by Ulrich Drepper <drepper@gnu.org>
+#
+# This file file be copied and used freely without restrictions.  It can
+# be used in projects which are not available under the GNU Public License
+# but which still want to provide support for the GNU gettext functionality.
+# Please note that the actual code is *not* freely available.
+
+PACKAGE = @PACKAGE@
+VERSION = @VERSION@
+
+SHELL = /bin/sh
+@SET_MAKE@
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datadir = $(prefix)/@DATADIRNAME@
+localedir = $(datadir)/locale
+gnulocaledir = $(prefix)/share/locale
+gettextsrcdir = $(prefix)/share/gettext/po
+subdir = po
+
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+MKINSTALLDIRS = $(top_srcdir)/@MKINSTALLDIRS@
+
+CC = @CC@
+GENCAT = @GENCAT@
+GMSGFMT = PATH=../src:$$PATH @GMSGFMT@
+MSGFMT = @MSGFMT@
+XGETTEXT = PATH=../src:$$PATH @XGETTEXT@
+MSGMERGE = PATH=../src:$$PATH msgmerge
+
+DEFS = @DEFS@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+
+INCLUDES = -I.. -I$(top_srcdir)/intl
+
+COMPILE = $(CC) -c $(DEFS) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) $(XCFLAGS)
+
+SOURCES = cat-id-tbl.c
+POFILES = @POFILES@
+GMOFILES = @GMOFILES@
+DISTFILES = ChangeLog Makefile.in.in POTFILES.in $(PACKAGE).pot \
+stamp-cat-id $(POFILES) $(GMOFILES) $(SOURCES)
+
+POTFILES = \
+
+CATALOGS = @CATALOGS@
+CATOBJEXT = @CATOBJEXT@
+INSTOBJEXT = @INSTOBJEXT@
+
+.SUFFIXES:
+.SUFFIXES: .c .o .po .pox .gmo .mo .msg .cat
+
+.c.o:
+       $(COMPILE) $<
+
+.po.pox:
+       $(MAKE) $(PACKAGE).pot
+       $(MSGMERGE) $< $(srcdir)/$(PACKAGE).pot -o $*.pox
+
+.po.mo:
+       $(MSGFMT) -o $@ $<
+
+.po.gmo:
+       file=$(srcdir)/`echo $* | sed 's,.*/,,'`.gmo \
+         && rm -f $$file && $(GMSGFMT) -o $$file $<
+
+.po.cat:
+       sed -f ../intl/po2msg.sed < $< > $*.msg \
+         && rm -f $@ && $(GENCAT) $@ $*.msg
+
+
+all: all-@USE_NLS@
+
+all-yes: cat-id-tbl.c $(CATALOGS)
+all-no:
+
+$(srcdir)/$(PACKAGE).pot: $(POTFILES)
+       $(XGETTEXT) --default-domain=$(PACKAGE) --directory=$(top_srcdir) \
+         --add-comments --keyword=_ --keyword=N_ \
+         --files-from=$(srcdir)/POTFILES.in
+       rm -f $(srcdir)/$(PACKAGE).pot
+       mv $(PACKAGE).po $(srcdir)/$(PACKAGE).pot
+
+$(srcdir)/cat-id-tbl.c: stamp-cat-id; @:
+$(srcdir)/stamp-cat-id: $(PACKAGE).pot
+       rm -f cat-id-tbl.tmp
+       sed -f ../intl/po2tbl.sed $(srcdir)/$(PACKAGE).pot \
+               | sed -e "s/@PACKAGE NAME@/$(PACKAGE)/" > cat-id-tbl.tmp
+       if cmp -s cat-id-tbl.tmp $(srcdir)/cat-id-tbl.c; then \
+         rm cat-id-tbl.tmp; \
+       else \
+         echo cat-id-tbl.c changed; \
+         rm -f $(srcdir)/cat-id-tbl.c; \
+         mv cat-id-tbl.tmp $(srcdir)/cat-id-tbl.c; \
+       fi
+       cd $(srcdir) && rm -f stamp-cat-id && echo timestamp > stamp-cat-id
+
+
+install: install-exec install-data
+install-exec:
+install-data: install-data-@USE_NLS@
+install-data-no: all
+install-data-yes: all
+       if test -r $(MKINSTALLDIRS); then \
+         $(MKINSTALLDIRS) $(datadir); \
+       else \
+         $(top_srcdir)/mkinstalldirs $(datadir); \
+       fi
+       @catalogs='$(CATALOGS)'; \
+       for cat in $$catalogs; do \
+         cat=`basename $$cat`; \
+         case "$$cat" in \
+           *.gmo) destdir=$(gnulocaledir);; \
+           *)     destdir=$(localedir);; \
+         esac; \
+         lang=`echo $$cat | sed 's/\$(CATOBJEXT)$$//'`; \
+         dir=$$destdir/$$lang/LC_MESSAGES; \
+         if test -r $(MKINSTALLDIRS); then \
+           $(MKINSTALLDIRS) $$dir; \
+         else \
+           $(top_srcdir)/mkinstalldirs $$dir; \
+         fi; \
+         if test -r $$cat; then \
+           $(INSTALL_DATA) $$cat $$dir/$(PACKAGE)$(INSTOBJEXT); \
+           echo "installing $$cat as $$dir/$(PACKAGE)$(INSTOBJEXT)"; \
+         else \
+           $(INSTALL_DATA) $(srcdir)/$$cat $$dir/$(PACKAGE)$(INSTOBJEXT); \
+           echo "installing $(srcdir)/$$cat as" \
+                "$$dir/$(PACKAGE)$(INSTOBJEXT)"; \
+         fi; \
+         if test -r $$cat.m; then \
+           $(INSTALL_DATA) $$cat.m $$dir/$(PACKAGE)$(INSTOBJEXT).m; \
+           echo "installing $$cat.m as $$dir/$(PACKAGE)$(INSTOBJEXT).m"; \
+         else \
+           if test -r $(srcdir)/$$cat.m ; then \
+             $(INSTALL_DATA) $(srcdir)/$$cat.m \
+               $$dir/$(PACKAGE)$(INSTOBJEXT).m; \
+             echo "installing $(srcdir)/$$cat as" \
+                  "$$dir/$(PACKAGE)$(INSTOBJEXT).m"; \
+           else \
+             true; \
+           fi; \
+         fi; \
+       done
+       if test "$(PACKAGE)" = "gettext"; then \
+         if test -r $(MKINSTALLDIRS); then \
+           $(MKINSTALLDIRS) $(gettextsrcdir); \
+         else \
+           $(top_srcdir)/mkinstalldirs $(gettextsrcdir); \
+         fi; \
+         $(INSTALL_DATA) $(srcdir)/Makefile.in.in \
+                         $(gettextsrcdir)/Makefile.in.in; \
+       else \
+         : ; \
+       fi
+
+# Define this as empty until I found a useful application.
+installcheck:
+
+uninstall:
+       catalogs='$(CATALOGS)'; \
+       for cat in $$catalogs; do \
+         cat=`basename $$cat`; \
+         lang=`echo $$cat | sed 's/\$(CATOBJEXT)$$//'`; \
+         rm -f $(localedir)/$$lang/LC_MESSAGES/$(PACKAGE)$(INSTOBJEXT); \
+         rm -f $(localedir)/$$lang/LC_MESSAGES/$(PACKAGE)$(INSTOBJEXT).m; \
+         rm -f $(gnulocaledir)/$$lang/LC_MESSAGES/$(PACKAGE)$(INSTOBJEXT); \
+         rm -f $(gnulocaledir)/$$lang/LC_MESSAGES/$(PACKAGE)$(INSTOBJEXT).m; \
+       done
+       rm -f $(gettextsrcdir)/po-Makefile.in.in
+
+check: all
+
+cat-id-tbl.o: ../intl/libgettext.h
+
+dvi info tags TAGS ID:
+
+mostlyclean:
+       rm -f core core.* *.pox $(PACKAGE).po *.old.po cat-id-tbl.tmp
+       rm -fr *.o
+
+clean: mostlyclean
+
+distclean: clean
+       rm -f Makefile Makefile.in POTFILES *.mo *.msg *.cat *.cat.m
+
+maintainer-clean: distclean
+       @echo "This command is intended for maintainers to use;"
+       @echo "it deletes files that may require special tools to rebuild."
+       rm -f $(GMOFILES) Makefile PSPP.pot cat-id-tbl.c stamp-cat-id
+
+distdir = ../$(PACKAGE)-$(VERSION)/$(subdir)
+dist distdir: update-po $(DISTFILES)
+       dists="$(DISTFILES)"; \
+       for file in $$dists; do \
+         ln $(srcdir)/$$file $(distdir) 2> /dev/null \
+           || cp -p $(srcdir)/$$file $(distdir); \
+       done
+
+update-po: Makefile
+       $(MAKE) $(PACKAGE).pot
+       PATH=`pwd`/../src:$$PATH; \
+       cd $(srcdir); \
+       catalogs='$(CATALOGS)'; \
+       for cat in $$catalogs; do \
+         cat=`basename $$cat`; \
+         lang=`echo $$cat | sed 's/\$(CATOBJEXT)$$//'`; \
+         mv $$lang.po $$lang.old.po; \
+         echo "$$lang:"; \
+         if $(MSGMERGE) $$lang.old.po $(PACKAGE).pot -o $$lang.po; then \
+           rm -f $$lang.old.po; \
+         else \
+           echo "msgmerge for $$cat failed!"; \
+           rm -f $$lang.po; \
+           mv $$lang.old.po $$lang.po; \
+         fi; \
+       done
+
+POTFILES: POTFILES.in
+       ( if test 'x$(srcdir)' != 'x.'; then \
+           posrcprefix='$(top_srcdir)/'; \
+         else \
+           posrcprefix="../"; \
+         fi; \
+         rm -f $@-t $@ \
+           && (sed -e '/^#/d' -e '/^[  ]*$$/d' \
+                   -e "s@.*@   $$posrcprefix& \\\\@" < $(srcdir)/$@.in \
+               | sed -e '$$s/\\$$//') > $@-t \
+           && chmod a-w $@-t \
+           && mv $@-t $@ )
+
+Makefile: Makefile.in.in ../config.status POTFILES
+       cd .. \
+         && CONFIG_FILES=$(subdir)/$@.in CONFIG_HEADERS= \
+              $(SHELL) ./config.status
+
+# Tell versions [3.59,3.63) of GNU make not to export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644 (file)
index 0000000..2a20611
--- /dev/null
@@ -0,0 +1,138 @@
+# List of source files containing translatable strings.
+# Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+
+# Common library files
+lib/misc/strtol.c
+lib/misc/getopt.c
+
+# Package source files
+src/aggregate.c
+src/alloc.c
+src/expr-evl.c
+src/expr-prs.c
+src/expr-opt.c
+src/debug-print.h
+src/alloc.h
+src/approx.h
+src/avl.c
+src/avl.h
+src/hash.c
+src/bitvector.h
+src/filename.c
+src/apply-dict.c
+src/filename.h
+src/hash.h
+src/heap.c
+src/heap.h
+src/pool.c
+src/pool.h
+src/stat.h
+src/str.c
+src/str.h
+src/data-in.c
+src/data-in.h
+src/data-list.c
+src/dfm.c
+src/dfm.h
+src/file-handle.h
+src/file-handle.q
+src/file-type.c
+src/format.c
+src/format.h
+src/formats.c
+src/get.c
+src/inpt-pgm.c
+src/inpt-pgm.h
+src/matrix-data.c
+src/pfm-read.c
+src/pfm-write.c
+src/pfm.h
+src/sfm-read.c
+src/sfm-write.c
+src/sfm.h
+src/sfmP.h
+src/sysfile-info.c
+src/exprP.h
+src/expr.h
+src/command.c
+src/command.h
+src/getline.c
+src/getline.h
+src/lexer.c
+src/lexer.h
+src/cmdline.c
+src/error.c
+src/error.h
+src/glob.c
+src/main.c
+src/main.h
+src/misc.c
+src/misc.h
+src/version.h
+src/ascii.c
+src/magic.c
+src/magic.h
+src/matrix.c
+src/matrix.h
+src/random.c
+src/random.h
+src/stats.c
+src/stats.h
+src/cases.c
+src/data-out.c
+src/font.h
+src/groff-font.c
+src/html.c
+src/htmlP.h
+src/log.h
+src/output.c
+src/output.h
+src/postscript.c
+src/som.c
+src/som.h
+src/tab.c
+src/tab.h
+src/do-if.c
+src/correlations.q
+src/crosstabs.q
+src/descript.q
+src/frequencies.q
+src/list.q
+src/means.q
+src/t-test.q
+src/cases.h
+src/count.c
+src/var.h
+src/vars-atr.c
+src/vars-prs.c
+src/vfm.c
+src/vfm.h
+src/vfmP.h
+src/autorecode.c
+src/compute.c
+src/flip.c
+src/print.c
+src/recode.c
+src/sel-if.c
+src/sort.c
+src/sort.h
+src/do-ifP.h
+src/include.c
+src/loop.c
+src/repeat.c
+src/mis-val.c
+src/modify-vars.c
+src/numeric.c
+src/rename-vars.c
+src/sample.c
+src/set.q
+src/settings.h
+src/split-file.c
+src/temporary.c
+src/title.c
+src/val-labs.c
+src/var-labs.c
+src/vector.c
+src/vector.h
+src/weight.c
+
diff --git a/po/pspp.pot b/po/pspp.pot
new file mode 100644 (file)
index 0000000..ba2ecf3
--- /dev/null
@@ -0,0 +1,5355 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2000-01-07 20:39-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: ENCODING\n"
+
+#: lib/misc/getopt.c:514
+#, c-format
+msgid "%s: option `%s' is ambiguous\n"
+msgstr ""
+
+#: lib/misc/getopt.c:538
+#, c-format
+msgid "%s: option `--%s' doesn't allow an argument\n"
+msgstr ""
+
+#: lib/misc/getopt.c:543
+#, c-format
+msgid "%s: option `%c%s' doesn't allow an argument\n"
+msgstr ""
+
+#: lib/misc/getopt.c:557
+#, c-format
+msgid "%s: option `%s' requires an argument\n"
+msgstr ""
+
+#. --option
+#: lib/misc/getopt.c:585
+#, c-format
+msgid "%s: unrecognized option `--%s'\n"
+msgstr ""
+
+#. +option or -option
+#: lib/misc/getopt.c:589
+#, c-format
+msgid "%s: unrecognized option `%c%s'\n"
+msgstr ""
+
+#. 1003.2 specifies the format of this message.
+#: lib/misc/getopt.c:614
+#, c-format
+msgid "%s: illegal option -- %c\n"
+msgstr ""
+
+#: lib/misc/getopt.c:616
+#, c-format
+msgid "%s: invalid option -- %c\n"
+msgstr ""
+
+#. 1003.2 specifies the format of this message.
+#: lib/misc/getopt.c:650
+#, c-format
+msgid "%s: option requires an argument -- %c\n"
+msgstr ""
+
+#: src/aggregate.c:191
+msgid "OUTFILE specified multiple times."
+msgstr ""
+
+#: src/aggregate.c:217
+msgid "while expecting COLUMNWISE"
+msgstr ""
+
+#: src/aggregate.c:232
+msgid "BREAK specified multiple times."
+msgstr ""
+
+#: src/aggregate.c:261
+msgid "BREAK subcommand not specified."
+msgstr ""
+
+#: src/aggregate.c:504
+msgid "expecting aggregation function"
+msgstr ""
+
+#: src/aggregate.c:520
+#, c-format
+msgid "Unknown aggregation function %s."
+msgstr ""
+
+#: src/aggregate.c:535
+msgid "expecting `('"
+msgstr ""
+
+#: src/aggregate.c:570
+#, c-format
+msgid "Missing argument %d to %s."
+msgstr ""
+
+#: src/aggregate.c:578
+#, c-format
+msgid "Arguments to %s must be of same type as source variables."
+msgstr ""
+
+#: src/aggregate.c:588 src/expr-prs.c:664
+msgid "expecting `)'"
+msgstr ""
+
+#: src/aggregate.c:600 src/autorecode.c:114
+#, c-format
+msgid ""
+"Number of source variables (%d) does not match number of target variables "
+"(%d)."
+msgstr ""
+
+#: src/aggregate.c:671
+#, c-format
+msgid ""
+"Variable name %s is not unique within the aggregate file dictionary, which "
+"contains the aggregate variables and the break variables."
+msgstr ""
+
+#: src/expr-evl.c:1180
+msgid ""
+"A number being treated as a Boolean in an expression was found to have a "
+"value other than 0 (false), 1 (true), or the system-missing value.  The "
+"result was forced to 0."
+msgstr ""
+
+#: src/expr-evl.c:1224
+#, c-format
+msgid ""
+"SYSMIS is not a valid index value for vector %s.  The result will be set to "
+"SYSMIS."
+msgstr ""
+
+#: src/expr-evl.c:1228
+#, c-format
+msgid ""
+"%g is not a valid index value for vector %s.  The result will be set to "
+"SYSMIS."
+msgstr ""
+
+#: src/expr-evl.c:1246
+#, c-format
+msgid ""
+"SYSMIS is not a valid index value for vector %s.  The result will be set to "
+"the empty string."
+msgstr ""
+
+#: src/expr-evl.c:1251
+#, c-format
+msgid ""
+"%g is not a valid index value for vector %s.  The result will be set to the "
+"empty string."
+msgstr ""
+
+#: src/expr-evl.c:1362
+#, c-format
+msgid "evaluate_expression(): not implemented: %s\n"
+msgstr ""
+
+#: src/expr-evl.c:1365
+#, c-format
+msgid "evaluate_expression(): not implemented: %d\n"
+msgstr ""
+
+#: src/expr-prs.c:140
+msgid ""
+"A string expression was supplied in a place where a Boolean expression was "
+"expected."
+msgstr ""
+
+#: src/expr-prs.c:151
+msgid ""
+"A numeric expression was expected in a place where one was not supplied."
+msgstr ""
+
+#: src/expr-prs.c:159
+msgid "A string expression was expected in a place where one was not supplied."
+msgstr ""
+
+#: src/expr-prs.c:173
+msgid "The OR operator cannot take string operands."
+msgstr ""
+
+#: src/expr-prs.c:221
+msgid "The AND operator cannot take string operands."
+msgstr ""
+
+#: src/expr-prs.c:270
+msgid "The NOT operator cannot take a string operand."
+msgstr ""
+
+#: src/expr-prs.c:297
+msgid ""
+"Strings cannot be compared with numeric or Boolean values with the "
+"relational operators = >= > <= < <>."
+msgstr ""
+
+#: src/expr-prs.c:354
+msgid "The `+' and `-' operators may only be used with numeric operands."
+msgstr ""
+
+#: src/expr-prs.c:406
+msgid "The `*' and `/' operators may only be used with numeric operands."
+msgstr ""
+
+#: src/expr-prs.c:457
+msgid "The unary minus (-) operator can only take a numeric operand."
+msgstr ""
+
+#: src/expr-prs.c:487
+msgid "Both operands to the ** operator must be numeric."
+msgstr ""
+
+#: src/expr-prs.c:581
+msgid "Use of $LENGTH is obsolete, returning default of 66."
+msgstr ""
+
+#: src/expr-prs.c:586
+msgid "Use of $WIDTH is obsolete, returning default of 131."
+msgstr ""
+
+#: src/expr-prs.c:591
+#, c-format
+msgid "Unknown system variable %s."
+msgstr ""
+
+#: src/expr-prs.c:630
+msgid "expecting variable name"
+msgstr ""
+
+#: src/expr-prs.c:672
+msgid "in expression"
+msgstr ""
+
+#: src/expr-prs.c:849
+msgid "Argument 2 to LAG must be a small positive integer constant."
+msgstr ""
+
+#: src/expr-prs.c:922 src/expr-prs.c:961
+#, c-format
+msgid ""
+"Type mismatch in argument %d of %s, which was expected to be of %s type.  It "
+"was actually of %s type. "
+msgstr ""
+
+#: src/expr-prs.c:948
+#, c-format
+msgid "%s cannot take Boolean operands."
+msgstr ""
+
+#: src/expr-prs.c:980
+msgid "in function call"
+msgstr ""
+
+#: src/expr-prs.c:994
+msgid "RANGE requires an odd number of arguments, but at least three."
+msgstr ""
+
+#: src/expr-prs.c:1004
+#, c-format
+msgid "%s requires at least two arguments."
+msgstr ""
+
+#: src/expr-prs.c:1019
+#, c-format
+msgid "%s.%d requires at least %d arguments."
+msgstr ""
+
+#: src/expr-prs.c:1061
+#, c-format
+msgid ""
+"Argument %d to CONCAT is type %s.  All arguments to CONCAT must be strings."
+msgstr ""
+
+#: src/expr-prs.c:1120
+#, c-format
+msgid ""
+"Argument %d to %s was expected to be of %s type.  It was actually of type %s."
+msgstr ""
+
+#: src/apply-dict.c:72 src/apply-dict.c:73 src/expr-prs.c:1123
+#: src/expr-prs.c:1464 src/expr-prs.c:1483 src/formats.c:105
+#: src/pfm-read.c:654 src/print.c:719 src/sfm-read.c:1009 src/sfm-read.c:1137
+#: src/sfm-read.c:1138
+msgid "numeric"
+msgstr ""
+
+#: src/apply-dict.c:72 src/apply-dict.c:73 src/expr-prs.c:1123
+#: src/expr-prs.c:1467 src/expr-prs.c:1485 src/formats.c:105
+#: src/pfm-read.c:654 src/print.c:719 src/sfm-read.c:1009 src/sfm-read.c:1137
+#: src/sfm-read.c:1138
+msgid "string"
+msgstr ""
+
+#: src/expr-prs.c:1139
+#, c-format
+msgid "%s is not a numeric format."
+msgstr ""
+
+#: src/expr-prs.c:1165
+#, c-format
+msgid "Too few arguments to function %s."
+msgstr ""
+
+#: src/expr-prs.c:1197
+#, c-format
+msgid ""
+"Type mismatch in argument %d of %s, which was expected to be numeric.  It "
+"was actually type %s."
+msgstr ""
+
+#: src/expr-prs.c:1206
+#, c-format
+msgid "Missing comma following argument %d of %s."
+msgstr ""
+
+#: src/expr-prs.c:1244
+msgid "The index value after a vector name must be numeric."
+msgstr ""
+
+#: src/expr-prs.c:1251
+msgid "`)' expected after a vector index value."
+msgstr ""
+
+#: src/expr-prs.c:1283
+#, c-format
+msgid "There is no function named %s."
+msgstr ""
+
+#: src/expr-prs.c:1288
+#, c-format
+msgid "Function %s may not be given a minimum number of arguments."
+msgstr ""
+
+#: src/expr-prs.c:1297
+#, c-format
+msgid "expecting `)' after %s function"
+msgstr ""
+
+#. FE
+#: src/error.c:281 src/error.c:288 src/error.c:291 src/expr-prs.c:1458
+msgid "error"
+msgstr ""
+
+#: src/expr-prs.c:1461
+msgid "Boolean"
+msgstr ""
+
+#: src/expr-prs.c:1689
+msgid "!!TERMINAL!!"
+msgstr ""
+
+#: src/expr-prs.c:1715
+msgid "!!SENTINEL!!"
+msgstr ""
+
+#: src/expr-prs.c:1718
+#, c-format
+msgid "!!ERROR%d!!"
+msgstr ""
+
+#: src/expr-prs.c:1736
+msgid "postfix:"
+msgstr ""
+
+#: src/expr-opt.c:662
+msgid ""
+"While optimizing a constant expression, there was a bad value for the third "
+"argument to INDEX."
+msgstr ""
+
+#: src/expr-opt.c:687
+msgid ""
+"While optimizing a constant expression, there was a bad value for the third "
+"argument to RINDEX."
+msgstr ""
+
+#: src/expr-opt.c:746
+#, c-format
+msgid "Third argument to %cPAD() must be at least one character in length."
+msgstr ""
+
+#: src/expr-opt.c:779
+#, c-format
+msgid "Second argument to %cTRIM() must be at least one character in length."
+msgstr ""
+
+#: src/expr-opt.c:880
+msgid ""
+"When optimizing a constant expression, an integer that was being used as an "
+"Boolean value was found to have a constant value other than 0, 1, or SYSMIS."
+msgstr ""
+
+#: src/hash.c:315
+msgid "hash table:"
+msgstr ""
+
+#: src/filename.c:240
+#, c-format
+msgid "Searching for `%s'..."
+msgstr ""
+
+#: src/filename.c:248 src/filename.c:280
+msgid "Search unsuccessful!"
+msgstr ""
+
+#: src/filename.c:273
+#, c-format
+msgid "Found `%s'."
+msgstr ""
+
+#: src/filename.c:694
+#, c-format
+msgid "Not opening pipe file `%s' because SAFER option set."
+msgstr ""
+
+#: src/apply-dict.c:69
+#, c-format
+msgid "Variable %s is %s in target file, but %s in source file."
+msgstr ""
+
+#: src/apply-dict.c:85
+#, c-format
+msgid "Cannot add value labels from source file to long string variable %s."
+msgstr ""
+
+#: src/apply-dict.c:121
+#, c-format
+msgid ""
+"Cannot apply missing values from source file to long string variable %s."
+msgstr ""
+
+#: src/apply-dict.c:153
+msgid "No matching variables found between the source and target files."
+msgstr ""
+
+#: src/heap.c:167
+#, c-format
+msgid "bad ordering of keys %d and %d\n"
+msgstr ""
+
+#: src/heap.c:177
+msgid "Heap contents:\n"
+msgstr ""
+
+#: src/data-in.c:71
+msgid "data-file error: "
+msgstr ""
+
+#: src/data-in.c:73
+#, c-format
+msgid "(column %d"
+msgstr ""
+
+#: src/data-in.c:75
+#, c-format
+msgid "(columns %d-%d"
+msgstr ""
+
+#: src/data-in.c:76
+#, c-format
+msgid ", field type %s) "
+msgstr ""
+
+#: src/data-in.c:225
+msgid "Field contents followed by garbage."
+msgstr ""
+
+#. Return an overflow error.
+#: src/data-in.c:258
+msgid "Overflow in floating-point constant."
+msgstr ""
+
+#. Return an underflow error.
+#: src/data-in.c:264
+msgid "Underflow in floating-point constant."
+msgstr ""
+
+#. There was no number.
+#: src/data-in.c:270
+msgid "Field does not form a valid floating-point constant."
+msgstr ""
+
+#: src/data-in.c:295
+msgid "All characters in field must be digits."
+msgstr ""
+
+#: src/data-in.c:320
+msgid "Unrecognized character in field."
+msgstr ""
+
+#: src/data-in.c:338 src/data-in.c:592
+msgid "Field must have even length."
+msgstr ""
+
+#: src/data-in.c:348 src/data-in.c:602
+msgid "Field must contain only hex digits."
+msgstr ""
+
+#: src/data-in.c:385
+msgid ""
+"Quality of zoned decimal (Z) input format code is suspect.  Check your "
+"results three times, report bugs to author."
+msgstr ""
+
+#: src/data-in.c:397
+msgid "Zoned decimal field contains fewer than 2 characters."
+msgstr ""
+
+#: src/data-in.c:405
+msgid "Bad sign byte in zoned decimal number."
+msgstr ""
+
+#: src/data-in.c:422
+msgid "Format error in zoned decimal number."
+msgstr ""
+
+#: src/data-in.c:436
+msgid "Error in syntax of zoned decimal number."
+msgstr ""
+
+#: src/data-in.c:647
+msgid "Unexpected end of field."
+msgstr ""
+
+#: src/data-in.c:673
+msgid "Digit expected in field."
+msgstr ""
+
+#: src/data-in.c:698
+#, c-format
+msgid "Day (%ld) must be between 1 and 31."
+msgstr ""
+
+#: src/data-in.c:723
+msgid "Delimiter expected between fields in date."
+msgstr ""
+
+#: src/data-in.c:820
+#, c-format
+msgid "Month (%ld) must be between 1 and 12."
+msgstr ""
+
+#: src/data-in.c:861
+#, c-format
+msgid "Month (%s) must be between I and XII."
+msgstr ""
+
+#: src/data-in.c:888
+#, c-format
+msgid "Month name (%s...) is too long."
+msgstr ""
+
+#: src/data-in.c:899
+#, c-format
+msgid "Bad month name (%s)."
+msgstr ""
+
+#: src/data-in.c:915
+#, c-format
+msgid "Year (%ld) must be between 1582 and 19999."
+msgstr ""
+
+#: src/data-in.c:926
+#, c-format
+msgid "Trailing garbage \"%s\" following date."
+msgstr ""
+
+#: src/data-in.c:941
+#, c-format
+msgid "Julian day (%d) must be between 1 and 366."
+msgstr ""
+
+#: src/data-in.c:953
+#, c-format
+msgid "Year (%d) must be between 1582 and 19999."
+msgstr ""
+
+#: src/data-in.c:969
+#, c-format
+msgid "Quarter (%ld) must be between 1 and 4."
+msgstr ""
+
+#: src/data-in.c:979
+msgid "`Q' expected between quarter and year."
+msgstr ""
+
+#: src/data-in.c:995
+#, c-format
+msgid "Week (%ld) must be between 1 and 53."
+msgstr ""
+
+#: src/data-in.c:1006
+msgid "`WK' expected between week and year."
+msgstr ""
+
+#: src/data-in.c:1029
+msgid "Delimiter expected between fields in time."
+msgstr ""
+
+#: src/data-in.c:1041
+#, c-format
+msgid "Hour (%ld) must be positive."
+msgstr ""
+
+#: src/data-in.c:1053
+#, c-format
+msgid "Minute (%ld) must be between 0 and 59."
+msgstr ""
+
+#: src/data-in.c:1100
+#, c-format
+msgid "Hour (%ld) must be between 0 and 23."
+msgstr ""
+
+#: src/data-in.c:1114 src/data-in.c:1149
+msgid "Day of the week expected in date value."
+msgstr ""
+
+#: src/data-in.c:1200
+msgid "Date is not in valid range between 15 Oct 1582 and 31 Dec 19999."
+msgstr ""
+
+#: src/data-in.c:1528
+#, c-format
+msgid "Field too long (%d characters).  Truncated after character %d."
+msgstr ""
+
+#: src/data-list.c:154
+msgid ""
+"DATA LIST may not use a different file from that specified on its "
+"surrounding FILE TYPE."
+msgstr ""
+
+#: src/data-list.c:173
+msgid "The END subcommand may only be specified once."
+msgstr ""
+
+#: src/data-list.c:209
+msgid "Only one of FIXED, FREE, or LIST may be specified."
+msgstr ""
+
+#: src/data-list.c:339 src/print.c:320
+#, c-format
+msgid ""
+"The record number specified, %ld, is before the previous record, %d.  Data "
+"fields must be listed in order of increasing record number."
+msgstr ""
+
+#: src/data-list.c:371 src/data-list.c:1635
+msgid ""
+"SPSS-like or FORTRAN-like format specification expected after variable names."
+msgstr ""
+
+#: src/data-list.c:382 src/print.c:352
+msgid ""
+"Variables are specified on records that should not exist according to "
+"RECORDS subcommand."
+msgstr ""
+
+#: src/autorecode.c:125 src/command.c:714 src/compute.c:361
+#: src/data-list.c:390 src/data-list.c:840 src/data-list.c:1646
+#: src/do-if.c:267 src/file-handle.q:90 src/get.c:436 src/lexer.c:384
+#: src/loop.c:250 src/matrix-data.c:527 src/print.c:359 src/print.c:1100
+#: src/recode.c:411 src/sel-if.c:56 src/sel-if.c:136 src/vector.c:208
+msgid "expecting end of command"
+msgstr ""
+
+#: src/data-list.c:414 src/data-list.c:427 src/print.c:529 src/print.c:542
+msgid "Column positions for fields must be positive."
+msgstr ""
+
+#: src/data-list.c:432
+msgid "The ending column for a field must be greater than the starting column."
+msgstr ""
+
+#: src/data-list.c:456 src/print.c:570
+msgid "A format specifier on this line has extra characters on the end."
+msgstr ""
+
+#: src/data-list.c:471 src/print.c:586
+msgid "The value for number of decimal places must be at least 1."
+msgstr ""
+
+#: src/data-list.c:485 src/print.c:599
+#, c-format
+msgid "Input format %s doesn't accept decimal places."
+msgstr ""
+
+#: src/data-list.c:506 src/print.c:619
+#, c-format
+msgid "The %d columns %d-%d can't be evenly divided into %d fields."
+msgstr ""
+
+#: src/data-list.c:539 src/data-list.c:626 src/data-list.c:823
+#, c-format
+msgid "%s is a duplicate variable name."
+msgstr ""
+
+#: src/data-list.c:544
+#, c-format
+msgid "There is already a variable %s of a different type."
+msgstr ""
+
+#: src/data-list.c:551
+#, c-format
+msgid "There is already a string variable %s of a different width."
+msgstr ""
+
+#: src/data-list.c:615 src/print.c:708
+msgid ""
+"The number of format specifications exceeds the number of variable names "
+"given."
+msgstr ""
+
+#: src/data-list.c:699 src/print.c:792
+msgid ""
+"There aren't enough format specifications to match the number of variable "
+"names given."
+msgstr ""
+
+#: src/data-list.c:733 src/data-list.c:867 src/descript.q:799 src/print.c:824
+#: src/sysfile-info.c:130 src/sysfile-info.c:369 src/vfm.c:1133
+msgid "Variable"
+msgstr ""
+
+#: src/data-list.c:734 src/print.c:825
+msgid "Record"
+msgstr ""
+
+#: src/data-list.c:735 src/print.c:826
+msgid "Columns"
+msgstr ""
+
+#: src/data-list.c:736 src/data-list.c:868 src/print.c:827
+msgid "Format"
+msgstr ""
+
+#: src/data-list.c:758
+#, c-format
+msgid "Reading %d record%s from file %s."
+msgstr ""
+
+#: src/data-list.c:759
+#, c-format
+msgid "Reading %d record%s from the command file."
+msgstr ""
+
+#: src/data-list.c:764 src/data-list.c:765
+msgid "Occurrence data specifications."
+msgstr ""
+
+#: src/data-list.c:891
+#, c-format
+msgid "Reading free-form data from file %s."
+msgstr ""
+
+#: src/data-list.c:892
+msgid "Reading free-form data from the command file."
+msgstr ""
+
+#: src/data-list.c:943 src/matrix-data.c:957
+msgid "Scope of string exceeds line."
+msgstr ""
+
+#: src/data-list.c:1004
+msgid "Attempt to read past end of file."
+msgstr ""
+
+#: src/data-list.c:1033
+msgid "abort in write_case()\n"
+msgstr ""
+
+#. Note that this can't occur on the first record.
+#: src/data-list.c:1064
+#, c-format
+msgid "Partial case of %d of %d records discarded."
+msgstr ""
+
+#: src/data-list.c:1113
+#, c-format
+msgid "Partial case discarded.  The first variable missing was %s."
+msgstr ""
+
+#: src/data-list.c:1154
+#, c-format
+msgid ""
+"Missing value(s) for all variables from %s onward.  These will be filled "
+"with the system-missing value or blanks, as appropriate."
+msgstr ""
+
+#: src/data-list.c:1312
+msgid ""
+"REPEATING DATA must use the same file as its corresponding DATA LIST or FILE "
+"TYPE."
+msgstr ""
+
+#: src/data-list.c:1322
+msgid "STARTS subcommand given multiple times."
+msgstr ""
+
+#: src/data-list.c:1346
+#, c-format
+msgid "STARTS beginning column (%d) exceeds STARTS ending column (%d)."
+msgstr ""
+
+#: src/data-list.c:1357
+msgid "OCCURS subcommand given multiple times."
+msgstr ""
+
+#: src/data-list.c:1370
+msgid "LENGTH subcommand given multiple times."
+msgstr ""
+
+#: src/data-list.c:1383
+msgid "CONTINUED subcommand given multiple times."
+msgstr ""
+
+#: src/data-list.c:1402
+#, c-format
+msgid "CONTINUED beginning column (%d) exceeds CONTINUED ending column (%d)."
+msgstr ""
+
+#: src/data-list.c:1416
+msgid "ID subcommand given multiple times."
+msgstr ""
+
+#: src/data-list.c:1425
+#, c-format
+msgid "ID beginning column (%ld) must be positive."
+msgstr ""
+
+#: src/data-list.c:1440
+#, c-format
+msgid "ID ending column (%ld) must be positive."
+msgstr ""
+
+#: src/data-list.c:1446
+#, c-format
+msgid "ID ending column (%ld) cannot be less than ID beginning column (%d)."
+msgstr ""
+
+#: src/data-list.c:1485
+msgid "Missing required specification STARTS."
+msgstr ""
+
+#: src/data-list.c:1487
+msgid "Missing required specification OCCURS."
+msgstr ""
+
+#: src/data-list.c:1494
+msgid "ID specified without CONTINUED."
+msgstr ""
+
+#: src/data-list.c:1582
+msgid "String variable not allowed here."
+msgstr ""
+
+#: src/data-list.c:1592
+#, c-format
+msgid "%s (%d) must be at least 1."
+msgstr ""
+
+#: src/data-list.c:1598
+#, c-format
+msgid "Variable or integer expected for %s."
+msgstr ""
+
+#: src/data-list.c:1737
+#, c-format
+msgid "Mismatched case ID (%s).  Expected value was %s."
+msgstr ""
+
+#: src/data-list.c:1769
+#, c-format
+msgid ""
+"Variable %s startging in column %d extends beyond physical record length of "
+"%d."
+msgstr ""
+
+#: src/data-list.c:1837
+#, c-format
+msgid "Invalid value %d for OCCURS."
+msgstr ""
+
+#: src/data-list.c:1843
+#, c-format
+msgid "Beginning column for STARTS (%d) must be at least 1."
+msgstr ""
+
+#: src/data-list.c:1851
+#, c-format
+msgid "Ending column for STARTS (%d) is less than beginning column (%d)."
+msgstr ""
+
+#: src/data-list.c:1859
+#, c-format
+msgid "Invalid value %d for LENGTH."
+msgstr ""
+
+#: src/data-list.c:1866
+#, c-format
+msgid "Beginning column for CONTINUED (%d) must be at least 1."
+msgstr ""
+
+#: src/data-list.c:1874
+#, c-format
+msgid "Ending column for CONTINUED (%d) is less than beginning column (%d)."
+msgstr ""
+
+#: src/data-list.c:1897
+#, c-format
+msgid ""
+"Number of repetitions specified on OCCURS (%d) exceed number of repetitions "
+"available in space on STARTS (%d), and CONTINUED not specified."
+msgstr ""
+
+#: src/data-list.c:1914
+#, c-format
+msgid "Unexpected end of file with %d repetitions remaining out of %d."
+msgstr ""
+
+#: src/dfm.c:92
+#, c-format
+msgid "%s: Closing data-file handle %s."
+msgstr ""
+
+#: src/dfm.c:117
+msgid "<<Bug in dfm.c>>"
+msgstr ""
+
+#: src/dfm.c:139
+#, c-format
+msgid "%s: Opening data-file handle %s for reading."
+msgstr ""
+
+#: src/dfm.c:156 src/dfm.c:173
+msgid "BEGIN DATA expected."
+msgstr ""
+
+#: src/dfm.c:193
+#, c-format
+msgid "An error occurred while opening \"%s\" for reading as a data file: %s."
+msgstr ""
+
+#: src/dfm.c:222
+#, c-format
+msgid "%s: Opening data-file handle %s for writing."
+msgstr ""
+
+#: src/dfm.c:228
+msgid "Cannot open the inline file for writing."
+msgstr ""
+
+#: src/dfm.c:243
+#, c-format
+msgid "An error occurred while opening \"%s\" for writing as a data file: %s."
+msgstr ""
+
+#: src/dfm.c:388
+msgid ""
+"Unexpected end-of-file while reading data in BEGIN DATA.  This probably "
+"indicates a missing or misformatted END DATA command.  END DATA must appear "
+"by itself on a single line with exactly one space between words."
+msgstr ""
+
+#: src/dfm.c:421 src/dfm.c:442
+#, c-format
+msgid "Error reading file %s: %s."
+msgstr ""
+
+#: src/dfm.c:445
+#, c-format
+msgid "%s: Partial record at end of file."
+msgstr ""
+
+#: src/dfm.c:501
+#, c-format
+msgid "Cannot read from file %s already opened for %s."
+msgstr ""
+
+#: src/dfm.c:515
+#, c-format
+msgid "Attempt to read beyond end-of-file on file %s."
+msgstr ""
+
+#: src/dfm.c:609
+#, c-format
+msgid "Cannot write to file %s already opened for %s."
+msgstr ""
+
+#: src/dfm.c:633
+#, c-format
+msgid "Error writing file %s: %s."
+msgstr ""
+
+#: src/dfm.c:676
+msgid ""
+"This command is not valid here since the current input program does not "
+"access the inline file."
+msgstr ""
+
+#. Initialize inline_file.
+#: src/dfm.c:683
+msgid "inline file: Opening for reading."
+msgstr ""
+
+#: src/dfm.c:697
+msgid "Skipping remaining inline data."
+msgstr ""
+
+#: src/dfm.c:709
+msgid "reading as a data file"
+msgstr ""
+
+#: src/dfm.c:716
+msgid "writing as a data file"
+msgstr ""
+
+#: src/file-handle.q:74
+#, c-format
+msgid ""
+"File handle %s had already been defined to refer to file %s.  It is not "
+"possible to redefine a file handle within a session."
+msgstr ""
+
+#: src/file-handle.q:96
+msgid "The FILE HANDLE required subcommand NAME is not present."
+msgstr ""
+
+#: src/file-handle.q:109
+msgid ""
+"Fixed length records were specified on /RECFORM, but record length was not "
+"specified on /LRECL.  80-character records will be assumed."
+msgstr ""
+
+#: src/file-handle.q:116
+#, c-format
+msgid ""
+"Record length (%ld) must be at least one byte.  80-character records will be "
+"assumed."
+msgstr ""
+
+#: src/file-handle.q:127
+msgid ""
+"/RECFORM SPANNED is not implemented, as the author doesn't know what it is "
+"supposed to do.  Send the author a note."
+msgstr ""
+
+#: src/file-handle.q:140
+msgid ""
+"/MODE IMAGE is not implemented, as the author doesn't know what it is "
+"supposed to do.  Send the author a note."
+msgstr ""
+
+#: src/file-handle.q:147
+msgid "/MODE MULTIPUNCH is not implemented.  If you care, complain."
+msgstr ""
+
+#: src/file-handle.q:151
+msgid "/MODE 360 is not implemented.  If you care, complain."
+msgstr ""
+
+#: src/file-handle.q:233
+#, c-format
+msgid "File handle `%s' has not been previously declared on FILE HANDLE."
+msgstr ""
+
+#: src/file-handle.q:309
+msgid "<Inline File>"
+msgstr ""
+
+#: src/file-handle.q:328
+msgid "expecting a file name or handle"
+msgstr ""
+
+#: src/file-type.c:126
+msgid "MIXED, GROUPED, or NESTED expected."
+msgstr ""
+
+#: src/file-type.c:149
+msgid "The CASE subcommand is not valid on FILE TYPE MIXED."
+msgstr ""
+
+#: src/file-type.c:167
+msgid "WARN or NOWARN expected after WILD."
+msgstr ""
+
+#: src/file-type.c:175
+msgid "The DUPLICATE subcommand is not valid on FILE TYPE MIXED."
+msgstr ""
+
+#: src/file-type.c:189
+msgid "DUPLICATE=CASE is only valid on FILE TYPE NESTED."
+msgstr ""
+
+#: src/file-type.c:198
+#, c-format
+msgid "WARN%s expected after DUPLICATE."
+msgstr ""
+
+#: src/file-type.c:199
+msgid ", NOWARN, or CASE"
+msgstr ""
+
+#: src/file-type.c:200
+msgid " or NOWARN"
+msgstr ""
+
+#: src/file-type.c:208
+msgid "The MISSING subcommand is not valid on FILE TYPE MIXED."
+msgstr ""
+
+#: src/file-type.c:220
+msgid "WARN or NOWARN after MISSING."
+msgstr ""
+
+#: src/file-type.c:228
+msgid "ORDERED is only valid on FILE TYPE GROUPED."
+msgstr ""
+
+#: src/file-type.c:239
+msgid "YES or NO expected after ORDERED."
+msgstr ""
+
+#: src/file-type.c:245 src/file-type.c:555 src/get.c:420
+msgid "while expecting a valid subcommand"
+msgstr ""
+
+#: src/file-type.c:252
+msgid "The required RECORD subcommand was not present."
+msgstr ""
+
+#: src/file-type.c:260
+msgid "The required CASE subcommand was not present."
+msgstr ""
+
+#: src/file-type.c:266
+msgid "CASE and RECORD must specify different variable names."
+msgstr ""
+
+#: src/file-type.c:317
+msgid "Column value must be positive."
+msgstr ""
+
+#: src/file-type.c:332
+msgid "Ending column precedes beginning column."
+msgstr ""
+
+#: src/file-type.c:351
+msgid "Bad format specifier name."
+msgstr ""
+
+#: src/file-type.c:417 src/file-type.c:576
+msgid ""
+"This command may only appear within a FILE TYPE/END FILE TYPE structure."
+msgstr ""
+
+#: src/file-type.c:424
+msgid "OTHER may appear only on the last RECORD TYPE command."
+msgstr ""
+
+#: src/file-type.c:434
+msgid "No input commands (DATA LIST, REPEATING DATA) for above RECORD TYPE."
+msgstr ""
+
+#: src/file-type.c:488
+msgid ""
+"The CASE subcommand is not allowed on the RECORD TYPE command for FILE TYPE "
+"MIXED."
+msgstr ""
+
+#: src/file-type.c:498
+msgid ""
+"No variable name may be specified for the CASE subcommand on RECORD TYPE."
+msgstr ""
+
+#: src/file-type.c:506
+msgid ""
+"The CASE column specification on RECORD TYPE must give a format specifier "
+"that is the same type as that of the CASE column specification given on FILE "
+"TYPE."
+msgstr ""
+
+#: src/file-type.c:522
+msgid "WARN or NOWARN expected on DUPLICATE subcommand."
+msgstr ""
+
+#: src/file-type.c:536
+msgid "WARN or NOWARN expected on MISSING subcommand."
+msgstr ""
+
+#: src/file-type.c:549
+msgid "YES or NO expected on SPREAD subcommand."
+msgstr ""
+
+#: src/file-type.c:589
+msgid "No input commands (DATA LIST, REPEATING DATA) on above RECORD TYPE."
+msgstr ""
+
+#: src/file-type.c:596
+msgid "No commands between FILE TYPE and END FILE TYPE."
+msgstr ""
+
+#: src/file-type.c:661
+#, c-format
+msgid "Unknown record type \"%.*s\"."
+msgstr ""
+
+#: src/file-type.c:685
+#, c-format
+msgid "Unknown record type %g."
+msgstr ""
+
+#: src/format.c:75
+msgid "X and T format specifiers not allowed here."
+msgstr ""
+
+#: src/format.c:81
+#, c-format
+msgid "%s is not a valid data format."
+msgstr ""
+
+#: src/format.c:112
+#, c-format
+msgid "Format %s may not be used as an input format."
+msgstr ""
+
+#: src/format.c:117
+#, c-format
+msgid ""
+"Input format %s specifies a bad width %d.  Format %s requires a width "
+"between %d and %d."
+msgstr ""
+
+#: src/format.c:124
+#, c-format
+msgid ""
+"Input format %s specifies an odd width %d, but format %s requires an even "
+"width between %d and %d."
+msgstr ""
+
+#: src/format.c:131
+#, c-format
+msgid ""
+"Input format %s specifies a bad number of implied decimal places %d.  Input "
+"format %s allows up to 16 implied decimal places."
+msgstr ""
+
+#: src/format.c:151
+#, c-format
+msgid ""
+"Output format %s specifies a bad width %d.  Format %s requires a width "
+"between %d and %d."
+msgstr ""
+
+#: src/format.c:161
+#, c-format
+msgid ""
+"Output format %s requires minimum width %d to allow %d decimal places.  Try "
+"%s%d.%d instead of %s."
+msgstr ""
+
+#: src/format.c:169
+#, c-format
+msgid ""
+"Output format %s specifies an odd width %d, but output format %s requires an "
+"even width between %d and %d."
+msgstr ""
+
+#: src/format.c:176
+#, c-format
+msgid ""
+"Output format %s specifies a bad number of implied decimal places %d.  "
+"Output format %s allows a number of implied decimal places between 1 and 16."
+msgstr ""
+
+#: src/format.c:193
+#, c-format
+msgid "Can't display a string variable of width %d with format specifier %s."
+msgstr ""
+
+#: src/format.c:303
+msgid "Format specifier expected."
+msgstr ""
+
+#: src/format.c:314
+#, c-format
+msgid "Data format %s does not specify a width."
+msgstr ""
+
+#: src/format.c:331
+#, c-format
+msgid "Data format %s is not valid."
+msgstr ""
+
+#: src/formats.c:95
+msgid "`(' expected after variable list"
+msgstr ""
+
+#: src/formats.c:104
+#, c-format
+msgid "Format %s may not be assigned to a %s variable."
+msgstr ""
+
+#: src/formats.c:125 src/numeric.c:68 src/numeric.c:142
+msgid "`)' expected after output format."
+msgstr ""
+
+#: src/formats.c:155
+msgid "Formats:\n"
+msgstr ""
+
+#: src/formats.c:156
+msgid "  Name      Print         Write\n"
+msgstr ""
+
+#: src/get.c:125
+msgid "GET translation table from file to memory:\n"
+msgstr ""
+
+#: src/get.c:130 src/get.c:1487
+#, c-format
+msgid "  %8s from %3d,%3d to %3d,%3d\n"
+msgstr ""
+
+#: src/get.c:426
+msgid "All variables deleted from system file dictionary."
+msgstr ""
+
+#: src/get.c:472
+#, c-format
+msgid ""
+"Cannot rename %s as %s because there already exists a variable named %s.  To "
+"rename variables with overlapping names, use a single RENAME subcommand such "
+"as \"/RENAME (A=B)(B=C)(C=A)\", or equivalently, \"/RENAME (A B C=B C A)\"."
+msgstr ""
+
+#: src/get.c:497
+msgid "`=' expected after variable list."
+msgstr ""
+
+#: src/get.c:504
+#, c-format
+msgid ""
+"Number of variables on left side of `=' (%d) do not match number of "
+"variables on right side (%d), in parenthesized group %d of RENAME subcommand."
+msgstr ""
+
+#: src/get.c:522
+#, c-format
+msgid "Duplicate variables name %s."
+msgstr ""
+
+#: src/get.c:545
+msgid ""
+"\n"
+"Variables in dictionary:\n"
+msgstr ""
+
+#: src/get.c:659
+msgid "The BY subcommand may be given once at most."
+msgstr ""
+
+#: src/get.c:726
+msgid "The active file may not be specified more than once."
+msgstr ""
+
+#: src/get.c:735
+msgid "Cannot specify the active file since no active file has been defined."
+msgstr ""
+
+#: src/get.c:767
+msgid ""
+"IN, FIRST, and LAST subcommands may not occur before the first FILE or TABLE."
+msgstr ""
+
+#: src/get.c:799
+#, c-format
+msgid "Multiple %s subcommands for a single FILE or TABLE."
+msgstr ""
+
+#: src/get.c:809
+#, c-format
+msgid "Duplicate variable name %s while creating %s variable."
+msgstr ""
+
+#: src/get.c:823
+msgid ""
+"RENAME, KEEP, and DROP subcommands may not occur before the first FILE or "
+"TABLE."
+msgstr ""
+
+#: src/get.c:847
+msgid "The BY subcommand is required when a TABLE subcommand is given."
+msgstr ""
+
+#: src/get.c:868
+#, c-format
+msgid "File %s lacks BY variable %s."
+msgstr ""
+
+#: src/get.c:1386
+#, c-format
+msgid ""
+"Variable %s in file %s (%s) has different type or width from the same "
+"variable in earlier file (%s)."
+msgstr ""
+
+#: src/get.c:1438
+msgid "expecting COMM or TAPE"
+msgstr ""
+
+#: src/get.c:1482
+msgid "IMPORT translation table from file to memory:\n"
+msgstr ""
+
+#: src/inpt-pgm.c:81
+msgid "No matching INPUT PROGRAM command."
+msgstr ""
+
+#: src/inpt-pgm.c:86
+msgid ""
+"No data-input or transformation commands specified between INPUT PROGRAM and "
+"END INPUT PROGRAM."
+msgstr ""
+
+#: src/inpt-pgm.c:302 src/inpt-pgm.c:445
+msgid ""
+"This command may only be executed between INPUT PROGRAM and END INPUT "
+"PROGRAM."
+msgstr ""
+
+#: src/inpt-pgm.c:361
+msgid "COLUMN subcommand multiply specified."
+msgstr ""
+
+#: src/inpt-pgm.c:375
+msgid "expecting file handle name"
+msgstr ""
+
+#: src/inpt-pgm.c:418
+msgid ""
+"REREAD: Column numbers must be positive finite numbers.  Column set to 1."
+msgstr ""
+
+#: src/matrix-data.c:204
+msgid "VARIABLES subcommand multiply specified."
+msgstr ""
+
+#: src/matrix-data.c:219
+msgid "VARNAME_ cannot be explicitly specified on VARIABLES."
+msgstr ""
+
+#: src/matrix-data.c:285
+msgid "in FORMAT subcommand"
+msgstr ""
+
+#: src/matrix-data.c:296
+msgid "SPLIT subcommand multiply specified."
+msgstr ""
+
+#: src/matrix-data.c:303
+msgid "in SPLIT subcommand"
+msgstr ""
+
+#: src/matrix-data.c:312
+msgid "Split variable may not be named ROWTYPE_ or VARNAME_."
+msgstr ""
+
+#: src/matrix-data.c:348
+#, c-format
+msgid "Split variable %s is already another type."
+msgstr ""
+
+#: src/matrix-data.c:363
+msgid "FACTORS subcommand multiply specified."
+msgstr ""
+
+#: src/matrix-data.c:378
+#, c-format
+msgid "Factor variable %s is already another type."
+msgstr ""
+
+#: src/matrix-data.c:393
+msgid "CELLS subcommand multiply specified."
+msgstr ""
+
+#: src/matrix-data.c:399 src/matrix-data.c:418
+msgid "expecting positive integer"
+msgstr ""
+
+#: src/matrix-data.c:412
+msgid "N subcommand multiply specified."
+msgstr ""
+
+#: src/matrix-data.c:433
+msgid "CONTENTS subcommand multiply specified."
+msgstr ""
+
+#: src/matrix-data.c:453
+msgid "Nested parentheses not allowed."
+msgstr ""
+
+#: src/matrix-data.c:463
+msgid "Mismatched right parenthesis (`(')."
+msgstr ""
+
+#: src/matrix-data.c:468
+msgid "Empty parentheses not allowed."
+msgstr ""
+
+#: src/matrix-data.c:481 src/matrix-data.c:489
+msgid "in CONTENTS subcommand"
+msgstr ""
+
+#: src/matrix-data.c:496
+#, c-format
+msgid "Content multiply specified for %s."
+msgstr ""
+
+#: src/matrix-data.c:513
+msgid "Missing right parenthesis."
+msgstr ""
+
+#: src/matrix-data.c:533
+msgid "Missing VARIABLES subcommand."
+msgstr ""
+
+#: src/matrix-data.c:539
+msgid ""
+"CONTENTS subcommand not specified: assuming file contains only CORR matrix."
+msgstr ""
+
+#: src/matrix-data.c:549
+msgid ""
+"Missing CELLS subcommand.  CELLS is required when ROWTYPE_ is not given in "
+"the data and factors are present."
+msgstr ""
+
+#: src/matrix-data.c:557
+msgid "Split file values must be present in the data when ROWTYPE_ is present."
+msgstr ""
+
+#: src/matrix-data.c:613
+msgid "No continuous variables specified."
+msgstr ""
+
+#: src/matrix-data.c:1024
+#, c-format
+msgid "End of line expected %s while reading %s."
+msgstr ""
+
+#: src/matrix-data.c:1210
+#, c-format
+msgid "expecting value for %s %s"
+msgstr ""
+
+#: src/matrix-data.c:1360
+#, c-format
+msgid "Syntax error expecting SPLIT FILE value %s."
+msgstr ""
+
+#: src/matrix-data.c:1369
+#, c-format
+msgid "Expecting value %g for %s."
+msgstr ""
+
+#: src/matrix-data.c:1408 src/matrix-data.c:1821
+#, c-format
+msgid "Syntax error expecting factor value %s."
+msgstr ""
+
+#: src/matrix-data.c:1417
+#, c-format
+msgid "Syntax error expecting value %g for %s %s."
+msgstr ""
+
+#: src/matrix-data.c:1624
+#, c-format
+msgid "Syntax error %s expecting SPLIT FILE value."
+msgstr ""
+
+#: src/matrix-data.c:1735
+#, c-format
+msgid ""
+"Expected %d lines of data for %s content; actually saw %d lines.  No data "
+"will be output for this content."
+msgstr ""
+
+#: src/matrix-data.c:1766
+#, c-format
+msgid "Multiply specified ROWTYPE_ %s."
+msgstr ""
+
+#: src/matrix-data.c:1771
+#, c-format
+msgid "Syntax error %s expecting ROWTYPE_ string."
+msgstr ""
+
+#: src/matrix-data.c:1790
+#, c-format
+msgid "Syntax error %s."
+msgstr ""
+
+#: src/matrix-data.c:1936
+#, c-format
+msgid "Duplicate specification for %s."
+msgstr ""
+
+#: src/matrix-data.c:1948
+#, c-format
+msgid "Too many rows of matrix data for %s."
+msgstr ""
+
+#: src/matrix-data.c:1993
+#, c-format
+msgid "Syntax error expecting value for %s %s."
+msgstr ""
+
+#: src/pfm-read.c:107
+#, c-format
+msgid "portable file %s corrupt at offset %ld: "
+msgstr ""
+
+#: src/pfm-read.c:126 src/pfm-write.c:499
+#, c-format
+msgid "%s: Closing portable file: %s."
+msgstr ""
+
+#: src/lexer.c:948 src/pfm-read.c:150 src/repeat.c:227
+msgid "Unexpected end of file."
+msgstr ""
+
+#: src/pfm-read.c:158
+msgid "Bad line end."
+msgstr ""
+
+#: src/pfm-read.c:239
+#, c-format
+msgid "Cannot read file %s as portable file: already opened for %s."
+msgstr ""
+
+#: src/pfm-read.c:245
+#, c-format
+msgid "%s: Opening portable-file handle %s for reading."
+msgstr ""
+
+#: src/pfm-read.c:253
+#, c-format
+msgid ""
+"An error occurred while opening \"%s\" for reading as a portable file: %s."
+msgstr ""
+
+#. F
+#: src/pfm-read.c:287
+msgid "Data record expected."
+msgstr ""
+
+#: src/pfm-read.c:289
+msgid "Read portable-file dictionary successfully."
+msgstr ""
+
+#. Come here on unsuccessful completion.
+#: src/pfm-read.c:298
+msgid "Error reading portable-file dictionary."
+msgstr ""
+
+#. /
+#: src/pfm-read.c:396
+msgid "Missing numeric terminator."
+msgstr ""
+
+#: src/pfm-read.c:433
+msgid "Bad integer format."
+msgstr ""
+
+#: src/pfm-read.c:463
+#, c-format
+msgid "Bad string length %d."
+msgstr ""
+
+#: src/pfm-read.c:562
+#, c-format
+msgid "Bad date string length %d."
+msgstr ""
+
+#. 0
+#. 9
+#: src/pfm-read.c:566
+msgid "Bad character in date."
+msgstr ""
+
+#: src/pfm-read.c:586
+#, c-format
+msgid "Bad time string length %d."
+msgstr ""
+
+#. 0
+#. 9
+#: src/pfm-read.c:590
+msgid "Bad character in time."
+msgstr ""
+
+#: src/pfm-read.c:640
+#, c-format
+msgid "%s: Bad format specifier byte %d."
+msgstr ""
+
+#: src/pfm-read.c:649 src/sfm-read.c:993 src/sfm-read.c:1003
+#, c-format
+msgid "%s: Bad format specifier byte (%d)."
+msgstr ""
+
+#: src/pfm-read.c:651
+#, c-format
+msgid "%s variable %s has %s format specifier %s."
+msgstr ""
+
+#: src/pfm-read.c:652 src/print.c:631 src/sfm-read.c:1007
+msgid "String"
+msgstr ""
+
+#: src/pfm-read.c:652 src/print.c:631 src/sfm-read.c:1007
+msgid "Numeric"
+msgstr ""
+
+#. 4
+#: src/pfm-read.c:690
+msgid "Expected variable count record."
+msgstr ""
+
+#: src/pfm-read.c:694
+#, c-format
+msgid "Invalid number of variables %d."
+msgstr ""
+
+#: src/pfm-read.c:704
+#, c-format
+msgid "Unexpected flag value %d."
+msgstr ""
+
+#. 7
+#: src/pfm-read.c:728
+msgid "Expected variable record."
+msgstr ""
+
+#: src/pfm-read.c:734
+#, c-format
+msgid "Invalid variable width %d."
+msgstr ""
+
+#: src/pfm-read.c:752
+#, c-format
+msgid "position %d: Variable name has %u characters."
+msgstr ""
+
+#. A
+#. Z
+#. @
+#: src/pfm-read.c:756
+#, c-format
+msgid "position %d: Variable name begins with invalid character."
+msgstr ""
+
+#: src/pfm-read.c:760
+#, c-format
+msgid "position %d: Variable name begins with lowercase letter %c."
+msgstr ""
+
+#: src/pfm-read.c:773
+#, c-format
+msgid "position %d: Variable name character %d is lowercase letter %c."
+msgstr ""
+
+#: src/pfm-read.c:783
+#, c-format
+msgid "position %d: character `\\%03o' is not valid in a variable name."
+msgstr ""
+
+#: src/pfm-read.c:794
+#, c-format
+msgid "Duplicate variable name %s."
+msgstr ""
+
+#: src/pfm-read.c:838
+#, c-format
+msgid "Bad missing values for %s."
+msgstr ""
+
+#: src/pfm-read.c:860
+#, c-format
+msgid "Weighting variable %s not present in dictionary."
+msgstr ""
+
+#: src/pfm-read.c:928
+#, c-format
+msgid "Unknown variable %s while parsing value labels."
+msgstr ""
+
+#: src/pfm-read.c:931
+#, c-format
+msgid ""
+"Cannot assign value labels to %s and %s, which have different variable types "
+"or widths."
+msgstr ""
+
+#: src/pfm-read.c:978
+#, c-format
+msgid "Duplicate label for value %g for variable %s."
+msgstr ""
+
+#: src/pfm-read.c:981
+#, c-format
+msgid "Duplicate label for value `%.*s' for variable %s."
+msgstr ""
+
+#: src/pfm-read.c:1053
+msgid "End of file midway through case."
+msgstr ""
+
+#: src/pfm-read.c:1063
+msgid "reading as a portable file"
+msgstr ""
+
+#: src/pfm-write.c:72
+#, c-format
+msgid "Cannot write file %s as portable file: already opened for %s."
+msgstr ""
+
+#: src/pfm-write.c:78
+#, c-format
+msgid "%s: Opening portable-file handle %s for writing."
+msgstr ""
+
+#: src/pfm-write.c:88
+#, c-format
+msgid ""
+"An error occurred while opening \"%s\" for writing as a portable file: %s."
+msgstr ""
+
+#: src/pfm-write.c:124
+msgid "Wrote portable-file header successfully."
+msgstr ""
+
+#: src/pfm-write.c:129
+msgid "Error writing portable-file header."
+msgstr ""
+
+#: src/pfm-write.c:169
+#, c-format
+msgid "%s: Writing portable file: %s."
+msgstr ""
+
+#: src/pfm-write.c:508
+msgid "writing as a portable file"
+msgstr ""
+
+#: src/sfm-read.c:188
+msgid "corrupt system file: "
+msgstr ""
+
+#: src/sfm-read.c:204 src/sfm-write.c:744
+#, c-format
+msgid "%s: Closing system file: %s."
+msgstr ""
+
+#: src/sfm-read.c:277
+#, c-format
+msgid "Cannot read file %s as system file: already opened for %s."
+msgstr ""
+
+#: src/sfm-read.c:282
+#, c-format
+msgid "%s: Opening system-file handle %s for reading."
+msgstr ""
+
+#: src/sfm-read.c:290
+#, c-format
+msgid ""
+"An error occurred while opening \"%s\" for reading as a system file: %s."
+msgstr ""
+
+#: src/sfm-read.c:324
+#, c-format
+msgid ""
+"%s: Weighting variable may not be a continuation of a long string variable."
+msgstr ""
+
+#: src/sfm-read.c:327
+#, c-format
+msgid "%s: Weighting variable may not be a string variable."
+msgstr ""
+
+#: src/sfm-read.c:352
+#, c-format
+msgid ""
+"%s: Orphaned variable index record (type 4).  Type 4 records must always "
+"immediately follow type 3 records."
+msgstr ""
+
+#: src/sfm-read.c:407
+#, c-format
+msgid "%s: Unrecognized record type 7, subtype %d encountered in system file."
+msgstr ""
+
+#: src/sfm-read.c:431
+#, c-format
+msgid "%s: Unrecognized record type %d."
+msgstr ""
+
+#. Come here on successful completion.
+#: src/sfm-read.c:437
+msgid "Read system-file dictionary successfully."
+msgstr ""
+
+#. Come here on unsuccessful completion.
+#: src/sfm-read.c:447
+msgid "Error reading system-file header."
+msgstr ""
+
+#: src/sfm-read.c:471
+#, c-format
+msgid ""
+"%s: Bad size (%d) or count (%d) field on record type 7, subtype 3.\tExpected "
+"size %d, count 8."
+msgstr ""
+
+#: src/sfm-read.c:485
+#, c-format
+msgid ""
+"%s: Floating-point representation in system file is not IEEE-754.  PSPP "
+"cannot convert between floating-point formats."
+msgstr ""
+
+#: src/sfm-read.c:506
+#, c-format
+msgid ""
+"%s: File-indicated endianness (%s) does not match endianness intuited from "
+"file header (%s)."
+msgstr ""
+
+#: src/sfm-read.c:508 src/sfm-read.c:509
+msgid "big-endian"
+msgstr ""
+
+#: src/sfm-read.c:508 src/sfm-read.c:509
+msgid "little-endian"
+msgstr ""
+
+#: src/sfm-read.c:510
+msgid "unknown"
+msgstr ""
+
+#: src/sfm-read.c:514
+#, c-format
+msgid "%s: File-indicated character representation code (%s) is not ASCII."
+msgstr ""
+
+#: src/sfm-read.c:516
+msgid "DEC Kanji"
+msgstr ""
+
+#: src/data-out.c:145 src/sfm-read.c:516 src/sysfile-info.c:114
+msgid "Unknown"
+msgstr ""
+
+#: src/sfm-read.c:535
+#, c-format
+msgid ""
+"%s: Bad size (%d) or count (%d) field on record type 7, subtype 4.\tExpected "
+"size %d, count 8."
+msgstr ""
+
+#: src/sfm-read.c:550
+#, c-format
+msgid ""
+"%s: File-indicated value is different from internal value for at least one "
+"of the three system values.  SYSMIS: indicated %g, expected %g; HIGHEST: %g, "
+"%g; LOWEST: %g, %g."
+msgstr ""
+
+#: src/sfm-read.c:594
+#, c-format
+msgid ""
+"%s: Bad magic.  Proper system files begin with the four characters `$FL2'. "
+"This file will not be read."
+msgstr ""
+
+#: src/sfm-read.c:637
+#, c-format
+msgid ""
+"%s: File layout code has unexpected value %d.  Value should be 2, in "
+"big-endian or little-endian format."
+msgstr ""
+
+#: src/sfm-read.c:653
+#, c-format
+msgid "%s: Number of elements per case (%d) is not between 1 and %d."
+msgstr ""
+
+#: src/sfm-read.c:660
+#, c-format
+msgid ""
+"%s: Index of weighting variable (%d) is not between 0 and number of elements "
+"per case (%d)."
+msgstr ""
+
+#: src/sfm-read.c:666
+#, c-format
+msgid "%s: Number of cases in file (%ld) is not between -1 and %d."
+msgstr ""
+
+#: src/sfm-read.c:671
+#, c-format
+msgid "%s: Compression bias (%g) is not the usual value of 100."
+msgstr ""
+
+#: src/sfm-read.c:767
+#, c-format
+msgid "%s: position %d: Bad record type (%d); the expected value was 2."
+msgstr ""
+
+#: src/sfm-read.c:776
+#, c-format
+msgid ""
+"%s: position %d: String variable does not have proper number of continuation "
+"records."
+msgstr ""
+
+#: src/sfm-read.c:784
+#, c-format
+msgid "%s: position %d: Superfluous long string continuation record."
+msgstr ""
+
+#: src/sfm-read.c:789
+#, c-format
+msgid "%s: position %d: Bad variable type code %d."
+msgstr ""
+
+#: src/sfm-read.c:792
+#, c-format
+msgid "%s: position %d: Variable label indicator field is not 0 or 1."
+msgstr ""
+
+#: src/sfm-read.c:796
+#, c-format
+msgid ""
+"%s: position %d: Missing value indicator field is not -3, -2, 0, 1, 2, or 3."
+msgstr ""
+
+#: src/sfm-read.c:809
+#, c-format
+msgid "%s: position %d: Variable name begins with invalid character."
+msgstr ""
+
+#: src/sfm-read.c:812
+#, c-format
+msgid "%s: position %d: Variable name begins with lowercase letter %c."
+msgstr ""
+
+#: src/sfm-read.c:815
+#, c-format
+msgid ""
+"%s: position %d: Variable name begins with octothorpe (`#').  Scratch "
+"variables should not appear in system files."
+msgstr ""
+
+#: src/sfm-read.c:829
+#, c-format
+msgid "%s: position %d: Variable name character %d is lowercase letter %c."
+msgstr ""
+
+#: src/sfm-read.c:837
+#, c-format
+msgid ""
+"%s: position %d: character `\\%03o' (%c) is not valid in a variable name."
+msgstr ""
+
+#: src/sfm-read.c:877
+#, c-format
+msgid "%s: Variable %s indicates variable label of invalid length %d."
+msgstr ""
+
+#: src/sfm-read.c:893
+#, c-format
+msgid "%s: Long string variable %s may not have missing values."
+msgstr ""
+
+#: src/sfm-read.c:917
+#, c-format
+msgid ""
+"%s: String variable %s may not have missing values specified as a range."
+msgstr ""
+
+#: src/sfm-read.c:954
+#, c-format
+msgid "%s: Long string continuation records omitted at end of dictionary."
+msgstr ""
+
+#: src/sfm-read.c:957
+#, c-format
+msgid ""
+"%s: System file header indicates %d variable positions but %d were read from "
+"file."
+msgstr ""
+
+#: src/sfm-read.c:966
+#, c-format
+msgid "%s: Duplicate variable name `%s' within system file."
+msgstr ""
+
+#: src/sfm-read.c:1006
+#, c-format
+msgid "%s: %s variable %s has %s format specifier %s."
+msgstr ""
+
+#: src/sfm-read.c:1085
+#, c-format
+msgid ""
+"%s: Variable index record (type 4) does not immediately follow value label "
+"record (type 3) as it ought."
+msgstr ""
+
+#: src/sfm-read.c:1095
+#, c-format
+msgid ""
+"%s: Number of variables associated with a value label (%d) is not between 1 "
+"and the number of variables (%d)."
+msgstr ""
+
+#: src/sfm-read.c:1113
+#, c-format
+msgid ""
+"%s: Variable index associated with value label (%d) is not between 1 and the "
+"number of values (%d)."
+msgstr ""
+
+#: src/sfm-read.c:1120
+#, c-format
+msgid ""
+"%s: Variable index associated with value label (%d) refers to a continuation "
+"of a string variable, not to an actual variable."
+msgstr ""
+
+#: src/sfm-read.c:1124
+#, c-format
+msgid "%s: Value labels are not allowed on long string variables (%s)."
+msgstr ""
+
+#: src/sfm-read.c:1134
+#, c-format
+msgid ""
+"%s: Variables associated with value label are not all of identical type.  "
+"Variable %s has %s type, but variable %s has %s type."
+msgstr ""
+
+#: src/sfm-read.c:1177
+#, c-format
+msgid "%s: File contains duplicate label for value %g for variable %s."
+msgstr ""
+
+#: src/sfm-read.c:1180
+#, c-format
+msgid "%s: File contains duplicate label for value `%.*s' for variable %s."
+msgstr ""
+
+#: src/sfm-read.c:1220 src/sfm-read.c:1498
+#, c-format
+msgid "%s: Reading system file: %s."
+msgstr ""
+
+#: src/sfm-read.c:1222 src/sfm-read.c:1403 src/sfm-read.c:1444
+#, c-format
+msgid "%s: Unexpected end of file."
+msgstr ""
+
+#: src/sfm-read.c:1239
+#, c-format
+msgid "%s: System file contains multiple type 6 (document) records."
+msgstr ""
+
+#: src/sfm-read.c:1245
+#, c-format
+msgid "%s: Number of document lines (%ld) must be greater than 0."
+msgstr ""
+
+#: src/sfm-read.c:1266
+msgid "dictionary:\n"
+msgstr ""
+
+#. debug_printf (("(indices:%d,%d)", v->index, v->foo));
+#: src/sfm-read.c:1275
+msgid "num"
+msgstr ""
+
+#: src/sfm-read.c:1276
+msgid "str"
+msgstr ""
+
+#. debug_printf (("(get.fv:%d,%d)", v->get.fv, v->get.nv));
+#: src/sfm-read.c:1280
+msgid "left"
+msgstr ""
+
+#: src/sfm-read.c:1280
+msgid "right"
+msgstr ""
+
+#: src/sfm-read.c:1286
+msgid "none"
+msgstr ""
+
+#: src/sfm-read.c:1290
+msgid "one"
+msgstr ""
+
+#: src/sfm-read.c:1294
+msgid "two"
+msgstr ""
+
+#: src/sfm-read.c:1298
+msgid "three"
+msgstr ""
+
+#: src/descript.q:166 src/sfm-read.c:1302
+msgid "range"
+msgstr ""
+
+#: src/sfm-read.c:1306
+msgid "low"
+msgstr ""
+
+#: src/sfm-read.c:1310
+msgid "high"
+msgstr ""
+
+#: src/sfm-read.c:1314
+msgid "range+1"
+msgstr ""
+
+#: src/sfm-read.c:1318
+msgid "low+1"
+msgstr ""
+
+#: src/sfm-read.c:1322
+msgid "high+1"
+msgstr ""
+
+#: src/sfm-read.c:1356
+#, c-format
+msgid "%s: Error reading file: %s."
+msgstr ""
+
+#: src/sfm-read.c:1394
+#, c-format
+msgid "%s: Compressed data is corrupted.  Data ends partway through a case."
+msgstr ""
+
+#: src/sfm-read.c:1500
+#, c-format
+msgid "%s: Partial record at end of system file."
+msgstr ""
+
+#: src/sfm-read.c:1538
+msgid "reading as a system file"
+msgstr ""
+
+#: src/sfm-write.c:114
+#, c-format
+msgid "Cannot write file %s as system file: already opened for %s."
+msgstr ""
+
+#: src/sfm-write.c:119
+#, c-format
+msgid "%s: Opening system-file handle %s for writing."
+msgstr ""
+
+#: src/sfm-write.c:129
+#, c-format
+msgid ""
+"An error occurred while opening \"%s\" for writing as a system file: %s."
+msgstr ""
+
+#: src/sfm-write.c:182
+msgid "Wrote system-file header successfully."
+msgstr ""
+
+#: src/sfm-write.c:187
+msgid "Error writing system-file header."
+msgstr ""
+
+#: src/sfm-write.c:608
+#, c-format
+msgid "%s: Writing system file: %s."
+msgstr ""
+
+#: src/sfm-write.c:754
+msgid "writing as a system file"
+msgstr ""
+
+#: src/sysfile-info.c:96
+msgid "File:"
+msgstr ""
+
+#: src/sysfile-info.c:98
+msgid "Label:"
+msgstr ""
+
+#: src/sysfile-info.c:100
+msgid "No label."
+msgstr ""
+
+#: src/sysfile-info.c:101
+msgid "Created:"
+msgstr ""
+
+#: src/sysfile-info.c:104
+msgid "Endian:"
+msgstr ""
+
+#: src/sysfile-info.c:106
+msgid "Big."
+msgstr ""
+
+#: src/sysfile-info.c:107
+msgid "Little."
+msgstr ""
+
+#: src/sysfile-info.c:108
+msgid "<internal error>"
+msgstr ""
+
+#: src/sysfile-info.c:109
+msgid "Variables:"
+msgstr ""
+
+#: src/sysfile-info.c:112
+msgid "Cases:"
+msgstr ""
+
+#: src/sysfile-info.c:115
+msgid "Type:"
+msgstr ""
+
+#: src/sysfile-info.c:116
+msgid "System File."
+msgstr ""
+
+#: src/sysfile-info.c:117
+msgid "Weight:"
+msgstr ""
+
+#: src/sysfile-info.c:119
+msgid "Not weighted."
+msgstr ""
+
+#: src/sysfile-info.c:120
+msgid "Mode:"
+msgstr ""
+
+#: src/sysfile-info.c:122
+#, c-format
+msgid "Compression %s."
+msgstr ""
+
+#: src/sysfile-info.c:122
+msgid "on"
+msgstr ""
+
+#: src/sysfile-info.c:122
+msgid "off"
+msgstr ""
+
+#: src/sysfile-info.c:131 src/sysfile-info.c:376
+msgid "Description"
+msgstr ""
+
+#: src/sysfile-info.c:132 src/sysfile-info.c:373
+msgid "Position"
+msgstr ""
+
+#: src/sysfile-info.c:192
+msgid "The active file does not have a file label."
+msgstr ""
+
+#: src/sysfile-info.c:195
+msgid "File label:"
+msgstr ""
+
+#: src/sysfile-info.c:257
+msgid "No variables to display."
+msgstr ""
+
+#: src/sysfile-info.c:282
+msgid "Macros not supported."
+msgstr ""
+
+#: src/sysfile-info.c:290
+msgid "The active file dictionary does not contain any documents."
+msgstr ""
+
+#: src/sysfile-info.c:298
+msgid "Documents in the active file:"
+msgstr ""
+
+#: src/sysfile-info.c:378 src/sysfile-info.c:538 src/vfm.c:1135
+msgid "Label"
+msgstr ""
+
+#: src/sysfile-info.c:450
+#, c-format
+msgid "Format: %s"
+msgstr ""
+
+#: src/sysfile-info.c:457
+#, c-format
+msgid "Print Format: %s"
+msgstr ""
+
+#: src/sysfile-info.c:460
+#, c-format
+msgid "Write Format: %s"
+msgstr ""
+
+#: src/sysfile-info.c:468
+msgid "Missing Values: "
+msgstr ""
+
+#: src/crosstabs.q:1233 src/crosstabs.q:1260 src/crosstabs.q:1280
+#: src/crosstabs.q:1302 src/frequencies.q:895 src/frequencies.q:1012
+#: src/sysfile-info.c:537 src/vfm.c:1134
+msgid "Value"
+msgstr ""
+
+#: src/sysfile-info.c:598
+msgid "No vectors defined."
+msgstr ""
+
+#: src/sysfile-info.c:613
+msgid "Vector"
+msgstr ""
+
+#: src/command.c:161
+#, c-format
+msgid "%s not allowed inside FILE TYPE/END FILE TYPE."
+msgstr ""
+
+#: src/command.c:165
+#, c-format
+msgid "%s not allowed inside FILE TYPE GROUPED/END FILE TYPE."
+msgstr ""
+
+#: src/command.c:168
+msgid "RECORD TYPE must be the first command inside a FILE TYPE structure."
+msgstr ""
+
+#: src/command.c:213
+msgid "This line does not begin with a valid command name."
+msgstr ""
+
+#: src/command.c:223
+#, c-format
+msgid "%s is not yet implemented."
+msgstr ""
+
+#: src/command.c:241
+#, c-format
+msgid ""
+"%s is not allowed (1) before a command to specify the input program, such as "
+"DATA LIST, (2) between FILE TYPE and END FILE TYPE, (3) between INPUT "
+"PROGRAM and END INPUT PROGRAM."
+msgstr ""
+
+#: src/command.c:245
+#, c-format
+msgid "%s is not allowed within an input program."
+msgstr ""
+
+#: src/command.c:246 src/command.c:247
+#, c-format
+msgid "%s is only allowed within an input program."
+msgstr ""
+
+#: src/command.c:256
+#, c-format
+msgid "%s command beginning\n"
+msgstr ""
+
+#: src/command.c:292
+#, c-format
+msgid ""
+"%s command completed\n"
+"\n"
+msgstr ""
+
+#: src/command.c:307
+msgid "The identifier(s) specified do not form a valid command name:"
+msgstr ""
+
+#: src/command.c:310
+msgid "The identifier(s) specified do not form a complete command name:"
+msgstr ""
+
+#: src/command.c:434
+msgid ""
+"This command is not accepted in a syntax file.  Instead, use FINISH to "
+"terminate a syntax file."
+msgstr ""
+
+#: src/command.c:452
+msgid ""
+"This command is not executed in interactive mode.  Instead, PSPP drops down "
+"to the command prompt.  Use EXIT if you really want to quit."
+msgstr ""
+
+#: src/command.c:543
+msgid "The sentinel may not be the empty string."
+msgstr ""
+
+#: src/command.c:601 src/command.c:732
+msgid "This command not allowed when the SAFER option is set."
+msgstr ""
+
+#: src/command.c:614
+#, c-format
+msgid "Error removing `%s': %s."
+msgstr ""
+
+#: src/command.c:664
+#, c-format
+msgid "Couldn't fork: %s."
+msgstr ""
+
+#: src/command.c:705
+#, c-format
+msgid "Error executing command: %s."
+msgstr ""
+
+#: src/command.c:755
+msgid "No operating system support for this command."
+msgstr ""
+
+#: src/command.c:784
+msgid "This command is not valid in a syntax file."
+msgstr ""
+
+#: src/getline.c:160
+#, c-format
+msgid "Can't find `%s' in include file search path."
+msgstr ""
+
+#: src/getline.c:315
+#, c-format
+msgid "%s: Opening as syntax file."
+msgstr ""
+
+#: src/getline.c:320
+#, c-format
+msgid "Opening `%s': %s."
+msgstr ""
+
+#: src/getline.c:329 src/html.c:334 src/postscript.c:1480
+#, c-format
+msgid "Reading `%s': %s."
+msgstr ""
+
+#: src/getline.c:387
+#, c-format
+msgid "Closing `%s': %s."
+msgstr ""
+
+#: src/lexer.c:216
+#, c-format
+msgid "%s does not form a valid number."
+msgstr ""
+
+#: src/lexer.c:334
+#, c-format
+msgid "Bad character in input: `%c'."
+msgstr ""
+
+#: src/lexer.c:336
+#, c-format
+msgid "Bad character in input: `\\%o'."
+msgstr ""
+
+#: src/lexer.c:357
+msgid "Syntax error at end of file."
+msgstr ""
+
+#: src/lexer.c:367
+#, c-format
+msgid "Syntax error %s at `%s'."
+msgstr ""
+
+#: src/lexer.c:370
+#, c-format
+msgid "Syntax error at `%s'."
+msgstr ""
+
+#: src/lexer.c:473
+#, c-format
+msgid "expecting `%s'"
+msgstr ""
+
+#: src/lexer.c:490
+#, c-format
+msgid "expecting %s"
+msgstr ""
+
+#: src/lexer.c:504
+msgid "expecting string"
+msgstr ""
+
+#: src/lexer.c:518
+msgid "expecting integer"
+msgstr ""
+
+#: src/lexer.c:532
+msgid "expecting number"
+msgstr ""
+
+#: src/lexer.c:546
+msgid "expecting identifier"
+msgstr ""
+
+#: src/lexer.c:682
+msgid "The rest of this command has been discarded."
+msgstr ""
+
+#: src/lexer.c:822 src/print.c:1193
+msgid "<ERROR>"
+msgstr ""
+
+#: src/lexer.c:974
+msgid "binary"
+msgstr ""
+
+#: src/lexer.c:974
+msgid "octal"
+msgstr ""
+
+#: src/lexer.c:974
+msgid "hex"
+msgstr ""
+
+#: src/lexer.c:988
+#, c-format
+msgid "String of %s digits has %d characters, which is not a multiple of %d."
+msgstr ""
+
+#: src/lexer.c:1017
+#, c-format
+msgid "`%c' is not a valid %s digit."
+msgstr ""
+
+#: src/lexer.c:1048
+msgid "Unterminated string constant."
+msgstr ""
+
+#: src/lexer.c:1120
+#, c-format
+msgid "String exceeds 255 characters in length (%d characters)."
+msgstr ""
+
+#: src/lexer.c:1135
+msgid ""
+"Sorry, literal strings may not contain null characters.  Replacing with "
+"spaces."
+msgstr ""
+
+#: src/cmdline.c:111
+msgid "-f not yet implemented\n"
+msgstr ""
+
+#: src/cmdline.c:129
+msgid "-n not yet implemented\n"
+msgstr ""
+
+#: src/cmdline.c:140
+msgid "-p not yet implemented\n"
+msgstr ""
+
+#: src/cmdline.c:153
+msgid ""
+"\n"
+"Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.\n"
+"This is free software; see the source for copying conditions.  There is NO\n"
+"WARRANTY; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
+"\n"
+"Written by Ben Pfaff <blp@gnu.org>."
+msgstr ""
+
+#: src/cmdline.c:213
+#, c-format
+msgid ""
+"PSPP, a program for statistical analysis of sample data.\n"
+"\n"
+"Usage: %s [OPTION]... FILE...\n"
+"\n"
+"If a long option shows an argument as mandatory, then it is mandatory\n"
+"for the equivalent short option also.  Similarly for optional arguments.\n"
+"\n"
+"Configuration:\n"
+"  -B, --config-dir=DIR      set configuration directory to DIR\n"
+"  -o, --device=DEVICE       select output driver DEVICE and disable "
+"defaults\n"
+"  -d, --define=VAR[=VALUE]  set environment variable VAR to VALUE, or empty\n"
+"  -u, --undef=VAR           undefine environment variable VAR\n"
+"\n"
+"Input and output:\n"
+"  -f, --out-file=FILE       send output to FILE (overwritten)\n"
+"  -p, --pipe                read script from stdin, send output to stdout\n"
+"  -I-, --no-include         clear include path\n"
+"  -I, --include=DIR         append DIR to include path\n"
+"  -c, --command=COMMAND     execute COMMAND before .pspp/rc at startup\n"
+"\n"
+"Language modifiers:\n"
+"  -i, --interactive         interpret scripts in interactive mode\n"
+"  -n, --edit                just check syntax; don't actually run the code\n"
+"  -r, --no-statrc           disable execution of .pspp/rc at startup\n"
+"  -s, --safer               don't allow some unsafe operations\n"
+"\n"
+"Informative output:\n"
+"  -h, --help                print this help, then exit\n"
+"  -l, --list                print a list of known driver classes, then exit\n"
+"  -V, --version             show PSPP version, then exit\n"
+"  -v, --verbose             increments verbosity level\n"
+"\n"
+"Non-option arguments:\n"
+" FILE1 FILE2                run FILE1, clear the dictionary, run FILE2\n"
+" FILE1 + FILE2              run FILE1 then FILE2 without clearing "
+"dictionary\n"
+" KEY=VALUE                  overrides macros in output initialization file\n"
+"\n"
+msgstr ""
+
+#: src/cmdline.c:246
+msgid ""
+"\n"
+"Report bugs to <bug-gnu-pspp@gnu.org>.\n"
+msgstr ""
+
+#: src/error.c:130
+msgid "Terminating NOW due to a fatal error!"
+msgstr ""
+
+#: src/error.c:207
+msgid "Terminating execution of syntax file due to error."
+msgstr ""
+
+#: src/error.c:209
+#, c-format
+msgid "Errors (%d) exceeds limit (%d)."
+msgstr ""
+
+#: src/error.c:212
+#, c-format
+msgid "Warnings (%d) exceed limit (%d)."
+msgstr ""
+
+#: src/error.c:279
+msgid "fatal"
+msgstr ""
+
+#. SE
+#: src/error.c:282 src/error.c:289 src/error.c:292
+msgid "warning"
+msgstr ""
+
+#. SW
+#: src/error.c:283 src/error.c:293
+msgid "note"
+msgstr ""
+
+#. SM
+#: src/error.c:285 src/error.c:286
+msgid "installation error"
+msgstr ""
+
+#: src/error.c:509
+msgid ""
+"\n"
+"\t*********************\n"
+"\t* INDUCING SEGFAULT *\n"
+"\t*********************\n"
+msgstr ""
+
+#: src/glob.c:183
+msgid ""
+"Your machine does not appear to be either big- or little-endian.  At the "
+"moment, PSPP only supports machines of these standard endiannesses.  If you "
+"want to hack in others, contact the author."
+msgstr ""
+
+#: src/glob.c:267
+msgid "data> "
+msgstr ""
+
+#: src/glob.c:328
+msgid "Specify a terminal type with `setenv TERM <yourtype>'."
+msgstr ""
+
+#: src/glob.c:339
+msgid "Could not access the termcap data base."
+msgstr ""
+
+#: src/glob.c:341
+#, c-format
+msgid "Terminal type `%s' is not defined."
+msgstr ""
+
+#: src/glob.c:342
+msgid "Assuming screen of size 79x25."
+msgstr ""
+
+#: src/glob.c:377
+msgid "Jan"
+msgstr ""
+
+#: src/glob.c:377
+msgid "Feb"
+msgstr ""
+
+#: src/glob.c:377
+msgid "Mar"
+msgstr ""
+
+#: src/glob.c:377
+msgid "Apr"
+msgstr ""
+
+#: src/glob.c:377
+msgid "May"
+msgstr ""
+
+#: src/glob.c:377
+msgid "Jun"
+msgstr ""
+
+#: src/glob.c:378
+msgid "Jul"
+msgstr ""
+
+#: src/glob.c:378
+msgid "Aug"
+msgstr ""
+
+#: src/glob.c:378
+msgid "Sep"
+msgstr ""
+
+#: src/glob.c:378
+msgid "Oct"
+msgstr ""
+
+#: src/glob.c:378
+msgid "Nov"
+msgstr ""
+
+#: src/glob.c:378
+msgid "Dec"
+msgstr ""
+
+#: src/main.c:65
+msgid "Error initializing output drivers."
+msgstr ""
+
+#: src/main.c:123
+msgid "This command not executed."
+msgstr ""
+
+#: src/main.c:127
+msgid ""
+"Skipping the rest of this command.  Part of this command may have been "
+"executed."
+msgstr ""
+
+#: src/main.c:132
+msgid ""
+"Skipping the rest of this command.  This command was fully executed up to "
+"this point."
+msgstr ""
+
+#: src/main.c:137
+msgid ""
+"Trailing garbage was encountered following this command.  The command was "
+"fully executed to this point."
+msgstr ""
+
+#: src/ascii.c:216
+#, c-format
+msgid "ASCII driver initializing as `%s'..."
+msgstr ""
+
+#: src/ascii.c:273
+#, c-format
+msgid ""
+"ascii driver: Area of page excluding margins and headers must be at least 59 "
+"characters wide by 15 lines long.  Page as configured is only %d characters "
+"by %d lines."
+msgstr ""
+
+#: src/ascii.c:378 src/html.c:102 src/postscript.c:474
+#, c-format
+msgid "%s: Initialization complete."
+msgstr ""
+
+#: src/ascii.c:389 src/html.c:114 src/postscript.c:487
+#, c-format
+msgid "%s: Beginning closing..."
+msgstr ""
+
+#: src/ascii.c:399 src/html.c:119 src/postscript.c:506
+#, c-format
+msgid "%s: Finished closing."
+msgstr ""
+
+#: src/ascii.c:460
+#, c-format
+msgid ""
+"Bad index value for `box' key: syntax is box[INDEX], 0 <= INDEX < %d "
+"decimal, with INDEX expressed in base 4."
+msgstr ""
+
+#: src/ascii.c:466
+#, c-format
+msgid "Duplicate value for key `%s'."
+msgstr ""
+
+#: src/ascii.c:476
+#, c-format
+msgid "Unknown configuration parameter `%s' for ascii device driver."
+msgstr ""
+
+#: src/ascii.c:489
+#, c-format
+msgid ""
+"Unknown character set `%s'.  Valid character sets are `ascii' and `latin1'."
+msgstr ""
+
+#: src/ascii.c:498
+#, c-format
+msgid ""
+"Unknown overstrike style `%s'.  Valid overstrike styles are `single' and "
+"`line'."
+msgstr ""
+
+#: src/ascii.c:507
+#, c-format
+msgid ""
+"Unknown carriage return style `%s'.  Valid carriage return styles are `cr' "
+"and `bs'."
+msgstr ""
+
+#: src/ascii.c:519 src/postscript.c:695
+#, c-format
+msgid "Positive integer required as value for `%s'."
+msgstr ""
+
+#: src/ascii.c:550
+#, c-format
+msgid "Zero or positive integer required as value for `%s'."
+msgstr ""
+
+#: src/ascii.c:620 src/postscript.c:654
+#, c-format
+msgid "Boolean value expected for %s."
+msgstr ""
+
+#: src/ascii.c:649 src/ascii.c:664 src/ascii.c:681
+#, c-format
+msgid "ASCII output driver: %s: %s"
+msgstr ""
+
+#: src/ascii.c:750
+#, c-format
+msgid "ascii_line_horz: bad hline (%d,%d),%d out of (%d,%d)\n"
+msgstr ""
+
+#: src/ascii.c:784
+#, c-format
+msgid "ascii_line_vert: bad vline %d,(%d,%d) out of (%d,%d)\n"
+msgstr ""
+
+#: src/ascii.c:814
+#, c-format
+msgid "ascii_line_intersection: bad intsct (%d,%d) out of (%d,%d)\n"
+msgstr ""
+
+#: src/ascii.c:976
+#, c-format
+msgid "%s: horiz=%d, vert=%d\n"
+msgstr ""
+
+#: src/ascii.c:1148
+#, c-format
+msgid "Writing `%s': %s"
+msgstr ""
+
+#: src/ascii.c:1542 src/postscript.c:2116
+#, c-format
+msgid "%s - Page %d"
+msgstr ""
+
+#: src/data-out.c:253
+msgid ""
+"The N output format cannot be used to output a negative number or the "
+"system-missing value."
+msgstr ""
+
+#: src/data-out.c:363
+msgid ""
+"Quality of zoned decimal (Z) output format code is suspect.  Check your "
+"results, report bugs to author."
+msgstr ""
+
+#: src/data-out.c:370
+msgid "The system-missing value cannot be output as a zoned decimal number."
+msgstr ""
+
+#: src/data-out.c:383
+#, c-format
+msgid "Number %g too big to fit in field with format Z%d.%d."
+msgstr ""
+
+#: src/data-out.c:777
+#, c-format
+msgid "Time value %g too large in magnitude to convert to alphanumeric time."
+msgstr ""
+
+#: src/data-out.c:830
+#, c-format
+msgid "Weekday index %d does not lie between 1 and 7."
+msgstr ""
+
+#: src/data-out.c:851
+#, c-format
+msgid "Month index %d does not lie between 1 and 12."
+msgstr ""
+
+#: src/data-out.c:963
+#, c-format
+msgid ""
+"Year %d cannot be represented in four digits for output formatting purposes."
+msgstr ""
+
+#: src/groff-font.c:107
+#, c-format
+msgid "%s: Opening Groff font file..."
+msgstr ""
+
+#: src/groff-font.c:162
+msgid "Missing font name."
+msgstr ""
+
+#: src/groff-font.c:172
+msgid "Missing encoding filename."
+msgstr ""
+
+#: src/groff-font.c:185
+msgid "Bad spacewidth value."
+msgstr ""
+
+#: src/groff-font.c:197
+msgid "Bad slant value."
+msgstr ""
+
+#: src/groff-font.c:222
+#, c-format
+msgid "Unknown ligature `%s'."
+msgstr ""
+
+#: src/groff-font.c:257
+msgid "Unexpected end of line reading character set."
+msgstr ""
+
+#: src/groff-font.c:265
+msgid "Can't use ditto mark for first character."
+msgstr ""
+
+#: src/groff-font.c:270
+msgid "Can't ditto into an unnamed character."
+msgstr ""
+
+#: src/groff-font.c:287
+#, c-format
+msgid "Missing metrics for character `%s'."
+msgstr ""
+
+#: src/groff-font.c:296
+#, c-format
+msgid "Missing type for character `%s'."
+msgstr ""
+
+#: src/groff-font.c:305
+#, c-format
+msgid "Missing code for character `%s'."
+msgstr ""
+
+#: src/groff-font.c:324
+msgid "Malformed kernpair."
+msgstr ""
+
+#: src/groff-font.c:331
+msgid "Unexpected end of line reading kernpairs."
+msgstr ""
+
+#: src/groff-font.c:337
+msgid "Bad kern value."
+msgstr ""
+
+#: src/groff-font.c:369
+#, c-format
+msgid "Font read successfully with internal name %s."
+msgstr ""
+
+#: src/groff-font.c:389
+msgid "Error reading font."
+msgstr ""
+
+#: src/groff-font.c:400
+msgid "installation error: Groff font error: "
+msgstr ""
+
+#: src/groff-font.c:425
+#, c-format
+msgid "Bad character \\%3o."
+msgstr ""
+
+#: src/groff-font.c:665
+#, c-format
+msgid "Groff font error: Cannot find \"%s\"."
+msgstr ""
+
+#: src/groff-font.c:730
+#, c-format
+msgid "%s: Opening Groff description file..."
+msgstr ""
+
+#: src/groff-font.c:746
+msgid "Multiple `sizes' declarations."
+msgstr ""
+
+#: src/groff-font.c:763
+msgid "Unexpected end of file.  Missing 0 terminator to `sizes' command?"
+msgstr ""
+
+#: src/groff-font.c:775 src/groff-font.c:782 src/groff-font.c:795
+msgid "Bad argument to `sizes'."
+msgstr ""
+
+#: src/groff-font.c:787
+msgid "Bad range in argument to `sizes'."
+msgstr ""
+
+#: src/groff-font.c:816
+msgid "Family name expected."
+msgstr ""
+
+#: src/groff-font.c:821
+msgid "This command already specified."
+msgstr ""
+
+#: src/groff-font.c:841
+#, c-format
+msgid "%s: Device characteristic already defined."
+msgstr ""
+
+#: src/groff-font.c:847
+#, c-format
+msgid "%s: Invalid numeric format."
+msgstr ""
+
+#: src/groff-font.c:877
+msgid "Missing `res', `unitwidth', and/or `sizes' line(s)."
+msgstr ""
+
+#: src/groff-font.c:903
+msgid "Description file read successfully."
+msgstr ""
+
+#: src/groff-font.c:935
+msgid "Error reading description file."
+msgstr ""
+
+#: src/groff-font.c:991
+msgid "<<fallback>>"
+msgstr ""
+
+#: src/html.c:66
+#, c-format
+msgid "HTML driver initializing as `%s'..."
+msgstr ""
+
+#: src/html.c:154
+#, c-format
+msgid "Unknown configuration parameter `%s' for HTML device driver."
+msgstr ""
+
+#: src/html.c:240
+msgid ""
+"Cannot find HTML prologue.  The use of `-vv' on the command line is "
+"suggested as a debugging aid."
+msgstr ""
+
+#: src/html.c:245
+#, c-format
+msgid "%s: %s: Opening HTML prologue..."
+msgstr ""
+
+#: src/html.c:272 src/html.c:283 src/postscript.c:1372 src/postscript.c:1383
+msgid "nobody"
+msgstr ""
+
+#: src/html.c:279 src/html.c:284 src/postscript.c:1379 src/postscript.c:1384
+msgid "nowhere"
+msgstr ""
+
+#: src/html.c:343
+#, c-format
+msgid "%s: HTML prologue read successfully."
+msgstr ""
+
+#: src/html.c:347
+#, c-format
+msgid "%s: Error reading HTML prologue."
+msgstr ""
+
+#: src/html.c:375
+#, c-format
+msgid "HTML output driver: %s: %s"
+msgstr ""
+
+#: src/html.c:406 src/list.q:277
+#, c-format
+msgid "Cannot open first page on HTML device %s."
+msgstr ""
+
+#: src/output.c:162
+#, c-format
+msgid "Unknown output driver `%s'."
+msgstr ""
+
+#: src/output.c:164
+#, c-format
+msgid "Output driver `%s' referenced but never defined."
+msgstr ""
+
+#: src/output.c:292
+msgid "Cannot find output initialization file.  Use `-vv' to view search path."
+msgstr ""
+
+#: src/output.c:297
+#, c-format
+msgid "%s: Opening device description file..."
+msgstr ""
+
+#: src/output.c:301 src/output.c:1161 src/postscript.c:1114
+#, c-format
+msgid "Opening %s: %s."
+msgstr ""
+
+#: src/output.c:313 src/output.c:1173 src/postscript.c:1131
+#, c-format
+msgid "Reading %s: %s."
+msgstr ""
+
+#: src/output.c:335 src/output.c:487
+msgid "Syntax error."
+msgstr ""
+
+#: src/output.c:345 src/postscript.c:1142
+#, c-format
+msgid "Closing %s: %s."
+msgstr ""
+
+#: src/output.c:350
+msgid "No output drivers are active."
+msgstr ""
+
+#: src/output.c:353
+msgid "Device definition file read successfully."
+msgstr ""
+
+#: src/output.c:355
+msgid "Error reading device definition file."
+msgstr ""
+
+#: src/output.c:459
+msgid ""
+"Driver classes:\n"
+"\t"
+msgstr ""
+
+#: src/output.c:588
+msgid "Syntax error in string constant."
+msgstr ""
+
+#: src/output.c:619
+msgid "Syntax error in options."
+msgstr ""
+
+#: src/output.c:629
+msgid "Syntax error in options (`=' expected)."
+msgstr ""
+
+#: src/output.c:636
+msgid "Syntax error in options (value expected after `=')."
+msgstr ""
+
+#: src/output.c:708
+msgid "Driver name expected."
+msgstr ""
+
+#: src/output.c:729
+msgid "Class name expected."
+msgstr ""
+
+#: src/output.c:738
+#, c-format
+msgid "Unknown output driver class `%s'."
+msgstr ""
+
+#: src/output.c:745
+#, c-format
+msgid "Can't initialize output driver class `%s'."
+msgstr ""
+
+#: src/output.c:752
+#, c-format
+msgid "Can't initialize output driver `%s' of class `%s'."
+msgstr ""
+
+#: src/output.c:774
+#, c-format
+msgid "Unknown device type `%s'."
+msgstr ""
+
+#: src/output.c:786
+#, c-format
+msgid "Can't complete initialization of output driver `%s' of class `%s'."
+msgstr ""
+
+#: src/output.c:833
+#, c-format
+msgid "Can't deinitialize output driver class `%s'."
+msgstr ""
+
+#: src/output.c:906
+#, c-format
+msgid "Trying to find keyword `%s'...\n"
+msgstr ""
+
+#: src/output.c:1023
+#, c-format
+msgid "Unit \"%s\" is unknown in dimension \"%s\"."
+msgstr ""
+
+#: src/output.c:1038
+#, c-format
+msgid "Bad dimension \"%s\"."
+msgstr ""
+
+#: src/output.c:1064
+#, c-format
+msgid "`x' expected in paper size `%s'."
+msgstr ""
+
+#: src/output.c:1074
+#, c-format
+msgid "Trailing garbage `%s' on paper size `%s'."
+msgstr ""
+
+#: src/output.c:1123
+msgid "Paper size name must not be empty."
+msgstr ""
+
+#: src/output.c:1153
+msgid "Cannot find `papersize' configuration file."
+msgstr ""
+
+#: src/output.c:1157
+#, c-format
+msgid "%s: Opening paper size definition file..."
+msgstr ""
+
+#: src/output.c:1200
+msgid "Syntax error in paper size definition."
+msgstr ""
+
+#: src/output.c:1229
+msgid "Paper size definition file read successfully."
+msgstr ""
+
+#: src/output.c:1231
+msgid "Error reading paper size definition file."
+msgstr ""
+
+#: src/output.c:1300
+#, c-format
+msgid "Error closing page on %s device of %s class."
+msgstr ""
+
+#: src/output.c:1304
+#, c-format
+msgid "Error opening page on %s device of %s class."
+msgstr ""
+
+#: src/postscript.c:339
+#, c-format
+msgid "PostScript driver initializing as `%s'..."
+msgstr ""
+
+#: src/postscript.c:463
+#, c-format
+msgid ""
+"PostScript driver: The defined page is not long enough to hold margins and "
+"headers, plus least 15 lines of the default fonts.  In fact, there's only "
+"room for %d lines of each font at the default size of %d.%03d points."
+msgstr ""
+
+#: src/postscript.c:592
+#, c-format
+msgid "Unknown configuration parameter `%s' for PostScript device driver."
+msgstr ""
+
+#: src/postscript.c:608
+#, c-format
+msgid ""
+"Unknown orientation `%s'.  Valid orientations are `portrait' and `landscape'."
+msgstr ""
+
+#: src/postscript.c:620
+msgid ""
+"Unknown value for `data'.  Valid values are `clean7bit', `clean8bit', and "
+"`binary'."
+msgstr ""
+
+#: src/postscript.c:629
+msgid "Unknown value for `line-ends'.  Valid values are `lf' and `crlf'."
+msgstr ""
+
+#: src/postscript.c:638
+msgid "Unknown value for `line-style'.  Valid values are `thick' and `double'."
+msgstr ""
+
+#: src/postscript.c:700
+#, c-format
+msgid ""
+"Default font size must be at least 1 point (value of 1000 for key `%s')."
+msgstr ""
+
+#: src/postscript.c:732
+#, c-format
+msgid "Value for `%s' must be a dimension of positive length (i.e., `1in')."
+msgstr ""
+
+#: src/postscript.c:795
+#, c-format
+msgid "Nonnegative integer required as value for `%s'."
+msgstr ""
+
+#: src/postscript.c:925
+#, c-format
+msgid "%s: %s: Opening PostScript font encoding..."
+msgstr ""
+
+#: src/postscript.c:931
+#, c-format
+msgid ""
+"PostScript driver: Cannot open encoding file `%s': %s.  Substituting "
+"ISOLatin1Encoding for missing encoding."
+msgstr ""
+
+#: src/postscript.c:970
+msgid "PostScript driver: Invalid numeric format."
+msgstr ""
+
+#: src/postscript.c:975
+#, c-format
+msgid ""
+"PostScript driver: Codes must be between 0 and 255.  (%d is not allowed.)"
+msgstr ""
+
+#: src/postscript.c:1011
+#, c-format
+msgid "PostScript driver: Error closing encoding file `%s'."
+msgstr ""
+
+#: src/postscript.c:1014
+#, c-format
+msgid "%s: PostScript font encoding read successfully."
+msgstr ""
+
+#: src/postscript.c:1109
+#, c-format
+msgid "%s: %s: Opening PostScript encoding list file."
+msgstr ""
+
+#: src/postscript.c:1144
+#, c-format
+msgid "%s: PostScript encoding list file read successfully."
+msgstr ""
+
+#: src/postscript.c:1158
+msgid "<<default encoding>>"
+msgstr ""
+
+#: src/postscript.c:1316
+msgid ""
+"Cannot find PostScript prologue.  The use of `-vv' on the command line is "
+"suggested as a debugging aid."
+msgstr ""
+
+#: src/postscript.c:1321
+#, c-format
+msgid "%s: %s: Opening PostScript prologue..."
+msgstr ""
+
+#: src/postscript.c:1493
+#, c-format
+msgid "%s: PostScript prologue read successfully."
+msgstr ""
+
+#: src/postscript.c:1497
+#, c-format
+msgid "%s: Error reading PostScript prologue."
+msgstr ""
+
+#: src/postscript.c:1667
+#, c-format
+msgid "PostScript output driver: %s: %s"
+msgstr ""
+
+#: src/postscript.c:2355
+#, c-format
+msgid "PostScript driver: Cannot find encoding `%s' for PostScript font `%s'."
+msgstr ""
+
+#: src/tab.c:276
+#, c-format
+msgid "bad vline: x=%d+%d=%d y=(%d+%d=%d,%d+%d=%d) in table size (%d,%d)\n"
+msgstr ""
+
+#: src/tab.c:312
+#, c-format
+msgid "bad hline: x=(%d+%d=%d,%d+%d=%d) y=%d+%d=%d in table size (%d,%d)\n"
+msgstr ""
+
+#: src/tab.c:352
+#, c-format
+msgid ""
+"bad box: (%d+%d=%d,%d+%d=%d)-(%d+%d=%d,%d+%d=%d) in table size (%d,%d)\n"
+msgstr ""
+
+#: src/do-if.c:121
+msgid "There is no DO IF to match with this ELSE IF."
+msgstr ""
+
+#: src/do-if.c:126
+msgid "The ELSE command must follow all ELSE IF commands in a DO IF structure."
+msgstr ""
+
+#: src/do-if.c:149
+msgid "End of command expected."
+msgstr ""
+
+#: src/do-if.c:167
+msgid "There is no DO IF to match with this ELSE."
+msgstr ""
+
+#: src/do-if.c:173
+msgid ""
+"There may be at most one ELSE clause in each DO IF structure.  It must be "
+"the last clause."
+msgstr ""
+
+#: src/do-if.c:210
+msgid "There is no DO IF to match with this END IF."
+msgstr ""
+
+#: src/do-if.c:296
+#, c-format
+msgid "DO IF %d: true\n"
+msgstr ""
+
+#: src/do-if.c:301
+#, c-format
+msgid "DO IF %d: false\n"
+msgstr ""
+
+#: src/do-if.c:306
+#, c-format
+msgid "DO IF %d: missing\n"
+msgstr ""
+
+#: src/crosstabs.q:273
+msgid ""
+"Missing mode REPORT not allowed in general mode.  Assuming MISSING=TABLE."
+msgstr ""
+
+#: src/crosstabs.q:283
+msgid "Write mode ALL not allowed in general mode.  Assuming WRITE=CELLS."
+msgstr ""
+
+#: src/crosstabs.q:367
+msgid "expecting BY"
+msgstr ""
+
+#: src/crosstabs.q:440
+msgid "VARIABLES must be specified before TABLES."
+msgstr ""
+
+#: src/crosstabs.q:477
+#, c-format
+msgid "Maximum value (%ld) less than minimum value (%ld)."
+msgstr ""
+
+#: src/crosstabs.q:937
+msgid "Summary."
+msgstr ""
+
+#: src/crosstabs.q:939
+msgid "Cases"
+msgstr ""
+
+#: src/crosstabs.q:940 src/frequencies.q:893
+msgid "Valid"
+msgstr ""
+
+#: src/crosstabs.q:941 src/frequencies.q:960
+msgid "Missing"
+msgstr ""
+
+#: src/crosstabs.q:942 src/crosstabs.q:1143 src/crosstabs.q:1886
+#: src/frequencies.q:969
+msgid "Total"
+msgstr ""
+
+#: src/crosstabs.q:952
+msgid "N"
+msgstr ""
+
+#: src/crosstabs.q:953 src/frequencies.q:897 src/frequencies.q:898
+#: src/frequencies.q:899
+msgid "Percent"
+msgstr ""
+
+#: src/crosstabs.q:1192
+msgid "count"
+msgstr ""
+
+#: src/crosstabs.q:1193
+#, c-format
+msgid "row %"
+msgstr ""
+
+#: src/crosstabs.q:1194
+#, c-format
+msgid "column %"
+msgstr ""
+
+#: src/crosstabs.q:1195
+#, c-format
+msgid "total %"
+msgstr ""
+
+#: src/crosstabs.q:1196
+msgid "expected"
+msgstr ""
+
+#: src/crosstabs.q:1197
+msgid "residual"
+msgstr ""
+
+#: src/crosstabs.q:1198
+msgid "std. resid."
+msgstr ""
+
+#: src/crosstabs.q:1199
+msgid "adj. resid."
+msgstr ""
+
+#: src/crosstabs.q:1232 src/crosstabs.q:1259 src/crosstabs.q:1279
+#: src/crosstabs.q:1300
+msgid "Statistic"
+msgstr ""
+
+#: src/crosstabs.q:1234
+msgid "df"
+msgstr ""
+
+#: src/crosstabs.q:1236
+msgid "Asymp. Sig. (2-sided)"
+msgstr ""
+
+#: src/crosstabs.q:1238
+msgid "Exact. Sig. (2-sided)"
+msgstr ""
+
+#: src/crosstabs.q:1240
+msgid "Exact. Sig. (1-sided)"
+msgstr ""
+
+#: src/crosstabs.q:1258 src/crosstabs.q:1299
+msgid "Category"
+msgstr ""
+
+#: src/crosstabs.q:1261 src/crosstabs.q:1303
+msgid "Asymp. Std. Error"
+msgstr ""
+
+#: src/crosstabs.q:1262 src/crosstabs.q:1304
+msgid "Approx. T"
+msgstr ""
+
+#: src/crosstabs.q:1263 src/crosstabs.q:1305
+msgid "Approx. Sig."
+msgstr ""
+
+#: src/crosstabs.q:1278
+msgid " 95%% Confidence Interval"
+msgstr ""
+
+#: src/crosstabs.q:1281
+msgid "Lower"
+msgstr ""
+
+#: src/crosstabs.q:1282
+msgid "Upper"
+msgstr ""
+
+#: src/crosstabs.q:1301
+msgid "Type"
+msgstr ""
+
+#: src/crosstabs.q:2063
+msgid "Pearson Chi-Square"
+msgstr ""
+
+#: src/crosstabs.q:2064
+msgid "Likelihood Ratio"
+msgstr ""
+
+#: src/crosstabs.q:2065
+msgid "Fisher's Exact Test"
+msgstr ""
+
+#: src/crosstabs.q:2066
+msgid "Continuity Correction"
+msgstr ""
+
+#: src/crosstabs.q:2067
+msgid "Linear-by-Linear Association"
+msgstr ""
+
+#: src/crosstabs.q:2104 src/crosstabs.q:2174 src/crosstabs.q:2233
+msgid "N of Valid Cases"
+msgstr ""
+
+#: src/crosstabs.q:2120 src/crosstabs.q:2249
+msgid "Nominal by Nominal"
+msgstr ""
+
+#: src/crosstabs.q:2121 src/crosstabs.q:2250
+msgid "Ordinal by Ordinal"
+msgstr ""
+
+#: src/crosstabs.q:2122
+msgid "Interval by Interval"
+msgstr ""
+
+#: src/crosstabs.q:2123
+msgid "Measure of Agreement"
+msgstr ""
+
+#: src/crosstabs.q:2128
+msgid "Phi"
+msgstr ""
+
+#: src/crosstabs.q:2129
+msgid "Cramer's V"
+msgstr ""
+
+#: src/crosstabs.q:2130
+msgid "Contingency Coefficient"
+msgstr ""
+
+#: src/crosstabs.q:2131
+msgid "Kendall's tau-b"
+msgstr ""
+
+#: src/crosstabs.q:2132
+msgid "Kendall's tau-c"
+msgstr ""
+
+#: src/crosstabs.q:2133
+msgid "Gamma"
+msgstr ""
+
+#: src/crosstabs.q:2134
+msgid "Spearman Correlation"
+msgstr ""
+
+#: src/crosstabs.q:2135
+msgid "Pearson's R"
+msgstr ""
+
+#: src/crosstabs.q:2136
+msgid "Kappa"
+msgstr ""
+
+#: src/crosstabs.q:2206
+#, c-format
+msgid "Odds Ratio for %s (%g / %g)"
+msgstr ""
+
+#: src/crosstabs.q:2209
+#, c-format
+msgid "Odds Ratio for %s (%.*s / %.*s)"
+msgstr ""
+
+#: src/crosstabs.q:2217
+#, c-format
+msgid "For cohort %s = %g"
+msgstr ""
+
+#: src/crosstabs.q:2220
+#, c-format
+msgid "For cohort %s = %.*s"
+msgstr ""
+
+#: src/crosstabs.q:2251
+msgid "Nominal by Interval"
+msgstr ""
+
+#: src/crosstabs.q:2256
+msgid "Lambda"
+msgstr ""
+
+#: src/crosstabs.q:2257
+msgid "Goodman and Kruskal tau"
+msgstr ""
+
+#: src/crosstabs.q:2258
+msgid "Uncertainty Coefficient"
+msgstr ""
+
+#: src/crosstabs.q:2259
+msgid "Somers' d"
+msgstr ""
+
+#: src/crosstabs.q:2260
+msgid "Eta"
+msgstr ""
+
+#: src/crosstabs.q:2265
+msgid "Symmetric"
+msgstr ""
+
+#: src/crosstabs.q:2266 src/crosstabs.q:2267
+#, c-format
+msgid "%s Dependent"
+msgstr ""
+
+#: src/descript.q:151 src/frequencies.q:95
+msgid "Mean"
+msgstr ""
+
+#: src/descript.q:151
+msgid "mean"
+msgstr ""
+
+#: src/descript.q:152 src/frequencies.q:96
+msgid "S.E. Mean"
+msgstr ""
+
+#: src/descript.q:152
+msgid "S E Mean"
+msgstr ""
+
+#: src/descript.q:152
+msgid "SE"
+msgstr ""
+
+#: src/descript.q:153
+msgid "standard error of mean"
+msgstr ""
+
+#: src/descript.q:154 src/frequencies.q:99
+msgid "Std Dev"
+msgstr ""
+
+#: src/descript.q:154
+msgid "SD"
+msgstr ""
+
+#: src/descript.q:155
+msgid "standard deviation"
+msgstr ""
+
+#: src/descript.q:156 src/frequencies.q:100
+msgid "Variance"
+msgstr ""
+
+#: src/descript.q:157
+msgid "Var"
+msgstr ""
+
+#: src/descript.q:157
+msgid "variance"
+msgstr ""
+
+#: src/descript.q:158 src/frequencies.q:101
+msgid "Kurtosis"
+msgstr ""
+
+#: src/descript.q:159
+msgid "Kurt"
+msgstr ""
+
+#: src/descript.q:159
+msgid "kurtosis"
+msgstr ""
+
+#: src/descript.q:160 src/frequencies.q:102
+msgid "S.E. Kurt"
+msgstr ""
+
+#: src/descript.q:160
+msgid "S E Kurt"
+msgstr ""
+
+#: src/descript.q:160
+msgid "SEKurt"
+msgstr ""
+
+#: src/descript.q:161
+msgid "standard error of kurtosis"
+msgstr ""
+
+#: src/descript.q:162 src/frequencies.q:103
+msgid "Skewness"
+msgstr ""
+
+#: src/descript.q:162
+msgid "Skew"
+msgstr ""
+
+#: src/descript.q:163
+msgid "skewness"
+msgstr ""
+
+#: src/descript.q:164 src/frequencies.q:104
+msgid "S.E. Skew"
+msgstr ""
+
+#: src/descript.q:164
+msgid "S E Skew"
+msgstr ""
+
+#: src/descript.q:164
+msgid "SESkew"
+msgstr ""
+
+#: src/descript.q:165
+msgid "standard error of skewness"
+msgstr ""
+
+#: src/descript.q:166 src/frequencies.q:105
+msgid "Range"
+msgstr ""
+
+#: src/descript.q:166
+msgid "Rng"
+msgstr ""
+
+#: src/descript.q:167 src/frequencies.q:106
+msgid "Minimum"
+msgstr ""
+
+#: src/descript.q:167
+msgid "Min"
+msgstr ""
+
+#: src/descript.q:168
+msgid "minimum"
+msgstr ""
+
+#: src/descript.q:169 src/frequencies.q:107
+msgid "Maximum"
+msgstr ""
+
+#: src/descript.q:169
+msgid "Max"
+msgstr ""
+
+#: src/descript.q:170
+msgid "maximum"
+msgstr ""
+
+#: src/descript.q:171 src/frequencies.q:108
+msgid "Sum"
+msgstr ""
+
+#: src/descript.q:171
+msgid "sum"
+msgstr ""
+
+#: src/descript.q:212 src/list.q:161
+msgid "No variables specified."
+msgstr ""
+
+#: src/descript.q:218
+msgid "OPTIONS may not be used with SAVE, FORMAT, or MISSING."
+msgstr ""
+
+#: src/descript.q:280
+#, c-format
+msgid "It's not possible to sort on `%s' without displaying `%s'."
+msgstr ""
+
+#: src/descript.q:295
+msgid ""
+"At least one case in the data file had a weight value that was "
+"system-missing, zero, or negative.  These case(s) were ignored."
+msgstr ""
+
+#: src/descript.q:336
+msgid ""
+"Names for z-score variables must be given for individual variables, not for "
+"groups of variables."
+msgstr ""
+
+#: src/descript.q:344
+msgid "Name for z-score variable expected."
+msgstr ""
+
+#: src/descript.q:349
+#, c-format
+msgid ""
+"Z-score variable name `%s' is a duplicate variable name with a current "
+"variable."
+msgstr ""
+
+#: src/descript.q:358
+#, c-format
+msgid "Z-score variable name `%s' is used multiple times."
+msgstr ""
+
+#: src/descript.q:366
+msgid "`)' expected after z-score variable name."
+msgstr ""
+
+#: src/descript.q:426
+msgid ""
+"Ran out of generic names for Z-score variables.  There are only 126 generic "
+"names: ZSC001-ZSC0999, STDZ01-STDZ09, ZZZZ01-ZZZZ09, ZQZQ01-ZQZQ09."
+msgstr ""
+
+#: src/descript.q:455
+msgid "Mapping of variables to corresponding Z-scores."
+msgstr ""
+
+#: src/descript.q:460
+msgid "Source"
+msgstr ""
+
+#: src/descript.q:461
+msgid "Target"
+msgstr ""
+
+#: src/descript.q:548 src/descript.q:554
+msgid "Z-score of "
+msgstr ""
+
+#: src/descript.q:803
+msgid "Valid N"
+msgstr ""
+
+#: src/descript.q:804
+msgid "Missing N"
+msgstr ""
+
+#: src/descript.q:831
+#, c-format
+msgid "Valid cases = %g; cases with missing value(s) = %g."
+msgstr ""
+
+#: src/frequencies.q:97
+msgid "Median"
+msgstr ""
+
+#: src/frequencies.q:98
+msgid "Mode"
+msgstr ""
+
+#: src/frequencies.q:271
+msgid ""
+"At most one of BARCHART, HISTOGRAM, or HBAR should be given.  HBAR will be "
+"assumed.  Argument values will be given precedence increasing along the "
+"order given."
+msgstr ""
+
+#: src/frequencies.q:352
+#, c-format
+msgid ""
+"MAX must be greater than or equal to MIN, if both are specified.  However, "
+"MIN was specified as %g and MAX as %g.  MIN and MAX will be ignored."
+msgstr ""
+
+#: src/frequencies.q:602
+msgid ""
+"Upper limit of integer mode value range must be greater than lower limit."
+msgstr ""
+
+#: src/frequencies.q:614
+#, c-format
+msgid "Variable %s specified multiple times on VARIABLES subcommand."
+msgstr ""
+
+#: src/frequencies.q:627
+#, c-format
+msgid "Integer mode specified, but %s is not a numeric variable."
+msgstr ""
+
+#: src/frequencies.q:687
+msgid "`)' expected after GROUPED interval list."
+msgstr ""
+
+#: src/frequencies.q:697
+#, c-format
+msgid "Variables %s specified on GROUPED but not on VARIABLES."
+msgstr ""
+
+#: src/frequencies.q:700
+#, c-format
+msgid "Variables %s specified multiple times on GROUPED subcommand."
+msgstr ""
+
+#: src/frequencies.q:751
+msgid "Percentile list expected after PERCENTILES."
+msgstr ""
+
+#: src/frequencies.q:759
+msgid "Percentiles must be greater than 0 and less than 100."
+msgstr ""
+
+#: src/frequencies.q:894 src/frequencies.q:984 src/frequencies.q:985
+#: src/frequencies.q:1015
+msgid "Cum"
+msgstr ""
+
+#: src/frequencies.q:896 src/frequencies.q:1420
+msgid "Frequency"
+msgstr ""
+
+#: src/frequencies.q:915
+msgid "Value Label"
+msgstr ""
+
+#: src/frequencies.q:1013
+msgid "Freq"
+msgstr ""
+
+#: src/frequencies.q:1014 src/frequencies.q:1016
+msgid "Pct"
+msgstr ""
+
+#: src/frequencies.q:1132
+#, c-format
+msgid "No valid data for variable %s; statistics not displayed."
+msgstr ""
+
+#: src/frequencies.q:1226
+#, c-format
+msgid "only %g case%s for variable %s, statistics not computed"
+msgstr ""
+
+#: src/frequencies.q:1261
+#, c-format
+msgid ""
+"The variable %s has %d modes.  The lowest of these is the one given in the "
+"table."
+msgstr ""
+
+#. Draw axis labels.
+#. 18-point text
+#: src/frequencies.q:1420
+msgid "Percentage"
+msgstr ""
+
+#: src/frequencies.q:1443
+msgid "low-res graphs not implemented"
+msgstr ""
+
+#: src/frequencies.q:1577
+#, c-format
+msgid ""
+"Could not make histogram for %s for specified minimum %g and maximum %g; "
+"please discard graph."
+msgstr ""
+
+#: src/frequencies.q:1716
+msgid "Percentile    Value     Percentile    Value     Percentile    Value"
+msgstr ""
+
+#: src/frequencies.q:1733
+msgid "this form of percentiles not supported"
+msgstr ""
+
+#: src/frequencies.q:1797
+#, c-format
+msgid "Difference between %g and %g is too small for grouping interval %g."
+msgstr ""
+
+#: src/list.q:169
+#, c-format
+msgid ""
+"The first case (%ld) specified precedes the last case (%ld) specified.  The "
+"values will be swapped."
+msgstr ""
+
+#: src/list.q:177
+#, c-format
+msgid ""
+"The first case (%ld) to list is less than 1.  The value is being reset to 1."
+msgstr ""
+
+#: src/list.q:183
+#, c-format
+msgid ""
+"The last case (%ld) to list is less than 1.  The value is being reset to 1."
+msgstr ""
+
+#: src/list.q:189
+#, c-format
+msgid "The step value %ld is less than 1.  The value is being reset to 1."
+msgstr ""
+
+#: src/list.q:217
+msgid "`/FORMAT WEIGHT' specified, but weighting is not on."
+msgstr ""
+
+#: src/list.q:455
+msgid "Line"
+msgstr ""
+
+#: src/means.q:108
+msgid "Missing required subcommand TABLES."
+msgstr ""
+
+#: src/means.q:155
+msgid "TABLES or CROSSBREAK subcommand may not appear more than once."
+msgstr ""
+
+#: src/means.q:202
+#, c-format
+msgid ""
+"Variable %s specified on TABLES or CROSSBREAK, but not specified on "
+"VARIABLES."
+msgstr ""
+
+#: src/means.q:216
+#, c-format
+msgid "LOWEST and HIGHEST may not be used for independent variables (%s)."
+msgstr ""
+
+#: src/means.q:224
+#, c-format
+msgid ""
+"Independent variables (%s) may not have noninteger endpoints in their ranges."
+msgstr ""
+
+#: src/means.q:245
+#, c-format
+msgid "Variable %s is multiply specified on TABLES or CROSSBREAK."
+msgstr ""
+
+#: src/means.q:271
+msgid "VARIABLES must precede TABLES."
+msgstr ""
+
+#: src/means.q:328
+#, c-format
+msgid "Upper value (%g) is less than lower value (%g) on VARIABLES subcommand."
+msgstr ""
+
+#: src/t-test.q:470
+msgid "expecting variable name in GROUPS subcommand"
+msgstr ""
+
+#: src/t-test.q:475
+#, c-format
+msgid "Long string variable %s is not valid here."
+msgstr ""
+
+#: src/t-test.q:491
+msgid ""
+"When applying GROUPS to a string variable, at least one value must be "
+"specified."
+msgstr ""
+
+#: src/t-test.q:581
+#, c-format
+msgid ""
+"PAIRED was specified but the number of variables preceding WITH (%d) did not "
+"match the number following (%d)."
+msgstr ""
+
+#: src/t-test.q:597
+msgid "At least two variables must be specified on PAIRS."
+msgstr ""
+
+#: src/count.c:189
+msgid "Destination cannot be a string variable."
+msgstr ""
+
+#: src/count.c:299
+#, c-format
+msgid ""
+"%g THRU %g is not a valid range.  The number following THRU must be at least "
+"as big as the number preceding THRU."
+msgstr ""
+
+#: src/vars-atr.c:61
+msgid "Vartree:\n"
+msgstr ""
+
+#: src/vars-atr.c:313
+#, c-format
+msgid "clearing variable %d:%s %s\n"
+msgstr ""
+
+#: src/vars-atr.c:314
+msgid "in default dictionary"
+msgstr ""
+
+#: src/vars-atr.c:315
+msgid "in auxiliary dictionary"
+msgstr ""
+
+#: src/vars-prs.c:113
+#, c-format
+msgid "%s is not declared as a variable."
+msgstr ""
+
+#: src/vars-prs.c:131
+#, c-format
+msgid "%s is not a variable name."
+msgstr ""
+
+#: src/vars-prs.c:230
+#, c-format
+msgid "%s TO %s is not valid syntax since %s precedes %s in the dictionary."
+msgstr ""
+
+#: src/vars-prs.c:239
+#, c-format
+msgid ""
+"When using the TO keyword to specify several variables, both variables must "
+"be from the same variable dictionaries, of either ordinary, scratch, or "
+"system variables.  %s and %s are from different dictionaries."
+msgstr ""
+
+#: src/vars-prs.c:256
+#, c-format
+msgid "Scratch variables (such as %s) are not allowed here."
+msgstr ""
+
+#: src/vars-prs.c:279
+#, c-format
+msgid ""
+"%s is not a numeric variable.  It will not be included in the variable list."
+msgstr ""
+
+#: src/vars-prs.c:285
+#, c-format
+msgid ""
+"%s is not a string variable.  It will not be included in the variable list."
+msgstr ""
+
+#: src/vars-prs.c:291
+#, c-format
+msgid ""
+"%s and %s are not the same type.  All variables in this variable list must "
+"be of the same type.  %s will be omitted from list."
+msgstr ""
+
+#: src/vars-prs.c:299
+#, c-format
+msgid "Variable %s appears twice in variable list."
+msgstr ""
+
+#: src/vars-prs.c:370
+msgid "incorrect use of TO convention"
+msgstr ""
+
+#: src/vars-prs.c:410
+msgid "Scratch variables not allowed here."
+msgstr ""
+
+#: src/vars-prs.c:432
+msgid "Prefixes don't match in use of TO convention."
+msgstr ""
+
+#: src/vars-prs.c:437
+msgid "Bad bounds in use of TO convention."
+msgstr ""
+
+#: src/vfm.c:311
+#, c-format
+msgid ""
+"Workspace overflow predicted.  Max workspace is currently set to %d KB (%d "
+"cases at %d bytes each).  Paging active file to disk."
+msgstr ""
+
+#: src/vfm.c:374
+msgid "!ERROR!"
+msgstr ""
+
+#: src/vfm.c:395
+msgid "<NOVAR>"
+msgstr ""
+
+#: src/vfm.c:655
+#, c-format
+msgid ""
+"An error occurred attempting to create a temporary file for use as the "
+"active file: %s."
+msgstr ""
+
+#: src/vfm.c:673
+#, c-format
+msgid ""
+"An error occurred while attempting to read from a temporary file created for "
+"the active file: %s."
+msgstr ""
+
+#: src/vfm.c:701
+#, c-format
+msgid ""
+"An error occurred while attempting to write to a temporary file used as the "
+"active file: %s."
+msgstr ""
+
+#: src/vfm.c:715
+#, c-format
+msgid ""
+"An error occurred while attempting to rewind a temporary file used as the "
+"active file: %s."
+msgstr ""
+
+#: src/vfm.c:830
+msgid "Virtual memory exhausted.  Paging active file to disk."
+msgstr ""
+
+#: src/vfm.c:833
+#, c-format
+msgid ""
+"Workspace limit of %d KB (%d cases at %d bytes each) overflowed.  Paging "
+"active file to disk."
+msgstr ""
+
+#: src/vfm.c:857 src/vfm.c:894
+#, c-format
+msgid ""
+"An error occurred while attempting to write to a temporary file created as "
+"the active file, while paging to disk: %s."
+msgstr ""
+
+#: src/vfm.c:1008
+msgid "transform: "
+msgstr ""
+
+#: src/autorecode.c:135
+#, c-format
+msgid "Target variable %s duplicates existing variable %s."
+msgstr ""
+
+#: src/autorecode.c:142
+#, c-format
+msgid "Duplicate variable name %s among target variables."
+msgstr ""
+
+#: src/compute.c:140 src/compute.c:186 src/compute.c:292 src/compute.c:329
+#, c-format
+msgid ""
+"When executing COMPUTE: SYSMIS is not a valid value as an index into vector "
+"%s."
+msgstr ""
+
+#: src/compute.c:143 src/compute.c:189 src/compute.c:295 src/compute.c:332
+#, c-format
+msgid ""
+"When executing COMPUTE: %g is not a valid value as an index into vector %s."
+msgstr ""
+
+#: src/compute.c:422
+#, c-format
+msgid "There is no vector named %s."
+msgstr ""
+
+#: src/compute.c:471
+msgid "Extra characters after expression."
+msgstr ""
+
+#: src/flip.c:160
+#, c-format
+msgid "Could not create acceptable variant for variable %s."
+msgstr ""
+
+#: src/flip.c:176
+msgid "Cannot create more than 99999 variable names."
+msgstr ""
+
+#: src/flip.c:290
+#, c-format
+msgid "Error reading FLIP source file: %s."
+msgstr ""
+
+#: src/flip.c:366
+msgid "Could not create temporary file for FLIP."
+msgstr ""
+
+#: src/flip.c:376 src/flip.c:395
+#, c-format
+msgid "Error writing FLIP file: %s."
+msgstr ""
+
+#: src/flip.c:431
+msgid "Error creating FLIP source file."
+msgstr ""
+
+#: src/flip.c:434
+#, c-format
+msgid "Error rewinding FLIP file: %s."
+msgstr ""
+
+#: src/flip.c:443
+#, c-format
+msgid "Error reading FLIP file: %s."
+msgstr ""
+
+#: src/flip.c:455
+#, c-format
+msgid "Error seeking FLIP source file: %s."
+msgstr ""
+
+#: src/flip.c:460
+#, c-format
+msgid "Error writing FLIP source file: %s."
+msgstr ""
+
+#: src/flip.c:468
+#, c-format
+msgid "Error rewind FLIP source file: %s."
+msgstr ""
+
+#: src/print.c:209
+msgid "expecting a valid subcommand"
+msgstr ""
+
+#: src/print.c:389 src/print.c:406
+#, c-format
+msgid "%g is not a valid column location."
+msgstr ""
+
+#: src/print.c:400
+#, c-format
+msgid "Column location expected following `%d-'."
+msgstr ""
+
+#: src/print.c:411
+#, c-format
+msgid ""
+"%d-%ld is not a valid column range.  The second column must be greater than "
+"or equal to the first."
+msgstr ""
+
+#: src/print.c:517
+#, c-format
+msgid ""
+"%s is not of the same type as %s.  To specify variables of different types "
+"in the same variable list, use a FORTRAN-like format specifier."
+msgstr ""
+
+#: src/print.c:547
+msgid ""
+"The ending column for a field must not be less than the starting column."
+msgstr ""
+
+#: src/print.c:630
+#, c-format
+msgid "%s variables cannot be displayed with format %s."
+msgstr ""
+
+#: src/print.c:717
+#, c-format
+msgid "Display format %s may not be used with a %s variable."
+msgstr ""
+
+#: src/print.c:867
+#, c-format
+msgid "Writing %3d records to file %s."
+msgstr ""
+
+#: src/print.c:868
+#, c-format
+msgid "Writing %3d records to the listing file."
+msgstr ""
+
+#: src/print.c:1082
+msgid "A file name or handle was expected in the OUTFILE subcommand."
+msgstr ""
+
+#: src/print.c:1134
+#, c-format
+msgid ""
+"The expression on PRINT SPACE evaluated to %d.  It's not possible to PRINT "
+"SPACE a negative number of lines."
+msgstr ""
+
+#: src/recode.c:290
+#, c-format
+msgid ""
+"%d variable(s) cannot be recoded into %d variable(s).  Specify the same "
+"number of variables as input and output variables."
+msgstr ""
+
+#: src/recode.c:304
+#, c-format
+msgid ""
+"There is no string variable named %s.  (All string variables specified on "
+"INTO must already exist.  Use the STRING command to create a string "
+"variable.)"
+msgstr ""
+
+#: src/recode.c:313
+#, c-format
+msgid ""
+"Type mismatch between input and output variables.  Output variable %s is not "
+"a string variable, but all the input variables are string variables."
+msgstr ""
+
+#: src/recode.c:332
+#, c-format
+msgid "Type mismatch after INTO: %s is not a numeric variable."
+msgstr ""
+
+#: src/recode.c:362
+msgid ""
+"INTO must be used when the input values are numeric and output values are "
+"string."
+msgstr ""
+
+#: src/recode.c:370
+msgid ""
+"INTO must be used when the input values are string and output values are "
+"numeric."
+msgstr ""
+
+#: src/recode.c:507
+msgid ""
+"Inconsistent output types.  The output values must be all numeric or all "
+"string."
+msgstr ""
+
+#: src/recode.c:558
+msgid "following LO THRU"
+msgstr ""
+
+#: src/recode.c:574 src/recode.c:603
+msgid "in source value"
+msgstr ""
+
+#: src/recode.c:616
+msgid ""
+"Keyword CONVERT may only be used with string input values and numeric output "
+"values."
+msgstr ""
+
+#: src/recode.c:872
+msgid "!!END!!"
+msgstr ""
+
+#: src/recode.c:893 src/recode.c:909
+msgid "!!ERROR!!"
+msgstr ""
+
+#: src/sel-if.c:102
+msgid "The filter variable must be numeric."
+msgstr ""
+
+#: src/sel-if.c:108
+msgid "The filter variable may not be scratch."
+msgstr ""
+
+#: src/sel-if.c:142
+msgid "Only last instance of this command is in effect."
+msgstr ""
+
+#: src/sort.c:131
+msgid "`A' or `D' expected inside parentheses."
+msgstr ""
+
+#: src/sort.c:137
+msgid "`)' expected."
+msgstr ""
+
+#: src/sort.c:462
+#, c-format
+msgid "%s: Cannot create temporary directory: %s."
+msgstr ""
+
+#: src/sort.c:486
+#, c-format
+msgid "%s: Error removing directory for temporary files: %s."
+msgstr ""
+
+#: src/sort.c:530
+#, c-format
+msgid ""
+"Out of memory.  Could not allocate room for minimum of %d cases of %d bytes "
+"each.  (PSPP workspace is currently restricted to a maximum of %d KB.)"
+msgstr ""
+
+#: src/sort.c:542
+#, c-format
+msgid "allocated %d cases == %d bytes\n"
+msgstr ""
+
+#: src/sort.c:580
+#, c-format
+msgid "%s: Error writing temporary file: %s."
+msgstr ""
+
+#: src/sort.c:592
+#, c-format
+msgid "SORT: Closing handle %d."
+msgstr ""
+
+#: src/sort.c:598 src/sort.c:822
+#, c-format
+msgid "%s: Error closing temporary file: %s."
+msgstr ""
+
+#: src/sort.c:620 src/sort.c:636
+#, c-format
+msgid "SORT: %s: Opening for writing as run %d."
+msgstr ""
+
+#: src/sort.c:642
+#, c-format
+msgid "%s: Error opening temporary file for reading: %s."
+msgstr ""
+
+#: src/sort.c:668 src/sort.c:684
+#, c-format
+msgid "%s: Error creating temporary file: %s."
+msgstr ""
+
+#: src/sort.c:826 src/sort.c:987 src/sort.c:1037 src/sort.c:1207
+#: src/sort.c:1214
+#, c-format
+msgid "%s: Error removing temporary file: %s."
+msgstr ""
+
+#. Find the shortest runs; put them in runs[] in reverse order
+#. of length, to force dummy runs of length 0 to the end of the
+#. list.
+#: src/sort.c:969
+msgid "merging runs"
+msgstr ""
+
+#: src/sort.c:977
+#, c-format
+msgid " into run %d(%d)\n"
+msgstr ""
+
+#: src/sort.c:996
+msgid "Out of memory expanding Huffman priority queue."
+msgstr ""
+
+#: src/sort.c:1048
+#, c-format
+msgid "%s: Error creating temporary file for merge: %s."
+msgstr ""
+
+#: src/sort.c:1076 src/sort.c:1134
+#, c-format
+msgid "%s: Error reading temporary file in merge: %s."
+msgstr ""
+
+#: src/sort.c:1079 src/sort.c:1138
+#, c-format
+msgid "%s: Unexpected end of temporary file in merge."
+msgstr ""
+
+#: src/sort.c:1104
+#, c-format
+msgid "%s: Error writing temporary file in merge: %s."
+msgstr ""
+
+#: src/sort.c:1154 src/sort.c:1187
+#, c-format
+msgid "%s: Error closing temporary file in merge: %s."
+msgstr ""
+
+#: src/sort.c:1159
+#, c-format
+msgid "%s: Error removing temporary file in merge: %s."
+msgstr ""
+
+#: src/sort.c:1258
+#, c-format
+msgid "%s: Cannot open sort result file: %s."
+msgstr ""
+
+#: src/sort.c:1269
+#, c-format
+msgid "%s: Error reading sort result file: %s."
+msgstr ""
+
+#: src/sort.c:1272
+#, c-format
+msgid "%s: Unexpected end of sort result file: %s."
+msgstr ""
+
+#: src/sort.c:1283
+#, c-format
+msgid "%s: Error closing sort result file: %s."
+msgstr ""
+
+#: src/sort.c:1287
+#, c-format
+msgid "%s: Error removing sort result file: %s."
+msgstr ""
+
+#: src/include.c:51
+msgid "Unrecognized filename format."
+msgstr ""
+
+#: src/loop.c:203
+msgid "The index variable may not be a string variable."
+msgstr ""
+
+#: src/loop.c:323
+msgid "There is no LOOP command that corresponds to this END LOOP."
+msgstr ""
+
+#: src/loop.c:524
+msgid ""
+"This command may only appear enclosed in a LOOP/END LOOP control structure."
+msgstr ""
+
+#: src/loop.c:530
+msgid "BREAK not enclosed in DO IF structure."
+msgstr ""
+
+#: src/loop.c:607
+#, c-format
+msgid "%s without %s."
+msgstr ""
+
+#: src/repeat.c:160
+#, c-format
+msgid "Identifier %s is given twice."
+msgstr ""
+
+#: src/repeat.c:203
+#, c-format
+msgid ""
+"There must be the same number of substitutions for each dummy variable "
+"specified.  Since there were %d substitutions for %s, there must be %d for "
+"%s as well, but %d were specified."
+msgstr ""
+
+#: src/repeat.c:312
+msgid "No commands in scope."
+msgstr ""
+
+#: src/mis-val.c:332 src/repeat.c:485
+msgid "String expected."
+msgstr ""
+
+#: src/repeat.c:512
+msgid "No matching DO REPEAT."
+msgstr ""
+
+#: src/mis-val.c:83
+msgid "`)' expected after value specification."
+msgstr ""
+
+#: src/mis-val.c:117
+#, c-format
+msgid "`(' expected after variable name%s."
+msgstr ""
+
+#: src/mis-val.c:129
+msgid "Long string value specified."
+msgstr ""
+
+#: src/mis-val.c:134
+msgid "Short strings must be of equal width."
+msgstr ""
+
+#: src/mis-val.c:191
+#, c-format
+msgid "Range %g THRU %g is not valid because %g is greater than %g."
+msgstr ""
+
+#: src/mis-val.c:222
+msgid "Number or range expected."
+msgstr ""
+
+#: src/mis-val.c:255
+msgid "At most one range can exist in the missing values for any one variable."
+msgstr ""
+
+#: src/mis-val.c:261
+msgid "At most one individual value can be missing along with one range."
+msgstr ""
+
+#: src/mis-val.c:323
+msgid "String is not of proper length."
+msgstr ""
+
+#: src/mis-val.c:372
+msgid "Missing value:"
+msgstr ""
+
+#: src/mis-val.c:377
+msgid "(long string variable)"
+msgstr ""
+
+#: src/mis-val.c:382
+msgid "(no missing values)\n"
+msgstr ""
+
+#: src/mis-val.c:405
+#, c-format
+msgid "(!!!INTERNAL ERROR--%d!!!)\n"
+msgstr ""
+
+#: src/modify-vars.c:109
+msgid "REORDER subcommand may be given at most once."
+msgstr ""
+
+#: src/modify-vars.c:131
+msgid "Cannot specify ALL after specifying a set of variables."
+msgstr ""
+
+#: src/modify-vars.c:141
+msgid "`(' expected on REORDER subcommand."
+msgstr ""
+
+#: src/modify-vars.c:153
+msgid "`)' expected following variable names on REORDER subcommand."
+msgstr ""
+
+#: src/modify-vars.c:185
+msgid "RENAME subcommand may be given at most once."
+msgstr ""
+
+#: src/modify-vars.c:198
+msgid "`(' expected on RENAME subcommand."
+msgstr ""
+
+#: src/modify-vars.c:206
+msgid ""
+"`=' expected between lists of new and old variable names on RENAME "
+"subcommand."
+msgstr ""
+
+#: src/modify-vars.c:216 src/rename-vars.c:74
+#, c-format
+msgid ""
+"Differing number of variables in old name list (%d) and in new name list "
+"(%d)."
+msgstr ""
+
+#: src/modify-vars.c:227
+msgid "`)' expected after variable lists on RENAME subcommand."
+msgstr ""
+
+#: src/modify-vars.c:243
+msgid ""
+"KEEP subcommand may be given at most once.  It may notbe given in "
+"conjunction with the DROP subcommand."
+msgstr ""
+
+#: src/modify-vars.c:281
+msgid ""
+"DROP subcommand may be given at most once.  It may notbe given in "
+"conjunction with the KEEP subcommand."
+msgstr ""
+
+#: src/modify-vars.c:307
+#, c-format
+msgid "Unrecognized subcommand name `%s'."
+msgstr ""
+
+#: src/modify-vars.c:309
+msgid "Subcommand name expected."
+msgstr ""
+
+#: src/modify-vars.c:317
+msgid "`/' or `.' expected."
+msgstr ""
+
+#: src/modify-vars.c:471 src/rename-vars.c:124
+#, c-format
+msgid "Duplicate variable name `%s' after renaming."
+msgstr ""
+
+#: src/numeric.c:61
+#, c-format
+msgid "Format type %s may not be used with a numeric variable."
+msgstr ""
+
+#: src/numeric.c:81 src/numeric.c:164 src/vector.c:167
+#, c-format
+msgid "There is already a variable named %s."
+msgstr ""
+
+#: src/numeric.c:135
+#, c-format
+msgid "Format type %s may not be used with a string variable."
+msgstr ""
+
+#: src/rename-vars.c:59
+msgid "`(' expected."
+msgstr ""
+
+#: src/rename-vars.c:67
+msgid "`=' expected between lists of new and old variable names."
+msgstr ""
+
+#: src/rename-vars.c:85
+msgid "`)' expected after variable names."
+msgstr ""
+
+#: src/sample.c:72
+msgid "The sampling factor must be between 0 and 1 exclusive."
+msgstr ""
+
+#: src/sample.c:92
+#, c-format
+msgid "Cannot sample %d observations from a population of %d."
+msgstr ""
+
+#: src/set.q:216
+msgid "BLOCK is obsolete."
+msgstr ""
+
+#: src/set.q:219
+msgid "BOXSTRING is obsolete."
+msgstr ""
+
+#: src/set.q:223
+msgid "Active file compression is not yet implemented (and probably won't be)."
+msgstr ""
+
+#: src/set.q:232
+msgid "CPI must be greater than 0."
+msgstr ""
+
+#: src/set.q:237
+msgid "HISTOGRAM is obsolete."
+msgstr ""
+
+#: src/set.q:241
+msgid "LPI must be greater than 0."
+msgstr ""
+
+#: src/set.q:248
+msgid ""
+"CASE is not implemented and probably won't be.  If you care, complain about "
+"it."
+msgstr ""
+
+#: src/set.q:278
+#, c-format
+msgid "Value for MITERATE (%ld) must be greater than 0."
+msgstr ""
+
+#: src/set.q:286
+#, c-format
+msgid "Value for MNEST (%ld) must be greater than 0."
+msgstr ""
+
+#: src/set.q:294
+msgid "MXERRS must be at least 1."
+msgstr ""
+
+#: src/set.q:301
+msgid "MXLOOPS must be at least 1."
+msgstr ""
+
+#: src/set.q:306
+msgid "MXMEMORY is obsolete."
+msgstr ""
+
+#: src/set.q:312
+msgid "SCRIPTTAB is obsolete."
+msgstr ""
+
+#: src/set.q:314
+msgid "TBFONTS not implemented."
+msgstr ""
+
+#: src/set.q:316
+msgid "TB1 not implemented."
+msgstr ""
+
+#: src/set.q:320
+msgid "WORKSPACE is obsolete."
+msgstr ""
+
+#: src/set.q:327
+msgid "AUTOMENU is obsolete."
+msgstr ""
+
+#: src/set.q:329
+msgid "BEEP is obsolete."
+msgstr ""
+
+#: src/set.q:348
+msgid "EJECT is obsolete."
+msgstr ""
+
+#: src/set.q:352
+msgid "HELPWINDOWS is obsolete."
+msgstr ""
+
+#: src/set.q:356
+msgid "MENUS is obsolete."
+msgstr ""
+
+#: src/set.q:370
+msgid "PTRANSLATE is obsolete."
+msgstr ""
+
+#: src/set.q:376
+msgid "XSORT is obsolete."
+msgstr ""
+
+#: src/set.q:390
+#, c-format
+msgid ""
+"CC%c: Length of custom currency string `%s' (%d) exceeds maximum length of "
+"16."
+msgstr ""
+
+#: src/set.q:412
+#, c-format
+msgid ""
+"CC%c: Custom currency string `%s' does not contain exactly three periods or "
+"commas (not both)."
+msgstr ""
+
+#: src/set.q:555
+msgid "LENGTH must be at least 1."
+msgstr ""
+
+#: src/set.q:592
+msgid "Missing identifier in RESULTS subcommand."
+msgstr ""
+
+#: src/set.q:603
+msgid "Unrecognized identifier in RESULTS subcommand."
+msgstr ""
+
+#: src/set.q:639
+msgid "WIDTH must be at least 1."
+msgstr ""
+
+#: src/set.q:662
+#, c-format
+msgid ""
+"FORMAT requires numeric output format as an argument.  Specified format %s "
+"is of type string."
+msgstr ""
+
+#: src/set.q:706
+msgid "Text color must be in range 0-15."
+msgstr ""
+
+#: src/set.q:719
+msgid "Background color must be in range 0-7."
+msgstr ""
+
+#: src/set.q:730
+msgid "Border color must be in range 0-7."
+msgstr ""
+
+#: src/set.q:774
+msgid "RCOLOR is obsolete."
+msgstr ""
+
+#: src/set.q:786
+msgid "Lower window color must be between 0 and 6."
+msgstr ""
+
+#: src/set.q:800
+msgid "Upper window color must be between 0 and 6."
+msgstr ""
+
+#: src/set.q:812
+msgid "Frame color must be between 0 and 6."
+msgstr ""
+
+#: src/set.q:845
+msgid "VIEWLENGTH not implemented."
+msgstr ""
+
+#: src/set.q:855
+msgid "WORKDEV is obsolete."
+msgstr ""
+
+#: src/set.q:864
+msgid "Drive letter expected in WORKDEV subcommand."
+msgstr ""
+
+#: src/temporary.c:65
+msgid "This command is not valid inside DO IF or LOOP."
+msgstr ""
+
+#: src/temporary.c:72
+msgid ""
+"This command may only appear once between procedures and procedure-like "
+"commands."
+msgstr ""
+
+#: src/title.c:57
+#, c-format
+msgid "%s before: %s\n"
+msgstr ""
+
+#: src/title.c:57
+msgid "<none>"
+msgstr ""
+
+#: src/title.c:69
+#, c-format
+msgid "%s: `.' expected after string."
+msgstr ""
+
+#: src/title.c:84
+#, c-format
+msgid "%s after: %s\n"
+msgstr ""
+
+#: src/title.c:134
+#, c-format
+msgid "Document entered %s %02d:%02d:%02d by %s (%s):"
+msgstr ""
+
+#: src/val-labs.c:139
+#, c-format
+msgid ""
+"It is not possible to assign value labels to long string variables such as "
+"%s."
+msgstr ""
+
+#: src/val-labs.c:186
+msgid "String expected for value."
+msgstr ""
+
+#: src/val-labs.c:195
+msgid "Number expected for value."
+msgstr ""
+
+#: src/val-labs.c:199
+#, c-format
+msgid "Value label `%g' is not integer."
+msgstr ""
+
+#: src/val-labs.c:209
+msgid "Truncating value label to 60 characters."
+msgstr ""
+
+#: src/val-labs.c:242
+msgid "Value labels:"
+msgstr ""
+
+#: src/val-labs.c:259
+msgid "    (no value labels)\n"
+msgstr ""
+
+#: src/var-labs.c:55
+msgid "String expected for variable label."
+msgstr ""
+
+#: src/var-labs.c:61
+msgid "Truncating variable label to 120 characters."
+msgstr ""
+
+#: src/var-labs.c:89
+msgid "Variable labels:\n"
+msgstr ""
+
+#: src/var-labs.c:96
+msgid "(no variable label)"
+msgstr ""
+
+#: src/vector.c:80
+#, c-format
+msgid "Vector name %s is given twice."
+msgstr ""
+
+#: src/vector.c:86
+#, c-format
+msgid "There is already a vector with name %s."
+msgstr ""
+
+#. There's more than one vector name.
+#: src/vector.c:105
+msgid ""
+"A slash must be used to separate each vector specification when using the "
+"long form.  Commands such as VECTOR A,B=Q1 TO Q20 are not supported."
+msgstr ""
+
+#: src/vector.c:139
+msgid "Vectors must have at least one element."
+msgstr ""
+
+#: src/vector.c:153
+#, c-format
+msgid "%s%d is too long for a variable name."
+msgstr ""
+
+#: src/vector.c:195
+msgid ""
+"The syntax for this command does not match the expected syntax for either "
+"the long form or the short form of VECTOR."
+msgstr ""
+
+#: src/weight.c:61
+msgid "The weighting variable must be numeric."
+msgstr ""
+
+#: src/weight.c:66
+msgid "The weighting variable may not be scratch."
+msgstr ""
+
+#: src/weight.c:106
+msgid "bad weighting variable, canceling\n"
+msgstr ""
diff --git a/pref.h.orig b/pref.h.orig
new file mode 100644 (file)
index 0000000..1577094
--- /dev/null
@@ -0,0 +1,268 @@
+/* Let's tell EMACS what language this is: -*- C -*- */
+
+/* Used by separable libraries to enable PSPP-specific features. */
+#define PSPP 1
+
+/*
+ *
+ * Debugging
+ *
+ */
+
+/* Define to get lots of info printed by procedures. */
+/*#define DEBUGGING 1*/
+
+#if DEBUGGING
+#define GLOBAL_DEBUGGING 1
+#endif
+
+/* Define these if DEBUGGING is off and you want to make certain
+   additional optimizations. */
+#if !DEBUGGING
+/* #define PRODUCTION 1 */             /* disable extra function calls */
+/* #define NDEBUG 1 */                 /* disable assert() sanity checks */
+#endif
+\f
+/* Compilers. */
+
+/* Fix Windows lossage. */
+#ifdef __WIN32__
+#undef __WIN32__
+#define __WIN32__ 1
+#undef __MSDOS__
+#define __MSDOS__ 1
+#endif
+
+/* Fix DJGPP lossage. */
+#if __DJGPP__
+#undef unix
+#undef __unix__
+#endif
+
+/* Fix Cygnus Windows lossage. */
+#if defined (__CYGWIN32__)
+#define unix 1
+#endif
+
+/* Ensure that unix and __unix__ are equivalent. */
+#if defined (unix) || defined (__unix__) || defined (__unix)
+#undef unix
+#define unix 1
+
+#undef __unix__
+#define __unix__ 1
+
+#undef __unix
+#define __unix 1
+#endif
+
+/* Make sure to use the proper keywords. */
+#if __GNUC__ > 1 && !defined (inline)
+#define inline __inline__
+#endif
+
+/* GNU C allows the programmer to declare that certain functions take
+   printf-like arguments, never return, etc.  Conditionalize these
+   declarations on whether gcc is in use. */
+#if __GNUC__ > 1
+#define __attribute__(X) __attribute__ (X)
+#else
+#define __attribute__(X)
+#endif
+
+/* GNU C allows unused variables and parameters to be declared as
+   such. */
+#if __GNUC__ >= 2
+#define unused __attribute__ ((__unused__))
+#else
+#define unused
+#endif
+\f
+/* CPUs. */
+
+/* Check that the floating-point representation is one that we
+   understand. */
+#if FPREP==FPREP_IEEE754
+     
+#if SIZEOF_DOUBLE == 8
+#define second_lowest_flt64 second_lowest_value
+#else
+#error Must define second_lowest_flt64 for your architecture.
+#endif
+
+/* This trick borrowed from gcc-lib/.../include/float.h. */
+#if __GNUC__ && (ENDIAN==BIG || ENDIAN==LITTLE)
+#ifndef __DBL_UNION__
+#define __DBL_UNION__
+union blp_convert_double {
+  unsigned char convert_double_i[8];
+  double convert_double_d;
+};
+#endif /* !defined __DBL_UNION__ */
+#if ENDIAN==LITTLE
+#define SECOND_LOWEST_VALUE                                            \
+       (__extension__ ((union blp_convert_double)                      \
+                       {{0xfe,0xff,0xff,0xff, 0xff,0xff,0xef,0xff}})   \
+        .convert_double_d)
+#elif ENDIAN==BIG
+#define SECOND_LOWEST_VALUE                                            \
+       (__extension__ ((union blp_convert_double)                      \
+                       {{0xff,0xef,0xff,0xff, 0xff,0xff,0xff,0xfe}})   \
+        .convert_double_d)
+#endif /* endianness */
+#endif /* __GNUC__ and known endianness */
+
+#else /* FPREP != FPREP_IEEE754 */
+#error Floating point representation must be known at compile time.
+#endif /* fprep */
+
+/* Figure out which integer type on this system is a signed 32-bit
+   integer. */
+#if SIZEOF_SHORT==4
+  #define int32 short
+#elif SIZEOF_INT==4
+  #define int32 int
+#elif SIZEOF_LONG==4
+  #define int32 long
+#else
+  #error Which one of your basic types is 32-bit signed integer?
+#endif
+
+#if SIZEOF_FLOAT==8
+  #define flt64 float
+  #define FLT64_MAX FLT_MAX
+#elif SIZEOF_DOUBLE==8
+  #define flt64 double
+  #define FLT64_MAX DBL_MAX
+#elif SIZEOF_LONG_DOUBLE==8
+  #define flt64 long double
+  #define FLT64_MAX LDBL_MAX
+#else
+  #error Which one of your basic types is 64-bit floating point?
+  #define flt64 double
+  #define FLT64_MAX DBL_MAX
+#endif
+\f
+/* Environments. */
+
+/* Internationalization. */
+#include <libintl.h>
+
+#if !ENABLE_NLS
+/* If we don't do this then gettext() still monkeys with the string,
+   which causes gcc not to do its checking on printf() format
+   types. */
+#undef gettext
+#define gettext(STRING)                        \
+       STRING
+#endif
+
+#define _(STRING)                              \
+       gettext(STRING)
+     
+#define N_(STRING)                             \
+       STRING
+
+/* Even C emulation of alloca counts as an alloca implementation. */
+#if C_ALLOCA
+#define HAVE_ALLOCA 1
+#endif
+
+/* Define PAGED_STACK if alloca() is supported and the stack can
+   expand arbitrarily.  (Under some broken OSes like DOS and
+   Windows the stack is small and fixed in size.)  This will prevent
+   big alloca() requests (like 1MB). */
+#if HAVE_ALLOCA && unix
+#define PAGED_STACK 1
+#endif
+
+/* Saves on #if's. */
+#if HAVE_ALLOCA && !__CHECKER__
+#define local_alloc(X)                         \
+       alloca(X)
+
+#define local_free(P)                          \
+       do                                      \
+         {                                     \
+         }                                     \
+       while (0)
+#elif !__CHECKER__
+#define local_alloc(X)                         \
+       xmalloc (X)
+     
+#define local_free(P)                          \
+       free (P)
+#else /* __CHECKER__ */
+/* Why define these this way?  Because if you do it this way then if
+   you try to free a block returned by local_alloc() with the free()
+   function, you get an error message. */
+#define local_alloc(X)                                 \
+       ((void *) (((char *) (xmalloc (X+16))) + 16))
+
+#define local_free(P)                          \
+       free (((char *) P) - 16)
+#endif /* __CHECKER__ */
+\f
+/* Filesystems. */
+
+/* Directory separator character for this OS, if applicable. */
+#if !__MSDOS__
+#define DIR_SEPARATOR '/'
+#elif
+#define DIR_SEPARATOR '\\'
+#endif
+
+/* Path delimiter character. */
+#if !__MSDOS__
+#define PATH_DELIMITER ':'
+#else
+#define PATH_DELIMITER ';'
+#endif
+
+/* MSDOS mkdir() takes only one argument. */
+#if __MSDOS__ && !__DJGPP__
+#define mkdir(path, mode)                      \
+       mkdir (path)
+#endif
+\f
+/* Options. */
+
+/* Approximate amount of memory, in bytes, to allocate before paging
+   to disk.  */
+#define MAX_WORKSPACE (4*1024*1024)    /* 4 MBytes */
+
+/* (libhistory) The location for the history file that records
+   commands entered interactively.  Tilde expansion is performed. */
+#define HISTORY_FILE "~/.pspp_history"
+
+/* (libhistory) Maximum number of commands to record in history
+   file. */
+#define MAX_HISTORY 500
+\f
+/* Output drivers. */
+
+/* Define to exclude the HTML output driver. */
+/* #define NO_HTML 1 */
+
+/* Define to exclude the PostScript and Encapsulated PostScript
+   driver. */
+/* #define NO_POSTSCRIPT 1 */
+\f
+/* Procedure-specific options. */
+
+/* CROSSTABS: Maximum number of tables to process in one pass. */
+#define MAX_CROSSTAB_TABLES 32
+
+/* FREQUENCIES: Define to allow bars greater than 1/2" wide. */
+/* #define ALLOW_HUGE_BARS 1 */
+
+/* FREQUENCIES: Minimum, maximum number of bars in a histogram.  The
+   number is based on the number of unique values of the variable,
+   unless overridden.  */
+#define MIN_HIST_BARS 3
+#define MAX_HIST_BARS 20
+
+/* FREQUENCIES: Density of polyline used to approximate the normal
+   curve.  The value is the number of samples taken per chart.  Higher
+   values give smoother curves.  */
+#define POLYLINE_DENSITY (MAX_HIST_BARS*4)
diff --git a/reconfigure b/reconfigure
new file mode 100755 (executable)
index 0000000..8fc43d5
--- /dev/null
@@ -0,0 +1,61 @@
+#! /bin/sh -e
+
+nls=--disable-nls
+clean=no
+include_deps=yes
+
+while true; do
+  if [ "$1" = "--enable-nls" ]; then
+    nls=
+  elif [ "$1" = "--disable-nls" ]; then
+    nls=--disable-nls
+  elif [ "$1" = "--clean" ]; then
+    clean=yes
+  elif [ "$1" = "--not-clean" ]; then
+    clean=no
+  elif [ "$1" = "--no-include-deps" ]; then
+    include_deps=no
+  elif [ "$1" = "--include-deps" ]; then
+    include_deps=yes
+  elif [ "$1" = "--help" ]; then
+    echo "Usage: $0 [--enable-nls] [--clean] [--include-deps] ..."
+    exit 0
+  else
+    break
+  fi
+  
+  shift
+done
+
+# Extract PSPP version number.
+VERSION=`sed -ne 's/^.*\[//;s/].*$//;/^[0-9]*\.[0-9]*\.[0-9]*$/p' < configure.in`
+
+if [ "$clean" = "no" ]; then
+  echo "Generating a Makefile for cleaning..."
+  if [ ! -f Makefile ]; then
+    aclocal
+    autoheader
+    make -f Makefile.am docfiles VERSION=$VERSION
+    automake --foreign
+    autoconf
+    ./configure $nls $*
+  fi
+  echo "Cleaning the distribution..."
+  make -k maintainer-clean || true
+fi
+
+echo "Configuring..."
+aclocal
+autoheader
+make -f Makefile.am docfiles VERSION=$VERSION
+automake
+autoconf
+./configure $nls $*
+make mostlyclean || true       # This causes dependencies to be generated
+
+if [ "$include_deps" = "yes" ]; then
+  echo "Configuring with included dependencies..."
+  automake --include-deps
+  autoconf
+  ./configure $nls $*
+fi
diff --git a/src/ChangeLog b/src/ChangeLog
new file mode 100644 (file)
index 0000000..c3877c4
--- /dev/null
@@ -0,0 +1,7067 @@
+Sun Jan  2 21:40:13 2000  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Reorganized.  Put locale dir in version.c instead
+       of passing it to each compile command.  Only put local gmp libs in
+       LD_ADD if not installed on system.  Remove `boast' target.
+
+       * All source files: struct and union typedefs eliminated.
+       `sizeof type' replaced by `sizeof object' where practical.  Moved
+       `unused' qualifiers from start to end of declarations for gcc
+       2.7.2 compatibility.  Change `while (1)' to `for (;;)'.  Made
+       assertions on pointers strictly compliant.  Removed _ prefixes on
+       some function parameter names.
+
+       * alloc.c: New source file, containing these external linkage
+       functions removed from common.c: xmalloc, xcalloc, xrealloc,
+       xstrdup.
+
+       * arena.c: Removed.
+       
+       * arena.h: Removed.
+
+       * ascii.c: Migrated from arenas to pools.
+       (struct ascii_driver_ext) ops[], box[], fonts[] changed from
+       c_string to len_string.  All references changed.
+       (ascii_option) Signature changed to comply to new output.c
+       interface.
+       (count_fancy_chars) Removed.
+       (delineate) Removed support for rich text.
+       (ascii_text_metrics) Ditto.
+       (text_draw) Ditto.
+       (output_shorts) Change `box', `off', `on' from c_string to
+       len_string.  Change `remaining' from int to size_t.
+       (ascii_close_page) Make page numbering less haphazard.
+
+       * autorecode.c: Migrate from arenas to pools.
+
+       * avl.c: Migrate from arenas to pools.  Synch from libavl 1.4.0.
+
+       * bitvector.h: New file containing these macros from misc.h:
+       SET_BIT, CLEAR_BIT, SET_BIT_TO, TEST_BIT, BIT_INDEX.
+
+       * command.c: (struct command) cmd1, cmd2, cmd3 members changed to
+       word[3].  ncmd removed.
+       (var empty_string) Removed.
+       (var cmd_table) Declaration updated.
+       (var cmdtab) Removed.
+       (cmp_command) Removed.
+       (split_words) Rewritten to use strtok_r().
+       (init_cmd_parser) Renamed cmd_init().  Rewritten.
+       (find_command) Removed.
+       (FILE_TYPE_okay) Rewritten.
+       (cmd_parse) Rewritten.  Semantics of the return value of command
+       handlers has changed: they must now return one of the new CMD_*
+       enumerals, rather than a magic value.  This meant that all
+       commands had to be modified, and they were.
+       (figure_out_command) New function.
+
+       * command.def: Add CORRELATIONS, PEARSON CORRELATIONS.  Add
+       #defines for INIT, INPU, etc.
+
+       * command.h: New CMD_* enum series.
+       (cur_proc) Make const char *, not char *.
+       (cmd_init) Prototype.
+       (cmd_parse) Ditto.
+
+       * common.c: Removed.
+
+       * common.h: Removed.
+
+       * correlations.q: New file.
+
+       * crosstabs.q: Migrate from arenas to pools.  Migrate to new-style
+       q2c.
+       (custom_tables) Renamed crs_custom_tables().
+       (custom_variables) Renamed crs_custom_variables().
+       (calc_integer) Add in some `const' qualifiers.
+       (table_value_missing) Change from a_string to len_string.
+       (float_M_suffix) Change from a_string to len_string.
+
+       * data-in.c: Rewritten.  All references to
+       parse_string_as_format() changed to data_in().
+
+       * data-in.h: New file.
+
+       * data-list.c: Change DLS_* from #define's to enums.  Move from
+       rpd_msg() to tmsg().
+       (RPD_ERR) New #define.
+       (do_reading) Change dfm_push_cust() to dfm_push(), pop_cust() to
+       dfm_pop().
+       (read_from_data_list_fixed) Change from old
+       parse_string_as_format() to new data_in().
+       (read_from_data_list_free) Ditto.
+       (read_from_data_list_list) Ditto.
+       (cmd_repeating_data) Modify approach to checking for end of
+       command.
+       (rpd_msg) Removed.
+       (rpd_parse_record) Change from old parse_string_as_format() to new
+       data_in().  Change from old convert_format_to_string() to new
+       data_out().
+       (read_one_set_of_repetitions) Change dfm_push_cust() to
+       dfm_push(), pop_cust() to dfm_pop().
+
+       * data-out.c: Rewritten.  All references to
+       convert_format_to_string() changed to data_out().
+
+       * descript.q: Migrate to new q2c.
+       (cmd_descriptives) Removed.
+       (internal_cmd_descriptives) Renamed cmd_descriptives ().
+       (custom_variables) Renamed dsc_custom_variables().
+
+       * dfm.c: (struct dfm_fhuser_ext) `ln' removed.  All references
+       removed.
+       (open_file_r) Initialize h->where.line_number.  Migrate to new
+       struct string.
+       (open_file_w) Initialize h->where.line_number.
+       (read_record) Change from ext->ln to h->where.line_number.
+       Migrate to struct string.
+       (dfm_put_record) Rephrased.
+       (dfm_push_cust) Renamed dfm_push(), rewritten.
+       (dfm_pop) New function.
+
+       * error.c: All references updated.
+       (glob var error_count) Renamed err_err_count.
+       (glob var warning_count) Renamed err_warning_count.
+       (glob var error_already_flagged) Renamed err_already_flagged.
+       (glob var verbosity) Renamed err_verbosity.
+       (glob var cust_fn) Removed.
+       (glob var cust_ln) Removed.
+       (static var file_loc) New.
+       (static var nfile_loc) New.
+       (static var mfile_loc) New.
+       (tmsg) New function.
+       (push_cust) Removed.
+       (pop_cust) Removed.
+       (msg) Rewritten.
+       (static var terminating) Removed.
+       (failure) Renamed err_failure().
+       (hcf) Renamed err_hcf().
+       (err_push_file_locator) New function.
+       (err_pop_file_locator) New function.
+       (err_location) New function.
+       (check_error_count) Renamed err_check_count().
+       (vmsg) Renamed err_vmsg().  Interface changed.
+       (verbose_msg) Removed.
+       (err_cond_fail) New function.
+       (error_break) Renamed err_break().
+
+       * error.h: All references updated.
+       (enum MSG_CLASS_COUNT) Renamed ERR_CLASS_COUNT.
+       (enum ERR_CLASS_MASK, ERR_VERBOSITY_SHIFT, ERR_VERBOSITY_MASK)
+       New.
+       (struct file_locator) New.
+       (struct error) New.
+       (macro verbose_msg) Removed.
+       (macro cond_fail) Removed.
+
+       * expr-opt.c: (evaluate_tree) sizeof(char) == 1.
+
+       * expr-prs.c: Reorganized.  All references updated.
+       (exprtypename) Renamed expr_type_name().
+       (typename) Renamed type_name().
+       (free_expression) Renamed expr_free().
+       (parse_expression) Renamed expr_parse().  Uses new type_check()
+       function.
+       (init_functab) Renamed init_func_tab().
+       (type_check) New function.
+       (parse_or) Rewritten to use new allocate_nonterminal() and
+       append_nonterminal_arg() functions.
+       (parse_and) Ditto.
+       (parse_not) Ditto.
+       (parse_rel) Ditto.  Also simplified logic.
+       (parse_add) Ditto.
+       (parse_mul) Ditto.
+       (parse_neg) Ditto.
+       (parse_exp) Ditto.
+       (SYSMIS_func) Ditto.
+       (VALUE_func) Rephrased.
+       (CONCAT_func) Fix memory leak by replacing free by free_node on
+       lossage.
+       (generic_str_func) Ditto.
+       (parse_function) Ditto.  Also rephrasings.  Uses bsearch() to find
+       function.
+       (allocate_nonterminal) New function.
+       (append_nonterminal_arg) New function.
+       (static func_tab[]) Now at file level.
+       (cmp_func) Moved.
+       (init_func_tab) Moved.  Now just uses qsort() to sort func_tab[].
+
+       * expr.h: (enum series OP_*) Moved to exprP.h.
+       (OP_* defines) Ditto.
+       (struct op_desc) Ditto.
+       (global ops[]) Ditto.
+       (struct num_con_node) Ditto.
+       (struct str_con_node) Ditto.
+       (struct var_node) Ditto.
+       (struct lag_node) Ditto.
+       (struct casenum_node) Ditto.
+       (struct nonterm_node) Ditto.
+       (union any_node) Members renamed.
+       (struct sys_node) Removed.
+       (struct val_node) Removed.
+       (operator typedef) Removed.
+       (typedef exprtype) Removed.
+       (enum series EX_*) Moved to exprP.h.
+       (struct expression) Ditto.  Also renamed a lot of the members.
+       (PXP_* defines) Changed to enums.
+       (free_node prototype) Moved to exprP.h.
+
+       * file-handle.h: (struct file_handle) New member `where'.
+
+       * file-handle.q: Migrated to new q2c format.
+       (prepend_current_directory) Removed (dead code).
+       (cmd_file_handle) Incorporated all of internal_cmd_file_handle().
+       (fh_get_handle_by_filename) Removed dead code.
+       Set new `where' member.
+
+       * file-type.c: (file_type_source_read) References to
+       parse_string_as_format() changed to data_in().
+       dfm_push_cust()/pop_cust() changed to dfm_push()/dfm_pop().
+
+       * filename.c: All references updated.
+       (init_filename) Renamed fn_init().
+       (expand_line) Removed.
+       (macro EXPAND_LINE) Removed.
+       (interp_vars) Renamed fn_interp_vars().  Now uses st_*() instead
+       of custom functions.
+       (gnu_getcwd) Renamed fn_get_cwd(), rewritten.
+       (tilde_expand) Renamed fn_tilde_expand(), uses ds_*().
+       (normalize_filename) Renamed fn_normalize().
+       (search_path) Renamed fn_search_path(), rewritten.
+       (prepend_dir) Renamed fn_prepend_dir().
+       (blp_getenv) Renamed fn_getenv().
+       (blp_dirname) Renamed fn_dirname().
+       (fn_basename) New function, not used.
+       (absolute_filename_p) Renamed fn_absolute_p().
+       (is_special_filename) Renamed fn_special_p().
+       (file_exists) Renamed fn_exists_p().
+       (readlink_malloc) Renamed fn_readlink().
+       (getenv_default) Renamed fn_getenv_default().
+       (open_file) Renamed fn_open().
+       (close_file) Renamed fn_close().
+       (open_file_ext) Renamed fn_open_ext().
+       (close_file_ext) Renamed fn_close_ext().
+
+       * font.h: Migrate from arenas to pools.
+
+       * format.c: (parse_format_specifier_name) Deal with ds_* strings.
+
+       * frequencies.g: Migrate from arenas to pools.
+
+       * frequencies.q: Migrate to new q2c version.  Migrate from arenas
+       to pools.
+
+       * getline.c: All references updated.
+       (global getl_buf) Changed from char * to struct string.
+       (static getl_include_path) Ditto.
+       (global getl_buf_len) Removed.
+       (global getl_buf_size) Removed.
+       (getl_include_path) Deal with new getl_buf, getl_include_path.
+       (getl_uninitialize) New function.
+       (getl_get_current_directory) Rewritten.
+       (getl_clear_include_path) Rewritten.
+       (getl_add_include_dir) Rewritten.
+       (getl_add_file) Assertion fixed.
+       (getl_add_virtual_file) Change initial value of `remaining_loops'
+       from 2 to 1.
+       (welcome) Rewritten.
+       (handle_line_buffer) Make static.  Change logic to make
+       getl_add_virtual_file() change sensible.  Use ds_*() strings.
+       (getl_read_line) Use ds_*() strings.  Implement SET ECHO.
+       (getl_close_file) Moved.
+       (getl_location) New function.
+
+       * getline.h: All references updated.
+       (macro curln) Removed.
+       (macro curfn) Removed.
+       (macro am_interactive) Renamed getl_am_interactive.
+       (macro am_reading_script) Renamed getl_reading_script.
+
+       * glob.c: (global fmt_parse_ignore_error) Removed.
+       (init_glob) Use locale_dir not LOCALEDIR.  Use feholdexcept() on
+       systems that support it (C99).  Turn off SET ECHO by default.  No
+       necessary julcal initialization anymore.
+
+       * groff-font.c: Migrate from arenas to pools.
+       (groff_read_font) Use err_push_file_locator().
+       (groff_read_DESC) Ditto.
+       (font_msg) Use tmsg().
+
+       * hash.c: (hsh_sort) Fix debug code.
+       [GLOBAL_DEBUGGING] Include stdio.h.
+
+       * hash.h: (macro force_hsh_insert) Rephrase.
+
+       * heap.c: Rewritten.
+       
+       * heap.h: Rewritten.
+
+       * html.c: (html_option) Change from outp_value to struct string.
+       (postopen) Change from curfn to getl_location().
+       (escape_string) Remove rich-text code.  Signature changed.
+       (output_tab_table) Switch from a_string to struct len_string.
+       Remove rich text support.
+
+       * lexer.c: All references updated.  Largely rewritten.  Major
+       changes listed below.  Removed tagged quote support.  Adapted to
+       struct string tokstr.
+       (global tokstr) Changed to struct string.
+       (global tokstr_size) Removed.
+       (global tokstr_len) Removed.
+       (global tokid) New.
+       (global tokint) Removed.
+       (global toklongstr) Removed.
+       (C* defines) Removed.
+       (static tbl[]) Removed.
+       (static id[]) Removed.
+       (static une[]) Removed.
+       (discard_line) Renamed lex_discard_line().
+       (get_entire_line) Renamed lex_entire_line().
+       (get_rest_of_line) Renamed lex_rest_of_line().
+       (get_dotted_rest_of_line) Merged into lex_rest_of_line().
+       (make_hexit) Removed.
+       (syntax_error) Renamed lex_error().  Return value removed.
+       (get_token_representation) Renamed lex_token_representation().
+       (putback) Renamed lex_put_back().
+       (putfwd) Renamed lex_put_forward().
+       (convert_negative_to_dash) Renamed lex_negative_to_dash().
+       (set_prog) Renamed lex_set_prog().
+       (init_lex) Renamed lex_init().
+       (reset_eof) Renamed lex_reset_eof().
+       (lookahead) Renamed lex_look_ahead().
+       (check_id) Rewritten.
+       (yylex) Renamed lex_get(), rewritten.
+       (lex_end_of_command) New function.  Many commands were rephrased
+       using this.
+       (lex_integer_p) New function.  Replaces compare of tokint against
+       NOT_LONG.
+       (lex_integer) New function.  Replaces tokint.
+       (match_tok) Renamed lex_match().
+       (match_id) Renamed lex_match_id().
+       (match_int) Renamed lex_match_int().
+       (force_match_id) Renamed lex_force_match_id(), added return value.
+       (force_match) Renamed lex_force_match(), added return value.
+       (force_string) Renamed lex_force_string(), added return value.
+       (force_int) Renamed lex_force_int(), added return value.
+       (lex_id_match_len) New function.
+       (id_match) Renamed lex_id_match(), rewritten.
+       (get_line) Renamed lex_get_line().
+       (preprocess_line) Renamed lex_preprocess_line().
+       (tokname) Renamed lex_token_name().
+       (bin_value_func) Removed.
+       (oct_value_func) Removed.
+       (hex_value_func) Removed.
+       (unexpected_eof) New function.
+       (convert_numeric_string_to_char_string) New function.
+       (parse_string) Rewritten, signature changed.
+       (add_tokstr_char) Removed.
+       (add_tokstr_unsigned) Removed.
+       (add_tokstr_string) Removed.
+       (parse_tagged_quote) Removed.
+       (skip_comment) Renamed lex_skip_comment().
+
+       * lexer.h: All references updated.
+       (macro is_id1) Renamed CHAR_IS_ID1.
+       (macro is_idn) Renamed CHAR_IS_IDN.
+       (token names ID, NUM, STRING, STOP, ... WITH, EXP) Renamed with
+       prefix T_: T_ID, T_NUM, T_STRING, T_STOP, ... T_WITH, T_EXP.
+       (macro get_token) Removed.
+       (macro id_match) Removed.
+       (macro force_match_id) Removed.
+       (macro force_match) Removed.
+       (macro force_string) Removed.
+       (macro force_int) Removed.
+       (macro force_num) Removed.
+       (macro force_id) Removed.
+
+       * lexerP.h: Removed.
+
+       * list.q: Migrated to new q2c format.
+       (write_line) Deal with struct len_string.
+       (write_varname) Ditto.
+       (write_fallback_headers) Ditto.
+
+       * magic.c: New file, incorporating the following global variables
+       previously in other files: endian, second_lowest_value.  And both
+       of those are conditional on #define's.
+
+       * magic.h: New file, incorporating the following global variable
+       declarations: endian, second_lowest_value, and the following macro
+       declarations: NOT_DOUBLE, NOT_LONG, NOT_INT.
+
+       * main.c: Added declarations of pgmname, finished, curdate,
+       start_interactive.
+       (main) Call new parse_script() function.
+       (parse_script) New function.
+       (execute_command) New function.
+       (dump_token) Removed.
+       (handle_error) New function.
+
+       * matrix.c: New file.
+
+       * matrix.h: New file.
+
+       * matrix-data.c: Migrated from arenas to pools.
+       (mget_token) Change from parse_string_as_format() to data_in().
+
+       * means.q: Migrate to new q2c.
+       (custom_tables) Renamed mns_custom_tables().
+       (custom_crossbreak) Renamed mns_custom_crossbreak().
+       (custom_variables) Renamed mns_custom_variables().
+
+       * mis-val.c: (static var width) Changed from `int' to `size_t'.
+       (parse_varnames) Prototype.
+       (parse_numeric) Rephrasings.
+       (parse_alpha) Adapt to new struct string tokstr.
+
+       * misc.c: (intlog10) Rewritten.
+       (spacing) Removed.
+       (ansi_rand) Renamed real_rand(), moved into random.c.
+       (ansi_srand) Renamed real_srand(), moved into random.c.
+       (setup_randomize) Moved to random.c.
+       (rand_uniform) Ditto.
+       (rand_normal) Ditto.
+       (rand_simple) Ditto.
+       (get_config_line) Removed.
+       (reverse) Removed (dead code).
+
+       * misc.h: (macro SET_BIT) Moved to bitvector.h.
+       (macro CLEAR_BIT) Ditto.
+       (macro TEST_BIT) Ditto.
+       (macro SET_BIT_TO) Ditto.
+       (macro BIT_INDEX) Ditto.
+
+       * output.c: (outp_read_devices) Move to err_push_file_locator()
+       from push_cust().  Use struct string.
+       (expand_op_tokstr) Removed.
+       (static var op_tokstr) Changed to struct string.
+       (static var op_tokstr_size) Removed.
+       (tokener) Rephrasings.  Use struct string.
+       (parse_options) Use struct string.
+       (destroy_driver) Fix assertion.
+       (outp_get_paper_size) Move to err_push_file_locator().
+       [0] Removed dead code.
+       (outp_string_width) Move to len_string.
+
+       * output.h: Comment fixes.
+       (TAG_* enum series) Removed.
+       (struct outp_value) Removed.
+       (enum OUTP_T_FANCY) Removed.
+       (struct outp_text) `s' changed from a_string to len_string.
+       (struct outp_class) `option' change arg 3 from outp_value to
+       struct string.
+
+       * pfm-read.c: (corrupt_msg) Rewritten.
+
+       * pfm-write.c: (bufwrite) Fix assertion.
+
+       * pool.c: New file, reference version.
+
+       * pool.h: New file, reference version.
+
+       * postscript.c: (ps_font_sizes) Fix assertion.
+       (ps_option) Change arg 3 from outp_value to struct string.
+       Adapt to struct string.
+       (macro output_line) Removed.
+       (macro add_string) Removed.
+       (output_encodings) Adapted to struct string.  Moved to
+       err_push_file_locator().
+       (find_encoding_file) Fix assertion.
+       (read_ps_encodings) Move to err_push_file_locator().
+       (postopen) Use getl_location() instead of curfn.
+       (out_text_plain) Move to len_string.
+       (text) Ditto.  Remove rich text support.
+
+       * print.c: (cmd_print) Remove now-unneeded resource cleanup code.
+       (cmd_print_eject) Ditto.
+       (cmd_write) Ditto.
+       (internal_cmd_print) Now cleans up after itself.  Uses
+       fh_parse_file_handle() now.
+       (cmd_print_space) Use PXP_NUMERIC to type-check.
+
+       * q2c.c: Overhauled.  Removed _("") i18n support.  All references
+       updated.  All output functions updated to handle structures rather
+       than local or static variables.  Adapt to new PSPP lex_*()
+       functions.
+       (macro _) Removed.
+       (macro N_) Removed.
+       (macro MAX_N_SBC) Removed.
+       (global bare) Removed.
+       (enum STRING) Renamed T_STRING.
+       (enum ID) Renamed T_ID.
+       (get_buffer) Buffer size increased.
+       (strlower) Renamed st_lower(), rephrased.
+       (strupper) Renamed st_upper(), rephrased.
+       (skip_ws) New function.
+       (get_line) Don't special-case any types of lines (like those
+       beginning with ! or $, for instance).
+       (get_token) Renamed lex_get().  Rephrased.
+       (static var `prefix') New.
+       (parse) New function.
+       (parse_setting) Minor rephrasing.
+       (dump_specifier_vars) Ditto.
+       (make_identifier) Put null terminator on identifier, duh.
+       (dump_vars) Renamed dump_declarations().  Never indent.  Never
+       static.  Output changed entirely.
+       (dump_specifier_init) Rephrase.
+       (dump_vars_init) No index variable needed.  Other modifications.
+       (dump_parser) Don't parse command name.  Do dump functions instead
+       of just code fragments.
+       (dump_free) Dump function instead of code fragment.
+       (recognize_directive) New function.
+       (main) Use recognize_directive().  Don't rely on magic $ line
+       beginning: instead, parse comments.  Update list of headers.
+
+       * random.c: New file, containing the following functions:
+       real_rand(), real_srand(), setup_randomize, shuffle, rand_uniform,
+       rand_normal, rand_simple.
+
+       * random.h: New file.
+
+       * recode.c: (cmd_recode) Merge internal_cmd_recode() into this
+       function.  `max_src_width', `max_dst_width' changed to size_t.
+       (internal_cmd_recode) Removed.
+       (parse_dest_spec) Merge similar cases.
+       (parse_src_spec) Add assertion.
+
+       * repeat.c: (recognize_keyword) New function.
+       (internal_cmd_do_repeat) Parse and handle PRINT keyword on END
+       REPEAT.  Improve recognition of END REPEAT (use
+       recognize_keyword()).  Move from curfn to getl_location().  Use
+       struct string.
+               
+       (perform_DO_REPEAT_substitutions) Adapt to struct string.
+
+       * set.q: Adapt to new q2c.
+       (cmd_set) Range-check some values better.
+       (custom_blanks) Renamed stc_custom_blanks().
+       (custom_length) Renamed stc_custom_length().
+       (custom_results) Renamed stc_custom_results().
+       (custom_seed) Renamed stc_custom_seed().
+       (custom_width) Renamed stc_custom_width().
+       (custom_format) Renamed stc_custom_format().
+       (custom_journal) Renamed stc_custom_journal().
+       (custom_color) Renamed stc_custom_color().
+       (custom_listing) Renamed stc_custom_listing().
+       (custom_disk) Renamed stc_custom_disk().
+       (custom_log) Renamed stc_custom_log().
+       (custom_rcolor) Renamed stc_custom_rcolor().
+       (custom_viewlength) Renamed stc_custom_viewlength().
+       (custom_workdev) Renamed stc_custom_workdev().
+
+       * settings.h: Not necessary to include format.h any longer.
+
+       * sfm-read.h: (macro bswap_int32) Moved here from sfmP.h.
+       (corrupt_msg) Rewritten.
+
+       * sort.c: Adapt to rewritten heap ADT.
+
+       * str.c: (aa_strcpy) Removed.
+       (ab_strcpy) Removed.
+       (ac_strcpy) Removed.
+       (ba_strcpy) Removed.
+       (bb_strcpy) Removed.
+       (ca_strcpy) Removed.
+       (aa_strdup) Removed.
+       (aa_strdupcpy) Removed.
+       (ba_strdup) Removed.
+       (sa_strdup) Removed.
+       (memrev) Renamed mm_reverse().
+       (memrmem) Renamed mm_find_reverse().
+       (cmp_str) Renamed st_compare_pad().
+       (strmaxcpy) Removed.
+       (strbarepadcpy) Renamed st_bare_pad_copy(), signature changed.
+       (strbarepadlencpy) Renamed st_bare_pad_len_copy(), signature
+       changed.
+       (strpadcpy) Renamed st_pad_copy(), signature changed.
+       (blpstrset) Removed.
+       (ds_create) New function.
+       (ds_init) New function.
+       (ds_replace) New function.
+       (ds_destroy) New function.
+       (ds_clear) New function.
+       (ds_extend) New function.
+       (ds_shrink) New function.
+       (ds_truncate) New function.
+       (ds_length) New function.
+       (ds_size) New function.
+       (ds_value) New function.
+       (ds_end) New function.
+       (ds_concat) New function.
+       (ds_concat_buffer) New function.
+       (ds_printf) New function.
+       (ds_putchar) New function.
+       (ds_getline) New function.
+       (ds_get_config_line) New function derived from the old
+       misc.c:get_config_line().
+       (ls_create) New function.
+       (ls_create_buffer) New function.
+       (ls_init) New function.
+       (ls_shallow_copy) New function.
+       (ls_destroy) New function.
+       (ls_null) New function.
+       (ls_null_p) New function.
+       (ls_empty_p) New function.
+       (ls_length) New function.
+       (ls_value) New function.
+       (ls_end) New function.
+
+       * str.h: Reformatted.
+       (struct a_string) Removed.
+       (struct b_string) Removed.
+       (struct c_string) Removed.
+       (struct len_string) New.
+       (struct string) New.
+       (macro as_streq) Removed.
+       (macro bs_streq) Removed.
+       (macro cs_streq) Removed.
+       (macro sa_streq) Removed.
+       (macro sb_streq) Removed.
+       [__GNUC__] (inline function ds_putchar) New function.
+       [__GNUC__] (inline function ds_length) New function.
+       [__GNUC__] (inline function ds_value) New function.
+       [__GNUC__] (inline function ds_end) New function.
+
+       * sysfile-info.c: (cmd_sysfile_info) Rephrased.
+       (display_vectors) Fix missing i18n.
+
+       * t-test.q: Migrate to new q2c.
+
+       * tab.c: Migrate from arenas to pools.
+       (tab_create) Use struct len_string.
+       (tab_realloc) Ditto.
+       (text_format) Ditto.
+       (tab_joint_text) Ditto.
+       (tab_natural_width) Remove rich text support.
+       (tab_natural_height) Ditto.
+       (tab_output_text) Handle TAT_FIX.
+       (tab_raw) Change arg from a_string to len_string.
+       (tabi_driver) Fix assertion.  Use struct len_string.
+       (render_strip) Use struct len_string.  Remove rich text support.
+       Add `const' qualifiers.
+
+       * tab.h: (enum TAB_RICH) Remove.
+       (enums TAB_COL_NONE, TAB_COL_DONE) New.  Where appropriate,
+       SOM_COL_* updated to read TAB_COL_*.
+       (struct tab_table) Change arena to pool.  Change a_string to
+       len_string.
+
+       * temporary.c: (restore_dictionary) Rewrite Checker code.
+
+       * var.h: (macros MAX_SHORT_STRING, MIN_LONG_STRING, SYSMIS,
+       LOWEST, HIGHEST) Moved here from common.h.
+       (typedef any_trns) Removed.  All references changed to `struct
+       trns_header'.
+
+       * vars-atr.c: (force_create_variable) Fix assertion.
+       (force_dup_variable) Fix assertion.
+       
+Thu Jun  3 18:40:42 1999  Ben Pfaff  <blp@gnu.org>
+
+       Using alphanumeric variables in functions under AGGREGATE
+       segfaulted.  Fixed.  Thanks to Dr. Dirk Melcher
+       <BZN-mdksh@t-online.de> for reporting this bug.
+       
+       * aggregate.c: (parse_aggregate_functions) When setting the
+       FSTRING bit, also allocate memory for the `string' member of
+       agr_next.
+       (free_aggregate_functions) Free iter->string.  Don't use the
+       non-function bits when indexing the array of functions.
+       [DEBUGGING] (debug_print) Don't use the non-function bits when
+       indexing the array of functions.        
+
+Sun May 30 00:00:54 1999  Ben Pfaff  <blp@gnu.org>
+
+       Under certain circumstances, the final case would be omitted from
+       the results of an AGGREGATE operation.  Fixed.  Thanks to Dr. Dirk
+       Melcher <BZN-mdksh@t-online.de> for reporting this bug.
+       
+       * aggregate.c (agr_00x_end_func): Increment number of cases in
+       sink before writing case.  For streams that keep track of how many
+       cases there are based on this value, this means that the last case
+       will be read in on the next stream read.
+
+Sat May 29 22:03:31 1999  Ben Pfaff  <blp@gnu.org>
+
+       Undefined behavior was invoked by referencing a freed pointer.
+       
+       * vfm.c (memory_stream_write): Free pointer *after* checking for
+       non-null status.
+
+Sat May 29 22:02:22 1999  Ben Pfaff  <blp@gnu.org>
+
+       A wrong record size was displayed when paging the active file to
+       disk.
+       
+       * vfm.c: (memory_stream_write) Fix off-by-one error.
+
+Sat May 29 21:50:26 1999  Ben Pfaff  <blp@gnu.org>
+
+       Not having enough temporary space for sorting caused a core dump.
+       Fixed.
+       
+       * sort.c: (allocate_cases) Initialize i.
+
+Sat May 29 21:40:54 1999  Ben Pfaff  <blp@gnu.org>
+
+       Syntax errors in function descriptions on AGGREGATE caused core
+       dumps.  Fixed.
+       
+       * aggregate.c (cmd_aggregate): Don't free agr_dict after calling
+       free_aggregate_functions(), since that function already frees
+       agr_dict.
+       
+Sat May 29 21:06:10 1999  Ben Pfaff  <blp@gnu.org>
+
+       A null pointer was dereferenced, causing a core dump, when
+       PERCENTILES was specified on FREQUENCIES.  This fixes the problem,
+       but PSPP still doesn't calculate percentiles.  Thanks to Regnor
+       Jernsletten <rjernsle@eunet.no> for reporting this problem.
+       
+       * arena.c: (arena_malloc) If the arena hasn't been initialized
+       already, initialize it.
+
+Sat May 29 20:47:29 1999  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.cygwin: New file supplied by Hankin <hankin@dunno.com>
+       for compilation with Cygnus Windows B20.  Not used by other
+       systems.
+
+Sat May 29 20:36:04 1999  Ben Pfaff  <blp@gnu.org>
+
+       SORT always sorted in ascending order.  Fixed.  Thanks to Dr. Dirk
+       Melcher <BZN-mdksh@t-online.de> for reporting this bug.
+
+       * sort.c: (compare_case_lists) Reverse sense of comparison if
+       sorting in descending order.
+       (compare_record) Ditto.
+
+Tue Mar  9 13:18:54 1999  Ben Pfaff  <blp@gnu.org>
+
+       SPLIT FILE with a string variable caused a core dump.  Fixed.
+
+       * vfm.c: If the variable is a string then make a temporary value
+       struct pointing to it.  The underlying problem is a lot bigger
+       than this (see TODO) but this is a stopgap for the simple case at
+       least.
+       
+Tue Mar  9 13:15:53 1999  Ben Pfaff  <blp@gnu.org>
+
+       Nested INCLUDEs didn't work.  Fixed.
+
+       * getline.c: (getl_include) Set first_line to NULL in allocated
+       structure.
+
+Tue Mar  9 13:13:46 1999  Ben Pfaff  <blp@gnu.org>
+
+       The MATCH FILES procedure set the values of variables not present
+       to 0.  It should have been SYSMIS.  This is now fixed.
+
+       * get.c: (mtf_delete_file_in_place) Replace 0.0 by SYSMIS.
+
+Tue Mar  9 12:52:23 1999  Ben Pfaff  <blp@gnu.org>
+
+       The REMARK command was too aggressive about skipping lines.  It
+       didn't like being the last command in a file.
+
+       * command.c: (cmd_remark) Call get_entire_line() instead of
+       get_line().
+
+Tue Mar  9 12:48:05 1999  Ben Pfaff  <blp@gnu.org>
+
+       Comment parsing wasn't consistent with the rest of the code in its
+       idea of where one command ends and another starts.  This meant
+       that sometimes commands would be mysteriously ignored.  Thanks to
+       Dr. Dirk Melcher <BZN-mdksh@t-online.de> for reporting this bug.
+        
+       * command.c: (parse_cmd) Hand off comment parsing to new function
+       skip_comment() in lexer.c.
+       * lexer.c: (skip_comment) New function.
+
+Wed Jan 20 20:22:07 1999  Ben Pfaff  <blp@gnu.org>
+
+       The TABLE subcommand on MATCH FILES worked only erratically at
+       best.  This fixes it.  Thanks to Dr. Dirk Melcher
+       <BZN-mdksh@t-online.de> for reporting this bug.
+
+       * get.c: (mtf_compare_BY_values) When comparing string values, a
+       difference of 1 is still a difference :-)
+       (mtf_processing) Inverted TABLE reading logic fixed.  Also don't
+       advance TABLE files automatically when matched.  Comment fixes.
+
+Tue Jan 19 22:32:31 1999  Ben Pfaff  <blp@gnu.org>
+
+       VARIABLE LABELS rejected a slash before the first variable
+       specification, contradicting the documentation.  Thanks to Walter
+       M. Gray <graywm@northernc.on.ca> for reporting this bug.
+
+       * var-labs.c: (cmd_variable_labels) Ignore a leading slash in
+       command specification.
+
+Tue Jan 19 22:29:54 1999  Ben Pfaff  <blp@gnu.org>
+
+       Because of an incorrect optimization in memory allocation,
+       CROSSTABS sometimes segfaulted when asked to output multiple
+       tables.  Thanks to Walter M. Gray <graywm@northernc.on.ca> for
+       reporting this bug.
+
+       * crosstabs.q: (postcalc) New variables maxcols, maxcells, which
+       are passed to output_pivot_table() for its use.
+       (output_pivot_table) Instead of assuming the number of columns is
+       constant, keep track with maxcols.  In general mode, use maxcells
+       to determine whether more matrix cells need to be allocated.    
+
+Tue Jan 19 22:27:46 1999  Ben Pfaff  <blp@gnu.org>
+
+       CROSSTABS didn't display value labels for column and row
+       variables.  Thanks to Walter M. Gray <graywm@northernc.on.ca> for
+       reporting this bug.
+
+       * crosstabs.q: (table_value_missing) If the specified value has a
+       value label for this variable, then show it instead of the raw
+       value.
+       (display_dimensions) Delegate display of value_labels to
+       table_value_missing.
+
+Mon Jan 18 20:04:06 1999  Ben Pfaff  <blp@gnu.org>
+
+       WRITE didn't write line ends.  Fixed.  Thanks to Dr. Dirk Melcher
+       <BZN-mdksh@t-online.de> for reporting this bug.
+
+       * print.c: (print_trns_proc) Write (CR/)LF if PRINT is used _or_
+       if the file isn't declared as binary.
+
+Mon Jan 18 19:56:45 1999  Ben Pfaff  <blp@gnu.org>
+
+       MATCH FILES corrupted memory and dumped core on some syntax
+       errors.  Fixed.
+
+       * get.c: (cmd_match_files) Set file->handle to NULL before
+       jumping to lossage.
+       (mtf_free_file) Don't free a null dictionary.   
+
+Mon Jan 18 19:27:57 1999  Ben Pfaff  <blp@gnu.org>
+
+       MATCH FILES should set numeric values not available to the
+       system-missing value, not to 0.  Thanks to Dr. Dirk Melcher
+       <BZN-mdksh@t-online.de> for reporting this bug.
+
+       * get.c: (mtf_processing) Set unused records to system-missing,
+       not 0.
+
+Mon Jan 18 15:06:46 1999  Ben Pfaff  <blp@gnu.org>
+
+       KEEP didn't work properly on the SAVE procedure.  Fixed.  Thanks
+       to Ralf Geschke <ralf@kuerbis.org> for reporting this bug.
+
+       * temporary.c: (save_dictionary) Initialize var_by_name AVL tree
+       in newly created dictionary, and add each copied variable to the
+       tree.
+Mon Jan 18 15:04:48 1999  Ben Pfaff  <blp@gnu.org>
+
+       Memory leak fix.
+       
+       * get.c: (trim_dictionary) Free variable list for KEEP after
+       finishing with it.
+
+Mon Jan 18 12:57:36 1999  Ben Pfaff  <blp@gnu.org>
+
+       Some systems didn't like the way open_file was coded.  Thanks to
+       Hankin <hankin@rogue.consultco.com> for pointing this out.
+
+       * filename.c: (open_file) Don't try to store stdin, stdout,
+       stderr as part of an array, because that doesn't always work.
+
+Mon Jan 18 12:53:27 1999  Ben Pfaff  <blp@gnu.org>
+
+       The SAVE procedure didn't save long string variables properly.
+       Fixed by this patch.  Thanks to Hankin
+       <hankin@rogue.consultco.com> for this patch.
+       
+       * sfm-write.c: (write_variable) Fix off-by-one error in writing
+       out variable pad records.
+
+Tue Jan  5 14:29:27 1999  Ben Pfaff  <blp@gnu.org>
+
+       Previously, if PRINT SPACE were given a negative argument, it
+       would report an error, then spin in an (almost) infinite loop.
+       This fixes that behavior.
+
+       * print.c: (print_space_trns_proc) After reporting a negative
+       argument, set number of lines to print to 1.
+
+Tue Jan  5 13:59:55 1999  Ben Pfaff  <blp@gnu.org>
+
+       SPSS 8.0 outputs some new record types in its system files, and it
+       allows longer value labels.  Accept these system files.
+
+       * sfm-read.c: (sfm_read_dictionary) Ignore record type 7 subtype
+       11 emitted by SPSS 8.0.
+       
+Tue Jan  5 13:55:50 1999  Ben Pfaff  <blp@gnu.org>
+
+       The LIST procedure was too conservative in allocating space for
+       buffers, which caused a bug that only showed up with very long
+       output variables.  Thanks to Hankin <hankin@dunno.com> for this
+       bug report.
+
+       * list.q: (determine_layout) Allocate 1022 bytes instead of 256.
+
+Tue Jan  5 13:34:34 1999  Ben Pfaff  <blp@gnu.org>
+
+       Typo meant string format specifiers weren't checked properly.  I
+       think that Hankin <hankin@dunno.com> sent me this report, but I'm
+       willing to be corrected on this point.
+       * format.c: (check_string_specifier) Fix obvious typo.  
+
+Tue Jan  5 12:50:42 1999  Ben Pfaff  <blp@gnu.org>
+
+       Using $CASENUM in an expression didn't work.  Here's a fix.
+       Thanks to Dirk Melcher <BZN-mdksh@t-online.de> for reporting this
+       bug.
+        
+       * expr-evl.c: (evaluate_expression) Add OP_CASENUM case.
+
+       * expr-opt.c: (dump_node) OP_CASENUM is acceptable.
+
+Tue Jan  5 12:47:48 1999  Ben Pfaff  <blp@gnu.org>
+
+       The changes in 0.2.1 to fix DATA LIST FREE parsing broke some
+       other behavior, *sigh*.  This patch hopefully fixes that.  This
+       time I've actually tested it.
+
+       Thanks to Hankin <hankin@dunno.com> for reporting this bug.
+
+       * data-list.c: (read_from_data_list_free,
+       read_from_data_list_list) Call parse_string_as_format() directly
+       without mucking around with the field width.
+
+Tue Jan  5 12:31:19 1999  Ben Pfaff  <blp@gnu.org>
+
+       Occasionally, you may encounter a script that wants to be
+       interpreted in interactive mode.  Make -i emulate this behavior to
+       allow such scripts to be executed with PSPP.
+
+       Thanks to Hankin <hankin@dunno.com> for reporting this behavior.
+
+       * cmdline.c: (pre_syntax_message[]) Update -i description.
+
+       * lexer.c: (preprocess_line) When getl_interactive is 2 (i.e.,
+       when -i is given on the command line) don't treat unindented lines
+       as starting a new command.
+
+Tue Jan  5 12:30:10 1999  Ben Pfaff  <blp@gnu.org>
+
+       In conjunction with egcs 1.1.1, Checker emits some bogus warnings,
+       mostly caused by local initialized aggregates.  After egcs is
+       fixed upstream these can be removed, but for now they're not a big
+       deal.
+       
+       * ascii.c: (ascii_postopen_driver) Checker chokes on local
+       initialized arrays.  Avoid this.
+
+       * sfm-write.c: (sfm_write_dictionary) Don't use a local
+       initialized struct.
+
+Tue Jan  5 12:07:24 1999  Ben Pfaff  <blp@gnu.org>
+
+       egcs 1.1.1 has some new warnings relative to gcc 2.8.1, which the
+       following changes avoid.  Currently I compile sources with egcs
+       1.1.1 and gcc 2.7.2.3 before sending them out.
+
+       * apply-dict.c: (apply_dict) Use new avl_traverser_init() macro.
+       
+       * ascii.c: (option_tab[]) Initialize all struct members.
+
+       * avl.h: (avl_traverser_init) New macro.
+       
+       * command.c: (DEFCMD, UNIMPL macros, cmd_table[]) Initialize all
+       struct members.
+
+       * crosstabs.q: (enum_var_values) Use new hsh_iterator_init()
+       macro.
+
+       * hash.c: Comment fix.
+
+       * hash.h: (hsh_iterator_init) New macro.
+
+       * html.c: (option_tab[]) Initialize all struct members.
+
+       * pfm-write.c: (write_value_labels) Use new avl_traverser_init()
+       macro.
+
+       * postscript.c: (option_tab[]) Initialize all struct members.
+       (output_encodings, preclose, dump_lines) Use new
+       hsh_iterator_init() macro.
+
+       * sfm-write.c: (write_value_labels) Use new avl_traverser_init()
+       macro.
+
+       * sysfile-info.c: (describe_variable) Use new avl_traverser_init()
+       macro.
+
+Thu Nov 19 12:32:45 1998  Ben Pfaff  <blp@gnu.org>
+
+       * data-in.c: Examined each of the parsing functions to make sure
+       that they wouldn't dump core if they were passed a string of the
+       wrong length, since now the DATA LIST FREE/LIST routines don't
+       check for field width before passing it to the data parser.
+       (parse_RBHEX, parse_AHEX) Reject odd length input.
+       (parse_string_as_format) Reject input that's too short or too
+       long.
+
+       * data-list.c: Before, the DATA LIST FREE/LIST routines would pad
+       a field to its entire declared output width then pass it to the
+       data-in parsing routines.  This contradicted the documented
+       behavior.  This is fixed in these changes.  Thanks to Mark H. Wood
+       <mwood@IUPUI.Edu>.  In addition, this fixes a few more details of
+       free-format parsing that differed from SPSS.
+       (cut_field) Commas and spaces are treated identically.  Returns
+       the proper column instead of a fixed 1 value.
+       (parse_field) Removed.
+       (read_from_data_list_free, read_from_data_list_list) Call
+       parse_string_as_format directly instead of parse_field.
+
+       * heap.c: (heap_delete) Stylistic fixes.
+
+Sun Aug  9 11:12:13 1998  Ben Pfaff  <blp@gnu.org>
+
+       * loop.c: (loop_2_trns_proc) Formatting fix.
+
+       * sel-if.c: (cmd_filter) Set FILTER_before_TEMPORARY.
+
+       * var.h: (glob var FILTER_before_TEMPORARY) New global var.
+
+       * vfm.c: (macro FILTERED) New.
+       (static var filter_var) New.
+       (process_active_file_write_case) Use FILTERED.
+       (setup_filter) Set filter_var.
+       (close_active_file) Delete the filter if not
+       FILTER_before_TEMPORARY.
+       (procedure_write_case) Use FILTERED.
+
+Sat Aug  8 00:20:14 1998  Ben Pfaff  <blp@gnu.org>
+
+       * crosstabs.q: Changed /PIVOT={ON,OFF} to /FORMAT={PIVOT,NOPIVOT}.
+
+       * data-in.c: (parse_day_count) Message fix.
+       (parse_month) Style fix.
+
+       * data-list.c: (struct data_list_pgm) New member eof.
+       (cmd_data_list) Init eof to 0.
+       (do_reading) Implement the /END subcommand and read-past-eof
+       checking.
+
+       * do-if.c: Include stdio.h when debugging.
+       (cmd_else_if) Make sure the command is .-terminated.
+
+       * glob.c: (init_glob) Capitalize the command prompt.
+
+       * inpt-pgm.c: (end_case_trns_proc) Debugging message.
+       (end_file_trns_proc) Debugging message.
+
+       * loop.c: (internal_cmd_loop) Make it work when there's no loop
+       index!
+       (loop_2_trns_proc) Enable MXLOOPS (why was this disabled?)
+
+       * main.c: (dump_token) Make kwtab[] const.
+
+       * set.q: Spelling, comment fixes.
+
+       * sysfile-info.c: (cmd_display) DISPLAY VECTORS not DISPLAY
+       VECTOR.
+
+       * vars-prs.c: (fill_all_vars) Style fix.
+
+       * vfm.c: (index_to_varname) Return const.
+
+Tue Aug  4 23:49:23 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Changes in many source files for partial -ansi -pedantic and
+       no-debugging compliance: Remove trailing common in enum
+       declarations; add `unused' attributes; insert some appropriate
+       casts.
+
+       * cmdline.c: (parse_command_line) Add new --testing-mode flag.
+
+       * command.c: (shell) Make static.
+       (run_command) Make static.
+
+       * data-list.c: (dump_fixed_table) Remove use of local_strdup().
+
+       * dfm.c: (cmd_begin_data) I18n fix.
+
+       * error.c: (verbose_msg) Define if __STRICT_ANSI__.
+
+       * error.h: (macro verbose_msg) Define if __STRICT_ANSI__.
+
+       * expr-opt.c: (evaluate_tree) Don't initialize local arrays if
+       __STRICT_ANSI__.
+
+       * file-handle.q: Don't prepend the source file directory name to
+       the data file name.  (Ongoing issue.)
+       (prepend_current_directory) Comment out.
+       (internal_cmd_file_handle) Don't call prepend_current_directory().
+       (fh_get_handle_by_filename) Ditto.
+
+       * filename.c: Append zero byte to readlink() return value.
+
+       * getline.c: (getl_read_line) I18n fix.
+
+       * lexer.h: Don't use gcc features if __STRICT_ANSI__.
+
+       * misc.h: Don't use gcc features if __STRICT_ANSI__.
+
+       * pfm-write.c: (bufwrite) Don't try to increment a void * pointer
+       directly.
+
+       * postscript.c: (output_encodings) Don't use local_strdup().
+       (postopen) Ditto.
+
+       * print.c: Don't use gcc features if __STRICT_ANSI__.
+
+       * q2c.c: (dump_vars) Don't put a , at the end of the last enum.
+
+       * recode.c: (parse_src_spec) Fully brace nested if's.
+
+       * set.q: (global var set_testing_mode) New var.
+
+Wed Jul 29 22:01:44 1998  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c: Add some more `unused' attributes that only come into
+       play when NDEBUG is defined.
+       (ascii_close_page) Set s_len when reallocating s.
+       
+       * crosstabs.q: (delete_missing) New function.
+       (output_pivot_table) Call delete_missing() if /MISSING=REPORT.
+       (make_summary_table) Create summary table reallocable.
+
+       * postscript.c: Add more `unused' attributes as above.
+
+       * tab.c: (tab_create) [GLOBAL_DEBUGGING] Set reallocable member.
+       (tab_realloc) [GLOBAL_DEBUGGING] Assert that table is reallocable.
+        
+       * tab.h: (struct tab_table) [GLOBAL_DEBUGGING] New `reallocable'
+       member.
+
+       * var.h: (macro force_dup_variable) [!GLOBAL_DEBUGGING] Remove
+       gratuitous space between parameter definition.
+
+       * vars-atr.c: Changed some assert(0)'s to abort()'s to prevent
+       complaints about running off the end of functions with NDEBUG
+       enabled.
+
+Sun Jul  5 00:17:25 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Several source files: Removed some PORTME notes when reflection
+       revealed that ANSI forbids that sort of breakage.  Also, added
+       lots of `unused' qualifiers here and there.
+
+       * aggregate.c: (accumulate_aggregate_info) Remove local var
+       weighting that turned out not to be used.
+
+       * avl.c: Update to version 1.1.0.  Add unused specifier.
+       (avl_destroy) Initialize ab to 0.  Comment fixes.  Cast return
+       value to void *.
+       (avl_probe) Replace some instances of 1 with +1 where appropriate.
+       (avl_find) Cast return value to void *.
+       (avl_delete) q doesn't need to be initialized at the beginning of
+       the function.  Replace some instances of 1 with +1.
+       (force_avl_delete) Renamed avl_force_delete, all references changed.
+       (compare_ints) `param' marked unused.
+       (print_int) `param' marked unused.
+       (recurse_tree) Replace some instances of 1 with +1.
+
+       * avl.h: Update to version 1.1.0.  Only declares avl function
+       types if not already declared.
+       (AVL_MAX_HEIGHT) Only define if not already defined.
+       (struct avl_node) New unused member char pad[2].
+       [GLOBAL_DEBUGGING] Change conditionalization to NDEBUG instead.
+       (force_avl_insert) Renamed avl_force_insert.
+       (force_avl_delete) Renamed avl_force_delete.
+
+       * crosstabs.q: (struct table_entry) Put `freq' into a union with
+       new member `data'.
+       (struct crosstab) Add new member `ofs'.
+       (glob var int_tab) Removed.
+       (custom_tables) In integer mode, assign v[i] properly through the
+       indirect var_dict.
+       (custom_variables) Now p.crs.max == max + 1.
+       [DEBUGGING] (debug_print) p.crs.min and p.crs.max are now ints.
+       (precalc) Implement integer mode.
+       (calc_integer) Implement integer mode.
+       (compare_table_entry) Remove unused local variable `comparing'.
+       (make_summary_table) Implement integer mode.
+       (macro ns_rows) Implemented as static variable now.
+       (several variables) Made static, from global.
+       (output_pivot_table) Use table_value_missing() for column heads.
+       Remove several unused local variables.  Implement integer mode
+       table summing.  Count up ns_rows.
+       (crosstabs_dim) Make columns wider when /MISSING=REPORT requested.
+       (find_pivot_extent) Moved into find_pivot_extent_general; now just
+       calls that function or find_pivot_extent_integer.
+       (find_pivot_extent_integer) New function.
+       (enum_var_values) Implemented for integer mode.
+       (table_value_missing) New function.
+       (display_dimensions) Call table_value_missing() for heads.
+       (float_M_suffix) New function.
+       (display_crosstabulation) Call table_value_missing() for row
+       heads.  Handle missing values in /MISSING=REPORT mode.
+       (calc_fisher) Remove unused var N.
+       (calc_r) Remove unused var fact.
+
+       * data-list.c: (dump_fixed_table) Fix table dimensioning.
+       (read_one_set_of_repetitions) Remove unused vars var_spec, column.
+
+       * data-out.c: (insert_commas) Remove unused var cp.
+       (convert_CCx) Remove unused vars save_set_decimal,
+       save_set_grouping.
+
+       * descript.q: (dump_z_table) Fix table dimensioning.
+       (pre_calc) Remove unused var j.
+       (display) Remove unused vars title, s.  Fix table dimensioning.
+
+       * expr-evl.c: Comment fixes.
+
+       * frequencies.q: (full_dim) New function.
+       (dump_full) Fix table dimensioning.
+       (condensed_dim) New function.
+       (dump_condensed) Fix table dimensioning.
+
+       * get.c: (cmd_match_files) Remove unused var n_val.  Remove unused
+       label winnage.
+
+       * html.c: (html_close_drive) Remove unused var i.
+       (postopen) Remove unused vars title, curfn_len, cp.
+       (preclose) Remove unused vars this, x.
+
+       * lexer.c: Comment fixes.
+
+       * matrix-data.c: (cmd_matrix_data) Remove unused var index.
+
+       * means.q: (custom_tables) Remove unused var m_dim.
+
+       * mis-val.c: Format fix.
+
+       * modify-vars.c: (cmd_modify_vars) Remove unused var new_dict.
+
+       * output.c: (outp_get_paper_size) Remove unused var cp.
+
+       * pfm-read.c: (read_float) Remove unused var save, unused label
+       underflow.
+       (read_variables) Remove unused vars cp, j.
+       (read_value_label) Remove unused var j.
+
+       * pfm-write.c: (bufwrite) Remove unused var i.
+
+       * postscript.c: (ps_postopen_drive) Remove unused vars dev_info,
+       fn.
+       (output_encodings) Remove unused vars char_cp, n_output.
+       (read_ps_encodings) Remove unused var ep.
+       (postopen) Remove unused var title.
+       (preclose) Remove unused var fp.
+       (ps_open_page) Remove unused vars true, false, orientation,
+       mirror_horz, mirror_vert, width, length.
+       (ps_text_metrics) Remove unused var x.
+
+       * q2c.c: (find_symbol) Remove unused var y.
+       (parse_setting) Remove unused parameter sbc, all references
+       changed.
+       (dump_parser) Remove unused var cp.
+       (dump_free) Remove unused var i.
+
+       * set.q: (static vars args, n) Removed.
+       (internal_cmd_gset) Removed.
+
+       * sfm-read.c: (sfm_read_dictionary) Removed unused var i.
+       (read_machine_flt64_info) Removed unused var file_endian.
+       (read_documents) Removed unused var i.
+       (read_compressed_data) Removed unused parameter dict, all
+       references changed.
+
+       * sfm-write.c: (bufwrite) Removed unused var i.
+       (sfm_write_case) Removed unused var i.
+
+       * sort.c: (merge_once) Remove unused var t.
+       (write_separate) #if 0 out as dead code.
+
+       * split-file.c: (cmd_split_file) Remove unused var i.
+
+       * sysfile-info.c: (sysfile_info_dim) New function.
+       (cmd_sysfile_info) Fix table dimensioning.
+       (variables_dim) New function.
+       (display_variables) Fix table dimensioning.
+       (describe_variable) Remove unused var prev_r.
+
+       * t-test.q: (z_postcalc) Removed.
+       (pairs_calc) Remove unused var bad_weight.
+       (postcalc) Remove unused vars dfn, dfd.
+
+       * tab.c: (tab_create) Set t->dim to NULL.
+       (tab_dim) Make sure t->dim is NULL first.
+       (tab_natural_width) Remove parameter `clamp'.
+       (tab_value) Remove duplicate assertion for table.
+       (tab_raw) New function.
+       (nowrap_dim) New function.
+       (wrap_dim) New function.
+       (tab_output_text) Fix table dimensioning.
+
+       * tab.h: (tab_raw) New macro.
+
+       * val-labs.c: (get_label) Remove unused var type.
+       (copy_value_labels) Remove unused var trav.
+
+       * var.h: (struct crosstab_proc) Completely changed.
+
+       * vars-prs.c: (parse_dict_variable) Remove unused var v.
+
+       * vfm.c: (open_active_file) Remove unused vars i, lp.
+
+       * weight.c: (weight_trns_proc) #if 0 out as dead code.
+       
+Tue Jun  2 23:37:21 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Add apply-dict.c, flip.c.
+
+       * apply-dict.c: New file.
+       
+       * command.c: (struct command) Make cmd[] larger for CLEAR
+       TRANSFORMATIONS command name.
+       (parse_cmd) Make sure we're in a valid state before using it as an
+       index.  Discard variables and reset state on invalid transitions.
+       (cmd_clear_transformations) New function.
+
+       * command.def: Add APPLY DICTIONARY, CLEAR TRANSFORMATIONS, FLIP.
+       Add unimplemented PRESERVE, RESTORE.
+
+       * file-handle.h: Include stddef.h.
+
+       * flip.c: New file.
+       
+       * pfm-read.c: (parse_value) Pad value label values with spaces,
+       not nulls.
+
+       * sfm-read.c: (struct sfm_fhuser_ext) Add reference count.
+       (sfm_close) Decrement reference count, make sure it's zero.
+       (sfm_maybe_close) New function.
+       (sfm_read_dictionary) Handle reference counts.
+
+       * vars-atr.c: (clear_default_dict) New function.
+       (discard_variables) Use clear_default_dict().
+
+Sun May 31 00:58:05 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Add pfm-write.c.
+       (LDADD) Add the libgmp2 libraries.
+
+       * command.def: Define EXPORT.
+
+       * get.c: (cmd_export) New function.
+       (export_write_case_func) New function.
+
+       * pfm-read.c: (static spss2ascii[]) Make it const.
+
+       * pfm-write.c: New file.
+
+       * sfm-write.c: Formatting, comment fixes.
+
+       * var.h: Comment fix.
+
+Fri May 29 21:44:12 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Add pfm.h, pfm-read.c.
+
+       * command.def: IMPORT is now implemented.
+
+       * format.c: (glob var translate_fmt[]) New var.
+
+       * get.c: (enum GTSV_NONE) Renamed GTSV_OPT_NONE.
+       (cmd_import) New function.
+       (import_source_read) New function.
+       (glob var import_source) New var.
+
+       * pfm-read.c: New file.
+
+       * pfm.h: New file.
+       
+       * sfm-read.c: (parse_format_spec) Local variable translate_fmt[]
+       moved in format.c.
+       (dump_dictionary) Disabled printing a couple of items.
+
+Mon May 25 12:42:37 1998  Ben Pfaff  <blp@gnu.org>
+
+       * crosstabs.q: (postcalc) Call make_summary_table().
+       (make_summary_table) New function.
+       (insert_summary) New function.
+       (display_dimensions) Remove some unnecessary arguments, all
+       references changed.
+       (output_pivot_table) Fix lots of problems with the risk table
+       setup.
+       (submit) Don't display an empty table.
+       (display_risk) Fix order of arguments to calc_risk().
+
+       * glob.c: Always include assert.h and stdlib.h.
+
+       * output.h: (enum OUTP_T_JUST_FULL) Removed, all references
+       removed.
+
+       * tab.c: (tab_create) Cosmetic changes.
+
+       * tab.h: (enum TAB_JUSTIFY) Removed, all references removed.
+
+Sun May 24 22:39:23 1998  Ben Pfaff  <blp@gnu.org>
+
+       * tab.def: Removed.
+
+       * crosstabs.q: (output_pivot_table) Headers drawing and submission
+       code simplified, moved into new function submit().
+       (submit) New function.
+       (crosstabs_dim) New function.
+       (display_directional) Substitute variable names for %s where
+       appropriate.
+       (somers_d_v[], somers_d_ase[], somers_d_t[]) New static vars.
+       (calc_symmetric) Initialize parameters only if non-NULL.
+       Calculate Somers' d.
+       (calc_directional) Calculate Somers' d (or copy it, really).
+       Calculate eta.
+
+       * output.c: (outp_string_width) New function.
+
+       * postscript.c: (postopen) Calculate font widths based on the
+       width of the zero '0' character, not the width of the space
+       character.  Set paper-width and paper-length based on points, not
+       device units.
+       (ps_open_page) Fix page setup string for landscape mode.
+
+       * som.h: (struct som_dimension) Removed.
+       (struct som_table_class) height, width members take int * not
+       som_dimesion * now.
+
+       * tab.c: Many functions now have added parameter validation.
+       (tab_height, tab_width) These functions were removed and merged
+       into a single function tab_resize(), and all references changed.
+       (tab_dim) Rewritten since the interface changed; reduced from
+       hundreds of lines to two.  All callers were changed.  Currently
+       most of them just use tab_natural_dimensions as their callback and
+       await detailed translation of functionality.
+       (tab_natural_width) New function.
+       (tab_natural_height) New function.
+       (tab_natural_dimensions) New function.  This is a callback
+       function, not something that you'd want to call directly.
+       (tab_nat_dim) Removed.
+       (tabi_table) Allocates t->w and t->h.
+       (tabi_driver) Inlined sum_columns()'s functionality.  Calls the
+       dimensions callback.
+       (evaluate_dimensions) Removed.
+       (sum_columns) Removed.
+
+       * tab.h: (enum TAL_1THIN) Removed.
+       (enum series t_*) Removed.
+       (struct tab_table) Members trh, trv changed to unsigned char *
+       from int *.  Member dim changed to a function pointer from a
+       unsigned char *.  Member max_stack_height removed.  New members
+       hr_tot, vr_tot.
+       (macros tab_l, tab_r, tab_t, tab_b) New.
+
+Sat May 23 23:22:13 1998  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c: (delineate) Assign last_space_nchars before skipping
+       spaces, to fix right justification.
+
+       * crosstabs.q: (static vars risk, direct) New vars.
+       (static var pearson_r) Removed.
+       (glob var chisq_fisher) Made static.
+       (static vars row_tot[], col_tot[]) Don't include grand total
+       anymore.
+       (static var grand_total) Renamed W, all references changed.
+       (output_pivot_table) Only make `table' if num_cells != 0.  Make
+       risk and directional tables.  Deal with grand total no longer part
+       of col_tot[].  Free rows and cols after we're done with them.
+       (display_risk) New function.
+       (display_directional) New function.
+       (clac_r) Rewritten so that it stores all its results into its
+       arguments, so it can be used for Spearman's correlation too.
+       (calc_symmetric) Added a t[] argument, all references changed.
+       Calculates ASEs for tau-b, tau-c, gamma.  Calculates Spearman's r,
+       Pearson's r, Cohen's kappa.
+       (calc_risk) New function.
+       (calc_directional) New function.
+
+       * som.c: (som_submit) Improved debugging code.
+
+       * stats.c: (hypercube) New function.
+       (cube) New function.
+       (sqr) New function.
+       (normal_sig) Went back to old implementation, which actually
+       worked.
+
+       * stats.h: (macros square, cube, hypercube) Removed.  The
+       equivalent functions in stats.c are inlined here; all references
+       to square changed to sqr.
+
+Fri May 22 00:03:41 1998  Ben Pfaff  <blp@gnu.org>
+
+       * crosstabs.q: (N_SYMMETRIC) New define.
+       (postcalc) Disable debug printing.
+       (static vars chisq_fisher, pearson_r) New.
+       (output_pivot_table) Add support for symmetric measures.  Add
+       chi-square output of exact sigs.
+       (display_chisq) Rewritten.
+       (display_symmetric) New function.
+       (gamma_int) New function.
+       (Pr) New function.
+       (swap) New function.
+       (calc_fisher) New function.
+       (calc_chisq) Check boundary conditions better. Calculate Yates,
+       Fisher, Mantel-Haenszel tests.
+       (calc_r) New function.
+       (calc_symmetric) New function.
+
+       * stats.c: (normal_sig) Rewritten with new algorithm.  Renamed
+       from calc_normal.
+       (chisq_sig) Better boundary conditions.  Renamed from
+       calc_significance.
+
+       * tab.h: (struct tab_table) New member cf.
+
+       * tab.c: (tab_create) Set cf.
+       (tab_width) New function.
+       (tab_realloc) Handle cf.
+       (tab_vline) Handle cf.
+       (tab_hline) Handle cf.
+       (tab_box) Handle cf.
+       (tab_value) Handle cf.
+       (tab_float) Handle cf.
+       (tab_text) Handle cf.
+       (tab_joint_text) Handle cf.
+       (tab_offset) Handle cf.
+       (tab_next_row) Handle cf.
+       (evaluate_dimensions) Handle cf.
+       (render_strip) Handle cf.
+
+Wed May 20 00:03:59 1998  Ben Pfaff  <blp@gnu.org>
+
+       * crosstabs.q: (postcalc) New vars row_tot, col_tot, pass them to
+       output_pivot_table().
+       (output_pivot_table) Moved lots of local variables outside and
+       made them static.  Add beginnings of chi-square statistic
+       support.  Now column and row totals aren't in the main matrix.
+       Always zero out any leftover rows & columns after we're done with
+       the table entries.  Move all output stuff into
+       display_dimensions(), display_crosstabs(), display_chisq().
+       (display_dimensions) New function.
+       (display_crosstabulation) New function.
+       (display_chisq) New function.
+       (calc_chisq) Implemented Pearson and likelihood-ratio chisquares.
+
+       * frequencies.q: (dump_full, dump_condensed) Remove tab_null()
+       references, simplify logic.
+
+       * postscript.c: Remove scale, translate-x, translate-y,
+       mirror-horz, mirror-vert, rotate-180 options.
+       (struct ps_driver_ext) Remove scale, translate_x, translate_y.
+       All references deleted.
+       (macro YT) New macro.
+       (array option_tab[]) Removed options.
+       (ps_option) Removed options.
+       (ps_open_page) Write page setup explicitly to output file, without
+       using now-deleted BP function.
+       (macro dump_line) Use YT().
+       (macro dump_thick_line) Use YT().
+       (draw_headers) Use YT().
+       (switch_font) Reorder arguments to SF function.
+       (write_text) Use YT().
+
+       * sfm-read.c: (sfm_read_case) Don't attempt to read variables that
+       have get.fv == -1.
+
+       * sysfile-info.c: (describe_variables) Don't use tab_nulls().
+
+       * tab.c: (tab_create) Initialize t->ct to zeros.  Remove
+       null-debugging code.
+       (tab_realloc) Remove null-debugging code.  Initialize new regions
+       of t->ct to zeros.
+       (tab_vline) Support offsets.
+       (tab_hline) Support offsets.
+       (tab_box) Support offsets.
+       (tab_null) Removed.
+       (tab_nulls) Removed.
+       (tab_row) Removed.
+       (tab_col) Removed.
+       (evaluate_dimensions) Remove null-debugging code.  Understand
+       TAB_EMPTY attribute.  Assert that text.s.s is always non-NULL if
+       TAB_EMPTY not present.
+
+       * tab.h: New cell attribute TAB_EMPTY.
+       (macros tab_nr, tab_nc, tab_row, tab_col) New.
+
+       * vars-atr.c: (init_variable) Set get.fv to -1 so that GET doesn't
+       try to read them from system files.
+
+       * vfm.c: (dump_splits) Don't call tab_null().   
+
+Sat May 16 19:36:55 1998  Ben Pfaff  <blp@gnu.org>
+
+       * crosstabs.q: (struct crosstab) Added `missing' member.
+       (custom_tables) Init missing.
+       (calc_general) Handle missing values.
+       (calc_chisq) New function.
+       (output_pivot_table) Start work on chi-square output.  Update for
+       new tab offset support functions.  Shorten statistic names.
+
+       * Several files: add in more `const's to placate gcc's warnings.
+
+       * tab.h: (struct tab_table) Add col_ofs, row_ofs members.  Comment
+       fixes.
+
+       * tab.c: (tab_height, tab_realloc, tab_vline, tab_hline, tab_box,
+       tab_null, tab_nulls, tab_value, tab_float, tab_text,
+       tab_joint_text) Add col_ofs and row_ofs support.
+       (tab_offset) New function.
+       (tab_next_row) New function.
+       (tab_row) New function.
+       (tab_col) New function.
+       (tabi_table) Add col_ofs and row_ofs support.
+
+       * vars-atr.c: (is_system_missing) New function.
+
+Tue May 12 16:14:30 1998  Ben Pfaff  <blp@gnu.org>
+
+       * crosstabs.q: Expanded subcommand names RESID --> RESIDUAL, etc.
+       (static var no_cells) Removed.
+       (static var num_cells) New.
+       (static var expected) New.
+       (static var cells[]) New.
+       (internal_cmd_crosstabs) Deal with new variables.
+       (postcalc) Removed most of the meat and put it in new function
+       output_pivot_table().
+       (output_pivot_table) Calculates and outputs an entire pivot table.
+
+       * postscript.c: (postopen) Fix problems with free()ing addresses
+       not obtained from malloc().
+
+       * som.c: (som_submit) Add assertion.
+
+       * sysfile-info.c: (describe_variable) Use new tab_nulls()
+       function.
+
+       * tab.c: (static var tab_names[]) New.
+       (tab_realloc) -1 for nc or nr indicates no change.
+       (tab_nulls) New function.
+       (tab_dim) Use tab_names[].
+       (tabi_cumulate) Don't include bottom or right headers.  Furrfu.
+       (evaluate_dimensions) Don't terminate on uninited cells, just put
+       an X in them and emit a notice.  Use tab_names[].
+
+       * tab.h: Move bits into tab.def.
+
+       * tab.def: New.  Don't try to declare tab_table_class because then
+       som.h has to be included.       
+       
+Thu May  7 22:55:04 1998  Ben Pfaff  <blp@gnu.org>
+
+       * command.def: New file, contains all the command definitions
+       previously included bodily in command.c.
+
+       * format.def: New file, contains all of the format definitions
+       previously split across format.h, format.c, and sfm-write.c.
+
+       * lexer.h: Renamed from tokens.h in order to match corresponding
+       .c file name.
+
+       * lexerP.h: Moved some rarely used functions exported by lexer.c
+       into here.
+
+       * Makefile.am: Commemorate renamed files.
+       (EXTRA_DIST) Add command.def, format.def.
+
+       * command.c: [0] (walk_cmdtable_func) Removed.
+
+       * crosstabs.q: (postcalc) Made it work and print out matrices
+       proving it.
+       (enum_column_values) Renamed enum_var_values, generalized for any
+       variable.
+
+       * format.h: (struct fmt_desc) New member `spss'.
+
+       * q2c.c: (main) Generated code includes lexer.h instead of
+       tokens.h.
+
+       * sfm-write.c: (write_format_spec) Use new spss member of fmt_spec
+       instead of an independent translation table.
+
+Tue May  5 13:19:03 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Lots of source files: Added const to declarations.
+
+       * aggregate.c: (parse_aggregate_function) Rename inner i to j.
+       
+       * arena.c: (arena_clear) Set prev pointer to null when done.
+
+       * ascii.c: (ascii_option) Rename index as indx.
+
+       * avl.c: This is now a separate library called libavl.
+       (xmalloc) Make static.
+       (avl_probe) Step A7 can use the cache instead of an explicit
+       compare.
+       (avl_delete) Don't maintain a q pointer because it's always
+       available in the pointer stack.  Comment fix.
+
+       * avl.h: This is now a separate library called libavl.
+
+       * command.c: (cmd_table[]) Remove spurious trailing "".
+
+       * common.h: Only include random() fix if this system needs it.
+
+       * crosstabs.q: Include alloca headers.
+       (n_sorted_tab) New global var.
+       (postcalc) Mostly rewritten.
+       (find_pivot_extent) Rewritten.
+       (enum_column_values) Rewritten.
+
+       * data-out.c: (convert_F) Rename inner n as n_spaces.
+
+       * error.c: (dump_message) Don't have an outer var i.
+
+       * file-handle.q: (static var f) Removed.  All references removed.
+       (internal_cmd_file_handle) Uses a local variable instead of f.
+
+       * get.c: (trim_dictionary) Change scope of i, i1, i2.
+       (cmd_match_files) Don't strcpy tokstr into sbc (why was this ever
+       done?)
+
+       * getline.h: Declare getl_history as extern.  Reported by
+       palme@uni-wuppertal.de (Hubert Palme).
+
+       * postscript.c: (postopen) Some large mods for constness.
+
+       * recode.c: Remove spurious copyrights since PSPP is owned by FSF
+       anyway.
+
+Fri Apr 24 12:52:47 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Rename BUILT_SOURCES to q_sources, all references
+       changed.  Add avl.c, avl.h to pspp_SOURCES.  Remove avllib from
+       LDADD.
+
+       * avl.c, avl.h: New files.  These form a clean-room
+       reimplementation of avllib.  Iterative algorithms are used in
+       place of recursive ones, so there is no resemblance in the code.
+
+       * Lots of headers: Don't include other headers by default.
+
+       * Lots of source files: Explicitly include all needed headers.
+
+       * arena.c: (arena_clear) New function.
+
+       * crosstabs.q: (ROW_VAR, COL_VAR) New enums.
+       (static var ar) Removed.
+       (staitc vars ar_tc, ar_col) New.
+       (cmd_crosstabs) Destroy the arenas.
+       (internal_cmd_crosstabs) Create the arenas.
+       (precalc) Don't need a free function for the hash.
+       (calc_general) Make sure to zero out the trailer on the key data
+       before inserting.
+       (print_table_entries) Updated.
+       (postcalc) Worked on actually implementing.
+       (find_pivot_extent) New function.
+       (compare_value) New function.
+       (enum_column_values) New function.
+
+       * data-in.c: (parse_month) Make local array `static const'.
+       
+       * data-out.c: (convert_date) Make local array `static const'.
+       (convert_WKDAY) Same.
+       (convert_MONTH) Same.
+
+       * frequencies.q: (postprocess_freq_tab) avl_walk_inorder() has
+       been renamed to avl_walk().
+       
+       * hash.c: Rewritten more efficiently.
+
+       * hash.h: Add attribute const to hsh_next_prime declaration.
+
+       * lexer.c: (id_match) Make arguments const.
+
+       * postscript.c: (ps_postopen_driver) Make default fonts the
+       Helvetica family.
+
+       * q2c.c: (main) Generated code needs stdlib.h.
+
+       * sfm-write.c: (write_value_labels) An avl_traverser needs to be
+       initialized to 0 now, not to NULL.  All other references to
+       avl_traverser were updated in the same way.
+
+       * tokens.h: Macro version of id_match updated to use const
+       properly.
+
+       * val-labs.c: (inc_ref_count) New function.
+       (copy_value_labels) Simply through use of new avl_copy() function.
+
+Wed Apr 15 13:01:58 1998  Ben Pfaff  <blp@gnu.org>
+
+       * crosstabs.q: Probably doesn't compile.  New PIVOT subcommand.
+       (postcalc) Worked on this.
+
+       * postscript.c: (OPO_DOUBLE_LINE) New enum.
+       (struct ps_driver_ext) New line_width_thick member.
+       (ps_preopen_drive) Init line_width_thick.
+       (option_tab[]) Add line-* options.
+       (ps_option) Parse line-* options.
+       (postopen) Add line_width_thick support.  Strip leading spaces on
+       prologue output lines.
+       (ps_open_page) Include line_width_thick in output.
+       (macro dump_thick_line) New.
+       (dump_fancy_line) Support thick lines as well as double lines.
+
+Tue Apr 14 00:50:08 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Add crosstabs.c to BUILT_SOURCES.  Add crosstabs.q
+       to pspp_SOURCES.  Add crosstabs.q to EXTRA_DIST.
+
+       * Many source files: Rename `options' to `pv_opts' as appropriate.
+
+       * command.c: (static var cmd_table[]) Add CROSSTABS command.
+
+       * common.c: (xcalloc) New function.
+
+       * crosstabs.q: New file.  Not finished yet, though.
+               
+       * data-list.c: Comment fix.
+
+       * error.c: Remove some old Checker cruft.
+
+       * frequencies.q: (dump_full) Cumulate valid percent instead of
+       regular percent.
+
+       * getline.c: Comment fix.
+
+       * hash.c: Comment fixes.
+
+       * hash.h: (struct hsh_table) Make hash functions return unsigned
+       instead of int to avoid problems with taking the modulo of
+       negative return values.  All references changed.
+
+       * misc.c: (intlog10) Make its table static const instead of auto.
+
+       * sfm-read.c: (read_header) Make `prefix' static const instead of
+       auto.
+
+       * var.h: (union value) Add member `hash'.
+       (struct variable) Rename prv_index as `foo'--all references
+       changed.  Reorder.
+       (typedef pv_opts) Removed.  All references changed.
+
+       * vars-prs.c: (parse_variables) Message fixes.
+       
+Mon Mar  9 15:35:08 1998  Ben Pfaff  <blp@gnu.org>
+
+       * get.c: (cmd_match_files) Don't reverse the order of FILEs as
+       they are being inserted.  Don't check for BY variables of
+       different types.  Discard variables if the active file isn't
+       included in the merge.
+       (mtf_processing) Essentially rewritten.
+       (mtf_merge_dictionary) Check for master/slave variables of
+       different types/widths.
+
+       * vfm.c: (static var not_canceled) New var.
+       (process_active_file) Don't call vfm_source->read() if
+       there's no vfm-source.  Initialize not_canceled.
+       (process_active_file_write_case) Honor and update not_canceled.
+       (prepare_for_writing) Rollback changes from yesterday, they were
+       wrong.
+       (close_active_file) Don't destroy vfm_source unless it exists.
+       
+Mon Mar  9 00:56:16 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Lots of source files: Added { } around nested if/else constructs
+       to avoid new gcc 2.8 warnings.
+
+       * data-in.c: (parse_Z) Declare `int' type explicitly.
+       (convert_Z) Ditto.
+
+       * get.c: (struct mtf_file) Add prev, next_min, by, input members.
+       (cmd_match_files) Initialize mtf_by_values.  Manage by, input,
+       prev members.  Put TABLEs at the end of the chain and FILEs at the
+       beginning.  Don't allow the active file in STATE_INIT.  Use proper
+       `seen' value for the active file.  Fill out the by members and
+       make sure they're of consistent type.  Do the actual merge
+       operation.
+       (mtf_processing_finish) New function.
+       (var_type_description) New function.
+       (mtf_free_file) New function.
+       (mtf_free) Rewritten.
+       (mtf_delete_file_in_place) New function.
+       (mtf_read_nonactive_records) New function.
+       (mtf_compare_BY_values) New function.
+       (static var mtf_seq_no) New var.
+       (mtf_processing) New function.
+       (mtf_merge_dictionary) Assign nval members for the system file
+       dictionary.  Assign fv values for its variables.  Point each slave
+       variable to the corresponding master variable.
+
+       * hash.c: Include str.h.
+
+       * mis-val.c: (copy_missing_values) src arg is const.
+
+       * misc.c: (spacing) Make `max' var explicitly int.
+
+       * sfm-read.c: (dump_dictionary) Message reformatting.
+       (sfm_read_case) Add assertion.
+
+       * sort.c: Esthetic fixes.
+
+       * var.h: (struct match_files_proc) New struct.
+       (struct variable) Add private data match_files_proc.
+
+       * vars-atr.c: (delete_variable) Implement.  Add argument for the
+       dictionary that owning the variable.
+       (dup_variable) Add assertion.
+
+       * vfm.c: Comment fixes, hopefully the comments are correct now.
+       (process_active_file) New function.
+       (process_active_file_write_case) New function.
+       (process_active_file_output_case) New function.
+       (prepare_for_writing) Use temp_dict->nval for vfm_info, not
+       default_dict.nval.
+       (write_case) Renamed procedure_write_case().  Now write_case is a
+       pointer to a function.  Style fixes.
+       
+1998-03-05  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (q2c) Link with libmisc.
+       (version.c) Define default_config_path, include_path,
+       groff_font_path.
+
+       * ascii.c: (ascii_postopen_driver) When the default newline string
+       is requested, open file in text mode.  Suggested by
+       palme@uni-wuppertal.de (Hubert Palme).
+       (static vars line_buf, line_p) Change from char * to unsigned char
+       *.
+       (ascii_close_page) char * to unsigned char *.
+
+       * cmdline.c: (parse_command_line) Implement -r option by
+       prepending ~/.pspp/rc to the list of files to process.
+
+       * command.c: (cmd_remark) Reset getl_prompt to the standard prompt
+       before pulling in a final line.
+       (null_func, null_int_func) Removed (dead code).
+
+       * descript.q: (display) Calculate width of variable name column
+       properly.  Calculate number of valid cases properly.  Reported by
+       palme@uni-wuppertal.de (Hubert Palme).
+
+       * filename.c: (init_filename) Use default_config_path instead of
+       now obsolete CONFIG_PATH.
+
+       * getline.c: (getl_initialize) Use include_path instead of now
+       obsolete INCLUDE_PATH.
+       (getl_add_file) New argument `where'.  All references changed.
+
+       * groff.c: (find_font_file) Use groff_font_path instead of now
+       obsolete GROFF_FONT_PATH.
+       
+       * postscript.c: (find_ps_file) Use groff_font_path instead of now
+       obsolete GROFF_FONT_PATH.  Copy through temporary variable to
+       avoid problems with constness.
+
+       * str.h: (macro cs_streq) New macro.
+
+       * version.h: (glob var default_config_path, include_path,
+       groff_font_path) New vars.
+       
+1998-02-23  Ben Pfaff  <blp@gnu.org>
+
+       * Many source files: Change verbose_msg() priority levels and
+       messages.
+
+       * aggregate.c: Include debug-print.h.
+
+       * cmdline.c: (parse_command_line) Add --safer/-s and --command/-c
+       options.
+       (static var pre_syntax_message) Document --safer/-s and
+       --command/-c.
+
+       * command.c: (cmd_erase, cmd_host) Disable if set_safer is set.
+
+       * dfm.c: (open_inline_file) [__CHECKER__] Zero out ext->file,
+       because it's not used but it's still copied.
+       (open_file_r) Remove gratuitous debug message.
+
+       * filename.c: (safety_violation) New function.
+       (open_file) Remove gratuitous debug messages.  Don't allow pipe
+       files if set_safer is set.
+
+       * get.c: Turn off debugging.
+
+       * getline.c: (getl_add_virtual_file) New function.
+       (getl_read_line) Add verbose_msg() call for opening new syntax
+       file.
+       (getl_perform_delayed_reset) Add a return value describing whether
+       any action was taken.  Call reset_eof().
+
+       * getline.h: Comment fix.
+
+       * groff-font.c: (groff_read_font) Use `goto next_iteration' in
+       place of incorrect `continue'.  Use strtok_r() instead of
+       strtok().  Always check strtok_r() return value.
+       (groff_read_DESC) Use strtok_r() instead of strtok().
+
+       * lexer.c: (reset_eof) New function.
+
+       * main.c: (parse) Get a token after performing a delayed reset
+       action; allow empty syntax files.
+
+       * postscript.c: (output_encodings) Use strtok_r() instead of
+       strtok().
+
+       * q2c.c: (dump_parser) Use strtok_r() instead of strtok().
+
+       * set.q: Comment fixes.
+       (glob var set_safer) New var.
+       (internal_cmd_set) Support SAFER.
+
+       * str.h: [!HAVE_STRTOK_R] Declare strtok_r() prototype.
+
+       * temporary.c: (free_dictionary) Set d->splits to NULL after
+       freeing.
+
+       * vars-atr.c: (clear_variable) Decrement dict->n_splits if
+       variable deleted, not if it *isn't* deleted.
+
+1998-02-16  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: (array cmd_table[]) Add MATCH FILES.
+
+       * common.c: Comment fixes.
+
+       * data-list.c, dfm.c, error.c, filename.c, list.q, matrix-data.c,
+       modify-vars.c, postscript.c, sfm-read.c, sfm-write.c, tab.c:
+       Include alloca.h.  Problem reported by palme@uni-wuppertal.de
+       (Hubert Palme).
+
+       * expr-opt.c: Include str.h.  Problem reported by
+       palme@uni-wuppertal.de (Hubert Palme).
+
+       * get.c: (cmd_get) [DEBUGGING] Update v->p.get to v->get.
+       (static var mtf_by) Change from char ** to variable **.
+       (static var mtf_master) New var.
+       (mtf_merge_dictionary) New function.
+       (cmd_match_files) Init mtf_master.  Parse mtf_by according to new
+       var type.  Reorder tests properly.  Initialize file->dict.  Detect
+       TABLE= without BY=.  Read file dictionaries and merge them.  Give
+       subcommand name with IN, LAST, FIRST error messages.  Create IN,
+       LAST, FIRST variables.  Comment fixes.
+       (mtf_free) Don't free default_dict.  Free mtf_master.
+
+       * getline.c: Define getl_mode.  Change getl_buf_size to size_t
+       from int.
+       (handle_line_buffer) Cast int to size_t in comparison to avoid
+       warning.
+
+       * getline.h: Declare getl_mode extern.
+
+       * groff-font.c: (groff_read_font) Type-fix calls to getline.
+       (groff_read_DESC) Make line_size a size_t.
+       (match_tok) Parenthesize name to avoid macro expansion.
+
+       * mis-val.c: (copy_missing_values) New function.
+
+       * postscript.c: (postopen) Make buf_size a size_t.
+
+       * sfm-read.c: (dump_dictionary) Make global from static.  Print
+       variable info in parts for easier debugging with Checker.
+
+       * temporary.c: (copy_variable) Use copy_value_labels().
+       (new_dictionary) New arg: whether to copy file label, documents.
+
+       * val-labs.c: (copy_value_labels) New function.
+
+       * var.h: (enums MISSING_*) Add MISSING_COUNT.
+
+       * vars-atr.c: [GLOBAL_DEBUGGING] (force_dup_variable) New
+       function.
+       (dup_variable) Set prv_index, get.fv, get.nv.
+
+Fri Feb 13 15:38:36 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (pspp_SOURCE) Add htmlP.h.
+
+       * Many source files: For ANSI-compliance, add empty statement
+       after label.  Reported by palme@uni-wuppertal.de (Hubert Palme)
+       and Micah Altman <maltman@www-vdc.fas.harvard.edu>.
+
+       * data-in.c: (parse_numeric) Some header files break on
+       -DBL_MIN_10_EXP because they get a --; add () for safety.
+       Reported by palme@uni-wuppertal.de (Hubert Palme).
+
+       * dfm.c: Idea by Dr Eberhard W Lisse <el@linux.lisse.na>.
+       (struct dfm_fhuser_ext) Change `file' from FILE * to file_ext.
+       (dfm_close) Use close_file_ext.
+       (open_inline_file) Set file.file to NULL, not file.
+       (open_file_r, open_file_w) Initialize file.file; fill in file_ext
+       struct and use open_file_ext().
+       (read_record) Use file.file.
+
+       * file-handle.q: (prepend_current_directory) Pass through special
+       filenames.
+
+       * filename.c: Only include unistd.h if HAVE_UNISTD_H.
+       (normalize_filename) Pass through special filenames.
+       (open_file, close_file) Accept pipe| and |pipe syntaxes as
+       equivalent.
+       (dirname) Rename blp_dirname() because of name conflict on some
+       OS.  All references changed.  Reported by palme@uni-wuppertal.de
+       (Hubert Palme).
+       (is_special_filename) New function.
+
+       * get.c: (GTSV_OPT*) Add GTSV_OPT_MATCH_FILES.
+       (trim_dictionary) Conditionalize some of the options on whether
+       GTSV_OPT_MATCH_FILES is in *options.
+       (rename_variables) Don't allow variables to be renamed as scratch
+       variables.
+       (MTF_*) New enum series.
+       (struct mtf_file) New struct.
+       (static vars mtf_head, mtf_tail, mtf_by, mtf_n_by, mtf_free) New
+       vars.
+       (cmd_match_files, mtf_free) New functions.
+
+       * lexer.c: (match_int) Needed parentheses around name to escape
+       macro expansion.  Reported by Micah Altman
+       <maltman@www-vdc.fas.harvard.edu>.
+
+       * print.c: Needed to include alloca.h.  Reported by Micah Altman
+       <maltman@www-vdc.fas.harvard.edu>.
+
+       * recode.c: (convert_to_double) Parenthesize -DBL_MIN_10_EXP to
+       -(DBL_MIN_10_EXP).  Reported by palme@uni-wuppertal.de (Hubert
+       Palme).
+       
+       * str.h: Include stdarg.h.  Reported by palme@uni-wuppertal.de
+       (Hubert Palme) and Micah Altman <maltman@www-vdc.fas.harvard.edu>.
+
+Thu Feb  5 00:18:21 1998  Ben Pfaff  <blp@gnu.org>
+
+       * html.c: (struct html_driver_ext) Move into htmlP.h.
+       (html_preopen_driver) Initialize cp_x, cp_y.
+       (html_submit) Implement as call to output_tab_table().
+       (change_attributes) New function.
+       (escape_string) New function.
+       (output_tab_table) New function.
+
+       * list.q: (write_all_headers) Add code for writing headers for the
+       html driver.
+       (clean_up) Write out the html close-table tag.
+       (determine_layout) Ignore html driver.
+       (list_cases) Write html data.
+
+       * som.c: (som_submit) Move more of the code into output_table().
+
+       * tab.c: (static var hit) Make a global var and rename tab_hit.
+       (static var tab_table_class) Make a global var.
+
+       * htmlP.h: New file.
+
+Tue Feb  3 16:12:18 1998  Ben Pfaff  <blp@gnu.org>
+
+       * dump-sysfile.c: Removed.
+
+       * html.c: (preclose) Change comment in emitted code.
+
+       * matrix-data.c: Debugging off by default.  Comment fixes.
+       (static var container) New var.
+       (cmd_matrix_data) Create and destroy container.  Initialize
+       is_per_factor[] to 0s.  Move code into new function
+       string_to_content_type().  Require split values to be present in
+       the data when ROWTYPE_ is explicit.  Call specific function, not
+       general read_matrices().
+       (string_to_content_type) New function.
+       (context) Exclude all whitespace, not just spaces.
+       (mget_token) A dot is a number.  Add assertion.
+       (static var data) Renamed nr_data.
+       (static var factor_values) Renamed nr_factor_values.
+       (read_matrices) Renamed read_matrices_without_rowtype().  Handle
+       only specific case.  Close data_file before exit.
+       (fill_matrix) New function.
+       (read_data_lines) Renamed nr_read_data_lines().  Remove debug
+       printing.  Style fixes.  Message fixes.  Move code into
+       fill_matrix().
+       (read_matrices_without_rowtype) Rename
+       matrix_data_read_without_rowtype().  Fix off-by-one error on
+       loops.  Allocate nr_data[] memory from arena.
+       (read_matrices_with_rowtype) Removed.
+       (read_splits) Renamed nr_read_splits().  Style fixes.
+       (read_factors) Renamed nr_read_factors().
+       (dump_cell_content) Comment fixes.  Arguments changed.  Change
+       debug printing.  All references changed.
+       (output_data) Renamed nr_output_data().
+       (static var wr_content) New var.
+       (struct factor_data) New struct.
+       (static var wr_data) New var.
+       (static var wr_current) New var.
+       (matrix_data_source_destroy_source) Removed.
+       (read_matrices_with_rowtype) New function.
+       (matrix_data_read_with_rowtype) New function.
+       (wr_read_splits) New function.
+       (compare_factors) New function.
+       (wr_output_data) New function.
+       (wr_read_rowtype) New function.
+       (wr_read_factors) New function.
+       (wr_read_indeps) New function.
+       (glob var matrix_data_source) Make destroy_source member NULL as
+       well.
+
+Fri Jan 23 00:09:08 1998  Ben Pfaff  <blp@gnu.org>
+
+       * lexer.c: (syntax_error) Give better error message when at end of
+       file.
+
+       * matrix-data.c: (var content_names[]) Fix PROX spelling.  Change
+       N_SCALAR to output as plain N.
+       (mdump_token) Change output format.
+       (context) Fix message output interaction with spaces in input.
+       (another_token) New function.
+       (force_eol) Improved error message.
+       (static var max_cell_index) New var.
+       (read_matrices) Init `cells'.  factor_values is now per-cell.
+       Init max_cell_index.
+       (read_data_lines) Replace `compare' local with new `compare' arg.
+       Debugging messages changed.  Only read factors if per_factor.
+       Propagate error return from read_factors(), force_eol().
+       Copy N_SCALAR values across the N vector.
+       (read_matrices_without_rowtype) Don't init `cells'.  Don't need to
+       check parentheses manually since we now have is_per_factor[].
+       Call read_data_lines() with new args.  Check for end of data after
+       looping, using another_token().
+       (read_factors) Arguments changed.  Use max_cell_index to determine
+       whether to read or compare factors.  Message fixes.
+       (dump_cell_content) New function.
+       (output_data) Completely rewritten because content types were
+       supported to be nested inside factor values, not vice versa.
+       
+Thu Jan 22 00:26:38 1998  Ben Pfaff  <blp@gnu.org>
+
+       * lexer.c: (syntax_error) Support formatted varargs messages.
+
+       * matrix-data.c: Turn debugging on by default.
+       (static content_type[]) New array.
+       (static content_names[]) New array.
+       (static rowtype_, varname_) New vars.
+       (static is_per_factor[]) New array.
+       (static split_values) Moved declaration.
+       (static n_continuous, first_continuous) New var.
+       (cmd_matrix_data) Don't init split_values.  Assign ROWTYPE_ to
+       rowtype_.  Simplify SPLIT code.  Init is_per_factor[].  Assign
+       VARNAME_ to varname_.  Initialize first_continuous, n_continuous.
+       Check for continuous variables.
+       [DEBUGGING] (debug_print) Remove content_names[].
+       (mdump_token) New macro.
+       (mget_token_dump) New function.
+       (mdump_token) New function.
+       (context) New function.
+       (mget_token) Fix messages.
+       (static var data, split_values, factor_values) New vars.
+       (read_matrices) Manage split_values, factor_values.
+       (read_data_lines) New function.
+       (read_matrices_without_rowtype) Implemented.
+       (read_splits) Message fixes.  Uses `just_read'.
+       (read_factors) New function.
+       (output_data) New function.
+       (matrix_data_source_destroy_source) Close the file handle.
+       (glob var matrix_source) Change name from "DATA LIST" to "MATRIX
+       DATA".
+
+       * str.c: (strpadcmp) Removed.
+
+       * vfm.c: (dump_splits) Initialize i; fix test for end of splits.
+
+Sun Jan 18 00:30:59 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Lots of source files: Add cast to unsigned character to calls to
+       tolower() and toupper().
+
+       * aggregate.c: Set default_dict.splits to NULL.
+
+       * command.c: (static variable tab[]) Add MATRIX DATA.
+
+       * data-in.c: Add debugging defines.  Formatting fixes.
+
+       * expr-opt.c: Formatting fixes.
+
+       * lexer.c: (syntax_error) Message fixes.
+
+       * matrix-data.c: New enum series.
+       (static vars fmt, section, diag, explicit_rowtype, signle_split,
+       split_values, n_factors, factors, cells, pop_n, contents,
+       n_contents) New vars.
+       (cmd_matrix_data) Finished implementation.
+       (compare_variables_by_mxd_vartype) New function.
+       [DEBUGGING] (debug_print) New function.
+       (static vars mtoken, mtokstr, mtoklen, mtokval) New vars.
+       (read_matrices) New function.
+       (read_matrices_without_rowtype) New function.
+       (read_matrices_with_rowtype) New function.
+       (read_splits) New function.
+       (mget_token) New function.
+       (force_eol) New function.
+       [0] (test_tokenizer) New function.
+       (matrix_data_source_destroy_source) New function.
+       (glob var matrix_data_source) New var.
+
+       * misc.h: Include ieeefp.h if present.
+
+       * split-file.h: (cmd_split_file) Changes corresponding to struct
+       dictionary changes.
+
+       * str.h: Fix memmem prototype.
+
+       * temporary.c: (save_dictionary, restore_dictionary,
+       free_dictionary) Changes corresponding to struct dictionary
+       changes.
+
+       * var.h: (MXD_* enums) New enum series.
+       (struct matrix_data_proc) New struct.
+       (struct split) Removed.
+       (struct dictionary) Changed `splits' member from `split *' to
+       `variable **'.
+       (macro force_create_variable) New macro.  Replaced lots of
+       create_variable()/assert() calls with calls to this macro.
+
+       * vars-atr.c: (discard_variables) Changed assertion.
+       [GLOBAL_DEBUGGING] (force_create_variable) New function
+       called by the macro of the same name.
+       (clear_variable) Changes to delete splits from the dictionary
+       corresponding to struct dictionary changes.
+
+       * vars-prs.c: (parse_variables) [GLOBAL_DEBUGGING] Check for
+       corrupted variable `index' values in the dictionary passed in
+       every time this function is called.
+
+       * vfm.c: (dump_splits, SPLIT_FILE_procfunc) Changes corresponding
+       to struct dictionary changes.
+
+Tue Jan 13 23:45:02 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (pspp_SOURCES) Add matrix-data.c.
+
+       * command.c: New includes.
+       (static array cmd_table[]) Add ERASE, HOST, NEW FILE.
+       (cmd_erase) New function.
+       [unix] (shell) New function.
+       (run_command) New function.
+       (cmd_host) New function.
+       (cmd_new_file) New function.
+
+       * expr-prs.c: (parse_primary) Message fix.
+
+       * inpt-pgm.c: Formatting fix.
+       (cmd_reread) Implement the FILE subcommand.
+
+       * matrix-data.c: New file.
+
+       * q2c.c: (dump_header) Change output commenting style.
+
+       * weight.c: Comment fix.
+
+Tue Jan 13 00:53:39 1998  Ben Pfaff  <blp@gnu.org>
+
+       * aggregate.c: (buf64_10x) Renamed buf64_1xx, all references
+       changed.
+       (buf_10x) Renamed buf_1xx, all references changed.
+       (cmd_aggregate) Implemented cases 010, 011, 110, and 111 (all
+       cases now implemented).
+       (create_sysfile) New function.
+       (agr_11x_func) New function.
+
+       * data-in.c: (parse_numeric) Work properly if there's an
+       explicitly coded decimal point in the data and decimal places are
+       specified on DATA LIST.  Bug reported by Dr Eberhard W Lisse
+       <el@linux.lisse.na>.
+
+       * get.c: (cmd_get, cmd_save_internal) Allow extraneous slash
+       before file specification on GET, SAVE, XSAVE.  Bug reported by Dr
+       Eberhard W Lisse <el@linux.lisse.na>.
+
+       * q2c.c: [!HAVE_STRERROR] Include misc/strerror.c, not
+       strerror.c.  Bug reported by Alexandre Oliva
+       <oliva@dcc.unicamp.br>.
+
+       * sort.c: Include sort.h.  Comment fixes.  A few esthetic fixes.
+       (static var separate_case_tab) New var.
+       (cmd_sort_cases) Cancel temporary transformations here.  Free
+       v_sort before return.
+       (sort_cases) Run an EXECUTE procedure if SEPARATE is nonzero and
+       we're reading from a sort stream.  Don't cancel temporary
+       transformations.  Offload internal sorting to do_internal_sort().
+       (do_internal_sort) New function.  Handles internal sorting even
+       when SEPARATE is nonzero.  Doesn't free v_sort.
+       (do_external_sort) Take new arg SEPARATE.  Only destroy `x' if
+       it's non-NULL.
+       (write_initial_runs) Take new arg SEPARATE.  Only destroy the old
+       sink if SEPARATE is zero.
+       (read_output_cases) Renamed read_sort_output(), all references
+       changed.  Now uses separate_case_tab when it exists.
+       (write_separate) New function.
+
+       * vfm.c: (page_to_disk) Destroy memory_source_cases, not
+       memory_sink_cases.  Don't redundantly call
+       vfm_source->destroy_source().
+       (memory_stream_mode) After switching over, set memory_sink_cases
+       to NULL.
+
+Sat Jan 10 23:35:51 1998  Ben Pfaff  <blp@gnu.org>
+
+       * aggregate.c: (struct agr_var) Expand dbl[] array from 2 to 3
+       elements.
+       (static var prev_case) New, moved out of aggregate_single_case()
+       local scope.
+       (static var buf64_10x, buf_10x) New.
+       (cmd_aggregate) Initialize prev_case.  Comment fixes.  Implement
+       the 000, 001, 100, and 101 cases.  Free prev_case.
+       (parse_aggregate_functions) Disallow scratch variables.
+       (free_aggregate_functions) Only free agr_dict if non-null.  Use
+       iter->function to determine numeric/string type, not
+       iter->src->type.
+       (aggregate_single_case) Don't manage prev_case.  Initialize
+       aggregate info after dumping it.
+       (accumulate_aggregate_info) Fix sum, weighted sum, mean, weighted
+       mean, stddev, weighted stddev definitions.
+       (dump_aggregate_info) Implemented.
+       (initialize_aggregate_info) Renamed from
+       initialize_aggregate_functions().  Initializes dbl[2].
+       (agr_00x_trns_proc, agr_00x_end_func, write_case_to_sfm,
+       agr_10x_trns_proc, agr_10x_trns_free, agr_10x_end_func) New.
+
+       * cases.c: (alloc_val) Removed.
+
+       * get.c: (cmd_save_internal) Initialize new `dict' member.
+
+       * sfm-write.c: (sfm_write_dictionary, write_header,
+       write_variable, write_value_labels, write_documents) Reorganize,
+       simplify for new parameter structure.
+       (write_variable) Only one variable * argument now.
+
+       * sfm.h: (struct sfm_write_info) Removed `pri', `sec', and
+       replaced by new `dict' member.
+
+       * temporary.c: (new_dictionary) Initialize n_documents.
+
+       * vars-atr.c: (dup_variable) Allocate `value's from dict into
+       v->fv manually.
+       (init_variable, replace_variable) Eliminate usage of alloc_val().
+
+       * vars-prs.c: (parse_DATA_LIST_vars) Accept PV_NO_SCRATCH option.
+
+       * vfm.c: (arrange_compaction) Allow `temporary' value of 2 to
+       signal that AGGREGATE is to be used for forming final cases.
+       (close_active_file) Call end_func before stopping lagging.  Cancel
+       temporary after finishing compaction.
+       (write_case) Comment fixes.  Cleaned up.
+       (compact_case) Let AGGREGATE handle compaction when `temporary' is
+       2.
+
+Sat Jan 10 02:10:47 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (BUILT_SOURCES) Add means.c.
+       (pspp_SOURCES) Add means.c.
+       (EXTRA_DIST) Add means.q.
+
+       * command.c: (array cmd_table[]) Add MEANS.
+
+       * common.h: Esthetic fixes.  Comment fixes.  Test for
+       MAX_SHORT_STRING greater than 8.
+       (macros LOWEST, HIGHEST) New.
+
+       * data-in.c, data-list.c, recode.c: Comment fixes.
+
+       * means.q: New file, base version.
+
+       * mis-val.c: (parse_num_or_range, parse_numeric) Replace -DBL_MAX
+       with LOWEST, DBL_MAX with HIGHEST.
+
+       * q2c.c: (dump_vars) Add an enum to array types giving the number
+       of values for the enum.
+
+       * sfm-read.c: (sfm_read_dictionary, read_machine_flt64_info)
+       Replace second_lowest_value with second_lowest_flt64.
+
+       * sfm-write.c: (write_variable, write_rec_7_34) Replace
+       second_lowest_value with second_lowest_flt64.
+
+       * t-test.q: Comment fix.
+
+       * temporary.c: (restore_dictionary) Esthetic fix.
+
+       * tokens.h: (force_match_id, force_match, force_string, force_int,
+       force_num, force_id) Replace msg() with syntax_error().
+
+       * var.h: (struct means_proc) New.
+       (struct variable) Add mns member to `p' union.
+
+       * vars-prs.c: (parse_variable, parse_dict_variable,
+       parse_variables, parse_DATA_LIST_vars) Replace msg() with
+       syntax_error().
+
+Thu Jan  8 22:28:41 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (pspp_SOURCES) Add tab.h.
+
+       * Most source files: Added a cast to unsigned char in usages of
+       the ctype is*() functions.  Replaced `end of command expected'
+       calls to msg() with calls to syntax_error().
+
+       * frequencies.q: (dump_condensed) Fix tab_dim() column reference.
+
+       * lexer.c: (hex_val) Removed (was dead code).
+       (idmatch) Parenthesize function name to avoid macro expansion.
+
+       * postscript.c: Comment fixes.
+       (ps_preopen_driver) Change default font size to 10pt.
+
+       * sfm-read.c: (read_variables) Byteswap sv.print, sv.write as
+       int32s.
+       (parse_format_spec) Change system-file format spec argument type
+       to int32.  Parse the format spec with bitwise operators.
+
+       * sfmP.h: (struct sysfile_format) Removed.
+       (struct sysfile_variable) Changed print, write members from
+       sysfile_format to int32.
+
+       * tokens.h: Esthetic fixes.
+       [__GNUC__] (macro id_match) New macro to hopefully speed up
+       identifier matching.
+       (macros match_id, match_tok, match_int) Implemented in
+       compiler-independent manner; no longer GNU C only.
+
+       * vfm.h: Include time.h.
+
+Mon Jan  5 11:06:15 1998  Ben Pfaff  <blp@gnu.org>
+
+       * data-list.c: (dump_fixed_table) Change tab_dim().
+
+       * dump-sysfile.c: (open_sysfile) Fix mmap() call.
+
+       * error.c: Include command.h.
+
+       * frequencies.g: Formatting fixes.
+
+       * frequencies.q: Add tab_dim() calls.  Make the total cell a
+       joined cell.
+
+       * glob.c: Include command.h.
+
+       * sfm-read.c: (struct sfm_fhuser_ext) New members sysmis, highest,
+       lowest.
+       (sfm_read_dictionary) Initialize sysmis, highest, lowest.
+       (sfm_read_machine_flt64_info) Update sysmis, highest, lowest.
+       (read_variables) Byteswap sv.type; byteswap sv.print, sv.write as
+       the other elements (is this right?).
+       (read_variables) Use lowest, highest members.
+       (parse_format_spec) New arg `vv' for more stringent checking.
+       (dump_dictionary) Byteswaps nonexplicit data.
+       (sfm_read_case) Byteswap numeric data.
+
+       * som.c: Initialize table_num to 1.
+       (render_segments) Remember to increment y_index after each table
+       segment.
+
+       * sysfile-info.c: (cmd_sysfile_info) Change tab_dim().  Don't call
+       avl_count() on a NULL tree.  No title for the second table.
+       (cmd_display) Handle DISPLAY VECTORS by calling display_vectors().
+       Handle AS_SCRATCH as AS_NAMES.  Warn if no variables.  Re-enable;
+       fix call to display_variables().
+       (display_variables) Default to 4 columns, not 3.  Set up headers.
+       Column title is Variable, not Name.  Fix index column.
+       Add joint text.  Add tab_dim().  Handle value labels properly.
+       Handle DISPLAY LABELS properly.  Draw boxes correctly.
+       (describe_variable) Value labels don't need titles.  Don't clear
+       nonexistent index column.
+       (compare_vectors_by_name) New function.
+       (display_vectors) New function.
+
+       * tab.c: (tab_height) Add assertion.
+       (tab_null) Add debug code.
+       (evaluate_dimensions) Add debug code.
+
+       * var.h: (struct variable) get_proc data is sometimes used
+       simultaneously with other per-procedure info, therefore it was
+       removed from the union.  All references changed.        
+
+Sun Jan  4 18:13:33 1998  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c: (ascii_close_page) Put title on second line of headers
+       if there is no subtitle.
+
+       * command.c: (glob var cur_proc) Move definition here, from
+       common.c.
+       (cmd_remark) Emit blank line before remarks.
+
+       * command.h: (glob var cur_proc) Move declaration here, from
+       common.h.
+
+       * data-list.c: (dump_fixed_table) Fix messages.
+       (dump_free_table) Call tab_nat_dim().
+
+       * descript.q: (dump_z_table) Modify tab_dim() call.
+
+       * frequencies.q: (dump_condensed, dump_statistics) Add tab_dim()
+       call.
+       (dump_statistics) Don't output header.
+
+       * groff-font.c: Minor format fix.
+
+       * html.c: Comment fix.
+
+       * list.q: (write_varname) Indent after advancing page.
+
+       * output.h: Minor reordering.
+
+       * postscript.c: Comment fixes.  Many places, '\n' was replaced by
+       a reference to eol[].
+       (struct ps_driver_ext) New member eol[].
+       (ps_preopen_driver) Initialize eol[].
+       (ps_postopen_driver) Fix sense of text for text_opt, line_opt
+       defaults.  Handle headers.  Fix test for minimum page length.
+       (static var option_tab[]) Add `line-ends'.
+       (ps_option) Handle line-ends to change eol[].
+       (postopen) Scale prop_em_width and fixed_width properly.  Set the
+       prologue title to outp_title if applicable.  Replace the prologue
+       line ends with eol[].  Call draw_headers() if headers are enabled.
+       (text_width) New function.
+       (out_text_plain) New function.
+       (draw_headers) New function.
+
+       * print.c: (dump_table) Call tab_nat_dim().
+
+       * som.c: (som_blank_line) Only advance a line if not at the top of
+       a page.
+       (som_submit) Move several informational table calls here.
+       Increment subtable_num if SOMF_NO_TITLE not set.
+       (output_table) Advance a line if SOMF_NO_SPACING not set.
+       (render_columns, render_segments, render_simple) Handle spacing
+       between tables.  Handle table titles.  Remove debug output.
+
+       * som.h: (SOMF_*) New enum series.
+       (struct som_table_class) New member `flags'.
+
+       * sysfile-info.c: (cmd_sysfile_info) Calls tab_nat_dim().  No
+       headers or spacing.
+       (display_variables) Calls tab_nat_dim().
+       (describe_variable) Remove restriction on number of value labels.
+       Make value labels separated by thin lines.
+
+       * tab.c: (tab_create) Default `flags' to none.
+       (tab_float) New arg `w'.  All references changed.
+       (tab_nat_dim) New function.
+       (tab_output_text) No title or spacing.
+       (tab_flags) New function.
+       (tabi_flags) New function.
+       (tabi_title) New function.
+       (strip_height) Removed.
+       (tabi_render) Skip title when necessary.
+       (static var tab_tab_class) Add tabi_flags, tabi_title.
+       (evaluate_dimensions) Disable display of column, row size.
+       (sum_columns) Add title height to top header.
+       (render_strip) Moved within file.
+
+       * tab.h: (struct tab_table) New member `flags'.
+
+       * vfm.c: (dump_splits) Calls tab_nat_dim().  No title.
+
+Sat Jan  3 16:55:44 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Most source files: Add `const' attribute in all appropriate
+       places.
+       
+       * sysfile-info.c: (cmd_sysfile_info) Add tab_dim() call, add a
+       column to the variables table for use by describe_variable().
+       (cmd_display) Disable for the present.
+       (display_documents) Don't wrap documents.
+       (display_variables) Table has four columns now.
+       (describe_variable) Table has four columns now.  Don't use a
+       subtable, use joined cells instead.
+
+       * tab.c: (tab_create) Don't set `join'.
+       (tab_realloc) ct array is not made up of a_string's.
+       Reallocate trh, hrh, h arrays, initialize trh array.  Initialize
+       cell contents on GLOBAL_DEBUGGING, not DEBUGGING.
+       (text_format) New function.
+       (tab_title) Rewritten, uses text_format().
+       (tab_text) Rewritten, uses text_format().
+       (tab_joint_text) New function.
+       (tab_join) Removed.
+       (static var hit) New variable.
+       (render_strip) New args r1, r2.  Implement joined cells that fit
+       on a single page.
+       (tabi_render) Increment hit.  Pass new args to render_strip().
+       (evaluate_dimensions) [GLOBAL_DEBUGGING] Check for uninitialized
+       cells.  For t_naw and t_nah, ignore joined cells and null cells in
+       calculations.
+       
+       * tab.h: (struct tab_join_rect) Removed.
+       (struct tab_table) Removed `join'.
+       (TAB_JOIN_MAIN) Removed.
+       (struct tab_joined_cell) New struct.
+       (TAT_NOWRAP) New enum.
+
+Fri Jan  2 01:39:58 1998  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c: (ascii_postopen) Replace ASCII_* macros with their
+       expansions.
+       (ascii_postopen_driver) Fix initialization of *_spacing so that
+       the TAL_0 bit doesn't count.
+
+       * data-list.c: (dump_fixed_table) Use natural width for Format
+       column.
+
+       * glob.c: (rerange) Removed.
+       (get_date) Formatting fixes.  Internationalization fix.
+
+       * html.c: (html_postopen_driver) Replace HTML_DEFAULT_OUTPUT_FILE
+       with "pspp.html".
+
+       * postscript.c: (ps_postopen_driver) Replace
+       PS_DEFAULT_OUTPUT_FILE with "pspp.ps".
+
+       * som.c: (som_submit) Don't eject page before every table.
+       (output_table) Fix order of arguments on call to area().
+       (render_columns) Fix calculation of max_len.
+       
+       * tab.c: (tabi_cumulate) Minor change to increase elegance.
+       (render_strip) New function.
+       (strip_height) New function.
+       (tabi_render) Rewrite as calls to render_strip().
+
+       * tab.h: (TAT_* enums) Removed TAT_RICH, all references removed.
+       Renumbered TAT_PRINTF, TAT_TITLE, TAT_FIX to correspond better
+       with the TAB_* and OUTP_T_* constants.
+       
+Thu Jan  1 11:53:52 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Formatting fixes.
+
+       * ascii.c: (ascii_postopen_driver) Initialize *_line_spacing[],
+       *_line_width[].
+
+       * data-list.c: (dump_fixed_table) Add tab_dim() call.
+
+       * descript.q: (dump_z_table, display) Add tab_dim() calls.
+
+       * dump-sysfile.c: (glob var length) Make type off_t.
+       (usage) Fix arguments.
+       (main) Return 0.
+
+       * output.h: (OUTP_T_*) Change constants' value to match tab.h.
+       Now right-justification is the default so many references had to
+       change.
+       (struct outp_class) Removed line_width, all references changed.
+       (OUTP_DEV_*) Add OUTP_DEV_DISABLED.
+       (struct outp_driver) Add elements horiz_line_width,
+       vert_line_width, horiz_line_spacing, vert_line_spacing.  Remove
+       som element.
+
+       * postscript.c: (outp_encodings) Formatting fixes.  Fix garbage
+       collection.
+       (postopen) Initialize all the informational members of
+       outp_driver.
+
+       * som.c: (som_blank_line) New function, renamed from blank_line(),
+       all references changed.
+       (som_submit) Disables drivers whose pages can't be opened.
+       (render_columns, render_simple, render_segments) Add debug output.
+       (render_columns) Fix loop range.
+       (render_simple) Don't try to render the headers, they're taken
+       care of automatically.  Advance cp_y past the table when done.
+       (render_segments) Fix loop ranges.
+
+       * tab.c: Initialize new members of tab_table.
+       (tab_vline) Handle trv[]; don't set style for spacing-only lines.
+       (tab_hline) Handle trh[]; don't set style for spacing-only lines.
+       (tab_box) Handle trh[], trv[]; don't set style for spacing-only
+       lines.
+       (set_expr) Removed.
+       (tab_dim) New function.
+       (tab_col_width) Removed.
+       (tab_row_height) Removed.
+       (tab_output_text) Call tab_dim().
+       (tabi_driver) Call evaluate_dimensions(), sum_columns().
+       (tabi_area) Implemented.
+       (tabi_cumulate) Implemented.
+       (tabi_render) Partially implemented, but broken.
+       (var tab_table_class) Made static.
+       (evaluate_dimensions) New function.
+       (sum_columns) New function.
+
+       * tab.h: (enum t_*) Now start at t_end.  New: t_ptw, t_nr, t_nc,
+       t_nah, t_naw, t_neg, t_xch, t_dup, t_lbl, t_jnz, t_sac, t_sar,
+       t_scr, t_srr, t_sentinel.  Removed: t_nat.
+       (struct tab_table) New: wl, wr, ht, hb, trh, hrh, trv, wrv, dim,
+       max_stack_height, w, h.  Removed: ce, re.
+       (macro blank_line) Removed.
+       (glob var zero_length) Removed.
+
+Fri Dec 26 15:44:31 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Most source files: include some of the new include files broken
+       out of var.h.
+       
+       * Makefile.am: (pspp_SOURCES) Add all the new source files to the
+       list.
+
+       * aggregate.c: (glob var outfile) Make static.
+
+       * command.c: (glob var pgm_state) Move here.
+
+       * common.c: (glob vars endian, second_lowest_value, pgmname,
+       finished, curdate, cur_proc, start_interactive, history_file) Move
+       here.
+
+       * descript.q: (structs dsc_z_score, descriptives_trns) Move here.
+
+       * file-handle.q: (glob vars files, inline_file) Move here.
+
+       * glob.c: Lost lots of glob vars, detailed in individual file
+       entries.
+       (init_glob) set_printer, set_screen were obsolete, deleted.
+       set_cprompt has fewer spaces because pspp has fewer letters than
+       fiasco.
+
+       * inpt-pgm.c: (glob vars inp_init, inp_init_size) Move here.
+       (inp_nval) Made static.
+
+       * lexer.c: (glob vars token, tokval, tokstr, tokstr_size,
+       tokstr_len, toklongstr, tokint) Move here.
+
+       * misc.c: Lost several vars and functions.
+
+       * set.q: (all the set_* variables) Move here.
+
+       * str.c: (strmaxcpy, strbarepadcpy, strbarepadlencpy, strpadcpy,
+       blpstrset, strpadcmp, memrev, memrmem, cmp_str) Move here from
+       misc.c.
+
+       * tab.c: (set_expr, tab_col_width, tab_row_height) New functions.
+
+       * tab.h: (enum series t_*) New enums.
+       (struct tab_table) Use arena struct tag.  New members ce, re.
+
+       * tokens.h: Comment fixes.
+
+       * var.h: Move lots of enums and variables and functions and
+       structures to other files.  Use and declare a lot more union and
+       struct tags.  Comment fixes.  
+
+       * vector.c: (glob vars vec, nvec) Move here.
+
+       * vfm.c: (glob vars reinit_sysmis, reinit_blanks, init_zero,
+       init_blanks, last_vfm_invocation) Move here.
+
+       * cases.h: New file.
+       (struct long_vec) Move here.
+       (vec_init, vec_clear, vec_insert, vec_delete, devector, envector)
+       Move here.
+
+       * command.h: New file.
+       (STATE_* enums) Move here.
+       (glob var pgm_state) Move here.
+
+       * format.c: New file.
+       (glob var formats) Move here.
+       (parse_format_specifier_name, fmt_to_string,
+       check_input_specifier, check_output_specifier,
+       check_string_specifier, convert_fmt_ItoO, parse_format_specifier)
+       Move here.
+
+       * format.h: New file.  Move functions now in format.c here.
+       (FMT_* enums) Move here.
+       (struct fmt_desc) Move here.
+       (FCAT_* enums) Move here.
+       (struct fmt_spec) Move here.
+       (glob vars formats, fmt_parse_ignore_error) Move here.
+
+       * inpt-pgm.h: New file.
+       (INP_* enums) Move here, make #defines into enums.
+       (glob vars inp_init, inp_init_size) Move here.
+
+       * sort.h: New file.
+       (glob vars v_sort, nv_sort) Move here.
+       (sort_cases, read_sort_output) Move here.
+
+       * vector.h: New file.
+       (struct vector) Move here, add struct tag.
+       (glob vars vec, nvec) Move here.
+       (find_vector) Move here.
+
+       * New file.
+       (glob vars last_vfm_invocation, temp_case, reinit_sysmis,
+       reinit_blanks, init_zero, init_blanks) Move here.
+       (struct case_stream) Move here.
+       (glob vars vfm_source, vfm_sink, vfm_memory_stream,
+       vfm_disk_stream, sort_stream, data_list_source,
+       input_program_source, file_type_source, get_source, n_lag) Move
+       here.
+       (procedure, write_case, lagged_case, compact_case, page_to_disk)
+       Move here.
+               
+Wed Dec 24 22:40:42 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (pspp_SOURCES) Added html.c, som.c, som.h.
+       (LDADD) Add libdcdflib.
+
+       * ascii.c: Comment and formatting fixes.  Almost every external
+       function had an assert added, checking driver_open and page_open.
+       (ascii_init_driver) Broken into ascii_preopen_driver,
+       ascii_postopen_driver, ascii_close_driver.  Manages driver_open.
+       (ascii_open_page) Sets page_open.
+       (ascii_close_page) Clears page_open.
+
+       * html.c: Comment and formatting fixes.  Almost every external
+       function had an assert added, checking driver_open and page_open.
+       (html_init_driver) Broken into html_preopen_driver,
+       html_postopen_driver, html_close_driver.  Manages driver_open.
+       (html_open_page) Sets page_open.
+       (html_close_page) Clears page_open.
+       (html_submit) Disabled.
+
+       * lexer.c: (parse_string) Remove debugging printf.
+
+       * list.q: (determine_layout) Open a page if one is not yet open.
+
+       * output.c: Comment fixes.
+       (add_class) Set the class member of the new list element.
+       (parse_options) Don't handle device type.
+       (colon_tokenize) New function.
+       (configure_driver) New four-field format with a field for device
+       type.  Now initialize driver_open, page_open, next, and prev
+       fields.  Use new colon_tokenize() function.  Don't do a memory
+       copy to replace a driver, it doesn't work; instead delete the old
+       driver and insert a new one.
+       (destroy_driver) Don't call som_destroy_driver().  Close the page
+       if it's open.  Find the class in the list of classes and decrement
+       that reference count.  Remove the driver from the global driver
+       list.
+       (outp_iterate_enabled_drivers) Renamed outp_drivers().  All
+       references changed.  Rewritten.  Don't return a driver that's not
+       enabled.
+       (outp_eject_page) All references to som_internal_eject_page()
+       changed to use this.  Sets cp_x to 0 as well as cp_y.
+
+       * output.h: (OUTP_I_* enums) Removed.
+       (struct som_submission_form) Removed.
+       (struct outp_class) init_driver broken into preopen_driver,
+       postopen_driver, and close_driver.  submit changed to take a
+       som_table argument.
+
+       * postscript.c: Comment and formatting fixes.  Almost every
+       external function had an assert added, checking driver_open and
+       page_open.
+       (ps_init_driver) Broken into ps_preopen_driver,
+       ps_postopen_driver, ps_close_driver.  Manages driver_open.
+       (ps_open_page) Sets page_open.
+       (ps_close_page) Clears page_open.
+
+       * som.c: New file, base implementation.
+       
+       * som.h: (struct som_table) Add struct tag.
+       (enum SOM_COL_ACROSS) Removed.
+       (SOM_ROWS, SOM_COLUMNS) New enums.
+       (struct som_table_class) Add member `cumulate'.  Remove `segment';
+       change `render' arguments.
+       (struct som_point, struct som_rect) Removed.
+       (som_submit_table) Fixed typo, should have been som_submit.
+
+       * sysfile-info: (describe_variable) Don't try to insert a
+       subtable; just destroy it for now.
+
+       * t-test.q: Include dcdflib/cdflib.h instead of cdflib.h.  Fix
+       references to value labels.
+
+       * tab.c: (tab_destroy) New function.
+       (tab_columns) Change argument.
+       [0] (tab_submit) Remove dead code.
+       (tab_title) Allocate string from the table's arena.
+       (tab_output_text) Only free the buffer if we allocated it.
+       (tab_submit) New function.
+       (static vars t, d) New static vars.
+       (tabi_table, tabi_driver, tabi_count, tabi_area, tabi_columns,
+       tabi_headers, tabi_cumulate, tabi_render) New functions.
+       (glob var tab_table_class) New global var.
+
+       * tab.h: (struct tab_join_rect) Don't use a som_rect; directly
+       encapsulate the rectangle.  All references changed.
+       
+Sun Dec 21 16:18:58 1997  Ben Pfaff  <blp@gnu.org>
+
+       * All header files updated to use struct tags in addition to
+       typedefs for all structures.  Don't use word `struct' in struct
+       tags.
+       
+       * Makefile.am: (pspp_SOURCES) Remove html.c.
+       (INCLUDES) Replace the lib/* includes with a single lib/ include;
+       all references updated.
+
+       * command.c: (parse_cmd) Remove call to som_check_workspace.
+       (output_line) Update to new som.
+
+       * data-in.c: (parse_numeric) A single dot is not an error; it is
+       the system-missing value.
+
+       * data-list.c: (dump_fixed_table, dump_free_table) Update to new
+       som.
+
+       * data-out.c: Added `const' as appropriate to many prototypes.
+       (convert_E, convert_F, convert_CCx) Take double argument instead
+       of value * argument.
+       (convert_format_to_string) Call changed functions appropriately.
+       Instead of modifying the caller's value for FCAT_SHIFT_DECIMAL,
+       make a local copy of the value.
+
+       * descript.q: Remove custom_variables() prototype now provided by
+       q2c.  
+       (custom_variables) Don't increment sbc_variables, the caller does
+       this.
+       (dump_z_table, display) Update to new som.
+
+       * error.c: (vmsg) Add const to prototype.  Remove code to handle
+       `too many errors' condition.
+       (check_error_count) New function.
+       (msg) Add const to prototype.
+
+       * filename.c: (open_file) Rewrite for elegance.
+
+       * frequencies.q: Remove custom_*() prototypes now provided by q2c.
+       (dump_full, dump_condensed, dump_statistics) Update for new som.
+
+       * list.q: Don't include somP.h.  Change all references to
+       som_driver_ext to refer to the new members of som_driver.  Change
+       som_internal_eject_page() references to outp_eject_page().
+
+       * main.c: (parse) Rewrite for elegance.  Add call to
+       check_error_count().
+
+       * output.c: (add_class, outp_list_classes, outp_configure_driver)
+       Rewrite or revise for new outp_driver_class_list structure.
+       (outp_iterate_enabled_drivers) Fix comparison between disabled
+       devices and current device type.
+       (outp_eject_page) New function.
+
+       * output.h: Comment fixes.
+       (struct outp_driver) New members driver_open, page_open, cp_x,
+       cp_y, font_height, prop_em_width, fixed_width.  Deleted members
+       ref_count, next.
+       (struct outp_driver_class_list) New struct.
+       (outp_class_list) Changed to type outp_driver_class_list; all
+       references updated.
+
+       * print.c: (dump_table, print_trns_proc) Updated for new som.
+
+       * q2c.c: (dump_vars) Simplify array subcommand code.  Declare
+       prototypes for custom subcommands.
+       (dump_subcommand) Always include the `else'.
+       (dump_parser) Fix comments in output code.
+
+       * set.q: Reordered functions.
+
+       * som-frnt.c, som-high.c, som-low.c, somP.h: Removed.
+       
+       * som.h: Rewritten from scratch.
+
+       * str.h: Remove dead code.
+
+       * tab.c, tab.h: New files, base implementation.
+
+       * sysfile-info.c: (cmd_sysfile_info, describe_variable) Update to
+       new som.
+
+       * t-test.q: New code from John Williams
+       <johnr.williams@stonebow.otago.ac.nz>.  Include math.h, cdflib.h.
+       Many many new static vars and defines.
+       (precalc, postcalc, g_postcalc, z_postcalc, t_pairs, t_groups,
+       groups_calc, pairs_calc, z_dev_calc, z_calc) New functions.
+       (struct value_list) New struct.
+       (variance, covariance, pooled_variance, oneway, pearson_r, f_sig,
+       t_crt, t_sig, print_t_groups) New functions.
+       (cmd_t_test) Implemented.
+
+       * temporary.c: (cancel_temporary) Only free the temp_dict if it's
+       non-NULL.
+
+       * vfm.c: (dump_splits) Update to new som.
+
+Thu Dec  4 23:02:22 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (fiasco_SOURCES) Add html.c.
+
+       * aggregate.c: Base source.
+
+       * ascii.c: (postopen, preclose) Reformat.
+
+       * data-out.c, expr-evl.c: Comment fixes.
+       
+       * filename.c: (open_file) When opening a file for writing, use
+       line buffering instead of full buffering for better interactive
+       performance.  Suggested by Valerio Aimale
+       <valerio@svpop.com.dist.unige.it>.  Also, recognize special file
+       names `stdin', `stdout', `stderr'.
+
+       * groff-font.c: Comment fixes.
+
+       * html.c: New file; base version.
+
+       * list.q: (write_all_headers, clean_up, determine_layout,
+       list_cases) Ignore `special' devices for now.  Needs to be fixed
+       later.
+
+       * output.c: (outp_init) Add html driver to list; reverse list
+       order.
+
+       * output.h: (struct outp_class_struct) New members `special',
+       `submit'; comment fixes.  All references changed.
+
+       * postscript.c: (ps_init_driver) Make defaults for text_opt,
+       line_opt depend on whether the OUTP_DEV_SCREEN bit is set on the
+       device.
+       (postopen) Comment fix.
+       (preclose) Comment fixes, formatting fixes.  Change x->file.file
+       references to more proper f->file.
+
+       * som-high.c: (som_submit_table) Special classes use their own
+       renderers.
+
+       * som.h: Comment fixes.
+
+       * temporary.c: (new_dictionary) Don't try to xstrdup() a NULL
+       string.
+       
+Tue Dec  2 14:36:07 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (fiasco_SOURCES) Add aggregate.c back in.
+
+       * aggregate.c: Still working on this.
+
+       * command.c: (cmd_table[]) Add AGGREGATE back in.
+       (split_words) Make '-' a legal word separator as well as ' '.
+
+       * main.c: Comment fixes.
+
+       * q2c.c: (dump_parser) Don't require the procedure's full name to
+       be present, in the generated source.
+
+       * t-test.q: Change name to `t-test' from `t test'.  Let PAIRS be
+       multiply specified and let it be default; let MISSING, CRITERIA,
+       FORMAT be multiply specified.
+       (cmd_t_test) Parse command name.  [DEBUGGING] Call debug_print().
+       (custom_groups) Fix defaults.
+       (custom_pairs) Check whether this is a PAIRS subcommand before
+       attempting to parse.  Better garbage collection.  Proper storage
+       allocation.
+       [DEBUGGING] (debug_print) New function.
+
+       * temporary.c: Comment fixes.
+       (copy_variable) Don't copy variable name and index.
+       (save_dictionary) Copy variable name and index by hand.
+
+       * vars-atr.c: Comment fixes.
+       (create_variable) New dictionary argument.  All references
+       changed.
+       (common_init_stuff) New dictionary argument.  All references
+       changed.
+       (init_variable) New dictionary argument.  All references changed.
+       (dup_variable) New function.
+
+       * vars-prs.c: (parse_variables) If there are any errors, we always
+       return 0.  Previously, it was possible for some types of errors to
+       be ignored.
+       
+Sat Nov 22 01:20:59 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (fiasco_SOURCES) For 0.1.5 release, remove
+       aggregate.c.
+
+       * command.c: (cmd_table[]) Comment out AGGREGATE; add T TEST.
+
+       * list.q, t-test.q: Remove ALL option from VARLIST declaration in
+       grammar rules.
+
+       * q2c.c: Comment fixes.
+       (SBC_* enums) Remove SBC_VARLIST_ALL; all references removed.
+       
+       * t-test.q: (cmd_list) Rename cmd_t_test.
+
+       * temporary.c: (new_dictionary) Don't declare as static.
+       
+Fri Nov 21 00:03:06 1997  Ben Pfaff  <blp@gnu.org>
+
+       * aggregate.c: Changes, still not finished.
+
+       * file-handle.q, frequencies.q, list.q, set.q: Comment fixes.
+
+       * q2c.c: Comment fixes.  Now its output is internationalized.
+       (get_token) Fix parsing of escapes within literal strings.
+       (main) Fix bad #line directives in output.
+
+       * t-test.q: Base implementation.
+
+       * temporary.c: (new_dictionary) New function.
+       (restore_dictionary) [__CHECKER__] Change fill character to *
+       (from @).
+       
+Sun Nov 16 01:29:57 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (BUILT_SOURCES, fiasco_SOURCES) Add t-test.c
+
+       * aggregate.c: Changes, still not finished.
+
+       * descript.q, list.q: Comment fixes.
+
+       * q2c.c: Almost completely rewritten.
+
+       * t-test.q: New file, not complete.
+
+Fri Nov 14 00:14:48 1997  Ben Pfaff  <blp@gnu.org>
+
+       * aggregate.c: Changes, still not finished.
+
+       * sort.c: (sort_cases) Call cancel_temporary() instead of doing it
+       by hand.
+
+       * temporary.c: (cancel_temporary) New function.
+
+       * vars-atr.c: (discard_variables) Call cancel_temporary() instead
+       of doing it by hand.
+
+       * vfm.c: (close_active_file) After restoring a TEMPORARY
+       dictionary, set temp_dict to NULL.  Cancel TEMPORARY through
+       cancel_temporary().
+       (SPLIT_FILE_procfunc) Comment fix.
+
+Tue Oct 28 16:08:45 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (fiasco_SOURCES) Add aggregate.c.
+
+       * aggregate.c: New file, not finished yet.
+
+       * command.c: (cmd_table) Add AGGREGATE.
+
+       * common.h: (pgm_state) Move declaration to var.h.
+
+       * lexer.c: (bin_value_func, oct_value_func, hex_value_func) i18n
+       fixes.
+       (parse_string) Message fix.
+
+       * recode.c: Comment fix.
+
+       * sfm-read.c: (read_variables) Code esthetic fixes.
+       (write_header) Default date is `Jan', not `JAN'.
+
+       * sfmP.h: (bswap_int32) [!__linux__] Fix off-by-one errors.
+
+       * sort.c: (cmd_sort_cases) Farm the work out to new function
+       parse_sort_variables().
+       (parse_sort_variables) New function.
+       (sort_cases) New function.  Cancels temporary transformations,
+       which sorting didn't do previously.
+       (cmd_sort_cases) Better garbage collection on error.  Uses
+       do_external_sort().
+       (write_initial_runs, merge_once) Improved code esthetics.
+       (sort_stream_read) Reduced to one call to read_output_cases().
+       (read_output_cases) New function.
+
+       * var-labs.c: (cmd_variable_labels) Re-enabled truncation of
+       variable labels to 120 characters.
+
+       * var.h: Comment fixes.
+       (glob var pgm_state) From common.h.
+
+       * vars-atr.c: (discard_variables) Set pgm_state to STATE_INIT.
+
+       * vars-prs.c: (parse_DATA_LIST_vars) Support PV_SINGLE in
+       options.  Set *names to NULL on error.
+
+       * vfm.c: (memory_stream_init) Assert compaction_nval != 0.
+
+Thu Oct  9 09:59:49 1997  Ben Pfaff  <blp@gnu.org>
+
+       * sfm-write.c, vfm.c: [HAVE_UNISTD] #include <unistd.h>, needed by
+       SunOS4.  From Alexandre Oliva <oliva@dcc.unicamp.br>.
+
+Wed Oct  8 18:55:24 1997  Ben Pfaff  <blp@gnu.org>
+
+       * vfm.c: (page_to_disk) Added missing local variables.
+
+Tue Oct  7 20:23:17 1997  Ben Pfaff  <blp@gnu.org>
+
+       * get.c: Comment fix.
+
+       * sort.c: (cmd_sort_cases) Attempt to perform internal sort if the
+       source is anything other than a disk stream, not just if it's in a
+       memory stream.  Call page_to_disk() before external sort.
+       (allocate_cases) Message fix.
+
+       * vfm.c: (prepare_for_writing) Warn user when paging workspace to
+       disk.
+       (page_to_disk) New function.
+
+Sun Oct  5 15:56:14 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (INCLUDES) Include .. instead of $(top_srcdir).
+
+       * common.h: (macro strerror) Remove.  From Alexandre Oliva
+       <oliva@dcc.unicamp.br>.
+
+       * get.c: (dict_delete_run) The number of variables to delete is
+       not necessarily the number of variables that need to be shifted
+       up.
+       (trim_dictionary) Don't set *options to 0.  Fix bug that caused
+       too many variables to be deleted.
+
+       * postscript.c: Comment fix.
+
+       * q2c.c: Include strerror.c.  From Alexandre Oliva
+       <oliva@dcc.unicamp.br>.
+
+       * set.q: #undef ON and OFF.  From Alexandre Oliva
+       <oliva@dcc.unicamp.br>.
+
+       * sfm-read.c: (sfm_read_dictionary) Don't set the file class too
+       early, otherwise errors cause a bad free().
+
+       * str.h: (macro nvsprintf) s/FORMATS/FORMAT/ typo.  From Alexandre
+       Oliva <oliva@dcc.unicamp.br>.
+
+       * temporary.c: (save_dictionary) Don't allocate memory if
+       n_documents is 0.
+
+       * vfm.c: (memory_stream_write) Message fix.
+
+Sat Oct  4 16:20:43 1997  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: (static var cmd_table[]) Define REPEATING DATA
+       command.
+
+       * common.h: Added support for broken systems that are missing
+       EXIT_SUCCESS, EXIT_FAILURE, RAND_MAX, and/or strerror().
+
+       * Many source files: Replace syntax error messages via msg() with
+       call to syntax_error().
+
+       * data-list.c: (dump_fixed_table) Add support for dumping table
+       for REPEATING DATA as well as DATA LIST FIXED.
+       (cmd_repeating_data) Allows and requires `/' between subcommands.
+       Does proper thing with allowing rpd.starts_end to stay 0.  Allows
+       CONTINUED specifications to be omitted.  Forces CONTINUED to be
+       specified if ID is.  Calculates starts_end, cont_end from logical
+       record length as reported by fhp.  Calls dump_fixed_table() if
+       requested.  Fixed length of record copied by memcpy.
+       (parse_num_or_var) Sets `num' to 0, not NOT_INT, for variables.
+       Message fix.
+       (realize_value) Returns sensible value for out-of-range variable
+       values.
+       (rpd_parse_record) New argument `ofs'.  Fixed confusion between
+       length of occurrences and length of line.  Added warning for
+       fields that exceed the line length.  Fixed infinite loop.
+       (read_one_set_of_repetitions) Numerous minor changes for more
+       complete SPSS compliance.  Message fixes.
+
+       * dfm.c: (dfm_close) If the file being closed is the inline file,
+       read all the remaining data before closing it.
+       (dfm_get_record) Don't close the file on lossage, as either it
+       has been closed already or it doesn't belong to us.
+
+       * error.c: (puts_stdout) New function.
+       (vmsg) Use puts_stdout instead of puts.
+
+       * file-handle.q: (fh_record_width) New function.
+
+       * inpt-pgm.c: (init_case) Fixed buffer overrun when inp_nval % 4
+       == 0.
+       (clear_case) Ditto.
+       (input_program_source_read) Made an old kluge an approved method.
+
+       * lexer.c: (syntax_error) New function.
+
+       * misc.c: [BROKEN_RAND] (ansi_rand, ansi_srand; static var next)
+       New.
+
+       * output.c: (oupt_get_paper_size) Message fix.
+
+       * q2c.c: Numerous fixes to formatting of generated code made to
+       conform to GNU coding standards.  Uses syntax_error() in generated
+       code.  Other miscellaneous generated message fixes.  Added support
+       for broken systems that are missing EXIT_SUCCESS, EXIT_FAILURE,
+       RAND_MAX, and/or strerror().
+
+Sat Oct  4 02:09:56 1997  Ben Pfaff  <blp@gnu.org>
+
+       * data-in.c: Comment fixes.
+
+       * data-list.c: (struct repeating_data_trns) New member `id_spec'.
+       (find_variable_input_spec) New function.
+       (cmd_repeating_data) Initializes id_spec.
+       (rpd_parse_record) Implemented.
+       (read_one_set_of_repetitions) Returns -3 by default in order to
+       kluge out some potential bugs.
+
+       * data-out.c: Comment fixes.
+
+       * file-type.c: (internal_cmd_record_type) Message fix.
+
+       * inpt-pgm.c: (input_program_source_read) Special temporary kluge
+       for handling -3 return value.
+
+Sat Sep 20 23:58:15 1997  Ben Pfaff  <blp@gnu.org>
+
+       * data-list.c: Comment fixes.
+       (struct dls_var_spec) Reordered members.
+       (read_from_data_list_fixed) Restructured.
+       (struct repeating_data_trns) Reordered members.  Renamed `starts'
+       as `starts_beg', `ends' as `starts_end'.
+       (cmd_repeating_data) Calculates length of repeated data if
+       necessary and possible.
+       (parse_num_or_var) Don't allow string variables.
+       (realize_value) New function.
+       (rpd_msg) New function.
+       (rpd_parse_record) New function.  Currently stubbed out.
+       (read_one_set_of_repetitions) Implemented.
+
+       * inpt-pgm.c: (input_program_source_read) Comment fix.
+
+Thu Sep 18 21:34:57 1997  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: (cmd_end_repeat_p) Removed.
+       (init_cmd_parser) Doesn't set cmd_end_repeat_p.
+       (parse_cmd_name) Removed.
+
+       * data-list.c: Comment fixes.
+       (data_list_pgm) Removed `eof' member.
+       (static var first) New var.
+       (cmd_data_list) Sets `first'.  Ensures that DATA LIST uses the
+       FILE TYPE file inside FILE TYPE structures.
+       (append_var_spec) Appends to *first, not dls.spec.
+       (parse_fixed) Message fixes.
+       (struct rpd_num_or_var) New.
+       (struct repeating_data_trns) New.
+       (static var rpd) New.
+       (cmd_repeating_data) New function.
+       (parse_num_or_var) New function.
+       (parse_repeating_data) New function.
+       (read_one_set_of_repetitions) New function.
+
+       * file-type.c: (cmd_file_type) Message fixes.  Always
+       default_handle to FILE TYPE file handle.
+       (internal_cmd_record_type) Message fixes.
+
+Wed Aug 20 14:22:03 1997  Ben Pfaff  <blp@gnu.org>
+
+       * repeat.c: Comment fix.  Disable debugging.
+
+       * temporary.c: (restore_dictionary) Sets splits to NULL and
+       n_splits to 0 before destroying the variables because now doing
+       this tries to remove split variables.
+
+       * vars-atr.c: (discard_variables) Asserts that n_splits is 0 after
+       destroying the dictionary.
+       (clear_variable) Removes a variable from splits after destroying
+       it.
+
+Mon Aug 18 18:06:55 1997  Ben Pfaff  <blp@gnu.org>
+
+       * cmdline.c: (set_compat) Removed.
+       (pick_compat) Removed.
+       (parse_command_line) Removed -c option.
+       (pre_syntax_message) Removed -c option.
+       (usage) Remove compatibility code.
+
+       * common.h: (macros VER_PC, VER_WND, VER_X) Removed.
+       (glob var compat) Removed.
+
+       * compute.c: (type_check) Fixed messages about type mismatches.
+
+       * data-list.c: (cmd_data_list) Removed compatibility code.
+       (fixed_parse_compatible) Calls convert_negative_to_dash().
+       Fixed bug where it only set the variable in fx.spec if it created
+       the variable itself.
+       (dump_fmt_list) Spelling fix.
+       (cut_field) Removed compatibility code.
+
+       * dfm.c: (cmd_begin_data) Don't require a command terminator on
+       BEGIN DATA command.
+
+       * expr-evl.c: (evaluate_expression) Implement LAG.
+
+       * expr-prs.c: (parse_add) Calls convert_negative_to_dash().
+       (parse_neg) Calls convert_negative_to_dash().
+       (LAG_func) Increases n_lag to the lag requested.  Fixed assignment
+       bug.
+
+       * expr.h: (struct expression_struct) Removed member max_lag.
+
+       * file-type.c: (parse_col_spec) Calls convert_negative_to_dash().
+       (internal_cmd_record_type) Removed special handling to produce
+       negative numbers from dash tokens.
+
+       * getline.c: (static var DO_REPEAT_level) New var.
+       (getl_add_DO_REPEAT_file) Increments DO_REPEAT_level.
+       (handle_line_buffer) Copies the line into getl_buf; doesn't call
+       copy_with_DO_REPEAT_substitutions().
+       (getl_read_line) Maintains value of getl_mode.  Calls
+       perform_DO_REPEAT_substitutions() whenever DO_REPEAT_level is
+       positive.
+       (getl_close_file) Decrements DO_REPEAT_level when appropriate.
+
+       * getline.h: (getl_mode) New glob var.
+
+       * glob.c: Comment fixes.
+       (init_glob) Restructured.  Sets set_seed.
+       (init_compat_dependent) Removed.  All references removed.
+       (get_date) Format changed from MM/DD/YY to DD MMM YYYY.
+       (__htonl, __htons) Removed.  (What were these for?)
+
+       * lexer.c: (static var tbl) Dash set to class CNUM.
+       (make_hexit) New function from data-out.c.
+       (get_token_representation) Rewritten.
+       (convert_negative_to_dash) New function.
+       (lex_init_compat_dependent) Removed.
+       (yylex) A dash is parsed as part of a number if it is followed by
+       a digit.  The ASCII representation of a number is copied to
+       tokstr.  String parsing farmed out to parse_string().  Comment
+       fixes.
+       (bin_value_func, oct_value_func, hex_value_func, parse_string) New
+       functions.
+       (preprocess_line) Line processing depends on interactive/batch
+       mode, not on compatibility mode.  Removed PC+ compatibility code.
+
+       * loop.c: (loop_3_trns_proc) Comment fix.
+
+       * main.c: Remove dead code.
+       (main) Remove call to init_compat_dependent().
+
+       * misc.c: (convert_fmt_ItoO) Make E format conversion more
+       conformant.
+
+       * print.c: (parse_string_argument) Calls
+       convert_negative_to_dash().
+       (fixed_parse_compatible) Calls convert_negative_to_dash().
+
+       * repeat.c: (RPT_* defines) Removed.
+       (struct rpt_numeric) Removed.
+       (struct repeat_entry) New member type, changed `replacement' from
+       char * to char **.
+       (clean_up) Deallocation adapted to new repeat_entry.
+       (internal_cmd_do_repeat) `type' defaults to 0.  Remove lookahead()
+       usage.  Creates vars for `type' of 1.
+       (parse_ids) Sets type of 1.  Adapted to new repeat_entry.
+       (store_numeric) Rewritten, new interface.
+       (parse_numbers) Rewritten.
+       (parse_strings) Rewritten.
+       (find_DO_REPEAT_substitution) New function.
+       (perform_DO_REPEAT_substitutions) New function.
+       (copy_with_DO_REPEAT_substitutions) Removed.
+       (debug_print) Rewritten.
+
+       * set.q: Comment fix.
+       (custom_results) Removed compatibility code.
+       (internal_cmd_set) Removed SET EMULATION subcommand.  Removed
+       compatibility code.
+
+       * sysfile-info.c: (cmd_display) Removed compatibility code.
+
+       * tokens.h: Comment fixes.
+       (token types enum) Removed `toktype' typedef name for this int
+       type.  Removed SUBST.  Restructured.
+
+       * vars-atr.c: (discard_variables) Sets n_lag to 0.
+
+       * vars-prs.c: Comment fix.
+
+       * vfm.c: Comment fixes.
+       (glob var n_lag) New var.
+       (static vars lag_count, lag_head, lag_queue) New vars.
+       (procedure) Removed argument nlag.
+       (setup_lag) New function.
+       (close_active_file) Discards lagging state.
+       (lag_case) New function.
+       (lagged_case) New function.
+       (write_case) Lags a case if lagging.
+
+       * weight.c: (cmd_weight) Removed compatibility code.
+       
+Sun Aug 17 22:34:40 1997  Ben Pfaff  <blp@gnu.org>
+
+       * getline.h: (struct getl_script) New members loop_index, macros.
+
+       * getline.c: (getl_add_file) Sets first_line field to NULL.
+       (getl_add_DO_REPEAT_file) New function.
+       (handle_line_buffer) When the current line's length is negative,
+       set the filename and line number.  Increment line number after
+       reading line.  Pass the line to
+       copy_with_DO_REPEAT_substitutions() for processing.
+       (getl_close_file) Free DO REPEAT lines before freeing the
+       filename, and just set the filename to NULL when doing this,
+       because otherwise the filename gets freed twice.
+
+       * glob.c: (glob var queuing) Removed.  All references removed.
+
+       * lexer.c: Comment fixes.
+       (get_token_representation) New function.
+
+       * repeat.c: Comment fixes.
+       (struct repeat_entry) Replaced type and v union members with a
+       simple string.
+       (append_record) New function.
+       (internal_cmd_do_repeat) Started reforming it for the new
+       repeat_entry struct.  Properly records filename changes in the
+       getl_line_buf.  Fixed improper use of = for ==.  Fixed sense of
+       strncasecmp() result usage.  Uses append_record() to simplify.
+       Properly discards END REPEAT line.  Calls getl_add_DO_REPEAT_file
+       to add in the file.
+
+       (copy_with_DO_REPEAT_substitutions) Started coding.
+
+       [DEBUGGING] (debug_print_lines) New function.
+
+       * set.q: (custom_results, internal_cmd_set) s/VER_PCP40/VER_PC/;
+
+       * tokens.h: (macro is_id1, is_idn) New macros.
+
+Sat Aug 16 10:57:41 1997  Ben Pfaff  <blp@gnu.org>
+
+       * cmdline.c: (static var pre_syntax_message) Changed `win'
+       compatibility mode to `wnd'.
+
+       * data-list.c: (fixed_parse_spss) Renamed
+       fixed_parse_compatible().
+
+       * glob.c: (init_glob) Excise unused code for
+       program_invocation_short_name.
+
+       * lexer.c: (preprocess_line) Leading indentors are ignored in Wnd
+       as well as in X.
+
+       * print.c: (fixed_parse_spss) Renamed fixed_parse_compatible().
+
+       * set.q: `win' compatibility renamed `wnd'.
+
+Thu Aug 14 22:11:12 1997  Ben Pfaff  <blp@gnu.org>
+
+       * filename.c: [__WIN32__] Change the included Windows header files
+       (again).
+       (absolute_filename_p) [__MSDOS__] A filename with a colon as the
+       second character is absolute.
+       (dirname) Fix logic error.  Don't printf() the results.
+       (prepend_dir) Don't printf() the results.
+
+       * getline.c: (handle_line_buffer) New function.
+       (getl_read_line) Reads line with handle_line_buffer() when
+       appropriate.
+       (getl_close_file) Discard line buffer data.
+
+       * getline.h: Comment fixes.
+       (struct getl_line_list) New struct.
+       (getl_script_struct) Added line buffer members.  These are hooks
+       for use by DO REPEAT to allow it to insert virtual source code
+       into the program.
+
+       * glob.c: (init_glob) [__DJGPP__ || (__WIN32__ && __BORLANDC__)]
+       Override Borland C++ stupidity that claims Windows has a console
+       window size of 0x3.
+
+       * repeat.c: This is in the process of being restructured from
+       using a token-buffering approach to the DO REPEAT facility to
+       using the more flexible approach of a line-buffering approach in
+       conjunction with the getline module.  Comment fixes.
+       (struct tok_struct) Removed.
+       (static vars queue_index, queue_head, queue) Removed.
+       (static vars line_buf_head, line_buf_tail) New vars.
+       (internal_cmd_do_repeat) Instead of queuing tokens, queue lines.
+       Not complete.
+       (pull_queue, destroy_queue) Removed.
+       [DEBUGGING] (debug_print_tokens) Removed.
+
+Tue Aug  5 13:57:58 1997  Ben Pfaff  <blp@gnu.org>
+
+       * file-handle.q: (prepend_current_directory) New function.
+       (internal_cmd_file_handle, fh_get_handle_by_filename) Prepends
+       current directory before normalizing filename.
+
+       * filename.c: (gnu_getcwd) New function.
+       (absolute_filename_p) New function.
+       (search_path) New argument, PREPEND.  All references changed to
+       pass NULL except those explicitly mentioned.  Uses
+       absolute_filename_p().  Prepends PREPEND before trying the
+       filename.
+       (dirname, prepend_dir) New functions.
+
+       * getline.c: (getl_get_current_directory) New function.
+       (getl_include) Passes getl_get_current_directory() as PREPEND arg
+       to search_path().
+               
+Sun Aug  3 11:42:36 1997  Ben Pfaff  <blp@gnu.org>
+
+       * In several source files, the term `script' was replaced with
+       `syntax file' inside error messages.  Usage of the term `script'
+       in the sense of a syntax file is now deprecated.
+
+       * cmdline.c: (static vars pre_syntax_message, post_syntax_message)
+       Updated messages.
+
+       * dump-sysfile.c: (usage) Update message.
+
+       * getline.c: (getl_read_line) Ignore lines beginning with `#!'.
+
+       * getline.h: (glob var getl_include_path) Declare extern.
+
+       * list.q: Define EXTERN as extern before #including somP.h.
+
+       * var.h: Remove declaration of `disptype' variable.
+
+       * vfm.c: (close_active_file) After switching the data sink to a
+       data source, set vfm_sink to NULL, because it doesn't exist any
+       more.
+
+Thu Jul 17 21:41:44 1997  Ben Pfaff  <blp@gnu.org>
+
+       * glob.c: [__BORLANDC__] Include math.h.  Define _matherr() and
+       _matherrl() to ignore all math errors.
+
+       * sfm-read.c: (read_value_labels) When reading the labels from
+       disk, read the little parts separately instead of as a struct;
+       this avoids alignment problems.
+
+       * sfm-write.c: (struct sfm_fhuser_ext) New member `elem_type'.
+       (sfm_write_dictionary) Sets elem_type and frees it on lossage.
+       (write_header) Allocates and initializes elem_type.
+       (sfm_write_case) Uses elem_type to determine how to handle each
+       flt64 element.
+       (sfm_close) Frees elem_type.
+
+       * sfmP.h: Comment fix.
+       [__BORLANDC__] Uses #pragma -a to adjust structure member
+       alignment.
+       
+Thu Jul 17 01:55:12 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (fiasco_SOURCES) Remove display.c.
+
+       * common.c: Fix typo.
+
+       * dfm.c: (read_record) Remove strncasecmp() emulation and fix the
+       sense of the condition.
+
+       * expr-evl.c: (macro ALLOC_STRING_SPACE) [!PAGED_STACK] Add
+       line-continuation backslash.
+
+       * filename.c: [__WIN32__] Include <windef.h> before <winbase.h>.
+
+       * frequencies.q: (custom_grouped, add_percentile) Don't use a
+       non-constant expression as an argument to sizeof.
+
+       * glob.c: [__WIN32__ && __BORLANDC__] When including <conio.h>,
+       undefine gettext macro because that's a conio function.
+
+       * hash.h: (hsh_prime_tab declaration) Remove.
+
+       * list.q: (write_fallback_headers) Move `leader' allocation out of
+       main loop.  Change to local_alloc() allocation.
+
+       * output.h: Formatting fixes.  Put __attribute__ in right place on
+       function prototypes.
+
+       * sfm-read.c: (read_machine_flt64_info, read_variables) Change
+       incorrect `SECOND_LOWEST_VALUE' references to proper
+       `second_lowest_value'.
+
+       * som-frnt.c: (EXTERN macro) Define as `extern' instead of null
+       value.  This way 2 out of 3 of the som files define the vars
+       extern, the correct way, that actually works under BC++.
+       (som_set_float) Don't use nonconstant initializers for a struct.
+
+       * som-high.c: Add the standard alloca() header.
+       (replicate_table) Add prototype.
+
+       Merged DISPLAY routine.
+       * sysfile-info.c: (AS_*) New enum series.
+       (cmd_sysfile_info) Gutted.  Calls describe_variable() to do the
+       dirty work.
+       (cmd_display, display_macros, display_documents,
+       display_variables) Stolen from defunct display.c.
+       (describe_variable) New function.
+
+       * temporary.c: [0] (display_tree) New debug function.
+       (copy_variable) Performs shallow copy of value labels instead of
+       deep copy; i.e., just copys the AVL tree and increments the
+       reference counts.
+
+       * val-labs.c: Comment fixes.
+       (do_value_labels) Optionally skip leading forward slash.
+       (get_label) Creates only a single value label instead of many
+       copies of one, and sets the reference count.
+
+       * display.c: Removed.
+
+       * dump-sysfile.c: New file, not yet complete.
+
+Fri Jul 11 23:02:18 1997  Ben Pfaff  <blp@gnu.org>
+
+       For lots of source files I added more verbose_msg's.  These aren't
+       listed below as they have tested as being benign.  In some cases
+       these replaced debug_printf() calls.
+
+       * output.c: (outp_read_devices) Message fix.
+
+       * postscript.c: (output_encodings) Message fix.  Reports errors on
+       fclose().
+       (postopen) Message fix.
+       
+Fri Jul 11 14:09:40 1997  Ben Pfaff  <blp@gnu.org>
+
+       * dfm.c: (dfm_close) Don't call fclose() for a NULL FILE.
+
+       * filename.c: (close_file_ext) Set f->file to NULL *after* closing
+       it.
+
+       * main.c: Remove <malloc.h> #include.
+
+       * mis-val.c: (parse_numeric) Set .f member for each missing[]
+       instead of trying to just set the missing[] itself, which is a
+       gcc-specific idiom.
+
+       * sfm-read.c: (read_variables) Same.
+
+       * str.h: Add memmem() prototype.
+
+       * val-labs.c, var-labs.c: Replace <malloc.h> with <stdlib.h>.
+
+Thu Jul 10 22:13:53 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (q2c) Don't include any libraries in the link.
+
+       * dfm.c: (force_line_buffer_extension) New macro.
+       (count_tabs) New function.
+       (tabs_To_spaces) New function.
+       (read_record) Calls tabs_to_spaces() on the line being processed.
+
+       * q2c.c: Disabled i18n for this proglet so that libintl.a doesn't
+       have to be compiled twice (once for CC, once for LOCAL_CC).
+Sun Jul  6 19:14:33 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (INCLUDES) Add intl directory; fix directories.
+       (LDADD) Add @INTLLIBS@.
+       (q2c) Add LIBS, @INTLLIBS@ to link step.
+
+       * inpt-pgm.c: Turn off debugging.
+
+       * postscript.c: (postopen) Format fix.  local_free() blocks
+       returned by local_alloc(); don't free() them.
+
+Sat Jul  5 23:44:51 1997  Ben Pfaff  <blp@gnu.org>
+
+       * data-in.c: (parse_string_as_format) Comment fix.  Fix check for
+       string length.
+
+       * data-list.c: (read_from_data_list_fixed) Pass proper value for
+       LEN arg, not simply the full string length.
+
+       * sort.c: (allocate_file_handles) Check SPSS compatible temp file
+       directories before generic temp file directories.
+
+       * vfm.c: Disable debugging.
+
+Fri Jul  4 13:26:41 1997  Ben Pfaff  <blp@gnu.org>
+
+       * get.c: Comment fix.
+       (cmd_save_internal) Always passes GTSV_OPT_SAVE option.
+
+Wed Jun 25 22:52:28 1997  Ben Pfaff  <blp@gnu.org>
+
+       * expr-prs.c: (debug_print_postfix) Conditionally included on
+       GLOBAL_DEBUGGING.  Removed out_header() reference.
+
+       * exprP.h: Removed #undef GLOBAL_DEBUGGING.
+
+Sun Jun 22 22:00:28 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c: Removed obsolete ascii_close_page() prototype.
+
+       * command.c: (output_line) Comment fix.
+
+       * data-in.c: Formatting fix.
+       (parse_string_as_format) Now the `fc' argument is used only for
+       the purpose of error messages; it is not an index into the string
+       passed.  All references changed.
+
+       * data-list.c: Comment fix.
+       (cut_field) Comment fix.  Now returns the column number of the
+       position of the field cut out on success.
+       (parse_field) Added `column' argument.  Puts the column numbers in
+       the error message.
+       (read_from_data_list_free, read_from_data_list_list) Record the
+       column number returned by cut_field(), pass it to parse_field().
+
+       * dfm.c: Comment fix.
+
+       * do-ifP.h: Comment fix.
+
+       * expr-prs.c: (SYSMIS_func) Implemented string-type arguments for
+       the SYSMIS function.
+
+       * expr.h, exprP.h: Comment fix.
+
+       * glob.c: (init_glob) Only calls setlocale() and family if
+       ENABLE_NLS set.
+
+       * hash.h: Comment fix.
+
+       * include.c: Comment fix.
+
+       * output.c: Comment fix.
+
+       * postscript.c: (ps_line_intersection) Simplified assertion.
+
+       * repeat.c: Comment fix.
+
+       * vars-atr.c: Comment fix.
+
+       * vars-prs.c: Comment fix.
+
+       * vfm.c: (vector_initialization) [DEBUGGING] Fixed undefined
+       behavior with usage of postincrement.
+       (memory_stream_read) Discards cases as it goes. 
+
+Sun Jun 15 16:45:17 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Cleans q2c, not just distcleans it.  Distcleans
+       foo.
+
+       * Most source files: Includes debug-print.h, related comment
+       fixes.
+
+       * cases.c: (alloc_val) Removed complex allocation code.  Merely
+       increments default_dict.nval and returns the former value.
+       (envector, devector) Removed references to lv member of struct
+       variable.
+
+       * common.h: (macro VME) Replaced complex definition with simple
+       one.
+
+       * data-list.c: (cmd_data_list) Sets vfm_source instead of
+       read_active_file and cancel_input_pgm.
+       (read_from_data_list, cancel_data_list) Removed.
+       (data_list_source_read, data_list_source_destroy_source) New
+       functions.
+       (glob var data_list_source) New var.
+
+       * dfm.c: (open_file_r, open_file_w) Simplified debug output.
+       (cmd_begin_data) Improved criteria for an input program accessing
+       the inline file.  Still not perfect.
+
+       * do-if.c: (do_if_trns_proc) Simplified debug output.
+
+       * expr-prs.c: Comment fixes.
+       [DEBUGGING] (debug_print_postfix) Simplified debug output.
+
+       * file-handle.q: (fh_close_handle) Simplified debug output.
+
+       * file-type.c: Comment fixes.
+       (cmd_file_type) Sets vfm_source instead of read_active_file and
+       cancel_input_pgm.
+       (cmd_end_file_type) On failure, discards variables in place of
+       just canceling the input program.
+       (read_from_file_type) Renamed file_type_source_read.
+       (cancel_file_type) Renamed file_type_source_destroy_source.
+       (glob var file_type_source) New var.
+
+       * get.c: (GTSV_* enum series) New enums GTSV_OPT_SAVE, GTSV_NONE.
+       (cmd_get) Initializes options to GTSV_NONE before passing to
+       trim_dictionary().  Removed `lv' reference.  Sets vfm_source
+       instead of read_active_file and cancel_input_pgm.
+       (cmd_save_internal) Initializes options before passing to
+       trim_dictionary().  Local var `nval' removed.
+       (dict_delete_run) Comment fixes.
+       (trim_dictionary) Comment fixes.  Disallows scratch variables if
+       GTSV_OPT_SAVE set in options.
+       (read_from_get) Renamed get_source_read.
+       (cancel_get) Renamed get_source_destroy_source.
+       (glob var get_source) New var.
+
+       * inpt-pgm.c: (cmd_input_program) Sets vfm_source instead of
+       read_active_file and cancel_input_pgm.
+       (read_from_input_program) Renamed input_program_source_read.
+       Simplified debug output.
+       (cancel_input_program) Renamed
+       input_program_source_destroy_source.
+       (glob var input_program_source) New var.
+
+       * loop.c: (loop_1_trns_proc) Simplified debug output.
+
+       * main.c: (dump_token) Made eof output more explicit.
+
+       * sfm-read.c: (read_variables, dump_dictionary) Removed `lv'
+       references.
+
+       * sort.c: (cmd_sort_cases) Disallows scratch variables.  Removed
+       code for always-memory or always-disk cases.  malloc's case-list
+       based on vfm_source_info.ncases.  Explicit support for
+       memory_stream via memory_source_cases.
+       (do_external_sort) Sets vfm_source instead of read_active_file and
+       cancel_input_pgm.
+       (allocate_file_handles) The temporary directory permissions are
+       set to 0700 instead of 0777.
+       (allocate_cases) Formatting fixes.  Simplified debug output.
+       (output_record) Compacts the case if necessary before writing it
+       out.
+       (close_handle, open_handle_w) Simplified debug output.
+       (write_initial_runs) Destroys vfm_sink, then sets it to
+       sort_stream.  Writes records to memory based on
+       vfm_sink_info.case_size.
+       (write_to_sort_cases) Renamed sort_stream_write().
+       (merge) Simplified error handling.  Simplified debug output.
+       Formatting fixes.
+       (read_from_external_sort) Renamed sort_stream_read().
+       Reads records based on vfm_source_info.case_size.
+       (sort_stream_write) Writes records to memory based on
+       vfm_sink_info.case_size.
+       (sort_stream_mode) New function.
+       (glob var sort_stream) New variable.
+
+       * temporary.c: (cmd_temporary) Simplified debug output.
+       (copy_variable) Removed references to `lv'.
+
+       * title.c: (get_title) Simplified debug output.
+
+       * var.h: Comment fixes.
+       (struct get_proc) Removed member `lv'.
+       (struct variable) Removed member `lv'.  Comment fixes.
+       (glob vars read_active_file, write_active_file, cancel_input_pgm)
+       Removed.
+       (struct case_stream) New.
+
+       * vars-atr.c: (discard_variables) Changed cancel_input_pgm,
+       read_active_file references to use vfm_source.
+       (init_variable, replace_variable) Removed references to `lv'.
+
+       * vfm.c: Comment fixes.
+       (glob var vfm_source, vfm_sink, vfm_source_info, vfm_sink_info)
+       New variables.
+       (static var queue, qh, qt, n_lag) Removed.  All references
+       removed.
+       (glob var compaction_necessary, compaction_nval, compaction_case,
+       paging) New variables.
+       (record_case) Removed.
+       (procedure) Comment fixes.  Calls vfm_source->read() instead of
+       read_active_file().
+       (lag) Removed.
+       (prepare_for_writing, arrange_compaction, make_temp_case,
+       vector_initialization, setup_filter) New function.
+       (open_active_file) Most of the code moved into the abovementioned
+       new functions.  Now sets temp_dict to &default_dict if there is no
+       temporary dictionary, for convenience.  New debug output.
+       (close_active_file) Deals with changing the sink to the source.
+       Calls finish_compaction().  Frees compaction_case.  Mostly
+       rewritten.
+       (glob vars disk_source_file, disk_sink_file) New vars.
+       (destroy_active_file, read_from_memory) Removed.
+       (disk_stream_init, disk_stream_read, disk_stream_write,
+       disk_stream_mode, disk_stream_destroy_source,
+       disk_stream_destroy_sink) New functions.
+       (glob var vfm_disk_stream) New var.
+       (glob vars memory_source_cases, memory_sink_cases,
+       memory_sink_iter, memory_sink_max_cases) New vars.
+       (memory_stream_init, memory_stream_read, memory_stream_write,
+       memory_stream_mode, memory_stream_destroy_source,
+       memory_stream_destroy_sink) New functions.
+       (glob var vfm_memory_stream) New var.
+       (write_case) Local var `i' renamed `cur_trns'; local var `retval'
+       named `more_cases'.  Simplified debug output.  Otherwise mostly
+       rewritten.
+       (record_case) Moved into the stream drivers.  Removed.
+       (transform) Removed (was dead code).
+       (SPLIT_FILE_procfunc) s/vfm_replacement/vfm_sink_info/.  In the
+       common case that the splits don't change, we don't need to copy
+       the case into prev_case again--pointless.
+       (compact_case) New function.
+       (finish_compaction) New function.
+
+       * vfmP.h: Comment fixes.
+       (DEV_* enum series) Removed. 
+       (struct storage) Renamed `stream_info'.  Removed variant record.
+       Removed `device' member.
+
+       * debug-print.h: New file.
+       
+Sun Jun  8 01:12:38 1997  Ben Pfaff  <blp@gnu.org>
+
+       * autorecode.c: Turned off debugging.
+
+       * data-list.c: (destroy_dls) Closes the associated file handle.
+
+       * descript.q: (custom_variables) Added PV_NO_SCRATCH to
+       parse_variables() options.
+
+       * dfm.c: (open_file_r) Removed gratuituous argument to msg() call.
+
+       * display.c: (display_variables) Really fixed null cell bug.
+
+       * file-handle.q: (fh_close_handle) Changed debugging message.
+
+       * frequencies.q: (custom_variables) Added PV_NO_SCRATCH to
+       parse_variables() options.
+
+       * list.q: Added PV_NO_SCRATCH in q2c varlist options.
+       (cmd_list) Fails if no variables specified.
+       (determine_layout) Writes blank lines manually.
+
+       * loop.c: (loop_1_trns_proc) Made debugging code only print
+       messages if debugging.
+
+       * q2c.c: (dump_subcommand) Appends sbc->message to SBC_VARLIST
+       parse_variables() arguments.
+       (main) Parses optional parenthesized options to varlist
+       subcommands into sbc->message.
+
+       * sfm-read.c: Format fix.
+
+       * var.h: (FV_*) New enum series.
+       (PV_*) New enum PV_NO_SCRATCH.
+
+       * vars-prs.c: (find_var) Removed.
+       (fill_all_vars) Takes FV_* enum instead of boolean third
+       argument.  Rewritten to deal with scratch as well as system
+       variables.
+       (parse_variables) Error message on scratch variable if
+       PV_NO_SCRATCH set.
+
+       * vfm.c: (static var virt_begin_func) New var.
+       (procedure) Sets up virt_begin_func.
+       (SPLIT_FILE_procfunc) For the first case, calls virt_begin_func()
+       after dump_splits().  For succeeding groups changes, calls
+       virt_begin_func() instead of begin_func().      
+
+Fri Jun  6 22:42:23 1997  Ben Pfaff  <blp@gnu.org>
+
+       * count.c, data-out.c, file-handle.q, list.q, loop.c: Turned off
+       debugging.
+
+       * dfm.c: Added some debugging messages, disabled by default.
+       (open_file_r) Fixed error message.
+       (read_record) On eof on inline_file, instead of calling
+       fh_close_handle(), simply jump to eof label like a normal file.
+       Message fixes.
+
+       * display.c: Thin lines between rows for certain kinds of
+       listing.  Fixed `null cell' bug.
+
+       * error.c: (failure) Flush stdout, stderr before failing.
+
+       * file-handle.q: (fh_close_handle) Added debugging message.
+
+       * frequencies.q: (dump_full) Bottom line extends across entire
+       table width.  Changed title formatting.
+       (dump_condensed) Changed title formatting.
+       (dump_statistics) Fixed title formatting.
+
+       * glob.c: (init_glob) Moved initialization of cur_proc out of #if.
+       Sets default value of set_format.
+
+       * list.q: (cmd_list) Calls blank_line() before determine_layout().
+       Passes write_all_headers() to procedure() as pre-group func.
+       (write_all_headers) New function.
+       (determine_layout) Removed calls to write_header().
+       Calls blank_line() before and after write_fallback_headers().
+
+       * recode.c: (recode_trns_free) Only attempts to free head->map if
+       non-NULL.
+
+       * sfm-read.c: (read_variables) Allows `#' at beginning of system
+       file variable names but gives a warning.  Sets `left' based on
+       first character being/not being `#'.  On lossage frees dict->var.
+
+       * som-high.c: (som_draw_title) Simplified title formatting.
+
+       * vfm.c: (dump_splits) Fixed and changed splits formatting.
+
+Thu Jun  5 22:51:15 1997  Ben Pfaff  <blp@gnu.org>
+
+       * autorecode.c: (cmd_autorecode) Sets h_trans to NULL at
+       beginning.  Frees v_src, v_dest on successful exit.  Frees
+       h_trans[*], h_trans on lossage.
+       (recode) Frees h_trans[*], h_trans.
+
+       * dfm.c: (dfm_close) Formatting change.
+       (open_inline_file) Now passed a dfm_fhuser_ext to initialize; no
+       longer allocates its own in inline_file.
+       (open_file_r) Passes the local dfm_fhuser_ext to
+       open_inline_file().
+       (open_file_w) Message fix. 
+       (read_record) Buffer reallocation strategy changed.  Frees
+       ext->line even in inline_file to prevent leaks.
+       (dfm_put_record) Fixed bug where `ext' was cached before the file
+       was opened and thus it would be NULL when the file really was
+       open.
+       (cmd_begin_data) Sets up inline_file basics itself, then calls
+       open_inline_file() for the dfm_fhuser_ext.  Formatting fix.
+
+       * list.q: (write_line) Formatting fix.
+       (clean_up) Minor strategy change.  Sets proportional font after
+       finishing cleanup.
+       (determine_layout) Sets fixed font before writing regular headers,
+       or after writing fallback headers.
+
+       * modify-vars.c: (cmd_modify_vars) Frees variable lists for DROP
+       and KEEP vars after using them.
+
+       * postscript.c: (ps_init_driver) Frees x->family.
+       (postopen) When loading fonts, free the temporary font name buffer
+       after using it.
+       (ps_text_set_font_by_position) Free temporary font name buffer
+       after using it.
+       (text) Fixed code that calculated `lig' so that `lig' always gets
+       initialized.  Formatting fix.
+
+       * som-low.c: (get_cell_size, som_get_table_size) `prop_height' ->
+       `font_height'.
+       [GLOBAL_DEBUGGIGN] (check_table) Use arena_alloc() to allocate
+       cells, not xmalloc(), so that the cells will get destroyed
+       automatically.
+
+       * sysfile-info.c: (cmd_sysfile_info) Frees the dictionary after
+       using it.
+
+Tue Jun  3 23:33:22 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c: (ascii_text_draw) Always sets metrics for strings that
+       are drawn.
+
+       * dfm.c: Comment fix.
+
+       * list.q: Comment fixes.  Include somP.h.  Removed static vars
+       table, n_columns, n_rows, part.  New struct list_ext.  New static
+       var line_buf.
+       (n_lines_remaining, n_chars_width, write_line) New functions.
+       (cmd_list, list_cases) Rewritten.
+       (begin_row, end_row, flush_table) Removed.
+       (write_header, clean_up, write_varname, write_fallback_headers,
+       determine_layout) New functions.
+
+       * output.c: (outp_iterate_enabled_drivers) Minor reformat.
+
+       * output.h: Comment fix.
+
+       * postscript.c: Comment fix.
+       (struct ps_driver_ext) Removed prop_size, fixed_size members;
+       added font_size.  All references changed.
+       (ps_init_driver) Initializes font_size.  Simplified space checking
+       code.
+       (static var option_tab[]) Removed prop-size, fixed-size; added
+       font-size.
+       (ps_option) Handles font_size.
+
+       * som-high.c: Moved prototypes into somP.h.
+       (som_init_driver) New function.
+       (som_submit_table) Moved some code into new function
+       som_init_driver().
+       (build_target) Moved some code into new function
+       som_internal_eject_page().
+       (som_eject_page) Uses som_internal_eject_page().
+       (som_internal_eject_page) New function.
+
+       * som-low.c: Moved prototypes into somP.h.
+
+       * som.h: Formatting fixes.
+
+       * somP.h: (struct som_driver_ext) Removed em_width;
+       added prop_em_width, fixed_width.
+
+Mon Jun  2 14:25:25 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added `localedir' definition.  Added
+       -DLOCALEDIR="..." to DEFS.  Added -I. to INCLUDES.
+
+       * ascii.c: (macro draw_line) Fixed capitalization.
+
+       * ascii.c, autorecode.c, cases.c, cmdline.c, command.c, common.c,
+       compute.c, count.c, data-in.c, data-list.c, data-out.c,
+       descript.q, dfm.c, display.c, do-if.c, error.c, expr-evl.c,
+       expr-opt.c, expr-prs.c, file-handle.q, file-type.c, filename.c,
+       formats.c, frequencies.q, get.c, getline.c, glob.c, groff-font.c,
+       hash.c, heap.c, include.c, inpt-pgm.c, lexer.c, list.q, loop.c,
+       main.c, mis-val.c, misc.c, modify-vars.c, numeric.c, output.c,
+       postscript.c, print.c, q2c.c, recode.c, rename-vars.c, repeat.c,
+       sample.c, sel-if.c, sfm-read.c, sfm-write.c, sfmP.h, som-frnt.c,
+       som-high.c, som-low.c, sort.c, split-file.c, sysfile-info.c,
+       temporary.c, title.c, tokens.h, val-labs.c, var-labs.c,
+       vars-atr.c, vars-prs.c, vector.c, vfm.c, weight.c: Marked strings
+       for internationlization.
+
+       * glob.c: [HAVE_LOCALE_H] Includes locale.h.
+
+Sun Jun  1 23:31:18 1997  Ben Pfaff  <blp@gnu.org>
+
+       * do-if.c, sort.c, val-labs.c: Comment fixes.
+
+       * glob.c: (init_glob) Uncommented, updated i18n support.
+       
+       * arena.c, ascii.c, data-in.c, descript.q, error.c, expr-evl.c,
+       expr-opt.c, expr-prs.c, filename.c, frequencies.q, groff-font.c,
+       output.c, postscript.c, sfm-read.c, som-high.c, vars-prs.c: Made
+       the declarations of macros taking arguments a lot nicer.
+
+Sun Jun  1 17:22:04 1997  Ben Pfaff  <blp@gnu.org>
+
+       * error.h: Removed CE, CW aliases for SE, SW.
+
+       * q2c.c: Removed explicit streq() definition since it's duplicated
+       in str.h.
+       
+       * approx.h, error.h, font.h, hash.h, misc.h, output.h, somP.h,
+       stats.h, str.h, tokens.h: Made the declarations of macros taking
+       arguments a lot nicer-looking of <pinard@iro.umontreal.ca>.
+       Comment fixes.
+
+Sun Jun  1 12:02:06 1997  Ben Pfaff  <blp@gnu.org>
+
+       * cmdline.c: Comment fixes.
+       (pick_compat) Changed return type to int.  Now, instead of setting
+       glob var `compat' to the emulation, returns the emulation.  All
+       references changed.
+       (parse_command_line) Added terminating null to end of
+       `long_options' array definition.
+       (pre_syntax_message) Fixes.
+       (usage) Shows the default emulation in the syntax message by
+       calling pick_compat().
+
+       * getline.c: (getl_add_include_dir) Separates paths with
+       PATH_DELIMITER, not DIR_SEPARATOR.
+
+       * glob.c: (init_glob) Fixed references to DEFAULT_VER_PCP40,
+       DEFAULT_VER_WIN61, DEFAULT_VER_X40.
+
+       * output.c: (outp_configure_macro) Make earlier definitions for a
+       particular key override later ones for the same key.
+       
+Fri May 30 19:40:49 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c: Comment fixes.
+
+       * output.c: (outp_get_paper_size)
+       s/STAT_OUTPUT_INIT_FILE/STAT_OUTPUT_PAPERSIZE_FILE/.
+       
+Sun May 25 22:34:07 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c, postscript.c, sfm-read.c, sfm-write.c, sort.c: Include
+       <errno.h>.  GNU libc 2 enforces this!
+
+       * command.c: (parse_cmd) Fixed problem with `else' clause being
+       paired with wrong `if'.  Comment fix.
+
+Fri May  9 16:53:52 1997  Ben Pfaff  <blp@gnu.org>
+
+       * getline.c: [!HAVE_LIBREADLINE] (read_console) Changed
+       blp_getline() to getline().
+
+       * output.c: (outp_eval_dimension) Changed the fix from last time;
+       there was no variable `a'.
+
+       * q2c.c: (get_line) Fixed boundary condition overrun bug.
+
+Mon May  5 21:58:22 1997  Ben Pfaff  <blp@gnu.org>
+
+       * output.c: (outp_evaluate_dimension) Fixed handling of negative
+       numbers having fractional parts.  Added case of a fraction without
+       a whole-number part.
+
+Fri May  2 22:08:05 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c: (ascii_text_get_font_position) Removed.
+
+       * expr.h, exprP.h: Disabled debugging.
+
+       * groff-font.c, postscript.c: Changed `groff' to `Groff' in
+       several places.
+
+       * output.h: (struct outp_class_struct) Removed
+       text_get_font_position method.  All references deleted.
+
+       * postscript.c: Big change here.  Fontmaps were completely
+       eliminated because of a change in philosophy.  Comment fixes.
+       (struct ps_fontmap, ps2dit_map, font_family, dit2family_map)
+       Removed.
+       (struct ps_driver_ext) `position', `fontmap', `prop_name',
+       `fixed_name' members removed.  New members `prop_family',
+       `fixed_family'.  `family' member changed to type char *.
+       (static var ps_fontmaps) Removed.
+       () Removed.
+       (ps_init_driver) Removed obsolete references, updated.
+       Initializes `translate_x', `translate_y', `scale'.  Doesn't read
+       fontmap, of course.  Refers to font names through internal_name
+       rather than subversive means.  Frees proper items.
+       (static var option_tab[]) Removed `fontmap-file' option; renamed
+       `fixed-font', `prop-font'.
+       (ps_option) Corresponds to option_tab[].
+       (read_fontmap, release_fontmap, ps_to_dit, compare_ps2dit,
+       hash_ps2dit, compare_dit2family, hash_dit2family, compare_family,
+       hash_family) Removed.
+       (postopen) Generates font names from family names.  Gets
+       PostScript font name properly.  New prologue file comment `!!!'
+       style.
+       (ps_open_page) Adds translate_x, translate_y to BP prologue
+       function; gives SF argument floating-point format.
+       (ps_text_set_font_by_name) Doesn't try to map PostScript->Groff
+       font name.  Doesn't change font family.
+       (ps_text_set_font_by_position) Generates Groff font name from font
+       family name instead of through table lookup.
+       (ps_text_set_font_by_family) Renamed `ps_text_set_font_family',
+       all references changed.  Reduced to simple string assignment.
+       (ps_get_font_name) Removed.
+       (ps_get_font_family) Reduced to string return.
+       (text) Doesn't save `position' since it no longer exists.  Ugly
+       kluge to save font family--fix soon?
+       (load_font) Removed PostScript name argument.
+       
+Thu May  1 14:58:59 1997  Ben Pfaff  <blp@gnu.org>
+
+       * postscript.c: Comment fix.
+       (ps_open_page) Puts scale factor in PostScript output.
+       
+Sat Apr 26 11:49:32 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Distcleans q2c.
+
+Wed Apr 23 21:33:48 1997  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c: (delineate) Sets text size even if width is zero.
+
+       * command.c: Comment fix.
+       (static var cmd_table[]) Re-enabled EVALUATE command.
+       (parse_cmd) Lotsa comment fixes.  Fixed infinite loop in parsing
+       of comments in script files.  Now more liberal on criteria for
+       performing a state transition--if *anything* happened correctly,
+       not just if *everything* happened correctly.
+
+       * data-out.c: (convert_F) Comment fix.  Why in the fsck does
+       Checker segfault on formatting large numbers and why in the fsck
+       hadn't I noticed this before?
+
+       * expr.h, exprP.h: No longer turn off GLOBAL_DEBUGGING.
+
+       * list.q: (cmd_list) Commented out the actual output routine
+       because of various problems.  Probably will abandon the idea of
+       using the general `crushed tables' for the LIST procedure.
+
+       * temporary.c: (restore_dictionary) Sets var_by_name to NULL after
+       clearing it.  Allocates a new var_by_name dictionary before trying
+       to add members to it.
+
+       * vars-atr.c: [DEBUGGING] (dump_one_var_node) Removed argument
+       `sib'.  Changed type of `node' argument.
+       [DEBUGGING] (dump_var_tree) Replaced avlwalk() with
+       avl_walk_inorder().
+       (clear_variable) Only dumps the var tree if var_by_name non-NULL.
+       [DEBUGGING] Only deletes the variable from var_by_name if that var
+       non-NULL.
+
+Fri Apr 18 16:48:41 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added include files to SOURCES.  Added
+       frequencies.q to EXTRA_DIST.  Removed include/ from INCLUDES.  Now
+       includes rules for q2c.  Added `boast' target.
+
+Fri Apr 18 15:42:22 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Maintainer-clean Makefile.in.
+       
+       * Makefile.am: Fixed redundant EXTRA_DIST line.
+
+       * ascii.c: Comment fixes.
+       (ascii_line_vert) Fixed overly aggressive range check.
+
+       * display.c: Removed dead code.
+
+       * list.q: Turn debugging on.
+       (flush_table) New debug code.
+
+       * sfm-read.c: (read_value_labels) malloc's the structure before
+       trying to assign to its members.
+
+       * sfm-write.c: Comment fix.
+
+       * som-high.c: (som_submit_table) Sets som.t and som.d on each call
+       to output_table().
+       (output_table) No arguments anymore--gets them through `som'
+       global.  New debug code.  In crushed tables, now sets `htv' as
+       well as `hv' to avoid bad confusion later.
+       (dump_crush_page) New debug code.
+
+       * som-low.c: (som_dump_crush_page) New debug code.
+
+Thu Mar 27 01:11:29 1997  Ben Pfaff  <blp@gnu.org>
+
+       All source files: Broke long lines into multiple lines.
+       
+       * ascii.c: (ascii_close_page) Uses host_system var in place of
+       HOST_SYSTEM constant.
+
+       * cmdline.c: (var syntax_message[]) Broke into
+       pre_syntax_message[] and post_syntax_message[].
+       (usage) Outputs both parts, separated by driver list.
+
+       * error.h: Fixed broken formatting.
+
+       * expr-opt.c: (str_search, str_rsearch) New functions.
+
+       * misc.c: (blp_getdelim) Removed.  All references changed to
+       `getdelim'.
+       (str_search, str_rsearch) Removed.
+       (memrmem) New function.
+
+       * misc.h: (blp_getline) Removed.  All reference changed to
+       `getline'.
+
+       * stat.h: New file.
+
+       * filename.c: Includes "stat.h", not <sys/stat.h>.
+       (blp_getenv) Uses host_system var instead of HOST_SYSTEM constant.
+
+       * output.c: (outp_list_classes) Changed output formatting.
+
+       * sfm-write.c: (write_header) Uses host_system var instead of
+       HOST_SYSTEM constant.
+       (write_rec_7_34) Extracts version numbers from the version string.
+       Untested.
+
+       * sort.c: Includes "stat.h", not <sys/stat.h>.
+
+       * str.c: (strcasecmp) Removed.
+
+       * title.c: (cmd_document) Uses host_system var instead of
+       HOST_SYSTEM constant.
+
+       * version.c: Generated on-the-fly by the Makefile instead of being
+       static.
+
+       * str.h: Comment fixes.  Doesn't substitute for missing memmove or
+       memcpy.
+       [!HAVE_STRNCASECMP] Declares strncasecmp().
+
+       * version.h: Removed stray character.  Comment fixes.
+       (vars host_system, build_system) New vars.
+
+Mon Mar 24 21:47:31 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Most source files: Changed formatting of copyright notice; fixed
+       FSF address; reformatted to better conform to GNU standards;
+       comment fixes.  Added markups to prevent GNU indent from messing
+       up my beautiful formatting :-).
+       
+       * q2c.c: (get_line) Ignores lines that begin with `/* *INDENT' so
+       that GNU indent markups can be passed through without problems.
+
+Wed Feb 19 21:30:31 1997  Ben Pfaff  <blp@gnu.org>
+
+       * get.c: Turned off debugging.
+
+       * glob.c: (init_glob) Turned on save-file compression by default.
+
+       * sfm-write.c: (sfm_write_case) Fixed bug which resulted in less
+       compression than was possible in save files.
+
+Sun Feb 16 20:57:20 1997  Ben Pfaff  <blp@gnu.org>
+
+       * data-out.c: (convert_F) Comment fixes.  Debug message fixes.
+
+       * frequencies.q: Removed Fiasco extensions.  Updated calculation
+       algorithms.  Polished output format. 
+       (struct frq_info_struct) Removed members `max_degree', `min_n',
+       all references removed.
+       (macro frq_extensions) Removed.
+       (static vars min_n, max_degree) Removed, all references removed.
+       (internal_cmd_frequencies) Doesn't handle extensions.  Doesn't
+       calculate `min_n', `max_degree'.
+       (postcalc) Passes new arg to dump_statistics().
+       (dump_full) Honor NOLABEL option.  Buggy?  Adds variable name
+       title.
+       (dump_condensed) Adds variable name title.
+       (sum_freqs) Removed.
+       (calc_stats) Updated calculation algorithm.
+       (dump_statistics) Removed warning for too-few observations.
+       Changed table formatting.  Adds variable name title if passed new
+       arg is nonzero.
+
+       * output.h: Comment fix.
+
+       * recode.c, sample.c, sort.c: Disabled debug code.
+
+       * som-frnt.c: (som_set_value, som_set_float, som_set_text)
+       Improved debug code.
+
+       * var.h: (enum series frq_*) Removed Fiasco extensions.
+
+Sat Feb 15 21:26:53 1997  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: Added PROCESS IF to command table.
+
+       * Lots & lots of places, removed checks for NULLs preceding calls
+       to free_expression(), which itself checks.
+
+       * descript.q: Removed Fiasco extensions.  Removed optimizations
+       for non-weighted active files.  Implemented some options.
+       Finished polishing output format.  Comment fixes.  Merged
+       `descript.g'.
+       (static vars n_glob_miss_list, n_glob_valid, n_glob_missing,
+       max_degree, min_n) Removed.
+       (macro dsc_extensions) Removed.
+       (struct dsc_info_struct) Removed `min_n' member, all references
+       fixed.
+       (internal_cmd_descriptives) Removed calculation of min_n,
+       max_degree.  Only deals with one `calc' routine instead of two
+       flavors.
+       (precalc) Eliminated redundancy.  Updated for changes to
+       descriptives_proc structure.
+       (calc) Moved here from `descript.g'.  Rewritten to calculate
+       statistics via `moments about the mean' rather than by summing,
+       summing squares, summing cubes, and so on.
+       (postcalc) Rewritten for new-style statistical calculation.
+       (display) Removed support for displaying variables across rows.
+       No longer crushes the descriptives table.  Removed ancient code.
+       Added display of N, by variable and listwise.
+
+       * descript.g: Removed; merged into `descript.q'.
+
+       * expr-evl.c: (evaluate_expression) Now returns a double.  For
+       numeric results, it returns the result as well as storing it in
+       the passed `value' structure if non-NULL.  For string results it
+       just returns 0.0 and it must be passed non-NULL.  Many references
+       to this function were optimized by use of this change, especially
+       but not exclusively in `compute.c'.
+
+       * frequencies.g: Comment fix.
+
+       * glob.c: (glob var process_if_expr) New global var.
+
+       * postscript.c: (static var option_tab[]) Corrected entry for
+       `fixed_size'.
+       (postopen) Sets x->size to x->prop_size.
+       (ps_text_set_font_by_name) Sets font size as well as typeface for
+       PROP and FIXED fonts.
+       
+       * sel-if.c: (cmd_process_if) New function.
+
+       * sfm-write.c: (struct sfm_fhuser_ext) New member `n_cases'.
+       (sfm_write_dictionary) Sets `n_cases' to 0.
+       (sfm_write_case) Increments `n_cases'.
+       (sfm_close) Attempts to seek the system file back to the header
+       and write the number of cases in its proper slot.
+
+       * som-frnt.c: (som_insert_table) Masks off expansion options since
+       only SOPT_X_NORM seems to work sensibly.
+
+       * som-low.c: (get_cell_size) Fixed bug when a table cell was sized
+       with a `fixed' value of 2.
+
+       * sort.c: (cmd_sort_cases) Cancels PROCESS IF.
+
+       * sysfile-info.c: (cmd_sysfile_info) Doesn't display more than 10
+       value labels; uses SOPT_NONE instead of SOPT_X_BOTH.
+
+       * var.h: (enum series dsc_*) Removed Fiasco extensions.
+       (struct descriptives_proc) Removed `miss_noweight'; new members
+       `X_bar', `M2', `M3', `M4', `min', `max'.
+
+       * vars-atr.c: (discard_variables) Cancels PROCESS IF.
+
+       * vfm.c: (close_active_file) Cancels PROCESS IF.
+       (write_case) Doesn't process cases unselected by PROCESS IF.
+
+Fri Feb 14 23:32:58 1997  Ben Pfaff  <blp@gnu.org>
+
+       * glob.c: (glob var err) Removed.
+
+       * sysfile-info.c: (cmd_sysfile_info) When adjusting table size,
+       doesn't have to take into account number of value labels since
+       they're in a subtable anyway.  Also, doesn't display more than 10
+       value labels since we can't yet break pages in subtables.
+
+Tue Feb  4 15:15:50 1997  Ben Pfaff  <blp@gnu.org>
+
+       * som-frnt.c: (som_change_table_size) Simple change for elegance
+       that shouldn't change behavior.
+       (som_set_value) Comment fix.
+
+       * som-high.c: (som_submit_table) Message fix.
+
+Wed Jan 22 21:54:00 1997  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: Added SYSFILE INFO to command table.
+
+       * file-handle.q: (fh_handle_filename) New function.
+
+       * get.c: (save_trns_proc) Fixed a bug in padding of output data
+       with spaces.
+
+       * main.c: (parse) New return value for command functions, -3.
+
+       * misc.h: Comment fix.
+
+       * output.h: Comment fixes.
+       (macro COMPONENTS) Removed.
+
+       * postscript.c: (write_text) Modified literal_chars[] so that `('
+       and ')' are not written to the output in strings as literals.
+
+       * sfm-read.c: (sfm_read_dictionary) New argument.
+       (read_header) New argument.  Sets the information structure's
+       values from the header information.  
+       (read_variables) [__CHECKER__] Redefines isalnum()--some sort of
+       bizarre Checker problem, I guess.
+       (read_variables) Proper cleanup on lossage.
+
+       * sfm.h: (struct sfm_read_info) New struct for use by
+       sfm_read_dictionary().
+
+       * som-frnt.c: (som_create_table) New argument CREATE_FLAGS,
+       currently used just for tables that can be dynamically resized and
+       thus have to be allocated with arena_malloc() instead of
+       arena_alloc().  All references changed.
+       (som_change_table_size) New function.
+       (som_insert_table) Bugfix: now inserts `cell', not `c'!
+
+       * som-high.c: [GLOBAL_DEBUGGING] (check_table) Moved to som-low.c.
+       (som_submit_table) [GLOBAL_DEBUGGING] Doesn't call check_table()
+       any more.
+
+       * som-low.c: (draw_cell) Calls draw_table_cell() for SCON_TABLE
+       cells.
+       (draw_intersection) Now takes an argument specifying the table in
+       question.  All references changed.
+       (draw_table_cell) New function.
+       (som_get_table_size) [GLOBAL_DEBUGGING] Calls check_table().
+       (som_get_table_size) Many nice new explanatory comments.
+       [GLOBAL_DEBUGGING] (check_table) Moved here from som-high.c.
+
+       * som.h: New enum series SOM_CREATE_* for use as create flags with
+       som_create_table().
+
+       * str.h: Moved a comment here from TODO.
+
+       * sysfile-info.c: New file.  Reference implementation.
+
+Sun Jan 19 14:22:11 1997  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: Added RENAME VARIABLES to table of commands.
+
+       * data-in.c: (dls_error) Sets `cust_field'.
+       (parse_N) Message fix.
+       (parse_day_count) New function.
+       (to_roman) Never outputs VX as a `short form' of V.
+       (parse_month) Fixed parsing of Roman numerals.
+       (parse_trailer) Message fix.
+       (parse_DATE, parse_ADATE, parse_EDATE, parse_SDATE, parse_JDATE,
+       parse_QYR, parse_MOYR, parse_WKYR, parse_DTIME) Issue a message if
+       the date is invalid.
+       (parse_SDATE) Fixed swapped day, year.
+       (parse_JDATE) Fixed bug for dates in 1582.
+       (parse_DTIME) Allows days not between 1 and 31.
+       (parse_numeric) Makes local copy of f.type for easier usage.
+       FMT_DOLLAR fixed.
+
+       * data-out.c: (convert_F) When outputting as scientific, properly
+       sets f.type as fp->type.
+       (insert_commas) Fixed operator precedence problem with setting of
+       nitems.  Changed strcpy to memcpy (no null terminator). 
+       (convert_date) Fixed FMT_JDATE: added 1900 to year.
+       (convert_CCx) Essentially rewritten, but now it works.
+
+       * display.c: (cmd_display) Added DISPLAY FILE LABEL (undocumented
+       feature of Fiasco).
+       (display_documents) Implemented.
+
+       * error.c: (glob var cust_field) New var.
+       (vmsg) Displays cust_field as part of message classes DE and DW.
+
+       * formats.c: (debug_print) Fixed to compile under updated
+       dictionary format.
+
+       * get.c: (cmd_get, cmd_save_internal) Close file handle on
+       failure.
+
+       * misc.c: (parse_format_specifier) Formatting fix.
+
+       * modify-vars.c: (struct var_modification) Renamed `n_reorder' as
+       `n_rename' for clarity.
+       (cmd_modify_vars) Initializes `forward' and `positional' at
+       appropriate times.  Frees lists of vars to rename on failure.
+       Comment fix.  Frees memory on success.  
+       (rearrange_dict) Simplified `for' loop condition.
+
+       * rename-vars.c: New file (reference implementation).
+       
+       * set.q: (internal_cmd_set) Fixed `emu' test condition.
+
+       * sfm-read.c: (read_header) File label is created only if file
+       label in file is not blank.
+       (read_variables) Initializes `dict' local variable.
+       (read_documents) Proper behavior on lossage.
+
+       * sfm-write.c: (write_header) Doesn't blank out the file label
+       (why was this here to begin with?!)
+
+       * temporary.c: (save_dictionary) File label is copied only if
+       non-NULL.  Doesn't try to xstrdup() dictionary documents.
+       Adapted so as to not irritate Checker.
+       (free_dictionary) Only destroys var_by_name if non-NULL.
+
+       * title.c: (cmd_file_label) Doesn't skip FILE, LABEL tokens.
+       (cmd_document) Doesn't skip DOCUMENT token.  Adds some header
+       lines to the document, indents the document.  Also, it works now.
+       (add_document_line) New function.
+
+       * var.h: (struct dictionary) Reordering.
+
+       * vars-prs.c: (parse_variables) On lossage, only local_free()'s
+       bits if it was allocated to begin with.
+
+Thu Jan 16 13:08:57 1997  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: Added MODIFY VARS to list of commands.
+
+       * configure.in: Updated custom macros for autoconf 2.12.  Removed
+       mmap reference; fixed termcap library reference.
+
+       * display.c: (display_variables) Fixed a few bugs although it's
+       still not well written.
+
+       * error.c: [!__CHECKER__] (chkr_disp_call_chain) New function.
+       (induce_segfault) Calls chkr_disp_call_chain() instead of
+       inducing an actual SIGSEGV.
+
+       * expr-opt.c: (evaluate_tree) Swapped order of arguments to
+       str_search() and str_rsearch().  Fixed tests for matches on
+       OP_INDEX and OP_RINDEX.
+
+       * filename.c: (good_getcwd) Removed as the new libc for Checker
+       doesn't contain this bug, apparently.
+
+       * misc.c: (str_search, str_rsearch) Changed order of arguments for
+       consistency with GNU memmem.
+       (blp_getdelim) Changed `len' from `int' to `size_t'.
+
+       * modify-vars.c: Reference implementation.
+
+       * som-frnt.c: (zero_length) New global var.
+       (som_create_table) Message fix.
+
+       * som.h: Added gcc attributions to som_set_text(),
+       som_output_text() prototypes.  blank_line() refers to
+       zero_length[] instead of a literal null string to suppress gcc
+       warnings.
+
+       * sort.c: (do_external_sort) Fixed fencepost error on lossage.
+       (allocate_cases) Decrements x_max so the last element of x[] can
+       be used by the algorithm.
+
+       * var.h: Changed minor details of `variable' declaration.  
+       (struct modify_vars_proc) New struct.
+       (struct variable) Added field p.mfv.
+
+       * vars-atr.c: Comment fix.
+
+       * vars-prs.c: (fill_all_vars) More optimal implementation.
+
+       * vfm.c: (dump_splits) Sets the last byte of temp_buf to a null
+       character, which it shouldn't have to do but printf() seems to
+       read the null byte even though I supply a maximum length...
+
+Fri Jan 10 20:22:08 1997  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: Removed command alias X for QUIT.
+       (parse_cmd) Fixed comment parsing.
+
+       * dfm.c: (struct dfm_fhuser_ext) Fields `len', `size' are now of
+       type size_t.
+       (read_record) Fixed references to len, size.
+       (dfm_get_record) Restructured.
+
+       * file-handle.h: (struct file_handle) Field `lrecl' now of type
+       size_t.
+
+       * file-handle.q: (internal_cmd_file_handle) Checks for nonpositive
+       record length.
+
+       * modify-vars.c: New file.  Not complete.
+       
+       * set.q: (set_ccx) Fixed operator precedence problem regarding ^
+       and ==.
+
+       * sfm-read.c: (bswap_flt64, read_header, write_variable) Fixed
+       problems caused by int/size_t differences.
+
+       * sort.c: (output_record, merge_once) Cast `size_t's to `int's in
+       appropriate spots.
+
+       * str.c: (strcasecmp) Fixed bug that cropped up when the strings
+       being compared were of equal length.
+
+Thu Jan  2 19:08:23 1997  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: Added DOCUMENT, DROP DOCUMENTS, FILE LABEL.
+
+       * lexer.c: (get_dotted_rest_of_line) New function.
+
+       * sel-if.c: (cmd_filter) Cannot choose string or scratch variables
+       as filters.
+
+       * sfm-read.c: (sfm_read_dictionary) Calls read_documents() to read
+       type 6 records.  Frees the dictionary properly.
+       (read_header) Initializes the dictionary instead of letting
+       read_variables() do it.  Sets the dictionary file label from the
+       system file.
+       (read_documents) New function.
+
+       * sfm-write.c: (sfm_write_dictionary) Calls write_documents() to
+       write type 6 record if appropriate.
+       (write_header) Writes file label from dictionary.
+       (write_documents) New function.
+
+       * temporary.c: (save_dictionary, restore_dictionary,
+       free_dictionary) Properly handle new fields in dictionary struct.
+
+       * title.c: (get_title) Returns after failure().
+       (cmd_file_label, cmd_document, cmd_drop_documents) New functions
+       for new commands FILE LABEL, DOCUMENT, DROP DOCUMENTS.  Untested.
+
+       * var.h: (struct dictionary) New fields `label', `n_documents',
+       `documents'.
+
+Wed Jan  1 22:08:10 1997  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: Added FILTER to list of commands.
+
+       * frequencies.g: [WEIGHTING] Removed test for weighting!=-1 since
+       it's always true.
+
+       * get.c: (cmd_save_internal) Removed weighting code since it's now
+       handled by sfm-write.c.  Properly commented out debug code.
+
+       * glob.c: (glob var weighting) Removed.
+
+       * sel-if.c: Comment fixes.
+       (cmd_filter) New function.
+
+       * sfm-read.c: (struct sfm_fhuser_ext) New field `weight_index'.
+       (sfm_read_dictionary) Sets weighting variable direct in the
+       created dictionary now.  (Apparently we previously didn't support
+       weighting on GET?)
+       (read_header) Sets weight_index field in sfm_fhuser_ext from
+       header read from disk.
+
+       * sfm-write.c: (sfm_write_dictionary) Comment fix.
+       (write_header) Now sets the weighting in the header from the
+       passed primary dictionary instead of from the sfm_write_info.
+
+       * sfm.h: (struct sfm_write_info) Removed field `weight'.
+
+       * som-high.c: (dump_crush_table) Fixed a couple of assertions that
+       broke on boundary conditions.
+
+       * var.h: (struct dictionary) New fields `weight_var',
+       `weight_index', and `filter_var'.
+       (glob var weighting) Removed.  This is now part of struct
+       dictionary.  All references changed; the less mechanical changes
+       are described above.
+
+       * vars-atr.c: (find_dict_variable) New function.
+
+       * vfm.c: (static var filter_index) New variable.
+       (open_active_file) Initializes filter_index from default_dict.
+       (write_case) Calls proc_func() only if the filter variable is
+       nonzero; this implements FILTER behavior.
+
+       * weight.c: (static var weight_varname) Removed.
+       (cmd_weight) Modified default_dict instead of glob vars.
+       (update_weighting) Changed the signature to modify a dictionary
+       instead of glob vars.  Now returns the weighting variable.
+       (get_weighting_variable) Removed; its function is absorbed by
+       update_weighting().
+       (stop_weighting) Operates on a dictionary now.
+
+Wed Jan  1 17:00:59 1997  Ben Pfaff  <blp@gnu.org>
+
+       * sort.c: Removed debugging info from messages.
+       (do_external_sort) Cleans up after itself by deleting the
+       temporary directory on failure.  (On success it is deleted by the
+       input program.)
+       (allocate_cases) Removed debug code.  Added clean up code.
+       (output_record) Removed debug code.
+       (merge) Added code to close all the input files that are currently
+       open.  This is a likely location for bugs, because I'm not sure
+       about boundary conditions.  Removed an unnecesary heap_delete().
+       (merge_once) Removed input file "optimization" that in fact
+       screwed up the rest of the code.  Message and comment fixes.
+
+Sun Dec 29 21:36:48 1996  Ben Pfaff  <blp@gnu.org>
+
+       * error.c: [__CHECKER__] (induce_segfault) Flushes output streams.
+
+       * heap.c: (heap_delete) New argument.
+
+       * sort.c: Finished implementation of external sort.
+
+       * vfm.c: (read_from_disk) Returns after a disk error.
+
+Sun Dec 22 23:10:39 1996  Ben Pfaff  <blp@gnu.org>
+
+       * sort.c: (static var state) Removed.
+       (static vars max_handles, tmp_basename, tmp_extname,
+       huffman_queue) New variables.
+       (do_external_sort) Moved most code to new functions.
+       Creates huffman_queue.
+       (allocate_file_handles, allocate_cases) New functions.
+       (static vars run_no, run_length, file_index, case_count) New
+       variables. 
+       (output_record) Returns success.  Now really writes to the output
+       file.
+       (begin_run, end_run) New functions.
+       (write_initial_runs) Returns success.  Initializes run_no to -1.
+       Calls begin_run(), end_run() at appropriate times.  Outputs debug
+       messages.
+       (write_to_sort_cases) Calls begin_run(), end_run() at appropriate
+       times.
+       (merge) New function.
+
+       * heap.c, heap.h: New files.  Hopefully in near-final form.
+
+Sat Dec 21 21:51:04 1996  Ben Pfaff  <blp@gnu.org>
+
+       * glob.c: Added write_active_file to global vars.
+
+       * sort.c: Several new miscellaneous static variables.
+       (cmd_sort_cases) Big comment fix.
+       (perform_case_2) Renamed `do_external_sort' and completely
+       rewritten.
+       (case_2_proc_func) Removed.
+       (output_record, write_initial_runs, write_to_sort_cases,
+       compare_record) New functions.
+
+       * vfm.c: [DEBUGGING] (index_to_varname) Excised bit rot.
+
+Tue Dec 17 18:57:59 1996  Ben Pfaff  <blp@gnu.org>
+
+       * sort.c: (perform_case_2) Changed the method for allocation of
+       lots of memory--now allocates one case at a time in hopes that
+       more cases can be allocated with heavily fragmented memory.
+
+       * var.h: (write_active_file) New global var.
+
+       * vfm.c: (procedure, close_active_file, write_case,
+       SPLIT_FILE_procfunc) Now allow beginfunc, procfunc, and endfunc
+       arguments to procedure() to be NULL.  All references to
+       procedure() that made use of dummy functions were changed to NULL
+       functions.
+       (open_active_file) If write_active_file is non-NULL, the output
+       device becomes DEV_PGM (a new enum).
+       (close_active_file) Sets write_active_file to NULL.
+       (read_from_memory) Comment fix.
+       (record_case) Calls write_active_file() when the output device is
+       DEV_PGM.
+
+Sun Dec 15 15:32:16 1996  Ben Pfaff  <blp@gnu.org>
+
+       * sort.c: New file.
+
+       * autorecode.c: (cmd_autorecode) Fixed parsing of options.
+       Fixed checking for duplicate varnames.
+       (recode) xmalloc()'s the transformation instead of arena_alloc()'ing
+       it.
+       (autorecode_trns_free) Destroys hash tables for each recoding
+       specification.
+       (autorecode_proc_func) Compares NULL to *vpp instead of vpp.
+
+       * command.c: Added SORT CASES to cmd_table.
+       (null_func, null_int_func) Prototyped.
+
+       * descript.g: (calc_weight, calc_noweight) Computes own case
+       number now.
+       
+       * frequencies.q: (dump_statistics) Fixed problem with
+       too-few-cases warning message.
+
+       * get.c: (cmd_save_internal) Handles weighting properly.
+
+       * hash.c: (hsh_dump) Output format changed.
+       (force_hsh_insert) Actually works now, prototype changed.
+
+       * list.q: (static var case_num) New variable.
+       (cmd_list) Initializes case_num.
+       (list_cases) Increments case_num.
+
+       * var.h: Added definitions for SORT CASES.  Comment fixes.
+
+       * vfm.c: Some definitions moved to new file vfmP.h.  Comment
+       fixes.  `active' renamed vfm_active, `rep' renamed
+       vfm_replacement, all references changed.
+       (procedure) The procfunc no longer receives a case number.  All
+       references changed.
+       (write_case) Subtle reordering.
+       (SPLIT_FILE_procfunc) Counts cases differently.  Slightly less
+       redundant.
+
+       * weight.c: (get_weighting_variable) New function.
+
+       * vfmP.h: New file with definitions from vfm.c.
+
+Sat Dec 14 10:35:30 1996  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: (FILE_TYPE_okay) Commented out some tests because
+       they're clumsy and not yet needed.
+
+       * var.h: Most *_trns structures moved to their respective source
+       files.  Some were moved into a new file, do-ifP.h.  Comment fixes.
+       (union any_trns) Changed to a typedef for trns_header.
+       (struct input_program_pgm) Removed.
+
+       * vars-prs.c: (parse_variables) Only local_free()'s bits if it
+       was allocated in the first place.
+
+Fri Dec 13 21:30:53 1996  Ben Pfaff  <blp@gnu.org>
+
+       * autorecode.c: New file.
+       
+       * command.c: Added AUTORECODE to command table; re-enabled SET.
+
+       * data-out.c: (convert_F) Handles infinities and NaNs properly.
+
+       * error.c: (vmsg) Comment fixes.
+
+       * hash.c: Comment fix.
+       (hashpjw_d) New function.
+       (hashpjw) Reimplemented as call to more general function
+       hashpjw_d().
+       (internal_comparison_fn) Initializes pointers properly.
+       (hsh_sort) [GLOBAL_DEBUGGING] New debugging code.
+       (force_hsh_insert, force_hsh_find) New debugging wrapper
+       functions.
+
+       * main.c: (main) Message fix.
+
+       * output.c: (outp_read_devices) Message fix.
+
+       * set.q: Comment fixes.
+       (custom_results) Implemented Wnd/X form of subcommand.
+       (set_routing) New function.
+       (internal_cmd_set) Implemented ERRORS, MESSAGES.
+
+       * settings.h: (SET_ROUTE_*) New enum series.
+       (set_results) Renamed set_results_file, all references changed.
+       (set_messages) Removed.
+       (glob vars set_errors, set_messages, set_results) New vars.
+
+       * title.c: (get_title) Remembers to xstrdup() the result of
+       get_rest_of_line().
+
+       * var.h: (arc_item, arc_spec, autorecode_trns) New structures for
+       use by AUTORECODE.
+       (union any_trns) New element `arc'.
+
+Fri Dec  6 23:53:47 1996  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: (output_line) Removed references to set_screen.
+
+       * error.c: (static var terminating) New var.
+       (hcf) Sets terminating to 1.
+       (vmsg) If terminating is nonzero, does not attempt to call hcf().
+       This prevents an infinite loop if an error occurs within hcf().
+
+       * expr-evl.c: (evaluate_expression) [__CHECKER__] Replaced case
+       statement circumlocution with `case 42000' trick.
+       (evaluate_expression) New support for OP_STR_MIS.
+
+       * expr-opt.c: (evaluate_expression) [__CHECKER__] Replaced case
+       statement circumlocution with `case 42000' trick.
+       (dump_node) Handles OP_STR_MIS.
+
+       * expr-prs.c: (MISSING_func, SYSMIS_func) Rewrote to handle string
+       variables exceptions.
+       (parse_function) Message fix.
+       (ops[]) Added OP_STR_MIS.
+
+       * expr.h: Added OP_STR_MIS to OP_* enum.  Comment fixes.
+
+       * exprP.h: [__CHECKER__] Removed case statement circumlocution.
+
+       * glob.c: Removed set_scrnfile glob var.
+       (init_glob) set_errorbreak set to 0 by default.
+
+       * groff-font.c: Changed included files.
+       (groff_read_font) Initializes font_arena local var correctly.
+       (default_font) New function.
+
+       * output.c: Comment fixes.
+       (glob var disabled_devices) New variable.
+       [GLOBAL_DEBUGGING] (static var iterating_driver_list) New
+       variable.
+       [GLOBAL_DEBUGGING] (reentrancy) New function.
+       [GLOBAL_DEBUGGING] (outp_read_devices, outp_done, find_driver,
+       outp_iterate_enabled_drivers) Calls to reentrancy().
+       (destroy_list) New function.
+       (outp_done) Moved code to destroy_list().
+       (parse_options) Parses `listing', `screen', `printer' options
+       internally.
+       (configure_driver) Sets new `device' member of driver.
+       (outp_iterate_enabled_drivers, outp_enable_device) New functions.
+
+       * output.h: Comment fixes.  New enum series OUTP_DEV_*.
+       (struct outp_driver_struct) New member `device'.
+
+       * postscript.c: (find_encoding_file) Doesn't display its own error
+       messages.
+       (default_encoding) New function.
+       (switch_font) Calls default_encoding() if no encoding can be
+       found.
+       (text) Makes up a character metric if none exists for the desired
+       character.
+       (load_font) Properly copies a fallback filename.  Calls
+       default_font() for a font if none at all are known.
+
+       * set.q: Comment fixes.  Removed OUTPUT subcommand.
+       (custom_listing) Calls outp_enable_device() to enable/disable
+       listing device.
+       (turn_screen_on) Removed.
+       (internal_cmd_set) Calls outp_enable_device() to enable/disable
+       screen, printer devices.
+
+       * settings.h: Comment fixes.
+       (glob vars set_output, set_printer, set_screen, set_scrnfile)
+       Removed.
+
+       * som-high.c: (som_submit_table, som_eject_page) Use
+       outp_iterate_enabled_drivers() instead of iterating
+       outp_driver_list directly.
+
+Wed Dec  4 21:34:17 1996  Ben Pfaff  <blp@gnu.org>
+
+       * data-in.c: (parse_EDATE, parse_SDATE) New functions.
+       (parse_string_as_format) Handles new formats.
+       (parse_numeric) Now handles DOT and PCT formats.
+
+       * data-out.c: (convert_E, convert_F, insert_commas) Handle DOT
+       format now.
+       (convert_date) Handle EDATE and SDATE formats.
+       (convert_CCx) Now if there's not room for the currency characters,
+       converts it as F format if it's positive instead of giving up
+       quickly.  Also fixed save-and-restore bug with decimal point
+       characters.  
+       (convert_format_to_string) Handles new formats.
+
+       * misc.c: (formats[]) Added new formats.
+       (convert_fmt_ItoO) Supports new formats.
+
+       * sfm-read.c: (parse_format_spec) Supports new formats.  Better
+       data checking.  New argument, all references changed.
+
+       * sfm-write.c: (write_format_spec) Supports new formats.
+
+       * var.h: New formats FMT_DOT, FMT_PCT, FMT_EDATE, FMT_SDATE.
+       Comment fixes.
+
+Sun Dec  1 17:19:00 1996  Ben Pfaff  <blp@gnu.org>
+
+       * cmdline.c: Comment fixes.
+       (parse_command_line) Changed return type to void.
+
+       * data-in.c: (parse_string_as_format) Added FMT_CCA...FMT_CCE to
+       switch.
+       (parse_numeric) Handles international numbers (comma as decimal
+       point).  Some reformatting.
+
+       * data-list.c: (parse_free) Default output format is now
+       set_format instead of hard-coded F8.2.
+       (read_from_data_list_list) Emits error message on undefined data
+       only if set_undefined is nonzero.
+
+       * data-out.c: (convert_E) Changes decimal point from period to
+       comma if appropriate.  Restructured.  Better comments.
+       (convert_F) Changes decimal point from period to comma if
+       appropriate.
+       (insert_commas) Major bug with handling of negative values fixed.
+       Also, inserts periods instead of commas if appropriate.
+       (convert_CCx) New function.
+       (convert_format_to_string) Added FMT_CCA...FMT_CCE to switch.
+       (num_to_string) Changed `.' to set_decimal.
+
+       * dfm.c: Comment fixes.
+       (dfm_close) Frees ext->line even in inline_file.
+       (open_inline_file) New function.
+       (open_file_r) When opening the inline file: now properly
+       recognizes `BEGIN DATA.' line, and calls open_inline_file() to
+       finish up.
+       (read_record) Calls fh_close_handle() instead of dfm_close() to
+       close the inline file.  Makes a copy of the line getl_buf to avoid
+       interlock problems.
+       (dfm_get_record) Restructured.  Now checks the return value of
+       open_file_r().
+       (cmd_begin_data) Moved open code into open_inline_file().  Relaxed
+       checking for use of inline file.  No longer tries to close inline
+       file.
+
+       * error.c: (glob var error_already_flagged) New var.
+       (vmsg) Message change.  Now checks max number of errors/warnings,
+       acts on it.
+
+       * file-handle.q: (fh_handle_name) Now allows closing of
+       inline_file.
+       (fh_init_files) Reformatted.
+
+       * get.c: (trim_dictionary) Checks SCOMP option instead of COMP.
+
+       * getline.c: (getl_include) Fixed bug that popped up when called
+       when file queue was empty.
+       (read_console) Resets error_count, warning_count,
+       error_already_flagged to zero.
+
+       * glob.c: Many changes to update list of variables.
+       (init_compat_dependent) Now this function is called whenever
+       `compat' changes.  It now sets set_seed only if it hasn't
+       previously been referenced.  It now calls
+       lex_init_compat_dependent().
+
+       * include.c: (cmd_include_at) Frees temporary buffer instead of
+       line buffer.  
+       (cmd_include) Doesn't make copy of include file name.
+
+       * lexer.c: Comment fixes.
+       (init_lex) Moved some code into new function
+       lex_init_compat_dependent().
+       (lex_init_compat_dependent) New function.
+       (hex_val) Simplified.
+       (preprocess_line) Uses set_endcmd instead of hardcoding `.'.
+
+       * main.c: Comment fixes.
+       (main) Reformatted.
+
+       * misc.c: (formats[]) Added FMT_CCA...FMT_CCE.
+       (check_input_specifier) Disallows FMT_CCA...FMT_CCE.
+       (convert_fmt_ItoO) Detects FMT_CCA...FMT_CCE.
+       (setup_randomize) Sets set_seed_used.
+
+       * set.q: Comment fixes.
+       (custom_results) Conditionalizes on `compat'.
+       (custom_log) Calls custom_journal().
+       (set_ccx) New function.
+       (cmd_set) Calls init_compat_dependent() when `compat' changes.
+       Calls set_ccx() to handle CCA...CCE.  Sets set_grouping
+       when set_decimal changes.  Range-checks values for MITERATE,
+       MNEST.  Message fixes.
+
+       * settings.h: Comment fixes.
+       (struct set_cust_currency) New struct.
+       (set_cc[], set_grouping, set_seed_used) New global vars.
+
+       * var.h: (FMT_CCA...FMT_CCE) New output formats.
+       (FCAT_OUTPUT_ONLY) New FCAT_* constant.
+
+Thu Nov 28 23:14:07 1996  Ben Pfaff  <blp@gnu.org>
+
+       * glob.c: Revised variables to correspond to settings.h.
+       (init_glob) Initializes variables from settings.h properly.
+
+       * set.q: Began long-overdue major revision to correspond to new
+       philosophy.  Most code changed. 
+
+       * settings.h: Mostly changed; reorganized, reordered, large new
+       comment.
+
+Thu Nov 28 19:46:10 1996  Ben Pfaff  <blp@gnu.org>
+
+       * get.c: (cmd_save_internal) No longer forces compression off.
+
+       * sfm-read.c: (read_compressed_data) If eof is reached when
+       reading a new instruction octet, only signal error if we're in the
+       middle of a case.
+
+       * sfm-write.c: (COMPRESSION_BIAS) New #define.
+       (struct sfm_fhuser_ext) New member `end'.
+       (write_header) Refers to COMPRESSION_BIAS instead of magic 100.0.
+       (ensure_buf_space) New function.
+       (sfm_write_case) Reimplemented in order to support compression.
+       (sfm_close) Writes out the remaining contents of the compression
+       buffer if any.
+
+Wed Nov 27 23:18:35 1996  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: Defined SAVE and XSAVE commands in command table.
+
+       * common.h: second_lowest_value is of type flt64, not double.
+
+       * file-handle.h: Comment fix.
+
+       * get.c: Comment fixes.
+       (static var `trns') New.
+       (save_write_case_func, save_trns_proc, save_trns_free, null_func,
+       cmd_save_internal, cmd_save, cmd_xsave) New functions.
+       (dict_delete_run) Clears the variables and frees them now.
+       (trim_dictionary) Sets default for compression.
+       On KEEP subcommand, frees deleted variables as well as clearing
+       them.  Finally got the sense of the test for deleting all
+       variables correct.
+       [DEBUGGING] (dump_dict_variables) Message fix.
+
+       * glob.c: (init_glob) set_compression set to 1 by default.
+
+       * list.q: Properly #includes config.h.
+
+       * misc.h: New macro REM_RND_UP.
+
+       * settings.h: Comment fix.
+
+       * sfm-read.c: (structs sysfile_header, sysfile_format,
+       sysfile_variable; inline function bswap_int32) Moved to new file
+       sfmP.h.
+       (corrupt_msg) [__CHECKER__] No longer induces segfault.
+       (sfm_read_dictionary) Fixed bug caused by failing to initialize
+       var_by_index.
+       (read_machine_flt64_info) Fixed some problems caused by confusion
+       between flt64 and double types.
+       (read_header) Message fix.
+       (read_variables) Fixed set of cases in which we byte-swap sv.print
+       and sv.write.  Fixed confusion of flt64 and double.
+
+       * sfm.h: (struct sfm_write_info) New.
+
+       * som-high.c: (som_draw_title) Properly frees `s'.
+
+       * temporary.c: (save_dictionary) Comment fix.
+
+       * var.h: Comment fixes.  New FMT_* enum, FMT_NUMBER_OF_FORMATS.
+       (struct trns_header) Formatting fix.
+       (struct save_trns) New.
+
+       * vars-atr.c: (discard_variables) Comment fix.
+
+       * sfm-write.c: New file, baseline release.
+
+       * sfmP.h: New file, baseline release.
+
+Sun Nov 24 14:53:53 1996  Ben Pfaff  <blp@gnu.org>
+
+       * cmdline.c: (parse_command_line) `--version' output updated.
+       (glob var syntax_message[]) Added my e-mail address.
+
+       * file-handle.q, lexer.c, vfm.c: Changed many instances of
+       `illegal' to `invalid'.
+
+       * sfm-read.c: (struct sfm_fhuser_ext) New fields used as
+       uncompression buffer.
+       (sfm_close) Frees decompression buffer.
+       (sfm_read_dictionary) Initializes decompression buffer.
+       (buffer_input, read_compressed_data) New functions.
+       (sfm_read_case) Restructured; now calls read_compressed_data() to
+       handle compressed system file data.
+
+       * var.h: Comment fix.
+
+Mon Nov 11 15:34:09 1996  Ben Pfaff  <blp@gnu.org>
+
+       * dfm.c: (dfm_close) Does not set h->{ext,class} because the
+       caller handles it.
+        
+       * get.c: New comments.  New static var `get_file'.
+       (cmd_get) Now fully implemented.  Calls discard_variables();
+       initializes fv and lv for all variables; new debug code; sets
+       up the dictionary; sets up the input program.
+       (read_from_get, cancel_get) New functions.
+
+       * sfm-read.c: Comment fixes.
+       (sfm_close) New static function.
+       (sfm_read_dictionary) Properly sets up the class of the
+       file_handle.  No longer cares what size the data is in records of
+       type 7.  Also, on failure, properly cleans up the file_handle and
+       free()s some stuff.
+       (read_variables) No longer thinks it knows `nval' of the
+       dictionary.  Now sets p.get.fv, etc., instead of speculatively
+       setting fv itself.
+       (read_value_labels) Fixed off-by-one error in indexing of
+       var_by_index[].
+       (sfm_read_case) New function.
+       (sfm_r_class) New static var.
+
+       * var.h: (get_proc) New struct.
+       (struct variable) New member p.get.
+
+Thu Nov  7 20:52:28 1996  Ben Pfaff  <blp@gnu.org>
+
+       * get.c: Removed GTSV_OPT_MAP because of a misinterpretation of
+       the manual's meaning.
+       (rename_variables) New function.
+       (trim_variables) Doesn't try to parse MAP any more.  Removed debug
+       code.  Now properly reorders the dictionary on the KEEP keyword.
+
+       * sfm-read.c: (read_value_labels) Fixed some bugs regarding
+       garbage collection.
+
+       * vars-atr.c: (clear_variable) New argument `dictionary *'.
+       (rename_variable) New function.
+       (free_val_lab) Reformatted.
+
+Thu Nov  7 17:29:16 1996  Ben Pfaff  <blp@gnu.org>
+
+       * var.h: Reindented entire file.  Comment fixes.
+       (glob vars var, var_by_name, nvar, N, nval, n_splits, splits)
+       Removed.
+       (glob var default_dict) New.
+       (struct indirect_dictionary) Removed.
+
+       * Many other source files were changed to add `default_dict.'
+       before all references to the dictionary of the active file.
+       
+       * vars-atr.c: (make_indirect_dictionary) Removed.
+
+       * glob.c: Reindented all variable declarations.  Updated for
+       changed var.h.  Comment fixes.
+
+       * temporary.c: (restore_dictionary, save_dictionary) Simplified
+       because now we can mainly copy dictionary structs.
+
+       * vars-prs.c: (is_dict_varname, parse_dict_variable,
+       parse_variables) Takes dictionary instead of indirect_dictionary
+       first argument.
+       (parse_variables) Instead of calling make_indirect_dictionary,
+       just sets DICT to &default_dict if DICT is NULL.  Of course, lots
+       of `*dict.' references had to be changed to `dict->'.  Removed
+       debug code.
+
+Thu Nov  7 15:48:52 1996  Ben Pfaff  <blp@gnu.org>
+
+       * get.c: Added GTSV_OPT_* series of enums.
+       (trim_dictionary, dict_delete_run) New functions.
+       [DEBUGGING] (dump_dict_variables) New function.
+       (cmd_get) Calls trim_dictionary() to get dictionary fully set-up.
+       [DEBUGGING] Calls dump_dict_variables() to display results.
+
+       * glob.c: (cmp_variable) Now a public function declared in var.h.
+
+       * sfm-read.c: Turned off debug code.  Comment fixes.
+       (read_machine_int32_info, read_machine_flt64_info) New functions
+       to parse type 7 records.
+       (sfm_read_dictionary) Properly byteswaps several fields now.
+       Calls read_machine_*_info() to parse type 7 subtypes 3 and 4
+       records.  [DEBUGGING] Dumps dictionary.
+       (read_variables) Sets `index' field of variables created properly.
+       Constructs avl tree of variables in dictionary.  [DEBUGGING] No
+       longer dumps dictionary.
+       (read_value_labels) Properly byteswaps fields.  [DEBUGGING] New
+       debug code.
+       [DEBUGGING] (dump_dictionary) No longer stubbed out.
+
+       * temporary.c: (restore_dictionary) Destroys `var_by_name' glob
+       var before destroying any variables just to save a little time.
+
+       * var.h: (struct variable) Reordered in order to make name[] the
+       first member; this makes pointers to `variable' pointers to the
+       variable name, simplifying avl trees, etc.
+       (struct indirect_dictionary) New struct.
+
+       * vars-atr.c: (find_variable) Rewritten for efficiency.
+       (make_indirect_dictionary, is_dict_varname, parse_dict_variable)
+       New functions.
+       (is_varname) Rewritten for efficiency.
+       (parse_variables) New argument, which is a `dictionary *'.  All
+       references changed.  This function now reads variable names from
+       the dictionary passed, or from the default dictionary if NULL.
+
+Tue Nov  5 18:34:59 1996  Ben Pfaff  <blp@gnu.org>
+
+       * misc.h: Added new macro DIV_RND_UP to perform integer division,
+       rounding up.  Changed many references to ROUND_UP to use this
+       instead.
+
+       * sfm-read.c: Includes avl.h.
+       (corrupt_msg) Induces a segfault under Checker.
+       (macro assertive_bufread) New.  Many references to bufread() now
+       use this instead.
+       (sfm_read_dictionary) Split up into several functions.  Added code
+       to read dictionary records following the the type 2 records.  Not
+       quite complete.  New variable `var_by_index'.
+       (read_header, read_variables) New functions extracted from
+       sfm_read_dictionary().
+       (read_value_labels) New function.
+       (bufread) Checks ferror() if fread() doesn't return the expected
+       value; if ferror() is zero it's just EOF.
+       (dump_dictionary) Stubbed out.
+
+       * BTW: The source code now exceeds 50000 lines!
+       
+Mon Nov  4 22:03:28 1996  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: Added GET to cmd_table[].
+
+       * list.q: Removed reference to alloca headers.
+       (cmd_list) Gave prototype.
+
+       * sfm-read.c: Added DEBUGGING comments.
+       (sfm_read_dictionary) Checks bias correctly.  Sets
+       dict->var_by_name to NULL.  Calculates long_string_count
+       correctly.  realloc's dict->var[] array to minimum size.
+       [DEBUGGING] Calls dump_dictionary.
+       [DEBUGGING] (dump_dictionary) New function.
+
+       * temporary.c: (save_dictionary) Sets var_by_name to NULL.
+       (restore_dictionary) If the dictionary contains a non-NULL
+       var_by_name, uses that instead of generating one.
+       (free_dictionary) Destroys var_by_name.
+
+       * var.h: (struct dictionary) Added field `var_by_name'.
+
+       * get.c: New file, not complete.
+
+Sun Nov  3 12:24:36 1996  Ben Pfaff  <blp@gnu.org>
+
+       * mis-val.c: New enums MV_NOR_*.  New struct num_or_range.
+       (parse_num_or_range) New function.
+       (parse_numeric) Reimplemented in order to support LOW THRU <n> and
+       <n> THRU HIGH missing values.
+
+       * output.h: [__GNUC__>1 && __OPTIMIZE__] (width, height) Made
+       __attribute__((const)).
+
+       * q2c.c: (get_token) Merged isdigit || isalpha into isalnum.
+
+       * sfm-read.c: Finished reference implementation.
+
+       * sfm.h: Includes var.h.
+
+       * var.h: Comment fixes.
+       (struct `variable') Reordered some fields.
+
+       * vars-atr.c: (is_num_user_missing) Added support for MISSING_*
+       constants added previously.
+
+Wed Oct 30 17:13:08 1996  Ben Pfaff  <blp@gnu.org>
+
+       * common.h: Comment fixes.  Added declaration of
+       `second_lowest_value' as variable or macro.  Made `compat_type',
+       `pgm_state_type' into anonymous enums.
+
+       * display.c: Comment fix.
+
+       * glob.c: [ENDIAN==UNKNOWN] Added definition for `endian' global
+       var.
+       [!defined SECOND_LOWEST_VALUE] Added definition for
+       `second_lowest_value' global var.
+       (compat, pgm_state global vars) Changed types to `int'.
+       (init_glob) Initializes `second_lowest_value'.
+
+       * sfm-read.c: Continued work, not complete.
+
+       * var.h: Added new MISSING_* constants to handle LOWEST and
+       HIGHEST.
+
+Sat Oct 26 23:06:06 1996  Ben Pfaff  <blp@gnu.org>
+
+       * sfm-read.c: New file, not complete.
+
+       * cases.c: (vec_insert) Changed vector expansion algorithm.
+       (vec_delete) Fixed bug that screwed up deletion sometimes, it was
+       mucking up the RECODE transformation in particular.
+       (envector) Harmless change in notation.
+
+       dfm is now fairly well tested again.  
+       * dfm.c: (dfm_get_record) Only returns ext->ptr if ext is
+       non-NULL--duh.
+       (cmd_begin_data) if(ext->line) replaced by if(ext && ext->line).
+
+       * recode.c: Comment fix.
+
+       * sfm.h: Interface should be fairly final now, or at least for a
+       day or so...
+
+       * vfm.c: [DEBUGGING] (index_to_varname) New function.
+       (open_active_file) [DEBUGGING] Translates ccase indices into
+       variable names now to make it easier to understand what's really
+       going on.
+
+Sat Oct 26 20:46:31 1996  Ben Pfaff  <blp@gnu.org>
+
+       * data-in.c: Comment fix.
+
+       * data-list.c: Includes dfm.h.
+       (do_reading) Uses new function dfm_push_cust().
+
+       * data-out.c: (convert_time, convert_WKDAY, convert_MONTH) Added
+       `return 1;' at end.
+
+       * file-handle.h: Completely changed.  Some parts split off into
+       new file dfm.h.  Implemented in file-handle.q.
+       (enum FH_*) Removed.
+       (struct fh_ext_class) New struct.
+       (struct file_handle) Retained only these fields: name, norm_fn,
+       fn, recform, lrecl, mode.  New fields class, ext.
+       (get_handle_by_name, get_handle_by_filename, parse_file_handle,
+       close_handle, handle_name) Added `fh_' prefix to name, all
+       references changed.
+
+       * dfm.h: New file, implemented in dfm.c.
+       (get_record, put_record, fwd_record, bkwd_record, set_record,
+       get_cur_col) Functions moved from file-handle.h, now prefixed with
+       `dfm_'.
+       (dfm_push_cust) New function.
+
+       * sfm.h: New file.  Incomplete.
+
+       * dfm.c: All functions adjusted/rewritten for new dfm/fhp
+       interface.  Functions reordered, comments changed.  Not well
+       tested, probably full of bugs.
+       (struct dfm_fhuser_ext) New struct.
+       (dfm_close) New function.
+       (open_file_r) Pickier about finding `BEGIN DATA.' line.
+       (open_file_w) User messages changed.
+       (get_record) Comment fixed.
+       (read_record) Increments ext->ln even for inline_file.  Calls
+       dfm_close() for inline_file when `END DATA.' encountered.
+       (dfm_get_record) Experimental restructuring.
+       (dfm_push_cust) New function.
+       (cmd_begin_data) Detects whether the inline file was fully read by
+       checking whether it is still open; detects whether it was read at
+       all by checking whether the line number is greater than zero.
+
+       * file-handle.q: All functions adjust/rewritten for new dfm/fhp
+       interface.  Functions reordered, comments changed.  Not well
+       tested, probably full of bugs.
+       (init_file_handle) Removed initializers for obsolete fields, added
+       new fields.
+       (fh_close_handle) Much simpler, now mainly calls the class
+       function.
+       (fh_init_files) Renamed inline file internal filename.
+
+       * file-type.c: Includes dfm.h.
+       (read_from_file_type) Doesn't use dfm internal state anymore.
+
+       * inpt-pgm.c, print.c: Include dfm.h.
+
+       * recode.c: (internal_cmd_recode) Casts strlen() return value to
+       int in comparison with other int.
+
+       * som-high.c: (build_target) Fixed operator precedence problem in
+       if statement (& versus ==).
+
+Sat Oct 26 10:39:25 1996  Ben Pfaff  <blp@gnu.org>
+
+       * dfm.c: (read_record) Can now read fixed-length records; not
+       tested.
+       (put_record) Can now write fixed-length records; not tested.
+
+       * file-handle.h: FH_* defines changed to enums.  New enum series
+       FH_RF_*, FH_MD_*.
+       (struct file_handle) New members recform, lrecl, mode.
+
+       * file-handle.q: Parser changed.
+       (internal_cmd_file_handle) Added support for new /RECFORM, /MODE,
+       /LRECL subcommands.  These are compatible with Windows.
+       (init_file_handle) Initializes recform, mode fields.
+
+       * q2c.c: (get_line) When outputting `!' comment lines, now
+       increments the output file line number so that `#line' directives
+       are correct.
+       (make_identifier) New function that converts an arbitrary string
+       into a valid C identifier.
+       (dump_vars) Calls make_identifier() in two places in order to
+       suppress some errors for bad identifiers.
+       (make_match) Allows TRUE as synonym for YES and FALSE as synonym
+       for NO.  Allows numbers to be prefixed by underscores to make them
+       acceptable C identifiers but still to be parsed as numbers by the
+       Fiasco lexer.
+
+Thu Oct 24 20:13:42 1996  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: Re-enabled RECODE, SAMPLE, SELECT IF.
+       
+       * dfm.c: Comment fixes. (get_record) Gives error if file handle
+       was opened for writing.
+       (open_file_w) New function.
+       (read_record) Uses strncasecmp if available.  Improved error
+       messages, comments.
+       (put_record) New function.
+
+       * file-handle.h: Moved function comments into dfm.c and
+       file-handle.q.  Comment fixes.  Removed declarations of
+       tilde_expand() and normalize_filename().
+       (struct file_handle) Changed `open' from boolean to enumerated
+       field to allow for three states--closed, open for reading, open
+       for writing--all references changed.
+
+       * file-handle.q: Includes filename.h.
+
+       * print.c: (CMD_* enums) Renamed PRT_* and moved into var.h; all
+       references changed.
+       (alloc_line) Makes allowance for line terminator characters in
+       calculations.
+       (print_trns_proc) Now handles OUTFILE, WRITE differences.
+       (print_space_trns_proc) Handles OUTFILE differences.
+
+       * recode.c, sample.c: Comment fixes.
+
+       * var.h: (struct print_trns) Changed boolean field `eject' to
+       bitmapped field `options'; all references changed.  New enums
+       PRT_* for use with this field.
+
+       * exception.h, test-exception.c: Removed.
+
+Thu Oct 24 17:47:14 1996  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c: (delineate) Turned off debug output.
+
+       * common.c: [Checker and Linux] (__assert_fail, __eprintf) Moved
+       to error.c.
+
+       * data-in.c: (parse_string_as_format) Sets the entire string value
+       to spaces, not just the short string part of it.  Is this correct
+       now? 
+
+       * data-out.c: (convert_date) Fixed DATETIME format problems with
+       decimal places, removed debug code.
+
+       * dfm.c: (open_file_r) Fixed bug where an error would occur in the
+       middle of parsing BEGIN DATA that would cause the lexer to read
+       from a wild pointer `prog'; now calls new function
+       preprocess_line() in lexer.c.
+
+       * error.c: [__CHECKER__] (hcf) Calls induce_segfault() on improper
+       termination.
+       [Checker and Linux] (__assert_fail, _eprintf) Moved from common.c.
+       Now call induce_segfault() to induce the segfault.
+       (induce_segfault) New function.
+
+       * expr-opt.c: Comment fix.
+       (parse_sysvar) New function.
+       (parse_primary) Added system variable support--calls
+       parse_sysvar().
+       (global var ops) Added OP_CASENUM operator.
+
+       * expr.h: Comment fixes.
+       (OP_* enum) added OP_CASENUM operator.
+       (struct casenum_node) New struct.
+       (union any_union_union) New member `cas' of type `casenum_node'.
+
+       * glob.c: (global var last_vfm_invocation) New var.
+       (init_glob) Initializes last_vfm_invocation.
+
+       * lexer.c: (lookahead) Fixed reversed condition on if statement.
+
+       * getline.c: (get_line) Split into get_line() and preprocess_line().
+       (preprocess_line) New function.
+
+       * var.h: Declares last_vfm_invocation.
+
+       * vfm.c: (procedure) Sets last_vfm_invocation.
+
+Wed Oct 23 21:53:43 1996  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: (parse_cmd) Fixed bad assertion related to
+       lookahead().
+
+       * data-in.c: (parse_month) Implemented to parse months according
+       to full interpretation of standard.
+       (to_roman) New function.
+       (parse_wk_delimiter) Bug fix (forgot to skip `WK' in string).
+       (parse_weekday) Bug fix (forgot to skip all the day name).
+
+       * data-list.c: (read_from_data_list_fixed) Fixed bug that screwed
+       up parsing of multirecord data items.  Also fixed user message.
+
+       * data-out.c: Comment fix.
+       (year2, year4, convert_date, convert_time, convert_WKDAY,
+       convert_MONTH) New functions to support time & date output.
+       (convert_format_to_string) Calls new time & date output routines.
+
+       * expr-prs.c: (nary_num_func) Found a bug, but didn't fix it yet.
+
+       * lexer.c: (lookahead) Noted a previously unnoticed caveat in
+       comment.
+
+       * main.c: [DEBUGGING] (dump_token) Updated to handle getline.h.
+
+       * misc.c: (global var formats) Fixed declarations of DATETIME,
+       TIME, DTIME.
+
+       * postscript.c: (text) Fixed a pair of bugs in the reallocation of
+       the output_char buffer.
+
+       * vars-prs.c: (parse_DATA_LIST_vars) Fixed a failure to free
+       memory bug.  Fixed user messages.
+
+Tue Oct 22 17:27:04 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Removed #pragma argsused from lots of places.
+       
+       * data-in.c: Implemented zoned decimal and time-date formats.
+       Untested.  This is a huge chunk of code--maybe 1000 lines and 50
+       new functions.
+
+       * data-out.c: Implemented zoned decimal format.
+
+       * expr.h: Moved yrmoda() declaration here from exprP.h.
+
+       * misc.c: (global var formats) Minor fixes--added
+       FCAT_SHIFT_DECIMAL to formats N and Z.
+       (convert_fmt_ItoO) Added support for format Z.
+
+       * som-frnt.c: (som_set_value) Fixed bug regarding string values.
+
+Mon Oct 21 20:39:59 1996  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: (parse_cmd) [GLOBAL_DEBUGGING] Inserted call to
+       som_check_workspace() that is activated between commands.
+
+       * data-list.c: (dump_fixed_table, dump_free_table) Finished these
+       for good, I hope.
+
+       * list.q: (begin_row) Changed title expansion style from
+       SOPT_X_VERT to SOPT_X_SHSP.
+
+       * som-frnt.c: Now includes `somP.h'.
+       (som_push_workspace, som_pop_workspace) New functions that, taken
+       together, form a solution to the recursive table building problem
+       mentioned yesterday.  Surrounded every table output routine
+       throughout the program with calls to these functions.
+       [GLOBAL_DEBUGGING] (som_check_workspace) New function.
+       (som_create_table) Checks that there's an active workspace.
+       (som_destroy_all_tables, som_crush) Removed.
+
+       * som-high.c: (global var som_preserve_tables) Removed, all
+       references deleted.
+       (som_submit_table) Checks that there's an active workspace.
+       (dump_columnated_table) Doesn't columnate tables that would have
+       just one row per column.
+       (dump_crush_page, som_dump_crush_page) Removed debugging code.
+       (som_dump_crush_page) Moved row number labels from left side of
+       tables to right side.
+       (som_get_table_size) Added support for SOPT_X_SHSP.
+
+       * som.h: New cell expansion type SOPT_X_SHSP.
+
+       * somP.h: (global vars arena_stack, n_arena_stack, m_arena_stack)
+       New vars.
+       (global var curtab_arena) Moved from som-frnt.c.
+
+Sun Oct 20 13:45:28 1996  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c: [GLOBAL_DEBUGGING] (SUPPRESS_WARNINGS) New debug option
+       that causes bad location warnings to be suppressed.
+       (delineate) Saves current font when calling draw_text(); fixed
+       handling of NULLs when backing up.  Also fixed line-wrapping bug.
+
+       * command.c: Re-enabled `LEAVE', `NUMERIC', `PRINT', `PRINT EJECT',
+       `PRINT FORMATS', `PRINT SPACE', `STRING', `TITLE', `WRITE'.
+
+       * common.c: Added code to cause assertion failure to dump core
+       when run under Checker.
+
+       * data-list.c: (dump_fixed_table) Fixed some inconsistencies, but
+       there are still bugs.
+
+       * glob.c: (__eprintf) Removed.
+
+       * list.q: Inserted som_preserve_tables kluge that prevents tables
+       from being thrown away due to recursive table building through
+       som_output_line being called from a transformation during the LIST
+       procedure invocation.  This is a general problem that must be
+       solved in a better way since it applies to all procedures in
+       general.
+       (begin_row) Changed title options to SOM_X_VERT from SOM_X_BOTH.
+       (flush_table) Removed SOM_TOPT_PRESERVE from submission options.
+
+       * numeric.c: Fixed several errors in the form of msg() calls.
+
+       * print.c: Updated for use of som.
+       (dump_table) Reimplemented.
+       (print_trns_proc) Calls som_eject_page() instead of eject_page().
+       Calls som_output_text() instead of outs_line().
+
+       * som-frnt.c: (som_destroy_all_tables) Sets som_preserve_tables to
+       0.
+       (som_output_text) Function moved from som-low.c.  Interface
+       changed.
+
+       * som-high.c: (som_preserve_tables) New global public variable
+       declared in som.h.
+       (som_submit_table) Destroys the tables only if som_preserve_tables
+       is 0.
+       (paginate_horizontally) Bugfix: sets som.mpw even if there's only
+       one subrow per row.  Now labels subrows if there's more than one
+       subrow per row.
+       (dump_crush_table) Added wishlist comment.
+       (som_eject_page) New public function declared in som.h.
+
+       * som-low.c: (som_dump_crush_page) Draws row labels if there's
+       more than one subrow per row.
+       (som_output_text) Moved to som-frnt.c.
+
+       * som.h: (SOM_TOPT_PRESERVE) Removed.
+
+       * title.c: (get_title) Changed interface.
+       (cmd_title) Changed `title' to `outp_title'.
+       (cmd_subtitle) Changed `subtitle' to `outp_subtitle'.
+
+Sun Oct 20 09:04:15 1996  Ben Pfaff  <blp@gnu.org>
+
+       * list.q: (flush_table) Conforms to new partial options in
+       som_submission_form.
+
+       * som-high.c: (paginate_horizontally) Changed form of subrow
+       number labels.
+       (build_target) Omits spacing before table if
+       SOM_TOPT_PARTIAL_OMIT_TOP is selected.
+       (dump_crush_page) Changed interface.  Only trims bottom rule if
+       SOM_TOPT_PARTIAL_OMIT_BTM is not selected.
+       (dump_crush_table) Handles partial tables.
+       (output_row_label) New function.
+       (som_dump_crush_page) Emits subrow number labels.  Draws vertical
+       rule on the right edge of narrow subrows.
+
+       * som.h: Changed SOM_SUB_PARTIAL_* series of submission type
+       constants to a series of SOM_TOPT_PARTIAL_* submission options.
+       All references updated.
+
+Fri Oct 18 19:46:49 1996  Ben Pfaff  <blp@gnu.org>
+
+       * misc.c: Comment fix.
+
+       * som-high.c: (examine_table) Treats crushed tables separates for
+       purpose of determining header size.
+       (paginate_horizontally) Allots space for line numbers in crushed
+       tables with lots of subrows per row.  Calculates the `maximum page
+       width', the width of the widest horizontal page.
+       (build_target) Removed trim argument; all references changed.
+       Stricter assertions.  (dump_crush_page) New function.
+       (dump_crush_table) Reimplemented.
+
+       * som-low.c: (som_dump_page) Uses new RULE_ROW &c. constants.
+       (som_dump_crush_page) Reimplemented, interface changed.
+
+       * somP.h: Many many new helper macros for use with crushed tables.
+       (global var som) Removed `tv', `cum_y' members; all references
+       removed.  New members `mpw', `digit_space'.
+
+Sun Sep 29 19:37:03 1996  Ben Pfaff  <blp@gnu.org>
+
+       * arena.c: (arena_alloc) [!DISCRETE_BLOCKS] Removed `size'
+       variable, changed to constant 1024.
+       (arena_ca_strdup) Changed `sizeof(a_string)' to
+       `sizeof(c_string)'.
+       (arena_ca_strdup) [!DISCRETE_BLOCKS] Changed bad cast from
+       `(c_string *)' to `(char *)'; this fixed some offset problems.
+
+       * filename.c: (readlink_malloc) Changed initial allocation from
+       100 bytes to 128.
+       (good_getcwd) Changed from xmalloc() to local_alloc(); removed
+       comment.
+
+       * postscript.c: (read_fontmap) Fixed leak by changing &owner to
+       &fm->owner in several places.
+
+       * som-high.c: (output_table) Changed interface to rest of world.
+       (examine_crush_table) Removed.  Crushed tables are re-broken now,
+       in preparation for rewrite.
+
+       * som.h: Comment fix.
+
+Sat Sep 28 21:28:07 1996  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c: (ascii_init_driver) Disposes of x->file.filename and x
+       itself in the cleanup stage.
+
+       * descript.q: (display) At least temporarily, changed the table
+       format to a crushed table.
+
+       * list.q: (begin_row) At least temporarily, added horizontal lines
+       between cases.
+
+       * som-high.c: (examine_crush_table) Sets som.hh to the width of
+       the horizontal "headers," that is, to the width of the far left
+       and far right rules.
+       (justify_pagination) Sets som.th to the width of the widest row
+       in the crushed table.  Fixed inner loop off-by-one error.
+
+       * som-low.c: (som_dump_crush_page) Added code to draw horizontal
+       rules.
+
+       * somP.h: Comment fix.
+
+Fri Sep 27 20:08:39 1996  Ben Pfaff  <blp@gnu.org>
+
+       * filename.c: (open_file_ext) Now, doesn't set f->file to NULL
+       before closing it; also, opens the constructed filename `s'
+       instead of f->filename.
+
+       * postscript.c: Moved initialization of x->loaded, x->prop,
+       x->fixed, x->current, also the add_encoding() calls, into
+       postopen().
+       (preclose) Destroys x->combos; sets x->loaded, x->combos to NULL;
+       sets x->last_font to NULL; sets x->next_combo to zero.
+
+       * som-high.c: (crushed_row_height) Moved definition farther up.
+       (som_submit_table) Doesn't calculate line width, font size until
+       after calling open_page(), to accomodate changes to PostScript
+       driver.
+       (vert_headers) Removed; equivalent functionality moved to
+       examine_table(), examine_crush_table().
+       (justify_pagination) Replaced with different algorithm.
+       (dump_crush_table) Bugfix that caused tables to fail to be clipped
+       at the bottom of the page.
+
+Thu Sep 26 22:20:26 1996  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: Added cmd_list back into cmd_table.
+
+       * freq.c, frequencies.q, repeat.c, list.q, vars-atr.c, vfm.c:
+       Comment fix: `#define DEBUGGING' --> `#define DEBUGGING 1'.
+
+       * list.q: (flush_table) Updated to new som_submission_form format.
+
+       * som-frnt.c: Comment fix.
+
+       * som-high.c: Changed `#endif' to `#undef EXTERN'.
+       (output_table) Calls som_get_table_size() directly; handles
+       crushed tables.
+       (examine_crush_table) New function; calls vert_headers().
+       (examine_table) Moved some code into new function, vert_headers().
+       (justify_pagination) New function.
+       (dump_plain_table) Removed `static' from `cy'.
+       (dump_crush_table) New function.
+
+       * som-low.c: (som_dump_crush_page) New function.
+
+       * som.h: Comment fixes.
+       (enum SOM_TOPT_CRUSH) New.
+       (SOM_SUB_PARTIAL_BEG, SOM_SUB_PARTIAL_MID, SOM_SUB_PARTIAL_END)
+       Temporarily set to zero to make do with LIST procedure.
+
+       * somP.h: Re-ordering.
+
+Wed Sep 25 19:36:11 1996  Ben Pfaff  <blp@gnu.org>
+
+       * som.c: Split into som-frnt.c, som-high.c, som-low.c.
+
+       * somP.h: New file for use by som-high.c, som-low.c.
+
+       * q2c.c: Added definition for VME.
+       (get_line) Now dumps `!' comment lines to the output file
+       verbatim.
+
+       * crosstabs.q, descript.q, file-handle.q, frequencies.q, list.q,
+       set.q: Changed format of `!' comment lines.
+
+Tue Sep 24 18:39:09 1996  Ben Pfaff  <blp@gnu.org>
+
+       * All source files: Added copyright notice.
+
+       * common.c: (xmalloc, xrealloc, xstrdup) Cast size_t's to unsigned
+       longs in msg() calls.
+
+       * con32s.c: (xmalloc, xrealloc) Updated from common.c.
+
+       * q2c.c: (xmalloc, xrealloc, xstrdup) Updated from common.c.
+
+Sat Sep 21 23:16:31 1996  Ben Pfaff  <blp@gnu.org>
+
+       * output.c: (outp_read_devices) Changed criteria for
+       distinguishing different types of lines.
+
+Fri Sep 20 22:52:28 1996  Ben Pfaff  <blp@gnu.org>
+
+       * cmdline.c: Changed syntax message.
+
+       * filename.c: (good_getcwd) Bug fix (?).
+       (normalize_filename) [__BORLANDC__] Uses _fullpath() library
+       function.
+       (search_path) Appends DIR_SEPARATOR to directory name only if it
+       does not already end with one.
+
+       * glob.c: Checks STAT_PAGER envvar before PAGER.
+
+       * output.c: Checks environment variables instead of just local
+       macros.
+
+Tue Sep 10 21:39:00 1996  Ben Pfaff  <blp@gnu.org>
+
+       * arena.c: (arena_destroy) Swatted a subtle bug that cropped up
+       when the pointer passed to the function was within the arena
+       itself, so that it couldn't properly be set to NULL _after the
+       arena was freed_.
+
+       * command.c: Re-enabled DISPLAY.
+
+       * display.c: Rewritten to handle tables.  Untested.
+
+       * filename.c: (search_path) Fixed memory leak.
+
+       * frequencies.q: (cmd_frequencies) Frees v_variables.
+       (postcalc) Calls cleanup_freq_tab() after displaying statistics.
+       (cleanup_freq_tab) New function to garbage collect.
+       (dump_full) Elegantized.
+
+       * main.c: New comment.
+
+       * output.h: New tag for tagged quotes: TAG_NEWLINE.
+
+       * postscript.c: Comment fix.
+       (release_fontmap, free_font_entry) New functions.
+       (ps_init_driver) Sets free_font_entry() as the freefunc for
+       hashtable `loaded'.  Calls release_fontmap() when destroying a
+       driver; also frees the output filename; also frees the
+       ps_driver_ext block.
+       (free_ps_encoding) Frees the filename as well as the encoding
+       block.
+       (output_encodings) Frees the line buffer and pops the msg-filename
+       stack.
+       (read_fontmap) Frees the fontmap filename and the line buffer.
+       (postopen, preclose) Misc. garbage collection fixes.
+       (ps_open_page) Destroys the `combos' hash table; sets `last_font'
+       to NULL; this fixes some output problems.
+       (text) Handles TAG_NEWLINE.  Untested.
+
+       * som.c: (cell_byte_size) Merged SCON_VALUE and SCON_TEXT cases.
+       (som_set_string) Removed.  All references changed to
+       `som_set_text'.
+       (som_set_text) Rewritten.  New interface.  More general.
+
+       * som.h: Minor format changes.
+       (struct som_value_cell) Removed; all references changed to
+       `som_text_cell'.
+       (enums SOT_*) Changed.
+
+Mon Sep  9 21:43:13 1996  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: Re-enabled SPLIT FILE.
+
+       * postscript.c: Comment fix.
+
+       * som.h: Added `SOT_NONE'.
+
+       * split-file.h: (cmd_split_file) Removed superfluous parenthesis.
+
+       * vfm.c: (dump_splits) Reimplemented.
+
+Sat Sep  7 22:35:12 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Compiled the project under gcc 2.7.2, which gave some new
+       warnings.  This led to many additions of casts from unsigned to
+       int sprinkled throughout the code.
+       
+       * arena.c: Many uses of `unsigned' changed to `size_t'.
+
+       * command.c: Added END FILE, END REPEAT to command table.
+       (var cmd_end_repeat) Renamed cmd_end_repeat_p.
+       (find_command, FILE_TYPE_okay) Not commented out anymore.
+       (parse_cmd) Calls FILE_TYPE_okay again.
+       (output_line) Added calls to som_output_text() to put the line
+       in the output files.
+
+       * common.c: (macro VME) Format changes.
+       (xstrdup) Asserts that its argument is not NULL.
+       
+       * data-list.c: Implemented dump_fixed_table().
+       
+       * inpt-pgm.c: Formatting changes.  Comment changes.
+       (end_case_proc) Renamed end_case_trns_proc.
+       (cmd_end_file, end_file_trns_proc) New functions.
+
+       * misc.c: Many uses of `int' and `unsigned' changed to `size_t'.
+
+       * misc.h: (local_strdup) New macro corresponding to strdup() but
+       allocating its data through local_alloc() if possible--that is, if
+       GNU C is in use.
+
+       * postscript.c: Comment changes.
+       (quote_ps_name, quote_ps_string, output_encodings) New functions.
+       (output_line, add_string) New macros supporting
+       output_encodings().
+       (postopen) Fixed contents of ${fixed-font} and ${prop-font}
+       substitution vars.  Calls output_encodings() when a line
+       consisting of `!encodings' is encountered.
+       (preclose) Some code moved into quote_ps_string().
+       (dump_line) Changed into macro supporting dump_fancy_line().
+       (switch_font) Now outputs DSC "%%IncludeResource: font (...)"
+       command when appropriate.
+       (write_text) Fixed `literal_char' array (I think it's fixed, at
+       least.)
+       (text) Fixed bug when width was zero.  Now exits immediately on
+       zero height_left.  Now, when executing `goto restart;', checks
+       that cp<end, so that we don't read beyond end-of-string.  Also,
+       outputs the correct code to the output file by outputting the code
+       from the metric instead of the internal metric index.
+
+       * repeat.c: (cmd_end_repeat) New function.
+
+       * som.c: (var som) `headers' renamed `options' and semantics
+       changed.  All references changed.
+       (draw_title) `if(px!=-1 || px!=-1)' --> `if(px!=-1 || py!=-1)'.
+       (build_target) Only inserts spacing if SOM_TOPT_SPACING not
+       selected.
+       (som_text_table) Removed.
+       (som_output_text) New function.
+
+       * som.h: (struct som_submission_form) Removed `header', `reuse',
+       replaced with bitmapped field `options'.
+       (SOM_TOPT_*) New enum set for som_submission_form.options.
+       (SOT_*) New enum set for som_output_text().
+
+       * temporary.c: (copy_variable) When copying the var label, only
+       calls xstrdup() if it's non-NULL.
+
+       * var.h: (enum type `vartype') Removed; all references changed to
+       `int'.
+
+       * vars-atr.c: (init_variable) Changed local var `nbytes' from
+       `int' to `size_t'.
+
+Thu Sep  5 22:05:56 1996  Ben Pfaff  <blp@gnu.org>
+
+       * font.h: Comment changes.
+
+       * groff-font.c: (groff_read_font) Initializes `name' field to
+       NULL.  Handles `encoding' field.
+
+       * hash.c: (hsh_dump) [GLOBAL_DEBUGGING] Output formatting changes.
+
+       * postscript.c: (struct font_entry) Removed `position' field.
+       (struct ps_font_combo) New struct.
+       (struct ps_driver_ext) Removed field `next_position'.  New fields
+       `combos', `next_combo'.  `last_font' field changed from
+       `font_entry *' to `ps_font_combo *'.
+       (ps_init_driver) Reformatted; handles new fields.  When
+       OPO_AUTO_ENCODE is set, adds the two default fonts' encodings to
+       the encoding list.
+       (get_encoding, find_encoding_file) New functions.
+       (add_encoding) Some code moved out into find_encoding_file().
+       (postopen) Changed value for ${title}.
+       (preclose) Sets `loaded' field to NULL after destroying the hash
+       table.
+       (ps_open_page) Added comment.  Inits the `combos' and `next_combo'
+       fields.
+       (ps_text_set_font_by_position) Figures out the current family if
+       not known.
+       (compare_ps_combo, hash_ps_combo, free_ps_combo) New functions.
+       (switch_font) Implemented.
+       (write_text) Calls switch_font() more often.  Format changes.
+       #undefs its macros after they're no longer useful.
+       (text) Changed `continue' at one point to a jump to the top of the
+       loop because we don't want `separate' reset to 0 at that point.
+       (load_font) No longer sets `position' in the font_entry created.
+
+Wed Sep  4 21:45:35 1996  Ben Pfaff  <blp@gnu.org>
+
+       * font.h: (struct font_desc) New member `encoding', which is not
+       properly handled yet.
+
+       * glob.c: (init_glob) Some new i18n code, which is probably
+       screwed up.
+
+       * output.c: (outp_read_devices, outp_get_paper_size) Changed
+       `size' local from `int' to `size_t'.
+
+       * postscript.c: New driver configuration parameter `auto-encode'.
+       New enums OPO_AUTO_ENCODE, ODA_COUNT.
+       (struct font_entry) New member `position'.
+       (struct ps_driver_ext) Reordered.  New hash table member
+       `encodings'; new members `next_position', `next_encoding',
+       `last_font'.  Members `current', `prop', `fixed' changed from type
+       `font_desc *' to `font_entry *'; all references changed.
+       (struct ps_encoding) New struct.
+       (read_ps_encodings, compare_ps_encoding, hash_ps_encoding,
+       free_ps_encoding, add_encoding) New functions.
+       (ps_init_driver) Added OPO_AUTO_ENCODE to default
+       x->output_options.  Initializes new members of ps_driver_ext.
+       Changed default value for prologue_fn, encoding_fn.  Calls
+       read_ps_encodings after loading default fonts.
+       (option_tab[], ps_option) Handle new configuration parameter.
+       (switch_font) New function.
+       (struct output_char) `font' member changed from `font_desc *' to
+       `font_entry *'.  New member `separate'.
+       (read_fontmap) Changed `size' from `int' to `size_t'.
+       (output_line, put_number) New macros for write_text().
+       (write_text) Optimizes text output by consolidating multiple
+       calls to PostScript `show' operator.
+       (text) Keeps track of when text arguments can't be consolidated by
+       write_text(), and marks those spots in the output stream.
+       (load_font) Sets `position' of the allocated font_entry to -1, cuz
+       the font hasn't been switched to by switch_font(), which is where
+       the position is important--the PostScript is what cares about the
+       position.
+
+Sat Aug 31 23:52:38 1996  Ben Pfaff  <blp@gnu.org>
+
+       * hash.c: (hsh_destroy) Ignores NULL argument.  Doesn't try to
+       call a NULL free_func.
+       (hsh_rehash) Elegantized.
+       (hsh_probe) Fix bug that manifested when the table was expanded
+       and thus had to change location in memory.  Good thing
+       too--otherwise could have been much more subtle.
+       (hsh_find) [GLOBAL_DEBUGGING] Not stubbed out anymore.
+       (hsh_foreach) New function for hash table iteration.
+
+       * hash.h: (struct hsh_iterator) New.
+
+       * lexer.c: (parse_tagged_quote) Font and family name strings in
+       tags are now null-terminated.
+
+       * output.c: (outp_evaluate_dimension) Fixed over-aggressive unit
+       parsing.
+       (internal_get_paper_size, outp_get_paper_size) Fixed; now work as
+       documented.  (Never before tested?)
+
+       * output.h: Comment changes.
+
+       * postscript.c: New driver options `optimize-text-size',
+       `optimize-line-size', `max-fonts-simult'.  New enum set for
+       specing cached line types.  Comment fixes.
+       (struct line_form) New struct.
+       (struct ps_driver_struct) New members `text_opt', `line_opt',
+       `max_fonts', `lines'.
+       (ps_init_driver) Initializes new members of ps_driver_struct.
+       (user option type enum set) New member `nonneg_int_arg'.
+       (static var option_tab[]) Supports new options.
+       (ps_option) Handles new options.
+       (find_ps_file) Made static.  No longer calls hsh_dump().
+       (ps_get_var) Made static.
+       (preclose) Dumps out proper DSC trailer.
+       (ps_open_page) Elegantized.
+       (ps_close_page) Calls dump_lines() if appropriate.
+       (ps_line_horz, ps_line_vert, ps_line_intersection) Reduced to
+       wrappers around line().
+       (int_2_compare, compare_line, dump_line, dump_fancy_line,
+       dump_lines, hash_line, free_line, line) New functions for support
+       of line caching.
+       (write_text, text) Made static.
+       (text) Added to font support, not finished.
+
+Thu Aug 29 21:36:41 1996  Ben Pfaff  <blp@gnu.org>
+
+       * font.h: (struct font_desc) New members ascent, descent.
+
+       * groff-font.c: (groff_read_font) Calculates font ascent and
+       descent from the ascent and descent of the `d' and `p' characters,
+       respectively, as per a suggestion on comp.fonts.
+
+       * postscript.c: (ps_open_page, ps_close_page, ps_line_horz,
+       ps_line_vert, ps_line_intersection) Rewritten to deal with changed
+       prologue.
+       (write_text) Handles text right-justification and centering (not
+       full justification).  Still very inefficient.  (One output line
+       per character?!)
+       (struct output_char) Added fields for font and font size.
+       (text) Many bugfixes.
+
+Sat Aug 24 23:26:00 1996  Ben Pfaff  <blp@gnu.org>
+
+       * cmdline.c: (usage) Calls outp_list_classes().
+
+       * font.h: Comment fix.
+
+       * groff-font.c: New exported global var `space_index'.
+       (groff_init) New function to initialize `space_index'.
+       (hash_kern) Casts result to unsigned.
+       (font_name_to_index) Renamed font_char_name_to_index.  All
+       references changed.  Also, now returns the value of `space_index'
+       when passed an ASCII space character as an argument.  Fixed
+       handling of nulls.
+       (font_get_kern_adjust) Changed i from `int' to `unsigned'.
+       Handles passed NULL pointers properly.
+
+       * lexer.c: (parse_tagged_quote) Comment fix.  Better range
+       checking.
+
+       * output.c: (outp_list_drivers) Removed.  Removed all references.
+       
+       * output.h: Comment fixes.
+
+       * postscript.c: (ps_open_global) Calls groff_init().
+       (output_char) New structure.
+       (write_text) New function.
+       (text) No longer stubbed out!  Now the output is correct--with a
+       few exceptions, one of them being that the page has to be held
+       upside down into a mirror.
+
+Sun Aug 11 21:31:22 1996  Ben Pfaff  <blp@gnu.org>
+
+       * font.h: Comment fix.
+       
+       * font.c: (name_to_index) Renamed font_name_to_index, made extern.
+       All callers changed.
+       (number_to_index) Renamed font_number_to_index, made extern.  All
+       callers changed.
+       (font_get_kern_adjust, font_get_char_metrics) New functions.
+
+       * output.h: New constant OUTP_T_INTERNAL_DRAW.
+
+       * postscript.c: Changed default line width back to 1/2 point.
+       (ps_line_horz, ps_line_vert, ps_line_intersection) Now lines are
+       in the center of the space allotted for them, not just a fixed
+       offset from the edge of the space; this fixes some bugs.
+       (ps_line_intersection) Now supports all command line styles.
+       (ps_text_get_size) Bug fix in computation of em width.
+       (text) New function, the meat behind ps_text_metrics and
+       ps_text_draw.  Not complete.
+       (ps_text_metrics, ps_text_draw) Removed the stub taken from
+       ascii.c; call text().
+
+Sat Aug 10 23:28:17 1996  Ben Pfaff  <blp@gnu.org>
+
+       * arena.c: (arena_free) Assert that the argument is non-NULL.
+       
+       * groff-font.c: (add_kern) Calls arena_free() for old_kern if and
+       only if old_kern is non-NULL.
+
+       * postscript.c: (ps_init_driver) Changed default line width to 1
+       point.
+       (postopen) New prologue variables.
+       (ps_line_horz, ps_line_vert, ps_line_intersection) Implements some
+       more of the common line styles properly, but not all.
+       (ps_text_metrics) Fixed problem with this stubbed out version that
+       kept it from taking font sizes into account.
+
+Thu Aug  8 22:31:11 1996  Ben Pfaff  <blp@gnu.org>
+
+       * arena.c: (arena_malloc) Bug fix.
+       (arena_dump) [GLOBAL_DEBUGGING] New function.
+
+       * ascii.c: Comment fix.
+       (count_fancy_chars, delineate) Now static functions.
+       
+       * filename.c: (interp_vars) Bug fixes.
+
+       * font.h: Comment fixes.
+
+       * glob.c: (init_glob) Sets set_viewwidth, set_viewlength at
+       beginning in case we have an error message to display before
+       initializing the display.
+
+       * groff-font.c: Comment fix.  Changed rehash threshold from 2/3
+       full to 1/2 full.
+       (groff_read_font) Bug fixes.
+       (name_to_index) Increments hash.used.  Sets `name' field of hash
+       entry properly.
+       (add_kern) Sets kern_max_used after rehashing.  Other bug fixes.
+
+       * hash.c: Return type changed.
+
+       * postscript.c: Continued development.  Now marks lines on the
+       paper, but very buggy.
+
+Sat Aug  3 20:50:35 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Changed comments in many source files from `/* xxx /* yyy */' to
+       `/* xxx */ /* yyy */' for cleanliness.
+
+       * arena.c: (arena_sd_strdup) New function.
+       
+       * ascii.c: (struct ascii_driver_ext) New member `file'.
+       (ascii_init_driver) Fills out member `file' for initing; uses
+       close_file_ext for closing drivers.
+       (ascii_option) Changed %.*s back to %s because the a_string's are
+       always null-terminated.
+       (postopen, preclose) New functions.
+       (ascii_open_page) Uses new style of open_file_ext.
+       (ascii_option, commit_line_buf, output_lines) Use ext->file.file
+       instead of this->output.
+       (__assert_fail) Removed.
+
+       * cmdline.c: Changed syntax_message[].
+
+       * error.c: #include's <readline/history.h> only if the history
+       library is available, not if just the readline library is
+       available.
+
+       * filename.c: (expand_line) Removed alloca() support.
+       (interp_vars) No longer tilde-expands argument.  Limit on output
+       length removed.
+       (tilde_expand) Now treats argument as path rather than filename.
+       [!unix] Now is a no-op function.
+       (search_path) Better verbose message formatting.
+       (open_file, close_file) Comment fixes.
+       (close_file) [!unix] Doesn't bother with pipes.
+       (open_file_ext) Completely rewritten, interface revamped.
+       (close_file_ext) New function.
+
+       * font.h: Comment changes.
+
+       * frequencies.q: Removed AIX alloca support since it doesn't use
+       alloca.
+
+       * hash.c: Comment changes & additions.
+       (hsh_create) Initializes entire table instead of first M entries.
+       (hsh_probe) Stupid bug fixed.  Now it works.
+       (hsh_dump) [GLOBAL_DEBUGGING] New function.
+
+       * main.c: (parse) Detects EOF properly in token-eating loop.
+       Should the STOP token have its value changed to 0?
+
+       * misc.c: (blp_getdelim) [HAVE_GETDELIM] Now it's a macro.
+       (blp_getline) Now it's a macro.
+
+       * output.h: (struct outp_driver) Removed members output, filename.
+       
+       * output.c: (outp_init) [!NO_POSTSCRIPT] Installs PostScript
+       drivers in driver table.
+       (outp_read_devices) Frees buf.  Warns if there are no active
+       output drivers.
+       (outp_configure_clear) Sets outp_configure_vec to NULL after
+       deleting its elements.
+       (configure_driver, destroy_driver) Removed references to output,
+       filename members of outp_driver.
+       (outp_evaluate_dimension, internal_get_paper_size,
+       outp_get_paper_size) New functions.
+
+       * postscript.c: Continued development.  Now links but doesn't make
+       any marks on the page.  Lotsa bugs I suppose.
+
+       * str.c: (strcasecmp) [!HAVE_STRCASECMP] New function.
+
+       * str.h: Comment changes.
+
+Sat Jul 27 22:32:38 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Removed dependencies on non-nested comments in several files.
+       Also removed references to (unix || __unix__) in #if's since
+       prefh.orig makes those two equivalent.
+       
+       * ascii.c: (ascii_open_global) Creates ascii_arena.
+       (ascii_close_global) Destroys ascii_arena.
+       (ascii_init_driver) Doesn't create ascii_arena.
+       (ascii_copy_driver) Removed.
+       (ascii_option) Possible bugfix regarding %s vs. %.*s with a_string's.
+       (outp_class ascii_class) Removed ascii_copy_driver reference.
+
+       * frequencies.q: Now can display all statistics except median.
+       Still not finished.
+
+       * output.c: Handles outp_class.ref_count so output class
+       destructors are called properly.
+       (add_class) Sets ref_count to 0.
+       (configure_driver) Initializes class if ref_count++ is 0.
+       (destroy_driver) Destructs class if --ref_count is 0.  Frees the
+       class output file name.
+       
+       * output.h: (struct outp_class) Removed copy_driver, inited.
+       Added ref_count.
+
+       * postscript.c: Completely replaced but not finished.
+       
+Tue Jul 23 21:48:36 1996  Ben Pfaff  <blp@gnu.org>
+
+       * approx.h: #includes <float.h>.
+
+       * arena.h, arena.c: Many functions changed to take an arena **
+       instead of an arena *, for consistency.  All callers changed.
+       (arena_alloc) Now creates a new arena if passed *A that is NULL.
+       (arena_destroy) Sets *A to NULL.
+       
+       * ascii.c: (delineate) Implements OUTP_T_VERT correctly.  Removed
+       assertion that `width' be positive.
+
+       * command.c: Removed #if's from cmd_table.
+       (walk_cmdtable_func) [0] New function (debug code).
+       (init_cmd_parser) [0] Dumps out cmd_table (debug code).
+       (parse_cmd) Doesn't return failure for unimplemented commands.
+
+       * common.h: (SYSMIS) Changed from DBL_MAX to -DBL_MAX.
+       (SYSCODE) New constant macro.
+
+       * descript.q: Checks for positive n_variables before performing
+       analysis.
+
+       * file-handle.q: (get_handle_by_filename) Bug fix: passes &f to
+       avl_find instead of &fp as arg 2.
+
+       * frequencies.g, frequencies.q: Continued updating; now compiles &
+       works again, but not complete.
+
+       * main.c: Changes to user messages.
+
+       * misc.c: (reverse) [0] New function.
+
+       * settings.h: Comment removed.  #includes "common.h".
+
+       * som.c: (som_set_null) New function.
+       (som_set_value, som_set_string, som_set_text) More detailing
+       assertions.
+       (som_set_float) Implemented function.
+       (dump_columnated_table) Bug fix regarding page breaks.
+       (draw_cell) Bug fix regarding text that spilled out of a cell.
+       (draw_intersection, draw_horz_rule, draw_vert_rule) No longer draw
+       null lines.
+       (get_cell_size) Support SCON_EMPTY cells.
+       (get_table_size) When calculating rules' widths and heights, mask
+       out SLIN_SPACING bit.  Added SOPT_X_HLTL support.
+       
+       * som.h: (som_any_cell) New option SOPT_X_HTLT.  Removed
+       SOPT_X_SHADE.
+       (struct som_submission_form) New member `header'; all users
+       changed.
+
+       * val-labs.c: (get_label) User messages changed.
+
+       * var.h: Changed FREQUENCIES structures.
+
+       * vars-atr.c: (is_num_user_missing, is_str_user_missing) Made
+       inline.
+       
+Fri Jul 19 19:11:13 1996  Ben Pfaff  <blp@gnu.org>
+
+       * approx.h: Definition of EPSILON now depends on system's
+       DBL_EPSILON.  Removed GNU C specific code.
+       (cmpapx) Renamed approx_compare.
+
+       * frequencies.g, frequencies.q: Continued updating; still doesn't
+       compile.
+
+       * groff-font.c: (name_to_index) Fix bug that kept it from
+       compiling.
+
+       * hash.c, hash.h: Completed work.
+
+       * var.h: Changes to freq_tab, frequencies_proc.
+       
+Wed Jul 17 21:23:36 1996  Ben Pfaff  <blp@gnu.org>
+
+       New hashing code.
+       * hash.c, hash.h: New files.  Not completed.
+       * Makefile.am: Added hash.c to source file list.
+       * font.h: (struct font_desc) New member kern_size_p.
+       * groff-font.c: Uses hash.h.
+       (hashpjw) Moved to hash.c.
+       (next_prime_power) Rewrote, renamed hsh_next_prime, moved to
+       hash.c.
+       (static var hash) New member size_p.
+       * var.h: Includes hash.h.
+       (struct freq_tab) Changed AVL_TREE to hash_tab.
+
+       * vars-prs.c: Comment, formatting fixes.
+
+       * frequencies.g, frequencies.q: Continued updating.  Not yet
+       working.
+
+       * formats.c: Bug fix.
+
+Tue Jul 16 22:10:04 1996  Ben Pfaff  <blp@gnu.org>
+
+       Increasing parallelism between DESCRIPTIVES and FREQUENCIES.
+       * descript.g: Comment fixes.
+       * descript.q: Comment fixes.  Moved some declarations into var.h.
+       Made dsc_info a static table.  Updated FIXMEs.
+       (internal_cmd_descriptives) Beautified.
+       
+       * frequencies.q: Started updating into working order.
+       * frequencies.g: New file analogous to descript.g.
+       * var.h: Comment fixes.  Added structures for FREQUENCIES.
+       
+       * som.c: Removed vestiges of crushing and partial table support.
+
+Sun Jul 14 15:45:31 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Many more changes to som.c especially, but these will not be
+       documented as I have resolved to remove them.  This patchlevel is
+       being released solely so that I can fall back to it if I decide
+       that removing the changes is not a good idea.
+
+Sat Jul 13 09:58:44 1996  Ben Pfaff  <blp@gnu.org>
+
+       * som.c: (global var som) New member `cum_y'.
+       (build_target) Properly handles titles for partial tables.
+       (dump_partial_beg, dump_partial_mid, dump_partial_end)
+       Merged into single new function dump_partial().  Fixed problem
+       with titles on partial tables.
+       (dump_table) Calls dump_partial() for all parts of partial tables.
+       (dump_page) Criteria for drawing title changed.
+       
+Fri Jul 12 22:03:36 1996  Ben Pfaff  <blp@gnu.org>
+
+       * command.c: (cmd_table) Added LIST, WEIGHT.
+
+       * command.c: (cmd_remark) No longer frees `s' since it's not
+       dynamically allocated.
+       
+       * data-out.c: (convert_f) Now correctly handles the case where
+       abs(v->f)<1 but v->f rounds to a value of 1.00 given the specified
+       number of decimals.
+       (som_destroy_all_tables) Removed argument.  All callers changed.
+       (som_vline, som_hline) Argument validity checking corrected.
+       (som_set_value) Implemented half-heartedly.
+       (replicate_table) Copies tables piece-by-piece when using Checker.
+
+       * som.h: New line style SLIN_1THIN, currently equivalent to
+       SLIN_0.  New enum set SOM_SUB_*.
+       (struct som_submission_form) Removed `seq_no'.  Added `type'.
+       
+       * list.q: Newly working file; uses partial tables.
+       
+       * som.c: (som_reduce_table) Renamed som_set_table_height().
+       (som_crush) Removed argument `group'.
+       (global var som) Removed `nt', `seq_no'.  Added `type'.
+       (som_submit_table) Arguments changed.
+       (output_table) Removed partial table code.
+       (build_target) New arg; partial table support added.  All callers
+       changed.
+       (dump_plain_table) Removed partial table code.
+       (dump_partial_beg, dump_partial_mid, dump_partial_end) New functions.
+       (dump_table) Supports partial tables.
+       (dump_page) New argument to allow not drawing top and/or bottom
+       headers.  All callers changed.  Supports partial tables.
+
+Sat Jul  6 22:22:25 1996  Ben Pfaff  <blp@gnu.org>
+
+       * data-out.c: Changed `#include <approx.h>' to `#include
+       "approx.h".
+       (convert_F) Comment fix.  Now won't print `-.000', etc.
+
+       * descript.q: Now Z-scores work, although there appears to
+       be a bug (which might actually be in data-out.c:convert_F()).
+       (descriptives_trns_proc, descriptives_trns_free) New functions.
+       (run_z_pass) Implemented.
+       
+       * var.h: Comment fixes.
+       (dsc_z_score, descriptives_trns) New structs.
+       (descriptives_trns) Added to any_trns as `dsc'.
+
+       * error.c, error.h: New error class, IS (Installation Script
+       error), used in those instances where the error is in the
+       installation, but there is a script file or installation file that
+       can be usefully referred to.
+       
+       * output.c: Change many IE classes to IS classes.
+
+       * cases.c, command.c, common.c, crosstabs.q, expr-evl.c,
+       frequencies.q, list.q, vars-prs.c, vfm.c: Removed reference to
+       HAVE_MALLOC_H because Borland C++ alloca() is broken, so why
+       include the corresponding header?
+       
+       * glob.c: (init_glob) Don't malloc term_buffer under Checker.
+       Don't bail out if termcap can't be read.
+
+       * som.c: (som_destroy_table) Removed.
+       (som_reduce_table, som_destroy_all_tables) New functions.
+       (som_crush) New function, not implemented.
+       
+       * som.h: New table option STAB_CRUSH.  Comment fix.  New struct
+       som_submission_form.  Function prototypes revised.
+
+       Outputting huge tables (1000s of rows) a few rows at a time
+       is supported, though untested.  May even break everything.
+       Actually, the code doesn't even compile right now.
+       * som.c: (struct som) New fields htv, nt, seq_no.
+       (som_submit_table) Multiple arguments changed to a single
+       pointer to struct submission_form.  Only increments subtable_num
+       if seq_no is zero.  Only destroys table if it's not going to
+       be reused.
+       (replicate_table) New function.
+       (output_table) Comment fix.
+       (examine_table) Changed inline code to code calling
+       replicate_table().  Calculates htv.  Supports partial tables.
+       (draw_title) Removed comment.
+       (build_target) Only allows for title on first part of partial
+       tables.
+       (dump_plain_table) Only resets table chunk number on first part
+       of partial tables; FIXME: doesn't work quite right.  Supports
+       partial tables.
+       (dump_page) Titles only on first part of partial tables.
+
+Fri Jul  5 20:16:19 1996  Ben Pfaff  <blp@gnu.org>
+
+       * Thanks to an unreliable IDE hard drive, I have spent the last
+       day reconstructing my Debian GNU/Linux installation and redoing
+       the previous day's changes--somehow I managed to save every file
+       except for output.c and output.h.  So the following changes could
+       really be considered independent of the output.c, output.h changes
+       from Jul 4.
+
+       * output.h, output.c: Moved the outp_configure_vec global var,
+       outp_names struct, and enum set OUTP_S_* from output.h to output.c.
+       outp_configure_vec is now static.
+       
+Thu Jul  4 20:20:24 1996  Ben Pfaff  <blp@gnu.org>
+
+       * The entire philosophy behind configuration of the output drivers
+       changed.  Now there is a termcap-type configuration where drivers
+       to be read are determined beforehand, rather than parsing the
+       entire output init file and storing it in memory & deciding what
+       to actually use later.  Faster & more memory-efficient at the same
+       time, cool.
+       
+       * output.c: Comment fix.  Removed outp_init_drivers global var.
+       Removed all references to synonyms.  New structure outp_defn.  New
+       global vars outp_macros, outp_configure_vec.
+       (search_name, delete_name, add_name, check_configure_vec,
+       expand_name, find_defn_value) New static functions.
+       (outp_configure_clear, outp_configure_add, outp_configure_macro,
+       outp_read_devices) New extern functions.
+       (outp_init) Much functionality moved into outp_read_devices.
+       (outp_read_devices) Format of output init file changed; name of
+       file is `devices' rather than `output' to avoid Makefile
+       conflicts.
+       (outp_clear) Renamed outp_done.
+       (outp_list_classes) Bug fix, cleaned up.
+       (outp_list_drivers) Not implemented anymore.
+       (outp_configure_driver) Now a static function; simplified; now
+       interpolates macros; supports new structure.
+       (outp_enable_driver, match_synonym) Removed; all references
+       removed.
+       (find_driver) First argument removed.
+       
+       * output.h: Global var outp_init_drivers removed; new structure
+       outp_names; new enum set OUTP_S_*; new global var
+       outp_configure_vec; function prototypes for output.c exports
+       updated.
+       
+       * main.c: (main) Calls outp_read_devices() after parsing the
+       command line.
+       
+       * cmdline.c: (parse_command_line) New option -v --verbose;
+       --version changed to -V.  --device option changed syntax to just
+       take a single device name.  Accepts key=value declaration of
+       output init file macros.  Syntax message updated.
+
+       * filename.c: (expand_line) New function.
+       (interp_environ_vars) Renamed interp_vars; no longer uses
+       fixed-size buffer.
+       (blp_getenv) Allows $ARCH and $VER pseudo-environment-vars to be
+       overridden by real environment vars.
+       (search_path) Uses verbose_msg() instead of #ifdef'd printf().
+       * filename.h: interp_environ_vars() renamed interp_vars().
+       
+       * error.c, error.h: Added extern variable `verbosity', message
+       class MM.
+       
+       * error.c: (vmsg) Support message class MM.
+       (verbose_msg) New function.
+
+       * descript.q: (generate_z_varname) Bug fix in generation of
+       Z-score varnames.
+       (dump_z_table) Bug fix in column headers.
+       
+       * ascii.c: (ascii_init_driver) Changed minimum number of lines per
+       page from 29 to 15.  Don't set a default for ops[OPS_INIT,
+       OPS_DONE].  Writes the uninit string when the driver is closed.
+       (ascii_open_page) Write the init string before the first page.
+       (output_shorts) Form of main loop changed from `while' to `for'.
+       Bug fix with overstrikes: the character is printed *after* the
+       backspace.  Eliminated a lot of `& 0xff' modifiers.
+       (advance_to_left_margin) New function.
+       (return_carriage, output_lines) Handle left margin.
+
+Thu Jul  4 00:35:59 1996  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c: New option `carriage-return-style'.
+
+       * ascii.c: (count_fancy_chars) New function.
+       (delineate, text_metrics) Use new function; bug fixes regarding
+       rich text strings.
+       (text_draw) Bug fix with rich text.
+       (output_string, output_shorts) Reordered.
+       (output_shorts) Now handles boxchars and some overstrike font
+       changes.
+       (output_char, return_carriage) New functions.
+       (output_lines) Now handles overstriking and font changes properly;
+       some code moved to output_shorts.
+
+Tue Jul  2 22:13:23 1996  Ben Pfaff  <blp@gnu.org>
+
+       [GLOBAL_DEBUGGING]
+       * ascii.c: New member `debug' in ascii_driver_ext.
+       (ascii_init_driver, delineate) Uses new member.
+
+       Now you can set a vertical height on writing text.
+       * ascii.c: (delineate) Keeps track of vertical position.
+       (text_draw) No longer considers fully justified text an internal
+       error.
+       
+       * output.h: New flag OUTP_T_VERT; other OUTP_T_ values changed.
+
+       Tables' titles are drawn; they can have variable height.
+       * som.c: `som' struct has new member, title_height.
+       (draw_title) New argument.  Moved within file.  All caller
+       changed.
+       (build_target) New argument, amount of space needed for first row.
+       Calculates height of title, takes that into account.  All callers
+       changed.
+       (dump_plain_table, dump_columnated_table) Took calculation of y1,
+       y2 out of loop.
+       (dump_columnated_table) [GLOBAL_DEBUGGING] Debugging code
+       improved.
+       (dump_columnated_table) Organized for readability.
+       (dump_page) Makes use of som.title_height.
+
+       * som.c: Many comment bug fixes.
+
+       * descript.q: (try_name, generate_z_name) Bug fix regarding
+       generation of Z-score variable names.
+       * var.h: Removed num from descriptives_proc; all referents removed.
+
+Mon Jul  1 22:13:39 1996  Ben Pfaff  <blp@gnu.org>
+
+       * ascii.c: (ascii_line_horz, ascii_line_vert,
+       ascii_line_intersection) Added debugging code.
+
+       Added a descriptive line above each table to describe it.
+       * command.c: (parse_cmd) Calls som_new_series.
+       
+       * som.c: New static vars table_num, subtable_num.  New `som'
+       member `title'.
+       (dump_page) New arguments.
+       (som_submit_table) Handle new variables.
+       
+       * som.c, som.h: (som_submit_table) New arguments.  All callers
+       changed.
+       (som_new_series) New function.
+       (build_target) Makes room for extra line.
+       (draw_title) New function.
+       (dump_page) Calls draw_title.  Bug fix: doesn't always set
+       som.ext->cp to 0.
+       
+       Columnation of tables support.
+       * som.h: Deleted fr, lr, ri from som_table.  Reorganized.
+       
+       * som.c: Deleted references to fr, lr, ri.
+       (som_columnate) Bux fix: sets group member of table.
+       (som_add_options) Function removed.
+       (dump_table) Split into three functions; extensively reworked.
+       
+       * descript.q: (dump_z_table) Better output table formatting; added
+       title support to correspond to som.h changes.
+       (display) Title support.
+
+       * output.h: Added OUTP_T_NONE.
+       
+Mon Jul  1 13:00:00 1996  Ben Pfaff  <blp@gnu.org>
+
+       * descript.q: Improved handling of Z scores; still not perfect.
+       
+       * output.h, ascii.c: Added hook for getting em width of current
+       font.
+       
+       * som.c: Uses new em-width hook.  Added debugging code to
+       several functions.
+       (som_columnate) New argument.
+       (som_add_options) Removed.
+
+Jun 29 17:40:47 1996  Ben Pfaff  <blp@gnu.org>
+
+       * som.h, som.c, output.c, output.h, ascii.c: Updated to work with
+       rules as a property of the table instead of as a property of the
+       cells.
+       
+       * ascii.c: Added `header' to table of options.
+       
+       * descript.q: Added even shorter statistic names; modified to work
+       with new som interface.
+       
+       * misc.c (blp_getdelim): Bug fix.
+       
+       * version.c: includes 'conf.h'.
+       
+----------------------------------------------------------------------
+Local Variables:
+mode: change-log
+version-control: never
+End:
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644 (file)
index 0000000..87061dd
--- /dev/null
@@ -0,0 +1,87 @@
+## Process this file with automake to produce Makefile.in  -*- makefile -*-
+
+# PSPP
+
+bin_PROGRAMS = pspp
+
+INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src -I$(top_srcdir)/lib     \
+-I$(top_srcdir)/intl
+
+DISTCLEANFILES = foo $(q_sources_c)
+MAINTAINERCLEANFILES = Makefile.in
+EXTRA_DIST = $(q_sources_q) q2c.c
+ETAGS_ARGS = -l c $(q_sources_c)
+SUFFIXES = .q
+
+$(q_sources_c): q2c
+.q.c:
+       ./q2c $< $@
+
+q_sources_c = correlations.c crosstabs.c descript.c file-handle.c      \
+frequencies.c list.c means.c set.c t-test.c
+
+q_sources_q = correlations.q crosstabs.q descript.q file-handle.q      \
+frequencies.q list.q means.q set.q t-test.q
+
+pspp_SOURCES = aggregate.c alloc.c alloc.h apply-dict.c approx.h       \
+ascii.c autorecode.c avl.c avl.h bitvector.h cases.c cases.h cmdline.c \
+command.c command.def command.h compute.c correlations.c count.c       \
+crosstabs.c data-in.c data-in.h data-list.c data-out.c debug-print.h   \
+descript.c dfm.c dfm.h do-if.c do-ifP.h error.c error.h expr-evl.c     \
+expr-opt.c expr-prs.c expr.h exprP.h file-handle.c file-handle.h       \
+file-type.c filename.c filename.h flip.c font.h format.c format.def    \
+format.h formats.c frequencies.c frequencies.g get.c getline.c         \
+getline.h glob.c groff-font.c hash.c hash.h heap.c heap.h html.c       \
+htmlP.h include.c inpt-pgm.c inpt-pgm.h lexer.c lexer.h list.c log.h   \
+loop.c magic.c magic.h main.c main.h matrix-data.c matrix.c matrix.h   \
+means.c mis-val.c misc.c misc.h modify-vars.c numeric.c output.c       \
+output.h pfm-read.c pfm-write.c pfm.h pool.c pool.h postscript.c       \
+print.c random.c random.h recode.c rename-vars.c repeat.c sample.c     \
+sel-if.c set.c settings.h sfm-read.c sfm-write.c sfm.h sfmP.h som.c    \
+som.h sort.c sort.h split-file.c stat.h stats.c stats.h str.c str.h    \
+sysfile-info.c tab.c tab.h temporary.c title.c t-test.c val-labs.c     \
+var-labs.c var.h vars-atr.c vars-prs.c vector.c vector.h version.c     \
+version.h vfm.c vfm.h vfmP.h weight.c
+
+GMP_LIBS = ../lib/gmp/mpf/libmpf.a                     \
+       ../lib/gmp/mpn/libmpn.a                 \
+       ../lib/gmp/libgmp.a
+
+LDADD =        ../lib/julcal/libjulcal.a               \
+       ../lib/misc/libmisc.a                   \
+       ../lib/dcdflib/libdcdflib.a             \
+       @GMP_LIBS@ @INTLLIBS@
+
+version.c:
+       echo "#include <config.h>" > version.c
+       echo "const char bare_version[] = \"@VERSION@\";" >> version.c
+       echo "const char version[] = GNU_PACKAGE \" @VERSION@\";" >> version.c
+       echo "const char stat_version[] = GNU_PACKAGE \" @VERSION@ \
+(`date`).\";" >> version.c
+       echo "const char host_system[] = \"$(host_triplet)\";" >> version.c
+       echo "const char build_system[] = \"$(build_triplet)\";" >> version.c
+       echo "const char default_config_path[] =\
+\"~/.pspp:$(pkgsysconfdir)\";" >> version.c
+       echo "const char include_path[] =\
+\"./:~/.pspp/include:$(pkgdatadir)\";" >> version.c
+       echo "const char groff_font_path[] = \"~/.pspp/font:\" \\" >> version.c
+       echo "  \"$(pkgdatadir)/font:\" \\" >> version.c
+       echo "  \"/usr/local/lib/groff/font:\" \\" >> version.c
+       echo "  \"/usr/lib/groff/font:\" \\" >> version.c
+       echo "  \"/usr/local/share/groff/font:\" \\" >> version.c
+       echo "  \"/usr/share/groff/font\";" >> version.c
+       echo "const char locale_dir[] = \"$(datadir)/locale\";" >> version.c
+\f
+# q2c
+
+LOCAL_CC = @LOCAL_CC@
+LOCAL_COMPILE = $(LOCAL_CC) -c $(DEFS) $(INCLUDES) $(CPPFLAGS) $(CFLAGS)
+LOCAL_LINK = $(LOCAL_CC) $(LDFLAGS) -o $@
+
+q2c.o: q2c.c
+       $(LOCAL_COMPILE) $< -o q2c.o
+q2c: q2c.o
+       $(LOCAL_LINK) q2c.o ../lib/misc/libmisc.a -o q2c
+
+CLEANFILES = q2c
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/aggregate.c b/src/aggregate.c
new file mode 100644 (file)
index 0000000..d49ebf9
--- /dev/null
@@ -0,0 +1,1523 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "approx.h"
+#include "command.h"
+#include "error.h"
+#include "file-handle.h"
+#include "lexer.h"
+#include "misc.h"
+#include "settings.h"
+#include "sfm.h"
+#include "sort.h"
+#include "stats.h"
+#include "str.h"
+#include "var.h"
+#include "vfm.h"
+#include "vfmP.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* Specifies how to make an aggregate variable. */
+struct agr_var
+  {
+    struct agr_var *next;              /* Next in list. */
+
+    /* Collected during parsing. */
+    struct variable *src;      /* Source variable. */
+    struct variable *dest;     /* Target variable. */
+    int function;              /* Function. */
+    int include_missing;       /* 1=Include user-missing values. */
+    union value arg[2];                /* Arguments. */
+
+    /* Accumulated during AGGREGATE execution. */
+    double dbl[3];
+    int int1, int2;
+    char *string;
+    int missing;
+  };
+
+/* Aggregation functions. */
+enum
+  {
+    NONE, SUM, MEAN, SD, MAX, MIN, PGT, PLT, PIN, POUT, FGT, FLT, FIN,
+    FOUT, N, NU, NMISS, NUMISS, FIRST, LAST,
+    N_AGR_FUNCS, N_NO_VARS, NU_NO_VARS,
+    FUNC = 0x1f, /* Function mask. */
+    FSTRING = 1<<5, /* String function bit. */
+    FWEIGHT = 1<<6, /* Weighted function bit. */
+    FOPTIONS = FSTRING | FWEIGHT /* Function options mask. */
+  };
+
+/* Attributes of an aggregation function. */
+struct agr_func
+  {
+    const char *name;          /* Aggregation function name. */
+    int n_args;                        /* Number of arguments. */
+    int alpha_type;            /* When given ALPHA arguments, output type. */
+    struct fmt_spec format;    /* Format spec if alpha_type != ALPHA. */
+  };
+
+/* Attributes of aggregation functions. */
+static struct agr_func agr_func_tab[] = 
+  {
+    {"<NONE>",  0, -1,      {0, 0, 0}},
+    {"SUM",     0, -1,      {FMT_F, 8, 2}},
+    {"MEAN",   0, -1,      {FMT_F, 8, 2}},
+    {"SD",      0, -1,      {FMT_F, 8, 2}},
+    {"MAX",     0, ALPHA,   {-1, -1, -1}}, 
+    {"MIN",     0, ALPHA,   {-1, -1, -1}}, 
+    {"PGT",     1, NUMERIC, {FMT_F, 5, 1}},      
+    {"PLT",     1, NUMERIC, {FMT_F, 5, 1}},       
+    {"PIN",     2, NUMERIC, {FMT_F, 5, 1}},       
+    {"POUT",    2, NUMERIC, {FMT_F, 5, 1}},       
+    {"FGT",     1, NUMERIC, {FMT_F, 5, 3}},       
+    {"FLT",     1, NUMERIC, {FMT_F, 5, 3}},       
+    {"FIN",     2, NUMERIC, {FMT_F, 5, 3}},       
+    {"FOUT",    2, NUMERIC, {FMT_F, 5, 3}},       
+    {"N",       0, NUMERIC, {FMT_F, 7, 0}},       
+    {"NU",      0, NUMERIC, {FMT_F, 7, 0}},       
+    {"NMISS",   0, NUMERIC, {FMT_F, 7, 0}},       
+    {"NUMISS",  0, NUMERIC, {FMT_F, 7, 0}},       
+    {"FIRST",   0, ALPHA,   {-1, -1, -1}}, 
+    {"LAST",    0, ALPHA,   {-1, -1, -1}},
+    {NULL,      0, -1,      {-1, -1, -1}},
+    {"N",       0, NUMERIC, {FMT_F, 7, 0}},
+    {"NU",      0, NUMERIC, {FMT_F, 7, 0}},
+  };
+
+/* Output file, or NULL for the active file. */
+static struct file_handle *outfile;
+
+/* Missing value types. */
+enum
+  {
+    ITEMWISE,          /* Missing values item by item. */
+    COLUMNWISE         /* Missing values column by column. */
+  };
+
+/* ITEMWISE or COLUMNWISE. */
+static int missing;
+
+/* Aggregate variables. */
+static struct agr_var *agr_first, *agr_next;
+
+/* Aggregate dictionary. */
+static struct dictionary *agr_dict;
+
+/* Number of cases passed through aggregation. */
+static int case_count;
+
+/* Last values of the break variables. */
+static union value *prev_case;
+
+/* Buffers for use by the 10x transformation. */
+static flt64 *buf64_1xx;
+static struct ccase *buf_1xx;
+
+static void initialize_aggregate_info (void);
+
+/* Prototypes. */
+static int parse_aggregate_functions (void);
+static void free_aggregate_functions (void);
+static int aggregate_single_case (struct ccase *input, struct ccase *output);
+static int create_sysfile (void);
+
+static int agr_00x_trns_proc (struct trns_header *, struct ccase *);
+static void agr_00x_end_func (void);
+static int agr_10x_trns_proc (struct trns_header *, struct ccase *);
+static void agr_10x_trns_free (struct trns_header *);
+static void agr_10x_end_func (void);
+static int agr_11x_func (void);
+
+#if DEBUGGING
+static void debug_print (int flags);
+#endif
+\f
+/* Parsing. */
+
+/* Parses and executes the AGGREGATE procedure. */
+int
+cmd_aggregate (void)
+{
+  /* From sort.c. */
+  int parse_sort_variables (void);
+  
+  /* Have we seen these subcommands? */
+  unsigned seen = 0;
+
+  outfile = NULL;
+  missing = ITEMWISE;
+  v_sort = NULL;
+  prev_case = NULL;
+  
+  agr_dict = new_dictionary (1);
+  
+  lex_match_id ("AGGREGATE");
+
+  /* Read most of the subcommands. */
+  for (;;)
+    {
+      lex_match('/');
+      
+      if (lex_match_id ("OUTFILE"))
+       {
+         if (seen & 1)
+           {
+             free (v_sort);
+             free_dictionary (agr_dict);
+             msg (SE, _("OUTFILE specified multiple times."));
+             return CMD_FAILURE;
+           }
+         seen |= 1;
+             
+         lex_match ('=');
+         if (lex_match ('*'))
+           outfile = NULL;
+         else 
+           {
+             outfile = fh_parse_file_handle ();
+             if (outfile == NULL)
+               {
+                 free (v_sort);
+                 free_dictionary (agr_dict);
+                 return CMD_FAILURE;
+               }
+           }
+       }
+      else if (lex_match_id ("MISSING"))
+       {
+         lex_match ('=');
+         if (!lex_match_id ("COLUMNWISE"))
+           {
+             free (v_sort);
+             free_dictionary (agr_dict);
+             lex_error (_("while expecting COLUMNWISE"));
+             return CMD_FAILURE;
+           }
+         missing = COLUMNWISE;
+       }
+      else if (lex_match_id ("DOCUMENT"))
+       seen |= 2;
+      else if (lex_match_id ("PRESORTED"))
+       seen |= 4;
+      else if (lex_match_id ("BREAK"))
+       {
+         if (seen & 8)
+           {
+             free (v_sort);
+             free_dictionary (agr_dict);
+             msg (SE, _("BREAK specified multiple times."));
+             return CMD_FAILURE;
+           }
+         seen |= 8;
+
+         lex_match ('=');
+         if (!parse_sort_variables ())
+           {
+             free_dictionary (agr_dict);
+             return CMD_FAILURE;
+           }
+         
+         {
+           int i;
+           
+           for (i = 0; i < nv_sort; i++)
+             {
+               struct variable *v;
+             
+               v = dup_variable (agr_dict, v_sort[i], v_sort[i]->name);
+               assert (v != NULL);
+             }
+         }
+       }
+      else break;
+    }
+
+  /* Check for proper syntax. */
+  if (!(seen & 8))
+    msg (SW, _("BREAK subcommand not specified."));
+      
+  /* Read in the aggregate functions. */
+  if (!parse_aggregate_functions ())
+    {
+      free_aggregate_functions ();
+      free (v_sort);
+      return CMD_FAILURE;
+    }
+
+  /* Delete documents. */
+  if (!(seen & 2))
+    {
+      free (agr_dict->documents);
+      agr_dict->documents = NULL;
+      agr_dict->n_documents = 0;
+    }
+
+  /* Cancel SPLIT FILE. */
+  default_dict.n_splits = 0;
+  free (default_dict.splits);
+  default_dict.splits = NULL;
+  
+#if DEBUGGING
+  debug_print (seen);
+#endif
+
+  /* Initialize. */
+  case_count = 0;
+  initialize_aggregate_info ();
+
+  /* How to implement all this... There are three important variables:
+     whether output is going to the active file (0) or a separate file
+     (1); whether the input data is presorted (0) or needs sorting
+     (1); whether there is a temporary transformation (1) or not (0).
+     The eight cases are as follows:
+
+     000 (0): Pass it through an aggregate transformation that
+     modifies the data.
+
+     001 (1): Cancel the temporary transformation and handle as 000.
+
+     010 (2): Set up a SORT CASES and aggregate the output, writing
+     the results to the active file.
+     
+     011 (3): Cancel the temporary transformation and handle as 010.
+
+     100 (4): Pass it through an aggregate transformation that doesn't
+     modify the data but merely writes it to the output file.
+
+     101 (5): Handled as 100.
+
+     110 (6): Set up a SORT CASES and capture the output, aggregate
+     it, write it to the output file without modifying the active
+     file.
+
+     111 (7): Handled as 110. */
+  
+  {
+    unsigned type = 0;
+
+    if (outfile != NULL)
+      type |= 4;
+    if (nv_sort != 0 && (seen & 4) == 0)
+      type |= 2;
+    if (temporary)
+      type |= 1;
+
+    switch (type)
+      {
+      case 3:
+       cancel_temporary ();
+       /* fall through */
+      case 2:
+       sort_cases (0);
+       goto case0;
+         
+      case 1:
+       cancel_temporary ();
+       /* fall through */
+      case 0:
+      case0:
+       {
+         struct trns_header *t = xmalloc (sizeof *t);
+         t->proc = agr_00x_trns_proc;
+         t->free = NULL;
+         add_transformation (t);
+         
+         temporary = 2;
+         temp_dict = agr_dict;
+         temp_trns = n_trns;
+         
+         agr_dict = NULL;
+
+         procedure (NULL, NULL, agr_00x_end_func);
+         break;
+       }
+
+      case 4:
+      case 5:
+       {
+         if (!create_sysfile ())
+           goto lossage;
+         
+         {
+           struct trns_header *t = xmalloc (sizeof *t);
+           t->proc = agr_10x_trns_proc;
+           t->free = agr_10x_trns_free;
+           add_transformation (t);
+
+           procedure (NULL, NULL, agr_10x_end_func);
+         }
+         
+         break;
+       }
+         
+      case 6:
+      case 7:
+       sort_cases (1);
+       
+       if (!create_sysfile ())
+         goto lossage;
+       read_sort_output (agr_11x_func);
+       
+       {
+         struct ccase *save_temp_case = temp_case;
+         temp_case = NULL;
+         agr_11x_func ();
+         temp_case = save_temp_case;
+       }
+       
+       break;
+
+      default:
+       assert (0);
+      }
+  }
+  
+  free (buf64_1xx);
+  free (buf_1xx);
+  
+  /* Clean up. */
+  free (v_sort);
+  free_aggregate_functions ();
+  free (prev_case);
+  
+  return CMD_SUCCESS;
+
+lossage:
+  /* Clean up. */
+  free (v_sort);
+  free_aggregate_functions ();
+  free (prev_case);
+
+  return CMD_FAILURE;
+}
+
+/* Create a system file for use in aggregation to an external file,
+   and allocate temporary buffers for writing out cases. */
+static int
+create_sysfile (void)
+{
+  struct sfm_write_info w;
+  w.h = outfile;
+  w.dict = agr_dict;
+  w.compress = set_scompression;
+  if (!sfm_write_dictionary (&w))
+    {
+      free_aggregate_functions ();
+      free (v_sort);
+      free_dictionary (agr_dict);
+      return 0;
+    }
+    
+  buf64_1xx = xmalloc (sizeof *buf64_1xx * w.case_size);
+  buf_1xx = xmalloc (sizeof (struct ccase) + sizeof (union value) * (agr_dict->nval - 1));
+
+  return 1;
+}
+
+/* Parse all the aggregate functions. */
+static int
+parse_aggregate_functions (void)
+{
+  agr_first = agr_next = NULL;
+
+  /* Anticipate weighting for optimization later. */
+  update_weighting (&default_dict);
+  
+  /* Parse everything. */
+  for (;;)
+    {
+      char **dest;
+      char **dest_label;
+      int n_dest;
+
+      int include_missing;
+      struct agr_func *function;
+      int func_index;
+
+      union value arg[2];
+
+      struct variable **src;
+      int n_src;
+
+      int i;
+
+      dest = NULL;
+      dest_label = NULL;
+      n_dest = 0;
+      src = NULL;
+      n_src = 0;
+      arg[0].c = NULL;
+      arg[1].c = NULL;
+
+      /* Parse the list of target variables. */
+      while (!lex_match ('='))
+       {
+         int n_dest_prev = n_dest;
+         
+         if (!parse_DATA_LIST_vars (&dest, &n_dest, PV_APPEND | PV_SINGLE | PV_NO_SCRATCH))
+           goto lossage;
+
+         /* Assign empty labels. */
+         {
+           int j;
+
+           dest_label = xrealloc (dest_label, sizeof *dest_label * n_dest);
+           for (j = n_dest_prev; j < n_dest; j++)
+             dest_label[j] = NULL;
+         }
+         
+         if (token == T_STRING)
+           {
+             ds_truncate (&tokstr, 120);
+             dest_label[n_dest - 1] = xstrdup (ds_value (&tokstr));
+             lex_get ();
+           }
+       }
+
+      /* Get the name of the aggregation function. */
+      if (token != T_ID)
+       {
+         lex_error (_("expecting aggregation function"));
+         goto lossage;
+       }
+
+      include_missing = 0;
+      if (tokid[strlen (tokid) - 1] == '.')
+       {
+         include_missing = 1;
+         tokid[strlen (tokid) - 1] = 0;
+       }
+      
+      for (function = agr_func_tab; function->name; function++)
+       if (!strcmp (function->name, tokid))
+         break;
+      if (NULL == function->name)
+       {
+         msg (SE, _("Unknown aggregation function %s."), tokid);
+         goto lossage;
+       }
+      func_index = function - agr_func_tab;
+      lex_get ();
+
+      /* Check for leading lparen. */
+      if (!lex_match ('('))
+       {
+         if (func_index == N)
+           func_index = N_NO_VARS;
+         else if (func_index == NU)
+           func_index = NU_NO_VARS;
+         else
+           {
+             lex_error (_("expecting `('"));
+             goto lossage;
+           }
+       } else {
+         /* Parse list of source variables. */
+         {
+           int pv_opts = PV_NO_SCRATCH;
+
+           if (func_index == SUM || func_index == MEAN || func_index == SD)
+             pv_opts |= PV_NUMERIC;
+           else if (function->n_args)
+             pv_opts |= PV_SAME_TYPE;
+
+           if (!parse_variables (&default_dict, &src, &n_src, pv_opts))
+             goto lossage;
+         }
+
+         /* Parse function arguments, for those functions that
+            require arguments. */
+         if (function->n_args != 0)
+           for (i = 0; i < function->n_args; i++)
+             {
+               int type;
+           
+               lex_match (',');
+               if (token == T_STRING)
+                 {
+                   arg[i].c = xstrdup (ds_value (&tokstr));
+                   type = ALPHA;
+                 }
+               else if (token == T_NUM)
+                 {
+                   arg[i].f = tokval;
+                   type = NUMERIC;
+                 } else {
+                   msg (SE, _("Missing argument %d to %s."), i + 1, function->name);
+                   goto lossage;
+                 }
+           
+               lex_get ();
+
+               if (type != src[0]->type)
+                 {
+                   msg (SE, _("Arguments to %s must be of same type as "
+                              "source variables."),
+                        function->name);
+                   goto lossage;
+                 }
+             }
+
+         /* Trailing rparen. */
+         if (!lex_match(')'))
+           {
+             lex_error (_("expecting `)'"));
+             goto lossage;
+           }
+         
+         /* Now check that the number of source variables match the
+            number of target variables.  Do this here because if we
+            do it earlier then the user can get very misleading error
+            messages; i.e., `AGGREGATE x=SUM(y t).' will get this
+            error message when a proper message would be more like
+            `unknown variable t'. */
+         if (n_src != n_dest)
+           {
+             msg (SE, _("Number of source variables (%d) does not match "
+                        "number of target variables (%d)."),
+                  n_src, n_dest);
+             goto lossage;
+           }
+       }
+       
+      /* Finally add these to the linked list of aggregation
+         variables. */
+      for (i = 0; i < n_dest; i++)
+       {
+         struct agr_var *v = xmalloc (sizeof *v);
+
+         /* Add variable to chain. */
+         if (agr_first)
+           agr_next = agr_next->next = v;
+         else
+           agr_first = agr_next = v;
+         agr_next->next = NULL;
+         
+         /* Create the target variable in the aggregate
+             dictionary. */
+         {
+           struct variable *destvar;
+           
+           agr_next->function = func_index;
+
+           if (src)
+             {
+               int output_type;
+
+               agr_next->src = src[i];
+               
+               if (src[i]->type == ALPHA)
+                 {
+                   agr_next->function |= FSTRING;
+                   agr_next->string = xmalloc (src[i]->width);
+                 }
+               
+               if (default_dict.weight_index != -1)
+                 agr_next->function |= FWEIGHT;
+
+               if (agr_next->src->type == NUMERIC)
+                 output_type = NUMERIC;
+               else
+                 output_type = function->alpha_type;
+
+               if (function->alpha_type == ALPHA)
+                 destvar = dup_variable (agr_dict, agr_next->src, dest[i]);
+               else
+                 {
+                   destvar = create_variable (agr_dict, dest[i], output_type,
+                                              agr_next->src->width);
+                   if (output_type == NUMERIC)
+                     destvar->print = destvar->write = function->format;
+                   if (output_type == NUMERIC && default_dict.weight_index != -1
+                       && (func_index == N || func_index == N_NO_VARS
+                           || func_index == NU || func_index == NU_NO_VARS))
+                     {
+                       struct fmt_spec f = {FMT_F, 8, 2};
+                     
+                       destvar->print = destvar->write = f;
+                     }
+                 }
+             } else {
+               agr_next->src = NULL;
+               destvar = create_variable (agr_dict, dest[i], NUMERIC, 0);
+             }
+         
+           if (!destvar)
+             {
+               msg (SE, _("Variable name %s is not unique within the "
+                          "aggregate file dictionary, which contains "
+                          "the aggregate variables and the break "
+                          "variables."),
+                    dest[i]);
+               free (dest[i]);
+               goto lossage;
+             }
+
+           free (dest[i]);
+           if (dest_label[i])
+             {
+               destvar->label = dest_label[i];
+               dest_label[i] = NULL;
+             }
+           else if (function->alpha_type == ALPHA)
+             destvar->print = destvar->write = function->format;
+
+           agr_next->dest = destvar;
+         }
+         
+         agr_next->include_missing = include_missing;
+
+         if (agr_next->src != NULL)
+           {
+             int j;
+
+             if (agr_next->src->type == NUMERIC)
+               for (j = 0; j < function->n_args; j++)
+                 agr_next->arg[j].f = arg[j].f;
+             else
+               for (j = 0; j < function->n_args; j++)
+                 agr_next->arg[j].c = xstrdup (arg[j].c);
+           }
+       }
+      
+      if (src != NULL && src[0]->type == ALPHA)
+       for (i = 0; i < function->n_args; i++)
+         {
+           free (arg[i].c);
+           arg[i].c = NULL;
+         }
+
+      free (src);
+      free (dest);
+      free (dest_label);
+
+      if (!lex_match ('/'))
+       {
+         if (token == '.')
+           return 1;
+
+         lex_error ("expecting end of command");
+         return 0;
+       }
+      continue;
+      
+    lossage:
+      for (i = 0; i < n_dest; i++)
+       {
+         free (dest[i]);
+         free (dest_label[i]);
+       }
+      free (dest);
+      free (dest_label);
+      free (arg[0].c);
+      free (arg[1].c);
+      if (src && n_src && src[0]->type == ALPHA)
+       for (i = 0; i < function->n_args; i++)
+         {
+           free(arg[i].c);
+           arg[i].c = NULL;
+         }
+      free (src);
+       
+      return 0;
+    }
+}
+
+/* Frees all the state for the AGGREGATE procedure. */
+static void
+free_aggregate_functions (void)
+{
+  struct agr_var *iter, *next;
+
+  if (agr_dict)
+    free_dictionary (agr_dict);
+  for (iter = agr_first; iter; iter = next)
+    {
+      next = iter->next;
+
+      if (iter->function & FSTRING)
+       {
+         int n_args;
+         int i;
+
+         n_args = agr_func_tab[iter->function & FUNC].n_args;
+         for (i = 0; i < n_args; i++)
+           free (iter->arg[i].c);
+         free (iter->string);
+       }
+      free (iter);
+    }
+}
+\f
+/* Execution. */
+
+static void accumulate_aggregate_info (struct ccase *input);
+static void dump_aggregate_info (struct ccase *output);
+
+/* Processes a single case INPUT for aggregation.  If output is
+   warranted, it is written to case OUTPUT, which may be (but need not
+   be) an alias to INPUT.  Returns -1 when output is performed, -2
+   otherwise. */
+/* The code in this function has an eerie similarity to
+   vfm.c:SPLIT_FILE_procfunc()... */
+static int
+aggregate_single_case (struct ccase *input, struct ccase *output)
+{
+  /* The first case always begins a new break group.  We also need to
+     preserve the values of the case for later comparison. */
+  if (case_count++ == 0)
+    {
+      int n_elem = 0;
+      
+      {
+       int i;
+
+       for (i = 0; i < nv_sort; i++)
+         n_elem += v_sort[i]->nv;
+      }
+      
+      prev_case = xmalloc (sizeof *prev_case * n_elem);
+
+      /* Copy INPUT into prev_case. */
+      {
+       union value *iter = prev_case;
+       int i;
+
+       for (i = 0; i < nv_sort; i++)
+         {
+           struct variable *v = v_sort[i];
+           
+           if (v->type == NUMERIC)
+             (iter++)->f = input->data[v->fv].f;
+           else
+             {
+               memcpy (iter->s, input->data[v->fv].s, v->width);
+               iter += v->nv;
+             }
+         }
+      }
+           
+      accumulate_aggregate_info (input);
+       
+      return -2;
+    }
+      
+  /* Compare the value of each break variable to the values on the
+     previous case. */
+  {
+    union value *iter = prev_case;
+    int i;
+    
+    for (i = 0; i < nv_sort; i++)
+      {
+       struct variable *v = v_sort[i];
+      
+       switch (v->type)
+         {
+         case NUMERIC:
+           if (approx_ne (input->data[v->fv].f, iter->f))
+             goto not_equal;
+           iter++;
+           break;
+         case ALPHA:
+           if (memcmp (input->data[v->fv].s, iter->s, v->width))
+             goto not_equal;
+           iter += v->nv;
+           break;
+         default:
+           assert (0);
+         }
+      }
+  }
+
+  accumulate_aggregate_info (input);
+
+  return -2;
+  
+not_equal:
+  /* The values of the break variable are different from the values on
+     the previous case.  That means that it's time to dump aggregate
+     info. */
+  dump_aggregate_info (output);
+  initialize_aggregate_info ();
+  accumulate_aggregate_info (input);
+
+  /* Copy INPUT into prev_case. */
+  {
+    union value *iter = prev_case;
+    int i;
+
+    for (i = 0; i < nv_sort; i++)
+      {
+       struct variable *v = v_sort[i];
+           
+       if (v->type == NUMERIC)
+         (iter++)->f = input->data[v->fv].f;
+       else
+         {
+           memcpy (iter->s, input->data[v->fv].s, v->width);
+           iter += v->nv;
+         }
+      }
+  }
+  
+  return -1;
+}
+
+/* Accumulates aggregation data from the case INPUT. */
+static void 
+accumulate_aggregate_info (struct ccase *input)
+{
+  struct agr_var *iter;
+
+#define WEIGHT (input->data[default_dict.weight_index].f)
+
+  for (iter = agr_first; iter; iter = iter->next)
+    if (iter->src)
+      {
+       union value *v = &input->data[iter->src->fv];
+
+       if ((!iter->include_missing && is_missing (v, iter->src))
+           || (iter->include_missing && iter->src->type == NUMERIC
+               && v->f == SYSMIS))
+         {
+           switch (iter->function)
+             {
+             case NMISS | FWEIGHT:
+               iter->dbl[0] += WEIGHT;
+               break;
+             case NMISS:
+             case NUMISS:
+             case NUMISS | FWEIGHT:
+               iter->int1++;
+               break;
+             }
+           iter->missing = 1;
+           continue;
+         }
+       
+       /* This is horrible.  There are too many possibilities. */
+       switch (iter->function)
+         {
+         case SUM:
+         case SUM | FWEIGHT:
+           iter->dbl[0] += v->f;
+           break;
+         case MEAN:
+           iter->dbl[0] += v->f;
+           iter->int1++;
+           break;
+         case MEAN | FWEIGHT:
+           {
+             double w = WEIGHT;
+             iter->dbl[0] += v->f * w;
+             iter->dbl[1] += w;
+             break;
+           }
+         case SD:
+           iter->dbl[0] += v->f;
+           iter->dbl[1] += v->f * v->f;
+           iter->int1++;
+           break;
+         case SD | FWEIGHT:
+           {
+             double w = WEIGHT;
+             double product = v->f * w;
+             iter->dbl[0] += product;
+             iter->dbl[1] += product * v->f;
+             iter->dbl[2] += w;
+             break;
+           }
+         case MAX:
+         case MAX | FWEIGHT:
+           iter->dbl[0] = max (iter->dbl[0], v->f);
+           iter->int1 = 1;
+           break;
+         case MAX | FSTRING:
+         case MAX | FSTRING | FWEIGHT:
+           if (memcmp (iter->string, v->s, iter->src->width) < 0)
+             memcpy (iter->string, v->s, iter->src->width);
+           iter->int1 = 1;
+           break;
+         case MIN:
+         case MIN | FWEIGHT:
+           iter->dbl[0] = min (iter->dbl[0], v->f);
+           iter->int1 = 1;
+           break;
+         case MIN | FSTRING:
+         case MIN | FSTRING | FWEIGHT:
+           if (memcmp (iter->string, v->s, iter->src->width) > 0)
+             memcpy (iter->string, v->s, iter->src->width);
+           iter->int1 = 1;
+           break;
+         case FGT:
+         case PGT:
+           if (approx_gt (v->f, iter->arg[0].f))
+             iter->int1++;
+           iter->int2++;
+           break;
+         case FGT | FWEIGHT:
+         case PGT | FWEIGHT:
+           {
+             double w = WEIGHT;
+             if (approx_gt (v->f, iter->arg[0].f))
+               iter->dbl[0] += w;
+             iter->dbl[1] += w;
+             break;
+           }
+         case FGT | FSTRING:
+         case PGT | FSTRING:
+           if (memcmp (iter->arg[0].c, v->s, iter->src->width) < 0)
+             iter->int1++;
+           iter->int2++;
+           break;
+         case FGT | FSTRING | FWEIGHT:
+         case PGT | FSTRING | FWEIGHT:
+           {
+             double w = WEIGHT;
+             if (memcmp (iter->arg[0].c, v->s, iter->src->width) < 0)
+               iter->dbl[0] += w;
+             iter->dbl[1] += w;
+             break;
+           }
+         case FLT:
+         case PLT:
+           if (approx_lt (v->f, iter->arg[0].f))
+             iter->int1++;
+           iter->int2++;
+           break;
+         case FLT | FWEIGHT:
+         case PLT | FWEIGHT:
+           {
+             double w = WEIGHT;
+             if (approx_lt (v->f, iter->arg[0].f))
+               iter->dbl[0] += w;
+             iter->dbl[1] += w;
+             break;
+           }
+         case FLT | FSTRING:
+         case PLT | FSTRING:
+           if (memcmp (iter->arg[0].c, v->s, iter->src->width) > 0)
+             iter->int1++;
+           iter->int2++;
+           break;
+         case FLT | FSTRING | FWEIGHT:
+         case PLT | FSTRING | FWEIGHT:
+           {
+             double w = WEIGHT;
+             if (memcmp (iter->arg[0].c, v->s, iter->src->width) > 0)
+               iter->dbl[0] += w;
+             iter->dbl[1] += w;
+             break;
+           }
+         case FIN:
+         case PIN:
+           if (approx_in_range (v->f, iter->arg[0].f, iter->arg[1].f))
+             iter->int1++;
+           iter->int2++;
+           break;
+         case FIN | FWEIGHT:
+         case PIN | FWEIGHT:
+           {
+             double w = WEIGHT;
+             if (approx_in_range (v->f, iter->arg[0].f, iter->arg[1].f))
+               iter->dbl[0] += w;
+             iter->dbl[1] += w;
+             break;
+           }
+         case FIN | FSTRING:
+         case PIN | FSTRING:
+           if (memcmp (iter->arg[0].c, v->s, iter->src->width) <= 0
+               && memcmp (iter->arg[1].c, v->s, iter->src->width) >= 0)
+             iter->int1++;
+           iter->int2++;
+           break;
+         case FIN | FSTRING | FWEIGHT:
+         case PIN | FSTRING | FWEIGHT:
+           {
+             double w = WEIGHT;
+             if (memcmp (iter->arg[0].c, v->s, iter->src->width) <= 0
+                 && memcmp (iter->arg[1].c, v->s, iter->src->width) >= 0)
+               iter->dbl[0] += w;
+             iter->dbl[1] += w;
+             break;
+           }
+         case FOUT:
+         case POUT:
+           if (!approx_in_range (v->f, iter->arg[0].f, iter->arg[1].f))
+             iter->int1++;
+           iter->int2++;
+           break;
+         case FOUT | FWEIGHT:
+         case POUT | FWEIGHT:
+           {
+             double w = WEIGHT;
+             if (!approx_in_range (v->f, iter->arg[0].f, iter->arg[1].f))
+               iter->dbl[0] += w;
+             iter->dbl[1] += w;
+             break;
+           }
+         case FOUT | FSTRING:
+         case POUT | FSTRING:
+           if (memcmp (iter->arg[0].c, v->s, iter->src->width) > 0
+               && memcmp (iter->arg[1].c, v->s, iter->src->width) < 0)
+             iter->int1++;
+           iter->int2++;
+           break;
+         case FOUT | FSTRING | FWEIGHT:
+         case POUT | FSTRING | FWEIGHT:
+           {
+             double w = WEIGHT;
+             if (memcmp (iter->arg[0].c, v->s, iter->src->width) > 0
+                 && memcmp (iter->arg[1].c, v->s, iter->src->width) < 0)
+               iter->dbl[0] += w;
+             iter->dbl[1] += w;
+             break;
+           }
+         case N | FWEIGHT:
+           iter->dbl[0] += WEIGHT;
+           break;
+         case N:
+         case NU:
+         case NU | FWEIGHT:
+           iter->int1++;
+           break;
+         case FIRST:
+         case FIRST | FWEIGHT:
+           if (iter->int1 == 0)
+             {
+               iter->dbl[0] = v->f;
+               iter->int1 = 1;
+             }
+           break;
+         case FIRST | FSTRING:
+         case FIRST | FSTRING | FWEIGHT:
+           if (iter->int1 == 0)
+             {
+               memcpy (iter->string, v->s, iter->src->width);
+               iter->int1 = 1;
+             }
+           break;
+         case LAST:
+         case LAST | FWEIGHT:
+           iter->dbl[0] = v->f;
+           iter->int1 = 1;
+           break;
+         case LAST | FSTRING:
+         case LAST | FSTRING | FWEIGHT:
+           memcpy (iter->string, v->s, iter->src->width);
+           iter->int1 = 1;
+           break;
+         default:
+           assert (0);
+         }
+    } else {
+      switch (iter->function)
+       {
+       case N_NO_VARS | FWEIGHT:
+         iter->dbl[0] += WEIGHT;
+         break;
+       case N_NO_VARS:
+       case NU_NO_VARS:
+       case NU_NO_VARS | FWEIGHT:
+         iter->int1++;
+         break;
+       default:
+         assert (0);
+       }
+    }
+}
+
+/* We've come to a record that differs from the previous in one or
+   more of the break variables.  Make an output record from the
+   accumulated statistics in the OUTPUT case. */
+static void 
+dump_aggregate_info (struct ccase *output)
+{
+  debug_printf (("(dumping "));
+  
+  {
+    int n_elem = 0;
+    
+    {
+      int i;
+
+      for (i = 0; i < nv_sort; i++)
+       n_elem += v_sort[i]->nv;
+    }
+    debug_printf (("n_elem=%d:", n_elem));
+    memcpy (output->data, prev_case, sizeof (union value) * n_elem);
+  }
+  
+  {
+    struct agr_var *i;
+  
+    for (i = agr_first; i; i = i->next)
+      {
+       union value *v = &output->data[i->dest->fv];
+
+       debug_printf ((" %d,%d", i->dest->fv, i->dest->nv));
+
+       if (missing == COLUMNWISE && i->missing != 0
+           && (i->function & FUNC) != N && (i->function & FUNC) != NU
+           && (i->function & FUNC) != NMISS && (i->function & FUNC) != NUMISS)
+         {
+           if (i->function & FSTRING)
+             memset (v->s, ' ', i->dest->width);
+           else
+             v->f = SYSMIS;
+           continue;
+         }
+       
+       switch (i->function)
+         {
+         case SUM:
+         case SUM | FWEIGHT:
+           v->f = i->dbl[0];
+           break;
+         case MEAN:
+           v->f = i->int1 ? i->dbl[0] / i->int1 : SYSMIS;
+           break;
+         case MEAN | FWEIGHT:
+           v->f = i->dbl[1] != 0.0 ? i->dbl[0] / i->dbl[1] : SYSMIS;
+           break;
+         case SD:
+           v->f = ((i->int1 > 1)
+                   ? calc_stddev (calc_variance (i->dbl, i->int1))
+                   : SYSMIS);
+           break;
+         case SD | FWEIGHT:
+           v->f = ((i->dbl[2] > 1.0)
+                   ? calc_stddev (calc_variance (i->dbl, i->dbl[2]))
+                   : SYSMIS);
+           break;
+         case MAX:
+         case MAX | FWEIGHT:
+         case MIN:
+         case MIN | FWEIGHT:
+           v->f = i->int1 ? i->dbl[0] : SYSMIS;
+           break;
+         case MAX | FSTRING:
+         case MAX | FSTRING | FWEIGHT:
+         case MIN | FSTRING:
+         case MIN | FSTRING | FWEIGHT:
+           if (i->int1)
+             memcpy (v->s, i->string, i->dest->width);
+           else
+             memset (v->s, ' ', i->dest->width);
+           break;
+         case FGT:
+         case FGT | FSTRING:
+         case FLT:
+         case FLT | FSTRING:
+         case FIN:
+         case FIN | FSTRING:
+         case FOUT:
+         case FOUT | FSTRING:
+           v->f = i->int2 ? (double) i->int1 / (double) i->int2 : SYSMIS;
+           break;
+         case FGT | FWEIGHT:
+         case FGT | FSTRING | FWEIGHT:
+         case FLT | FWEIGHT:
+         case FLT | FSTRING | FWEIGHT:
+         case FIN | FWEIGHT:
+         case FIN | FSTRING | FWEIGHT:
+         case FOUT | FWEIGHT:
+         case FOUT | FSTRING | FWEIGHT:
+           v->f = i->dbl[1] ? i->dbl[0] / i->dbl[1] : SYSMIS;
+           break;
+         case PGT:
+         case PGT | FSTRING:
+         case PLT:
+         case PLT | FSTRING:
+         case PIN:
+         case PIN | FSTRING:
+         case POUT:
+         case POUT | FSTRING:
+           v->f = (i->int2
+                   ? (double) i->int1 / (double) i->int2 * 100.0
+                   : SYSMIS);
+           break;
+         case PGT | FWEIGHT:
+         case PGT | FSTRING | FWEIGHT:
+         case PLT | FWEIGHT:
+         case PLT | FSTRING | FWEIGHT:
+         case PIN | FWEIGHT:
+         case PIN | FSTRING | FWEIGHT:
+         case POUT | FWEIGHT:
+         case POUT | FSTRING | FWEIGHT:
+           v->f = i->dbl[1] ? i->dbl[0] / i->dbl[1] * 100.0 : SYSMIS;
+           break;
+         case N | FWEIGHT:
+           v->f = i->dbl[0];
+         case N:
+         case NU:
+         case NU | FWEIGHT:
+           v->f = i->int1;
+           break;
+         case FIRST:
+         case FIRST | FWEIGHT:
+         case LAST:
+         case LAST | FWEIGHT:
+           v->f = i->int1 ? i->dbl[0] : SYSMIS;
+           break;
+         case FIRST | FSTRING:
+         case FIRST | FSTRING | FWEIGHT:
+         case LAST | FSTRING:
+         case LAST | FSTRING | FWEIGHT:
+           if (i->int1)
+             memcpy (v->s, i->string, i->dest->width);
+           else
+             memset (v->s, ' ', i->dest->width);
+           break;
+         case N_NO_VARS | FWEIGHT:
+           v->f = i->dbl[0];
+           break;
+         case N_NO_VARS:
+         case NU_NO_VARS:
+         case NU_NO_VARS | FWEIGHT:
+           v->f = i->int1;
+           break;
+         case NMISS | FWEIGHT:
+           v->f = i->dbl[0];
+           break;
+         case NMISS:
+         case NUMISS:
+         case NUMISS | FWEIGHT:
+           v->f = i->int1;
+           break;
+         default:
+           assert (0);
+         }
+      }
+  }
+  debug_printf ((") "));
+}
+
+/* Resets the state for all the aggregate functions. */
+static void
+initialize_aggregate_info (void)
+{
+  struct agr_var *iter;
+
+  for (iter = agr_first; iter; iter = iter->next)
+    {
+      int plain_function = iter->function & ~FWEIGHT;
+
+      iter->missing = 0;
+      switch (plain_function)
+       {
+       case MIN:
+         iter->dbl[0] = DBL_MAX;
+         break;
+       case MIN | FSTRING:
+         memset (iter->string, 255, iter->src->width);
+         break;
+       case MAX:
+         iter->dbl[0] = -DBL_MAX;
+         break;
+       case MAX | FSTRING:
+         memset (iter->string, 0, iter->src->width);
+         break;
+       default:
+         iter->dbl[0] = iter->dbl[1] = iter->dbl[2] = 0.0;
+         iter->int1 = iter->int2 = 0;
+         break;
+       }
+    }
+}
+\f
+/* Aggregate each case as it comes through.  Cases which aren't needed
+   are dropped. */
+static int
+agr_00x_trns_proc (struct trns_header *h unused, struct ccase *c)
+{
+  int code = aggregate_single_case (c, compaction_case);
+  debug_printf (("%d ", code));
+  return code;
+}
+
+/* Output the last aggregate case.  It's okay to call the vfm_sink's
+   write() method here because end_func is called so soon after all
+   the cases have been output; very little has been cleaned up at this
+   point. */
+static void
+agr_00x_end_func (void)
+{
+  /* Ensure that info for the last break group gets written to the
+     active file. */
+  dump_aggregate_info (compaction_case);
+  vfm_sink_info.ncases++;
+  vfm_sink->write ();
+}
+
+/* Transform the aggregate case buf_1xx, in internal format, to system
+   file format, in buf64_1xx, and write the resultant case to the
+   system file. */
+static void
+write_case_to_sfm (void)
+{
+  flt64 *p = buf64_1xx;
+  int i;
+
+  for (i = 0; i < agr_dict->nvar; i++)
+    {
+      struct variable *v = agr_dict->var[i];
+      
+      if (v->type == NUMERIC)
+       {
+         double src = buf_1xx->data[v->fv].f;
+         if (src == SYSMIS)
+           *p++ = -FLT64_MAX;
+         else
+           *p++ = src;
+       }
+      else
+       {
+         memcpy (p, buf_1xx->data[v->fv].s, v->width);
+         memset (&((char *) p)[v->width], ' ',
+                 REM_RND_UP (v->width, sizeof (flt64)));
+         p += DIV_RND_UP (v->width, sizeof (flt64));
+       }
+    }
+
+  sfm_write_case (outfile, buf64_1xx, p - buf64_1xx);
+}
+
+/* Aggregate the current case and output it if we passed a
+   breakpoint. */
+static int
+agr_10x_trns_proc (struct trns_header *h unused, struct ccase *c)
+{
+  int code = aggregate_single_case (c, buf_1xx);
+
+  assert (code == -2 || code == -1);
+  if (code == -1)
+    write_case_to_sfm ();
+  return -1;
+}
+
+/* Close the system file now that we're done with it.  */
+static void
+agr_10x_trns_free (struct trns_header *h unused)
+{
+  fh_close_handle (outfile);
+}
+
+/* Ensure that info for the last break group gets written to the
+   system file. */
+static void
+agr_10x_end_func (void)
+{
+  dump_aggregate_info (buf_1xx);
+  write_case_to_sfm ();
+}
+
+/* When called with temp_case non-NULL (the normal case), runs the
+   case through the aggregater and outputs it to the system file if
+   appropriate.  If temp_case is NULL, finishes up writing the last
+   case if necessary. */
+static int
+agr_11x_func (void)
+{
+  if (temp_case != NULL)
+    {
+      int code = aggregate_single_case (temp_case, buf_1xx);
+      
+      assert (code == -2 || code == -1);
+      if (code == -1)
+       write_case_to_sfm ();
+    }
+  else
+    {
+      if (case_count)
+       {
+         dump_aggregate_info (buf_1xx);
+         write_case_to_sfm ();
+       }
+      fh_close_handle (outfile);
+    }
+  return 1;
+}
+\f
+/* Debugging. */
+#if DEBUGGING
+/* Print out useful debugging information. */
+static void
+debug_print (int flags)
+{
+  printf ("AGGREGATE\n /OUTFILE=%s\n",
+         outfile ? fh_handle_filename (outfile) : "*");
+
+  if (missing == COLUMNWISE)
+    puts (" /MISSING=COLUMNWISE");
+
+  if (flags & 2)
+    puts (" /DOCUMENT");
+  if (flags & 4)
+    puts (" /PRESORTED");
+  
+  {
+    int i;
+
+    printf (" /BREAK=");
+    for (i = 0; i < nv_sort; i++)
+      printf ("%s(%c) ", v_sort[i]->name,
+             v_sort[i]->p.srt.order == SRT_ASCEND ? 'A' : 'D');
+    putc ('\n', stdout);
+  }
+  
+  {
+    struct agr_var *iter;
+    
+    for (iter = agr_first; iter; iter = iter->next)
+      {
+       struct agr_func *f = &agr_func_tab[iter->function & FUNC];
+       
+       printf (" /%s", iter->dest->name);
+       if (iter->dest->label)
+         printf ("'%s'", iter->dest->label);
+       printf ("=%s(%s", f->name, iter->src->name);
+       if (f->n_args)
+         {
+           int i;
+           
+           for (i = 0; i < f->n_args; i++)
+             {
+               putc (',', stdout);
+               if (iter->src->type == NUMERIC)
+                 printf ("%g", iter->arg[i].f);
+               else
+                 printf ("%.*s", iter->src->width, iter->arg[i].c);
+             }
+         }
+       printf (")\n");
+      }
+  }
+}
+
+#endif /* DEBUGGING */
diff --git a/src/alloc.c b/src/alloc.c
new file mode 100644 (file)
index 0000000..763dab3
--- /dev/null
@@ -0,0 +1,122 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "str.h"
+
+static void out_of_memory (void);
+\f
+/* Public functions. */
+
+/* Allocates a block of SIZE bytes and returns it.
+   If SIZE is 0, returns a null pointer.
+   Aborts if unsuccessful. */
+void *
+xmalloc (size_t size)
+{
+  void *vp;
+  if (size == 0)
+    return NULL;
+
+  vp = malloc (size);
+  if (!vp)
+    out_of_memory ();
+
+  return vp;
+}
+
+/* Allocates a block of SIZE bytes, fill it with all-bits-0, and
+   returns it.
+   If SIZE is 0, returns a null pointer.
+   Aborts if unsuccessful. */
+void *
+xcalloc (size_t size)
+{
+  void *vp = xmalloc (size);
+  memset (vp, 0, size);
+  return vp;
+}
+
+/* If SIZE is 0, then block PTR is freed and a null pointer is
+   returned.
+   Otherwise, if PTR is a null pointer, then a new block is allocated
+   and returned.
+   Otherwise, block PTR is reallocated to be SIZE bytes in size and
+   the new location of the block is returned.
+   Aborts if unsuccessful. */
+void *
+xrealloc (void *ptr, size_t size)
+{
+  void *vp;
+  if (!size)
+    {
+      if (ptr)
+       free (ptr);
+
+      return NULL;
+    }
+
+  if (ptr)
+    vp = realloc (ptr, size);
+  else
+    vp = malloc (size);
+
+  if (!vp)
+    out_of_memory ();
+
+  return vp;
+}
+
+/* Makes a copy of string S in malloc()'d memory and returns the copy.
+   S must not be a null pointer. */
+char *
+xstrdup (const char *s)
+{
+  size_t size;
+  char *t;
+
+  assert (s != NULL);
+
+  size = strlen (s) + 1;
+
+  t = malloc (size);
+  if (!t)
+    out_of_memory ();
+
+  memcpy (t, s, size);
+  return t;
+}
+\f
+/* Private functions. */
+
+/* Report an out-of-memory condition and abort execution. */
+static void
+out_of_memory (void)
+{
+#if __CHECKER__
+  fprintf (stderr, "Out of memory: inducing segfault\n");
+  *((int *) 0) = 0;
+#else
+  fprintf (stderr, "virtual memory exhausted\n");
+  exit (EXIT_FAILURE);
+#endif
+}
diff --git a/src/alloc.h b/src/alloc.h
new file mode 100644 (file)
index 0000000..50e7ab1
--- /dev/null
@@ -0,0 +1,31 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !alloc_h
+#define alloc_h 1
+
+#include <stddef.h>
+
+/* Functions. */
+void *xmalloc (size_t size);
+void *xcalloc (size_t size);
+void *xrealloc (void *ptr, size_t size);
+char *xstrdup (const char *s);
+
+#endif /* alloc.h */
diff --git a/src/apply-dict.c b/src/apply-dict.c
new file mode 100644 (file)
index 0000000..f467453
--- /dev/null
@@ -0,0 +1,184 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include "avl.h"
+#include "command.h"
+#include "error.h"
+#include "file-handle.h"
+#include "lexer.h"
+#include "sfm.h"
+#include "str.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* Parses and executes APPLY DICTIONARY. */
+int
+cmd_apply_dictionary (void)
+{
+  struct file_handle *handle;
+  struct dictionary *dict;
+
+  int n_matched = 0;
+
+  int i;
+  
+  lex_match_id ("APPLY");
+  lex_match_id ("DICTIONARY");
+  
+  lex_match_id ("FROM");
+  lex_match ('=');
+  handle = fh_parse_file_handle ();
+  if (!handle)
+    return CMD_FAILURE;
+
+  dict = sfm_read_dictionary (handle, NULL);
+  if (dict == NULL)
+    return CMD_FAILURE;
+
+  for (i = 0; i < dict->nvar; i++)
+    {
+      struct variable *s = dict->var[i];
+      struct variable *t = find_variable (s->name);
+      if (t == NULL)
+       continue;
+
+      n_matched++;
+      if (s->type != t->type)
+       {
+         msg (SW, _("Variable %s is %s in target file, but %s in "
+                    "source file."),
+              s->name,
+              t->type == ALPHA ? _("string") : _("numeric"),
+              s->type == ALPHA ? _("string") : _("numeric"));
+         continue;
+       }
+
+      if (s->label && strcspn (s->label, " ") != strlen (s->label))
+       {
+         free (t->label);
+         t->label = s->label;
+         s->label = NULL;
+       }
+
+      if (s->val_lab && t->width > MAX_SHORT_STRING)
+       msg (SW, _("Cannot add value labels from source file to "
+                  "long string variable %s."),
+            s->name);
+      else if (s->val_lab)
+       {
+         if (t->width < s->width)
+           {
+             avl_traverser iter;
+             struct value_label *lab;
+
+             avl_traverser_init (iter);
+             while ((lab = avl_traverse (s->val_lab, &iter)) != NULL)
+               {
+                 int j;
+
+                 /* If the truncated characters aren't all blanks
+                    anyway, then don't apply the value labels. */
+                 for (j = t->width; j < s->width; j++)
+                   if (lab->v.s[j] != ' ')
+                     goto skip_value_labels;
+               }
+           }
+         else
+           {
+             /* Fortunately, we follow the convention that all value
+                label values are right-padded with spaces, so it is
+                unnecessary to bother padding values here. */
+           }
+         
+         avl_destroy (t->val_lab, free_val_lab);
+         t->val_lab = s->val_lab;
+         s->val_lab = NULL;
+       }
+    skip_value_labels: ;
+
+      if (s->miss_type != MISSING_NONE && t->width > MAX_SHORT_STRING)
+       msg (SW, _("Cannot apply missing values from source file to "
+                  "long string variable %s."),
+            s->name);
+      else if (s->miss_type != MISSING_NONE)
+       {
+         if (t->width < s->width)
+           {
+             static const int miss_count[MISSING_COUNT] = 
+               {
+                 0, 1, 2, 3, 2, 1, 1, 3, 2, 2,
+               };
+
+             int j, k;
+             
+             for (j = 0; j < miss_count[s->miss_type]; j++)
+               for (k = t->width; k < s->width; k++)
+                 if (s->missing[j].s[k] != ' ')
+                   goto skip_missing_values;
+           }
+
+         t->miss_type = s->miss_type;
+         memcpy (t->missing, s->missing, sizeof s->missing);
+       }
+
+      if (s->type == NUMERIC)
+       {
+         t->print = s->print;
+         t->write = s->write;
+       }
+    }
+
+  if (!n_matched)
+    msg (SW, _("No matching variables found between the source "
+              "and target files."));
+      
+  /* Weighting. */
+  {
+    const int tfw = find_variable (default_dict.weight_var) != 0;
+    const int sfw = dict->weight_var[0] != 0;
+    struct variable *w;
+
+    switch (10 * tfw + sfw)
+      {
+      case 10:
+       /* The working file retains its weighting variable. */
+       break;
+
+      case 00:
+      case 01:
+       /* Fall through to case 11. */
+
+      case 11:
+       w = find_variable (dict->weight_var);
+       if (w)
+         strcpy (default_dict.weight_var, dict->weight_var);
+       break;
+      }
+  }
+ skip_missing_values: ;
+  
+  sfm_maybe_close (handle);
+
+  return lex_end_of_command ();
+}
diff --git a/src/approx.h b/src/approx.h
new file mode 100644 (file)
index 0000000..8dda9d2
--- /dev/null
@@ -0,0 +1,59 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !approx_h
+#define approx_h 1
+
+#include <float.h>
+#include <math.h>
+
+/* Minimum difference to consider values to be distinct. */
+#define EPSILON (DBL_EPSILON*10)
+
+/* The boundary at EPSILON is considered to be equal. */
+/* Possible modification: insert frexp() into all these expressions. */
+
+#define approx_eq(A, B)                                \
+       (fabs((A)-(B))<=EPSILON)
+
+#define approx_ne(A, B)                                \
+       (fabs((A)-(B))>EPSILON)
+
+#define approx_ge(A, B)                                \
+       ((A) >= (B)-EPSILON)
+
+#define approx_gt(A, B)                                \
+       ((A) > (B)+EPSILON)
+
+#define approx_le(A, B)                                \
+       ((A) <= (B)+EPSILON)
+
+#define approx_lt(A, B)                                \
+       ((A) < (B)-EPSILON)
+
+#define approx_floor(x)                                \
+       (floor((x)+EPSILON))
+
+#define approx_in_range(V, L, H)                       \
+       (((V) >= (L)-EPSILON) && ((V) <= (H)+EPSILON))
+
+#define approx_compare(A, B)                                   \
+       (approx_gt(A,B) ? 1 : (approx_lt(A,B) ? -1 : 0))
+
+#endif /* !approx_h */
diff --git a/src/ascii.c b/src/ascii.c
new file mode 100644 (file)
index 0000000..c6f48f0
--- /dev/null
@@ -0,0 +1,1631 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "error.h"
+#include "filename.h"
+#include "main.h"
+#include "misc.h"
+#include "output.h"
+#include "pool.h"
+#include "version.h"
+
+/* ASCII driver options: (defaults listed first)
+
+   output-file="pspp.list"
+   char-set=ascii|latin1
+   form-feed-string="\f"        Written as a formfeed.
+   newline-string=default|"\r\n"|"\n"   
+                                Written as a newline.
+   paginate=on|off              Formfeeds are desired?
+   tab-width=8                  Width of a tab; 0 to not use tabs.
+   init=""                      Written at beginning of output.
+   done=""                      Written at end of output.
+   
+   headers=on|off               Put headers at top of page?
+   length=66
+   width=130
+   lpi=6                        Only used to determine font size.
+   cpi=10                       
+
+   left-margin=0
+   right-margin=0
+   top-margin=2
+   bottom-margin=2
+
+   box[x]="strng"               Sets box character X (X in base 4: 0-3333).
+   italic-on=overstrike|"strng" Turns on italic (underline).
+   italic-off=""|"strng"        Turns off italic; ignored for overstrike.
+   bold-on=overstrike|"strng"   Turns on bold.
+   bold-off=""|"strng"          Turns off bold; ignored for overstrike.
+   bold-italic-on=overstrike|"strng" Turns on bold-italic.
+   bold-italic-off=""|"strng"   Turns off bold-italic; ignored for overstrike.
+   overstrike-style=single|line Can we print a whole line then BS over it, or
+   must we go char by char, as on a terminal?
+   carriage-return-style=bs|cr  Must we return the carriage with a sequence of
+   BSes, or will a single CR do it?
+ */
+
+/* Disable messages by failed range checks. */
+/*#define SUPPRESS_WARNINGS 1 */
+
+/* Character set. */
+enum
+  {
+    CHS_ASCII,                 /* 7-bit ASCII */
+    CHS_LATIN1                 /* Latin 1; not really supported at the moment */
+  };
+
+/* Overstrike style. */
+enum
+  {
+    OVS_SINGLE,                        /* Overstrike each character: "a\b_b\b_c\b_" */
+    OVS_LINE                   /* Overstrike lines: "abc\b\b\b___" (or if
+                                  newline is "\r\n", then "abc\r___").  Easier
+                                  on the printer, doesn't work on a tty. */
+  };
+
+/* Basic output strings. */
+enum
+  {
+    OPS_INIT,                  /* Document initialization string. */
+    OPS_DONE,                  /* Document uninit string. */
+    OPS_FORMFEED,              /* Formfeed string. */
+    OPS_NEWLINE,               /* Newline string. */
+
+    OPS_COUNT                  /* Number of output strings. */
+  };
+
+/* Line styles bit shifts. */
+enum
+  {
+    LNS_TOP = 0,
+    LNS_LEFT = 2,
+    LNS_BOTTOM = 4,
+    LNS_RIGHT = 6,
+
+    LNS_COUNT = 256
+  };
+
+/* Carriage return style. */
+enum
+  {
+    CRS_BS,                    /* Multiple backspaces. */
+    CRS_CR                     /* Single carriage return. */
+  };
+
+/* Assembles a byte from four taystes. */
+#define TAYSTE2BYTE(T, L, B, R)                        \
+       (((T) << LNS_TOP)                       \
+        | ((L) << LNS_LEFT)                    \
+        | ((B) << LNS_BOTTOM)                  \
+        | ((R) << LNS_RIGHT))
+
+/* Extract tayste with shift value S from byte B. */
+#define BYTE2TAYSTE(B, S)                      \
+       (((B) >> (S)) & 3)
+
+/* Font style; take one of the first group |'d with one of the second group. */
+enum
+  {
+    FSTY_ON = 000,             /* Turn font on. */
+    FSTY_OFF = 001,            /* Turn font off. */
+
+    FSTY_ITALIC = 0,           /* Italic font. */
+    FSTY_BOLD = 2,             /* Bold font. */
+    FSTY_BOLD_ITALIC = 4,      /* Bold-italic font. */
+
+    FSTY_COUNT = 6             /* Number of font styles. */
+  };
+
+/* ASCII output driver extension record. */
+struct ascii_driver_ext
+  {
+    /* User parameters. */
+    int char_set;              /* CHS_ASCII/CHS_LATIN1; no-op right now. */
+    int headers;               /* 1=print headers at top of page. */
+    int page_length;           /* Page length in lines. */
+    int page_width;            /* Page width in characters. */
+    int lpi;                   /* Lines per inch. */
+    int cpi;                   /* Characters per inch. */
+    int left_margin;           /* Left margin in characters. */
+    int right_margin;          /* Right margin in characters. */
+    int top_margin;            /* Top margin in lines. */
+    int bottom_margin;         /* Bottom margin in lines. */
+    int paginate;              /* 1=insert formfeeds. */
+    int tab_width;             /* Width of a tab; 0 not to use tabs. */
+    struct len_string ops[OPS_COUNT]; /* Basic output strings. */
+    struct len_string box[LNS_COUNT]; /* Line & box drawing characters. */
+    struct len_string fonts[FSTY_COUNT]; /* Font styles; NULL=overstrike. */
+    int overstrike_style;      /* OVS_SINGLE or OVS_LINE. */
+    int carriage_return_style; /* Carriage return style. */
+
+    /* Internal state. */
+    struct file_ext file;      /* Output file. */
+    int page_number;           /* Current page number. */
+    unsigned short *page;      /* Page content. */
+    int page_size;             /* Number of bytes allocated for page, attr. */
+    int *line_len;             /* Length of each line in page, attr. */
+    int line_len_size;         /* Number of ints allocated for line_len. */
+    int w, l;                  /* Actual width & length w/o margins, etc. */
+    int n_output;              /* Number of lines output so far. */
+    int cur_font;              /* Current font by OUTP_F_*. */
+#if GLOBAL_DEBUGGING
+    int debug;                 /* Set by som_text_draw(). */
+#endif
+  };
+
+static struct pool *ascii_pool;
+
+static int postopen (struct file_ext *);
+static int preclose (struct file_ext *);
+
+int
+ascii_open_global (struct outp_class *this unused)
+{
+  ascii_pool = pool_create ();
+  return 1;
+}
+
+int
+ascii_close_global (struct outp_class *this unused)
+{
+  pool_destroy (ascii_pool);
+  return 1;
+}
+
+int *
+ascii_font_sizes (struct outp_class *this unused, int *n_valid_sizes)
+{
+  static int valid_sizes[] = {12, 12, 0, 0};
+
+  assert (n_valid_sizes);
+  *n_valid_sizes = 1;
+  return valid_sizes;
+}
+
+int
+ascii_preopen_driver (struct outp_driver *this)
+{
+  struct ascii_driver_ext *x;
+  int i;
+  
+  assert (this->driver_open == 0);
+  msg (VM (1), _("ASCII driver initializing as `%s'..."), this->name);
+  this->ext = x = xmalloc (sizeof (struct ascii_driver_ext));
+  x->char_set = CHS_ASCII;
+  x->headers = 1;
+  x->page_length = 66;
+  x->page_width = 79;
+  x->lpi = 6;
+  x->cpi = 10;
+  x->left_margin = 0;
+  x->right_margin = 0;
+  x->top_margin = 2;
+  x->bottom_margin = 2;
+  x->paginate = 1;
+  x->tab_width = 8;
+  for (i = 0; i < OPS_COUNT; i++)
+    ls_null (&x->ops[i]);
+  for (i = 0; i < LNS_COUNT; i++)
+    ls_null (&x->box[i]);
+  for (i = 0; i < FSTY_COUNT; i++)
+    ls_null (&x->fonts[i]);
+  x->overstrike_style = OVS_SINGLE;
+  x->carriage_return_style = CRS_BS;
+  x->file.filename = NULL;
+  x->file.mode = "wb";
+  x->file.file = NULL;
+  x->file.sequence_no = &x->page_number;
+  x->file.param = x;
+  x->file.postopen = postopen;
+  x->file.preclose = preclose;
+  x->page_number = 0;
+  x->page = NULL;
+  x->page_size = 0;
+  x->line_len = NULL;
+  x->line_len_size = 0;
+  x->n_output = 0;
+  x->cur_font = OUTP_F_R;
+#if GLOBAL_DEBUGGING
+  x->debug = 0;
+#endif
+  return 1;
+}
+
+int
+ascii_postopen_driver (struct outp_driver *this)
+{
+  struct ascii_driver_ext *x = this->ext;
+  
+  assert (this->driver_open == 0);
+  
+  if (NULL == x->file.filename)
+    x->file.filename = xstrdup ("pspp.list");
+  
+  x->w = x->page_width - x->left_margin - x->right_margin;
+  x->l = (x->page_length - (x->headers ? 3 : 0) - x->top_margin
+         - x->bottom_margin - 1);
+  if (x->w < 59 || x->l < 15)
+    {
+      msg (SE, _("ascii driver: Area of page excluding margins and headers "
+                "must be at least 59 characters wide by 15 lines long.  Page as "
+                "configured is only %d characters by %d lines."), x->w, x->l);
+      return 0;
+    }
+  
+  this->res = x->lpi * x->cpi;
+  this->horiz = x->lpi;
+  this->vert = x->cpi;
+  this->width = x->w * this->horiz;
+  this->length = x->l * this->vert;
+  
+  if (ls_null_p (&x->ops[OPS_FORMFEED]))
+    ls_create (ascii_pool, &x->ops[OPS_FORMFEED], "\f");
+  if (ls_null_p (&x->ops[OPS_NEWLINE])
+      || !strcmp (ls_value (&x->ops[OPS_NEWLINE]), "default"))
+    {
+      ls_create (ascii_pool, &x->ops[OPS_NEWLINE], "\n");
+      x->file.mode = "wt";
+    }
+  
+  {
+    int i;
+    
+    for (i = 0; i < LNS_COUNT; i++)
+      {
+       char c[2];
+       c[1] = 0;
+       if (!ls_null_p (&x->box[i]))
+         continue;
+       switch (i)
+         {
+         case TAYSTE2BYTE (0, 0, 0, 0):
+           c[0] = ' ';
+           break;
+
+         case TAYSTE2BYTE (0, 1, 0, 0):
+         case TAYSTE2BYTE (0, 1, 0, 1):
+         case TAYSTE2BYTE (0, 0, 0, 1):
+           c[0] = '-';
+           break;
+
+         case TAYSTE2BYTE (1, 0, 0, 0):
+         case TAYSTE2BYTE (1, 0, 1, 0):
+         case TAYSTE2BYTE (0, 0, 1, 0):
+           c[0] = '|';
+           break;
+
+         case TAYSTE2BYTE (0, 3, 0, 0):
+         case TAYSTE2BYTE (0, 3, 0, 3):
+         case TAYSTE2BYTE (0, 0, 0, 3):
+         case TAYSTE2BYTE (0, 2, 0, 0):
+         case TAYSTE2BYTE (0, 2, 0, 2):
+         case TAYSTE2BYTE (0, 0, 0, 2):
+           c[0] = '=';
+           break;
+
+         case TAYSTE2BYTE (3, 0, 0, 0):
+         case TAYSTE2BYTE (3, 0, 3, 0):
+         case TAYSTE2BYTE (0, 0, 3, 0):
+         case TAYSTE2BYTE (2, 0, 0, 0):
+         case TAYSTE2BYTE (2, 0, 2, 0):
+         case TAYSTE2BYTE (0, 0, 2, 0):
+           c[0] = '#';
+           break;
+
+         default:
+           if (BYTE2TAYSTE (i, LNS_LEFT) > 1
+               || BYTE2TAYSTE (i, LNS_TOP) > 1
+               || BYTE2TAYSTE (i, LNS_RIGHT) > 1
+               || BYTE2TAYSTE (i, LNS_BOTTOM) > 1)
+             c[0] = '#';
+           else
+             c[0] = '+';
+           break;
+         }
+       ls_create (ascii_pool, &x->box[i], c);
+      }
+  }
+  
+  {
+    int i;
+    
+    this->cp_x = this->cp_y = 0;
+    this->font_height = this->vert;
+    this->prop_em_width = this->horiz;
+    this->fixed_width = this->horiz;
+
+    this->horiz_line_width[0] = 0;
+    this->vert_line_width[0] = 0;
+    
+    for (i = 1; i < OUTP_L_COUNT; i++)
+      {
+       this->horiz_line_width[i] = this->vert;
+       this->vert_line_width[i] = this->horiz;
+      }
+    
+    for (i = 0; i < (1 << OUTP_L_COUNT); i++)
+      {
+       this->horiz_line_spacing[i] = (i & ~1) ? this->vert : 0;
+       this->vert_line_spacing[i] = (i & ~1) ? this->horiz : 0;
+      }
+  }
+  
+  this->driver_open = 1;
+  msg (VM (2), _("%s: Initialization complete."), this->name);
+
+  return 1;
+}
+
+int
+ascii_close_driver (struct outp_driver *this)
+{
+  struct ascii_driver_ext *x = this->ext;
+  
+  assert (this->driver_open == 1);
+  msg (VM (2), _("%s: Beginning closing..."), this->name);
+  
+  x = this->ext;
+  free (x->page);
+  free (x->line_len);
+  fn_close_ext (&x->file);
+  free (x->file.filename);
+  free (x);
+  
+  this->driver_open = 0;
+  msg (VM (3), _("%s: Finished closing."), this->name);
+  
+  return 1;
+}
+
+/* Generic option types. */
+enum
+  {
+    pos_int_arg = -10,
+    nonneg_int_arg,
+    string_arg,
+    font_string_arg,
+    boolean_arg
+  };
+
+static struct outp_option option_tab[] =
+  {
+    {"headers", boolean_arg, 0},
+    {"output-file", 1, 0},
+    {"char-set", 2, 0},
+    {"length", pos_int_arg, 0},
+    {"width", pos_int_arg, 1},
+    {"lpi", pos_int_arg, 2},
+    {"cpi", pos_int_arg, 3},
+    {"init", string_arg, 0},
+    {"done", string_arg, 1},
+    {"left-margin", nonneg_int_arg, 0},
+    {"right-margin", nonneg_int_arg, 1},
+    {"top-margin", nonneg_int_arg, 2},
+    {"bottom-margin", nonneg_int_arg, 3},
+    {"paginate", boolean_arg, 1},
+    {"form-feed-string", string_arg, 2},
+    {"newline-string", string_arg, 3},
+    {"italic-on", font_string_arg, 0},
+    {"italic-off", font_string_arg, 1},
+    {"bold-on", font_string_arg, 2},
+    {"bold-off", font_string_arg, 3},
+    {"bold-italic-on", font_string_arg, 4},
+    {"bold-italic-off", font_string_arg, 5},
+    {"overstrike-style", 3, 0},
+    {"tab-width", nonneg_int_arg, 4},
+    {"carriage-return-style", 4, 0},
+    {"", 0, 0},
+  };
+static struct outp_option_info option_info;
+
+void
+ascii_option (struct outp_driver *this, const char *key,
+             const struct string *val)
+{
+  struct ascii_driver_ext *x = this->ext;
+  int cat, subcat;
+  const char *value;
+
+  value = ds_value (val);
+  if (!strncmp (key, "box[", 4))
+    {
+      char *tail;
+      int indx = strtol (&key[4], &tail, 4);
+      if (*tail != ']' || indx < 0 || indx > LNS_COUNT)
+       {
+         msg (SE, _("Bad index value for `box' key: syntax is box[INDEX], "
+              "0 <= INDEX < %d decimal, with INDEX expressed in base 4."),
+              LNS_COUNT);
+         return;
+       }
+      if (!ls_null_p (&x->box[indx]))
+       msg (SW, _("Duplicate value for key `%s'."), key);
+      ls_create (ascii_pool, &x->box[indx], value);
+      return;
+    }
+
+  cat = outp_match_keyword (key, option_tab, &option_info, &subcat);
+
+  switch (cat)
+    {
+    case 0:
+      msg (SE, _("Unknown configuration parameter `%s' for ascii device driver."),
+          key);
+      break;
+    case 1:
+      free (x->file.filename);
+      x->file.filename = xstrdup (value);
+      break;
+    case 2:
+      if (!strcmp (value, "ascii"))
+       x->char_set = CHS_ASCII;
+      else if (!strcmp (value, "latin1"))
+       x->char_set = CHS_LATIN1;
+      else
+       msg (SE, _("Unknown character set `%s'.  Valid character sets are "
+            "`ascii' and `latin1'."), value);
+      break;
+    case 3:
+      if (!strcmp (value, "single"))
+       x->overstrike_style = OVS_SINGLE;
+      else if (!strcmp (value, "line"))
+       x->overstrike_style = OVS_LINE;
+      else
+       msg (SE, _("Unknown overstrike style `%s'.  Valid overstrike styles "
+            "are `single' and `line'."), value);
+      break;
+    case 4:
+      if (!strcmp (value, "bs"))
+       x->carriage_return_style = CRS_BS;
+      else if (!strcmp (value, "cr"))
+       x->carriage_return_style = CRS_CR;
+      else
+       msg (SE, _("Unknown carriage return style `%s'.  Valid carriage "
+            "return styles are `cr' and `bs'."), value);
+      break;
+    case pos_int_arg:
+      {
+       char *tail;
+       int arg;
+
+       errno = 0;
+       arg = strtol (value, &tail, 0);
+       if (arg < 1 || errno == ERANGE || *tail)
+         {
+           msg (SE, _("Positive integer required as value for `%s'."), key);
+           break;
+         }
+       switch (subcat)
+         {
+         case 0:
+           x->page_length = arg;
+           break;
+         case 1:
+           x->page_width = arg;
+           break;
+         case 2:
+           x->lpi = arg;
+           break;
+         case 3:
+           x->cpi = arg;
+           break;
+         default:
+           assert (0);
+         }
+      }
+      break;
+    case nonneg_int_arg:
+      {
+       char *tail;
+       int arg;
+
+       errno = 0;
+       arg = strtol (value, &tail, 0);
+       if (arg < 0 || errno == ERANGE || *tail)
+         {
+           msg (SE, _("Zero or positive integer required as value for `%s'."),
+                key);
+           break;
+         }
+       switch (subcat)
+         {
+         case 0:
+           x->left_margin = arg;
+           break;
+         case 1:
+           x->right_margin = arg;
+           break;
+         case 2:
+           x->top_margin = arg;
+           break;
+         case 3:
+           x->bottom_margin = arg;
+           break;
+         case 4:
+           x->tab_width = arg;
+           break;
+         default:
+           assert (0);
+         }
+      }
+      break;
+    case string_arg:
+      {
+       struct len_string *s;
+       switch (subcat)
+         {
+         case 0:
+           s = &x->ops[OPS_INIT];
+           break;
+         case 1:
+           s = &x->ops[OPS_DONE];
+           break;
+         case 2:
+           s = &x->ops[OPS_FORMFEED];
+           break;
+         case 3:
+           s = &x->ops[OPS_NEWLINE];
+           break;
+         default:
+           assert (0);
+         }
+       ls_create (ascii_pool, s, value);
+      }
+      break;
+    case font_string_arg:
+      {
+       if (!strcmp (value, "overstrike"))
+         {
+           ls_destroy (ascii_pool, &x->fonts[subcat]);
+           return;
+         }
+       ls_create (ascii_pool, &x->fonts[subcat], value);
+      }
+      break;
+    case boolean_arg:
+      {
+       int setting;
+       if (!strcmp (value, "on") || !strcmp (value, "true")
+           || !strcmp (value, "yes") || atoi (value))
+         setting = 1;
+       else if (!strcmp (value, "off") || !strcmp (value, "false")
+                || !strcmp (value, "no") || !strcmp (value, "0"))
+         setting = 0;
+       else
+         {
+           msg (SE, _("Boolean value expected for %s."), key);
+           return;
+         }
+       switch (subcat)
+         {
+         case 0:
+           x->headers = 0;
+           break;
+         case 1:
+           x->paginate = setting;
+           break;
+         default:
+           assert (0);
+         }
+      }
+      break;
+    default:
+      assert (0);
+    }
+}
+
+int
+postopen (struct file_ext *f)
+{
+  struct ascii_driver_ext *x = f->param;
+  struct len_string *s = &x->ops[OPS_INIT];
+
+  if (!ls_empty_p (s) && fwrite (ls_value (s), ls_length (s), 1, f->file) < 1)
+    {
+      msg (ME, _("ASCII output driver: %s: %s"),
+          f->filename, strerror (errno));
+      return 0;
+    }
+  return 1;
+}
+
+int
+preclose (struct file_ext *f)
+{
+  struct ascii_driver_ext *x = f->param;
+  struct len_string *d = &x->ops[OPS_DONE];
+
+  if (!ls_empty_p (d) && fwrite (ls_value (d), ls_length (d), 1, f->file) < 1)
+    {
+      msg (ME, _("ASCII output driver: %s: %s"),
+          f->filename, strerror (errno));
+      return 0;
+    }
+  return 1;
+}
+
+int
+ascii_open_page (struct outp_driver *this)
+{
+  struct ascii_driver_ext *x = this->ext;
+  int req_page_size;
+
+  assert (this->driver_open && !this->page_open);
+  x->page_number++;
+  if (!fn_open_ext (&x->file))
+    {
+      msg (ME, _("ASCII output driver: %s: %s"), x->file.filename,
+          strerror (errno));
+      return 0;
+    }
+
+  req_page_size = x->w * x->l;
+  if (req_page_size > x->page_size || req_page_size / 2 < x->page_size)
+    {
+      x->page_size = req_page_size;
+      x->page = xrealloc (x->page, sizeof *x->page * req_page_size);
+    }
+
+  if (x->l > x->line_len_size)
+    {
+      x->line_len_size = x->l;
+      x->line_len = xrealloc (x->line_len,
+                             sizeof *x->line_len * x->line_len_size);
+    }
+
+  memset (x->line_len, 0, sizeof *x->line_len * x->l);
+
+  this->page_open = 1;
+  return 1;
+}
+
+/* Ensures that at least the first L characters of line I in the
+   driver identified by struct ascii_driver_ext *X have been cleared out. */
+static inline void
+expand_line (struct ascii_driver_ext *x, int i, int l)
+{
+  int limit = i * x->w + l;
+  int j;
+
+  for (j = i * x->w + x->line_len[i]; j < limit; j++)
+    x->page[j] = ' ';
+  x->line_len[i] = l;
+}
+
+/* Puts line L at (H,K) in the current output page.  Assumes
+   struct ascii_driver_ext named `ext'. */
+#define draw_line(H, K, L)                             \
+       ext->page[ext->w * (K) + (H)] = (L) | 0x800
+
+/* Line styles for each position. */
+#define T(STYLE) (STYLE<<LNS_TOP)
+#define L(STYLE) (STYLE<<LNS_LEFT)
+#define B(STYLE) (STYLE<<LNS_BOTTOM)
+#define R(STYLE) (STYLE<<LNS_RIGHT)
+
+void
+ascii_line_horz (struct outp_driver *this, const struct rect *r,
+                const struct color *c unused, int style)
+{
+  struct ascii_driver_ext *ext = this->ext;
+  int x1 = r->x1 / this->horiz;
+  int x2 = r->x2 / this->horiz;
+  int y1 = r->y1 / this->vert;
+  int x;
+
+  assert (this->driver_open && this->page_open);
+  if (x1 == x2)
+    return;
+#if GLOBAL_DEBUGGING
+  if (x1 > x2
+      || x1 < 0 || x1 >= ext->w
+      || x2 <= 0 || x2 > ext->w
+      || y1 < 0 || y1 >= ext->l)
+    {
+#if !SUPPRESS_WARNINGS
+      printf (_("ascii_line_horz: bad hline (%d,%d),%d out of (%d,%d)\n"),
+             x1, x2, y1, ext->w, ext->l);
+#endif
+      return;
+    }
+#endif
+
+  if (ext->line_len[y1] < x2)
+    expand_line (ext, y1, x2);
+
+  for (x = x1; x < x2; x++)
+    draw_line (x, y1, (style << LNS_LEFT) | (style << LNS_RIGHT));
+}
+
+void
+ascii_line_vert (struct outp_driver *this, const struct rect *r,
+                const struct color *c unused, int style)
+{
+  struct ascii_driver_ext *ext = this->ext;
+  int x1 = r->x1 / this->horiz;
+  int y1 = r->y1 / this->vert;
+  int y2 = r->y2 / this->vert;
+  int y;
+
+  assert (this->driver_open && this->page_open);
+  if (y1 == y2)
+    return;
+#if GLOBAL_DEBUGGING
+  if (y1 > y2
+      || x1 < 0 || x1 >= ext->w
+      || y1 < 0 || y1 >= ext->l
+      || y2 < 0 || y2 > ext->l)
+    {
+#if !SUPPRESS_WARNINGS
+      printf (_("ascii_line_vert: bad vline %d,(%d,%d) out of (%d,%d)\n"),
+             x1, y1, y2, ext->w, ext->l);
+#endif
+      return;
+    }
+#endif
+
+  for (y = y1; y < y2; y++)
+    if (ext->line_len[y] <= x1)
+      expand_line (ext, y, x1 + 1);
+
+  for (y = y1; y < y2; y++)
+    draw_line (x1, y, (style << LNS_TOP) | (style << LNS_BOTTOM));
+}
+
+void
+ascii_line_intersection (struct outp_driver *this, const struct rect *r,
+                        const struct color *c unused,
+                        const struct outp_styles *style)
+{
+  struct ascii_driver_ext *ext = this->ext;
+  int x = r->x1 / this->horiz;
+  int y = r->y1 / this->vert;
+  int l;
+
+  assert (this->driver_open && this->page_open);
+#if GLOBAL_DEBUGGING
+  if (x < 0 || x >= ext->w || y < 0 || y >= ext->l)
+    {
+#if !SUPPRESS_WARNINGS
+      printf (_("ascii_line_intersection: bad intsct (%d,%d) out of (%d,%d)\n"),
+             x, y, ext->w, ext->l);
+#endif
+      return;
+    }
+#endif
+
+  l = ((style->l << LNS_LEFT) | (style->r << LNS_RIGHT)
+       | (style->t << LNS_TOP) | (style->b << LNS_BOTTOM));
+
+  if (ext->line_len[y] <= x)
+    expand_line (ext, y, x + 1);
+  draw_line (x, y, l);
+}
+
+void
+ascii_line_width (struct outp_driver *this, int *width, int *height)
+{
+  int i;
+
+  assert (this->driver_open && this->page_open);
+  width[0] = height[0] = 0;
+  for (i = 1; i < OUTP_L_COUNT; i++)
+    {
+      width[i] = this->horiz;
+      height[i] = this->vert;
+    }
+}
+
+/* FIXME: Later we could set this up so that for certain devices it
+   performs shading? */
+void
+ascii_box (struct outp_driver *this unused, const struct rect *r unused,
+          const struct color *bord unused, const struct color *fill unused)
+{
+  assert (this->driver_open && this->page_open);
+}
+
+/* Polylines not supported. */
+void
+ascii_polyline_begin (struct outp_driver *this unused, const struct color *c unused)
+{
+  assert (this->driver_open && this->page_open);
+}
+void
+ascii_polyline_point (struct outp_driver *this unused, int x unused, int y unused)
+{
+  assert (this->driver_open && this->page_open);
+}
+void
+ascii_polyline_end (struct outp_driver *this unused)
+{
+  assert (this->driver_open && this->page_open);
+}
+
+void
+ascii_text_set_font_by_name (struct outp_driver * this, const char *s)
+{
+  struct ascii_driver_ext *x = this->ext;
+  int len = strlen (s);
+
+  assert (this->driver_open && this->page_open);
+  x->cur_font = OUTP_F_R;
+  if (len == 0)
+    return;
+  if (s[len - 1] == 'I')
+    {
+      if (len > 1 && s[len - 2] == 'B')
+       x->cur_font = OUTP_F_BI;
+      else
+       x->cur_font = OUTP_F_I;
+    }
+  else if (s[len - 1] == 'B')
+    x->cur_font = OUTP_F_B;
+}
+
+void
+ascii_text_set_font_by_position (struct outp_driver *this, int pos)
+{
+  struct ascii_driver_ext *x = this->ext;
+  assert (this->driver_open && this->page_open);
+  x->cur_font = pos >= 0 && pos < 4 ? pos : 0;
+}
+
+void
+ascii_text_set_font_by_family (struct outp_driver *this unused, const char *s unused)
+{
+  assert (this->driver_open && this->page_open);
+}
+
+const char *
+ascii_text_get_font_name (struct outp_driver *this)
+{
+  struct ascii_driver_ext *x = this->ext;
+
+  assert (this->driver_open && this->page_open);
+  switch (x->cur_font)
+    {
+    case OUTP_F_R:
+      return "R";
+    case OUTP_F_I:
+      return "I";
+    case OUTP_F_B:
+      return "B";
+    case OUTP_F_BI:
+      return "BI";
+    default:
+      assert (0);
+    }
+  abort ();
+}
+
+const char *
+ascii_text_get_font_family (struct outp_driver *this unused)
+{
+  assert (this->driver_open && this->page_open);
+  return "";
+}
+
+int
+ascii_text_set_size (struct outp_driver *this, int size)
+{
+  assert (this->driver_open && this->page_open);
+  return size == this->vert;
+}
+
+int
+ascii_text_get_size (struct outp_driver *this, int *em_width)
+{
+  assert (this->driver_open && this->page_open);
+  if (em_width)
+    *em_width = this->horiz;
+  return this->vert;
+}
+
+static void text_draw (struct outp_driver *this, struct outp_text *t);
+
+/* Divides the text T->S into lines of width T->H.  Sets T->V to the
+   number of lines necessary.  Actually draws the text if DRAW is
+   nonzero.
+
+   You probably don't want to look at this code. */
+static void
+delineate (struct outp_driver *this, struct outp_text *t, int draw)
+{
+  /* Width we're fitting everything into. */
+  int width = t->h / this->horiz;
+
+  /* Maximum `y' position we can write to. */
+  int max_y;
+
+  /* Current position in string, character following end of string. */
+  const char *s = ls_value (&t->s);
+  const char *end = ls_end (&t->s);
+
+  /* Temporary struct outp_text to pass to low-level function. */
+  struct outp_text temp;
+
+#if GLOBAL_DEBUGGING && 0
+  if (!ext->debug)
+    {
+      ext->debug = 1;
+      printf (_("%s: horiz=%d, vert=%d\n"), this->name, this->horiz, this->vert);
+    }
+#endif
+
+  if (!width)
+    {
+      t->h = t->v = 0;
+      return;
+    }
+
+  if (draw)
+    {
+      temp.options = t->options;
+      ls_shallow_copy (&temp.s, &t->s);
+      temp.h = t->h / this->horiz;
+      temp.x = t->x / this->horiz;
+    }
+  else
+    t->y = 0;
+  temp.y = t->y / this->vert;
+
+  if (t->options & OUTP_T_VERT)
+    max_y = (t->v / this->vert) + temp.y - 1;
+  else
+    max_y = INT_MAX;
+  
+  while (end - s > width)
+    {
+      const char *beg = s;
+      const char *space;
+
+      /* Find first space before &s[width]. */
+      space = &s[width];
+      for (;;)
+       {
+         if (space > s)
+           {
+             if (!isspace ((unsigned char) space[-1]))
+               {
+                 space--;
+                 continue;
+               }
+             else
+               s = space;
+           }
+         else
+           s = space = &s[width];
+         break;
+       }
+
+      /* Draw text. */
+      if (draw)
+       {
+         ls_init (&temp.s, beg, space - beg);
+         temp.w = space - beg;
+         text_draw (this, &temp);
+       }
+      if (++temp.y > max_y)
+       return;
+
+      /* Find first nonspace after space. */
+      while (s < end && isspace ((unsigned char) *s))
+       s++;
+    }
+  if (s < end)
+    {
+      if (draw)
+       {
+         ls_init (&temp.s, s, end - s);
+         temp.w = end - s;
+         text_draw (this, &temp);
+       }
+      temp.y++;
+    }
+
+  t->v = (temp.y * this->vert) - t->y;
+}
+
+void
+ascii_text_metrics (struct outp_driver *this, struct outp_text *t)
+{
+  assert (this->driver_open && this->page_open);
+  if (!(t->options & OUTP_T_HORZ))
+    {
+      t->v = this->vert;
+      t->h = ls_length (&t->s) * this->horiz;
+    }
+  else
+    delineate (this, t, 0);
+}
+
+void
+ascii_text_draw (struct outp_driver *this, struct outp_text *t)
+{
+  /* FIXME: orientations not supported. */
+  assert (this->driver_open && this->page_open);
+  if (!(t->options & OUTP_T_HORZ))
+    {
+      struct outp_text temp;
+
+      temp.options = t->options;
+      temp.s = t->s;
+      temp.h = temp.v = 0;
+      temp.x = t->x / this->horiz;
+      temp.y = t->y / this->vert;
+      text_draw (this, &temp);
+      ascii_text_metrics (this, t);
+      
+      return;
+    }
+  delineate (this, t, 1);
+}
+
+static void
+text_draw (struct outp_driver *this, struct outp_text *t)
+{
+  struct ascii_driver_ext *ext = this->ext;
+  unsigned attr = ext->cur_font << 8;
+
+  int x = t->x;
+  int y = t->y * ext->w;
+
+  char *s = ls_value (&t->s);
+
+  /* Expand the line with the assumption that S takes up LEN character
+     spaces (sometimes it takes up less). */
+  int min_len;
+
+  assert (this->driver_open && this->page_open);
+  switch (t->options & OUTP_T_JUST_MASK)
+    {
+    case OUTP_T_JUST_LEFT:
+      break;
+    case OUTP_T_JUST_CENTER:
+      x -= (t->h - t->w) / 2;  /* fall through */
+    case OUTP_T_JUST_RIGHT:
+      x += (t->h - t->w);
+      break;
+    default:
+      assert (0);
+    }
+
+  if (!(t->y < ext->l && x < ext->w))
+    return;
+  min_len = min (x + ls_length (&t->s), ext->w);
+  if (ext->line_len[t->y] < min_len)
+    expand_line (ext, t->y, min_len);
+
+  {
+    int len = ls_length (&t->s);
+
+    if (len + x > ext->w)
+      len = ext->w - x;
+    while (len--)
+      ext->page[y + x++] = *s++ | attr;
+  }
+}
+\f
+/* ascii_close_page () and support routines. */
+
+#define LINE_BUF_SIZE 1024
+static unsigned char *line_buf;
+static unsigned char *line_p;
+
+static inline int
+commit_line_buf (struct outp_driver *this)
+{
+  struct ascii_driver_ext *x = this->ext;
+  
+  if ((int) fwrite (line_buf, 1, line_p - line_buf, x->file.file)
+      < line_p - line_buf)
+    {
+      msg (ME, _("Writing `%s': %s"), x->file.filename, strerror (errno));
+      return 0;
+    }
+
+  line_p = line_buf;
+  return 1;
+}
+
+/* Writes everything from BP to EP exclusive into line_buf, or to
+   THIS->output if line_buf overflows. */
+static inline void
+output_string (struct outp_driver *this, const char *bp, const char *ep)
+{
+  if (LINE_BUF_SIZE - (line_p - line_buf) >= ep - bp)
+    {
+      memcpy (line_p, bp, ep - bp);
+      line_p += ep - bp;
+    }
+  else
+    while (bp < ep)
+      {
+       if (LINE_BUF_SIZE - (line_p - line_buf) <= 1 && !commit_line_buf (this))
+         return;
+       *line_p++ = *bp++;
+      }
+}
+
+/* Writes everything from BP to EP exclusive into line_buf, or to
+   THIS->output if line_buf overflows.  Returns 1 if additional passes
+   over the line are required.  FIXME: probably could do a lot of
+   optimization here. */
+static inline int
+output_shorts (struct outp_driver *this,
+              const unsigned short *bp, const unsigned short *ep)
+{
+  struct ascii_driver_ext *ext = this->ext;
+  size_t remaining = LINE_BUF_SIZE - (line_p - line_buf);
+  int result = 0;
+
+  for (; bp < ep; bp++)
+    {
+      if (*bp & 0x800)
+       {
+         struct len_string *box = &ext->box[*bp & 0xff];
+         size_t len = ls_length (box);
+
+         if (remaining >= len)
+           {
+             memcpy (line_p, ls_value (box), len);
+             line_p += len;
+             remaining -= len;
+           }
+         else
+           {
+             if (!commit_line_buf (this))
+               return 0;
+             output_string (this, ls_value (box), ls_end (box));
+             remaining = LINE_BUF_SIZE - (line_p - line_buf);
+           }
+       }
+      else if (*bp & 0x0300)
+       {
+         struct len_string *on;
+         char buf[5];
+         int len;
+
+         switch (*bp & 0x0300)
+           {
+           case OUTP_F_I << 8:
+             on = &ext->fonts[FSTY_ON | FSTY_ITALIC];
+             break;
+           case OUTP_F_B << 8:
+             on = &ext->fonts[FSTY_ON | FSTY_BOLD];
+             break;
+           case OUTP_F_BI << 8:
+             on = &ext->fonts[FSTY_ON | FSTY_BOLD_ITALIC];
+             break;
+           default:
+             assert (0);
+           }
+         if (!on)
+           {
+             if (ext->overstrike_style == OVS_SINGLE)
+               switch (*bp & 0x0300)
+                 {
+                 case OUTP_F_I << 8:
+                   buf[0] = '_';
+                   buf[1] = '\b';
+                   buf[2] = *bp;
+                   len = 3;
+                   break;
+                 case OUTP_F_B << 8:
+                   buf[0] = *bp;
+                   buf[1] = '\b';
+                   buf[2] = *bp;
+                   len = 3;
+                   break;
+                 case OUTP_F_BI << 8:
+                   buf[0] = '_';
+                   buf[1] = '\b';
+                   buf[2] = *bp;
+                   buf[3] = '\b';
+                   buf[4] = *bp;
+                   len = 5;
+                   break;
+                 default:
+                   assert (0);
+                 }
+             else
+               {
+                 buf[0] = *bp;
+                 result = len = 1;
+               }
+           }
+         else
+           {
+             buf[0] = *bp;
+             len = 1;
+           }
+         output_string (this, buf, &buf[len]);
+       }
+      else if (remaining)
+       {
+         *line_p++ = *bp;
+         remaining--;
+       }
+      else
+       {
+         if (!commit_line_buf (this))
+           return 0;
+         remaining = LINE_BUF_SIZE - (line_p - line_buf);
+         *line_p++ = *bp;
+       }
+    }
+
+  return result;
+}
+
+/* Writes CH into line_buf N times, or to THIS->output if line_buf
+   overflows. */
+static inline void
+output_char (struct outp_driver *this, int n, int ch)
+{
+  if (LINE_BUF_SIZE - (line_p - line_buf) >= n)
+    {
+      memset (line_p, ch, n);
+      line_p += n;
+    }
+  else
+    while (n--)
+      {
+       if (LINE_BUF_SIZE - (line_p - line_buf) <= 1 && !commit_line_buf (this))
+         return;
+       *line_p++ = ch;
+      }
+}
+
+/* Advance the carriage from column 0 to the left margin. */
+static void
+advance_to_left_margin (struct outp_driver *this)
+{
+  struct ascii_driver_ext *ext = this->ext;
+  int margin;
+
+  margin = ext->left_margin;
+  if (margin == 0)
+    return;
+  if (ext->tab_width && margin >= ext->tab_width)
+    {
+      output_char (this, margin / ext->tab_width, '\t');
+      margin %= ext->tab_width;
+    }
+  if (margin)
+    output_char (this, margin, ' ');
+}
+
+/* Move the output file carriage N_CHARS left, to the left margin. */
+static void
+return_carriage (struct outp_driver *this, int n_chars)
+{
+  struct ascii_driver_ext *ext = this->ext;
+
+  switch (ext->carriage_return_style)
+    {
+    case CRS_BS:
+      output_char (this, n_chars, '\b');
+      break;
+    case CRS_CR:
+      output_char (this, 1, '\r');
+      advance_to_left_margin (this);
+      break;
+    default:
+      assert (0);
+    }
+}
+
+/* Writes COUNT lines from the line buffer in THIS, starting at line
+   number FIRST. */
+static void
+output_lines (struct outp_driver *this, int first, int count)
+{
+  struct ascii_driver_ext *ext = this->ext;
+
+  unsigned short *p = &ext->page[ext->w * first];
+  int *len = &ext->line_len[first];
+  struct len_string *newline = &ext->ops[OPS_NEWLINE];
+
+  int n_chars;
+  int n_passes;
+
+  if (NULL == ext->file.file)
+    return;
+
+  while (count--)              /* Iterate over all the lines to be output. */
+    {
+      unsigned short *end_p;
+      unsigned short *bp, *ep;
+      unsigned short attr = 0;
+
+      end_p = p + *len++;
+      assert (end_p >= p);
+
+      /* Output every character in the line in the appropriate
+         manner. */
+      n_passes = 1;
+      bp = ep = p;
+      n_chars = 0;
+      advance_to_left_margin (this);
+      for (;;)                 
+       {
+         while (ep < end_p && attr == (*ep & 0x0300))
+           ep++;
+         if (output_shorts (this, bp, ep))
+           n_passes = 2;
+         n_chars += ep - bp;
+         bp = ep;
+
+         if (bp >= end_p)
+           break;
+
+         /* Turn off old font. */
+         if (attr != (OUTP_F_R << 8))
+           {
+             struct len_string *off;
+
+             switch (attr)
+               {
+               case OUTP_F_I << 8:
+                 off = &ext->fonts[FSTY_OFF | FSTY_ITALIC];
+                 break;
+               case OUTP_F_B << 8:
+                 off = &ext->fonts[FSTY_OFF | FSTY_BOLD];
+                 break;
+               case OUTP_F_BI << 8:
+                 off = &ext->fonts[FSTY_OFF | FSTY_BOLD_ITALIC];
+                 break;
+               default:
+                 assert (0);
+               }
+             if (off)
+               output_string (this, ls_value (off), ls_end (off));
+           }
+
+         /* Turn on new font. */
+         attr = (*bp & 0x0300);
+         if (attr != (OUTP_F_R << 8))
+           {
+             struct len_string *on;
+
+             switch (attr)
+               {
+               case OUTP_F_I << 8:
+                 on = &ext->fonts[FSTY_ON | FSTY_ITALIC];
+                 break;
+               case OUTP_F_B << 8:
+                 on = &ext->fonts[FSTY_ON | FSTY_BOLD];
+                 break;
+               case OUTP_F_BI << 8:
+                 on = &ext->fonts[FSTY_ON | FSTY_BOLD_ITALIC];
+                 break;
+               default:
+                 assert (0);
+               }
+             if (on)
+               output_string (this, ls_value (on), ls_end (on));
+           }
+
+         ep = bp + 1;
+       }
+      if (n_passes > 1)
+       {
+         unsigned char ch;
+
+         return_carriage (this, n_chars);
+         n_chars = 0;
+         bp = ep = p;
+         for (;;)
+           {
+             while (ep < end_p && (*ep & 0x0300) == (OUTP_F_R << 8))
+               ep++;
+             if (ep >= end_p)
+               break;
+             output_char (this, ep - bp, ' ');
+
+             switch (*ep & 0x0300)
+               {
+               case OUTP_F_I << 8:
+                 ch = '_';
+                 break;
+               case OUTP_F_B << 8:
+                 ch = *ep;
+                 break;
+               case OUTP_F_BI << 8:
+                 ch = *ep;
+                 n_passes = 3;
+                 break;
+               }
+             output_char (this, 1, ch);
+             n_chars += ep - bp + 1;
+             bp = ep + 1;
+             ep = bp;
+           }
+       }
+      if (n_passes > 2)
+       {
+         return_carriage (this, n_chars);
+         bp = ep = p;
+         for (;;)
+           {
+             while (ep < end_p && (*ep & 0x0300) != (OUTP_F_BI << 8))
+               ep++;
+             if (ep >= end_p)
+               break;
+             output_char (this, ep - bp, ' ');
+             output_char (this, 1, '_');
+             bp = ep + 1;
+             ep = bp;
+           }
+       }
+      p += ext->w;
+
+      output_string (this, ls_value (newline), ls_end (newline));
+    }
+}
+
+int
+ascii_close_page (struct outp_driver *this)
+{
+  static unsigned char *s;
+  static int s_len;
+
+  struct ascii_driver_ext *x = this->ext;
+  int nl_len, ff_len, total_len;
+  unsigned char *cp;
+  int i;
+
+  assert (this->driver_open && this->page_open);
+  
+  if (!line_buf)
+    line_buf = xmalloc (LINE_BUF_SIZE);
+  line_p = line_buf;
+
+  nl_len = ls_length (&x->ops[OPS_NEWLINE]);
+  if (x->top_margin)
+    {
+      total_len = x->top_margin * nl_len;
+      if (s_len < total_len)
+       {
+         s_len = total_len;
+         s = xrealloc (s, s_len);
+       }
+      for (cp = s, i = 0; i < x->top_margin; i++)
+       {
+         memcpy (cp, ls_value (&x->ops[OPS_NEWLINE]), nl_len);
+         cp += nl_len;
+       }
+      output_string (this, s, &s[total_len]);
+    }
+  if (x->headers)
+    {
+      int len;
+
+      total_len = nl_len + x->w;
+      if (s_len < total_len + 1)
+       {
+         s_len = total_len + 1;
+         s = xrealloc (s, s_len);
+       }
+      
+      memset (s, ' ', x->w);
+
+      {
+       char temp[40];
+
+       snprintf (temp, 80, _("%s - Page %d"), curdate, x->page_number);
+       memcpy (&s[x->w - strlen (temp)], temp, strlen (temp));
+      }
+
+      if (outp_title && outp_subtitle)
+       {
+         len = min ((int) strlen (outp_title), x->w);
+         memcpy (s, outp_title, len);
+       }
+      memcpy (&s[x->w], ls_value (&x->ops[OPS_NEWLINE]), nl_len);
+      output_string (this, s, &s[total_len]);
+
+      memset (s, ' ', x->w);
+      len = strlen (version) + 3 + strlen (host_system);
+      if (len < x->w)
+       sprintf (&s[x->w - len], "%s - %s" , version, host_system);
+      if (outp_subtitle || outp_title)
+       {
+         char *string = outp_subtitle ? outp_subtitle : outp_title;
+         len = min ((int) strlen (string), x->w);
+         memcpy (s, string, len);
+       }
+      memcpy (&s[x->w], ls_value (&x->ops[OPS_NEWLINE]), nl_len);
+      output_string (this, s, &s[total_len]);
+      output_string (this, &s[x->w], &s[total_len]);
+    }
+  if (line_p != line_buf && !commit_line_buf (this))
+    return 0;
+
+  output_lines (this, x->n_output, x->l - x->n_output);
+
+  ff_len = ls_length (&x->ops[OPS_FORMFEED]);
+  total_len = x->bottom_margin * nl_len + ff_len;
+  if (s_len < total_len)
+    s = xrealloc (s, total_len);
+  for (cp = s, i = 0; i < x->bottom_margin; i++)
+    {
+      memcpy (cp, ls_value (&x->ops[OPS_NEWLINE]), nl_len);
+      cp += nl_len;
+    }
+  memcpy (cp, ls_value (&x->ops[OPS_FORMFEED]), ff_len);
+  output_string (this, s, &s[total_len]);
+  if (line_p != line_buf && !commit_line_buf (this))
+    return 0;
+
+  x->n_output = 0;
+  
+  this->page_open = 0;
+  return 1;
+}
+
+struct outp_class ascii_class =
+{
+  "ascii",
+  0,
+  0,
+
+  ascii_open_global,
+  ascii_close_global,
+  ascii_font_sizes,
+
+  ascii_preopen_driver,
+  ascii_option,
+  ascii_postopen_driver,
+  ascii_close_driver,
+
+  ascii_open_page,
+  ascii_close_page,
+
+  NULL,
+
+  ascii_line_horz,
+  ascii_line_vert,
+  ascii_line_intersection,
+
+  ascii_box,
+  ascii_polyline_begin,
+  ascii_polyline_point,
+  ascii_polyline_end,
+
+  ascii_text_set_font_by_name,
+  ascii_text_set_font_by_position,
+  ascii_text_set_font_by_family,
+  ascii_text_get_font_name,
+  ascii_text_get_font_family,
+  ascii_text_set_size,
+  ascii_text_get_size,
+  ascii_text_metrics,
+  ascii_text_draw,
+};
diff --git a/src/autorecode.c b/src/autorecode.c
new file mode 100644 (file)
index 0000000..5ae1e7f
--- /dev/null
@@ -0,0 +1,342 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "error.h"
+#include "hash.h"
+#include "lexer.h"
+#include "pool.h"
+#include "str.h"
+#include "var.h"
+#include "vfm.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1 */
+#include "debug-print.h"
+
+/* FIXME: This module is less than ideally efficient, both in space
+   and time.  If anyone cares, it would be a good project. */
+
+/* FIXME: Implement PRINT subcommand. */
+
+/* Explains how to recode one value.  `from' must be first element.  */
+struct arc_item
+  {
+    union value from;          /* Original value. */
+    double to;                 /* Recoded value. */
+  };
+
+/* Explains how to recode an AUTORECODE variable. */
+struct arc_spec
+  {
+    struct variable *src;      /* Source variable. */
+    struct variable *dest;     /* Target variable. */
+    struct hsh_table *items;   /* Hash table of `freq's. */
+  };
+
+/* AUTORECODE transformation. */
+struct autorecode_trns
+  {
+    struct trns_header h;
+    struct pool *owner;                /* Contains AUTORECODE specs. */
+    struct arc_spec *arc;      /* AUTORECODE specifications. */
+    int n_arc;                 /* Number of specifications. */
+  };
+
+/* Source and target variables, hash table translator. */
+static struct variable **v_src;
+static struct variable **v_dest;
+static struct hsh_table **h_trans;
+static int nv_src;
+
+/* Pool for allocation of hash table entries. */
+static struct pool *hash_pool;
+
+/* Options. */
+static int descend;
+static int print;
+
+static int autorecode_trns_proc (struct trns_header *, struct ccase *);
+static void autorecode_trns_free (struct trns_header *);
+static int autorecode_proc_func (struct ccase *);
+static int compare_alpha_value (const void *, const void *, void *);
+static unsigned hash_alpha_value (const void *, void *);
+static int compare_numeric_value (const void *, const void *, void *);
+static unsigned hash_numeric_value (const void *, void *);
+static void recode (void);
+
+/* Performs the AUTORECODE procedure. */
+int
+cmd_autorecode (void)
+{
+  /* Dest var names. */
+  char **n_dest = NULL;
+  int nv_dest = 0;
+
+  int i;
+
+  v_src = NULL;
+  descend = print = 0;
+  h_trans = NULL;
+
+  lex_match_id ("AUTORECODE");
+  lex_match_id ("VARIABLES");
+  lex_match ('=');
+  if (!parse_variables (&default_dict, &v_src, &nv_src, PV_NO_DUPLICATE))
+    return CMD_FAILURE;
+  if (!lex_force_match_id ("INTO"))
+    return CMD_FAILURE;
+  lex_match ('=');
+  if (!parse_DATA_LIST_vars (&n_dest, &nv_dest, PV_NONE))
+    goto lossage;
+  if (nv_dest != nv_src)
+    {
+      msg (SE, _("Number of source variables (%d) does not match number "
+          "of target variables (%d)."), nv_src, nv_dest);
+      goto lossage;
+    }
+  while (lex_match ('/'))
+    if (lex_match_id ("DESCENDING"))
+      descend = 1;
+    else if (lex_match_id ("PRINT"))
+      print = 1;
+  if (token != '.')
+    {
+      lex_error (_("expecting end of command"));
+      goto lossage;
+    }
+
+  for (i = 0; i < nv_dest; i++)
+    {
+      int j;
+
+      if (is_varname (n_dest[i]))
+       {
+         msg (SE, _("Target variable %s duplicates existing variable %s."),
+              n_dest[i], n_dest[i]);
+         goto lossage;
+       }
+      for (j = 0; j < i; j++)
+       if (!strcmp (n_dest[i], n_dest[j]))
+         {
+           msg (SE, _("Duplicate variable name %s among target variables."),
+                n_dest[i]);
+           goto lossage;
+         }
+    }
+
+  hash_pool = pool_create ();
+
+  v_dest = xmalloc (sizeof *v_dest * nv_dest);
+  h_trans = xmalloc (sizeof *h_trans * nv_dest);
+  for (i = 0; i < nv_dest; i++)
+    if (v_src[i]->type == ALPHA)
+      h_trans[i] = hsh_create (10, compare_alpha_value,
+                              hash_alpha_value, NULL,
+                              (void *) v_src[i]->width);
+    else
+      h_trans[i] = hsh_create (10, compare_numeric_value,
+                              hash_numeric_value, NULL, NULL);
+
+  procedure (NULL, autorecode_proc_func, NULL);
+
+  for (i = 0; i < nv_dest; i++)
+    {
+      v_dest[i] = force_create_variable (&default_dict, n_dest[i], NUMERIC, 0);
+      free (n_dest[i]);
+    }
+  free (n_dest);
+
+  recode ();
+  
+  free (v_src);
+  free (v_dest);
+
+  return CMD_SUCCESS;
+
+lossage:
+  if (h_trans != NULL)
+    for (i = 0; i < nv_src; i++)
+      hsh_destroy (h_trans[i]);
+  for (i = 0; i < nv_dest; i++)
+    free (n_dest[i]);
+  free (n_dest);
+  free (v_src);
+  return CMD_FAILURE;
+}
+\f
+/* AUTORECODE transformation. */
+
+static void
+recode (void)
+{
+  struct autorecode_trns *t;
+  struct pool *arc_pool;
+  int i;
+
+  arc_pool = pool_create ();
+  t = xmalloc (sizeof *t);
+  t->h.proc = autorecode_trns_proc;
+  t->h.free = autorecode_trns_free;
+  t->owner = arc_pool;
+  t->arc = pool_alloc (arc_pool, sizeof *t->arc * nv_src);
+  t->n_arc = nv_src;
+  for (i = 0; i < nv_src; i++)
+    {
+      struct arc_spec *spec = &t->arc[i];
+      void **p = hsh_sort (h_trans[i], NULL);
+      int count = hsh_count (h_trans[i]);
+      int j;
+
+      spec->src = v_src[i];
+      spec->dest = v_dest[i];
+
+      if (v_src[i]->type == ALPHA)
+       spec->items = hsh_create (2 * count, compare_alpha_value,
+                                 hash_alpha_value, NULL,
+                                 (void *) v_src[i]->width);
+      else
+       spec->items = hsh_create (2 * count, compare_numeric_value,
+                                 hash_numeric_value, NULL, NULL);
+
+      for (j = 0; *p; p++, j++)
+       {
+         struct arc_item *item = pool_alloc (arc_pool, sizeof *item);
+
+         memcpy (&item->from, *p, sizeof (union value));
+         if (v_src[i]->type == ALPHA)
+           item->from.c = pool_strdup (arc_pool, item->from.c);
+         item->to = !descend ? j + 1 : count - j;
+         force_hsh_insert (spec->items, item);
+       }
+      
+      hsh_destroy (h_trans[i]);
+    }
+  free (h_trans);
+  pool_destroy (hash_pool);
+  add_transformation ((struct trns_header *) t);
+}
+
+static int
+autorecode_trns_proc (struct trns_header * trns, struct ccase * c)
+{
+  struct autorecode_trns *t = (struct autorecode_trns *) trns;
+  int i;
+
+  for (i = 0; i < t->n_arc; i++)
+    {
+      struct arc_spec *spec = &t->arc[i];
+      struct arc_item *item;
+
+      if (spec->src->type == NUMERIC)
+       item = force_hsh_find (spec->items, &c->data[spec->src->fv].f);
+      else
+       {
+         union value v;
+         v.c = c->data[spec->src->fv].s;
+         item = force_hsh_find (spec->items, &v);
+       }
+
+      c->data[spec->dest->fv].f = item->to;
+    }
+  return -1;
+}
+
+static void
+autorecode_trns_free (struct trns_header * trns)
+{
+  struct autorecode_trns *t = (struct autorecode_trns *) trns;
+  int i;
+
+  for (i = 0; i < t->n_arc; i++)
+    hsh_destroy (t->arc[i].items);
+  pool_destroy (t->owner);
+}
+\f
+/* AUTORECODE procedure. */
+
+static int
+compare_alpha_value (const void *a, const void *b, void *len)
+{
+  return memcmp (((union value *) a)->c, ((union value *) b)->c, (int) len);
+}
+
+static unsigned
+hash_alpha_value (const void *a, void *len)
+{
+  return hashpjw_d (((union value *) a)->c, &((union value *) a)->c[(int) len]);
+}
+
+static int
+compare_numeric_value (const void *pa, const void *pb, void *foobar unused)
+{
+  double a = ((union value *) pa)->f, b = ((union value *) pb)->f;
+  return a > b ? 1 : (a < b ? -1 : 0);
+}
+
+static unsigned
+hash_numeric_value (const void *a, void *len unused)
+{
+  return hashpjw_d ((char *) &((union value *) a)->f,
+                   (char *) &(&((union value *) a)->f)[1]);
+}
+
+static int
+autorecode_proc_func (struct ccase * c)
+{
+  int i;
+
+  for (i = 0; i < nv_src; i++)
+    {
+      union value v;
+      union value *vp;
+      union value **vpp;
+
+      if (v_src[i]->type == NUMERIC)
+       {
+         v.f = c->data[v_src[i]->fv].f;
+         vpp = (union value **) hsh_probe (h_trans[i], &v);
+         if (NULL == *vpp)
+           {
+             vp = pool_alloc (hash_pool, sizeof (union value));
+             vp->f = v.f;
+             *vpp = vp;
+           }
+       }
+      else
+       {
+         v.c = c->data[v_src[i]->fv].s;
+         vpp = (union value **) hsh_probe (h_trans[i], &v);
+         if (NULL == *vpp)
+           {
+             vp = pool_alloc (hash_pool, sizeof (union value));
+#if __CHECKER__
+             memset (vp, 0, sizeof (union value));
+#endif
+             vp->c = pool_strdup (hash_pool, v.c);
+             *vpp = vp;
+           }
+       }
+    }
+  return 1;
+}
diff --git a/src/avl.c b/src/avl.c
new file mode 100644 (file)
index 0000000..75d9b2e
--- /dev/null
+++ b/src/avl.c
@@ -0,0 +1,1122 @@
+/* libavl - manipulates AVL trees.
+   Copyright (C) 1998-9, 2000 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA.
+
+   The author may be contacted at <pfaffben@pilot.msu.edu> on the
+   Internet, or as Ben Pfaff, 12167 Airport Rd, DeWitt MI 48820, USA
+   through more mundane means. */
+
+/* This is file avl.c in libavl. */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+#if PSPP
+#include "pool.h"
+#define HAVE_XMALLOC 1
+#endif
+#if SELF_TEST 
+#include <limits.h>
+#include <time.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include "avl.h"
+
+#if !PSPP && !__GCC__
+#define inline
+#endif
+
+#if !PSPP
+#if __GNUC__ >= 2
+#define unused __attribute__ ((unused))
+#else
+#define unused
+#endif
+#endif
+
+#ifdef HAVE_XMALLOC
+void *xmalloc (size_t);
+#else /* !HAVE_XMALLOC */
+/* Allocates SIZE bytes of space using malloc().  Aborts if out of
+   memory. */
+static void *
+xmalloc (size_t size)
+{
+  void *vp;
+
+  if (size == 0)
+    return NULL;
+  vp = malloc (size);
+
+  assert (vp != NULL);
+  if (vp == NULL)
+    {
+      fprintf (stderr, "virtual memory exhausted\n");
+      exit (EXIT_FAILURE);
+    }
+  return vp;
+}
+#endif /* !HAVE_XMALLOC */
+
+/* Creates an AVL tree in POOL (which can be NULL).  POOL is owned by
+   the caller, not by the AVL tree.  CMP is a order function for the
+   data to be stored in the tree.  PARAM is arbitrary data that
+   becomes an argument to the comparison function. */
+avl_tree *
+avl_create (MAYBE_POOL avl_comparison_func cmp, void *param)
+{
+  avl_tree *tree;
+
+  assert (cmp != NULL);
+#if PSPP
+  if (pool)
+    tree = pool_alloc (pool, sizeof *tree);
+  else
+#endif
+    tree = xmalloc (sizeof *tree);
+
+#if PSPP
+  tree->pool = pool;
+#endif
+  tree->root.link[0] = NULL;
+  tree->root.link[1] = NULL; 
+  tree->cmp = cmp;
+  tree->count = 0;
+  tree->param = param;
+
+  return tree;
+}
+
+/* Destroy tree TREE.  Function FREE_FUNC is called for every node in
+   the tree as it is destroyed.  
+
+   No effect if the tree has an pool owner and free_func is NULL.
+   The caller owns the pool and must destroy it itself.
+
+   Do not attempt to reuse the tree after it has been freed.  Create a
+   new one.  */
+void
+avl_destroy (avl_tree *tree, avl_node_func free_func)
+{
+  assert (tree != NULL);
+  
+#if PSPP
+  if (free_func || tree->pool == NULL)
+#endif
+    {
+      /* Uses Knuth's Algorithm 2.3.1T as modified in exercise 13
+        (postorder traversal). */
+      
+      /* T1. */
+      avl_node *an[AVL_MAX_HEIGHT];    /* Stack A: nodes. */
+      char ab[AVL_MAX_HEIGHT];         /* Stack A: bits. */
+      int ap = 0;                      /* Stack A: height. */
+      avl_node *p = tree->root.link[0];
+
+      for (;;)
+       {
+         /* T2. */
+         while (p != NULL)
+           {
+             /* T3. */
+             ab[ap] = 0;
+             an[ap++] = p;
+             p = p->link[0];
+           }
+
+         /* T4. */
+         for (;;)
+           {
+             if (ap == 0)
+               goto done;
+
+             p = an[--ap];
+             if (ab[ap] == 0)
+               {
+                 ab[ap++] = 1;
+                 p = p->link[1];
+                 break;
+               }
+      
+             if (free_func)
+               free_func (p->data, tree->param);
+#if PSPP
+             if (tree->pool == NULL)
+#endif
+               free (p);
+           }
+       }
+    }
+
+ done:
+#if PSPP
+  if (tree->pool == NULL)
+#endif
+    free (tree);
+}
+
+/* avl_destroy() with FREE_FUNC hardcoded as free(). */
+void
+avl_free (avl_tree *tree)
+{
+  avl_destroy (tree, (avl_node_func) free);
+}
+
+/* Return the number of nodes in TREE. */
+int
+avl_count (const avl_tree *tree)
+{
+  assert (tree != NULL);
+  return tree->count;
+}
+
+/* Allocates room for a new avl_node in POOL, or using xmalloc() if
+   POOL is NULL. */
+#if PSPP
+static inline avl_node *
+new_node (struct pool *pool)
+{
+  if (pool != NULL)
+    return pool_alloc (pool, sizeof (avl_node));
+  else
+    return xmalloc (sizeof (avl_node));
+}
+#else
+static inline avl_node *
+new_node (void)
+{
+  return xmalloc (sizeof (avl_node));
+}
+
+#define new_node(POOL)                         \
+       new_node ()
+#endif
+
+/* Copy the contents of TREE to a new tree in POOL.  If COPY is
+   non-NULL, then each data item is passed to function COPY, and the
+   return values are inserted into the new tree; otherwise, the items
+   are copied verbatim from the old tree to the new tree.  Returns the
+   new tree. */
+avl_tree *
+avl_copy (MAYBE_POOL const avl_tree *tree, avl_copy_func copy)
+{
+  /* This is a combination of Knuth's Algorithm 2.3.1C (copying a
+     binary tree) and Algorithm 2.3.1T as modified by exercise 12
+     (preorder traversal). */
+
+  avl_tree *new_tree;
+
+  /* PT1. */
+  const avl_node *pa[AVL_MAX_HEIGHT];  /* Stack PA: nodes. */
+  const avl_node **pp = pa;            /* Stack PA: stack pointer. */
+  const avl_node *p = &tree->root;
+  
+  /* QT1. */
+  avl_node *qa[AVL_MAX_HEIGHT];        /* Stack QA: nodes. */
+  avl_node **qp = qa;          /* Stack QA: stack pointer. */
+  avl_node *q;
+  
+  assert (tree != NULL);
+#if PSPP
+  new_tree = avl_create (pool, tree->cmp, tree->param);
+#else
+  new_tree = avl_create (tree->cmp, tree->param);
+#endif
+  new_tree->count = tree->count;
+  q = &new_tree->root;
+
+  for (;;)
+    {
+      /* C4. */
+      if (p->link[0] != NULL)
+       {
+         avl_node *r = new_node (pool);
+         r->link[0] = r->link[1] = NULL;
+         q->link[0] = r;
+       }
+
+      /* C5: Find preorder successors of P and Q.  */
+      goto start;
+      for (;;)
+       {
+         /* PT2. */
+         while (p != NULL)
+           {
+             goto escape;
+           start:
+             /* PT3. */
+             *pp++ = p;
+             *qp++ = q;
+             p = p->link[0];
+             q = q->link[0];
+           }
+      
+         /* PT4. */
+         if (pp == pa)
+           {
+             assert (qp == qa);
+             return new_tree;
+           }
+             
+         p = *--pp;
+         q = *--qp;
+
+         /* PT5. */
+         p = p->link[1];
+         q = q->link[1];
+       }
+    escape:
+
+      /* C2. */
+      if (p->link[1])
+       {
+         avl_node *r = new_node (pool);
+         r->link[0] = r->link[1] = NULL;
+         q->link[1] = r;
+       }
+
+      /* C3. */
+      q->bal = p->bal;
+      if (copy == NULL)
+       q->data = p->data;
+      else
+       q->data = copy (p->data, tree->param);
+    }
+}
+
+/* Walk tree TREE in inorder, calling WALK_FUNC at each node.  Passes
+   PARAM to WALK_FUNC.  */
+void
+avl_walk (const avl_tree *tree, avl_node_func walk_func, void *param)
+{
+  /* Uses Knuth's algorithm 2.3.1T (inorder traversal). */
+  assert (tree && walk_func);
+  
+  {
+    /* T1. */
+    const avl_node *an[AVL_MAX_HEIGHT];        /* Stack A: nodes. */
+    const avl_node **ap = an;          /* Stack A: stack pointer. */
+    const avl_node *p = tree->root.link[0];
+
+    for (;;)
+      {
+       /* T2. */
+       while (p != NULL)
+         {
+           /* T3. */
+           *ap++ = p;
+           p = p->link[0];
+         }
+      
+       /* T4. */
+       if (ap == an)
+         return;
+       p = *--ap;
+
+       /* T5. */
+       walk_func (p->data, param);
+       p = p->link[1];
+      }
+  }
+}
+
+/* Each call to this function for a given TREE and TRAV return the
+   next item in the tree in inorder.  Initialize the first element of
+   TRAV (init) to 0 before calling the first time.  Returns NULL when
+   out of elements.  */
+void *
+avl_traverse (const avl_tree *tree, avl_traverser *trav)
+{
+  assert (tree && trav);
+
+  /* Uses Knuth's algorithm 2.3.1T (inorder traversal). */
+  if (trav->init == 0)
+    {
+      /* T1. */
+      trav->init = 1;
+      trav->nstack = 0;
+      trav->p = tree->root.link[0];
+    }
+  else
+    /* T5. */
+    trav->p = trav->p->link[1];
+
+  for (;;)
+    {
+      /* T2. */
+      while (trav->p != NULL)
+       {
+         /* T3. */
+         trav->stack[trav->nstack++] = trav->p;
+         trav->p = trav->p->link[0];
+       }
+      
+      /* T4. */
+      if (trav->nstack == 0)
+       {
+         trav->init = 0;
+         return NULL;
+       }
+      trav->p = trav->stack[--trav->nstack];
+
+      /* T5. */
+      return trav->p->data;
+    }
+}
+
+/* Search TREE for an item matching ITEM.  If found, returns a pointer
+   to the address of the item.  If none is found, ITEM is inserted
+   into the tree, and a pointer to the address of ITEM is returned.
+   In either case, the pointer returned can be changed by the caller,
+   or the returned data item can be directly edited, but the key data
+   in the item must not be changed. */
+void **
+avl_probe (avl_tree *tree, void *item)
+{
+  /* Uses Knuth's Algorithm 6.2.3A (balanced tree search and
+     insertion), but caches results of comparisons.  In empirical
+     tests this eliminates about 25% of the comparisons seen under
+     random insertions.  */
+
+  /* A1. */
+  avl_node *t;
+  avl_node *s, *p, *q, *r;
+  
+  assert (tree != NULL);
+  t = &tree->root;
+  s = p = t->link[0];
+
+  if (s == NULL)
+    {
+      tree->count++;
+      assert (tree->count == 1);
+      q = t->link[0] = new_node (tree->pool);
+      q->data = item;
+      q->link[0] = q->link[1] = NULL;
+      q->bal = 0;
+      return &q->data;
+    }
+
+  for (;;)
+    {
+      /* A2. */
+      int diff = tree->cmp (item, p->data, tree->param);
+
+      /* A3. */
+      if (diff < 0)
+       {
+         p->cache = 0;
+         q = p->link[0];
+         if (q == NULL)
+           {
+             p->link[0] = q = new_node (tree->pool);
+             break;
+           }
+       }
+      /* A4. */
+      else if (diff > 0)
+       {
+         p->cache = 1;
+         q = p->link[1];
+         if (q == NULL)
+           {
+             p->link[1] = q = new_node (tree->pool);
+             break;
+           }
+       }
+      else
+       /* A2. */
+       return &p->data;
+
+      /* A3, A4. */
+      if (q->bal != 0)
+       t = p, s = q;
+      p = q;
+    }
+  
+  /* A5. */
+  tree->count++;
+  q->data = item;
+  q->link[0] = q->link[1] = NULL;
+  q->bal = 0;
+
+  /* A6. */
+  r = p = s->link[(int) s->cache];
+  while (p != q)
+    {
+      p->bal = p->cache * 2 - 1;
+      p = p->link[(int) p->cache];
+    }
+
+  /* A7. */
+  if (s->cache == 0)
+    {
+      /* a = -1. */
+      if (s->bal == 0)
+       {
+         s->bal = -1;
+         return &q->data;
+       }
+      else if (s->bal == +1)
+       {
+         s->bal = 0;
+         return &q->data;
+       }
+      
+      assert (s->bal == -1);
+      if (r->bal == -1)
+       {
+         /* A8. */
+         p = r;
+         s->link[0] = r->link[1];
+         r->link[1] = s;
+         s->bal = r->bal = 0;
+       }
+      else
+       {
+         /* A9. */
+         assert (r->bal == +1);
+         p = r->link[1];
+         r->link[1] = p->link[0];
+         p->link[0] = r;
+         s->link[0] = p->link[1];
+         p->link[1] = s;
+         if (p->bal == -1)
+           s->bal = 1, r->bal = 0;
+         else if (p->bal == 0)
+           s->bal = r->bal = 0;
+         else 
+           {
+             assert (p->bal == +1);
+             s->bal = 0, r->bal = -1;
+           }
+         p->bal = 0;
+       }
+    }
+  else
+    {
+      /* a == +1. */
+      if (s->bal == 0)
+       {
+         s->bal = 1;
+         return &q->data;
+       }
+      else if (s->bal == -1)
+       {
+         s->bal = 0;
+         return &q->data;
+       }
+
+      assert (s->bal == +1);
+      if (r->bal == +1)
+       {
+         /* A8. */
+         p = r;
+         s->link[1] = r->link[0];
+         r->link[0] = s;
+         s->bal = r->bal = 0;
+       }
+      else
+       {
+         /* A9. */
+         assert (r->bal == -1);
+         p = r->link[0];
+         r->link[0] = p->link[1];
+         p->link[1] = r;
+         s->link[1] = p->link[0];
+         p->link[0] = s;
+         if (p->bal == +1)
+           s->bal = -1, r->bal = 0;
+         else if (p->bal == 0)
+           s->bal = r->bal = 0;
+         else 
+           {
+             assert (p->bal == -1);
+             s->bal = 0, r->bal = 1;
+           }
+         p->bal = 0;
+       }
+    }
+               
+  /* A10. */
+  if (t != &tree->root && s == t->link[1])
+    t->link[1] = p;
+  else
+    t->link[0] = p;
+
+  return &q->data;
+}
+  
+/* Search TREE for an item matching ITEM, and return it if found. */
+void *
+avl_find (const avl_tree *tree, const void *item)
+{
+  const avl_node *p;
+
+  assert (tree != NULL);
+  for (p = tree->root.link[0]; p; )
+    {
+      int diff = tree->cmp (item, p->data, tree->param);
+
+      if (diff < 0)
+       p = p->link[0];
+      else if (diff > 0)
+       p = p->link[1];
+      else
+       return p->data;
+    }
+
+  return NULL;
+}
+
+/* Searches AVL tree TREE for an item matching ITEM.  If found, the
+   item is removed from the tree and the actual item found is returned
+   to the caller.  If no item matching ITEM exists in the tree,
+   returns NULL. */
+void *
+avl_delete (avl_tree *tree, const void *item)
+{
+  /* Uses my Algorithm D, which can be found at
+     http://www.msu.edu/user/pfaffben/avl.  Algorithm D is based on
+     Knuth's Algorithm 6.2.2D (Tree deletion) and 6.2.3A (Balanced
+     tree search and insertion), as well as the notes on pages 465-466
+     of Vol. 3. */
+
+  /* D1. */
+  avl_node *pa[AVL_MAX_HEIGHT];                /* Stack P: Nodes. */
+  char a[AVL_MAX_HEIGHT];              /* Stack P: Bits. */
+  int k = 1;                           /* Stack P: Pointer. */
+  
+  avl_node **q;
+  avl_node *p;
+
+  assert (tree != NULL);
+
+  a[0] = 0;
+  pa[0] = &tree->root;
+  p = tree->root.link[0];
+  for (;;)
+    {
+      /* D2. */
+      int diff;
+
+      if (p == NULL)
+       return NULL;
+
+      diff = tree->cmp (item, p->data, tree->param);
+      if (diff == 0)
+       break;
+
+      /* D3, D4. */
+      pa[k] = p;
+      if (diff < 0)
+       {
+         p = p->link[0];
+         a[k] = 0;
+       }
+      else if (diff > 0)
+       {
+         p = p->link[1];
+         a[k] = 1;
+       }
+      k++;
+    }
+  tree->count--;
+  
+  item = p->data;
+
+  /* D5. */
+  q = &pa[k - 1]->link[(int) a[k - 1]];
+  if (p->link[1] == NULL)
+    {
+      *q = p->link[0];
+      if (*q)
+       (*q)->bal = 0;
+    }
+  else
+    {
+      /* D6. */
+      avl_node *r = p->link[1];
+      if (r->link[0] == NULL)
+       {
+         r->link[0] = p->link[0];
+         *q = r;
+         r->bal = p->bal;
+         a[k] = 1;
+         pa[k++] = r;
+       }
+      else
+       {
+         /* D7. */
+         avl_node *s = r->link[0];
+         int l = k++;
+
+         a[k] = 0;
+         pa[k++] = r;
+           
+         /* D8. */
+         while (s->link[0] != NULL)
+           {
+             r = s;
+             s = r->link[0];
+             a[k] = 0;
+             pa[k++] = r;
+           }
+
+         /* D9. */
+         a[l] = 1;
+         pa[l] = s;
+         s->link[0] = p->link[0];
+         r->link[0] = s->link[1];
+         s->link[1] = p->link[1];
+         s->bal = p->bal;
+         *q = s;
+       }
+    }
+
+#if PSPP
+  if (tree->pool == NULL)
+#endif
+    free (p);
+
+  assert (k > 0);
+  /* D10. */
+  while (--k)
+    {
+      avl_node *s = pa[k], *r;
+
+      if (a[k] == 0)
+       {
+         /* D10. */
+         if (s->bal == -1)
+           {
+             s->bal = 0;
+             continue;
+           }
+         else if (s->bal == 0)
+           {
+             s->bal = 1;
+             break;
+           }
+
+         assert (s->bal == +1);
+         r = s->link[1];
+
+         assert (r != NULL);
+         if (r->bal == 0)
+           {
+             /* D11. */
+             s->link[1] = r->link[0];
+             r->link[0] = s;
+             r->bal = -1;
+             pa[k - 1]->link[(int) a[k - 1]] = r;
+             break;
+           }
+         else if (r->bal == +1)
+           {
+             /* D12. */
+             s->link[1] = r->link[0];
+             r->link[0] = s;
+             s->bal = r->bal = 0;
+             pa[k - 1]->link[(int) a[k - 1]] = r;
+           }
+         else 
+           {
+             /* D13. */
+             assert (r->bal == -1);
+             p = r->link[0];
+             r->link[0] = p->link[1];
+             p->link[1] = r;
+             s->link[1] = p->link[0];
+             p->link[0] = s;
+             if (p->bal == +1)
+               s->bal = -1, r->bal = 0;
+             else if (p->bal == 0)
+               s->bal = r->bal = 0;
+             else
+               {
+                 assert (p->bal == -1);
+                 s->bal = 0, r->bal = +1;
+               }
+             p->bal = 0;
+             pa[k - 1]->link[(int) a[k - 1]] = p;
+           }
+       }
+      else
+       {
+         assert (a[k] == 1);
+
+         /* D10. */
+         if (s->bal == +1)
+           {
+             s->bal = 0;
+             continue;
+           }
+         else if (s->bal == 0)
+           {
+             s->bal = -1;
+             break;
+           }
+
+         assert (s->bal == -1);
+         r = s->link[0];
+
+         if (r == NULL || r->bal == 0)
+           {
+             /* D11. */
+             s->link[0] = r->link[1];
+             r->link[1] = s;
+             r->bal = 1;
+             pa[k - 1]->link[(int) a[k - 1]] = r;
+             break;
+           }
+         else if (r->bal == -1)
+           {
+             /* D12. */
+             s->link[0] = r->link[1];
+             r->link[1] = s;
+             s->bal = r->bal = 0;
+             pa[k - 1]->link[(int) a[k - 1]] = r;
+           }
+         else if (r->bal == +1)
+           {
+             /* D13. */
+             p = r->link[1];
+             r->link[1] = p->link[0];
+             p->link[0] = r;
+             s->link[0] = p->link[1];
+             p->link[1] = s;
+             if (p->bal == -1)
+               s->bal = 1, r->bal = 0;
+             else if (p->bal == 0)
+               s->bal = r->bal = 0;
+             else
+               {
+                 assert (p->bal == 1);
+                 s->bal = 0, r->bal = -1;
+               }
+             p->bal = 0;
+             pa[k - 1]->link[(int) a[k - 1]] = p;
+           }
+       }
+    }
+      
+  return (void *) item;
+}
+
+/* Inserts ITEM into TREE.  Returns NULL if the item was inserted,
+   otherwise a pointer to the duplicate item. */
+void *
+avl_insert (avl_tree *tree, void *item)
+{
+  void **p;
+  
+  assert (tree != NULL);
+  
+  p = avl_probe (tree, item);
+  return (*p == item) ? NULL : *p;
+}
+
+/* If ITEM does not exist in TREE, inserts it and returns NULL.  If a
+   matching item does exist, it is replaced by ITEM and the item
+   replaced is returned.  The caller is responsible for freeing the
+   item returned. */
+void *
+avl_replace (avl_tree *tree, void *item)
+{
+  void **p;
+
+  assert (tree != NULL);
+  
+  p = avl_probe (tree, item);
+  if (*p == item)
+    return NULL;
+  else
+    {
+      void *r = *p;
+      *p = item;
+      return r;
+    }
+}
+
+/* Delete ITEM from TREE when you know that ITEM must be in TREE.  For
+   debugging purposes. */
+void *
+(avl_force_delete) (avl_tree *tree, void *item)
+{
+  void *found = avl_delete (tree, item);
+  assert (found != NULL);
+  return found;
+}
+\f
+#if SELF_TEST
+
+/* Used to flag delayed aborting. */
+int done = 0;
+
+/* Print the structure of node NODE of an avl tree, which is LEVEL
+   levels from the top of the tree.  Uses different delimiters to
+   visually distinguish levels. */
+void
+print_structure (avl_node *node, int level)
+{
+  char lc[] = "([{`/";
+  char rc[] = ")]}'\\";
+
+  assert (level <= 10);
+  
+  if (node == NULL)
+    {
+      printf (" nil");
+      return;
+    }
+  printf (" %c%d", lc[level % 5], (int) node->data);
+  if (node->link[0] || node->link[1])
+    print_structure (node->link[0], level + 1);
+  if (node->link[1])
+    print_structure (node->link[1], level + 1);
+  printf ("%c", rc[level % 5]);
+}
+
+/* Compare two integers A and B and return a strcmp()-type result. */
+int
+compare_ints (const void *a, const void *b, void *param unused)
+{
+  return ((int) a) - ((int) b);
+}
+
+/* Print the value of integer A. */
+void
+print_int (void *a, void *param unused)
+{
+  printf (" %d", (int) a);
+}
+
+/* Linearly print contents of TREE. */
+void
+print_contents (avl_tree *tree)
+{
+  avl_walk (tree, print_int, NULL);
+  printf ("\n");
+}
+
+/* Examine NODE in a avl tree.  *COUNT is increased by the number of
+   nodes in the tree, including the current one.  If the node is the
+   root of the tree, PARENT should be INT_MIN, otherwise it should be
+   the parent node value.  DIR is the direction that the current node
+   is linked from the parent: -1 for left child, +1 for right child;
+   it is not used if PARENT is INT_MIN.  Returns the height of the
+   tree rooted at NODE. */
+int
+recurse_tree (avl_node *node, int *count, int parent, int dir)
+{
+  if (node) 
+    {
+      int d = (int) node->data;
+      int nl = node->link[0] ? recurse_tree (node->link[0], count, d, -1) : 0;
+      int nr = node->link[1] ? recurse_tree (node->link[1], count, d, 1) : 0;
+      (*count)++;
+
+      if (nr - nl != node->bal)
+       {
+         printf (" Node %d is unbalanced: right height=%d, left height=%d, "
+               "difference=%d, but balance factor=%d.\n",
+                 d, nr, nl, nr - nl, node->bal);
+         done = 1;
+       }
+      
+      if (parent != INT_MIN)
+       {
+         assert (dir == -1 || dir == +1);
+         if (dir == -1 && d > parent)
+           {
+             printf (" Node %d is smaller than its left child %d.\n",
+                     parent, d);
+             done = 1;
+           }
+         else if (dir == +1 && d < parent)
+           {
+             printf (" Node %d is larger than its right child %d.\n",
+                     parent, d);
+             done = 1;
+           }
+       }
+      assert (node->bal >= -1 && node->bal <= 1);
+      return 1 + (nl > nr ? nl : nr);
+    }
+  else return 0;
+}
+
+/* Check that everything about TREE is kosher. */
+void
+verify_tree (avl_tree *tree)
+{
+  int count = 0;
+  recurse_tree (tree->root.link[0], &count, INT_MIN, 0);
+  if (count != tree->count)
+    {
+      printf (" Tree has %d nodes, but tree count is %d.\n",
+             count, tree->count);
+      done = 1;
+    }
+  if (done)
+    abort ();
+}
+
+/* Arrange the N elements of ARRAY in random order. */
+void
+shuffle (int *array, int n)
+{
+  int i;
+  
+  for (i = 0; i < n; i++)
+    {
+      int j = i + rand () % (n - i);
+      int t = array[j];
+      array[j] = array[i];
+      array[i] = t;
+    }
+}
+
+/* Compares avl trees rooted at A and B, making sure that they are
+   identical. */
+void
+compare_trees (avl_node *a, avl_node *b)
+{
+  if (a == NULL || b == NULL)
+    {
+      assert (a == NULL && b == NULL);
+      return;
+    }
+  if (a->data != b->data || a->bal != b->bal
+      || ((a->link[0] != NULL) ^ (b->link[0] != NULL))
+      || ((a->link[1] != NULL) ^ (b->link[1] != NULL)))
+    {
+      printf (" Copied nodes differ: %d b=%d a->bal=%d b->bal=%d a:",
+             (int) a->data, (int) b->data, a->bal, b->bal);
+      if (a->link[0])
+       printf ("l");
+      if (a->link[1])
+       printf ("r");
+      printf (" b:");
+      if (b->link[0])
+       printf ("l");
+      if (b->link[1])
+       printf ("r");
+      printf ("\n");
+      abort ();
+    }
+  if (a->link[0] != NULL)
+    compare_trees (a->link[0], b->link[0]);
+  if (a->link[1] != NULL)
+    compare_trees (a->link[1], b->link[1]);
+}
+
+/* Simple stress test procedure for the AVL tree routines.  Does the
+   following:
+
+   * Generate a random number seed.  By default this is generated from
+   the current time.  You can also pass a seed value on the command
+   line if you want to test the same case.  The seed value is
+   displayed.
+
+   * Create a tree and insert the integers from 0 up to TREE_SIZE - 1
+   into it, in random order.  Verify the tree structure after each
+   insertion.
+   
+   * Remove each integer from the tree, in a different random order.
+   After each deletion, verify the tree structure; also, make a copy
+   of the tree into a new tree, verify the copy and compare it to the
+   original, then destroy the copy.
+
+   * Destroy the tree, increment the random seed value, and start over.
+
+   If you make any modifications to the avl tree routines, then you
+   might want to insert some calls to print_structure() at strategic
+   places in order to be able to see what's really going on.  Also,
+   memory debuggers like Checker or Purify are very handy. */
+#define TREE_SIZE 1024
+#define N_ITERATIONS 16
+int
+main (int argc, char **argv)
+{
+  int array[TREE_SIZE];
+  int seed;
+  int iteration;
+  
+  if (argc == 2)
+    seed = atoi (argv[1]);
+  else
+    seed = time (0) * 257 % 32768;
+
+  fputs ("Testing avl...\n", stdout);
+  
+  for (iteration = 1; iteration <= N_ITERATIONS; iteration++)
+    {
+      avl_tree *tree;
+      int i;
+      
+      printf ("Iteration %4d/%4d: seed=%5d", iteration, N_ITERATIONS, seed);
+      fflush (stdout);
+      
+      srand (seed++);
+
+      for (i = 0; i < TREE_SIZE; i++)
+       array[i] = i;
+      shuffle (array, TREE_SIZE);
+      
+      tree = avl_create (compare_ints, NULL);
+      for (i = 0; i < TREE_SIZE; i++)
+       avl_force_insert (tree, (void *) (array[i]));
+      verify_tree (tree);
+
+      shuffle (array, TREE_SIZE);
+      for (i = 0; i < TREE_SIZE; i++)
+       {
+         avl_tree *copy;
+
+         avl_delete (tree, (void *) (array[i]));
+         verify_tree (tree);
+
+         copy = avl_copy (tree, NULL);
+         verify_tree (copy);
+         compare_trees (tree->root.link[0], copy->root.link[0]);
+         avl_destroy (copy, NULL);
+
+         if (i % 128 == 0)
+           {
+             putchar ('.');
+             fflush (stdout);
+           }
+       }
+      fputs (" good.\n", stdout);
+
+      avl_destroy (tree, NULL);
+    }
+  
+  return 0;
+}
+#endif /* SELF_TEST */
+
+/*
+  Local variables:
+  compile-command: "gcc -DSELF_TEST=1 -W -Wall -I. -o ./avl-test avl.c"
+  End:
+*/
+
diff --git a/src/avl.h b/src/avl.h
new file mode 100644 (file)
index 0000000..8835f17
--- /dev/null
+++ b/src/avl.h
@@ -0,0 +1,142 @@
+/* libavl - manipulates AVL trees.
+   Copyright (C) 1998-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* This is file avl.h in libavl, version 1.1.0. */
+
+#if !avl_h
+#define avl_h 1
+
+/* This stack size allows for AVL trees for between 5,704,880 and
+   4,294,967,295 nodes, depending on order of insertion.  If you
+   increase this it will require recoding some functions that assume
+   one long is big enough for a bitmap. */
+#ifndef AVL_MAX_HEIGHT
+#define AVL_MAX_HEIGHT 32
+#endif
+
+/* Structure for a node in an AVL tree. */
+typedef struct avl_node
+  {
+    void *data;                        /* Pointer to data. */
+    struct avl_node *link[2];  /* Subtrees. */
+    signed char bal;           /* Balance factor. */
+    char cache;                        /* Used during insertion. */
+    signed char pad[2];                /* Unused.  Reserved for threaded trees. */
+  }
+avl_node;
+
+/* Used for traversing an AVL tree. */
+typedef struct avl_traverser
+  {
+    int init;                  /* Initialized? */
+    int nstack;                        /* Top of stack. */
+    const avl_node *p;         /* Used for traversal. */
+    const avl_node *stack[AVL_MAX_HEIGHT];/* Descended trees. */
+  }
+avl_traverser;
+
+#define avl_traverser_init(TRAVERSER) (TRAVERSER).init = 0
+
+/* Function types. */
+#if !AVL_FUNC_TYPES
+#define AVL_FUNC_TYPES 1
+typedef int (*avl_comparison_func) (const void *a, const void *b, void *param);
+typedef void (*avl_node_func) (void *data, void *param);
+typedef void *(*avl_copy_func) (void *data, void *param);
+#endif
+
+/* Structure which holds information about an AVL tree. */
+typedef struct avl_tree
+  {
+#if PSPP
+    struct pool *pool;         /* Pool to store nodes. */
+#endif
+    avl_node root;             /* Tree root node. */
+    avl_comparison_func cmp;   /* Used to compare keys. */
+    int count;                 /* Number of nodes in the tree. */
+    void *param;               /* Arbitary user data. */
+  }
+avl_tree;
+
+#if PSPP
+#define MAYBE_POOL struct pool *pool,
+#else
+#define MAYBE_POOL /* nothing */
+#endif
+
+/* General functions. */
+avl_tree *avl_create (MAYBE_POOL avl_comparison_func, void *param);
+void avl_destroy (avl_tree *, avl_node_func);
+void avl_free (avl_tree *);
+int avl_count (const avl_tree *);
+avl_tree *avl_copy (MAYBE_POOL const avl_tree *, avl_copy_func);
+
+/* Walk the tree. */
+void avl_walk (const avl_tree *, avl_node_func, void *param);
+void *avl_traverse (const avl_tree *, avl_traverser *);
+
+/* Search for a given item. */
+void **avl_probe (avl_tree *, void *);
+void *avl_delete (avl_tree *, const void *);
+void *avl_find (const avl_tree *, const void *);
+
+#if __GCC__ >= 2
+extern inline void *
+avl_insert (avl_tree *tree, void *item)
+{
+  void **p = avl_probe (tree, item);
+  return (*p == item) ? NULL : *p;
+}
+
+extern inline void *
+avl_replace (avl_tree *tree, void *item)
+{
+  void **p = avl_probe (tree, item);
+  if (*p == item)
+    return NULL;
+  else
+    {
+      void *r = *p;
+      *p = item;
+      return r;
+    }
+}
+#else /* not gcc */
+void *avl_insert (avl_tree *tree, void *item);
+void *avl_replace (avl_tree *tree, void *item);
+#endif /* not gcc */
+
+/* Easy assertions on insertion & deletion. */
+#ifndef NDEBUG
+#define avl_force_insert(A, B)                 \
+       do                                      \
+         {                                     \
+            void *r = avl_insert (A, B);       \
+           assert (r == NULL);                 \
+         }                                     \
+       while (0)
+void *avl_force_delete (avl_tree *, void *);
+#else
+#define avl_force_insert(A, B)                 \
+       avl_insert (A, B)
+#define avl_force_delete(A, B)                 \
+       avl_delete (A, B)
+#endif
+
+#endif /* avl_h */
diff --git a/src/bitvector.h b/src/bitvector.h
new file mode 100644 (file)
index 0000000..6c3a878
--- /dev/null
@@ -0,0 +1,45 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !bitvector_h
+#define bitvector_h 1
+
+#include <limits.h>
+
+/* Sets bit Y starting at address X. */
+#define SET_BIT(X, Y)                                  \
+       (((unsigned char *) X)[(Y) / CHAR_BIT] |= 1 << ((Y) % CHAR_BIT))
+
+/* Clears bit Y starting at address X. */
+#define CLEAR_BIT(X, Y)                                \
+       (((unsigned char *) X)[(Y) / CHAR_BIT] &= ~(1 << ((Y) % CHAR_BIT)))
+
+/* Sets bit Y starting at address X to Z, which is zero/nonzero */
+#define SET_BIT_TO(X, Y, Z)                    \
+       ((Z) ? SET_BIT(X, Y) : CLEAR_BIT(X, Y))
+
+/* Nonzero if bit Y starting at address X is set. */
+#define TEST_BIT(X, Y)                                         \
+       (((unsigned char *) X)[(Y) / CHAR_BIT] & (1 << ((Y) % CHAR_BIT)))
+
+/* Returns 2**X, 0 <= X < 32. */
+#define BIT_INDEX(X)                           \
+       (1ul << (X))
+
+#endif /* bitvector.h */
diff --git a/src/cases.c b/src/cases.c
new file mode 100644 (file)
index 0000000..814a872
--- /dev/null
@@ -0,0 +1,129 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "cases.h"
+#include "var.h"
+#include "vfm.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* Initializes V. */
+void
+vec_init (struct long_vec * v)
+{
+  v->vec = NULL;
+  v->n = v->m = 0;
+}
+
+/* Deletes the contents of V. */
+void
+vec_clear (struct long_vec * v)
+{
+  free (v->vec);
+  v->vec = NULL;
+  v->n = v->m = 0;
+}
+
+/* Inserts ELEM into V. */
+void
+vec_insert (struct long_vec * v, long elem)
+{
+  if (v->n >= v->m)
+    {
+      v->m = (v->m == 0 ? 16 : 2 * v->m);
+      v->vec = xrealloc (v->vec, v->m * sizeof *v->vec);
+    }
+  v->vec[v->n++] = elem;
+}
+
+/* Deletes all occurrences of values A through B exclusive from V. */
+void
+vec_delete (struct long_vec * v, long a, long b)
+{
+  int i;
+
+  for (i = v->n - 1; i >= 0; i--)
+    if (v->vec[i] >= a && v->vec[i] < b)
+      v->vec[i] = v->vec[--v->n];
+}
+
+/* Sticks V->FV in the proper vector. */
+void
+envector (const struct variable *v)
+{
+  if (v->type == NUMERIC)
+    {
+      if (v->left)
+       vec_insert (&init_zero, v->fv);
+      else
+       vec_insert (&reinit_sysmis, v->fv);
+    }
+  else
+    {
+      int i;
+
+      if (v->left)
+       for (i = v->fv; i < v->fv + v->nv; i++)
+         vec_insert (&init_blanks, i);
+      else
+       for (i = v->fv; i < v->fv + v->nv; i++)
+         vec_insert (&reinit_blanks, i);
+    }
+}
+
+/* Removes V->FV from the proper vector. */
+void
+devector (const struct variable *v)
+{
+  if (v->type == NUMERIC)
+    {
+      if (v->left)
+       vec_delete (&init_zero, v->fv, v->fv + 1);
+      else
+       vec_delete (&reinit_sysmis, v->fv, v->fv + 1);
+    }
+  else if (v->left)
+    vec_delete (&init_blanks, v->fv, v->fv + v->nv);
+  else
+    vec_delete (&reinit_blanks, v->fv, v->fv + v->nv);
+}
diff --git a/src/cases.h b/src/cases.h
new file mode 100644 (file)
index 0000000..b786723
--- /dev/null
@@ -0,0 +1,42 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !cases_h
+#define cases_h 1
+
+/* Vectors. */
+
+/* A vector of longs. */
+struct long_vec
+  {
+    long *vec;                 /* Contents. */
+    int n;                     /* Number of elements. */
+    int m;                     /* Number of elements room is allocated for. */
+  };
+
+struct variable;
+
+void vec_init (struct long_vec *);
+void vec_clear (struct long_vec *);
+void vec_insert (struct long_vec *, long);
+void vec_delete (struct long_vec *, long a, long b);
+void devector (const struct variable *);
+void envector (const struct variable *);
+
+#endif /* !cases_h */
diff --git a/src/cmdline.c b/src/cmdline.c
new file mode 100644 (file)
index 0000000..bb9b9ba
--- /dev/null
@@ -0,0 +1,257 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "error.h"
+#include "filename.h"
+#include "getline.h"
+#include "main.h"
+#include "output.h"
+#include "settings.h"
+#include "str.h"
+#include "var.h"
+#include "version.h"
+
+void welcome (void);
+static void usage (void);
+
+char *subst_vars (char *);
+
+/* Parses the command line specified by ARGC and ARGV as received by
+   main(). */
+void
+parse_command_line (int argc, char **argv)
+{
+  static struct option long_options[] =
+  {
+    {"command", required_argument, NULL, 'c'},
+    {"config-directory", required_argument, NULL, 'B'},
+    {"device", required_argument, NULL, 'o'},
+    {"dry-run", no_argument, NULL, 'n'},
+    {"edit", no_argument, NULL, 'n'},
+    {"help", no_argument, NULL, 'h'},
+    {"include-directory", required_argument, NULL, 'I'},
+    {"interactive", no_argument, NULL, 'i'},
+    {"just-print", no_argument, NULL, 'n'},
+    {"list", no_argument, NULL, 'l'},
+    {"no-include", no_argument, NULL, 'I'},
+    {"no-statrc", no_argument, NULL, 'r'},
+    {"out-file", required_argument, NULL, 'f'},
+    {"pipe", no_argument, NULL, 'p'},
+    {"recon", no_argument, NULL, 'n'},
+    {"safer", no_argument, NULL, 's'},
+    {"testing-mode", no_argument, &set_testing_mode, 1},
+    {"verbose", no_argument, NULL, 'v'},
+    {"version", no_argument, NULL, 'V'},
+    {0, 0, 0, 0},
+  };
+
+  int c, i;
+
+  int cleared_device_defaults = 0;
+
+  int no_statrc = 0;
+
+  for (;;)
+    {
+      c = getopt_long (argc, argv, "B:c:f:hiI:lno:prsvV", long_options, NULL);
+      if (c == -1)
+       break;
+
+      switch (c)
+       {
+       case 'c':
+         {
+           static int n_cmds;
+           
+           struct getl_script *script = xmalloc (sizeof *script);
+           
+           {
+             struct getl_line_list *line;
+
+             script->first_line = line = xmalloc (sizeof *line);
+             line->line = xstrdup ("commandline");
+             line->len = --n_cmds;
+             line = line->next = xmalloc (sizeof *line);
+             line->line = xstrdup (optarg);
+             line->len = strlen (optarg);
+             line->next = NULL;
+           }
+
+           getl_add_virtual_file (script);
+         }
+         break;
+       case 'B':
+         config_path = optarg;
+         break;
+       case 'f':
+         printf (_("-f not yet implemented\n"));
+         break;
+       case 'h':
+         usage ();
+         assert (0);
+       case 'i':
+         getl_interactive = 2;
+         break;
+       case 'I':
+         if (optarg == NULL || !strcmp (optarg, "-"))
+           getl_clear_include_path ();
+         else
+           getl_add_include_dir (optarg);
+         break;
+       case 'l':
+         outp_list_classes ();
+         err_hcf (1);
+       case 'n':
+         printf (_("-n not yet implemented\n"));
+         break;
+       case 'o':
+         if (!cleared_device_defaults)
+           {
+             outp_configure_clear ();
+             cleared_device_defaults = 1;
+           }
+         outp_configure_add (optarg);
+         break;
+       case 'p':
+         printf (_("-p not yet implemented\n"));
+         break;
+       case 'r':
+         no_statrc = 1;
+         break;
+       case 's':
+         set_safer = 1;
+         break;
+       case 'v':
+         err_verbosity++;
+         break;
+       case 'V':
+         puts (version);
+         puts (_("\nCopyright (C) 1997-9, 2000 Free Software Foundation, "
+                 "Inc.\n"
+                 "This is free software; see the source for copying "
+                 "conditions.  There is NO\n"
+                 "WARRANTY; not even for MERCHANTABILITY or FITNESS "
+                 "FOR A PARTICULAR PURPOSE.\n\n"
+                 "Written by Ben Pfaff <blp@gnu.org>."));
+         err_hcf (1);
+       case '?':
+         usage ();
+         assert (0);
+       case 0:
+         break;
+       default:
+         assert (0);
+       }
+    }
+
+  if (set_testing_mode)
+    {
+      /* FIXME: Later this option should do some other things, too. */
+      set_viewwidth = 79;
+    }
+
+  for (i = optind; i < argc; i++)
+    {
+      int separate = 1;
+
+      if (!strcmp (argv[i], "+"))
+       {
+         separate = 0;
+         if (++i >= argc)
+           usage ();
+       }
+      else if (strchr (argv[i], '='))
+       {
+         outp_configure_macro (argv[i]);
+         continue;
+       }
+      getl_add_file (argv[i], separate, 0);
+    }
+  if (getl_head)
+    getl_head->separate = 0;
+
+  if (getl_am_interactive)
+    getl_interactive = 1;
+
+  if (!no_statrc)
+    {
+      char *pspprc_fn = fn_search_path ("rc", config_path, NULL);
+
+      if (pspprc_fn)
+       getl_add_file (pspprc_fn, 0, 1);
+
+      free (pspprc_fn);
+    }
+}
+
+/* Message that describes PSPP command-line syntax. */
+static const char pre_syntax_message[] =
+N_("PSPP, a program for statistical analysis of sample data.\n"
+"\nUsage: %s [OPTION]... FILE...\n"
+"\nIf a long option shows an argument as mandatory, then it is mandatory\n"
+"for the equivalent short option also.  Similarly for optional arguments.\n"
+"\nConfiguration:\n"
+"  -B, --config-dir=DIR      set configuration directory to DIR\n"
+"  -o, --device=DEVICE       select output driver DEVICE and disable defaults\n"
+"  -d, --define=VAR[=VALUE]  set environment variable VAR to VALUE, or empty\n"
+"  -u, --undef=VAR           undefine environment variable VAR\n"
+"\nInput and output:\n"
+"  -f, --out-file=FILE       send output to FILE (overwritten)\n"
+"  -p, --pipe                read script from stdin, send output to stdout\n"
+"  -I-, --no-include         clear include path\n"
+"  -I, --include=DIR         append DIR to include path\n"
+"  -c, --command=COMMAND     execute COMMAND before .pspp/rc at startup\n"
+"\nLanguage modifiers:\n"
+"  -i, --interactive         interpret scripts in interactive mode\n"
+"  -n, --edit                just check syntax; don't actually run the code\n"
+"  -r, --no-statrc           disable execution of .pspp/rc at startup\n"
+"  -s, --safer               don't allow some unsafe operations\n"
+"\nInformative output:\n"
+"  -h, --help                print this help, then exit\n"
+"  -l, --list                print a list of known driver classes, then exit\n"
+"  -V, --version             show PSPP version, then exit\n"
+"  -v, --verbose             increments verbosity level\n"
+"\nNon-option arguments:\n"
+" FILE1 FILE2                run FILE1, clear the dictionary, run FILE2\n"
+" FILE1 + FILE2              run FILE1 then FILE2 without clearing dictionary\n"
+" KEY=VALUE                  overrides macros in output initialization file\n"
+"\n");
+
+/* Message that describes PSPP command-line syntax, continued. */
+static const char post_syntax_message[] = 
+N_("\nReport bugs to <bug-gnu-pspp@gnu.org>.\n");
+
+/* Writes a syntax description to stdout and terminates. */
+static void
+usage (void)
+{
+  printf (gettext (pre_syntax_message), pgmname);
+  outp_list_classes ();
+  printf (gettext (post_syntax_message));
+
+  err_hcf (1);
+}
diff --git a/src/command.c b/src/command.c
new file mode 100644 (file)
index 0000000..40c186e
--- /dev/null
@@ -0,0 +1,791 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include "alloc.h"
+#include "command.h"
+#include "error.h"
+#include "getline.h"
+#include "lexer.h"
+#include "main.h"
+#include "settings.h"
+#include "som.h"
+#include "str.h"
+#include "tab.h"
+#include "var.h"
+#include "vfm.h"
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+\f
+/* Global variables. */
+
+/* A STATE_* constant giving the current program state. */
+int pgm_state;
+
+/* The name of the procedure currently executing, if any. */
+const char *cur_proc;
+\f
+/* Static variables. */
+
+/* A single command. */
+struct command
+  {
+    /* Initialized statically. */
+    char cmd[22];              /* Command name. */
+    int transition[4];         /* Transitions to make from each state. */
+    int (*func) (void);                /* Function to call. */
+
+    /* Calculated at startup time. */
+    char *word[3];             /* cmd[], divided into individual words. */
+    struct command *next;      /* Next command with same word[0]. */
+  };
+
+/* Prototype all the command functions. */
+#define DEFCMD(NAME, T1, T2, T3, T4, FUNC)     \
+       int FUNC (void);
+#define UNIMPL(NAME, T1, T2, T3, T4)
+#include "command.def"
+#undef DEFCMD
+#undef UNIMPL
+
+/* Define the command array. */
+#define DEFCMD(NAME, T1, T2, T3, T4, FUNC)             \
+       {NAME, {T1, T2, T3, T4}, FUNC, {NULL, NULL, NULL}, NULL},
+#define UNIMPL(NAME, T1, T2, T3, T4)                   \
+       {NAME, {T1, T2, T3, T4}, NULL, {NULL, NULL, NULL}, NULL},
+static struct command cmd_table[] = 
+  {
+#include "command.def"
+    {"", {ERRO, ERRO, ERRO, ERRO}, NULL, {NULL, NULL, NULL}, NULL},
+  };
+#undef DEFCMD
+#undef UNIMPL
+\f
+/* Command parser. */
+
+static struct command *figure_out_command (void);
+
+/* Breaks the `cmd' member of C into individual words and sets C's
+   word[] member appropriately. */
+static void
+split_words (struct command *c)
+{
+  char *cmd, *save;
+  int i;
+
+  cmd = xstrdup (c->cmd);
+  for (i = 0; i < 3; i++)
+    cmd = c->word[i] = strtok_r (i == 0 ? cmd : NULL, " -", &save);
+}
+
+/* Initializes the command parser. */
+void
+cmd_init (void)
+{
+  struct command *c;
+
+  /* Break up command names into words. */
+  for (c = cmd_table; c->cmd[0]; c++)
+    split_words (c);
+
+  /* Make chains of commands having the same first word. */
+  for (c = cmd_table; c->cmd[0]; c++)
+    {
+      struct command *first;
+      for (first = c; c[1].word[0] && !strcmp (c[0].word[0], c[1].word[0]); c++)
+       c->next = c + 1;
+
+      c->next = NULL;
+    }
+}
+
+/* Determines whether command C is appropriate to call in this
+   part of a FILE TYPE structure. */
+static int
+FILE_TYPE_okay (struct command *c)
+{
+  int okay = 0;
+  
+  if (c->func != cmd_record_type
+      && c->func != cmd_data_list
+      && c->func != cmd_repeating_data
+      && c->func != cmd_end_file_type)
+    msg (SE, _("%s not allowed inside FILE TYPE/END FILE TYPE."), c->cmd);
+#if 0
+  /* FIXME */
+  else if (c->func == cmd_repeating_data && fty.type == FTY_GROUPED)
+    msg (SE, _("%s not allowed inside FILE TYPE GROUPED/END FILE TYPE."),
+        c->cmd);
+  else if (!fty.had_rec_type && c->func != cmd_record_type)
+    msg (SE, _("RECORD TYPE must be the first command inside a "
+                     "FILE TYPE structure."));
+#endif
+  else
+    okay = 1;
+
+#if 0
+  if (c->func == cmd_record_type)
+    fty.had_rec_type = 1;
+#endif
+
+  return okay;
+}
+
+/* Parses an entire PSPP command.  This includes everything from the
+   command name to the terminating dot.  Does most of its work by
+   passing it off to the respective command dispatchers.  Only called
+   by parse() in main.c. */
+int
+cmd_parse (void)
+{
+  struct command *cp;  /* Iterator used to find the proper command. */
+
+#if C_ALLOCA
+  /* The generic alloca package performs garbage collection when it is
+     called with an argument of zero. */
+  alloca (0);
+#endif /* C_ALLOCA */
+
+  /* Null commands can result from extra empty lines. */
+  if (token == '.')
+    return CMD_SUCCESS;
+
+  /* Parse comments. */
+  if ((token == T_ID && !strcmp (tokid, "COMMENT"))
+      || token == T_EXP || token == '*' || token == '[')
+    {
+      lex_skip_comment ();
+      return CMD_SUCCESS;
+    }
+
+  /* Otherwise the line must begin with a command name, which is
+     always an ID token. */
+  if (token != T_ID)
+    {
+      msg (SE, _("This line does not begin with a valid command name."));
+      return CMD_FAILURE;
+    }
+
+  /* Parse the command name. */
+  cp = figure_out_command ();
+  if (cp == NULL)
+    return CMD_FAILURE;
+  if (cp->func == NULL)
+    {
+      msg (SE, _("%s is not yet implemented."), cp->cmd);
+      while (token && token != '.')
+       lex_get ();
+      return CMD_SUCCESS;
+    }
+
+  /* If we're in a FILE TYPE structure, only certain commands can be
+     allowed. */
+  if (pgm_state == STATE_INPUT && vfm_source == &file_type_source
+      && !FILE_TYPE_okay (cp))
+    return CMD_FAILURE;
+
+  /* Certain state transitions are not allowed.  Check for these. */
+  assert (pgm_state >= 0 && pgm_state < STATE_ERROR);
+  if (cp->transition[pgm_state] == STATE_ERROR)
+    {
+      static const char *state_name[4] =
+      {
+       N_("%s is not allowed (1) before a command to specify the "
+          "input program, such as DATA LIST, (2) between FILE TYPE "
+          "and END FILE TYPE, (3) between INPUT PROGRAM and END "
+          "INPUT PROGRAM."),
+       N_("%s is not allowed within an input program."),
+       N_("%s is only allowed within an input program."),
+       N_("%s is only allowed within an input program."),
+      };
+
+      msg (SE, gettext (state_name[pgm_state]), cp->cmd);
+      return CMD_FAILURE;
+    }
+
+#if DEBUGGING
+  if (cp->func != cmd_remark)
+    printf (_("%s command beginning\n"), cp->cmd);
+#endif
+
+  /* The structured output manager numbers all its tables.  Increment
+     the major table number for each separate procedure. */
+  som_new_series ();
+  
+  {
+    int result;
+    
+    /* Call the command dispatcher.  Save and restore the name of
+       the current command around this call. */
+    {
+      const char *prev_proc;
+      
+      prev_proc = cur_proc;
+      cur_proc = cp->cmd;
+      result = cp->func ();
+      cur_proc = prev_proc;
+    }
+    
+    /* Perform the state transition if the command completed
+       successfully (at least in part). */
+    if (result != 0)
+      {
+       pgm_state = cp->transition[pgm_state];
+
+       if (pgm_state == STATE_ERROR)
+         {
+           discard_variables ();
+           pgm_state = STATE_INIT;
+         }
+      }
+
+#if DEBUGGING
+    if (cp->func != cmd_remark)
+      printf (_("%s command completed\n\n"), cp->cmd);
+#endif
+
+    /* Pass the command's success value up to the caller. */
+    return result;
+  }
+}
+
+/* Parse the command name and return a pointer to the corresponding
+   struct command if successful.
+   If not successful, return a null pointer. */
+static struct command *
+figure_out_command (void)
+{
+  static const char *unk =
+    N_("The identifier(s) specified do not form a valid command name:");
+
+  static const char *inc = 
+    N_("The identifier(s) specified do not form a complete command name:");
+
+  struct command *cp;
+
+  /* Parse the INCLUDE short form.
+     Note that `@' is a valid character in identifiers. */
+  if (tokid[0] == '@')
+    return &cmd_table[0];
+
+  /* Find a command whose first word matches this identifier.
+     If it is the only command that begins with this word, return
+     it. */
+  for (cp = cmd_table; cp->cmd[0]; cp++)
+    if (lex_id_match (cp->word[0], tokid))
+      break;
+
+  if (cp->cmd[0] == '\0')
+    {
+      msg (SE, "%s %s.", gettext (unk), ds_value (&tokstr));
+      return NULL;
+    }
+
+  if (cp->next == NULL)
+    return cp;
+  
+  /* We know that there is more than one command starting with this
+     word.  Read the next word in the command name. */
+  {
+    struct command *ocp = cp;
+    
+    /* Verify that the next token is an identifier, because we
+       must disambiguate this command name. */
+    lex_get ();
+    if (token != T_ID)
+      {
+       /* If there's a command whose name is the first word only,
+          return it.  This happens with, i.e., PRINT vs. PRINT
+          SPACE. */
+       if (ocp->word[1] == NULL)
+         return ocp;
+       
+       msg (SE, "%s %s.", gettext (inc), ds_value (&tokstr));
+       return NULL;
+      }
+
+    for (; cp; cp = cp->next)
+      if (cp->word[1] && lex_id_match (cp->word[1], tokid))
+       break;
+
+    if (cp == NULL)
+      {
+       /* No match.  If there's a command whose name is the first
+          word only, return it.  This happens with, i.e., PRINT
+          vs. PRINT SPACE. */
+       if (ocp->word[1] == NULL)
+         return ocp;
+       
+       msg (SE, "%s %s %s.", gettext (unk), ocp->word[0], tokid);
+       return NULL;
+      }
+  
+    /* Check whether the next token is an identifier.
+       If not, bail. */
+    if (!isalpha ((unsigned char) (lex_look_ahead ())))
+      {
+       /* Check whether there is an unambiguous interpretation.
+          If not, give an error. */
+       if (cp->word[2]
+           && cp->next
+           && !strcmp (cp->word[1], cp->next->word[1]))
+         {
+           msg (SE, "%s %s %s.", gettext (inc), ocp->word[0], ocp->word[1]);
+           return NULL;
+         }
+       else
+         return cp;
+      }
+  }
+  
+  /* If this command can have a third word, disambiguate based on it. */
+  if (cp->word[2]
+      || (cp->next
+         && cp->next->word[2]
+         && !strcmp (cp->word[1], cp->next->word[1])))
+    {
+      struct command *ocp = cp;
+      
+      lex_get ();
+      assert (token == T_ID);
+
+      /* Try to find a command with this third word.
+        If found, bail. */
+      for (; cp; cp = cp->next)
+       if (cp->word[2]
+           && !strcmp (cp->word[1], ocp->word[1])
+           && lex_id_match (cp->word[2], tokid))
+         break;
+
+      if (cp != NULL)
+       return cp;
+
+      /* If no command with this third word found, make sure that
+        there's a command with those first two words but without a
+        third word. */
+      cp = ocp;
+      if (cp->word[2])
+       {
+         msg (SE, "%s %s %s %s.",
+              gettext (unk), ocp->word[0], ocp->word[1], ds_value (&tokstr));
+         return 0;
+       }
+    }
+
+  return cp;
+}
+\f
+/* Simple commands. */
+
+/* Parse and execute EXIT command. */
+int
+cmd_exit (void)
+{
+  if (getl_reading_script)
+    {
+      msg (SE, _("This command is not accepted in a syntax file.  "
+          "Instead, use FINISH to terminate a syntax file."));
+      lex_get ();
+    }
+  else
+    finished = 1;
+
+  return CMD_SUCCESS;
+}
+
+/* Parse and execute FINISH command. */
+int
+cmd_finish (void)
+{
+  /* Do not check for `.'
+     Do not fetch any extra tokens. */
+  if (getl_interactive)
+    {
+      msg (SM, _("This command is not executed "
+          "in interactive mode.  Instead, PSPP drops "
+          "down to the command prompt.  Use EXIT if you really want "
+          "to quit."));
+      getl_close_all ();
+    }
+  else
+    finished = 1;
+
+  return CMD_SUCCESS;
+}
+
+/* Extracts a null-terminated 8-or-fewer-character PREFIX from STRING.
+   PREFIX is converted to lowercase.  Removes trailing spaces from
+   STRING as a side effect.  */
+static void
+extract_prefix (char *string, char *prefix)
+{
+  /* Length of STRING. */
+  int len;
+
+  /* Points to the null terminator in STRING (`end pointer'). */
+  char *ep;
+
+  /* Strip spaces from end of STRING. */
+  len = strlen (string);
+  while (len && isspace ((unsigned char) string[len - 1]))
+    string[--len] = 0;
+
+  /* Find null terminator. */
+  ep = memchr (string, '\0', 8);
+  if (!ep)
+    ep = &string[8];
+
+  /* Copy prefix, converting to lowercase. */
+  while (string < ep)
+    *prefix++ = tolower ((unsigned char) (*string++));
+  *prefix = 0;
+}
+
+/* Prints STRING on the console and to the listing file, replacing \n
+   by newline. */
+static void
+output_line (char *string)
+{
+  /* Location of \n in line read in. */
+  char *cp;
+
+  cp = strstr (string, "\\n");
+  while (cp)
+    {
+      *cp = 0;
+      tab_output_text (TAB_LEFT | TAT_NOWRAP, string);
+      string = &cp[2];
+      cp = strstr (string, "\\n");
+    }
+  tab_output_text (TAB_LEFT | TAT_NOWRAP, string);
+}
+
+/* Parse and execute REMARK command. */
+int
+cmd_remark ()
+{
+  /* Points to the line read in. */
+  char *s;
+
+  /* Index into s. */
+  char *cp;
+
+  /* 8-character sentinel used to terminate remark. */
+  char sentinel[9];
+
+  /* Beginning of line used to compare with SENTINEL. */
+  char prefix[9];
+
+  som_blank_line ();
+  
+  s = lex_rest_of_line (NULL);
+  if (*s == '-')
+    {
+      output_line (&s[1]);
+      return CMD_SUCCESS;
+    }
+
+  /* Read in SENTINEL from end of current line. */
+  cp = s;
+  while (isspace ((unsigned char) *cp))
+    cp++;
+  extract_prefix (cp, sentinel);
+  if (sentinel[0] == 0)
+    {
+      msg (SE, _("The sentinel may not be the empty string."));
+      return CMD_FAILURE;
+    }
+
+  /* Read in other lines until we encounter the sentinel. */
+  while (getl_read_line ())
+    {
+      extract_prefix (ds_value (&getl_buf), prefix);
+      if (!strcmp (sentinel, prefix))
+       break;
+
+      /* Output the line. */
+      output_line (ds_value (&getl_buf));
+    }
+
+  /* Calling lex_entire_line() forces the sentinel line to be
+     discarded. */
+  getl_prompt = GETL_PRPT_STANDARD;
+  lex_entire_line ();
+
+  return CMD_SUCCESS;
+}
+
+/* Parses the N command. */
+int
+cmd_n_of_cases (void)
+{
+  /* Value for N. */
+  int x;
+
+  lex_match_id ("N");
+  lex_match_id ("OF");
+  lex_match_id ("CASES");
+  if (!lex_force_int ())
+    return CMD_FAILURE;
+  x = lex_integer ();
+  lex_get ();
+  if (!lex_match_id ("ESTIMATED"))
+    default_dict.N = x;
+
+  return lex_end_of_command ();
+}
+
+/* Parses, performs the EXECUTE procedure. */
+int
+cmd_execute (void)
+{
+  lex_match_id ("EXECUTE");
+  procedure (NULL, NULL, NULL);
+  return lex_end_of_command ();
+}
+
+/* Parses, performs the ERASE command. */
+int
+cmd_erase (void)
+{
+  if (set_safer)
+    {
+      msg (SE, _("This command not allowed when the SAFER option is set."));
+      return CMD_FAILURE;
+    }
+  
+  lex_match_id ("ERASE");
+  if (!lex_force_match_id ("FILE"))
+    return CMD_FAILURE;
+  lex_match ('=');
+  if (!lex_force_string ())
+    return CMD_FAILURE;
+
+  if (remove (ds_value (&tokstr)) == -1)
+    {
+      msg (SW, _("Error removing `%s': %s."),
+          ds_value (&tokstr), strerror (errno));
+      return CMD_FAILURE;
+    }
+
+  return lex_end_of_command ();
+}
+
+#if unix
+/* Spawn a shell process. */
+static int
+shell (void)
+{
+  int pid;
+  
+  pid = fork ();
+  switch (pid)
+    {
+    case 0:
+      {
+       const char *shell_fn;
+       char *shell_process;
+       
+       {
+         int i;
+         
+         for (i = 3; i < 20; i++)
+           close (i);
+       }
+
+       shell_fn = getenv ("SHELL");
+       if (shell_fn == NULL)
+         shell_fn = "/bin/sh";
+       
+       {
+         const char *cp = strrchr (shell_fn, '/');
+         cp = cp ? &cp[1] : shell_fn;
+         shell_process = local_alloc (strlen (cp) + 8);
+         strcpy (shell_process, "-");
+         strcat (shell_process, cp);
+         if (strcmp (cp, "sh"))
+           shell_process[0] = '+';
+       }
+       
+       execl (shell_fn, shell_process, NULL);
+
+       err_hcf (1);
+      }
+
+    case -1:
+      msg (SE, _("Couldn't fork: %s."), strerror (errno));
+      return 0;
+
+    default:
+      assert (pid > 0);
+      while (wait (NULL) != pid)
+       ;
+      return 1;
+    }
+}
+#endif /* unix */
+
+/* Parses the HOST command argument and executes the specified
+   command.  Returns a suitable command return code. */
+static int
+run_command (void)
+{
+  char *cmd;
+  int string;
+
+  /* Handle either a string argument or a full-line argument. */
+  {
+    int c = lex_look_ahead ();
+
+    if (c == '\'' || c == '"')
+      {
+       lex_get ();
+       if (!lex_force_string ())
+         return CMD_FAILURE;
+       cmd = ds_value (&tokstr);
+       string = 1;
+      }
+    else
+      {
+       cmd = lex_rest_of_line (NULL);
+       string = 0;
+      }
+  }
+
+  /* Execute the command. */
+  if (system (cmd) == -1)
+    msg (SE, _("Error executing command: %s."), strerror (errno));
+
+  /* Finish parsing. */
+  if (string)
+    {
+      lex_get ();
+
+      if (token != '.')
+       {
+         lex_error (_("expecting end of command"));
+         return CMD_TRAILING_GARBAGE;
+       }
+    }
+  else
+    token = '.';
+
+  return CMD_SUCCESS;
+}
+
+/* Parses, performs the HOST command. */
+int
+cmd_host (void)
+{
+  int code;
+
+  if (set_safer)
+    {
+      msg (SE, _("This command not allowed when the SAFER option is set."));
+      return CMD_FAILURE;
+    }
+  
+  lex_match_id ("HOST");
+
+#if unix
+  /* Figure out whether to invoke an interactive shell or to execute a
+     single shell command. */
+  if (lex_look_ahead () == '.')
+    {
+      lex_get ();
+      code = shell () ? CMD_PART_SUCCESS_MAYBE : CMD_SUCCESS;
+    }
+  else
+    code = run_command ();
+#else /* !unix */
+  /* Make sure that the system has a command interpreter, then run a
+     command. */
+  if (system (NULL) != 0)
+    success = run_command ();
+  else
+    {
+      msg (SE, _("No operating system support for this command."));
+      success = CMD_FAILURE;
+    }
+#endif /* !unix */
+
+  return code ? CMD_FAILURE : CMD_SUCCESS;
+}
+
+/* Parses, performs the NEW FILE command. */
+int
+cmd_new_file (void)
+{
+  lex_match_id ("NEW");
+  lex_match_id ("FILE");
+  
+  discard_variables ();
+
+  return lex_end_of_command ();
+}
+
+/* Parses, performs the CLEAR TRANSFORMATIONS command. */
+int
+cmd_clear_transformations (void)
+{
+  lex_match_id ("CLEAR");
+  lex_match_id ("TRANSFORMATIONS");
+
+  if (getl_reading_script)
+    {
+      msg (SW, _("This command is not valid in a syntax file."));
+      return CMD_FAILURE;
+    }
+
+  cancel_transformations ();
+
+  return CMD_SUCCESS;
+}
diff --git a/src/command.def b/src/command.def
new file mode 100644 (file)
index 0000000..beaf774
--- /dev/null
@@ -0,0 +1,134 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* State abbreviations. */
+#define INIT STATE_INIT
+#define INPU STATE_INPUT
+#define TRAN STATE_TRANS
+#define PROC STATE_PROC
+#define ERRO STATE_ERROR
+
+DEFCMD ("@",                      INIT, INPU, TRAN, PROC, cmd_include_at)
+UNIMPL ("ADD FILES",              TRAN, ERRO, TRAN, TRAN)
+DEFCMD ("ADD VALUE LABELS",       ERRO, INPU, TRAN, TRAN, cmd_add_value_labels)
+DEFCMD ("AGGREGATE",              ERRO, ERRO, PROC, TRAN, cmd_aggregate)
+DEFCMD ("APPLY DICTIONARY",       ERRO, ERRO, TRAN, TRAN, cmd_apply_dictionary)
+DEFCMD ("AUTORECODE",             ERRO, ERRO, PROC, PROC, cmd_autorecode)
+DEFCMD ("BEGIN DATA",             ERRO, ERRO, PROC, PROC, cmd_begin_data)
+DEFCMD ("BREAK",                  ERRO, INPU, TRAN, TRAN, cmd_break)
+DEFCMD ("CLEAR TRANSFORMATIONS",  ERRO, INPU, TRAN, TRAN, cmd_clear_transformations)
+DEFCMD ("COMPUTE",                ERRO, INPU, TRAN, TRAN, cmd_compute)
+DEFCMD ("CORRELATIONS",                  ERRO, ERRO, PROC, PROC, cmd_correlations)
+DEFCMD ("CONDESCRIPTIVES",        ERRO, ERRO, PROC, PROC, cmd_descriptives)
+DEFCMD ("COUNT",                  ERRO, INPU, TRAN, TRAN, cmd_count)
+DEFCMD ("CROSSTABS",              ERRO, ERRO, PROC, PROC, cmd_crosstabs)
+DEFCMD ("DATA LIST",              TRAN, INPU, TRAN, TRAN, cmd_data_list)
+DEFCMD ("DESCRIPTIVES",           ERRO, ERRO, PROC, PROC, cmd_descriptives)
+DEFCMD ("DISPLAY",                ERRO, INPU, TRAN, PROC, cmd_display)
+DEFCMD ("DO IF",                  ERRO, INPU, TRAN, TRAN, cmd_do_if)
+DEFCMD ("DO REPEAT",              ERRO, INPU, TRAN, TRAN, cmd_do_repeat)
+DEFCMD ("DOCUMENT",               ERRO, INPU, TRAN, TRAN, cmd_document)
+DEFCMD ("DROP DOCUMENTS",         INIT, INPU, TRAN, PROC, cmd_drop_documents)
+UNIMPL ("EDIT",                   INIT, INPU, TRAN, PROC)
+DEFCMD ("ELSE",                   ERRO, INPU, TRAN, TRAN, cmd_else)
+DEFCMD ("ELSE IF",                ERRO, INPU, TRAN, TRAN, cmd_else_if)
+DEFCMD ("END CASE",               ERRO, INPU, ERRO, ERRO, cmd_end_case)
+DEFCMD ("END FILE",               ERRO, INPU, ERRO, ERRO, cmd_end_file)
+DEFCMD ("END FILE TYPE",          ERRO, TRAN, ERRO, ERRO, cmd_end_file_type)
+DEFCMD ("END IF",                 ERRO, INPU, TRAN, TRAN, cmd_end_if)
+DEFCMD ("END INPUT PROGRAM",      ERRO, TRAN, ERRO, ERRO, cmd_end_input_program)
+DEFCMD ("END LOOP",               ERRO, INPU, TRAN, TRAN, cmd_end_loop)
+DEFCMD ("END REPEAT",             ERRO, INPU, TRAN, TRAN, cmd_end_repeat)
+DEFCMD ("ERASE",                  INIT, INPU, TRAN, PROC, cmd_erase)
+#if GLOBAL_DEBUGGING
+DEFCMD ("EVALUATE",               INIT, INPU, TRAN, PROC, cmd_evaluate)
+#endif
+DEFCMD ("EXECUTE",                ERRO, ERRO, PROC, PROC, cmd_execute)
+DEFCMD ("EXIT",                   INIT, INPU, TRAN, PROC, cmd_exit)
+DEFCMD ("EXPORT",                 ERRO, ERRO, PROC, PROC, cmd_export)
+DEFCMD ("FILE HANDLE",            INIT, INPU, TRAN, PROC, cmd_file_handle)
+DEFCMD ("FILE LABEL",             INIT, INPU, TRAN, PROC, cmd_file_label)
+DEFCMD ("FILE TYPE",              INPU, ERRO, INPU, INPU, cmd_file_type)
+DEFCMD ("FILTER",                 ERRO, ERRO, TRAN, TRAN, cmd_filter)
+DEFCMD ("FINISH",                 INIT, INPU, TRAN, PROC, cmd_finish)
+DEFCMD ("FLIP",                          ERRO, ERRO, PROC, PROC, cmd_flip)
+DEFCMD ("FORMATS",                INIT, INPU, TRAN, PROC, cmd_formats)
+DEFCMD ("FREQUENCIES",            ERRO, ERRO, PROC, PROC, cmd_frequencies)
+DEFCMD ("GET",                    TRAN, ERRO, TRAN, TRAN, cmd_get)
+DEFCMD ("HOST",                   INIT, INPU, TRAN, PROC, cmd_host)
+DEFCMD ("IF",                     ERRO, INPU, TRAN, TRAN, cmd_if)
+DEFCMD ("INCLUDE",                INIT, INPU, TRAN, PROC, cmd_include)
+UNIMPL ("INFO",                   INIT, INPU, TRAN, PROC)
+DEFCMD ("IMPORT",                 TRAN, ERRO, TRAN, TRAN, cmd_import)
+UNIMPL ("INPUT MATRIX",           INIT, INPU, TRAN, PROC)
+DEFCMD ("INPUT PROGRAM",          INPU, ERRO, INPU, INPU, cmd_input_program)
+UNIMPL ("KEYED DATA LIST",        INPU, ERRO, INPU, INPU)
+DEFCMD ("LEAVE",                  ERRO, INPU, TRAN, TRAN, cmd_leave)
+DEFCMD ("LIST",                   ERRO, ERRO, PROC, PROC, cmd_list)
+DEFCMD ("LOOP",                   ERRO, INPU, TRAN, TRAN, cmd_loop)
+DEFCMD ("MATCH FILES",            TRAN, ERRO, TRAN, PROC, cmd_match_files)
+DEFCMD ("MATRIX DATA",            TRAN, ERRO, TRAN, TRAN, cmd_matrix_data)
+DEFCMD ("MEANS",                  ERRO, ERRO, PROC, PROC, cmd_means)
+DEFCMD ("MISSING VALUES",         ERRO, INPU, TRAN, TRAN, cmd_missing_values)
+DEFCMD ("MODIFY VARS",            ERRO, INPU, TRAN, PROC, cmd_modify_vars)
+DEFCMD ("NEW FILE",               INIT, ERRO, INIT, INIT, cmd_new_file)
+DEFCMD ("N OF CASES",             INIT, INPU, TRAN, TRAN, cmd_n_of_cases)
+UNIMPL ("NUMBERED",               INIT, INPU, TRAN, PROC)
+DEFCMD ("NUMERIC",                ERRO, INPU, TRAN, TRAN, cmd_numeric)
+UNIMPL ("UNNUMBERED",             INIT, INPU, TRAN, PROC)
+DEFCMD ("PEARSON CORRELATIONS",          ERRO, ERRO, PROC, PROC, cmd_correlations)
+UNIMPL ("POINT",                  ERRO, INPU, ERRO, ERRO)
+UNIMPL ("PRESERVE",              INIT, INPU, TRAN, PROC)
+DEFCMD ("PRINT",                  ERRO, INPU, TRAN, TRAN, cmd_print)
+DEFCMD ("PRINT EJECT",            ERRO, INPU, TRAN, TRAN, cmd_print_eject)
+DEFCMD ("PRINT FORMATS",          ERRO, INPU, TRAN, TRAN, cmd_print_formats)
+DEFCMD ("PRINT SPACE",            ERRO, INPU, TRAN, TRAN, cmd_print_space)
+UNIMPL ("PROCEDURE OUTPUT",       INIT, INPU, TRAN, PROC)
+DEFCMD ("PROCESS IF",             ERRO, ERRO, TRAN, TRAN, cmd_process_if)
+DEFCMD ("Q",                      INIT, INPU, TRAN, PROC, cmd_exit)
+DEFCMD ("QUIT",                   INIT, INPU, TRAN, PROC, cmd_exit)
+DEFCMD ("RECODE",                 ERRO, INPU, TRAN, TRAN, cmd_recode)
+DEFCMD ("RECORD TYPE",            ERRO, INPU, ERRO, ERRO, cmd_record_type)
+UNIMPL ("REFORMAT",               ERRO, ERRO, TRAN, TRAN)
+DEFCMD ("REMARK",                 INIT, INPU, TRAN, PROC, cmd_remark)
+DEFCMD ("RENAME VARIABLES",       ERRO, INPU, TRAN, PROC, cmd_rename_variables)
+DEFCMD ("REPEATING DATA",         ERRO, INPU, ERRO, ERRO, cmd_repeating_data)
+DEFCMD ("REREAD",                 ERRO, INPU, ERRO, ERRO, cmd_reread)
+UNIMPL ("RESTORE",               INIT, INPU, TRAN, PROC)
+DEFCMD ("SAMPLE",                 ERRO, ERRO, TRAN, TRAN, cmd_sample)
+DEFCMD ("SAVE",                   ERRO, ERRO, PROC, PROC, cmd_save)
+DEFCMD ("SELECT IF",              ERRO, ERRO, TRAN, TRAN, cmd_select_if)
+DEFCMD ("SET",                    INIT, INPU, TRAN, PROC, cmd_set)
+UNIMPL ("SHOW",                   INIT, INPU, TRAN, PROC)
+DEFCMD ("SORT CASES",             ERRO, ERRO, PROC, PROC, cmd_sort_cases)
+DEFCMD ("SPLIT FILE",             ERRO, INPU, TRAN, TRAN, cmd_split_file)
+DEFCMD ("STRING",                 ERRO, INPU, TRAN, TRAN, cmd_string)
+DEFCMD ("SUBTITLE",               INIT, INPU, TRAN, PROC, cmd_subtitle)
+DEFCMD ("SYSFILE INFO",           INIT, INPU, TRAN, PROC, cmd_sysfile_info)
+DEFCMD ("TEMPORARY",              ERRO, ERRO, TRAN, TRAN, cmd_temporary)
+DEFCMD ("TITLE",                  INIT, INPU, TRAN, PROC, cmd_title)
+DEFCMD ("T-TEST",                 ERRO, ERRO, PROC, PROC, cmd_t_test)
+UNIMPL ("UPDATE",                 TRAN, ERRO, TRAN, TRAN)
+DEFCMD ("VALUE LABELS",           ERRO, INPU, TRAN, TRAN, cmd_value_labels)
+DEFCMD ("VARIABLE LABELS",        ERRO, INPU, TRAN, TRAN, cmd_variable_labels)
+DEFCMD ("VECTOR",                 ERRO, INPU, TRAN, TRAN, cmd_vector)
+DEFCMD ("WEIGHT",                 ERRO, INPU, TRAN, TRAN, cmd_weight)
+DEFCMD ("WRITE",                  ERRO, INPU, TRAN, TRAN, cmd_write)
+DEFCMD ("WRITE FORMATS",          ERRO, INPU, TRAN, TRAN, cmd_write_formats)
+DEFCMD ("XSAVE",                  ERRO, INPU, TRAN, TRAN, cmd_xsave)
diff --git a/src/command.h b/src/command.h
new file mode 100644 (file)
index 0000000..0a61996
--- /dev/null
@@ -0,0 +1,49 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !command_h
+#define command_h 1
+
+/* Current program state. */
+enum
+  {
+    STATE_INIT,                        /* Initialization state. */
+    STATE_INPUT,               /* Input state. */
+    STATE_TRANS,               /* Transformation state. */
+    STATE_PROC,                        /* Procedure state. */
+    STATE_ERROR                        /* Invalid state transition. */
+  };
+
+/* Command return values. */
+enum
+  {
+    CMD_FAILURE = 0x1000,      /* Command not executed. */
+    CMD_SUCCESS,               /* Command successfully parsed and executed. */
+    CMD_PART_SUCCESS_MAYBE,    /* Command may have been partially executed. */
+    CMD_PART_SUCCESS,          /* Command fully executed up to error. */
+    CMD_TRAILING_GARBAGE,      /* Command followed by garbage. */
+  };
+
+extern int pgm_state;
+extern const char *cur_proc;
+
+void cmd_init (void);
+int cmd_parse (void);
+
+#endif /* !command_h */
diff --git a/src/compute.c b/src/compute.c
new file mode 100644 (file)
index 0000000..c4ed544
--- /dev/null
@@ -0,0 +1,477 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "approx.h"
+#include "cases.h"
+#include "command.h"
+#include "error.h"
+#include "expr.h"
+#include "lexer.h"
+#include "str.h"
+#include "var.h"
+#include "vector.h"
+
+/* I can't think of any really good reason to disable debugging for
+   this module. */
+/*#undef DEBUGGING */
+#define DEBUGGING 1
+#include "debug-print.h"
+
+/* COMPUTE and IF transformation. */
+struct compute_trns
+  {
+    struct trns_header h;
+
+    /* Destination.  (Used only during parsing.) */
+    struct variable *v;                /* Destvar, if dest isn't a vector elem. */
+    int created;               /* Whether we created the destvar (used only during
+                                  parsing). */
+
+    /* Destination.  (Used during execution.) */
+    struct vector *vec;                /* Destination vector, if dest is a vector elem. */
+    int fv;                    /* `value' index of destination variable. */
+    int width;                 /* Target variable width (string vars only). */
+
+    /* Expressions. */
+    struct expression *vec_elem;               /* Destination vector element expr. */
+    struct expression *target;                 /* Target expression. */
+    struct expression *test;                   /* Test expression (IF only). */
+  };
+
+static int parse_target_expression (struct compute_trns *,
+                                   int (*func_tab[4]) (struct trns_header *, struct ccase *));
+static struct compute_trns *new_trns (void);
+static void delete_trns (struct compute_trns *);
+static void free_trns (struct trns_header *);
+static int parse_var_or_vec (struct compute_trns *);
+\f
+/* COMPUTE. */
+
+static int compute_num (struct trns_header *, struct ccase *);
+static int compute_str (struct trns_header *, struct ccase *);
+static int compute_num_vec (struct trns_header *, struct ccase *);
+static int compute_str_vec (struct trns_header *, struct ccase *);
+
+int
+cmd_compute (void)
+{
+  /* Table of functions to process data. */
+  static int (*func_tab[4]) (struct trns_header *, struct ccase *) =
+    {
+      compute_num,
+      compute_str,
+      compute_num_vec,
+      compute_str_vec,
+    };
+
+  /* Transformation being constructed. */
+  struct compute_trns *c;
+
+  lex_match_id ("COMPUTE");
+
+  c = new_trns ();
+  if (!parse_var_or_vec (c))
+    goto fail;
+
+  if (!lex_force_match ('=')
+      || !parse_target_expression (c, func_tab))
+    goto fail;
+
+  /* Goofy behavior, but compatible: Turn off LEAVE on the destvar. */
+  if (c->v && c->v->left && c->v->name[0] != '#')
+    {
+      devector (c->v);
+      c->v->left = 0;
+      envector (c->v);
+    }
+
+  add_transformation ((struct trns_header *) c);
+
+  return CMD_SUCCESS;
+
+fail:
+  delete_trns (c);
+  return CMD_FAILURE;
+}
+
+static int
+compute_num (struct trns_header * pt, struct ccase * c)
+{
+  struct compute_trns *t = (struct compute_trns *) pt;
+  expr_evaluate (t->target, c, &c->data[t->fv]);
+  return -1;
+}
+
+static int
+compute_num_vec (struct trns_header * pt, struct ccase * c)
+{
+  struct compute_trns *t = (struct compute_trns *) pt;
+
+  /* Index into the vector. */
+  union value index;
+
+  /* Rounded index value. */
+  int rindx;
+
+  expr_evaluate (t->vec_elem, c, &index);
+  rindx = floor (index.f + EPSILON);
+  if (index.f == SYSMIS || rindx < 1 || rindx > t->vec->nv)
+    {
+      if (index.f == SYSMIS)
+       msg (SW, _("When executing COMPUTE: SYSMIS is not a valid value as "
+            "an index into vector %s."), t->vec->name);
+      else
+       msg (SW, _("When executing COMPUTE: %g is not a valid value as "
+            "an index into vector %s."), index.f, t->vec->name);
+      return -1;
+    }
+  expr_evaluate (t->target, c, &c->data[t->vec->v[rindx - 1]->fv]);
+  return -1;
+}
+
+static int
+compute_str (struct trns_header * pt, struct ccase * c)
+{
+  struct compute_trns *t = (struct compute_trns *) pt;
+
+  /* Temporary storage for string expression return value. */
+  union value v;
+
+  expr_evaluate (t->target, c, &v);
+  st_bare_pad_len_copy (c->data[t->fv].s, &v.c[1], t->width, v.c[0]);
+  return -1;
+}
+
+static int
+compute_str_vec (struct trns_header * pt, struct ccase * c)
+{
+  struct compute_trns *t = (struct compute_trns *) pt;
+
+  /* Temporary storage for string expression return value. */
+  union value v;
+
+  /* Index into the vector. */
+  union value index;
+
+  /* Rounded index value. */
+  int rindx;
+
+  /* Variable reference by indexed vector. */
+  struct variable *vr;
+
+  expr_evaluate (t->vec_elem, c, &index);
+  rindx = floor (index.f + EPSILON);
+  if (index.f == SYSMIS || rindx < 1 || rindx > t->vec->nv)
+    {
+      if (index.f == SYSMIS)
+       msg (SW, _("When executing COMPUTE: SYSMIS is not a valid value as "
+            "an index into vector %s."), t->vec->name);
+      else
+       msg (SW, _("When executing COMPUTE: %g is not a valid value as "
+            "an index into vector %s."), index.f, t->vec->name);
+      return -1;
+    }
+
+  expr_evaluate (t->target, c, &v);
+  vr = t->vec->v[rindx - 1];
+  st_bare_pad_len_copy (c->data[vr->fv].s, &v.c[1], vr->width, v.c[0]);
+  return -1;
+}
+\f
+/* IF. */
+
+static int if_num (struct trns_header *, struct ccase *);
+static int if_str (struct trns_header *, struct ccase *);
+static int if_num_vec (struct trns_header *, struct ccase *);
+static int if_str_vec (struct trns_header *, struct ccase *);
+
+int
+cmd_if (void)
+{
+  /* Table of functions to process data. */
+  static int (*func_tab[4]) (struct trns_header *, struct ccase *) =
+    {
+      if_num,
+      if_str,
+      if_num_vec,
+      if_str_vec,
+    };
+
+  /* Transformation being constructed. */
+  struct compute_trns *c;
+
+  lex_match_id ("IF");
+  c = new_trns ();
+
+  /* Test expression. */
+  c->test = expr_parse (PXP_BOOLEAN);
+  if (!c->test)
+    goto fail;
+
+  /* Target variable. */
+  if (!parse_var_or_vec (c))
+    goto fail;
+
+  /* Target expression. */
+  
+  if (!lex_force_match ('=')
+      || !parse_target_expression (c, func_tab))
+    goto fail;
+
+  add_transformation ((struct trns_header *) c);
+
+  return CMD_SUCCESS;
+
+fail:
+  delete_trns (c);
+  return CMD_FAILURE;
+}
+
+static int
+if_num (struct trns_header * pt, struct ccase * c)
+{
+  struct compute_trns *t = (struct compute_trns *) pt;
+
+  if (expr_evaluate (t->test, c, NULL) == 1.0)
+    expr_evaluate (t->target, c, &c->data[t->fv]);
+  return -1;
+}
+
+static int
+if_str (struct trns_header * pt, struct ccase * c)
+{
+  struct compute_trns *t = (struct compute_trns *) pt;
+
+  if (expr_evaluate (t->test, c, NULL) == 1.0)
+    {
+      union value v;
+
+      expr_evaluate (t->target, c, &v);
+      st_bare_pad_len_copy (c->data[t->fv].s, &v.c[1], t->width, v.c[0]);
+    }
+  return -1;
+}
+
+static int
+if_num_vec (struct trns_header * pt, struct ccase * c)
+{
+  struct compute_trns *t = (struct compute_trns *) pt;
+
+  if (expr_evaluate (t->test, c, NULL) == 1.0)
+    {
+      /* Index into the vector. */
+      union value index;
+
+      /* Rounded index value. */
+      int rindx;
+
+      expr_evaluate (t->vec_elem, c, &index);
+      rindx = floor (index.f + EPSILON);
+      if (index.f == SYSMIS || rindx < 1 || rindx > t->vec->nv)
+       {
+         if (index.f == SYSMIS)
+           msg (SW, _("When executing COMPUTE: SYSMIS is not a valid value as "
+                "an index into vector %s."), t->vec->name);
+         else
+           msg (SW, _("When executing COMPUTE: %g is not a valid value as "
+                "an index into vector %s."), index.f, t->vec->name);
+         return -1;
+       }
+      expr_evaluate (t->target, c,
+                          &c->data[t->vec->v[rindx]->fv]);
+    }
+  return -1;
+}
+
+static int
+if_str_vec (struct trns_header * pt, struct ccase * c)
+{
+  struct compute_trns *t = (struct compute_trns *) pt;
+
+  if (expr_evaluate (t->test, c, NULL) == 1.0)
+    {
+      /* Index into the vector. */
+      union value index;
+
+      /* Rounded index value. */
+      int rindx;
+
+      /* Temporary storage for result of target expression. */
+      union value v2;
+
+      /* Variable reference by indexed vector. */
+      struct variable *vr;
+
+      expr_evaluate (t->vec_elem, c, &index);
+      rindx = floor (index.f + EPSILON);
+      if (index.f == SYSMIS || rindx < 1 || rindx > t->vec->nv)
+       {
+         if (index.f == SYSMIS)
+           msg (SW, _("When executing COMPUTE: SYSMIS is not a valid value as "
+                "an index into vector %s."), t->vec->name);
+         else
+           msg (SW, _("When executing COMPUTE: %g is not a valid value as "
+                "an index into vector %s."), index.f, t->vec->name);
+         return -1;
+       }
+      expr_evaluate (t->target, c, &v2);
+      vr = t->vec->v[rindx - 1];
+      st_bare_pad_len_copy (c->data[vr->fv].s, &v2.c[1], vr->width, v2.c[0]);
+    }
+  return -1;
+}
+\f
+/* Code common to COMPUTE and IF. */
+
+/* Checks for type mismatches on transformation C.  Also checks for
+   command terminator, sets the case-handling proc from the array
+   passed. */
+static int
+parse_target_expression (struct compute_trns *c,
+                        int (*proc_list[4]) (struct trns_header *, struct ccase *))
+{
+  int dest_type = c->v ? c->v->type : c->vec->v[0]->type;
+  c->target = expr_parse (dest_type == ALPHA ? PXP_STRING : PXP_NUMERIC);
+  if (!c->target)
+    return 0;
+
+  c->h.proc = proc_list[(dest_type == ALPHA) + 2 * (c->vec != NULL)];
+
+  if (token != '.')
+    {
+      lex_error (_("expecting end of command"));
+      return 0;
+    }
+  
+  return 1;
+}
+
+/* Returns a new struct compute_trns after initializing its fields. */
+static struct compute_trns *
+new_trns (void)
+{
+  struct compute_trns *c = xmalloc (sizeof *c);
+  c->h.proc = NULL;
+  c->h.free = free_trns;
+  c->v = NULL;
+  c->created = 0;
+  c->vec = NULL;
+  c->fv = 0;
+  c->width = 0;
+  c->vec_elem = NULL;
+  c->target = NULL;
+  c->test = NULL;
+  return c;
+}
+
+/* Deletes all the fields in C, the variable C->v if we created it,
+   and C itself. */
+static void
+delete_trns (struct compute_trns * c)
+{
+  free_trns ((struct trns_header *) c);
+  if (c->created)
+    delete_variable (&default_dict, c->v);
+  free (c);
+}
+
+/* Deletes all the fields in C. */
+static void
+free_trns (struct trns_header * pt)
+{
+  struct compute_trns *t = (struct compute_trns *) pt;
+
+  expr_free (t->vec_elem);
+  expr_free (t->target);
+  expr_free (t->test);
+}
+
+/* Parses a variable name or a vector element into C.  If the
+   variable does not exist, it is created.  Returns success. */
+static int
+parse_var_or_vec (struct compute_trns * c)
+{
+  if (!lex_force_id ())
+    return 0;
+  
+  if (lex_look_ahead () == '(')
+    {
+      /* Vector element. */
+      c->vec = find_vector (tokid);
+      if (!c->vec)
+       {
+         msg (SE, _("There is no vector named %s."), tokid);
+         return 0;
+       }
+      
+      lex_get ();
+      if (!lex_force_match ('('))
+       return 0;
+      c->vec_elem = expr_parse (PXP_NUMERIC);
+      if (!c->vec_elem)
+       return 0;
+      if (!lex_force_match (')'))
+       {
+         expr_free (c->vec_elem);
+         return 0;
+       }
+    }
+  else
+    {
+      /* Variable name. */
+      c->v = find_variable (tokid);
+      if (!c->v)
+       {
+         c->v = force_create_variable (&default_dict, tokid, NUMERIC, 0);
+         envector (c->v);
+         c->created = 1;
+       }
+      c->fv = c->v->fv;
+      c->width = c->v->width;
+      lex_get ();
+    }
+  return 1;
+}
+\f
+/* EVALUATE. */
+
+#if GLOBAL_DEBUGGING
+int
+cmd_evaluate (void)
+{
+  struct expression *expr;
+
+  lex_match_id ("EVALUATE");
+  expr = expr_parse (PXP_DUMP);
+  if (!expr)
+    return CMD_FAILURE;
+
+  expr_free (expr);
+  if (token != '.')
+    {
+      msg (SE, _("Extra characters after expression."));
+      return CMD_FAILURE;
+    }
+  
+  return CMD_SUCCESS;
+}
+#endif
diff --git a/src/correlations.q b/src/correlations.q
new file mode 100644 (file)
index 0000000..6b708f6
--- /dev/null
@@ -0,0 +1,166 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "file-handle.h"
+#include "command.h"
+#include "lexer.h"
+#include "var.h"
+/* (headers) */
+
+#undef DEBUGGING
+#define DEBUGGING 1
+#include "debug-print.h"
+
+struct cor_set
+  {
+    struct cor_set *next;
+    struct variable **v1, **v2;
+    int nv1, nv2;
+  };
+
+struct cor_set *cor_list, *cor_last;
+
+struct file_handle *matrix_file;
+
+static void free_correlations_state (void);
+static int internal_cmd_correlations (void);
+
+int
+cmd_correlations (void)
+{
+  int result = internal_cmd_correlations ();
+  free_correlations_state ();
+  return result;
+}
+
+/* (specification)
+   "CORRELATIONS" (cor_):
+     *variables=custom;
+     +missing=miss:!pairwise/listwise,
+             inc:include/exclude;
+     +print=tail:!twotail/onetail,
+           sig:!sig/nosig;
+     +format=fmt:!matrix/serial;
+     +matrix=custom;
+     +statistics[st_]=descriptives,xprod,all.
+*/
+/* (declarations) */
+/* (functions) */
+
+int
+internal_cmd_correlations (void)
+{
+  struct cmd_correlations cmd;
+
+  cor_list = cor_last = NULL;
+  matrix_file = NULL;
+
+  lex_match_id ("PEARSON");
+  lex_match_id ("CORRELATIONS");
+
+  if (!parse_correlations (&cmd))
+    return CMD_FAILURE;
+  free_correlations (&cmd);
+
+  return CMD_SUCCESS;
+}
+
+static int
+cor_custom_variables (struct cmd_correlations *cmd unused)
+{
+  struct variable **v1, **v2;
+  int nv1, nv2;
+  struct cor_set *cor;
+
+  /* Ensure that this is a VARIABLES subcommand. */
+  if (!lex_match_id ("VARIABLES") && (token != T_ID || !is_varname (tokid))
+      && token != T_ALL)
+    return 2;
+  lex_match ('=');
+
+  if (!parse_variables (&default_dict, &v1, &nv1,
+                       PV_NO_DUPLICATE | PV_NUMERIC))
+    return 0;
+  
+  if (lex_match (T_WITH))
+    {
+      if (!parse_variables (&default_dict, &v2, &nv2,
+                           PV_NO_DUPLICATE | PV_NUMERIC))
+       {
+         free (v1);
+         return 0;
+       }
+    }
+  else
+    {
+      nv2 = nv1;
+      v2 = v1;
+    }
+
+  cor = xmalloc (sizeof *cor);
+  cor->next = NULL;
+  cor->v1 = v1;
+  cor->v2 = v2;
+  cor->nv1 = nv1;
+  cor->nv2 = nv2;
+  if (cor_list)
+    cor_last = cor_last->next = cor;
+  else
+    cor_list = cor_last = cor;
+  
+  return 1;
+}
+
+static int
+cor_custom_matrix (struct cmd_correlations *cmd unused)
+{
+  if (!lex_force_match ('('))
+    return 0;
+  
+  if (lex_match ('*'))
+    matrix_file = inline_file;
+  else
+    matrix_file = fh_parse_file_handle ();
+
+  if (!matrix_file)
+    return 0;
+
+  if (!lex_force_match (')'))
+    return 0;
+
+  return 1;
+}
+
+static void
+free_correlations_state (void)
+{
+  struct cor_set *cor, *next;
+
+  for (cor = cor_list; cor != NULL; cor = next)
+    {
+      next = cor->next;
+      if (cor->v1 != cor->v2)
+       free (cor->v2);
+      free (cor->v1);
+      free (cor);
+    }
+}
diff --git a/src/count.c b/src/count.c
new file mode 100644 (file)
index 0000000..2546966
--- /dev/null
@@ -0,0 +1,641 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "approx.h"
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "str.h"
+#include "var.h"
+
+/* Implementation details:
+
+   The S?SS manuals do not specify the order that COUNT subcommands are
+   performed in.  Experiments, however, have shown that they are performed
+   in the order that they are specified in, rather than simultaneously.
+   So, with the two variables A and B, and the two cases,
+
+   A B
+   1 2
+   2 1
+
+   the command COUNT A=A B (1) / B=A B (2) will produce the following
+   results,
+
+   A B
+   1 1
+   1 0
+
+   rather than the results that would be produced if subcommands were
+   simultaneous:
+
+   A B
+   1 1
+   1 1
+
+   Perhaps simultaneity could be implemented as an option.  On the
+   other hand, what good are the above commands?  */
+
+#undef DEBUGGING
+#define DEBUGGING 1
+#include "debug-print.h"
+\f
+/* Definitions. */
+
+enum
+  {
+    CNT_ERROR,                 /* Invalid value. */
+    CNT_SINGLE,                        /* Single value. */
+    CNT_HIGH,                  /* x >= a. */
+    CNT_LOW,                   /* x <= a. */
+    CNT_RANGE,                 /* a <= x <= b. */
+    CNT_ANY,                   /* Count any. */
+    CNT_SENTINEL               /* List terminator. */
+  };
+
+struct cnt_num
+  {
+    int type;
+    double a, b;
+  };
+
+struct cnt_str
+  {
+    int type;
+    char *s;
+  };
+
+struct counting
+  {
+    struct counting *next;
+
+    /* variables to count */
+    struct variable **v;
+    int n;
+
+    /* values to count */
+    int missing;               /* (numeric only)
+                                  0=don't count missing,
+                                  1=count SYSMIS,
+                                  2=count system- and user-missing */
+    union                      /* Criterion values. */
+      {
+       struct cnt_num *n;
+       struct cnt_str *s;
+      }
+    crit;
+  };
+
+struct cnt_var_info
+  {
+    struct cnt_var_info *next;
+
+    struct variable *d;                /* Destination variable. */
+    char n[9];                 /* Name of dest var. */
+
+    struct counting *c;                /* The counting specifications. */
+  };
+
+struct count_trns
+  {
+    struct trns_header h;
+    struct cnt_var_info *specs;
+  };
+
+#if DEBUGGING
+static void debug_print (void);
+#endif
+
+/* First counting in chain. */
+static struct cnt_var_info *head;
+\f
+/* Parser. */
+
+static int count_trns_proc (struct trns_header *, struct ccase *);
+static void count_trns_free (struct trns_header *);
+
+static int parse_numeric_criteria (struct counting *);
+static int parse_string_criteria (struct counting *);
+
+int cmd_count (void);
+
+int
+internal_cmd_count (void)
+{
+  int code = cmd_count ();
+  if (!code)
+    {
+      struct count_trns c;
+      c.specs = head;
+      count_trns_free ((struct trns_header *) & c);
+    }
+  return code;
+}
+
+int
+cmd_count (void)
+{
+  /* Specification currently being parsed. */
+  struct cnt_var_info *cnt;
+
+  /* Counting currently being parsed. */
+  struct counting *c;
+
+  /* Return value from parsing function. */
+  int ret;
+
+  /* Transformation. */
+  struct count_trns *trns;
+
+  lex_match_id ("COUNT");
+
+  /* Parses each slash-delimited specification. */
+  head = cnt = xmalloc (sizeof *cnt);
+  for (;;)
+    {
+      /* Initialize this struct cnt_var_info to ensure proper cleanup. */
+      cnt->next = NULL;
+      cnt->d = NULL;
+      cnt->c = NULL;
+
+      /* Get destination struct variable, or at least its name. */
+      if (!lex_force_id ())
+       goto fail;
+      cnt->d = find_variable (tokid);
+      if (cnt->d)
+       {
+         if (cnt->d->type == ALPHA)
+           {
+             msg (SE, _("Destination cannot be a string variable."));
+             goto fail;
+           }
+       }
+      else
+       strcpy (cnt->n, tokid);
+
+      lex_get ();
+      if (!lex_force_match ('='))
+       goto fail;
+
+      c = cnt->c = xmalloc (sizeof *c);
+      for (;;)
+       {
+         c->next = NULL;
+         c->v = NULL;
+         if (!parse_variables (NULL, &c->v, &c->n, PV_DUPLICATE | PV_SAME_TYPE))
+           goto fail;
+
+         if (!lex_force_match ('('))
+           goto fail;
+
+         ret = (c->v[0]->type == NUMERIC
+                ? parse_numeric_criteria
+                : parse_string_criteria) (c);
+         if (!ret)
+           goto fail;
+
+         if (token == '/' || token == '.')
+           break;
+
+         c = c->next = xmalloc (sizeof *c);
+       }
+
+      if (token == '.')
+       break;
+
+      if (!lex_force_match ('/'))
+       goto fail;
+      cnt = cnt->next = xmalloc (sizeof *cnt);
+    }
+
+  /* Create all the nonexistent destination variables. */
+  for (cnt = head; cnt; cnt = cnt->next)
+    if (!cnt->d)
+      {
+       /* It's legal, though motivationally questionable, to count to
+          the same dest var more than once. */
+       cnt->d = find_variable (cnt->n);
+
+       if (!cnt->d)
+         cnt->d = force_create_variable (&default_dict, cnt->n, NUMERIC, 0);
+      }
+
+#if DEBUGGING
+  debug_print ();
+#endif
+
+  trns = xmalloc (sizeof *trns);
+  trns->h.proc = count_trns_proc;
+  trns->h.free = count_trns_free;
+  trns->specs = head;
+  add_transformation ((struct trns_header *) trns);
+
+  return CMD_SUCCESS;
+
+fail:
+  {
+    struct count_trns t;
+    t.specs = head;
+    count_trns_free ((struct trns_header *) & t);
+    return CMD_FAILURE;
+  }
+}
+
+/* Parses a set of numeric criterion values. */
+static int
+parse_numeric_criteria (struct counting * c)
+{
+  int n = 0;
+  int m = 0;
+
+  c->crit.n = 0;
+  c->missing = 0;
+  for (;;)
+    {
+      struct cnt_num *cur;
+      if (n >= m - 1)
+       {
+         m += 16;
+         c->crit.n = xrealloc (c->crit.n, m * sizeof (struct cnt_num));
+       }
+
+      cur = &c->crit.n[n++];
+      if (token == T_NUM)
+       {
+         cur->a = tokval;
+         lex_get ();
+         if (lex_match_id ("THRU"))
+           {
+             if (token == T_NUM)
+               {
+                 if (!lex_force_num ())
+                   return 0;
+                 cur->b = tokval;
+                 cur->type = CNT_RANGE;
+                 lex_get ();
+
+                 if (cur->a > cur->b)
+                   {
+                     msg (SE, _("%g THRU %g is not a valid range.  The "
+                                "number following THRU must be at least "
+                                "as big as the number preceding THRU."),
+                          cur->a, cur->b);
+                     return 0;
+                   }
+               }
+             else if (lex_match_id ("HI") || lex_match_id ("HIGHEST"))
+               cur->type = CNT_HIGH;
+             else
+               {
+                 lex_error (NULL);
+                 return 0;
+               }
+           }
+         else
+           cur->type = CNT_SINGLE;
+       }
+      else if (lex_match_id ("LO") || lex_match_id ("LOWEST"))
+       {
+         if (!lex_force_match_id ("THRU"))
+           return 0;
+         if (token == T_NUM)
+           {
+             cur->type = CNT_LOW;
+             cur->a = tokval;
+             lex_get ();
+           }
+         else if (lex_match_id ("HI") || lex_match_id ("HIGHEST"))
+           cur->type = CNT_ANY;
+         else
+           {
+             lex_error (NULL);
+             return 0;
+           }
+       }
+      else if (lex_match_id ("SYSMIS"))
+       {
+         if (c->missing < 1)
+           c->missing = 1;
+       }
+      else if (lex_match_id ("MISSING"))
+       c->missing = 2;
+      else
+       {
+         lex_error (NULL);
+         return 0;
+       }
+
+      lex_match (',');
+      if (lex_match (')'))
+       break;
+    }
+
+  c->crit.n[n].type = CNT_SENTINEL;
+  return 1;
+}
+
+/* Parses a set of string criteria values.  The skeleton is the same
+   as parse_numeric_criteria(). */
+static int
+parse_string_criteria (struct counting * c)
+{
+  int len = 0;
+
+  int n = 0;
+  int m = 0;
+
+  int i;
+
+  for (i = 0; i < c->n; i++)
+    if (c->v[i]->width > len)
+      len = c->v[i]->width;
+
+  c->crit.n = 0;
+  for (;;)
+    {
+      struct cnt_str *cur;
+      if (n >= m - 1)
+       {
+         m += 16;
+         c->crit.n = xrealloc (c->crit.n, m * sizeof (struct cnt_str));
+       }
+
+      if (!lex_force_string ())
+       return 0;
+      cur = &c->crit.s[n++];
+      cur->type = CNT_SINGLE;
+      cur->s = malloc (len + 1);
+      st_pad_copy (cur->s, ds_value (&tokstr), len + 1);
+      lex_get ();
+
+      lex_match (',');
+      if (lex_match (')'))
+       break;
+    }
+
+  c->crit.s[n].type = CNT_SENTINEL;
+  return 1;
+}
+\f
+/* Transformation. */
+
+/* Counts the number of values in case C matching counting CNT. */
+static inline int
+count_numeric (struct counting * cnt, struct ccase * c)
+{
+  int counter = 0;
+
+  struct cnt_num *num;
+
+  double cmp;
+  int i;
+
+  for (i = 0; i < cnt->n; i++)
+    {
+      /* Extract the variable value and eliminate missing values. */
+      cmp = c->data[cnt->v[i]->fv].f;
+      if (cmp == SYSMIS)
+       {
+         if (cnt->missing >= 1)
+           counter++;
+         continue;
+       }
+      if (cnt->missing >= 2 && is_num_user_missing (cmp, cnt->v[i]))
+       {
+         counter++;
+         continue;
+       }
+
+      /* Try to find the value in the list. */
+      for (num = cnt->crit.n;; num++)
+       switch (num->type)
+         {
+         case CNT_ERROR:
+           assert (0);
+           break;
+         case CNT_SINGLE:
+           if (approx_ne (cmp, num->a))
+             break;
+           counter++;
+           goto done;
+         case CNT_HIGH:
+           if (approx_lt (cmp, num->a))
+             break;
+           counter++;
+           goto done;
+         case CNT_LOW:
+           if (approx_gt (cmp, num->a))
+             break;
+           counter++;
+           goto done;
+         case CNT_RANGE:
+           if (approx_lt (cmp, num->a) || approx_gt (cmp, num->b))
+             break;
+           counter++;
+           goto done;
+         case CNT_ANY:
+           counter++;
+           goto done;
+         case CNT_SENTINEL:
+           goto done;
+         default:
+           assert (0);
+         }
+    done: ;
+    }
+  return counter;
+}
+
+/* Counts the number of values in case C matching counting CNT. */
+static inline int
+count_string (struct counting * cnt, struct ccase * c)
+{
+  int counter = 0;
+
+  struct cnt_str *str;
+
+  char *cmp;
+  int len;
+
+  int i;
+
+  for (i = 0; i < cnt->n; i++)
+    {
+      /* Extract the variable value, variable width. */
+      cmp = c->data[cnt->v[i]->fv].s;
+      len = cnt->v[i]->width;
+
+      for (str = cnt->crit.s;; str++)
+       switch (str->type)
+         {
+         case CNT_ERROR:
+           assert (0);
+         case CNT_SINGLE:
+           if (memcmp (cmp, str->s, len))
+             break;
+           counter++;
+           goto done;
+         case CNT_SENTINEL:
+           goto done;
+         default:
+           assert (0);
+         }
+    done: ;
+    }
+  return counter;
+}
+
+/* Performs the COUNT transformation T on case C. */
+static int
+count_trns_proc (struct trns_header * trns, struct ccase * c)
+{
+  struct cnt_var_info *info;
+  struct counting *cnt;
+  int counter;
+
+  for (info = ((struct count_trns *) trns)->specs; info; info = info->next)
+    {
+      counter = 0;
+      for (cnt = info->c; cnt; cnt = cnt->next)
+       if (cnt->v[0]->type == NUMERIC)
+         counter += count_numeric (cnt, c);
+       else
+         counter += count_string (cnt, c);
+      c->data[info->d->fv].f = counter;
+    }
+  return -1;
+}
+
+/* Destroys all dynamic data structures associated with T. */
+static void
+count_trns_free (struct trns_header * t)
+{
+  struct cnt_var_info *iter, *next;
+
+  for (iter = ((struct count_trns *) t)->specs; iter; iter = next)
+    {
+      struct counting *i, *n;
+
+      for (i = iter->c; i; i = n)
+       {
+         if (i->n && i->v)
+           {
+             if (i->v[0]->type == NUMERIC)
+               free (i->crit.n);
+             else
+               {
+                 struct cnt_str *s;
+
+                 for (s = i->crit.s; s->type != CNT_SENTINEL; s++)
+                   free (s->s);
+                 free (i->crit.s);
+               }
+           }
+         free (i->v);
+
+         n = i->next;
+         free (i);
+       }
+
+      next = iter->next;
+      free (iter);
+    }
+}
+\f
+/* Debugging. */
+
+#if DEBUGGING
+static void
+debug_print (void)
+{
+  struct cnt_var_info *iter;
+  struct counting *i;
+  int j;
+
+  printf ("COUNT\n");
+  for (iter = head; iter; iter = iter->next)
+    {
+      printf ("  %s=", iter->d->name);
+      for (i = iter->c; i; i = i->next)
+       {
+         for (j = 0; j < i->n; j++)
+           printf ("%s%s", j ? " " : "", i->v[j]->name);
+         printf (" (");
+         if (i->v[0]->type == NUMERIC)
+           {
+             struct cnt_num *n;
+
+             if (i->missing == 2)
+               printf ("MISSING");
+             else if (i->missing == 1)
+               printf ("SYSMIS");
+             else
+               assert (i->missing == 0);
+
+             for (n = i->crit.n; n->type != CNT_SENTINEL; n++)
+               {
+                 if (i->missing && n != i->crit.n)
+                   printf (",");
+                 switch (n->type)
+                   {
+                   case CNT_SINGLE:
+                     printf ("%g", n->a);
+                     break;
+                   case CNT_HIGH:
+                     printf ("%g THRU HIGH", n->a);
+                     break;
+                   case CNT_LOW:
+                     printf ("LOW THRU %g", n->a);
+                     break;
+                   case CNT_RANGE:
+                     printf ("%g THRU %g", n->a, n->b);
+                     break;
+                   case CNT_ANY:
+                     printf ("LOW THRU HIGH");
+                     break;
+                   default:
+                     printf ("<ERROR %d>", n->type);
+                     break;
+                   }
+               }
+           }
+         else
+           {
+             struct cnt_str *s;
+
+             for (s = i->crit.s; s->type != CNT_SENTINEL; s++)
+               {
+                 if (s != i->crit.s)
+                   printf (",");
+                 if (s->type == CNT_SINGLE)
+                   printf ("'%s'", s->s);
+                 else
+                   printf ("<ERROR %d>", s->type);
+               }
+           }
+         printf (")  ");
+       }
+      printf ("\n");
+    }
+}
+#endif /* DEBUGGING */
diff --git a/src/crosstabs.q b/src/crosstabs.q
new file mode 100644 (file)
index 0000000..5ea9a1d
--- /dev/null
@@ -0,0 +1,3311 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* FIXME:
+
+   - Pearson's R (but not Spearman!) is off a little.
+   - T values for Spearman's R and Pearson's R are wrong.
+   - How to calculate significance of symmetric and directional measures?
+   - Asymmetric ASEs and T values for lambda are wrong.
+   - ASE of Goodman and Kruskal's tau is not calculated.
+   - ASE of symmetric somers' d is wrong.
+   - Approx. T of uncertainty coefficient is wrong.
+
+*/
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "alloc.h"
+#include "avl.h"
+#include "hash.h"
+#include "pool.h"
+#include "dcdflib/cdflib.h"
+#include "command.h"
+#include "lexer.h"
+#include "error.h"
+#include "magic.h"
+#include "misc.h"
+#include "stats.h"
+#include "output.h"
+#include "tab.h"
+#include "var.h"
+#include "vfm.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* (specification)
+   crosstabs (crs_):
+     *tables=custom;
+     +variables=custom;
+     +missing=miss:!table/include/report;
+     +write[wr_]=none,cells,all;
+     +format=fmt:!labels/nolabels/novallabs,
+            val:!avalue/dvalue,
+            indx:!noindex/index,
+            tabl:!tables/notables,
+            box:!box/nobox,
+            pivot:!pivot/nopivot;
+     +cells[cl_]=count,none,row,column,total,expected,residual,sresidual,
+                asresidual,all;
+     +statistics[st_]=chisq,phi,cc,lambda,uc,none,btau,ctau,risk,gamma,d,
+                     kappa,eta,corr,all.
+*/
+/* (declarations) */
+/* (functions) */
+
+/* Number of chi-square statistics. */
+#define N_CHISQ 5
+
+/* Number of symmetric statistics. */
+#define N_SYMMETRIC 9
+
+/* Number of directional statistics. */
+#define N_DIRECTIONAL 13
+
+/* A single table entry for general mode. */
+struct table_entry
+  {
+    int table;         /* Flattened table number. */
+    union
+      {
+       double freq;    /* Frequency count. */
+       double *data;   /* Crosstabulation table for integer mode. */
+      }
+    u;
+    union value v[1];          /* Values. */
+  };
+
+/* A crosstabulation. */
+struct crosstab
+  {
+    int nvar;                  /* Number of variables. */
+    double missing;            /* Missing cases count. */
+    int ofs;                   /* Integer mode: Offset into sorted_tab[]. */
+    struct variable *v[2];     /* At least two variables; sorted by
+                                  larger indices first. */
+  };
+
+/* Indexes into crosstab.v. */
+enum
+  {
+    ROW_VAR = 0,
+    COL_VAR = 1
+  };
+
+/* General mode crosstabulation table. */
+static struct hsh_table *gen_tab;      /* Hash table. */
+static int n_sorted_tab;               /* Number of entries in sorted_tab. */
+static struct table_entry **sorted_tab;        /* Sorted table. */
+
+/* VARIABLES dictionary. */
+static struct dictionary *var_dict;
+
+/* TABLES. */
+static struct crosstab **xtab;
+static int nxtab;
+
+/* Integer or general mode? */
+enum
+  {
+    INTEGER,
+    GENERAL
+  };
+static int mode;
+
+/* CELLS. */
+static int num_cells;          /* Number of cells requested. */
+static int cells[8];           /* Cells requested. */
+static int expected;           /* Nonzero if expected value is needed. */
+
+/* WRITE. */
+static int write;              /* One of WR_* that specifies the WRITE style. */
+
+/* Command parsing info. */
+static struct cmd_crosstabs cmd;
+
+/* Pools. */
+static struct pool *pl_tc;     /* For table cells. */
+static struct pool *pl_col;    /* For column data. */
+
+static int internal_cmd_crosstabs (void);
+static void free_var_dict (void);
+static void precalc (void);
+static int calc_general (struct ccase *);
+static int calc_integer (struct ccase *);
+static void postcalc (void);
+static void submit (struct tab_table *);
+
+#if DEBUGGING
+static void debug_print (void);
+static void print_table_entries (struct table_entry **tab);
+#endif
+
+/* Parse and execute CROSSTABS, then clean up. */
+int
+cmd_crosstabs (void)
+{
+  int result = internal_cmd_crosstabs ();
+
+  free_var_dict ();
+  pool_destroy (pl_tc);
+  pool_destroy (pl_col);
+  
+  return result;
+}
+
+/* Parses and executes the CROSSTABS procedure. */
+static int
+internal_cmd_crosstabs (void)
+{
+  var_dict = NULL;
+  xtab = NULL;
+  nxtab = 0;
+  pl_tc = pool_create ();
+  pl_col = pool_create ();
+
+  lex_match_id ("CROSSTABS");
+  if (!parse_crosstabs (&cmd))
+    return CMD_FAILURE;
+
+#if DEBUGGING
+  /* Needs var_dict. */
+  debug_print ();
+#endif
+
+  mode = var_dict ? INTEGER : GENERAL;
+  free_var_dict();
+
+  /* CELLS. */
+  expected = 0;
+  if (!cmd.sbc_cells)
+    {
+      cmd.a_cells[CRS_CL_COUNT] = 1;
+      num_cells = 1;
+    }
+  else 
+    {
+      int i;
+      int count = 0;
+
+      for (i = 0; i < CRS_CL_count; i++)
+       if (cmd.a_cells[i])
+         count++;
+      if (count == 0)
+       {
+         cmd.a_cells[CRS_CL_COUNT] = 1;
+         cmd.a_cells[CRS_CL_ROW] = 1;
+         cmd.a_cells[CRS_CL_COLUMN] = 1;
+         cmd.a_cells[CRS_CL_TOTAL] = 1;
+       }
+      if (cmd.a_cells[CRS_CL_ALL])
+       {
+         for (i = 0; i < CRS_CL_count; i++)
+           cmd.a_cells[i] = 1;
+         cmd.a_cells[CRS_CL_ALL] = 0;
+       }
+      cmd.a_cells[CRS_CL_NONE] = 0;
+      for (num_cells = i = 0; i < CRS_CL_count; i++)
+       if (cmd.a_cells[i])
+         {
+           if (i >= CRS_CL_EXPECTED)
+             expected = 1;
+           cmd.a_cells[num_cells++] = i;
+         }
+    }
+
+  /* STATISTICS. */
+  if (cmd.sbc_statistics)
+    {
+      int i;
+      int count = 0;
+
+      for (i = 0; i < CRS_ST_count; i++)
+       if (cmd.a_statistics[i])
+         count++;
+      if (count == 0)
+       cmd.a_statistics[CRS_ST_CHISQ] = 1;
+      if (cmd.a_statistics[CRS_ST_ALL])
+       for (i = 0; i < CRS_ST_count; i++)
+         cmd.a_statistics[i] = 1;
+    }
+  
+  /* MISSING. */
+  if (cmd.miss == CRS_REPORT && mode == GENERAL)
+    {
+      msg (SE, _("Missing mode REPORT not allowed in general mode.  "
+                "Assuming MISSING=TABLE."));
+      cmd.miss = CRS_TABLE;
+    }
+
+  /* WRITE. */
+  if (cmd.a_write[CRS_WR_ALL] && cmd.a_write[CRS_WR_CELLS])
+    cmd.a_write[CRS_WR_ALL] = 0;
+  if (cmd.a_write[CRS_WR_ALL] && mode == GENERAL)
+    {
+      msg (SE, _("Write mode ALL not allowed in general mode.  "
+                "Assuming WRITE=CELLS."));
+      cmd.a_write[CRS_WR_CELLS] = 1;
+    }
+  if (cmd.sbc_write
+      && (cmd.a_write[CRS_WR_NONE]
+         + cmd.a_write[CRS_WR_ALL]
+         + cmd.a_write[CRS_WR_CELLS] == 0))
+    cmd.a_write[CRS_WR_CELLS] = 1;
+  if (cmd.a_write[CRS_WR_CELLS])
+    write = CRS_WR_CELLS;
+  else if (cmd.a_write[CRS_WR_ALL])
+    write = CRS_WR_ALL;
+  else
+    write = CRS_WR_NONE;
+
+  update_weighting (&default_dict);
+  procedure (precalc, mode == GENERAL ? calc_general : calc_integer, postcalc);
+
+  return CMD_SUCCESS;
+}
+
+/* Frees var_dict once it's no longer needed. */
+static void
+free_var_dict (void)
+{
+  if (!var_dict)
+    return;
+  
+  {
+    int i;
+
+    if (var_dict->var_by_name)
+      {
+       avl_destroy (var_dict->var_by_name, NULL);
+       var_dict->var_by_name = NULL;
+      }
+
+    for (i = 0; i < var_dict->nvar; i++)
+      free (var_dict->var[i]);
+    free (var_dict->var);
+    var_dict->var = NULL;
+    var_dict->nvar = 0;
+
+    free_dictionary (var_dict);
+
+    var_dict = NULL;
+  }
+}
+
+/* Parses the TABLES subcommand. */
+static int
+crs_custom_tables (struct cmd_crosstabs *cmd unused)
+{
+  struct dictionary *dict;
+  int n_by;
+  struct variable ***by = NULL;
+  int *by_nvar = NULL;
+  int nx = 1;
+  int success = 0;
+
+  /* Ensure that this is a TABLES subcommand. */
+  if (!lex_match_id ("TABLES")
+      && (token != T_ID || !is_varname (tokid))
+      && token != T_ALL)
+    return 2;
+  lex_match ('=');
+
+  dict = var_dict ? var_dict : &default_dict;
+  
+  for (n_by = 0; ;)
+    {
+      by = xrealloc (by, sizeof *by * (n_by + 1));
+      by_nvar = xrealloc (by_nvar, sizeof *by_nvar * (n_by + 1));
+      if (!parse_variables (dict, &by[n_by], &by_nvar[n_by],
+                           PV_NO_DUPLICATE | PV_NO_SCRATCH))
+       goto lossage;
+      nx *= by_nvar[n_by];
+      n_by++;
+
+      if (!lex_match (T_BY))
+       {
+         if (n_by < 1)
+           {
+             lex_error (_("expecting BY"));
+             goto lossage;
+           }
+         else 
+           break;
+       }
+    }
+  
+  {
+    int *by_iter = xcalloc (sizeof *by_iter * n_by);
+    int i;
+
+    xtab = xrealloc (xtab, sizeof *xtab * (nxtab + nx));
+    for (i = 0; i < nx; i++)
+      {
+       struct crosstab *x;
+
+       x = xmalloc (sizeof *x + sizeof (struct variable *) * (n_by - 2));
+       x->nvar = n_by;
+       x->missing = 0.;
+
+       {
+         int i;
+
+         if (var_dict == NULL)
+           for (i = 0; i < n_by; i++)
+             x->v[i] = by[i][by_iter[i]];
+         else
+           for (i = 0; i < n_by; i++)
+             x->v[i] = default_dict.var[by[i][by_iter[i]]->foo];
+       }
+       
+       {
+         int i;
+
+         for (i = n_by - 1; i >= 0; i--)
+           {
+             if (++by_iter[i] < by_nvar[i])
+               break;
+             by_iter[i] = 0;
+           }
+       }
+
+       xtab[nxtab++] = x;
+      }
+    free (by_iter);
+  }
+    
+  success = 1;
+  /* Despite the name, we come here whether we're successful or
+     not. */
+ lossage:
+  {
+    int i;
+
+    for (i = 0; i < n_by; i++)
+      free (by[i]);
+    free (by);
+    free (by_nvar);
+  }
+
+  return success;
+}
+
+/* Parses the VARIABLES subcommand. */
+static int
+crs_custom_variables (struct cmd_crosstabs *cmd unused)
+{
+  struct variable **v = NULL;
+  int nv = 0;
+
+  if (nxtab)
+    {
+      msg (SE, _("VARIABLES must be specified before TABLES."));
+      return 0;
+    }
+
+  lex_match ('=');
+  
+  for (;;)
+    {
+      int orig_nv = nv;
+      int i;
+
+      long min, max;
+      
+      if (!parse_variables (&default_dict, &v, &nv,
+                           (PV_APPEND | PV_NUMERIC
+                            | PV_NO_DUPLICATE | PV_NO_SCRATCH)))
+       return 0;
+
+      if (token != '(')
+       {
+         lex_error ("expecting `('");
+         goto lossage;
+       }
+      lex_get ();
+
+      if (!lex_force_int ())
+       goto lossage;
+      min = lex_integer ();
+      lex_get ();
+
+      lex_match (',');
+
+      if (!lex_force_int ())
+       goto lossage;
+      max = lex_integer ();
+      if (max < min)
+       {
+         msg (SE, _("Maximum value (%ld) less than minimum value (%ld)."),
+              max, min);
+         goto lossage;
+       }
+      lex_get ();
+
+      if (token != ')')
+       {
+         lex_error ("expecting `)'");
+         goto lossage;
+       }
+      lex_get ();
+      
+      for (i = orig_nv; i < nv; i++)
+       {
+         v[i]->p.crs.min = min;
+         v[i]->p.crs.max = max + 1.;
+         v[i]->p.crs.count = max - min + 1;
+       }
+      
+      if (token == '/')
+       break;
+    }
+  
+  {
+    int i;
+    
+    var_dict = new_dictionary (0);
+    var_dict->var = xmalloc (sizeof *var_dict->var * nv);
+    var_dict->nvar = nv;
+    for (i = 0; i < nv; i++)
+      {
+       struct variable *var = xmalloc (offsetof (struct variable, width));
+       strcpy (var->name, v[i]->name);
+       var->index = i;
+       var->type = v[i]->type;
+       var->foo = v[i]->index;
+       var_dict->var[i] = var;
+       avl_force_insert (var_dict->var_by_name, var);
+      }
+
+    free (v);
+    return 1;
+  }
+
+ lossage:
+  free (v);
+  return 0;
+}
+
+#if DEBUGGING
+static void
+debug_print (void)
+{
+  printf ("CROSSTABS\n");
+
+  if (var_dict)
+    {
+      int i;
+
+      printf ("\t/VARIABLES=");
+      for (i = 0; i < var_dict->nvar; i++)
+       {
+         struct variable *v = var_dict->var[i];
+         struct variable *iv = default_dict.var[v->foo];
+
+         printf ("%s ", v->name);
+         if (i < var_dict->nvar - 1)
+           {
+             struct variable *nv = var_dict->var[i + 1];
+             struct variable *niv = default_dict.var[nv->foo];
+             
+             if (iv->p.crs.min == niv->p.crs.min
+                 && iv->p.crs.max == niv->p.crs.max)
+               continue;
+           }
+         printf ("(%d,%d) ", iv->p.crs.min, iv->p.crs.max - 1);
+       }
+      printf ("\n");
+    }
+  
+  {
+    int i;
+
+    printf ("\t/TABLES=");
+    for (i = 0; i < nxtab; i++)
+      {
+       struct crosstab *x = xtab[i];
+       int j;
+
+       if (i)
+         printf("\t\t");
+       for (j = 0; j < x->nvar; j++)
+         {
+           if (j)
+             printf (" BY ");
+           printf ("%s", x->v[j]->name);
+         }
+       printf ("\n");
+      }
+  }
+}
+#endif /* DEBUGGING */
+\f
+/* Data file processing. */
+
+static int compare_table_entry (const void *, const void *, void *);
+static unsigned hash_table_entry (const void *, void *);
+
+/* Set up the crosstabulation tables for processing. */
+static void
+precalc (void)
+{
+  if (mode == GENERAL)
+    {
+      gen_tab = hsh_create (512, compare_table_entry, hash_table_entry,
+                           NULL, NULL);
+    }
+  else 
+    {
+      int i;
+
+      sorted_tab = NULL;
+      n_sorted_tab = 0;
+
+      for (i = 0; i < nxtab; i++)
+       {
+         struct crosstab *x = xtab[i];
+         int count = 1;
+         int *v;
+         int j;
+
+         x->ofs = n_sorted_tab;
+
+         for (j = 2; j < x->nvar; j++)
+           count *= x->v[j - 2]->p.crs.count;
+
+         sorted_tab = xrealloc (sorted_tab,
+                                sizeof *sorted_tab * (n_sorted_tab + count));
+         v = local_alloc (sizeof *v * x->nvar);
+         for (j = 2; j < x->nvar; j++)
+           v[j] = x->v[j]->p.crs.min;
+         for (j = 0; j < count; j++)
+           {
+             struct table_entry *te;
+             int k;
+
+             te = sorted_tab[n_sorted_tab++]
+               = xmalloc (sizeof *te + sizeof (union value) * (x->nvar - 1));
+             te->table = i;
+             
+             {
+               const int mat_size = (x->v[0]->p.crs.count
+                                     * x->v[1]->p.crs.count);
+               int m;
+               
+               te->u.data = xmalloc (sizeof *te->u.data * mat_size);
+               for (m = 0; m < mat_size; m++)
+                 te->u.data[m] = 0.;
+             }
+             
+             for (k = 2; k < x->nvar; k++)
+               te->v[k].f = v[k];
+             for (k = 2; k < x->nvar; k++)
+               if (++v[k] >= x->v[k]->p.crs.max)
+                 v[k] = x->v[k]->p.crs.min;
+               else
+                 break;
+           }
+         local_free (v);
+       }
+
+      sorted_tab = xrealloc (sorted_tab,
+                            sizeof *sorted_tab * (n_sorted_tab + 1));
+      sorted_tab[n_sorted_tab] = NULL;
+    }
+}
+
+/* Form crosstabulations for general mode. */
+static int
+calc_general (struct ccase *c)
+{
+  /* Case weight. */
+  double w = (default_dict.weight_index != -1
+             ? c->data[default_dict.var[default_dict.weight_index]->fv].f
+             : 1.0);
+
+  /* Flattened current table index. */
+  int t;
+
+  for (t = 0; t < nxtab; t++)
+    {
+      struct crosstab *x = xtab[t];
+      const size_t entry_size = (sizeof (struct table_entry)
+                                + sizeof (union value) * (x->nvar - 1));
+      struct table_entry *te = local_alloc (entry_size);
+
+      /* Construct table entry for the current record and table. */
+      te->table = t;
+      {
+       int j;
+
+       assert (x != NULL);
+       for (j = 0; j < x->nvar; j++)
+         {
+           if ((cmd.miss == CRS_TABLE
+                && is_missing (&c->data[x->v[j]->fv], x->v[j]))
+               || (cmd.miss == CRS_INCLUDE
+                   && is_system_missing (&c->data[x->v[j]->fv], x->v[j])))
+             {
+               x->missing += w;
+               goto next_crosstab;
+             }
+             
+           if (x->v[j]->type == NUMERIC)
+             te->v[j].f = c->data[x->v[j]->fv].f;
+           else
+             {
+               memcpy (te->v[j].s, c->data[x->v[j]->fv].s, x->v[j]->width);
+             
+               /* Necessary in order to simplify comparisons. */
+               memset (&te->v[j].s[x->v[j]->width], 0,
+                       sizeof (union value) - x->v[j]->width);
+             }
+         }
+      }
+
+      /* Add record to hash table. */
+      {
+       struct table_entry **tepp = (struct table_entry **) hsh_probe (gen_tab, te);
+       if (NULL == *tepp)
+         {
+           struct table_entry *tep = pool_alloc (pl_tc, entry_size);
+           
+           te->u.freq = w;
+           memcpy (tep, te, entry_size);
+           
+           *tepp = tep;
+         }
+       else
+         (*tepp)->u.freq += w;
+      }
+
+    next_crosstab:
+      local_free (te);
+    }
+  
+  return 1;
+}
+
+static int
+calc_integer (struct ccase *c)
+{
+  /* Case weight. */
+  double w = (default_dict.weight_index != -1
+             ? c->data[default_dict.var[default_dict.weight_index]->fv].f
+             : 1.0);
+  
+  /* Flattened current table index. */
+  int t;
+  
+  for (t = 0; t < nxtab; t++)
+    {
+      struct crosstab *x = xtab[t];
+      int i, fact, ofs;
+      
+      fact = i = 1;
+      ofs = x->ofs;
+      for (i = 0; i < x->nvar; i++)
+       {
+         struct variable *const v = x->v[i];
+         double value = c->data[v->fv].f;
+         
+         /* Note that the first test also rules out SYSMIS. */
+         if ((value < v->p.crs.min || value >= v->p.crs.max)
+             || (cmd.miss == CRS_TABLE && is_num_user_missing (value, v)))
+           {
+             x->missing += w;
+             goto next_crosstab;
+           }
+         
+         if (i > 1)
+           {
+             ofs += fact * ((int) value - v->p.crs.min);
+             fact *= v->p.crs.count;
+           }
+       }
+      
+      {
+       const int row = c->data[x->v[ROW_VAR]->fv].f - x->v[ROW_VAR]->p.crs.min;
+       const int col = c->data[x->v[COL_VAR]->fv].f - x->v[COL_VAR]->p.crs.min;
+       const int col_dim = x->v[COL_VAR]->p.crs.count;
+
+       sorted_tab[ofs]->u.data[col + row * col_dim] += w;
+      }
+      
+    next_crosstab: ;
+    }
+  
+  return 1;
+}
+
+#if DEBUGGING
+/* Print out all table entries in NULL-terminated TAB for use by a
+   debugger (a person, not a program). */
+static void
+print_table_entries (struct table_entry **tab)
+{
+  printf ("raw crosstabulation data:\n");
+  for (; *tab; tab++)
+    {
+      const struct crosstab *x = xtab[(*tab)->table];
+      int i;
+
+      printf ("(%g) table:%d ", (*tab)->u.freq, (*tab)->table);
+      for (i = 0; i < x->nvar; i++)
+       {
+         if (i)
+           printf (", ");
+         printf ("%s:", x->v[i]->name);
+         
+         if (x->v[i]->type == NUMERIC)
+           printf ("%g", (*tab)->v[i].f);
+         else
+           printf ("%.*s", x->v[i]->width, (*tab)->v[i].s);
+       }
+      printf ("\n");
+    }
+  fflush (stdout);
+}
+#endif
+
+/* Compare the table_entry's at PA and PB and return a strcmp()-type
+   result. */
+static int 
+compare_table_entry (const void *pa, const void *pb, void *foo unused)
+{
+  const struct table_entry *a = pa;
+  const struct table_entry *b = pb;
+  
+  {
+    const int difftable = a->table - b->table;
+    if (difftable)
+      return difftable;
+  }
+  
+  {
+    const struct crosstab *x = xtab[a->table];
+    int i;
+
+    for (i = x->nvar - 1; i >= 0; i--)
+      if (x->v[i]->type == NUMERIC)
+       {
+         const double diffnum = a->v[i].f - b->v[i].f;
+         if (diffnum < 0)
+           return -1;
+         else if (diffnum > 0)
+           return 1;
+       }
+      else 
+       {
+         assert (x->v[i]->type == ALPHA);
+         {
+           const int diffstr = strncmp (a->v[i].s, b->v[i].s, x->v[i]->width);
+           if (diffstr)
+             return diffstr;
+         }
+       }
+  }
+  
+  return 0;
+}
+
+/* Calculate a hash value from table_entry PA. */
+static unsigned
+hash_table_entry (const void *pa, void *foo unused)
+{
+  const struct table_entry *a = pa;
+  unsigned long hash = a->table;
+  int i;
+
+  /* Hash formula from _SPSS Statistical Algorithms_. */
+  for (i = 0; i < xtab[a->table]->nvar; i++)
+    {
+      hash = (hash << 3) | (hash >> (CHAR_BIT * SIZEOF_LONG - 3));
+      hash ^= a->v[i].hash[0];
+#if SIZEOF_DOUBLE / SIZEOF_LONG > 1
+      hash ^= a->v[i].hash[1];
+#endif
+    }
+  
+  return hash;
+}
+\f
+/* Post-data reading calculations. */
+
+static struct table_entry **find_pivot_extent (struct table_entry **, int *cnt, int pivot);
+static void enum_var_values (struct table_entry **beg, int cnt,
+                            union value **values, int *nvalues,
+                            int var_index);
+static void output_pivot_table (struct table_entry **, struct table_entry **,
+                               double **, double **, double **,
+                               int *, int *, int *);
+static void make_summary_table (void);
+
+static void
+postcalc (void)
+{
+  if (mode == GENERAL)
+    {
+      n_sorted_tab = hsh_count (gen_tab);
+      sorted_tab = (struct table_entry **) hsh_sort (gen_tab, compare_table_entry);
+#if DEBUGGING
+      print_table_entries (sorted_tab);
+#endif
+    }
+  
+  make_summary_table ();
+  
+  /* Identify all the individual crosstabulation tables, and deal with
+     them. */
+  {
+    struct table_entry **pb = sorted_tab, **pe;        /* Pivot begin, pivot end. */
+    int pc = n_sorted_tab;                     /* Pivot count. */
+
+    double *mat = NULL, *row_tot = NULL, *col_tot = NULL;
+    int maxrows = 0, maxcols = 0, maxcells = 0;
+
+    for (;;)
+      {
+       pe = find_pivot_extent (pb, &pc, cmd.pivot == CRS_PIVOT);
+       if (pe == NULL)
+         break;
+       
+       output_pivot_table (pb, pe, &mat, &row_tot, &col_tot,
+                           &maxrows, &maxcols, &maxcells);
+         
+       pb = pe;
+      }
+    free (mat);
+    free (row_tot);
+    free (col_tot);
+  }
+  
+  hsh_destroy (gen_tab);
+}
+
+static void insert_summary (struct tab_table *, int tab_index, double valid);
+
+/* Output a table summarizing the cases processed. */
+static void
+make_summary_table (void)
+{
+  struct tab_table *summary;
+  
+  struct table_entry **pb = sorted_tab, **pe;
+  int pc = n_sorted_tab;
+  int cur_tab = 0;
+
+  summary = tab_create (7, 3 + nxtab, 1);
+  tab_title (summary, 0, _("Summary."));
+  tab_headers (summary, 1, 0, 3, 0);
+  tab_joint_text (summary, 1, 0, 6, 0, TAB_CENTER, _("Cases"));
+  tab_joint_text (summary, 1, 1, 2, 1, TAB_CENTER, _("Valid"));
+  tab_joint_text (summary, 3, 1, 4, 1, TAB_CENTER, _("Missing"));
+  tab_joint_text (summary, 5, 1, 6, 1, TAB_CENTER, _("Total"));
+  tab_hline (summary, TAL_1, 1, 6, 1);
+  tab_hline (summary, TAL_1, 1, 6, 2);
+  tab_vline (summary, TAL_1, 3, 1, 1);
+  tab_vline (summary, TAL_1, 5, 1, 1);
+  {
+    int i;
+
+    for (i = 0; i < 3; i++)
+      {
+       tab_text (summary, 1 + i * 2, 2, TAB_RIGHT, _("N"));
+       tab_text (summary, 2 + i * 2, 2, TAB_RIGHT, _("Percent"));
+      }
+  }
+  tab_offset (summary, 0, 3);
+                 
+  for (;;)
+    {
+      double valid;
+      
+      pe = find_pivot_extent (pb, &pc, cmd.pivot == CRS_PIVOT);
+      if (pe == NULL)
+       break;
+
+      while (cur_tab < (*pb)->table)
+       insert_summary (summary, cur_tab++, 0.);
+
+      if (mode == GENERAL)
+       for (valid = 0.; pb < pe; pb++)
+         valid += (*pb)->u.freq;
+      else
+       {
+         const struct crosstab *const x = xtab[(*pb)->table];
+         const int n_cols = x->v[COL_VAR]->p.crs.count;
+         const int n_rows = x->v[ROW_VAR]->p.crs.count;
+         const int count = n_cols * n_rows;
+           
+         for (valid = 0.; pb < pe; pb++)
+           {
+             const double *data = (*pb)->u.data;
+             int i;
+               
+             for (i = 0; i < count; i++)
+               valid += *data++;
+           }
+       }
+      insert_summary (summary, cur_tab++, valid);
+
+      pb = pe;
+    }
+  
+  while (cur_tab < nxtab)
+    insert_summary (summary, cur_tab++, 0.);
+
+  submit (summary);
+}
+
+/* Inserts a line into T describing the crosstabulation at index
+   TAB_INDEX, which has VALID valid observations. */
+static void
+insert_summary (struct tab_table *t, int tab_index, double valid)
+{
+  struct crosstab *x = xtab[tab_index];
+
+  tab_hline (t, TAL_1, 0, 6, 0);
+  
+  /* Crosstabulation name. */
+  {
+    char *buf = local_alloc (128 * x->nvar);
+    char *cp = buf;
+    int i;
+
+    for (i = 0; i < x->nvar; i++)
+      {
+       if (i > 0)
+         cp = stpcpy (cp, " * ");
+
+       cp = stpcpy (cp, x->v[i]->label ? x->v[i]->label : x->v[i]->name);
+      }
+    tab_text (t, 0, 0, TAB_LEFT, buf);
+
+    local_free (buf);
+  }
+    
+  /* Counts and percentages. */
+  {
+    double n[3];
+    int i;
+
+    n[0] = valid;
+    n[1] = x->missing;
+    n[2] = n[0] + n[1];
+
+
+    for (i = 0; i < 3; i++)
+      {
+       tab_float (t, i * 2 + 1, 0, TAB_RIGHT, n[i], 8, 0);
+       tab_text (t, i * 2 + 2, 0, TAB_RIGHT | TAT_PRINTF, "%.1f%%",
+                 n[i] / n[2] * 100.);
+      }
+  }
+  
+  tab_next_row (t);
+}
+\f
+/* Output. */
+
+/* Tables. */
+static struct tab_table *table;        /* Crosstabulation table. */
+static struct tab_table *chisq;        /* Chi-square table. */
+static struct tab_table *sym;          /* Symmetric measures table. */
+static struct tab_table *risk;         /* Risk estimate table. */
+static struct tab_table *direct;       /* Directional measures table. */
+
+/* Statistics. */
+static int chisq_fisher;       /* Did any rows include Fisher's exact test? */
+
+/* Column values, number of columns. */
+static union value *cols;
+static int n_cols;
+
+/* Row values, number of rows. */
+static union value *rows;
+static int n_rows;
+             
+/* Number of statistically interesting columns/rows (columns/rows with
+   data in them). */
+static int ns_cols, ns_rows;
+
+/* Crosstabulation. */
+static struct crosstab *x;
+
+/* Number of variables from the crosstabulation to consider.  This is
+   either x->nvar, if pivoting is on, or 2, if pivoting is off. */
+static int nvar;
+
+/* Matrix contents. */
+static double *mat;            /* Matrix proper. */
+static double *row_tot;                /* Row totals. */
+static double *col_tot;                /* Column totals. */
+static double W;               /* Grand total. */
+
+static void display_dimensions (struct tab_table *, int first_difference,
+                               struct table_entry *);
+static void display_crosstabulation (void);
+static void display_chisq (void);
+static void display_symmetric (void);
+static void display_risk (void);
+static void display_directional (void);
+static void crosstabs_dim (struct tab_table *, struct outp_driver *);
+static void table_value_missing (struct tab_table *table, int c, int r,
+                                unsigned char opt, const union value *v,
+                                const struct variable *var);
+static void delete_missing (void);
+
+/* Output pivot table beginning at PB and continuing until PE,
+   exclusive.  For efficiency, *MATP is a pointer to a matrix that can
+   hold *MAXROWS entries. */
+static void
+output_pivot_table (struct table_entry **pb, struct table_entry **pe,
+                   double **matp, double **row_totp, double **col_totp,
+                   int *maxrows, int *maxcols, int *maxcells)
+{
+  /* Subtable. */
+  struct table_entry **tb = pb, **te;  /* Table begin, table end. */
+  int tc = pe - pb;            /* Table count. */
+
+  /* Table entry for header comparison. */
+  struct table_entry *cmp;
+
+  x = xtab[(*pb)->table];
+  enum_var_values (pb, pe - pb, &cols, &n_cols, COL_VAR);
+
+  nvar = cmd.pivot == CRS_PIVOT ? x->nvar : 2;
+
+  /* Crosstabulation table initialization. */
+  if (num_cells)
+    {
+      table = tab_create (nvar + n_cols,
+                         (pe - pb) / n_cols * 3 / 2 * num_cells + 10, 1);
+      tab_headers (table, nvar - 1, 0, 2, 0);
+
+      /* First header line. */
+      tab_joint_text (table, nvar - 1, 0, (nvar - 1) + (n_cols - 1), 0,
+                     TAB_CENTER | TAT_TITLE, x->v[COL_VAR]->name);
+  
+      tab_hline (table, TAL_1, nvar - 1, nvar + n_cols - 2, 1);
+            
+      /* Second header line. */
+      {
+       int i;
+
+       for (i = 2; i < nvar; i++)
+         tab_joint_text (table, nvar - i - 1, 0, nvar - i - 1, 1,
+                         TAB_RIGHT | TAT_TITLE,
+                         x->v[i]->label ? x->v[i]->label : x->v[i]->name);
+       tab_text (table, nvar - 2, 1, TAB_RIGHT | TAT_TITLE,
+                 x->v[ROW_VAR]->name);
+       for (i = 0; i < n_cols; i++)
+         table_value_missing (table, nvar + i - 1, 1, TAB_RIGHT, &cols[i],
+                              x->v[COL_VAR]);
+       tab_text (table, nvar + n_cols - 1, 1, TAB_CENTER, _("Total"));
+      }
+
+      tab_hline (table, TAL_1, 0, nvar + n_cols - 1, 2);
+      tab_vline (table, TAL_1, nvar + n_cols - 1, 0, 1);
+
+      /* Title. */
+      {
+       char *title = local_alloc (x->nvar * 64 + 128);
+       char *cp = title;
+       int i;
+    
+       if (cmd.pivot == CRS_PIVOT)
+         for (i = 0; i < nvar; i++)
+           {
+             if (i)
+               cp = stpcpy (cp, " by ");
+             cp = stpcpy (cp, x->v[i]->name);
+           }
+       else
+         {
+           cp = spprintf (cp, "%s by %s for", x->v[0]->name, x->v[1]->name);
+           for (i = 2; i < nvar; i++)
+             {
+               char buf[64], *bufp;
+
+               if (i > 2)
+                 *cp++ = ',';
+               *cp++ = ' ';
+               cp = stpcpy (cp, x->v[i]->name);
+               *cp++ = '=';
+               data_out (buf, &x->v[i]->print, &(*pb)->v[i]);
+               for (bufp = buf; isspace ((unsigned char) *bufp); bufp++)
+                 ;
+               cp = stpcpy (cp, bufp);
+             }
+         }
+
+       cp = stpcpy (cp, " [");
+       for (i = 0; i < num_cells; i++)
+         {
+           struct tuple
+             {
+               int value;
+               const char *name;
+             };
+       
+           static const struct tuple cell_names[] = 
+             {
+               {CRS_CL_COUNT, N_("count")},
+               {CRS_CL_ROW, N_("row %")},
+               {CRS_CL_COLUMN, N_("column %")},
+               {CRS_CL_TOTAL, N_("total %")},
+               {CRS_CL_EXPECTED, N_("expected")},
+               {CRS_CL_RESIDUAL, N_("residual")},
+               {CRS_CL_SRESIDUAL, N_("std. resid.")},
+               {CRS_CL_ASRESIDUAL, N_("adj. resid.")},
+               {-1, NULL},
+             };
+
+           const struct tuple *t;
+
+           for (t = cell_names; t->value != cells[i]; t++)
+             assert (t->value != -1);
+           if (i)
+             cp = stpcpy (cp, ", ");
+           cp = stpcpy (cp, gettext (t->name));
+         }
+       strcpy (cp, "].");
+
+       tab_title (table, 0, title);
+       local_free (title);
+      }
+      
+      tab_offset (table, 0, 2);
+    }
+  else
+    table = NULL;
+  
+  /* Chi-square table initialization. */
+  if (cmd.a_statistics[CRS_ST_CHISQ])
+    {
+      chisq = tab_create (6 + (nvar - 2),
+                         (pe - pb) / n_cols * 3 / 2 * N_CHISQ + 10, 1);
+      tab_headers (chisq, 1 + (nvar - 2), 0, 1, 0);
+
+      tab_title (chisq, 0, "Chi-square tests.");
+      
+      tab_offset (chisq, nvar - 2, 0);
+      tab_text (chisq, 0, 0, TAB_LEFT | TAT_TITLE, _("Statistic"));
+      tab_text (chisq, 1, 0, TAB_RIGHT | TAT_TITLE, _("Value"));
+      tab_text (chisq, 2, 0, TAB_RIGHT | TAT_TITLE, _("df"));
+      tab_text (chisq, 3, 0, TAB_RIGHT | TAT_TITLE,
+               _("Asymp. Sig. (2-sided)"));
+      tab_text (chisq, 4, 0, TAB_RIGHT | TAT_TITLE,
+               _("Exact. Sig. (2-sided)"));
+      tab_text (chisq, 5, 0, TAB_RIGHT | TAT_TITLE,
+               _("Exact. Sig. (1-sided)"));
+      chisq_fisher = 0;
+      tab_offset (chisq, 0, 1);
+    }
+  else
+    chisq = NULL;
+  
+  /* Symmetric measures. */
+  if (cmd.a_statistics[CRS_ST_PHI] || cmd.a_statistics[CRS_ST_CC]
+      || cmd.a_statistics[CRS_ST_BTAU] || cmd.a_statistics[CRS_ST_CTAU]
+      || cmd.a_statistics[CRS_ST_GAMMA] || cmd.a_statistics[CRS_ST_CORR]
+      || cmd.a_statistics[CRS_ST_KAPPA])
+    {
+      sym = tab_create (6 + (nvar - 2), (pe - pb) / n_cols * 7 + 10, 1);
+      tab_headers (sym, 2 + (nvar - 2), 0, 1, 0);
+      tab_title (sym, 0, "Symmetric measures.");
+
+      tab_offset (sym, nvar - 2, 0);
+      tab_text (sym, 0, 0, TAB_LEFT | TAT_TITLE, _("Category"));
+      tab_text (sym, 1, 0, TAB_LEFT | TAT_TITLE, _("Statistic"));
+      tab_text (sym, 2, 0, TAB_RIGHT | TAT_TITLE, _("Value"));
+      tab_text (sym, 3, 0, TAB_RIGHT | TAT_TITLE, _("Asymp. Std. Error"));
+      tab_text (sym, 4, 0, TAB_RIGHT | TAT_TITLE, _("Approx. T"));
+      tab_text (sym, 5, 0, TAB_RIGHT | TAT_TITLE, _("Approx. Sig."));
+      tab_offset (sym, 0, 1);
+    }
+  else
+    sym = NULL;
+
+  /* Risk estimate. */
+  if (cmd.a_statistics[CRS_ST_RISK])
+    {
+      risk = tab_create (4 + (nvar - 2), (pe - pb) / n_cols * 4 + 10, 1);
+      tab_headers (risk, 1 + nvar - 2, 0, 2, 0);
+      tab_title (risk, 0, "Risk estimate.");
+
+      tab_offset (risk, nvar - 2, 0);
+      tab_joint_text (risk, 2, 0, 3, 0, TAB_CENTER | TAT_TITLE | TAT_PRINTF,
+                     _(" 95%% Confidence Interval"));
+      tab_text (risk, 0, 1, TAB_LEFT | TAT_TITLE, _("Statistic"));
+      tab_text (risk, 1, 1, TAB_RIGHT | TAT_TITLE, _("Value"));
+      tab_text (risk, 2, 1, TAB_RIGHT | TAT_TITLE, _("Lower"));
+      tab_text (risk, 3, 1, TAB_RIGHT | TAT_TITLE, _("Upper"));
+      tab_hline (risk, TAL_1, 2, 3, 1);
+      tab_vline (risk, TAL_1, 2, 0, 1);
+      tab_offset (risk, 0, 2);
+    }
+  else
+    risk = NULL;
+
+  /* Directional measures. */
+  if (cmd.a_statistics[CRS_ST_LAMBDA] || cmd.a_statistics[CRS_ST_UC]
+      || cmd.a_statistics[CRS_ST_D] || cmd.a_statistics[CRS_ST_ETA])
+    {
+      direct = tab_create (7 + (nvar - 2), (pe - pb) / n_cols * 7 + 10, 1);
+      tab_headers (direct, 3 + (nvar - 2), 0, 1, 0);
+      tab_title (direct, 0, "Directional measures.");
+
+      tab_offset (direct, nvar - 2, 0);
+      tab_text (direct, 0, 0, TAB_LEFT | TAT_TITLE, _("Category"));
+      tab_text (direct, 1, 0, TAB_LEFT | TAT_TITLE, _("Statistic"));
+      tab_text (direct, 2, 0, TAB_LEFT | TAT_TITLE, _("Type"));
+      tab_text (direct, 3, 0, TAB_RIGHT | TAT_TITLE, _("Value"));
+      tab_text (direct, 4, 0, TAB_RIGHT | TAT_TITLE, _("Asymp. Std. Error"));
+      tab_text (direct, 5, 0, TAB_RIGHT | TAT_TITLE, _("Approx. T"));
+      tab_text (direct, 6, 0, TAB_RIGHT | TAT_TITLE, _("Approx. Sig."));
+      tab_offset (direct, 0, 1);
+    }
+  else
+    direct = NULL;
+
+  for (;;)
+    {
+      /* Find pivot subtable if applicable. */
+      te = find_pivot_extent (tb, &tc, 0);
+      if (te == NULL)
+       break;
+
+      /* Find all the row variable values. */
+      enum_var_values (tb, te - tb, &rows, &n_rows, ROW_VAR);
+
+      /* Allocate memory space for the column and row totals. */
+      if (n_rows > *maxrows)
+       {
+         *row_totp = xrealloc (*row_totp, sizeof **row_totp * n_rows);
+         row_tot = *row_totp;
+         *maxrows = n_rows;
+       }
+      if (n_cols > *maxcols)
+       {
+         *col_totp = xrealloc (*col_totp, sizeof **col_totp * n_cols);
+         col_tot = *col_totp;
+         *maxcols = n_cols;
+       }
+      
+      /* Allocate table space for the matrix. */
+      if (table && tab_row (table) + (n_rows + 1) * num_cells > tab_nr (table))
+       tab_realloc (table, -1,
+                    max (tab_nr (table) + (n_rows + 1) * num_cells,
+                         tab_nr (table) * (pe - pb) / (te - tb)));
+
+      if (mode == GENERAL)
+       {
+         /* Allocate memory space for the matrix. */
+         if (n_cols * n_rows > *maxcells)
+           {
+             *matp = xrealloc (*matp, sizeof **matp * n_cols * n_rows);
+             *maxcells = n_cols * n_rows;
+           }
+         
+         mat = *matp;
+
+         /* Build the matrix and calculate column totals. */
+         {
+           union value *cur_col = cols;
+           union value *cur_row = rows;
+           double *mp = mat;
+           double *cp = col_tot;
+           struct table_entry **p;
+
+           *cp = 0.;
+           for (p = &tb[0]; p < te; p++)
+             {
+               for (; memcmp (cur_col, &(*p)->v[COL_VAR], sizeof *cur_col);
+                    cur_row = rows)
+                 {
+                   *++cp = 0.;
+                   for (; cur_row < &rows[n_rows]; cur_row++)
+                     {
+                       *mp = 0.;
+                       mp += n_cols;
+                     }
+                   cur_col++;
+                   mp = &mat[cur_col - cols];
+                 }
+
+               for (; memcmp (cur_row, &(*p)->v[ROW_VAR], sizeof *cur_row);
+                    cur_row++)
+                 {
+                   *mp = 0.;
+                   mp += n_cols;
+                 }
+
+               *cp += *mp = (*p)->u.freq;
+               mp += n_cols;
+               cur_row++;
+             }
+
+           /* Zero out the rest of the matrix. */
+           for (; cur_row < &rows[n_rows]; cur_row++)
+             {
+               *mp = 0.;
+               mp += n_cols;
+             }
+           cur_col++;
+           if (cur_col < &cols[n_cols])
+             {
+               const int rem_cols = n_cols - (cur_col - cols);
+               int c, r;
+
+               for (c = 0; c < rem_cols; c++)
+                 *++cp = 0.;
+               mp = &mat[cur_col - cols];
+               for (r = 0; r < n_rows; r++)
+                 {
+                   for (c = 0; c < rem_cols; c++)
+                     *mp++ = 0.;
+                   mp += n_cols - rem_cols;
+                 }
+             }
+         }
+       }
+      else
+       {
+         int r, c;
+         double *tp = col_tot;
+         
+         assert (mode == INTEGER);
+         mat = (*tb)->u.data;
+         ns_cols = n_cols;
+
+         /* Calculate column totals. */
+         for (c = 0; c < n_cols; c++)
+           {
+             double cum = 0.;
+             double *cp = &mat[c];
+             
+             for (r = 0; r < n_rows; r++)
+               cum += cp[r * n_cols];
+             *tp++ = cum;
+           }
+       }
+      
+      {
+       double *cp;
+       
+       for (ns_cols = 0, cp = col_tot; cp < &col_tot[n_cols]; cp++)
+         ns_cols += *cp != 0.;
+      }
+
+      /* Calculate row totals. */
+      {
+       double *mp = mat;
+       double *rp = row_tot;
+       int r, c;
+               
+       for (ns_rows = 0, r = n_rows; r--; )
+         {
+           double cum = 0.;
+           for (c = n_cols; c--; )
+             cum += *mp++;
+           *rp++ = cum;
+           if (cum != 0.)
+             ns_rows++;
+         }
+      }
+
+      /* Calculate grand total. */
+      {
+       double *tp;
+       double cum = 0.;
+       int n;
+
+       if (n_rows < n_cols)
+         tp = row_tot, n = n_rows;
+       else
+         tp = col_tot, n = n_cols;
+       while (n--)
+         cum += *tp++;
+       W = cum;
+      }
+      
+#if DEBUGGING
+      /* Print the matrix. */
+      {
+       int i, r, c;
+
+       printf ("%s by %s for", x->v[0]->name, x->v[1]->name);
+       for (i = 2; i < nvar; i++)
+         printf (" %s=%g", x->v[i]->name, tb[0]->v[i].f);
+       printf ("\n");
+       printf ("     ");
+       for (c = 0; c < n_cols; c++)
+         printf ("%4g", cols[c].f);
+       printf ("\n");
+       for (r = 0; r < n_rows; r++)
+         {
+           printf ("%4g:", rows[r].f);
+           for (c = 0; c < n_cols; c++)
+             printf ("%4g", mat[c + r * n_cols]);
+           printf ("%4g", row_tot[r]);
+           printf ("\n");
+         }
+       printf ("     ");
+       for (c = 0; c < n_cols; c++)
+         printf ("%4g", col_tot[c]);
+       printf ("%4g", W);
+       printf ("\n\n");
+      }
+#endif
+
+      /* Find the first variable that differs from the last subtable,
+        then display the values of the dimensioning variables for
+        each table that needs it. */
+      {
+       int first_difference = nvar - 1;
+       
+       if (tb != pb)
+         for (; ; first_difference--)
+           {
+             assert (first_difference >= 2);
+             if (memcmp (&cmp->v[first_difference],
+                         &(*tb)->v[first_difference], sizeof *cmp->v))
+               break;
+           }
+       cmp = *tb;
+           
+       if (table)
+         display_dimensions (table, first_difference, *tb);
+       if (chisq)
+         display_dimensions (chisq, first_difference, *tb);
+       if (sym)
+         display_dimensions (sym, first_difference, *tb);
+       if (risk)
+         display_dimensions (risk, first_difference, *tb);
+       if (direct)
+         display_dimensions (direct, first_difference, *tb);
+      }
+
+      if (table)
+       display_crosstabulation ();
+      if (cmd.miss == CRS_REPORT)
+       delete_missing ();
+      if (chisq)
+       display_chisq ();
+      if (sym)
+       display_symmetric ();
+      if (risk)
+       display_risk ();
+      if (direct)
+       display_directional ();
+               
+      tb = te;
+      free (rows);
+    }
+
+  submit (table);
+  
+  if (chisq)
+    {
+      if (!chisq_fisher)
+       tab_resize (chisq, 4 + (nvar - 2), -1);
+      submit (chisq);
+    }
+
+  submit (sym);
+  submit (risk);
+  submit (direct);
+
+  free (cols);
+}
+
+/* Delete missing rows and columns for statistical analysis when
+   /MISSING=REPORT. */
+static void
+delete_missing (void)
+{
+  {
+    int r;
+
+    for (r = 0; r < n_rows; r++)
+      if (is_num_user_missing (rows[r].f, x->v[ROW_VAR]))
+       {
+         int c;
+
+         for (c = 0; c < n_cols; c++)
+           mat[c + r * n_cols] = 0.;
+         ns_rows--;
+       }
+  }
+  
+  {
+    int c;
+
+    for (c = 0; c < n_cols; c++)
+      if (is_num_user_missing (cols[c].f, x->v[COL_VAR]))
+       {
+         int r;
+
+         for (r = 0; r < n_rows; r++)
+           mat[c + r * n_cols] = 0.;
+         ns_cols--;
+       }
+  }
+}
+
+/* Prepare table T for submission, and submit it. */
+static void
+submit (struct tab_table *t)
+{
+  int i;
+  
+  if (t == NULL)
+    return;
+  
+  tab_resize (t, -1, 0);
+  if (tab_nr (t) == tab_t (t))
+    {
+      tab_destroy (t);
+      return;
+    }
+  tab_offset (t, 0, 0);
+  if (t != table)
+    for (i = 2; i < nvar; i++)
+      tab_text (t, nvar - i - 1, 0, TAB_RIGHT | TAT_TITLE,
+               x->v[i]->label ? x->v[i]->label : x->v[i]->name);
+  tab_box (t, TAL_2, TAL_2, -1, -1, 0, 0, tab_nc (t) - 1, tab_nr (t) - 1);
+  tab_box (t, -1, -1, -1, TAL_1, tab_l (t), tab_t (t) - 1, tab_nc (t) - 1,
+          tab_nr (t) - 1);
+  tab_box (t, -1, -1, -1, TAL_1 | TAL_SPACING, 0, tab_t (t), tab_l (t) - 1,
+          tab_nr (t) - 1);
+  tab_vline (t, TAL_2, tab_l (t), 0, tab_nr (t) - 1);
+  tab_dim (t, crosstabs_dim);
+  tab_submit (t);
+}
+
+/* Sets the widths of all the columns and heights of all the rows in
+   table T for driver D. */
+static void
+crosstabs_dim (struct tab_table *t, struct outp_driver *d)
+{
+  int i;
+  
+  /* Width of a numerical column. */
+  int c = outp_string_width (d, "0.000000");
+  if (cmd.miss == CRS_REPORT)
+    c += outp_string_width (d, "M");
+
+  /* Set width for header columns. */
+  if (t->l != 0)
+    {
+      int w = (d->width - t->vr_tot - c * (t->nc - t->l)) / t->l;
+      
+      if (w < d->prop_em_width * 8)
+       w = d->prop_em_width * 8;
+
+      if (w > d->prop_em_width * 15)
+       w = d->prop_em_width * 15;
+
+      for (i = 0; i < t->l; i++)
+       t->w[i] = w;
+    }
+
+  for (i = t->l; i < t->nc; i++)
+    t->w[i] = c;
+
+  for (i = 0; i < t->nr; i++)
+    t->h[i] = tab_natural_height (t, d, i);
+}
+
+static struct table_entry **find_pivot_extent_general (struct table_entry **tp,
+                                               int *cnt, int pivot);
+static struct table_entry **find_pivot_extent_integer (struct table_entry **tp,
+                                               int *cnt, int pivot);
+
+/* Calls find_pivot_extent_general or find_pivot_extent_integer, as
+   appropriate. */
+static struct table_entry **
+find_pivot_extent (struct table_entry **tp, int *cnt, int pivot)
+{
+  return (mode == GENERAL
+         ? find_pivot_extent_general (tp, cnt, pivot)
+         : find_pivot_extent_integer (tp, cnt, pivot));
+}
+
+/* Find the extent of a region in TP that contains one table.  If
+   PIVOT != 0 that means a set of table entries with identical table
+   number; otherwise they also have to have the same values for every
+   dimension after the row and column dimensions.  The table that is
+   searched starts at TP and has length CNT.  Returns the first entry
+   after the last one in the table; sets *CNT to the number of
+   remaining values.  If there are no entries in TP at all, returns
+   NULL.  A yucky interface, admittedly, but it works. */
+static struct table_entry **
+find_pivot_extent_general (struct table_entry **tp, int *cnt, int pivot)
+{
+  struct table_entry *fp = *tp;
+  struct crosstab *x;
+
+  if (*cnt == 0)
+    return NULL;
+  x = xtab[(*tp)->table];
+  for (;;)
+    {
+      tp++;
+      if (--*cnt == 0)
+       break;
+      assert (*cnt > 0);
+
+      if ((*tp)->table != fp->table)
+       break;
+      if (pivot)
+       continue;
+
+      if (memcmp (&(*tp)->v[2], &fp->v[2], sizeof (union value) * (x->nvar - 2)))
+       break;
+    }
+
+  return tp;
+}
+
+/* Integer mode correspondent to find_pivot_extent_general().  This
+   could be optimized somewhat, but I just don't give a crap about
+   CROSSTABS performance in integer mode, which is just a wart on
+   CROSSTABS' ass as far as I'm concerned.
+
+   That said, feel free to send optimization patches to me. */
+static struct table_entry **
+find_pivot_extent_integer (struct table_entry **tp, int *cnt, int pivot)
+{
+  struct table_entry *fp = *tp;
+  struct crosstab *x;
+
+  if (*cnt == 0)
+    return NULL;
+  x = xtab[(*tp)->table];
+  for (;;)
+    {
+      tp++;
+      if (--*cnt == 0)
+       break;
+      assert (*cnt > 0);
+
+      if ((*tp)->table != fp->table)
+       break;
+      if (pivot)
+       continue;
+      
+      if (memcmp (&(*tp)->v[2], &fp->v[2], sizeof (union value) * (x->nvar - 2)))
+       break;
+    }
+
+  return tp;
+}
+
+/* Compare value * A and B, where WIDTH is the string width or 0 for
+   numerics, and return a strcmp()-type result. */
+static int
+compare_value (const void *pa, const void *pb, void *pwidth)
+{
+  const union value *a = pa;
+  const union value *b = pb;
+  const int width = (int) pwidth;
+
+  if (width)
+    return strncmp (a->s, b->s, width);
+  else
+    return a->f < b->f ? -1 : (a->f > b->f ? 1 : 0);
+}
+
+/* Given a list of CNT table_entry's starting at BEG, creates a list
+   of *NVALUES values *VALUES of variable with index VAR_INDEX. */
+static void 
+enum_var_values (struct table_entry **beg, int cnt, union value **values, int *nvalues,
+                int var_index)
+{
+  if (mode == GENERAL)
+    {
+      avl_tree *tree;
+
+      tree = avl_create (pl_col, compare_value,
+                        (void *) (xtab[(*beg)->table]->v[var_index]->width));
+
+      {
+       int i;
+  
+       for (i = 0; i < cnt; i++)
+         avl_insert (tree, &beg[i]->v[var_index]);
+       *values = xmalloc (sizeof **values * avl_count (tree));
+      }
+  
+      {
+       avl_traverser trav;
+       union value *v;
+       int i;
+    
+       i = 0;
+       hsh_iterator_init (trav);
+       while (NULL != (v = avl_traverse (tree, &trav)))
+         (*values)[i++] = *v;
+       *nvalues = i;
+      }
+
+      /* Destroy tree. */
+      pool_destroy (pl_col);
+      pl_col = pool_create ();
+    }
+  else
+    {
+      struct crosstab_proc *crs = &xtab[(*beg)->table]->v[var_index]->p.crs;
+      int i;
+      
+      assert (mode == INTEGER);
+      *values = xmalloc (sizeof **values * crs->count);
+      for (i = 0; i < crs->count; i++)
+       (*values)[i].f = i + crs->min;
+      *nvalues = crs->count;
+    }
+}
+
+/* Sets cell (C,R) in TABLE, with options OPT, to have a value taken
+   from V, displayed with print format spec from variable VAR.  When
+   in REPORT missing-value mode, missing values have an M appended. */
+static void
+table_value_missing (struct tab_table *table, int c, int r, unsigned char opt,
+                    const union value *v, const struct variable *var)
+{
+  struct len_string s;
+
+  char *label = get_val_lab (var, *v, 0);
+  if (label) 
+    {
+      tab_text (table, c, r, TAB_LEFT, label);
+      return;
+    }
+
+  s.length = var->print.w;
+  s.string = tab_alloc (table, s.length + 1);
+  data_out (s.string, &var->print, v);
+  if (cmd.miss == CRS_REPORT && is_num_user_missing (v->f, var))
+    s.string[s.length++] = 'M';
+  while (s.length && *s.string == ' ')
+    {
+      s.length--;
+      s.string++;
+    }
+  tab_raw (table, c, r, opt, &s);
+}
+
+/* Draws a line across TABLE at the current row to indicate the most
+   major dimension variable with index FIRST_DIFFERENCE out of NVAR
+   that changed, and puts the values that changed into the table.  TB
+   and X must be the corresponding table_entry and crosstab,
+   respectively. */
+static void
+display_dimensions (struct tab_table *table, int first_difference, struct table_entry *tb)
+{
+  tab_hline (table, TAL_1, nvar - first_difference - 1, tab_nc (table) - 1, 0);
+
+  for (; first_difference >= 2; first_difference--)
+    table_value_missing (table, nvar - first_difference - 1, 0,
+                        TAB_RIGHT, &tb->v[first_difference],
+                        x->v[first_difference]);
+}
+
+/* Put value V into cell (C,R) of TABLE, suffixed with letter M. */
+static void
+float_M_suffix (struct tab_table *table, int c, int r, double v)
+{
+  static const struct fmt_spec f = {FMT_F, 8, 0};
+  struct len_string s;
+
+  s.length = 9;
+  s.string = tab_alloc (table, 9);
+  s.string[8] = 'M';
+  data_out (s.string, &f, (union value *) &v);
+  while (*s.string == ' ')
+    {
+      s.length--;
+      s.string++;
+    }
+  tab_raw (table, c, r, TAB_RIGHT, &s);
+}
+
+/* Displays the crosstabulation table. */
+static void
+display_crosstabulation (void)
+{
+  {
+    int r;
+       
+    for (r = 0; r < n_rows; r++)
+      table_value_missing (table, nvar - 2, r * num_cells,
+                          TAB_RIGHT, &rows[r], x->v[ROW_VAR]);
+  }
+  tab_text (table, nvar - 2, n_rows * num_cells,
+           TAB_LEFT, _("Total"));
+      
+  /* Put in the actual cells. */
+  {
+    double *mp = mat;
+    int r, c, i;
+
+    tab_offset (table, nvar - 1, -1);
+    for (r = 0; r < n_rows; r++)
+      {
+       if (num_cells > 1)
+         tab_hline (table, TAL_1, -1, n_cols, 0);
+       for (c = 0; c < n_cols; c++)
+         {
+           double expected_value;
+
+           if (expected)
+             expected_value = row_tot[r] * col_tot[c] / W;
+           for (i = 0; i < num_cells; i++)
+             {
+               double v;
+
+               switch (cells[i])
+                 {
+                 case CRS_CL_COUNT:
+                   v = *mp;
+                   break;
+                 case CRS_CL_ROW:
+                   v = *mp / row_tot[r] * 100.;
+                   break;
+                 case CRS_CL_COLUMN:
+                   v = *mp / col_tot[c] * 100.;
+                   break;
+                 case CRS_CL_TOTAL:
+                   v = *mp / W * 100.;
+                   break;
+                 case CRS_CL_EXPECTED:
+                   v = expected_value;
+                   break;
+                 case CRS_CL_RESIDUAL:
+                   v = *mp - expected_value;
+                   break;
+                 case CRS_CL_SRESIDUAL:
+                   v = (*mp - expected_value) / sqrt (expected_value);
+                   break;
+                 case CRS_CL_ASRESIDUAL:
+                   v = ((*mp - expected_value)
+                        / sqrt (expected_value
+                                * (1. - row_tot[r] / W)
+                                * (1. - col_tot[c] / W)));
+                   break;
+                 default:
+                   assert (0);
+                 }
+
+               if (cmd.miss == CRS_REPORT
+                   && (is_num_user_missing (cols[c].f, x->v[COL_VAR])
+                       || is_num_user_missing (rows[r].f, x->v[ROW_VAR])))
+                 float_M_suffix (table, c, i, v);
+               else if (v != 0.)
+                 tab_float (table, c, i, TAB_RIGHT, v, 8, 0);
+             }
+
+           mp++;
+         }
+
+       tab_offset (table, -1, tab_row (table) + num_cells);
+      }
+  }
+
+  /* Row totals. */
+  {
+    int r, i;
+
+    tab_offset (table, -1, tab_row (table) - num_cells * n_rows);
+    for (r = 0; r < n_rows; r++)
+      for (i = 0; i < num_cells; i++)
+       {
+         double v;
+
+         switch (cells[i])
+           {
+           case CRS_CL_COUNT:
+             v = row_tot[r];
+             break;
+           case CRS_CL_ROW:
+             v = 100.;
+             break;
+           case CRS_CL_COLUMN:
+             v = row_tot[r] / W * 100.;
+             break;
+           case CRS_CL_TOTAL:
+             v = row_tot[r] / W * 100.;
+             break;
+           case CRS_CL_EXPECTED:
+           case CRS_CL_RESIDUAL:
+           case CRS_CL_SRESIDUAL:
+           case CRS_CL_ASRESIDUAL:
+             v = 0.;
+             break;
+           default:
+             assert (0);
+           }
+
+         if (cmd.miss == CRS_REPORT
+             && is_num_user_missing (rows[r].f, x->v[ROW_VAR]))
+           float_M_suffix (table, n_cols, 0, v);
+         else if (v != 0.)
+           tab_float (table, n_cols, 0, TAB_RIGHT, v, 8, 0);
+
+         tab_next_row (table);
+       }
+  }
+
+  /* Column totals, grand total. */
+  {
+    int c, j;
+
+    if (num_cells > 1)
+      tab_hline (table, TAL_1, -1, n_cols, 0);
+    for (c = 0; c <= n_cols; c++)
+      {
+       double ct = c < n_cols ? col_tot[c] : W;
+       int i;
+           
+       for (i = j = 0; i < num_cells; i++)
+         {
+           double v;
+
+           switch (cells[i])
+             {
+             case CRS_CL_COUNT:
+               v = ct;
+               break;
+             case CRS_CL_ROW:
+               v = ct / W * 100.;
+               break;
+             case CRS_CL_COLUMN:
+               v = 100.;
+               break;
+             case CRS_CL_TOTAL:
+               v = ct / W * 100.;
+               break;
+             case CRS_CL_EXPECTED:
+             case CRS_CL_RESIDUAL:
+             case CRS_CL_SRESIDUAL:
+             case CRS_CL_ASRESIDUAL:
+               continue;
+             default:
+               assert (0);
+             }
+
+           if (cmd.miss == CRS_REPORT && c < n_cols 
+               && is_num_user_missing (cols[c].f, x->v[COL_VAR]))
+             float_M_suffix (table, c, j, v);
+           else if (v != 0.)
+             tab_float (table, c, j, TAB_RIGHT, v, 8, 0);
+
+           j++;
+         }
+      }
+
+    tab_offset (table, -1, tab_row (table) + j);
+  }
+  
+  tab_offset (table, 0, -1);
+}
+
+static void calc_r (double *X, double *Y, double *, double *, double *);
+static void calc_chisq (double[N_CHISQ], int[N_CHISQ], double *, double *);
+
+/* Display chi-square statistics. */
+static void
+display_chisq (void)
+{
+  static const char *chisq_stats[N_CHISQ] = 
+    {
+      N_("Pearson Chi-Square"),
+      N_("Likelihood Ratio"),
+      N_("Fisher's Exact Test"),
+      N_("Continuity Correction"),
+      N_("Linear-by-Linear Association"),
+    };
+  double chisq_v[N_CHISQ];
+  double fisher1, fisher2;
+  int df[N_CHISQ];
+  int s = 0;
+
+  int i;
+             
+  calc_chisq (chisq_v, df, &fisher1, &fisher2);
+
+  tab_offset (chisq, nvar - 2, -1);
+  
+  for (i = 0; i < N_CHISQ; i++)
+    {
+      if ((i != 2 && chisq_v[i] == SYSMIS)
+         || (i == 2 && fisher1 == SYSMIS))
+       continue;
+      s = 1;
+      
+      tab_text (chisq, 0, 0, TAB_LEFT, gettext (chisq_stats[i]));
+      if (i != 2)
+       {
+         tab_float (chisq, 1, 0, TAB_RIGHT, chisq_v[i], 8, 3);
+         tab_float (chisq, 2, 0, TAB_RIGHT, df[i], 8, 0);
+         tab_float (chisq, 3, 0, TAB_RIGHT,
+                    chisq_sig (chisq_v[i], df[i]), 8, 3);
+       }
+      else
+       {
+         chisq_fisher = 1;
+         tab_float (chisq, 4, 0, TAB_RIGHT, fisher2, 8, 3);
+         tab_float (chisq, 5, 0, TAB_RIGHT, fisher1, 8, 3);
+       }
+      tab_next_row (chisq);
+    }
+
+  tab_text (chisq, 0, 0, TAB_LEFT, _("N of Valid Cases"));
+  tab_float (chisq, 1, 0, TAB_RIGHT, W, 8, 0);
+  tab_next_row (chisq);
+    
+  tab_offset (chisq, 0, -1);
+}
+
+static int calc_symmetric (double[N_SYMMETRIC], double[N_SYMMETRIC],
+                          double[N_SYMMETRIC]);
+
+/* Display symmetric measures. */
+static void
+display_symmetric (void)
+{
+  static const char *categories[] = 
+    {
+      N_("Nominal by Nominal"),
+      N_("Ordinal by Ordinal"),
+      N_("Interval by Interval"),
+      N_("Measure of Agreement"),
+    };
+
+  static const char *stats[N_SYMMETRIC] =
+    {
+      N_("Phi"),
+      N_("Cramer's V"),
+      N_("Contingency Coefficient"),
+      N_("Kendall's tau-b"),
+      N_("Kendall's tau-c"),
+      N_("Gamma"),
+      N_("Spearman Correlation"),
+      N_("Pearson's R"),
+      N_("Kappa"),
+    };
+
+  static const int stats_categories[N_SYMMETRIC] =
+    {
+      0, 0, 0, 1, 1, 1, 1, 2, 3,
+    };
+
+  int last_cat = -1;
+  double sym_v[N_SYMMETRIC], sym_ase[N_SYMMETRIC], sym_t[N_SYMMETRIC];
+  int i;
+
+  if (!calc_symmetric (sym_v, sym_ase, sym_t))
+    return;
+
+  tab_offset (sym, nvar - 2, -1);
+  
+  for (i = 0; i < N_SYMMETRIC; i++)
+    {
+      if (sym_v[i] == SYSMIS)
+       continue;
+
+      if (stats_categories[i] != last_cat)
+       {
+         last_cat = stats_categories[i];
+         tab_text (sym, 0, 0, TAB_LEFT, gettext (categories[last_cat]));
+       }
+      
+      tab_text (sym, 1, 0, TAB_LEFT, gettext (stats[i]));
+      tab_float (sym, 2, 0, TAB_RIGHT, sym_v[i], 8, 3);
+      if (sym_ase[i] != SYSMIS)
+       tab_float (sym, 3, 0, TAB_RIGHT, sym_ase[i], 8, 3);
+      if (sym_t[i] != SYSMIS)
+       tab_float (sym, 4, 0, TAB_RIGHT, sym_t[i], 8, 3);
+      /*tab_float (sym, 5, 0, TAB_RIGHT, normal_sig (sym_v[i]), 8, 3);*/
+      tab_next_row (sym);
+    }
+
+  tab_text (sym, 0, 0, TAB_LEFT, _("N of Valid Cases"));
+  tab_float (sym, 2, 0, TAB_RIGHT, W, 8, 0);
+  tab_next_row (sym);
+    
+  tab_offset (sym, 0, -1);
+}
+
+static int calc_risk (double[], double[], double[], union value *);
+
+/* Display risk estimate. */
+static void
+display_risk (void)
+{
+  char buf[256];
+  double risk_v[3], lower[3], upper[3];
+  union value c[2];
+  int i;
+  
+  if (!calc_risk (risk_v, upper, lower, c))
+    return;
+  
+  tab_offset (risk, nvar - 2, -1);
+  
+  for (i = 0; i < 3; i++)
+    {
+      if (risk_v[i] == SYSMIS)
+       continue;
+
+      switch (i)
+       {
+       case 0:
+         if (x->v[COL_VAR]->type == NUMERIC)
+           sprintf (buf, _("Odds Ratio for %s (%g / %g)"),
+                    x->v[COL_VAR]->name, c[0].f, c[1].f);
+         else
+           sprintf (buf, _("Odds Ratio for %s (%.*s / %.*s)"),
+                    x->v[COL_VAR]->name,
+                    x->v[COL_VAR]->width, c[0].s,
+                    x->v[COL_VAR]->width, c[1].s);
+         break;
+       case 1:
+       case 2:
+         if (x->v[ROW_VAR]->type == NUMERIC)
+           sprintf (buf, _("For cohort %s = %g"),
+                    x->v[ROW_VAR]->name, rows[i - 1].f);
+         else
+           sprintf (buf, _("For cohort %s = %.*s"),
+                    x->v[ROW_VAR]->name,
+                    x->v[ROW_VAR]->width, rows[i - 1].s);
+         break;
+       }
+                  
+      tab_text (risk, 0, 0, TAB_LEFT, buf);
+      tab_float (risk, 1, 0, TAB_RIGHT, risk_v[i], 8, 3);
+      tab_float (risk, 2, 0, TAB_RIGHT, lower[i], 8, 3);
+      tab_float (risk, 3, 0, TAB_RIGHT, upper[i], 8, 3);
+      tab_next_row (risk);
+    }
+
+  tab_text (risk, 0, 0, TAB_LEFT, _("N of Valid Cases"));
+  tab_float (risk, 1, 0, TAB_RIGHT, W, 8, 0);
+  tab_next_row (risk);
+    
+  tab_offset (risk, 0, -1);
+}
+
+static int calc_directional (double[N_DIRECTIONAL], double[N_DIRECTIONAL],
+                            double[N_DIRECTIONAL]);
+
+/* Display directional measures. */
+static void
+display_directional (void)
+{
+  static const char *categories[] = 
+    {
+      N_("Nominal by Nominal"),
+      N_("Ordinal by Ordinal"),
+      N_("Nominal by Interval"),
+    };
+
+  static const char *stats[] =
+    {
+      N_("Lambda"),
+      N_("Goodman and Kruskal tau"),
+      N_("Uncertainty Coefficient"),
+      N_("Somers' d"),
+      N_("Eta"),
+    };
+
+  static const char *types[] = 
+    {
+      N_("Symmetric"),
+      N_("%s Dependent"),
+      N_("%s Dependent"),
+    };
+
+  static const int stats_categories[N_DIRECTIONAL] =
+    {
+      0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2,
+    };
+  
+  static const int stats_stats[N_DIRECTIONAL] = 
+    {
+      0, 0, 0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4,
+    };
+
+  static const int stats_types[N_DIRECTIONAL] = 
+    {
+      0, 1, 2, 1, 2, 0, 1, 2, 0, 1, 2, 1, 2,
+    };
+
+  static const int *stats_lookup[] = 
+    {
+      stats_categories,
+      stats_stats,
+      stats_types,
+    };
+
+  static const char **stats_names[] =
+    {
+      categories,
+      stats,
+      types,
+    };
+
+  int last[3] =
+    {
+      -1, -1, -1,
+    };
+    
+  double direct_v[N_DIRECTIONAL];
+  double direct_ase[N_DIRECTIONAL];
+  double direct_t[N_DIRECTIONAL];
+  
+  int i;
+
+  if (!calc_directional (direct_v, direct_ase, direct_t))
+    return;
+
+  tab_offset (direct, nvar - 2, -1);
+  
+  for (i = 0; i < N_DIRECTIONAL; i++)
+    {
+      if (direct_v[i] == SYSMIS)
+       continue;
+      
+      {
+       int j;
+
+       for (j = 0; j < 3; j++)
+         if (last[j] != stats_lookup[j][i])
+           {
+             if (j < 2)
+               tab_hline (direct, TAL_1, j, 6, 0);
+             
+             for (; j < 3; j++)
+               {
+                 char *string;
+                 int k = last[j] = stats_lookup[j][i];
+
+                 if (k == 0)
+                   string = NULL;
+                 else if (k == 1)
+                   string = x->v[0]->name;
+                 else
+                   string = x->v[1]->name;
+                 
+                 tab_text (direct, j, 0, TAB_LEFT | TAT_PRINTF,
+                           gettext (stats_names[j][k]), string);
+               }
+           }
+      }
+      
+      tab_float (direct, 3, 0, TAB_RIGHT, direct_v[i], 8, 3);
+      if (direct_ase[i] != SYSMIS)
+       tab_float (direct, 4, 0, TAB_RIGHT, direct_ase[i], 8, 3);
+      if (direct_t[i] != SYSMIS)
+       tab_float (direct, 5, 0, TAB_RIGHT, direct_t[i], 8, 3);
+      /*tab_float (direct, 6, 0, TAB_RIGHT, normal_sig (direct_v[i]), 8, 3);*/
+      tab_next_row (direct);
+    }
+
+  tab_offset (direct, 0, -1);
+}
+\f
+/* Statistical calculations. */
+
+/* Returns the value of the gamma (factorial) function for an integer
+   argument X. */
+double
+gamma_int (double x)
+{
+  double r = 1;
+  int i;
+  
+  for (i = 2; i < x; i++)
+    r *= i;
+  return r;
+}
+
+/* Calculate P_r as specified in _SPSS Statistical Algorithms_,
+   Appendix 5. */
+static inline double
+Pr (int a, int b, int c, int d)
+{
+  return (gamma_int (a + b + 1.) / gamma_int (a + 1.)
+         * gamma_int (c + d + 1.) / gamma_int (b + 1.)
+         * gamma_int (a + c + 1.) / gamma_int (c + 1.)
+         * gamma_int (b + d + 1.) / gamma_int (d + 1.)
+         / gamma_int (a + b + c + d + 1.));
+}
+
+/* Swap the contents of A and B. */
+static inline void
+swap (int *a, int *b)
+{
+  int t = *a;
+  *a = *b;
+  *b = t;
+}
+
+/* Calculate significance for Fisher's exact test as specified in
+   _SPSS Statistical Algorithms_, Appendix 5. */
+static void
+calc_fisher (int a, int b, int c, int d, double *fisher1, double *fisher2)
+{
+  int x;
+  
+  if (min (c, d) < min (a, b))
+    swap (&a, &c), swap (&b, &d);
+  if (min (b, d) < min (a, c))
+    swap (&a, &b), swap (&c, &d);
+  if (b * c < a * d)
+    {
+      if (b < c)
+       swap (&a, &b), swap (&c, &d);
+      else
+       swap (&a, &c), swap (&b, &d);
+    }
+
+  *fisher1 = 0.;
+  for (x = 0; x <= a; x++)
+    *fisher1 += Pr (a - x, b + x, c + x, d - x);
+
+  *fisher2 = *fisher1;
+  for (x = 1; x <= b; x++)
+    *fisher2 += Pr (a + x, b - x, c - x, d + x);
+}
+
+/* Calculates chi-squares into CHISQ.  MAT is a matrix with N_COLS
+   columns with values COLS and N_ROWS rows with values ROWS.  Values
+   in the matrix sum to W. */
+static void
+calc_chisq (double chisq[N_CHISQ], int df[N_CHISQ],
+           double *fisher1, double *fisher2)
+{
+  int r, c;
+
+  chisq[0] = chisq[1] = 0.;
+  chisq[2] = chisq[3] = chisq[4] = SYSMIS;
+  *fisher1 = *fisher2 = SYSMIS;
+
+  df[0] = df[1] = (ns_cols - 1) * (ns_rows - 1);
+
+  if (ns_rows <= 1 || ns_cols <= 1)
+    {
+      chisq[0] = chisq[1] = SYSMIS;
+      return;
+    }
+
+  for (r = 0; r < n_rows; r++)
+    for (c = 0; c < n_cols; c++)
+      {
+       const double expected = row_tot[r] * col_tot[c] / W;
+       const double freq = mat[n_cols * r + c];
+       const double residual = freq - expected;
+    
+       if (expected)
+         chisq[0] += residual * residual / expected;
+       if (freq)
+         chisq[1] += freq * log (expected / freq);
+      }
+
+  if (chisq[0] == 0.)
+    chisq[0] = SYSMIS;
+
+  if (chisq[1] != 0.)
+    chisq[1] *= -2.;
+  else
+    chisq[1] = SYSMIS;
+
+  /* Calculate Yates and Fisher exact test. */
+  if (ns_cols == 2 && ns_rows == 2)
+    {
+      double f11, f12, f21, f22;
+      
+      {
+       int nz_cols[2];
+       int i, j;
+
+       for (i = j = 0; i < n_cols; i++)
+         if (col_tot[i] != 0.)
+           {
+             nz_cols[j++] = i;
+             if (j == 2)
+               break;
+           }
+
+       assert (j == 2);
+
+       f11 = mat[nz_cols[0]];
+       f12 = mat[nz_cols[1]];
+       f21 = mat[nz_cols[0] + n_cols];
+       f22 = mat[nz_cols[1] + n_cols];
+      }
+
+      /* Yates. */
+      {
+       const double x = fabs (f11 * f22 - f12 * f21) - 0.5 * W;
+
+       if (x > 0.)
+         chisq[3] = (W * x * x
+                     / (f11 + f12) / (f21 + f22)
+                     / (f11 + f21) / (f12 + f22));
+       else
+         chisq[3] = 0.;
+
+       df[3] = 1.;
+      }
+
+      /* Fisher. */
+      if (f11 < 5. || f12 < 5. || f21 < 5. || f22 < 5.)
+       calc_fisher (f11 + .5, f12 + .5, f21 + .5, f22 + .5, fisher1, fisher2);
+    }
+
+  /* Calculate Mantel-Haenszel. */
+  if (x->v[ROW_VAR]->type == NUMERIC && x->v[COL_VAR]->type == NUMERIC)
+    {
+      double r, ase_0, ase_1;
+      calc_r ((double *) rows, (double *) cols, &r, &ase_0, &ase_1);
+    
+      chisq[4] = (W - 1.) * r * r;
+      df[4] = 1;
+    }
+}
+
+/* Calculate the value of Pearson's r.  r is stored into R, ase_1 into
+   ASE_1, and ase_0 into ASE_0.  The row and column values must be
+   passed in X and Y. */
+static void
+calc_r (double *X, double *Y, double *r, double *ase_0, double *ase_1)
+{
+  double SX, SY, S, T;
+  double Xbar, Ybar;
+  double sum_XYf, sum_X2Y2f;
+  double sum_Xr, sum_X2r;
+  double sum_Yc, sum_Y2c;
+  int i, j;
+
+  for (sum_X2Y2f = sum_XYf = 0., i = 0; i < n_rows; i++)
+    for (j = 0; j < n_cols; j++)
+      {
+       double fij = mat[j + i * n_cols];
+       double product = X[i] * Y[j];
+       double temp = fij * product;
+       sum_XYf += temp;
+       sum_X2Y2f += temp * product;
+      }
+
+  for (sum_Xr = sum_X2r = 0., i = 0; i < n_rows; i++)
+    {
+      sum_Xr += X[i] * row_tot[i];
+      sum_X2r += X[i] * X[i] * row_tot[i];
+    }
+  Xbar = sum_Xr / W;
+
+  for (sum_Yc = sum_Y2c = 0., i = 0; i < n_cols; i++)
+    {
+      sum_Yc += Y[i] * col_tot[i];
+      sum_Y2c += Y[i] * Y[i] * col_tot[i];
+    }
+  Ybar = sum_Yc / W;
+
+  S = sum_XYf - sum_Xr * sum_Yc / W;
+  SX = sum_X2r - sum_Xr * sum_Xr / W;
+  SY = sum_Y2c - sum_Yc * sum_Yc / W;
+  T = sqrt (SX * SY);
+  *r = S / T;
+  *ase_0 = sqrt ((sum_X2Y2f - (sum_XYf * sum_XYf) / W) / (sum_X2r * sum_Y2c));
+  
+  {
+    double s, c, y, t;
+    
+    for (s = c = 0., i = 0; i < n_rows; i++)
+      for (j = 0; j < n_cols; j++)
+       {
+         double Xresid, Yresid;
+         double temp;
+
+         Xresid = X[i] - Xbar;
+         Yresid = Y[j] - Ybar;
+         temp = (T * Xresid * Yresid
+                 - ((S / (2. * T))
+                    * (Xresid * Xresid * SY + Yresid * Yresid * SX)));
+         y = mat[j + i * n_cols] * temp * temp - c;
+         t = s + y;
+         c = (t - s) - y;
+         s = t;
+       }
+    *ase_1 = sqrt (s) / (T * T);
+  }
+}
+
+static double somers_d_v[3];
+static double somers_d_ase[3];
+static double somers_d_t[3];
+
+/* Calculate symmetric statistics and their asymptotic standard
+   errors.  Returns 0 if none could be calculated. */
+static int
+calc_symmetric (double v[N_SYMMETRIC], double ase[N_SYMMETRIC],
+               double t[N_SYMMETRIC])
+{
+  int q = min (ns_rows, ns_cols);
+  
+  if (q <= 1)
+    return 0;
+  
+  {
+    int i;
+
+    if (v) 
+      for (i = 0; i < N_SYMMETRIC; i++)
+       v[i] = ase[i] = t[i] = SYSMIS;
+  }
+
+  /* Phi, Cramer's V, contingency coefficient. */
+  if (cmd.a_statistics[CRS_ST_PHI] || cmd.a_statistics[CRS_ST_CC])
+    {
+      double Xp = 0.;  /* Pearson chi-square. */
+
+      {
+       int r, c;
+    
+       for (r = 0; r < n_rows; r++)
+         for (c = 0; c < n_cols; c++)
+           {
+             const double expected = row_tot[r] * col_tot[c] / W;
+             const double freq = mat[n_cols * r + c];
+             const double residual = freq - expected;
+    
+             if (expected)
+               Xp += residual * residual / expected;
+           }
+      }
+
+      if (cmd.a_statistics[CRS_ST_PHI])
+       {
+         v[0] = sqrt (Xp / W);
+         v[1] = sqrt (Xp / (W * (q - 1)));
+       }
+      if (cmd.a_statistics[CRS_ST_CC])
+       v[2] = sqrt (Xp / (Xp + W));
+    }
+  
+  if (cmd.a_statistics[CRS_ST_BTAU] || cmd.a_statistics[CRS_ST_CTAU]
+      || cmd.a_statistics[CRS_ST_GAMMA] || cmd.a_statistics[CRS_ST_D])
+    {
+      double *cum;
+      double Dr, Dc;
+      double P, Q;
+      double btau_cum, ctau_cum, gamma_cum, d_yx_cum, d_xy_cum;
+      double btau_var;
+      
+      {
+       int r, c;
+       
+       Dr = Dc = W * W;
+       for (r = 0; r < n_rows; r++)
+         Dr -= row_tot[r] * row_tot[r];
+       for (c = 0; c < n_cols; c++)
+         Dc -= col_tot[c] * col_tot[c];
+      }
+      
+      {
+       int r, c;
+
+       cum = xmalloc (sizeof *cum * n_cols * n_rows);
+       for (c = 0; c < n_cols; c++)
+         {
+           double ct = 0.;
+           
+           for (r = 0; r < n_rows; r++)
+             cum[c + r * n_cols] = ct += mat[c + r * n_cols];
+         }
+      }
+      
+      /* P and Q. */
+      {
+       int i, j;
+       double Cij, Dij;
+
+       P = Q = 0.;
+       for (i = 0; i < n_rows; i++)
+         {
+           Cij = Dij = 0.;
+
+           for (j = 1; j < n_cols; j++)
+             Cij += col_tot[j] - cum[j + i * n_cols];
+
+           if (i > 0)
+             for (j = 1; j < n_cols; j++)
+               Dij += cum[j + (i - 1) * n_cols];
+
+           for (j = 0;;)
+             {
+               double fij = mat[j + i * n_cols];
+               P += fij * Cij;
+               Q += fij * Dij;
+               
+               if (++j == n_cols)
+                 break;
+               assert (j < n_cols);
+
+               Cij -= col_tot[j] - cum[j + i * n_cols];
+               Dij += col_tot[j - 1] - cum[j - 1 + i * n_cols];
+               
+               if (i > 0)
+                 {
+                   Cij += cum[j - 1 + (i - 1) * n_cols];
+                   Dij -= cum[j + (i - 1) * n_cols];
+                 }
+             }
+         }
+      }
+
+      if (cmd.a_statistics[CRS_ST_BTAU])
+       v[3] = (P - Q) / sqrt (Dr * Dc);
+      if (cmd.a_statistics[CRS_ST_CTAU])
+       v[4] = (q * (P - Q)) / ((W * W) * (q - 1));
+      if (cmd.a_statistics[CRS_ST_GAMMA])
+       v[5] = (P - Q) / (P + Q);
+
+      /* ASE for tau-b, tau-c, gamma.  Calculations could be
+        eliminated here, at expense of memory.  */
+      {
+       int i, j;
+       double Cij, Dij;
+
+       btau_cum = ctau_cum = gamma_cum = d_yx_cum = d_xy_cum = 0.;
+       for (i = 0; i < n_rows; i++)
+         {
+           Cij = Dij = 0.;
+
+           for (j = 1; j < n_cols; j++)
+             Cij += col_tot[j] - cum[j + i * n_cols];
+
+           if (i > 0)
+             for (j = 1; j < n_cols; j++)
+               Dij += cum[j + (i - 1) * n_cols];
+
+           for (j = 0;;)
+             {
+               double fij = mat[j + i * n_cols];
+
+               if (cmd.a_statistics[CRS_ST_BTAU])
+                 {
+                   const double temp = (2. * sqrt (Dr * Dc) * (Cij - Dij)
+                                        + v[3] * (row_tot[i] * Dc
+                                                  + col_tot[j] * Dr));
+                   btau_cum += fij * temp * temp;
+                 }
+               
+               {
+                 const double temp = Cij - Dij;
+                 ctau_cum += fij * temp * temp;
+               }
+
+               if (cmd.a_statistics[CRS_ST_GAMMA])
+                 {
+                   const double temp = Q * Cij - P * Dij;
+                   gamma_cum += fij * temp * temp;
+                 }
+
+               if (cmd.a_statistics[CRS_ST_D])
+                 {
+                   d_yx_cum += fij * sqr (Dr * (Cij - Dij)
+                                          - (P - Q) * (W - row_tot[i]));
+                   d_xy_cum += fij * sqr (Dc * (Dij - Cij)
+                                          - (Q - P) * (W - col_tot[j]));
+                 }
+               
+               if (++j == n_cols)
+                 break;
+               assert (j < n_cols);
+
+               Cij -= col_tot[j] - cum[j + i * n_cols];
+               Dij += col_tot[j - 1] - cum[j - 1 + i * n_cols];
+               
+               if (i > 0)
+                 {
+                   Cij += cum[j - 1 + (i - 1) * n_cols];
+                   Dij -= cum[j + (i - 1) * n_cols];
+                 }
+             }
+         }
+      }
+
+      btau_var = ((btau_cum
+                  - (W * sqr (W * (P - Q) / sqrt (Dr * Dc) * (Dr + Dc))))
+                 / sqr (Dr * Dc));
+      if (cmd.a_statistics[CRS_ST_BTAU])
+       {
+         ase[3] = sqrt (btau_var);
+         t[3] = v[3] / (2 * sqrt ((ctau_cum - (P - Q) * (P - Q) / W)
+                                  / (Dr * Dc)));
+       }
+      if (cmd.a_statistics[CRS_ST_CTAU])
+       {
+         ase[4] = ((2 * q / ((q - 1) * W * W))
+                   * sqrt (ctau_cum - (P - Q) * (P - Q) / W));
+         t[4] = v[4] / ase[4];
+       }
+      if (cmd.a_statistics[CRS_ST_GAMMA])
+       {
+         ase[5] = ((4. / ((P + Q) * (P + Q))) * sqrt (gamma_cum));
+         t[5] = v[5] / (2. / (P + Q)
+                        * sqrt (ctau_cum - (P - Q) * (P - Q) / W));
+       }
+      if (cmd.a_statistics[CRS_ST_D])
+       {
+         somers_d_v[0] = (P - Q) / (.5 * (Dc + Dr));
+         somers_d_ase[0] = 2. * btau_var / (Dr + Dc) * sqrt (Dr * Dc);
+         somers_d_t[0] = (somers_d_v[0]
+                          / (4 / (Dc + Dr)
+                             * sqrt (ctau_cum - sqr (P - Q) / W)));
+         somers_d_v[1] = (P - Q) / Dc;
+         somers_d_ase[1] = 2. / sqr (Dc) * sqrt (d_xy_cum);
+         somers_d_t[1] = (somers_d_v[1]
+                          / (2. / Dc
+                             * sqrt (ctau_cum - sqr (P - Q) / W)));
+         somers_d_v[2] = (P - Q) / Dr;
+         somers_d_ase[2] = 2. / sqr (Dr) * sqrt (d_yx_cum);
+         somers_d_t[2] = (somers_d_v[2]
+                          / (2. / Dr
+                             * sqrt (ctau_cum - sqr (P - Q) / W)));
+       }
+
+      free (cum);
+    }
+
+  /* Spearman correlation, Pearson's r. */
+  if (cmd.a_statistics[CRS_ST_CORR])
+    {
+      double *R = local_alloc (sizeof *R * n_rows);
+      double *C = local_alloc (sizeof *C * n_cols);
+      
+      {
+       double y, t, c = 0., s = 0.;
+       int i = 0;
+       
+       for (;;)
+         {
+           R[i] = s + (row_tot[i] + 1.) / 2.;
+           y = row_tot[i] - c;
+           t = s + y;
+           c = (t - s) - y;
+           s = t;
+           if (++i == n_rows)
+             break;
+           assert (i < n_rows);
+         }
+      }
+      
+      {
+       double y, t, c = 0., s = 0.;
+       int j = 0;
+       
+       for (;;)
+         {
+           C[j] = s + (col_tot[j] + 1.) / 2;
+           y = col_tot[j] - c;
+           t = s + y;
+           c = (t - s) - y;
+           s = t;
+           if (++j == n_cols)
+             break;
+           assert (j < n_cols);
+         }
+      }
+      
+      calc_r (R, C, &v[6], &t[6], &ase[6]);
+      t[6] = v[6] / t[6];
+
+      local_free (R);
+      local_free (C);
+
+      calc_r ((double *) rows, (double *) cols, &v[7], &t[7], &ase[7]);
+      t[7] = v[7] / t[7];
+    }
+
+  /* Cohen's kappa. */
+  if (cmd.a_statistics[CRS_ST_KAPPA] && ns_rows == ns_cols)
+    {
+      double sum_fii, sum_rici, sum_fiiri_ci, sum_fijri_ci2, sum_riciri_ci;
+      int i, j;
+      
+      for (sum_fii = sum_rici = sum_fiiri_ci = sum_riciri_ci = 0., i = j = 0;
+          i < ns_rows; i++, j++)
+       {
+         double prod, sum;
+         
+         while (col_tot[j] == 0.)
+           j++;
+         
+         prod = row_tot[i] * col_tot[j];
+         sum = row_tot[i] + col_tot[j];
+         
+         sum_fii += mat[j + i * n_cols];
+         sum_rici += prod;
+         sum_fiiri_ci += mat[j + i * n_cols] * sum;
+         sum_riciri_ci += prod * sum;
+       }
+      for (sum_fijri_ci2 = 0., i = 0; i < ns_rows; i++)
+       for (j = 0; j < ns_cols; j++)
+         {
+           double sum = row_tot[i] + col_tot[j];
+           sum_fijri_ci2 += mat[j + i * n_cols] * sum * sum;
+         }
+      
+      v[8] = (W * sum_fii - sum_rici) / (W * W - sum_rici);
+
+      ase[8] = sqrt ((W * W * sum_rici
+                     + sum_rici * sum_rici
+                     - W * sum_riciri_ci)
+                    / (W * (W * W - sum_rici) * (W * W - sum_rici)));
+#if 0
+      t[8] = v[8] / sqrt (W * (((sum_fii * (W - sum_fii))
+                               / sqr (W * W - sum_rici))
+                              + ((2. * (W - sum_fii)
+                                  * (2. * sum_fii * sum_rici
+                                     - W * sum_fiiri_ci))
+                                 / cube (W * W - sum_rici))
+                              + (sqr (W - sum_fii)
+                                 * (W * sum_fijri_ci2 - 4.
+                                    * sum_rici * sum_rici)
+                                 / hypercube (W * W - sum_rici))));
+#else
+      t[8] = v[8] / ase[8];
+#endif
+    }
+
+  return 1;
+}
+
+/* Calculate risk estimate. */
+static int
+calc_risk (double *value, double *upper, double *lower, union value *c)
+{
+  double f11, f12, f21, f22;
+  double v;
+
+  {
+    int i;
+      
+    for (i = 0; i < 3; i++)
+      value[i] = upper[i] = lower[i] = SYSMIS;
+  }
+    
+  if (ns_rows != 2 || ns_cols != 2)
+    return 0;
+  
+  {
+    int nz_cols[2];
+    int i, j;
+
+    for (i = j = 0; i < n_cols; i++)
+      if (col_tot[i] != 0.)
+       {
+         nz_cols[j++] = i;
+         if (j == 2)
+           break;
+       }
+
+    assert (j == 2);
+
+    f11 = mat[nz_cols[0]];
+    f12 = mat[nz_cols[1]];
+    f21 = mat[nz_cols[0] + n_cols];
+    f22 = mat[nz_cols[1] + n_cols];
+
+    c[0] = cols[nz_cols[0]];
+    c[1] = cols[nz_cols[1]];
+  }
+
+  value[0] = (f11 * f22) / (f12 * f21);
+  v = sqrt (1. / f11 + 1. / f12 + 1. / f21 + 1. / f22);
+  lower[0] = value[0] * exp (-1.960 * v);
+  upper[0] = value[0] * exp (1.960 * v);
+
+  value[1] = (f11 * (f21 + f22)) / (f21 * (f11 + f12));
+  v = sqrt ((f12 / (f11 * (f11 + f12)))
+           + (f22 / (f21 * (f21 + f22))));
+  lower[1] = value[1] * exp (-1.960 * v);
+  upper[1] = value[1] * exp (1.960 * v);
+    
+  value[2] = (f12 * (f21 + f22)) / (f22 * (f11 + f12));
+  v = sqrt ((f11 / (f12 * (f11 + f12)))
+           + (f21 / (f22 * (f21 + f22))));
+  lower[2] = value[2] * exp (-1.960 * v);
+  upper[2] = value[2] * exp (1.960 * v);
+
+  return 1;
+}
+
+/* Calculate directional measures. */
+static int
+calc_directional (double v[N_DIRECTIONAL], double ase[N_DIRECTIONAL],
+                 double t[N_DIRECTIONAL])
+{
+  {
+    int i;
+
+    for (i = 0; i < N_DIRECTIONAL; i++)
+      v[i] = ase[i] = t[i] = SYSMIS;
+  }
+
+  /* Lambda. */
+  if (cmd.a_statistics[CRS_ST_LAMBDA])
+    {
+      double *fim = xmalloc (sizeof *fim * n_rows);
+      int *fim_index = xmalloc (sizeof *fim_index * n_rows);
+      double *fmj = xmalloc (sizeof *fmj * n_cols);
+      int *fmj_index = xmalloc (sizeof *fmj_index * n_cols);
+      double sum_fim, sum_fmj;
+      double rm, cm;
+      int rm_index, cm_index;
+      int i, j;
+
+      /* Find maximum for each row and their sum. */
+      for (sum_fim = 0., i = 0; i < n_rows; i++)
+       {
+         double max = mat[i * n_cols];
+         int index = 0;
+
+         for (j = 1; j < n_cols; j++)
+           if (mat[j + i * n_cols] > max)
+             {
+               max = mat[j + i * n_cols];
+               index = j;
+             }
+       
+         sum_fim += fim[i] = max;
+         fim_index[i] = index;
+       }
+
+      /* Find maximum for each column. */
+      for (sum_fmj = 0., j = 0; j < n_cols; j++)
+       {
+         double max = mat[j];
+         int index = 0;
+
+         for (i = 1; i < n_rows; i++)
+           if (mat[j + i * n_cols] > max)
+             {
+               max = mat[j + i * n_cols];
+               index = i;
+             }
+       
+         sum_fmj += fmj[j] = max;
+         fmj_index[j] = index;
+       }
+
+      /* Find maximum row total. */
+      rm = row_tot[0];
+      rm_index = 0;
+      for (i = 1; i < n_rows; i++)
+       if (row_tot[i] > rm)
+         {
+           rm = row_tot[i];
+           rm_index = i;
+         }
+
+      /* Find maximum column total. */
+      cm = col_tot[0];
+      cm_index = 0;
+      for (j = 1; j < n_cols; j++)
+       if (col_tot[j] > cm)
+         {
+           cm = col_tot[j];
+           cm_index = j;
+         }
+
+      v[0] = (sum_fim + sum_fmj - cm - rm) / (2. * W - rm - cm);
+      v[1] = (sum_fmj - rm) / (W - rm);
+      v[2] = (sum_fim - cm) / (W - cm);
+
+      /* ASE1 for Y given X. */
+      {
+       double accum;
+
+       for (accum = 0., i = 0; i < n_rows; i++)
+         for (j = 0; j < n_cols; j++)
+           {
+             const int deltaj = j == cm_index;
+             accum += (mat[j + i * n_cols]
+                       * sqr ((j == fim_index[i])
+                              - deltaj
+                              + v[0] * deltaj));
+           }
+      
+       ase[2] = sqrt (accum - W * v[0]) / (W - cm);
+      }
+
+      /* ASE0 for Y given X. */
+      {
+       double accum;
+      
+       for (accum = 0., i = 0; i < n_rows; i++)
+         if (cm_index != fim_index[i])
+           accum += (mat[i * n_cols + fim_index[i]]
+                     + mat[i * n_cols + cm_index]);
+       t[2] = v[2] / (sqrt (accum - sqr (sum_fim - cm) / W) / (W - cm));
+      }
+
+      /* ASE1 for X given Y. */
+      {
+       double accum;
+
+       for (accum = 0., i = 0; i < n_rows; i++)
+         for (j = 0; j < n_cols; j++)
+           {
+             const int deltaj = i == rm_index;
+             accum += (mat[j + i * n_cols]
+                       * sqr ((i == fmj_index[j])
+                              - deltaj
+                              + v[0] * deltaj));
+           }
+      
+       ase[1] = sqrt (accum - W * v[0]) / (W - rm);
+      }
+
+      /* ASE0 for X given Y. */
+      {
+       double accum;
+      
+       for (accum = 0., j = 0; j < n_cols; j++)
+         if (rm_index != fmj_index[j])
+           accum += (mat[j + n_cols * fmj_index[j]]
+                     + mat[j + n_cols * rm_index]);
+       t[1] = v[1] / (sqrt (accum - sqr (sum_fmj - rm) / W) / (W - rm));
+      }
+
+      /* Symmetric ASE0 and ASE1. */
+      {
+       double accum0;
+       double accum1;
+
+       for (accum0 = accum1 = 0., i = 0; i < n_rows; i++)
+         for (j = 0; j < n_cols; j++)
+           {
+             int temp0 = (fmj_index[j] == i) + (fim_index[i] == j);
+             int temp1 = (i == rm_index) + (j == cm_index);
+             accum0 += mat[j + i * n_cols] * sqr (temp0 - temp1);
+             accum1 += (mat[j + i * n_cols]
+                        * sqr (temp0 + (v[0] - 1.) * temp1));
+           }
+       ase[0] = sqrt (accum1 - 4. * W * v[0] * v[0]) / (2. * W - rm - cm);
+       t[0] = v[0] / (sqrt (accum0 - sqr ((sum_fim + sum_fmj - cm - rm) / W))
+                      / (2. * W - rm - cm));
+      }
+
+      free (fim);
+      free (fim_index);
+      free (fmj);
+      free (fmj_index);
+      
+      {
+       double sum_fij2_ri, sum_fij2_ci;
+       double sum_ri2, sum_cj2;
+
+       for (sum_fij2_ri = sum_fij2_ci = 0., i = 0; i < n_rows; i++)
+         for (j = 0; j < n_cols; j++)
+           {
+             double temp = sqr (mat[j + i * n_cols]);
+             sum_fij2_ri += temp / row_tot[i];
+             sum_fij2_ci += temp / col_tot[j];
+           }
+
+       for (sum_ri2 = 0., i = 0; i < n_rows; i++)
+         sum_ri2 += row_tot[i] * row_tot[i];
+
+       for (sum_cj2 = 0., j = 0; j < n_cols; j++)
+         sum_cj2 += col_tot[j] * col_tot[j];
+
+       v[3] = (W * sum_fij2_ci - sum_ri2) / (W * W - sum_ri2);
+       v[4] = (W * sum_fij2_ri - sum_cj2) / (W * W - sum_cj2);
+      }
+    }
+
+  if (cmd.a_statistics[CRS_ST_UC])
+    {
+      double UX, UY, UXY, P;
+      double ase1_yx, ase1_xy, ase1_sym;
+      int i, j;
+
+      for (UX = 0., i = 0; i < n_rows; i++)
+       if (row_tot[i] > 0.)
+         UX -= row_tot[i] / W * log (row_tot[i] / W);
+      
+      for (UY = 0., j = 0; j < n_cols; j++)
+       if (col_tot[j] > 0.)
+         UY -= col_tot[j] / W * log (col_tot[j] / W);
+
+      for (UXY = P = 0., i = 0; i < n_rows; i++)
+       for (j = 0; j < n_cols; j++)
+         {
+           double entry = mat[j + i * n_cols];
+
+           if (entry <= 0.)
+             continue;
+           
+           P += entry * sqr (log (col_tot[j] * row_tot[i] / (W * entry)));
+           UXY -= entry / W * log (entry / W);
+         }
+
+      for (ase1_yx = ase1_xy = ase1_sym = 0., i = 0; i < n_rows; i++)
+       for (j = 0; j < n_cols; j++)
+         {
+           double entry = mat[j + i * n_cols];
+
+           if (entry <= 0.)
+             continue;
+           
+           ase1_yx += entry * sqr (UY * log (entry / row_tot[i])
+                                   + (UX - UXY) * log (col_tot[j] / W));
+           ase1_xy += entry * sqr (UX * log (entry / col_tot[j])
+                                   + (UY - UXY) * log (row_tot[i] / W));
+           ase1_sym += entry * sqr ((UXY
+                                     * log (row_tot[i] * col_tot[j] / (W * W)))
+                                    - (UX + UY) * log (entry / W));
+         }
+      
+      v[5] = 2. * ((UX + UY - UXY) / (UX + UY));
+      ase[5] = (2. / (W * sqr (UX + UY))) * sqrt (ase1_sym);
+      t[5] = v[5] / ((2. / (W * (UX + UY)))
+                    * sqrt (P - sqr (UX + UY - UXY) / W));
+                   
+      v[6] = (UX + UY - UXY) / UX;
+      ase[6] = sqrt (ase1_xy) / (W * UX * UX);
+      t[6] = v[6] / (sqrt (P - W * sqr (UX + UY - UXY)) / (W * UX));
+      
+      v[7] = (UX + UY - UXY) / UY;
+      ase[7] = sqrt (ase1_yx) / (W * UY * UY);
+      t[7] = v[7] / (sqrt (P - W * sqr (UX + UY - UXY)) / (W * UY));
+    }
+
+  /* Somers' D. */
+  if (cmd.a_statistics[CRS_ST_D])
+    {
+      int i;
+      
+      if (!sym)
+       calc_symmetric (NULL, NULL, NULL);
+      for (i = 0; i < 3; i++)
+       {
+         v[8 + i] = somers_d_v[i];
+         ase[8 + i] = somers_d_ase[i];
+         t[8 + i] = somers_d_t[i];
+       }
+    }
+
+  /* Eta. */
+  if (cmd.a_statistics[CRS_ST_ETA])
+    {
+      {
+       double sum_Xr, sum_X2r;
+       double SX, SXW;
+       int i, j;
+      
+       for (sum_Xr = sum_X2r = 0., i = 0; i < n_rows; i++)
+         {
+           sum_Xr += rows[i].f * row_tot[i];
+           sum_X2r += rows[i].f * rows[i].f * row_tot[i];
+         }
+       SX = sum_X2r - sum_Xr * sum_Xr / W;
+      
+       for (SXW = 0., j = 0; j < n_cols; j++)
+         {
+           double cum;
+
+           for (cum = 0., i = 0; i < n_rows; i++)
+             {
+               SXW += rows[i].f * rows[i].f * mat[j + i * n_cols];
+               cum += rows[i].f * mat[j + i * n_cols];
+             }
+
+           SXW -= cum * cum / col_tot[j];
+         }
+       v[11] = sqrt (1. - SXW / SX);
+      }
+
+      {
+       double sum_Yc, sum_Y2c;
+       double SY, SYW;
+       int i, j;
+
+       for (sum_Yc = sum_Y2c = 0., i = 0; i < n_cols; i++)
+         {
+           sum_Yc += cols[i].f * col_tot[i];
+           sum_Y2c += cols[i].f * cols[i].f * col_tot[i];
+         }
+       SY = sum_Y2c - sum_Yc * sum_Yc / W;
+
+       for (SYW = 0., i = 0; i < n_rows; i++)
+         {
+           double cum;
+
+           for (cum = 0., j = 0; j < n_cols; j++)
+             {
+               SYW += cols[j].f * cols[j].f * mat[j + i * n_cols];
+               cum += cols[j].f * mat[j + i * n_cols];
+             }
+         
+           SYW -= cum * cum / row_tot[i];
+         }
+       v[12] = sqrt (1. - SYW / SY);
+      }
+    }
+
+  return 1;
+}
+
+/* 
+   Local Variables:
+   mode: c
+   End:
+*/
diff --git a/src/data-in.c b/src/data-in.c
new file mode 100644 (file)
index 0000000..04c035d
--- /dev/null
@@ -0,0 +1,1591 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <math.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "data-in.h"
+#include "error.h"
+#include "getline.h"
+#include "julcal/julcal.h"
+#include "lexer.h"
+#include "magic.h"
+#include "misc.h"
+#include "settings.h"
+#include "str.h"
+#include "var.h"
+\f
+#undef DEBUGGING
+/*#define DEBUGGING 1 */
+#include "debug-print.h"
+
+\f
+/* Specialized error routine. */
+
+static void dls_error (const struct data_in *, const char *format, ...)
+     __attribute__ ((format (printf, 2, 3)));
+
+static void
+dls_error (const struct data_in *i, const char *format, ...)
+{
+  char buf[1024];
+
+  if (i->flags & DI_IGNORE_ERROR)
+    return;
+
+  {
+    va_list args;
+
+    va_start (args, format);
+    snprintf (buf, 1024, format, args);
+    va_end (args);
+  }
+  
+  {
+    struct error e;
+    struct string title;
+
+    ds_init (NULL, &title, 64);
+    if (!getl_reading_script)
+      ds_concat (&title, _("data-file error: "));
+    if (i->f1 == i->f2)
+      ds_printf (&title, _("(column %d"), i->f1);
+    else
+      ds_printf (&title, _("(columns %d-%d"), i->f1, i->f2);
+    ds_printf (&title, _(", field type %s) "), fmt_to_string (&i->format));
+    
+    e.class = DE;
+    err_location (&e.where);
+    e.title = ds_value (&title);
+    e.text = buf;
+
+    err_vmsg (&e);
+
+    ds_destroy (&title);
+  }
+}
+
+/* Excludes leading and trailing whitespace from I by adjusting
+   pointers. */
+static void
+trim_whitespace (struct data_in *i)
+{
+  while (i->s < i->e && isspace (i->s[0])) 
+    i->s++;
+
+  while (i->s < i->e && isspace (i->e[-1]))
+    i->e--;
+}
+
+/* Returns nonzero if we're not at the end of the string being
+   parsed. */
+static inline int
+have_char (struct data_in *i)
+{
+  return i->s < i->e;
+}
+\f
+/* Format parsers. */ 
+
+static int parse_int (struct data_in *i, long *result);
+
+/* This function is based on strtod() from the GNU C library. */
+static int
+parse_numeric (struct data_in *i)
+{
+  short int sign;              /* +1 or -1. */
+  double num;                  /* The number so far.  */
+
+  int got_dot;                 /* Found a decimal point.  */
+  int got_digit;               /* Count of digits.  */
+
+  int decimal;                 /* Decimal point character. */
+  int grouping;                        /* Grouping character. */
+
+  long int exponent;           /* Number's exponent. */
+  int type;                    /* Usually same as i->format.type. */
+
+  trim_whitespace (i);
+
+  type = i->format.type;
+  if (type == FMT_DOLLAR && have_char (i) && *i->s == '$')
+    {
+      i->s++;
+      type = FMT_COMMA;
+    }
+
+  /* Get the sign.  */
+  if (have_char (i))
+    {
+      sign = *i->s == '-' ? -1 : 1;
+      if (*i->s == '-' || *i->s == '+')
+       i->s++;
+    }
+  
+  if (type != FMT_DOT)
+    {
+      decimal = set_decimal;
+      grouping = set_grouping;
+    }
+  else
+    {
+      decimal = set_grouping;
+      grouping = set_decimal;
+    }
+
+  i->v->f = SYSMIS;
+  num = 0.0;
+  got_dot = 0;
+  got_digit = 0;
+  exponent = 0;
+  for (; have_char (i); i->s++)
+    {
+      if (isdigit (*i->s))
+       {
+         got_digit++;
+
+         /* Make sure that multiplication by 10 will not overflow.  */
+         if (num > DBL_MAX * 0.1)
+           /* The value of the digit doesn't matter, since we have already
+              gotten as many digits as can be represented in a `double'.
+              This doesn't necessarily mean the result will overflow.
+              The exponent may reduce it to within range.
+
+              We just need to record that there was another
+              digit so that we can multiply by 10 later.  */
+           ++exponent;
+         else
+           num = (num * 10.0) + (*i->s - '0');
+
+         /* Keep track of the number of digits after the decimal point.
+            If we just divided by 10 here, we would lose precision.  */
+         if (got_dot)
+           --exponent;
+       }
+      else if (!got_dot && *i->s == decimal)
+       /* Record that we have found the decimal point.  */
+       got_dot = 1;
+      else if ((type != FMT_COMMA && type != FMT_DOT) || *i->s != grouping)
+       /* Any other character terminates the number.  */
+       break;
+    }
+
+  if (!got_digit)
+    {
+      if (got_dot)
+       {
+         i->v->f = SYSMIS;
+         return 1;
+       }
+      goto noconv;
+    }
+  
+  if (have_char (i)
+      && (tolower (*i->s) == 'e' || tolower (*i->s) == 'd'
+         || (type == FMT_E && (*i->s == '+' || *i->s == '-'))))
+    {
+      /* Get the exponent specified after the `e' or `E'.  */
+      long exp;
+
+      if (isalpha (*i->s))
+       i->s++;
+      if (!parse_int (i, &exp))
+       goto noconv;
+
+      exponent += exp;
+    }
+  else if (!got_dot)
+    exponent -= i->format.d;
+
+  if (type == FMT_PCT && have_char (i) && *i->s == '%')
+    i->s++;
+  if (i->s < i->e)
+    {
+      dls_error (i, _("Field contents followed by garbage."));
+      i->v->f = SYSMIS;
+      return 0;
+    }
+
+  if (num == 0.0)
+    {
+      i->v->f = 0.0;
+      return 1;
+    }
+
+  /* Multiply NUM by 10 to the EXPONENT power, checking for overflow
+     and underflow.  */
+
+  if (exponent < 0)
+    {
+      if (-exponent + got_digit > -(DBL_MIN_10_EXP) + 5
+         || num < DBL_MIN * pow (10.0, (double) -exponent))
+       goto underflow;
+      num *= pow (10.0, (double) exponent);
+    }
+  else if (exponent > 0)
+    {
+      if (num > DBL_MAX * pow (10.0, (double) -exponent))
+       goto overflow;
+      num *= pow (10.0, (double) exponent);
+    }
+
+  i->v->f = sign * num;
+  return 1;
+
+overflow:
+  /* Return an overflow error.  */
+  dls_error (i, _("Overflow in floating-point constant."));
+  i->v->f = SYSMIS;
+  return 0;
+
+underflow:
+  /* Return an underflow error.  */
+  dls_error (i, _("Underflow in floating-point constant."));
+  i->v->f = 0.0;
+  return 0;
+
+noconv:
+  /* There was no number.  */
+  dls_error (i, _("Field does not form a valid floating-point constant."));
+  i->v->f = SYSMIS;
+  return 0;
+}
+
+/* Returns the integer value of hex digit C. */
+static inline int
+hexit_value (int c)
+{
+  const char s[] = "0123456789abcdef";
+  const char *cp = strchr (s, tolower ((unsigned char) c));
+
+  assert (cp != NULL);
+  return cp - s;
+}
+
+static inline int
+parse_N (struct data_in *i)
+{
+  const unsigned char *cp;
+
+  for (cp = i->s; cp < i->e; cp++)
+    {
+      if (!isdigit (*cp))
+       {
+         dls_error (i, _("All characters in field must be digits."));
+         return 0;
+       }
+
+      i->v->f = i->v->f * 10.0 + *cp - '0';
+    }
+
+  if (i->format.d)
+    i->v->f /= pow (10.0, i->format.d);
+  return 1;
+}
+
+static inline int
+parse_PIBHEX (struct data_in *i)
+{
+  double n;
+  const unsigned char *cp;
+
+  trim_whitespace (i);
+
+  n = 0.0;
+  for (cp = i->s; cp < i->e; cp++)
+    {
+      if (!isxdigit (*cp))
+       {
+         dls_error (i, _("Unrecognized character in field."));
+         return 0;
+       }
+
+      n = n * 16.0 + hexit_value (*cp);
+    }
+  
+  i->v->f = n;
+  return 1;
+}
+
+static inline int
+parse_RBHEX (struct data_in *i)
+{
+  /* Validate input. */
+  trim_whitespace (i);
+  if ((i->e - i->s) % 2)
+    {
+      dls_error (i, _("Field must have even length."));
+      return 0;
+    }
+  
+  {
+    const unsigned char *cp;
+    
+    for (cp = i->s; cp < i->e; cp++)
+      if (!isxdigit (*cp))
+       {
+         dls_error (i, _("Field must contain only hex digits."));
+         return 0;
+       }
+  }
+  
+  /* Parse input. */
+  {
+    union
+      {
+       double d;
+       unsigned char c[sizeof (double)];
+      }
+    u;
+
+    int j;
+
+    memset (u.c, 0, sizeof u.c);
+    for (j = 0; j < min ((i->e - i->s) / 2, sizeof u.d); j++)
+      u.c[j] = 16 * hexit_value (i->s[j * 2]) + hexit_value (i->s[j * 2 + 1]);
+
+    i->v->f = u.d;
+  }
+  
+  return 1;
+}
+
+static inline int
+parse_Z (struct data_in *i)
+{
+  char buf[64];
+
+  /* Warn user that we suck. */
+  {
+    static int warned;
+
+    if (!warned)
+      {
+       msg (MW, _("Quality of zoned decimal (Z) input format code is "
+                  "suspect.  Check your results three times, report bugs "
+                  "to author."));
+       warned = 1;
+      }
+  }
+
+  /* Validate input. */
+  trim_whitespace (i);
+
+  if (i->e - i->s < 2)
+    {
+      dls_error (i, _("Zoned decimal field contains fewer than 2 "
+                     "characters."));
+      return 0;
+    }
+
+  /* Copy sign into buf[0]. */
+  if ((i->e[-1] & 0xc0) != 0xc0)
+    {
+      dls_error (i, _("Bad sign byte in zoned decimal number."));
+      return 0;
+    }
+  buf[0] = (i->e[-1] ^ (i->e[-1] >> 1)) & 0x10 ? '-' : '+';
+
+  /* Copy digits into buf[1 ... len - 1] and terminate string. */
+  {
+    const unsigned char *sp;
+    char *dp;
+
+    for (sp = i->s, dp = buf + 1; sp < i->e - 1; sp++, dp++)
+      if (*sp == '.')
+       *dp = '.';
+      else if ((*sp & 0xf0) == 0xf0 && (*sp & 0xf) < 10)
+       *dp = (*sp & 0xf) + '0';
+      else
+       {
+         dls_error (i, _("Format error in zoned decimal number."));
+         return 0;
+       }
+
+    *dp = '\0';
+  }
+
+  /* Parse as number. */
+  {
+    char *tail;
+    
+    i->v->f = strtod ((char *) buf, (char **) &tail);
+    if ((unsigned char *) tail != i->e)
+      {
+       dls_error (i, _("Error in syntax of zoned decimal number."));
+       return 0;
+      }
+  }
+  
+  return 1;
+}
+
+static inline int
+parse_IB (struct data_in *i)
+{
+  char buf[64];
+  const char *p;
+
+  unsigned char xor;
+
+  /* We want the data to be in big-endian format.  If this is a
+     little-endian machine, reverse the byte order. */
+  if (endian == LITTLE)
+    {
+      memcpy (buf, i->s, i->e - i->s);
+      mm_reverse (buf, i->e - i->s);
+      p = buf;
+    }
+  else
+    p = i->s;
+
+  /* If the value is negative, we need to logical-NOT each value
+     before adding it. */
+  if (p[0] & 0x80)
+    xor = 0xff;
+  else
+    xor = 0x00;
+  
+  {
+    int j;
+
+    i->v->f = 0.0;
+    for (j = 0; j < i->e - i->s; j++)
+      i->v->f = i->v->f * 256.0 + (p[j] ^ xor);
+  }
+
+  /* If the value is negative, add 1 and set the sign, to complete a
+     two's-complement negation. */
+  if (p[0] & 0x80)
+    i->v->f = -(i->v->f + 1.0);
+
+  if (i->format.d)
+    i->v->f /= pow (10.0, i->format.d);
+
+  return 1;
+}
+
+static inline int
+parse_PIB (struct data_in *i)
+{
+  int j;
+
+  i->v->f = 0.0;
+  if (endian == BIG)
+    for (j = 0; j < i->e - i->s; j++)
+      i->v->f = i->v->f * 256.0 + i->s[j];
+  else
+    for (j = i->e - i->s - 1; j >= 0; j--)
+      i->v->f = i->v->f * 256.0 + i->s[j];
+
+  if (i->format.d)
+    i->v->f /= pow (10.0, i->format.d);
+
+  return 1;
+}
+
+static inline int
+parse_P (struct data_in *i)
+{
+  const unsigned char *cp;
+
+  i->v->f = 0.0;
+  for (cp = i->s; cp < i->e - 1; cp++)
+    {
+      i->v->f = i->v->f * 10 + (*cp >> 4);
+      i->v->f = i->v->f * 10 + (*cp & 15);
+    }
+  i->v->f = i->v->f * 10 + (*cp >> 4);
+  if ((*cp ^ (*cp >> 1)) & 0x10)
+      i->v->f = -i->v->f;
+
+  if (i->format.d)
+    i->v->f /= pow (10.0, i->format.d);
+
+  return 1;
+}
+
+static inline int
+parse_PK (struct data_in *i)
+{
+  const unsigned char *cp;
+
+  i->v->f = 0.0;
+  for (cp = i->s; cp < i->e; cp++)
+    {
+      i->v->f = i->v->f * 10 + (*cp >> 4);
+      i->v->f = i->v->f * 10 + (*cp & 15);
+    }
+
+  if (i->format.d)
+    i->v->f /= pow (10.0, i->format.d);
+
+  return 1;
+}
+
+static inline int
+parse_RB (struct data_in *i)
+{
+  union
+    {
+      double d;
+      unsigned char c[sizeof (double)];
+    }
+  u;
+
+  memset (u.c, 0, sizeof u.c);
+  memcpy (u.c, i->s, min ((int) sizeof (u.c), i->e - i->s));
+  i->v->f = u.d;
+
+  return 1;
+}
+
+static inline int
+parse_A (struct data_in *i)
+{
+  ptrdiff_t len = i->e - i->s;
+  
+  if (len >= i->format.w)
+    memcpy (i->v->s, i->s, i->format.w);
+  else
+    {
+      memcpy (i->v->s, i->s, len);
+      memset (i->v->s + len, ' ', i->format.w - len);
+    }
+
+#if __CHECKER__
+  memset (i->v->s + i->format.w, '%',
+         REM_RND_UP (i->format.w, MAX_SHORT_STRING));
+#endif
+  
+  return 1;
+}
+
+static inline int
+parse_AHEX (struct data_in *i)
+{
+  /* Validate input. */
+  trim_whitespace (i);
+  if ((i->e - i->s) % 2)
+    {
+      dls_error (i, _("Field must have even length."));
+      return 0;
+    }
+
+  {
+    const unsigned char *cp;
+    
+    for (cp = i->s; cp < i->e; cp++)
+      if (!isxdigit (*cp))
+       {
+         dls_error (i, _("Field must contain only hex digits."));
+         return 0;
+       }
+  }
+  
+  {
+    int j;
+    
+    /* Parse input. */
+    for (j = 0; j < min (i->e - i->s, i->format.w); j += 2)
+      i->v->s[j / 2] = hexit_value (i->s[j]) * 16 + hexit_value (i->s[j + 1]);
+    memset (i->v->s + (i->e - i->s) / 2, ' ', (i->format.w - (i->e - i->s)) / 2);
+  }
+  
+#if __CHECKER__
+  memset (i->v->s + i->format.w / 2, '%',
+         REM_RND_UP (i->format.w / 2, MAX_SHORT_STRING));
+#endif
+
+  return 1;
+}
+\f
+/* Date & time format components. */
+
+/* Advances *CP past any whitespace characters. */
+static inline void
+skip_whitespace (struct data_in *i)
+{
+  while (isspace ((unsigned char) *i->s))
+    i->s++;
+}
+
+static inline int
+parse_leader (struct data_in *i)
+{
+  skip_whitespace (i);
+  return 1;
+}
+
+static inline int
+force_have_char (struct data_in *i)
+{
+  if (have_char (i))
+    return 1;
+
+  dls_error (i, _("Unexpected end of field."));
+  return 0;
+}
+
+static int
+parse_int (struct data_in *i, long *result)
+{
+  int negative = 0;
+  
+  if (!force_have_char (i))
+    return 0;
+
+  if (*i->s == '+')
+    {
+      i->s++;
+      force_have_char (i);
+    }
+  else if (*i->s == '-')
+    {
+      negative = 1;
+      i->s++;
+      force_have_char (i);
+    }
+  
+  if (!isdigit (*i->s))
+    {
+      dls_error (i, _("Digit expected in field."));
+      return 0;
+    }
+
+  *result = 0;
+  for (;;)
+    {
+      *result = *result * 10 + *i->s++ - '0';
+      if (!have_char (i) || !isdigit (*i->s))
+       break;
+    }
+
+  if (negative)
+    *result = -*result;
+  return 1;
+}
+
+static int
+parse_day (struct data_in *i, long *day)
+{
+  if (!parse_int (i, day))
+    return 0;
+  if (*day >= 1 && *day <= 31)
+    return 1;
+
+  dls_error (i, _("Day (%ld) must be between 1 and 31."), *day);
+  return 0;
+}
+
+static int
+parse_day_count (struct data_in *i, long *day_count)
+{
+  return parse_int (i, day_count);
+}
+
+static int
+parse_date_delimiter (struct data_in *i)
+{
+  int delim = 0;
+
+  while (have_char (i)
+        && (*i->s == '-' || *i->s == '/' || isspace (*i->s)
+            || *i->s == '.' || *i->s == ','))
+    {
+      delim = 1;
+      i->s++;
+    }
+  if (delim)
+    return 1;
+
+  dls_error (i, _("Delimiter expected between fields in date."));
+  return 0;
+}
+
+/* Formats NUMBER as Roman numerals in ROMAN, or as Arabic numerals if
+   the Roman expansion would be too long. */
+static void
+to_roman (int number, char roman[32])
+{
+  int save_number = number;
+
+  struct roman_digit
+    {
+      int value;               /* Value corresponding to this digit. */
+      char name;               /* Digit name. */
+    };
+
+  static const struct roman_digit roman_tab[7] =
+  {
+    {1000, 'M'},
+    {500, 'D'},
+    {100, 'C'},
+    {50, 'L'},
+    {10, 'X'},
+    {5, 'V'},
+    {1, 'I'},
+  };
+
+  char *cp = roman;
+
+  int i, j;
+
+  assert (32 >= INT_DIGITS + 1);
+  if (number == 0)
+    goto arabic;
+
+  if (number < 0)
+    {
+      *cp++ = '-';
+      number = -number;
+    }
+
+  for (i = 0; i < 7; i++)
+    {
+      int digit = roman_tab[i].value;
+      while (number >= digit)
+       {
+         number -= digit;
+         if (cp > &roman[30])
+           goto arabic;
+         *cp++ = roman_tab[i].name;
+       }
+
+      for (j = i + 1; j < 7; j++)
+       {
+         if (i == 4 && j == 5) /* VX is not a shortened form of V. */
+           break;
+
+         digit = roman_tab[i].value - roman_tab[j].value;
+         while (number >= digit)
+           {
+             number -= digit;
+             if (cp > &roman[29])
+               goto arabic;
+             *cp++ = roman_tab[j].name;
+             *cp++ = roman_tab[i].name;
+           }
+       }
+    }
+  *cp = 0;
+  return;
+
+arabic:
+  sprintf (roman, "%d", save_number);
+}
+
+/* Returns true if C is a (lowercase) roman numeral. */
+#define CHAR_IS_ROMAN(C)                               \
+       ((C) == 'x' || (C) == 'v' || (C) == 'i')
+
+/* Returns the value of a single (lowercase) roman numeral. */
+#define ROMAN_VALUE(C)                         \
+       ((C) == 'x' ? 10 : ((C) == 'v' ? 5 : 1))
+
+static int
+parse_month (struct data_in *i, long *month)
+{
+  if (!force_have_char (i))
+    return 0;
+  
+  if (isdigit (*i->s))
+    {
+      if (!parse_int (i, month))
+       return 0;
+      if (*month >= 1 && *month <= 12)
+       return 1;
+      
+      dls_error (i, _("Month (%ld) must be between 1 and 12."), *month);
+      return 0;
+    }
+
+  if (CHAR_IS_ROMAN (tolower (*i->s)))
+    {
+      int last = ROMAN_VALUE (tolower (*i->s));
+
+      *month = 0;
+      for (;;)
+       {
+         int value;
+
+         i->s++;
+         if (!have_char || !CHAR_IS_ROMAN (tolower (*i->s)))
+           {
+             if (last != INT_MAX)
+               *month += last;
+             break;
+           }
+
+         value = ROMAN_VALUE (tolower (*i->s));
+         if (last == INT_MAX)
+           *month += value;
+         else if (value > last)
+           {
+             *month += value - last;
+             last = INT_MAX;
+           }
+         else
+           {
+             *month += last;
+             last = value;
+           }
+       }
+
+      if (*month < 1 || *month > 12)
+       {
+         char buf[32];
+
+         to_roman (*month, buf);
+         dls_error (i, _("Month (%s) must be between I and XII."), buf);
+         return 0;
+       }
+      
+      return 1;
+    }
+  
+  {
+    static const char *months[12] =
+      {
+       "january", "february", "march", "april", "may", "june",
+       "july", "august", "september", "october", "november", "december",
+      };
+
+    char month_buf[32];
+    char *mp;
+
+    int j;
+
+    for (mp = month_buf;
+        have_char (i) && isalpha (*i->s) && mp < &month_buf[31];
+        i->s++)
+      *mp++ = tolower (*i->s);
+    *mp = '\0';
+
+    if (have_char (i) && isalpha (*i->s))
+      {
+       dls_error (i, _("Month name (%s...) is too long."), month_buf);
+       return 0;
+      }
+
+    for (j = 0; j < 12; j++)
+      if (lex_id_match (months[j], month_buf))
+       {
+         *month = j + 1;
+         return 1;
+       }
+
+    dls_error (i, _("Bad month name (%s)."), month_buf);
+    return 0;
+  }
+}
+
+static int
+parse_year (struct data_in *i, long *year)
+{
+  if (!parse_int (i, year))
+    return 0;
+  
+  if (*year >= 0 && *year <= 199)
+    *year += 1900;
+  if (*year >= 1582 || *year <= 19999)
+    return 1;
+
+  dls_error (i, _("Year (%ld) must be between 1582 and 19999."), *year);
+  return 0;
+}
+
+static int
+parse_trailer (struct data_in *i)
+{
+  skip_whitespace (i);
+  if (!have_char (i))
+    return 1;
+  
+  dls_error (i, _("Trailing garbage \"%s\" following date."), i->s);
+  return 0;
+}
+
+static int
+parse_julian (struct data_in *i, long *julian)
+{
+  if (!parse_int (i, julian))
+    return 0;
+   
+  {
+    int day = *julian % 1000;
+
+    if (day < 1 || day > 366)
+      {
+       dls_error (i, _("Julian day (%d) must be between 1 and 366."), day);
+       return 0;
+      }
+  }
+  
+  {
+    int year = *julian / 1000;
+
+    if (year >= 0 && year <= 199)
+      *julian += 1900000L;
+    else if (year < 1582 || year > 19999)
+      {
+       dls_error (i, _("Year (%d) must be between 1582 and 19999."), year);
+       return 0;
+      }
+  }
+
+  return 1;
+}
+
+static int
+parse_quarter (struct data_in *i, long *quarter)
+{
+  if (!parse_int (i, quarter))
+    return 0;
+  if (*quarter >= 1 && *quarter <= 4)
+    return 1;
+
+  dls_error (i, _("Quarter (%ld) must be between 1 and 4."), *quarter);
+  return 0;
+}
+
+static int
+parse_q_delimiter (struct data_in *i)
+{
+  skip_whitespace (i);
+  if (!have_char (i) || tolower (*i->s) != 'q')
+    {
+      dls_error (i, _("`Q' expected between quarter and year."));
+      return 0;
+    }
+  i->s++;
+  skip_whitespace (i);
+  return 1;
+}
+
+static int
+parse_week (struct data_in *i, long *week)
+{
+  if (!parse_int (i, week))
+    return 0;
+  if (*week >= 1 && *week <= 53)
+    return 1;
+
+  dls_error (i, _("Week (%ld) must be between 1 and 53."), *week);
+  return 0;
+}
+
+static int
+parse_wk_delimiter (struct data_in *i)
+{
+  skip_whitespace (i);
+  if (i->s + 1 >= i->e
+      || tolower (i->s[0]) != 'w' || tolower (i->s[1]) != 'k')
+    {
+      dls_error (i, _("`WK' expected between week and year."));
+      return 0;
+    }
+  i->s += 2;
+  skip_whitespace (i);
+  return 1;
+}
+
+static int
+parse_time_delimiter (struct data_in *i)
+{
+  int delim = 0;
+
+  while (have_char (i)
+        && (*i->s == ':' || *i->s == '.' || isspace (*i->s)))
+    {
+      delim = 1;
+      i->s++;
+    }
+
+  if (delim)
+    return 1;
+  
+  dls_error (i, _("Delimiter expected between fields in time."));
+  return 0;
+}
+
+static int
+parse_hour (struct data_in *i, long *hour)
+{
+  if (!parse_int (i, hour))
+    return 0;
+  if (*hour >= 0)
+    return 1;
+  
+  dls_error (i, _("Hour (%ld) must be positive."), *hour);
+  return 0;
+}
+
+static int
+parse_minute (struct data_in *i, long *minute)
+{
+  if (!parse_int (i, minute))
+    return 0;
+  if (*minute >= 0 && *minute <= 59)
+    return 1;
+  
+  dls_error (i, _("Minute (%ld) must be between 0 and 59."), *minute);
+  return 0;
+}
+
+static int
+parse_opt_second (struct data_in *i, double *second)
+{
+  int delim = 0;
+
+  char buf[64];
+  char *cp;
+
+  while (have_char (i)
+        && (*i->s == ':' || *i->s == '.' || isspace (*i->s)))
+    {
+      delim = 1;
+      i->s++;
+    }
+  
+  if (!delim || !isdigit (*i->s))
+    {
+      *second = 0.0;
+      return 1;
+    }
+
+  cp = buf;
+  while (have_char (i) && isdigit (*i->s))
+    *cp++ = *i->s++;
+  if (have_char (i) && *i->s == '.')
+    *cp++ = *i->s++;
+  while (have_char (i) && isdigit (*i->s))
+    *cp++ = *i->s++;
+  *cp = '\0';
+  
+  *second = strtod (buf, NULL);
+
+  return 1;
+}
+
+static int
+parse_hour24 (struct data_in *i, long *hour24)
+{
+  if (!parse_int (i, hour24))
+    return 0;
+  if (*hour24 >= 0 && *hour24 <= 23)
+    return 1;
+  
+  dls_error (i, _("Hour (%ld) must be between 0 and 23."), *hour24);
+  return 0;
+}
+
+     
+static int
+parse_weekday (struct data_in *i, int *weekday)
+{
+  /* PORTME */
+  #define TUPLE(A,B)                           \
+         (((A) << 8) + (B))
+
+  if (i->s + 1 >= i->e)
+    {
+      dls_error (i, _("Day of the week expected in date value."));
+      return 0;
+    }
+
+  switch (TUPLE (tolower (i->s[0]), tolower (i->s[1])))
+    {
+    case TUPLE ('s', 'u'):
+      *weekday = 1;
+      break;
+
+    case TUPLE ('m', 'o'):
+      *weekday = 2;
+      break;
+
+    case TUPLE ('t', 'u'):
+      *weekday = 3;
+      break;
+
+    case TUPLE ('w', 'e'):
+      *weekday = 4;
+      break;
+
+    case TUPLE ('t', 'h'):
+      *weekday = 5;
+      break;
+
+    case TUPLE ('f', 'r'):
+      *weekday = 6;
+      break;
+
+    case TUPLE ('s', 'a'):
+      *weekday = 7;
+      break;
+
+    default:
+      dls_error (i, _("Day of the week expected in date value."));
+      return 0;
+    }
+
+  while (have_char (i) && isalpha (*i->s))
+    i->s++;
+
+  return 1;
+
+  #undef TUPLE
+}
+
+static int
+parse_spaces (struct data_in *i)
+{
+  skip_whitespace (i);
+  return 1;
+}
+
+static int
+parse_sign (struct data_in *i, int *sign)
+{
+  if (!force_have_char (i))
+    return 0;
+
+  switch (*i->s)
+    {
+    case '-':
+      i->s++;
+      *sign = 1;
+      break;
+
+    case '+':
+      i->s++;
+      /* fall through */
+
+    default:
+      *sign = 0;
+      break;
+    }
+
+  return 1;
+}
+\f
+/* Date & time formats. */
+
+static int
+valid_date (struct data_in *i)
+{
+  if (i->v->f == SYSMIS)
+    {
+      dls_error (i, _("Date is not in valid range between "
+                  "15 Oct 1582 and 31 Dec 19999."));
+      return 0;
+    }
+  else
+    return 1;
+}
+
+static int
+parse_DATE (struct data_in *i)
+{
+  long day, month, year;
+
+  if (!parse_leader (i)
+      || !parse_day (i, &day)
+      || !parse_date_delimiter (i)
+      || !parse_month (i, &month)
+      || !parse_date_delimiter (i)
+      || !parse_year (i, &year)
+      || !parse_trailer (i))
+    return 0;
+
+  i->v->f = calendar_to_julian (year, month, day);
+  if (!valid_date (i))
+    return 0;
+  i->v->f *= 60. * 60. * 24.;
+
+  return 1;
+}
+
+static int
+parse_ADATE (struct data_in *i)
+{
+  long month, day, year;
+
+  if (!parse_leader (i)
+      || !parse_month (i, &month)
+      || !parse_date_delimiter (i)
+      || !parse_day (i, &day)
+      || !parse_date_delimiter (i)
+      || !parse_year (i, &year)
+      || !parse_trailer (i))
+    return 0;
+
+  i->v->f = calendar_to_julian (year, month, day);
+  if (!valid_date (i))
+    return 0;
+  i->v->f *= 60. * 60. * 24.;
+
+  return 1;
+}
+
+static int
+parse_EDATE (struct data_in *i)
+{
+  long month, day, year;
+
+  if (!parse_leader (i)
+      || !parse_day (i, &day)
+      || !parse_date_delimiter (i)
+      || !parse_month (i, &month)
+      || !parse_date_delimiter (i)
+      || !parse_year (i, &year)
+      || !parse_trailer (i))
+    return 0;
+
+  i->v->f = calendar_to_julian (year, month, day);
+  if (!valid_date (i))
+    return 0;
+  i->v->f *= 60. * 60. * 24.;
+
+  return 1;
+}
+
+static int
+parse_SDATE (struct data_in *i)
+{
+  long month, day, year;
+
+  if (!parse_leader (i)
+      || !parse_year (i, &year)
+      || !parse_date_delimiter (i)
+      || !parse_month (i, &month)
+      || !parse_date_delimiter (i)
+      || !parse_day (i, &day)
+      || !parse_trailer (i))
+    return 0;
+
+  i->v->f = calendar_to_julian (year, month, day);
+  if (!valid_date (i))
+    return 0;
+  i->v->f *= 60. * 60. * 24.;
+
+  return 1;
+}
+
+static int
+parse_JDATE (struct data_in *i)
+{
+  long julian;
+  
+  if (!parse_leader (i)
+      || !parse_julian (i, &julian)
+      || !parse_trailer (i))
+    return 0;
+
+  if (julian / 1000 == 1582)
+    i->v->f = calendar_to_julian (1583, 1, 1) - 365;
+  else
+    i->v->f = calendar_to_julian (julian / 1000, 1, 1);
+
+  if (valid_date (i))
+    {
+      i->v->f = (i->v->f + julian % 1000 - 1) * 60. * 60. * 24.;
+      if (i->v->f < 0.)
+       i->v->f = SYSMIS;
+    }
+
+  return valid_date (i);
+}
+
+static int
+parse_QYR (struct data_in *i)
+{
+  long quarter, year;
+
+  if (!parse_leader (i)
+      || !parse_quarter (i, &quarter)
+      || !parse_q_delimiter (i)
+      || !parse_year (i, &year)
+      || !parse_trailer (i))
+    return 0;
+
+  i->v->f = calendar_to_julian (year, (quarter - 1) * 3 + 1, 1);
+  if (!valid_date (i))
+    return 0;
+  i->v->f *= 60. * 60. * 24.;
+
+  return 1;
+}
+
+static int
+parse_MOYR (struct data_in *i)
+{
+  long month, year;
+
+  if (!parse_leader (i)
+      || !parse_month (i, &month)
+      || !parse_date_delimiter (i)
+      || !parse_year (i, &year)
+      || !parse_trailer (i))
+    return 0;
+
+  i->v->f = calendar_to_julian (year, month, 1);
+  if (!valid_date (i))
+    return 0;
+  i->v->f *= 60. * 60. * 24.;
+
+  return 1;
+}
+
+static int
+parse_WKYR (struct data_in *i)
+{
+  long week, year;
+
+  if (!parse_leader (i)
+      || !parse_week (i, &week)
+      || !parse_wk_delimiter (i)
+      || !parse_year (i, &year)
+      || !parse_trailer (i))
+    return 0;
+
+  i->v->f = calendar_to_julian (year, 1, 1);
+  if (!valid_date (i))
+    return 0;
+  i->v->f = (i->v->f + (week - 1) * 7) * 60. * 60. * 24.;
+
+  return 1;
+}
+
+static int
+parse_TIME (struct data_in *i)
+{
+  int sign;
+  double second;
+  long hour, minute;
+
+  if (!parse_leader (i)
+      || !parse_sign (i, &sign)
+      || !parse_spaces (i)
+      || !parse_hour (i, &hour)
+      || !parse_time_delimiter (i)
+      || !parse_minute (i, &minute)
+      || !parse_opt_second (i, &second))
+    return 0;
+
+  i->v->f = hour * 60. * 60. + minute * 60. + second;
+  if (sign)
+    i->v->f = -i->v->f;
+  return 1;
+}
+
+static int
+parse_DTIME (struct data_in *i)
+{
+  int sign;
+  long day_count, hour;
+  double second;
+  long minute;
+
+  if (!parse_leader (i)
+      || !parse_sign (i, &sign)
+      || !parse_spaces (i)
+      || !parse_day_count (i, &day_count)
+      || !parse_time_delimiter (i)
+      || !parse_hour (i, &hour)
+      || !parse_time_delimiter (i)
+      || !parse_minute (i, &minute)
+      || !parse_opt_second (i, &second))
+    return 0;
+
+  i->v->f = (day_count * 60. * 60. * 24.
+            + hour * 60. * 60.
+            + minute * 60.
+            + second);
+  if (sign)
+    i->v->f = -i->v->f;
+  return 1;
+}
+
+static int
+parse_DATETIME (struct data_in *i)
+{
+  long day, month, year;
+  long hour24;
+  double second;
+  long minute;
+
+  if (!parse_leader (i)
+      || !parse_day (i, &day)
+      || !parse_date_delimiter (i)
+      || !parse_month (i, &month)
+      || !parse_date_delimiter (i)
+      || !parse_year (i, &year)
+      || !parse_time_delimiter (i)
+      || !parse_hour24 (i, &hour24)
+      || !parse_time_delimiter (i)
+      || !parse_minute (i, &minute)
+      || !parse_opt_second (i, &second))
+    return 0;
+
+  i->v->f = calendar_to_julian (year, month, day);
+  if (!valid_date (i))
+    return 0;
+  i->v->f = (i->v->f * 60. * 60. * 24.
+            + hour24 * 60. * 60.
+            + minute * 60.
+            + second);
+
+  return 1;
+}
+
+static int
+parse_WKDAY (struct data_in *i)
+{
+  int weekday;
+
+  if (!parse_leader (i)
+      || !parse_weekday (i, &weekday)
+      || !parse_trailer (i))
+    return 0;
+
+  i->v->f = weekday;
+  return 1;
+}
+
+static int
+parse_MONTH (struct data_in *i)
+{
+  long month;
+
+  if (!parse_leader (i)
+      || !parse_month (i, &month)
+      || !parse_trailer (i))
+    return 0;
+
+  i->v->f = month;
+  return 1;
+}
+\f
+/* Main dispatcher. */
+
+static void
+default_result (struct data_in *i)
+{
+  const struct fmt_desc *const fmt = &formats[i->format.type];
+
+  /* Default to SYSMIS or blanks. */
+  if (fmt->cat & FCAT_STRING)
+    {
+#if __CHECKER__
+      memset (i->v->s, ' ', ROUND_UP (i->format.w, MAX_SHORT_STRING));
+#else
+      memset (i->v->s, ' ', i->format.w);
+#endif
+    }
+  else
+    i->v->f = set_blanks;
+}
+
+int
+data_in (struct data_in *i)
+{
+  const struct fmt_desc *const fmt = &formats[i->format.type];
+
+  /* Check that we've got a string to work with. */
+  if (i->e == i->s || i->format.w <= 0)
+    {
+      default_result (i);
+      return 1;
+    }
+
+  i->f2 = i->f1 + (i->e - i->s) - 1;
+
+  /* Make sure that the string isn't too long. */
+  if (i->format.w > fmt->Imax_w)
+    {
+      dls_error (i, _("Field too long (%d characters).  Truncated after "
+                  "character %d."),
+                i->format.w, fmt->Imax_w);
+      i->format.w = fmt->Imax_w;
+    }
+
+  if (fmt->cat & FCAT_BLANKS_SYSMIS)
+    {
+      const unsigned char *cp;
+
+      cp = i->s;
+      for (;;)
+       {
+         if (!isspace (*cp))
+           break;
+
+         if (++cp == i->e)
+           {
+             i->v->f = set_blanks;
+             return 1;
+           }
+       }
+    }
+  
+  {
+    static int (*const handlers[FMT_NUMBER_OF_FORMATS])(struct data_in *) = 
+      {
+       parse_numeric, parse_N, parse_numeric, parse_numeric,
+       parse_numeric, parse_numeric, parse_numeric,
+       parse_Z, parse_A, parse_AHEX, parse_IB, parse_P, parse_PIB,
+       parse_PIBHEX, parse_PK, parse_RB, parse_RBHEX,
+       NULL, NULL, NULL, NULL, NULL,
+       parse_DATE, parse_EDATE, parse_SDATE, parse_ADATE, parse_JDATE,
+       parse_QYR, parse_MOYR, parse_WKYR,
+       parse_DATETIME, parse_TIME, parse_DTIME,
+       parse_WKDAY, parse_MONTH,
+      };
+
+    int (*handler)(struct data_in *);
+    int success;
+
+    handler = handlers[i->format.type];
+    assert (handler != NULL);
+
+    success = handler (i);
+    if (!success)
+      default_result (i);
+
+    return success;
+  }
+}
+\f
+/* Utility function. */
+
+/* Sets DI->{s,e} appropriately given that LINE has length LEN and the
+   field starts at one-based column FC and ends at one-based column
+   LC, inclusive. */
+void
+data_in_finite_line (struct data_in *di, const char *line, size_t len,
+                    int fc, int lc)
+{
+  di->s = line + ((size_t) fc <= len ? fc - 1 : len);
+  di->e = line + ((size_t) lc <= len ? lc : len);
+}
diff --git a/src/data-in.h b/src/data-in.h
new file mode 100644 (file)
index 0000000..c520920
--- /dev/null
@@ -0,0 +1,59 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !data_in_h
+#define data_in_h 1
+
+#include "format.h"
+
+/* Flags. */
+enum
+  {
+    DI_IGNORE_ERROR = 01,      /* Don't report errors to the user. */
+  };
+
+/* Information about parsing one data field. */
+struct data_in
+  {
+    const unsigned char *s;    /* Source start. */
+    const unsigned char *e;    /* Source end. */
+
+    union value *v;            /* Destination. */
+
+    int flags;                 /* Zero or more of DI_*. */
+    int f1, f2;                        /* Columns the field was taken from. */
+    struct fmt_spec format;    /* Format specification to use. */
+  };
+
+int data_in (struct data_in *);
+
+void data_in_finite_line (struct data_in *di, const char *line, size_t len,
+                         int fc, int lc);
+
+#if __GNUC__ >= 2
+extern inline void
+data_in_finite_line (struct data_in *di, const char *line, size_t len,
+                    int fc, int lc)
+{
+  di->s = line + ((size_t) fc <= len ? fc - 1 : len);
+  di->e = line + ((size_t) lc <= len ? lc : len);
+}
+#endif /* GNU C */
+
+#endif /* data-in.h */
diff --git a/src/data-list.c b/src/data-list.c
new file mode 100644 (file)
index 0000000..c775c8d
--- /dev/null
@@ -0,0 +1,1935 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <float.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "data-in.h"
+#include "debug-print.h"
+#include "dfm.h"
+#include "error.h"
+#include "file-handle.h"
+#include "format.h"
+#include "lexer.h"
+#include "misc.h"
+#include "settings.h"
+#include "str.h"
+#include "tab.h"
+#include "var.h"
+#include "vfm.h"
+\f
+/* Utility function. */
+
+/* FIXME: Either REPEATING DATA must be the last transformation, or we
+   must multiplex the transformations that follow (i.e., perform them
+   for every case that we produce from a repetition instance).
+   Currently we do neither.  We should do one or the other. */
+   
+/* Describes how to parse one variable. */
+struct dls_var_spec
+  {
+    struct dls_var_spec *next;
+    struct variable *v;                /* Associated variable.  Used only in
+                                  parsing.  Not safe later. */
+    char name[9];              /* Free-format: Name of variable. */
+    int rec;                   /* Fixed-format: Record number (1-based). */
+    int fc, lc;                        /* Fixed-format: Column numbers in record. */
+    struct fmt_spec input;     /* Input format of this field. */
+    int fv;                    /* First value in case. */
+    int type;                  /* 0=numeric, >0=width of alpha field. */
+  };
+
+/* Constants for DATA LIST type. */
+/* Must match table in cmd_data_list(). */
+enum
+  {
+    DLS_FIXED,
+    DLS_FREE,
+    DLS_LIST
+  };
+
+/* DATA LIST private data structure. */
+struct data_list_pgm
+  {
+    struct trns_header h;
+    struct dls_var_spec *spec; /* Variable parsing specifications. */
+    struct file_handle *handle;        /* Input file, never NULL. */
+    /* Do not reorder preceding fields. */
+
+    int type;                  /* A DLS_* constant. */
+    struct variable *end;      /* Variable specified on END subcommand. */
+    int eof;                   /* End of file encountered. */
+    int nrec;                  /* Number of records. */
+  };
+
+/* Holds information on parsing the data file. */
+static struct data_list_pgm dls;
+
+/* Pointer to a pointer to where the first dls_var_spec should go. */
+static struct dls_var_spec **first;
+
+/* Last dls_var_spec in the chain.  Used for building the linked-list. */
+static struct dls_var_spec *next;
+
+static int parse_fixed (void);
+static int parse_free (void);
+static void dump_fixed_table (void);
+static void dump_free_table (void);
+static void destroy_dls (struct trns_header *);
+static int read_one_case (struct trns_header *, struct ccase *);
+
+/* Message title for REPEATING DATA. */
+#define RPD_ERR "REPEATING DATA: "
+
+int
+cmd_data_list (void)
+{
+  /* 0=print no table, 1=print table.  (TABLE subcommand.)  */
+  int table = -1;
+
+  lex_match_id ("DATA");
+  lex_match_id ("LIST");
+
+  if (vfm_source != &input_program_source
+      && vfm_source != &file_type_source)
+    discard_variables ();
+
+  dls.handle = default_handle;
+  dls.type = -1;
+  dls.end = NULL;
+  dls.eof = 0;
+  dls.nrec = 0;
+  dls.spec = NULL;
+  next = NULL;
+  first = &dls.spec;
+
+  while (token != '/')
+    {
+      if (lex_match_id ("FILE"))
+       {
+         lex_match ('=');
+         dls.handle = fh_parse_file_handle ();
+         if (!dls.handle)
+           return CMD_FAILURE;
+         if (vfm_source == &file_type_source && dls.handle != default_handle)
+           {
+             msg (SE, _("DATA LIST may not use a different file from "
+                        "that specified on its surrounding FILE TYPE."));
+             return CMD_FAILURE;
+           }
+       }
+      else if (lex_match_id ("RECORDS"))
+       {
+         lex_match ('=');
+         lex_match ('(');
+         if (!lex_force_int ())
+           return CMD_FAILURE;
+         dls.nrec = lex_integer ();
+         lex_get ();
+         lex_match (')');
+       }
+      else if (lex_match_id ("END"))
+       {
+         if (dls.end)
+           {
+             msg (SE, _("The END subcommand may only be specified once."));
+             return CMD_FAILURE;
+           }
+         
+         lex_match ('=');
+         if (!lex_force_id ())
+           return CMD_FAILURE;
+         dls.end = find_variable (tokid);
+         if (!dls.end)
+           dls.end = force_create_variable (&default_dict, tokid, NUMERIC, 0);
+         lex_get ();
+       }
+      else if (token == T_ID)
+       {
+         /* Must match DLS_* constants. */
+         static const char *id[] = {"FIXED", "FREE", "LIST", "NOTABLE",
+                                    "TABLE", NULL};
+         const char **p;
+         int index;
+
+         for (p = id; *p; p++)
+           if (lex_id_match (*p, tokid))
+             break;
+         if (*p == NULL)
+           {
+             lex_error (NULL);
+             return CMD_FAILURE;
+           }
+         
+         lex_get ();
+
+         index = p - id;
+         if (index < 3)
+           {
+             if (dls.type != -1)
+               {
+                 msg (SE, _("Only one of FIXED, FREE, or LIST may "
+                           "be specified."));
+                 return CMD_FAILURE;
+               }
+             
+             dls.type = index;
+           }
+         else
+           table = index - 3;
+       }
+      else
+       {
+         lex_error (NULL);
+         return CMD_FAILURE;
+       }
+    }
+
+  default_handle = dls.handle;
+
+  if (dls.type == -1)
+    dls.type = DLS_FIXED;
+
+  if (table == -1)
+    {
+      if (dls.type == DLS_FREE)
+       table = 0;
+      else
+       table = 1;
+    }
+
+  if (dls.type == DLS_FIXED)
+    {
+      if (!parse_fixed ())
+       return CMD_FAILURE;
+      if (table)
+       dump_fixed_table ();
+    }
+  else
+    {
+      if (!parse_free ())
+       return CMD_FAILURE;
+      if (table)
+       dump_free_table ();
+    }
+
+  if (vfm_source != NULL)
+    {
+      struct data_list_pgm *new_pgm;
+
+      dls.h.proc = read_one_case;
+      dls.h.free = destroy_dls;
+
+      new_pgm = xmalloc (sizeof *new_pgm);
+      memcpy (new_pgm, &dls, sizeof *new_pgm);
+      add_transformation ((struct trns_header *) new_pgm);
+    }
+  else
+    vfm_source = &data_list_source;
+
+  return CMD_SUCCESS;
+}
+
+static void
+append_var_spec (struct dls_var_spec *spec)
+{
+  if (next == 0)
+    *first = next = xmalloc (sizeof *spec);
+  else
+    next = next->next = xmalloc (sizeof *spec);
+
+#if __CHECKER__
+  spec->type = ROUND_UP (spec->type, 8);
+#endif
+
+  memcpy (next, spec, sizeof *spec);
+  next->next = NULL;
+}
+\f
+/* Fixed-format parsing. */
+
+/* Used for chaining together fortran-like format specifiers. */
+struct fmt_list
+  {
+    struct fmt_list *next;
+    int count;
+    struct fmt_spec f;
+    struct fmt_list *down;
+  };
+
+/* Used as "local" variables among the fixed-format parsing funcs.  If
+   it were guaranteed that PSPP were going to be compiled by gcc,
+   I'd make all these functions a single set of nested functions. */
+static struct
+  {
+    char **name;               /* Variable names. */
+    int nname;                 /* Number of names. */
+    int cname;                 /* dump_fmt_list: index of next name to use. */
+
+    int recno;                 /* Index of current record. */
+    int sc;                    /* 1-based column number of starting column for
+                                  next field to output. */
+
+    struct dls_var_spec spec;  /* Next specification to output. */
+    int fc, lc;                        /* First, last column in set of fields specified
+                                  together. */
+
+    int level;                 /* Nesting level in fixed_parse_fortran(). */
+  }
+fx;
+
+static int fixed_parse_compatible (void);
+static struct fmt_list *fixed_parse_fortran (void);
+
+static int
+parse_fixed (void)
+{
+  int i;
+
+  fx.recno = 0;
+  fx.sc = 1;
+
+  while (token != '.')
+    {
+      while (lex_match ('/'))
+       {
+         fx.recno++;
+         if (lex_integer_p ())
+           {
+             if (lex_integer () < fx.recno)
+               {
+                 msg (SE, _("The record number specified, %ld, is "
+                            "before the previous record, %d.  Data "
+                            "fields must be listed in order of "
+                            "increasing record number."),
+                      lex_integer (), fx.recno - 1);
+                 return 0;
+               }
+             
+             fx.recno = lex_integer ();
+             lex_get ();
+           }
+         fx.sc = 1;
+       }
+      fx.spec.rec = fx.recno;
+
+      if (!parse_DATA_LIST_vars (&fx.name, &fx.nname, PV_NONE))
+       return 0;
+
+      if (token == T_NUM)
+       {
+         if (!fixed_parse_compatible ())
+           goto fail;
+       }
+      else if (token == '(')
+       {
+         fx.level = 0;
+         fx.cname = 0;
+         if (!fixed_parse_fortran ())
+           goto fail;
+       }
+      else
+       {
+         msg (SE, _("SPSS-like or FORTRAN-like format "
+              "specification expected after variable names."));
+         goto fail;
+       }
+
+      for (i = 0; i < fx.nname; i++)
+       free (fx.name[i]);
+      free (fx.name);
+    }
+  if (dls.nrec && next->rec > dls.nrec)
+    {
+      msg (SE, _("Variables are specified on records that "
+                "should not exist according to RECORDS subcommand."));
+      return 0;
+    }
+  else if (!dls.nrec)
+    dls.nrec = next->rec;
+  if (token != '.')
+    {
+      lex_error (_("expecting end of command"));
+      return 0;
+    }
+  return 1;
+
+fail:
+  for (i = 0; i < fx.nname; i++)
+    free (fx.name[i]);
+  free (fx.name);
+  return 0;
+}
+
+static int
+fixed_parse_compatible (void)
+{
+  int dividend;
+  int i;
+
+  if (!lex_force_int ())
+    return 0;
+  
+  fx.fc = lex_integer ();
+  if (fx.fc < 1)
+    {
+      msg (SE, _("Column positions for fields must be positive."));
+      return 0;
+    }
+  lex_get ();
+
+  lex_negative_to_dash ();
+  if (lex_match ('-'))
+    {
+      if (!lex_force_int ())
+       return 0;
+      fx.lc = lex_integer ();
+      if (fx.lc < 1)
+       {
+         msg (SE, _("Column positions for fields must be positive."));
+         return 0;
+       }
+      else if (fx.lc < fx.fc)
+       {
+         msg (SE, _("The ending column for a field must be "
+                    "greater than the starting column."));
+         return 0;
+       }
+      
+      lex_get ();
+    }
+  else
+    fx.lc = fx.fc;
+
+  fx.spec.input.w = fx.lc - fx.fc + 1;
+  if (lex_match ('('))
+    {
+      struct fmt_desc *fdp;
+
+      if (token == T_ID)
+       {
+         const char *cp;
+
+         fx.spec.input.type = parse_format_specifier_name (&cp, 0);
+         if (fx.spec.input.type == -1)
+           return 0;
+         if (*cp)
+           {
+             msg (SE, _("A format specifier on this line "
+                        "has extra characters on the end."));
+             return 0;
+           }
+         
+         lex_get ();
+         lex_match (',');
+       }
+      else
+       fx.spec.input.type = FMT_F;
+
+      if (lex_integer_p ())
+       {
+         if (lex_integer () < 1)
+           {
+             msg (SE, _("The value for number of decimal places "
+                        "must be at least 1."));
+             return 0;
+           }
+         
+         fx.spec.input.d = lex_integer ();
+         lex_get ();
+       }
+      else
+       fx.spec.input.d = 0;
+
+      fdp = &formats[fx.spec.input.type];
+      if (fdp->n_args < 2 && fx.spec.input.d)
+       {
+         msg (SE, _("Input format %s doesn't accept decimal places."),
+              fdp->name);
+         return 0;
+       }
+      
+      if (fx.spec.input.d > 16)
+       fx.spec.input.d = 16;
+
+      if (!lex_force_match (')'))
+       return 0;
+    }
+  else
+    {
+      fx.spec.input.type = FMT_F;
+      fx.spec.input.d = 0;
+    }
+
+  fx.sc = fx.lc + 1;
+
+  if ((fx.lc - fx.fc + 1) % fx.nname)
+    {
+      msg (SE, _("The %d columns %d-%d "
+                "can't be evenly divided into %d fields."),
+          fx.lc - fx.fc + 1, fx.fc, fx.lc, fx.nname);
+      return 0;
+    }
+
+  dividend = (fx.lc - fx.fc + 1) / fx.nname;
+  fx.spec.input.w = dividend;
+  if (!check_input_specifier (&fx.spec.input))
+    return 0;
+
+  for (i = 0; i < fx.nname; i++)
+    {
+      int type;
+      struct variable *v;
+
+      if (fx.spec.input.type == FMT_A || fx.spec.input.type == FMT_AHEX)
+       type = ALPHA;
+      else
+       type = NUMERIC;
+
+      v = create_variable (&default_dict, fx.name[i], type, dividend);
+      if (v)
+       {
+         convert_fmt_ItoO (&fx.spec.input, &v->print);
+         v->write = v->print;
+       }
+      else
+       {
+         v = find_variable (fx.name[i]);
+         assert (v);
+         if (!vfm_source)
+           {
+             msg (SE, _("%s is a duplicate variable name."), fx.name[i]);
+             return 0;
+           }
+         if (type != v->type)
+           {
+             msg (SE, _("There is already a variable %s of a "
+                        "different type."),
+                  fx.name[i]);
+             return 0;
+           }
+         if (type == ALPHA && dividend != v->width)
+           {
+             msg (SE, _("There is already a string variable %s of a "
+                        "different width."), fx.name[i]);
+             return 0;
+           }
+       }
+
+      fx.spec.v = v;
+      fx.spec.fc = fx.fc + dividend * i;
+      fx.spec.lc = fx.spec.fc + dividend - 1;
+      fx.spec.fv = v->fv;
+      fx.spec.type = v->type == NUMERIC ? 0 : v->width;
+      append_var_spec (&fx.spec);
+    }
+  return 1;
+}
+
+/* Destroy a format list and, optionally, all its sublists. */
+static void
+destroy_fmt_list (struct fmt_list *f, int recurse)
+{
+  struct fmt_list *next;
+
+  for (; f; f = next)
+    {
+      next = f->next;
+      if (recurse && f->f.type == FMT_DESCEND)
+       destroy_fmt_list (f->down, 1);
+      free (f);
+    }
+}
+
+/* Takes a hierarchically structured fmt_list F as constructed by
+   fixed_parse_fortran(), and flattens it into a linear list of
+   dls_var_spec's. */
+static int
+dump_fmt_list (struct fmt_list *f)
+{
+  int i;
+
+  for (; f; f = f->next)
+    if (f->f.type == FMT_X)
+      fx.sc += f->count;
+    else if (f->f.type == FMT_T)
+      fx.sc = f->f.w;
+    else if (f->f.type == FMT_NEWREC)
+      {
+       fx.recno += f->count;
+       fx.sc = 1;
+      }
+    else
+      for (i = 0; i < f->count; i++)
+       if (f->f.type == FMT_DESCEND)
+         {
+           if (!dump_fmt_list (f->down))
+             return 0;
+         }
+       else
+         {
+           int type;
+           struct variable *v;
+
+           type = (formats[f->f.type].cat & FCAT_STRING) ? ALPHA : NUMERIC;
+           if (fx.cname >= fx.nname)
+             {
+               msg (SE, _("The number of format "
+                          "specifications exceeds the number of "
+                          "variable names given."));
+               return 0;
+             }
+           
+           fx.spec.v = v = create_variable (&default_dict,
+                                            fx.name[fx.cname++],
+                                            type, f->f.w);
+           if (!v)
+             {
+               msg (SE, _("%s is a duplicate variable name."), fx.name[i]);
+               return 0;
+             }
+           
+           fx.spec.input = f->f;
+           convert_fmt_ItoO (&fx.spec.input, &v->print);
+           v->write = v->print;
+
+           fx.spec.rec = fx.recno;
+           fx.spec.fc = fx.sc;
+           fx.spec.lc = fx.sc + f->f.w - 1;
+           fx.spec.fv = v->fv;
+           fx.spec.type = v->type == NUMERIC ? 0 : v->width;
+           append_var_spec (&fx.spec);
+
+           fx.sc += f->f.w;
+         }
+  return 1;
+}
+
+/* Calls itself recursively to parse nested levels of parentheses.
+   Returns to its original caller: NULL, to indicate error; non-NULL,
+   but nothing useful, to indicate success (it returns a free()'d
+   block). */
+static struct fmt_list *
+fixed_parse_fortran (void)
+{
+  struct fmt_list *head;
+  struct fmt_list *fl = NULL;
+
+  lex_get ();                  /* Skip opening parenthesis. */
+  while (token != ')')
+    {
+      if (fl)
+       fl = fl->next = xmalloc (sizeof *fl);
+      else
+       head = fl = xmalloc (sizeof *fl);
+
+      if (lex_integer_p ())
+       {
+         fl->count = lex_integer ();
+         lex_get ();
+       }
+      else
+       fl->count = 1;
+
+      if (token == '(')
+       {
+         fl->f.type = FMT_DESCEND;
+         fx.level++;
+         fl->down = fixed_parse_fortran ();
+         fx.level--;
+         if (!fl->down)
+           goto fail;
+       }
+      else if (lex_match ('/'))
+       fl->f.type = FMT_NEWREC;
+      else if (!parse_format_specifier (&fl->f, 1)
+              || !check_input_specifier (&fl->f))
+       goto fail;
+
+      lex_match (',');
+    }
+  fl->next = NULL;
+  lex_get ();
+
+  if (fx.level)
+    return head;
+
+  fl->next = NULL;
+  dump_fmt_list (head);
+  if (fx.cname < fx.nname)
+    {
+      msg (SE, _("There aren't enough format specifications "
+          "to match the number of variable names given."));
+      goto fail;
+    }
+  destroy_fmt_list (head, 1);
+  return head;
+
+fail:
+  fl->next = NULL;
+  destroy_fmt_list (head, 0);
+
+  return NULL;
+}
+
+/* Displays a table giving information on fixed-format variable
+   parsing on DATA LIST. */
+/* FIXME: The `Columns' column should be divided into three columns,
+   one for the starting column, one for the dash, one for the ending
+   column; then right-justify the starting column and left-justify the
+   ending column. */
+static void
+dump_fixed_table (void)
+{
+  struct dls_var_spec *spec;
+  struct tab_table *t;
+  char *buf;
+  const char *filename;
+  int i;
+
+  for (i = 0, spec = *first; spec; spec = spec->next)
+    i++;
+  t = tab_create (4, i + 1, 0);
+  tab_columns (t, TAB_COL_DOWN, 1);
+  tab_headers (t, 0, 0, 1, 0);
+  tab_text (t, 0, 0, TAB_CENTER | TAT_TITLE, _("Variable"));
+  tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("Record"));
+  tab_text (t, 2, 0, TAB_CENTER | TAT_TITLE, _("Columns"));
+  tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("Format"));
+  tab_box (t, TAL_1, TAL_1, TAL_0, TAL_1, 0, 0, 3, i);
+  tab_hline (t, TAL_2, 0, 3, 1);
+  tab_dim (t, tab_natural_dimensions);
+
+  for (i = 1, spec = *first; spec; spec = spec->next, i++)
+    {
+      tab_text (t, 0, i, TAB_LEFT, spec->v->name);
+      tab_text (t, 1, i, TAT_PRINTF, "%d", spec->rec);
+      tab_text (t, 2, i, TAT_PRINTF, "%3d-%3d",
+                   spec->fc, spec->lc);
+      tab_text (t, 3, i, TAB_LEFT | TAT_FIX,
+                   fmt_to_string (&spec->input));
+    }
+
+  if (*first == dls.spec)
+    {
+      filename = fh_handle_name (dls.handle);
+      if (filename == NULL)
+       filename = "";
+      buf = local_alloc (strlen (filename) + INT_DIGITS + 80);
+      sprintf (buf, (dls.handle != inline_file
+                    ? _("Reading %d record%s from file %s.")
+                    : _("Reading %d record%s from the command file.")),
+              dls.nrec, dls.nrec != 1 ? "s" : "", filename);
+    }
+  else
+    {
+      buf = local_alloc (strlen (_("Occurrence data specifications.")) + 1);
+      strcpy (buf, _("Occurrence data specifications."));
+    }
+  
+  tab_title (t, 0, buf);
+  tab_submit (t);
+  fh_handle_name (NULL);
+  local_free (buf);
+}
+\f
+/* Free-format parsing. */
+
+static int
+parse_free (void)
+{
+  struct dls_var_spec spec;
+  struct fmt_spec in, out;
+  char **name;
+  int nname;
+  int i;
+  int type;
+
+#if __CHECKER__
+  memset (&spec, 0, sizeof spec);
+#endif
+  lex_get ();
+  while (token != '.')
+    {
+      if (!parse_DATA_LIST_vars (&name, &nname, PV_NONE))
+       return 0;
+      if (lex_match ('('))
+       {
+         if (!parse_format_specifier (&in, 0) || !check_input_specifier (&in))
+           goto fail;
+         if (!lex_force_match (')'))
+           goto fail;
+         convert_fmt_ItoO (&in, &out);
+       }
+      else
+       {
+         lex_match ('*');
+         in.type = FMT_F;
+         in.w = 8;
+         in.d = 0;
+         out = set_format;
+       }
+
+      spec.input = in;
+      if (in.type == FMT_A || in.type == FMT_AHEX)
+       type = ALPHA;
+      else
+       type = NUMERIC;
+      for (i = 0; i < nname; i++)
+       {
+         struct variable *v;
+
+         spec.v = v = create_variable (&default_dict, name[i], type, in.w);
+         if (!v)
+           {
+             msg (SE, _("%s is a duplicate variable name."), name[i]);
+             return 0;
+           }
+         
+         v->print = v->write = out;
+
+         strcpy (spec.name, name[i]);
+         spec.fv = v->fv;
+         spec.type = type == NUMERIC ? 0 : v->width;
+         append_var_spec (&spec);
+       }
+      for (i = 0; i < nname; i++)
+       free (name[i]);
+      free (name);
+    }
+
+  if (token != '.')
+    lex_error (_("expecting end of command"));
+  return 1;
+
+fail:
+  for (i = 0; i < nname; i++)
+    free (name[i]);
+  free (name);
+  return 0;
+}
+
+/* Displays a table giving information on free-format variable parsing
+   on DATA LIST. */
+static void
+dump_free_table (void)
+{
+  struct tab_table *t;
+  int i;
+  
+  {
+    struct dls_var_spec *spec;
+    for (i = 0, spec = dls.spec; spec; spec = spec->next)
+      i++;
+  }
+  
+  t = tab_create (2, i + 1, 0);
+  tab_columns (t, TAB_COL_DOWN, 1);
+  tab_headers (t, 0, 0, 1, 0);
+  tab_text (t, 0, 0, TAB_CENTER | TAT_TITLE, _("Variable"));
+  tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("Format"));
+  tab_box (t, TAL_1, TAL_1, TAL_0, TAL_1, 0, 0, 1, i);
+  tab_hline (t, TAL_2, 0, 1, 1);
+  tab_dim (t, tab_natural_dimensions);
+  
+  {
+    struct dls_var_spec *spec;
+    
+    for (i = 1, spec = dls.spec; spec; spec = spec->next, i++)
+      {
+       tab_text (t, 0, i, TAB_LEFT, spec->v->name);
+       tab_text (t, 1, i, TAB_LEFT | TAT_FIX, fmt_to_string (&spec->input));
+      }
+  }
+  
+  {
+    const char *filename;
+
+    filename = fh_handle_name (dls.handle);
+    if (filename == NULL)
+      filename = "";
+    tab_title (t, 1,
+              (dls.handle != inline_file
+               ? _("Reading free-form data from file %s.")
+               : _("Reading free-form data from the command file.")),
+              filename);
+  }
+  
+  tab_submit (t);
+  fh_handle_name (NULL);
+}
+\f
+/* Input procedure. */ 
+
+/* Pointer to relevant parsing data.  Static just to avoid passing it
+   around so much. */
+static struct data_list_pgm *dlsp;
+
+/* Extracts a field from the current position in the current record.
+   Fields can be unquoted or quoted with single- or double-quote
+   characters.  *RET_LEN is set to the field length, *RET_CP is set to
+   the field itself.  After parsing the field, sets the current
+   position in the record to just past the field.  Returns 0 on
+   failure or a 1-based column number indicating the beginning of the
+   field on success. */
+static int
+cut_field (char **ret_cp, int *ret_len)
+{
+  char *cp, *ep;
+  int len;
+
+  cp = dfm_get_record (dlsp->handle, &len);
+  if (!cp)
+    return 0;
+
+  ep = cp + len;
+
+  /* Skip leading whitespace and commas. */
+  while ((isspace ((unsigned char) *cp) || *cp == ',') && cp < ep)
+    cp++;
+  if (cp >= ep)
+    return 0;
+
+  /* Three types of fields: quoted with ', quoted with ", unquoted. */
+  if (*cp == '\'' || *cp == '"')
+    {
+      int quote = *cp;
+
+      *ret_cp = ++cp;
+      while (cp < ep && *cp != quote)
+       cp++;
+      *ret_len = cp - *ret_cp;
+      if (cp < ep)
+       cp++;
+      else
+       msg (SW, _("Scope of string exceeds line."));
+    }
+  else
+    {
+      *ret_cp = cp;
+      while (cp < ep && !isspace ((unsigned char) *cp) && *cp != ',')
+       cp++;
+      *ret_len = cp - *ret_cp;
+    }
+
+  {
+    int beginning_column;
+    
+    dfm_set_record (dlsp->handle, *ret_cp);
+    beginning_column = dfm_get_cur_col (dlsp->handle) + 1;
+    
+    dfm_set_record (dlsp->handle, cp);
+    
+    return beginning_column;
+  }
+}
+
+static int read_from_data_list_fixed (void);
+static int read_from_data_list_free (void);
+static int read_from_data_list_list (void);
+static int do_reading (int flag);
+
+/* FLAG==0: reads any number of cases into temp_case and calls
+   write_case() for each one, returns garbage.  FLAG!=0: reads one
+   case into temp_case and returns -2 on eof, -1 otherwise.
+   Uses dlsp as the relevant parsing description. */
+static int
+do_reading (int flag)
+{
+  int (*func) (void);
+
+  int code;
+
+  dfm_push (dlsp->handle);
+
+  switch (dlsp->type)
+    {
+    case DLS_FIXED:
+      func = read_from_data_list_fixed;
+      break;
+    case DLS_FREE:
+      func = read_from_data_list_free;
+      break;
+    case DLS_LIST:
+      func = read_from_data_list_list;
+      break;
+    default:
+      assert (0);
+    }
+  if (flag)
+    {
+      code = func ();
+      if (code == -2)
+       {
+         if (dlsp->eof == 1)
+           {
+             msg (SE, _("Attempt to read past end of file."));
+             err_failure ();
+             return -2;
+           }
+         dlsp->eof = 1;
+       }
+      else
+       dlsp->eof = 0;
+
+      if (dlsp->end != NULL)
+       {
+         if (code == -2)
+           {
+             printf ("end of file, setting %s to 1\n", dlsp->end->name);
+             temp_case->data[dlsp->end->fv].f = 1.0;
+             code = -1;
+           }
+         else
+           {
+             printf ("not end of file, setting %s to 0\n", dlsp->end->name);
+             temp_case->data[dlsp->end->fv].f = 0.0;
+           }
+       }
+    }
+  else
+    {
+      while (func () != -2)
+       if (!write_case ())
+         {
+           debug_printf ((_("abort in write_case()\n")));
+           break;
+         }
+      fh_close_handle (dlsp->handle);
+#if __CHECKER__
+      code = 0;                        /* prevent error at `return code;' */
+#endif
+    }
+  dfm_pop (dlsp->handle);
+
+  return code;
+}
+
+/* Reads a case from the data file and parses it according to
+   fixed-format syntax rules. */
+static int
+read_from_data_list_fixed (void)
+{
+  struct dls_var_spec *var_spec = dlsp->spec;
+  int i;
+
+  if (!dfm_get_record (dlsp->handle, NULL))
+    return -2;
+  for (i = 1; i <= dlsp->nrec; i++)
+    {
+      int len;
+      char *line = dfm_get_record (dlsp->handle, &len);
+      
+      if (!line)
+       {
+         /* Note that this can't occur on the first record. */
+         msg (SW, _("Partial case of %d of %d records discarded."),
+              i - 1, dlsp->nrec);
+         return -2;
+       }
+
+      for (; var_spec && i == var_spec->rec; var_spec = var_spec->next)
+       {
+         struct data_in di;
+
+         data_in_finite_line (&di, line, len, var_spec->fc, var_spec->lc);
+         di.v = &temp_case->data[var_spec->fv];
+         di.flags = 0;
+         di.f1 = var_spec->fc;
+         di.format = var_spec->input;
+
+         data_in (&di);
+       }
+
+      dfm_fwd_record (dlsp->handle);
+    }
+
+  return -1;
+}
+
+/* Reads a case from the data file and parses it according to
+   free-format syntax rules. */
+static int
+read_from_data_list_free (void)
+{
+  struct dls_var_spec *var_spec;
+  char *field;
+  int len;
+
+  for (var_spec = dlsp->spec; var_spec; var_spec = var_spec->next)
+    {
+      int column;
+      
+      /* Cut out a field and read in a new record if necessary. */
+      for (;;)
+       {
+         column = cut_field (&field, &len);
+         if (column != 0)
+           break;
+
+         if (dfm_get_record (dlsp->handle, NULL))
+           dfm_fwd_record (dlsp->handle);
+         if (!dfm_get_record (dlsp->handle, NULL))
+           {
+             if (var_spec != dlsp->spec)
+               msg (SW, _("Partial case discarded.  The first variable "
+                    "missing was %s."), var_spec->name);
+             return -2;
+           }
+       }
+      
+      {
+       struct data_in di;
+
+       di.s = field;
+       di.e = field + len;
+       di.v = &temp_case->data[var_spec->fv];
+       di.flags = 0;
+       di.f1 = column;
+       di.format = var_spec->input;
+       data_in (&di);
+      }
+    }
+  return -1;
+}
+
+/* Reads a case from the data file and parses it according to
+   list-format syntax rules. */
+static int
+read_from_data_list_list (void)
+{
+  struct dls_var_spec *var_spec;
+  char *field;
+  int len;
+
+  if (!dfm_get_record (dlsp->handle, NULL))
+    return -2;
+
+  for (var_spec = dlsp->spec; var_spec; var_spec = var_spec->next)
+    {
+      /* Cut out a field and check for end-of-line. */
+      int column = cut_field (&field, &len);
+      
+      if (column == 0)
+       {
+         if (set_undefined)
+           msg (SW, _("Missing value(s) for all variables from %s onward.  "
+                "These will be filled with the system-missing value "
+                "or blanks, as appropriate."),
+                var_spec->name);
+         for (; var_spec; var_spec = var_spec->next)
+           if (!var_spec->type)
+             temp_case->data[var_spec->fv].f = SYSMIS;
+           else
+             memset (temp_case->data[var_spec->fv].s, ' ', var_spec->type);
+         break;
+       }
+      
+      {
+       struct data_in di;
+
+       di.s = field;
+       di.e = field + len;
+       di.v = &temp_case->data[var_spec->fv];
+       di.flags = 0;
+       di.f1 = column;
+       di.format = var_spec->input;
+       data_in (&di);
+      }
+    }
+
+  dfm_fwd_record (dlsp->handle);
+  return -1;
+}
+
+/* Destroys DATA LIST transformation or input program PGM. */
+static void
+destroy_dls (struct trns_header *pgm)
+{
+  struct data_list_pgm *dls = (struct data_list_pgm *) pgm;
+  struct dls_var_spec *iter, *next;
+
+  iter = dls->spec;
+  while (iter)
+    {
+      next = iter->next;
+      free (iter);
+      iter = next;
+    }
+  fh_close_handle (dls->handle);
+}
+
+/* Note that since this is exclusively an input program, C is
+   guaranteed to be temp_case. */
+static int
+read_one_case (struct trns_header *t, struct ccase *c unused)
+{
+  dlsp = (struct data_list_pgm *) t;
+  return do_reading (1);
+}
+\f
+/* Reads all the records from the data file and passes them to
+   write_case(). */
+static void
+data_list_source_read (void)
+{
+  dlsp = &dls;
+  do_reading (0);
+}
+
+/* Destroys the source's internal data. */
+static void
+data_list_source_destroy_source (void)
+{
+  destroy_dls ((struct trns_header *) & dls);
+}
+
+struct case_stream data_list_source = 
+  {
+    NULL,
+    data_list_source_read,
+    NULL,
+    NULL,
+    data_list_source_destroy_source,
+    NULL,
+    "DATA LIST",
+  };
+\f
+/* REPEATING DATA. */
+
+/* Represents a number or a variable. */
+struct rpd_num_or_var
+  {
+    int num;                   /* Value, or 0. */
+    struct variable *var;      /* Variable, if number==0. */
+  };
+    
+/* REPEATING DATA private data structure. */
+struct repeating_data_trns
+  {
+    struct trns_header h;
+    struct dls_var_spec *spec; /* Variable parsing specifications. */
+    struct file_handle *handle;        /* Input file, never NULL. */
+    /* Do not reorder preceding fields. */
+
+    struct rpd_num_or_var starts_beg;  /* STARTS=, before the dash. */
+    struct rpd_num_or_var starts_end;  /* STARTS=, after the dash. */
+    struct rpd_num_or_var occurs;      /* OCCURS= subcommand. */
+    struct rpd_num_or_var length;      /* LENGTH= subcommand. */
+    struct rpd_num_or_var cont_beg;    /* CONTINUED=, before the dash. */
+    struct rpd_num_or_var cont_end;    /* CONTINUED=, after the dash. */
+    int id_beg, id_end;                        /* ID subcommand, beginning & end columns. */
+    struct variable *id_var;           /* ID subcommand, DATA LIST variable. */
+    struct fmt_spec id_spec;           /* ID subcommand, input format spec. */
+  };
+
+/* Information about the transformation being parsed. */
+static struct repeating_data_trns rpd;
+
+static int read_one_set_of_repetitions (struct trns_header *, struct ccase *);
+static int parse_num_or_var (struct rpd_num_or_var *, const char *);
+static int parse_repeating_data (void);
+static void find_variable_input_spec (struct variable *v,
+                                     struct fmt_spec *spec);
+
+/* Parses the REPEATING DATA command. */
+int
+cmd_repeating_data (void)
+{
+  /* 0=print no table, 1=print table.  (TABLE subcommand.)  */
+  int table = 1;
+
+  /* Bits are set when a particular subcommand has been seen. */
+  unsigned seen = 0;
+  
+  lex_match_id ("REPEATING");
+  lex_match_id ("DATA");
+
+  assert (vfm_source == &input_program_source
+         || vfm_source == &file_type_source);
+  
+  rpd.handle = default_handle;
+  rpd.starts_beg.num = 0;
+  rpd.starts_beg.var = NULL;
+  rpd.starts_end = rpd.occurs = rpd.length = rpd.cont_beg
+    = rpd.cont_end = rpd.starts_beg;
+  rpd.id_beg = rpd.id_end = 0;
+  rpd.id_var = NULL;
+  rpd.spec = NULL;
+  first = &rpd.spec;
+  next = NULL;
+
+  lex_match ('/');
+  
+  for (;;)
+    {
+      if (lex_match_id ("FILE"))
+       {
+         lex_match ('=');
+         rpd.handle = fh_parse_file_handle ();
+         if (!rpd.handle)
+           return CMD_FAILURE;
+         if (rpd.handle != default_handle)
+           {
+             msg (SE, _("REPEATING DATA must use the same file as its "
+                        "corresponding DATA LIST or FILE TYPE."));
+             return CMD_FAILURE;
+           }
+       }
+      else if (lex_match_id ("STARTS"))
+       {
+         lex_match ('=');
+         if (seen & 1)
+           {
+             msg (SE, _("STARTS subcommand given multiple times."));
+             return CMD_FAILURE;
+           }
+         seen |= 1;
+
+         if (!parse_num_or_var (&rpd.starts_beg, "STARTS beginning column"))
+           return CMD_FAILURE;
+
+         lex_negative_to_dash ();
+         if (lex_match ('-'))
+           {
+             if (!parse_num_or_var (&rpd.starts_end, "STARTS ending column"))
+               return CMD_FAILURE;
+           } else {
+             /* Otherwise, rpd.starts_end is left uninitialized.
+                This is okay.  We will initialize it later from the
+                record length of the file.  We can't do this now
+                because we can't be sure that the user has specified
+                the file handle yet. */
+           }
+
+         if (rpd.starts_beg.num != 0 && rpd.starts_end.num != 0
+             && rpd.starts_beg.num > rpd.starts_end.num)
+           {
+             msg (SE, _("STARTS beginning column (%d) exceeds "
+                        "STARTS ending column (%d)."),
+                  rpd.starts_beg.num, rpd.starts_end.num);
+             return CMD_FAILURE;
+           }
+       }
+      else if (lex_match_id ("OCCURS"))
+       {
+         lex_match ('=');
+         if (seen & 2)
+           {
+             msg (SE, _("OCCURS subcommand given multiple times."));
+             return CMD_FAILURE;
+           }
+         seen |= 2;
+
+         if (!parse_num_or_var (&rpd.occurs, "OCCURS"))
+           return CMD_FAILURE;
+       }
+      else if (lex_match_id ("LENGTH"))
+       {
+         lex_match ('=');
+         if (seen & 4)
+           {
+             msg (SE, _("LENGTH subcommand given multiple times."));
+             return CMD_FAILURE;
+           }
+         seen |= 4;
+
+         if (!parse_num_or_var (&rpd.length, "LENGTH"))
+           return CMD_FAILURE;
+       }
+      else if (lex_match_id ("CONTINUED"))
+       {
+         lex_match ('=');
+         if (seen & 8)
+           {
+             msg (SE, _("CONTINUED subcommand given multiple times."));
+             return CMD_FAILURE;
+           }
+         seen |= 8;
+
+         if (!lex_match ('/'))
+           {
+             if (!parse_num_or_var (&rpd.cont_beg, "CONTINUED beginning column"))
+               return CMD_FAILURE;
+
+             lex_negative_to_dash ();
+             if (lex_match ('-')
+                 && !parse_num_or_var (&rpd.cont_end,
+                                       "CONTINUED ending column"))
+               return CMD_FAILURE;
+         
+             if (rpd.cont_beg.num != 0 && rpd.cont_end.num != 0
+                 && rpd.cont_beg.num > rpd.cont_end.num)
+               {
+                 msg (SE, _("CONTINUED beginning column (%d) exceeds "
+                            "CONTINUED ending column (%d)."),
+                      rpd.cont_beg.num, rpd.cont_end.num);
+                 return CMD_FAILURE;
+               }
+           }
+         else
+           rpd.cont_beg.num = 1;
+       }
+      else if (lex_match_id ("ID"))
+       {
+         lex_match ('=');
+         if (seen & 16)
+           {
+             msg (SE, _("ID subcommand given multiple times."));
+             return CMD_FAILURE;
+           }
+         seen |= 16;
+         
+         if (!lex_force_int ())
+           return CMD_FAILURE;
+         if (lex_integer () < 1)
+           {
+             msg (SE, _("ID beginning column (%ld) must be positive."),
+                  lex_integer ());
+             return CMD_FAILURE;
+           }
+         rpd.id_beg = lex_integer ();
+         
+         lex_get ();
+         lex_negative_to_dash ();
+         
+         if (lex_match ('-'))
+           {
+             if (!lex_force_int ())
+               return CMD_FAILURE;
+             if (lex_integer () < 1)
+               {
+                 msg (SE, _("ID ending column (%ld) must be positive."),
+                      lex_integer ());
+                 return CMD_FAILURE;
+               }
+             if (lex_integer () < rpd.id_end)
+               {
+                 msg (SE, _("ID ending column (%ld) cannot be less than "
+                            "ID beginning column (%d)."),
+                      lex_integer (), rpd.id_beg);
+                 return CMD_FAILURE;
+               }
+             
+             rpd.id_end = lex_integer ();
+             lex_get ();
+           }
+         else rpd.id_end = rpd.id_beg;
+
+         if (!lex_force_match ('='))
+           return CMD_FAILURE;
+         rpd.id_var = parse_variable ();
+         if (rpd.id_var == NULL)
+           return CMD_FAILURE;
+
+         find_variable_input_spec (rpd.id_var, &rpd.id_spec);
+       }
+      else if (lex_match_id ("TABLE"))
+       table = 1;
+      else if (lex_match_id ("NOTABLE"))
+       table = 0;
+      else if (lex_match_id ("DATA"))
+       break;
+      else
+       {
+         lex_error (NULL);
+         return CMD_FAILURE;
+       }
+
+      if (!lex_force_match ('/'))
+       return CMD_FAILURE;
+    }
+
+  /* Comes here when DATA specification encountered. */
+  if ((seen & (1 | 2)) != (1 | 2))
+    {
+      if ((seen & 1) == 0)
+       msg (SE, _("Missing required specification STARTS."));
+      if ((seen & 2) == 0)
+       msg (SE, _("Missing required specification OCCURS."));
+      return CMD_FAILURE;
+    }
+
+  /* Enforce ID restriction. */
+  if ((seen & 16) && !(seen & 8))
+    {
+      msg (SE, _("ID specified without CONTINUED."));
+      return CMD_FAILURE;
+    }
+
+  /* Calculate starts_end, cont_end if necessary. */
+  if (rpd.starts_end.num == 0 && rpd.starts_end.var == NULL)
+    rpd.starts_end.num = fh_record_width (rpd.handle);
+  if (rpd.cont_end.num == 0 && rpd.starts_end.var == NULL)
+    rpd.cont_end.num = fh_record_width (rpd.handle);
+      
+  /* Calculate length if possible. */
+  if ((seen & 4) == 0)
+    {
+      struct dls_var_spec *iter;
+      
+      for (iter = rpd.spec; iter; iter = iter->next)
+       {
+         if (iter->lc > rpd.length.num)
+           rpd.length.num = iter->lc;
+       }
+      assert (rpd.length.num != 0);
+    }
+  
+  lex_match ('=');
+  if (!parse_repeating_data ())
+    return CMD_FAILURE;
+
+  if (table)
+    dump_fixed_table ();
+
+  {
+    struct repeating_data_trns *new_trns;
+
+    rpd.h.proc = read_one_set_of_repetitions;
+    rpd.h.free = destroy_dls;
+
+    new_trns = xmalloc (sizeof *new_trns);
+    memcpy (new_trns, &rpd, sizeof *new_trns);
+    add_transformation ((struct trns_header *) new_trns);
+  }
+
+  return lex_end_of_command ();
+}
+
+/* Because of the way that DATA LIST is structured, it's not trivial
+   to determine what input format is associated with a given variable.
+   This function finds the input format specification for variable V
+   and puts it in SPEC. */
+static void 
+find_variable_input_spec (struct variable *v, struct fmt_spec *spec)
+{
+  int i;
+  
+  for (i = 0; i < n_trns; i++)
+    {
+      struct data_list_pgm *pgm = (struct data_list_pgm *) t_trns[i];
+      
+      if (pgm->h.proc == read_one_case)
+       {
+         struct dls_var_spec *iter;
+
+         for (iter = pgm->spec; iter; iter = iter->next)
+           if (iter->v == v)
+             {
+               *spec = iter->input;
+               return;
+             }
+       }
+    }
+  
+  assert (0);
+}
+
+/* Parses a number or a variable name from the syntax file and puts
+   the results in VALUE.  Ensures that the number is at least 1; else
+   emits an error based on MESSAGE.  Returns nonzero only if
+   successful. */
+static int
+parse_num_or_var (struct rpd_num_or_var *value, const char *message)
+{
+  if (token == T_ID)
+    {
+      value->num = 0;
+      value->var = parse_variable ();
+      if (value->var == NULL)
+       return 0;
+      if (value->var->type == ALPHA)
+       {
+         msg (SE, _("String variable not allowed here."));
+         return 0;
+       }
+    }
+  else if (lex_integer_p ())
+    {
+      value->num = lex_integer ();
+      
+      if (value->num < 1)
+       {
+         msg (SE, _("%s (%d) must be at least 1."), message, value->num);
+         return 0;
+       }
+      
+      lex_get ();
+    } else {
+      msg (SE, _("Variable or integer expected for %s."), message);
+      return 0;
+    }
+  return 1;
+}
+
+/* Parses data specifications for repeating data groups.  Taken from
+   parse_fixed().  Returns nonzero only if successful.  */
+static int
+parse_repeating_data (void)
+{
+  int i;
+
+  fx.recno = 0;
+  fx.sc = 1;
+
+  while (token != '.')
+    {
+      fx.spec.rec = fx.recno;
+
+      if (!parse_DATA_LIST_vars (&fx.name, &fx.nname, PV_NONE))
+       return 0;
+
+      if (token == T_NUM)
+       {
+         if (!fixed_parse_compatible ())
+           goto fail;
+       }
+      else if (token == '(')
+       {
+         fx.level = 0;
+         fx.cname = 0;
+         if (!fixed_parse_fortran ())
+           goto fail;
+       }
+      else
+       {
+         msg (SE, _("SPSS-like or FORTRAN-like format "
+              "specification expected after variable names."));
+         goto fail;
+       }
+
+      for (i = 0; i < fx.nname; i++)
+       free (fx.name[i]);
+      free (fx.name);
+    }
+  if (token != '.')
+    {
+      lex_error (_("expecting end of command"));
+      return 0;
+    }
+  
+  return 1;
+
+fail:
+  for (i = 0; i < fx.nname; i++)
+    free (fx.name[i]);
+  free (fx.name);
+  return 0;
+}
+
+/* Obtains the real value for rpd_num_or_var N in case C and returns
+   it.  The valid range is nonnegative numbers, but numbers outside
+   this range can be returned and should be handled by the caller as
+   invalid. */
+static int
+realize_value (struct rpd_num_or_var *n, struct ccase *c)
+{
+  if (n->num > 0)
+    return n->num;
+  
+  assert (n->num == 0);
+  if (n->var != NULL)
+    {
+      double v = c->data[n->var->fv].f;
+
+      if (v == SYSMIS || v <= INT_MIN || v >= INT_MAX)
+       return -1;
+      else
+       return v;
+    }
+  else
+    return 0;
+}
+
+/* Parses one record of repeated data and outputs corresponding cases.
+   Repeating data is present in line LINE having length LEN.
+   Repeating data begins in column BEG and continues through column
+   END inclusive (1-based columns); occurrences are offset OFS columns
+   from each other.  C is the case that will be filled in; T is the
+   REPEATING DATA transformation.  The record ID will be verified if
+   COMPARE_ID is nonzero; if it is zero, then the record ID is
+   initialized to the ID present in the case (assuming that ID
+   location was specified by the user).  Returns number of occurrences
+   parsed up to the specified maximum of MAX_OCCURS. */
+static int
+rpd_parse_record (int beg, int end, int ofs, struct ccase *c,
+                 struct repeating_data_trns *t,
+                 char *line, int len, int compare_id, int max_occurs)
+{
+  int occurrences;
+  int cur = beg;
+
+  /* Handle record ID values. */
+  if (t->id_beg != 0)
+    {
+      static union value comparator;
+      union value v;
+      
+      {
+       struct data_in di;
+
+       data_in_finite_line (&di, line, len, t->id_beg, t->id_end);
+       di.v = &v;
+       di.flags = 0;
+       di.f1 = t->id_beg;
+       di.format = t->id_spec;
+
+       if (!data_in (&di))
+         return 0;
+      }
+
+      if (compare_id == 0)
+       comparator = v;
+      else if ((t->id_var->type == NUMERIC && comparator.f != v.f)
+              || (t->id_var->type == ALPHA
+                  && strncmp (comparator.s, v.s, t->id_var->width)))
+       {
+         char comp_str [64];
+         char v_str [64];
+
+         if (!data_out (comp_str, &t->id_var->print, &comparator))
+           comp_str[0] = 0;
+         if (!data_out (v_str, &t->id_var->print, &v))
+           v_str[0] = 0;
+         
+         comp_str[t->id_var->print.w] = v_str[t->id_var->print.w] = 0;
+           
+         tmsg (SE, RPD_ERR, 
+               _("Mismatched case ID (%s).  Expected value was %s."),
+               v_str, comp_str);
+
+         return 0;
+       }
+    }
+
+  /* Iterate over the set of expected occurrences and record each of
+     them as a separate case.  FIXME: We need to execute any
+     transformations that follow the current one. */
+  {
+    int warned = 0;
+
+    for (occurrences = 0; occurrences < max_occurs; )
+      {
+       if (cur + ofs > end + 1)
+         break;
+       occurrences++;
+
+       {
+         struct dls_var_spec *var_spec = t->spec;
+       
+         for (; var_spec; var_spec = var_spec->next)
+           {
+             int fc = var_spec->fc - 1 + cur;
+             int lc = var_spec->lc - 1 + cur;
+
+             if (fc > len && !warned && var_spec->input.type != FMT_A)
+               {
+                 warned = 1;
+
+                 tmsg (SW, RPD_ERR,
+                       _("Variable %s startging in column %d extends "
+                         "beyond physical record length of %d."),
+                       var_spec->v->name, fc, len);
+               }
+             
+             {
+               struct data_in di;
+
+               data_in_finite_line (&di, line, len, fc, lc);
+               di.v = &c->data[var_spec->fv];
+               di.flags = 0;
+               di.f1 = fc + 1;
+               di.format = var_spec->input;
+
+               if (!data_in (&di))
+                 return 0;
+             }
+           }
+       }
+
+       cur += ofs;
+
+       if (!write_case ())
+         return 0;
+      }
+  }
+
+  return occurrences;
+}
+
+/* Analogous to read_one_case; reads one set of repetitions of the
+   elements in the REPEATING DATA structure.  Returns -1 on success,
+   -2 on end of file or on failure. */
+static int
+read_one_set_of_repetitions (struct trns_header *trns, struct ccase *c)
+{
+  dfm_push (dlsp->handle);
+  
+  {
+    struct repeating_data_trns *t = (struct repeating_data_trns *) trns;
+    
+    char *line;                /* Current record. */
+    int len;           /* Length of current record. */
+
+    int starts_beg;    /* Starting column. */
+    int starts_end;    /* Ending column. */
+    int occurs;                /* Number of repetitions. */
+    int length;                /* Length of each occurrence. */
+    int cont_beg;      /* Starting column for continuation lines. */
+    int cont_end;      /* Ending column for continuation lines. */
+
+    int occurs_left;   /* Number of occurrences remaining. */
+
+    int code;          /* Return value from rpd_parse_record(). */
+    
+    int skip_first_record = 0;
+    
+    /* Read the current record. */
+    dfm_bkwd_record (dlsp->handle, 1);
+    line = dfm_get_record (dlsp->handle, &len);
+    if (line == NULL)
+      return -2;
+    dfm_fwd_record (dlsp->handle);
+
+    /* Calculate occurs, length. */
+    occurs_left = occurs = realize_value (&t->occurs, c);
+    if (occurs <= 0)
+      {
+       tmsg (SE, RPD_ERR, _("Invalid value %d for OCCURS."), occurs);
+       return -3;
+      }
+    starts_beg = realize_value (&t->starts_beg, c);
+    if (starts_beg <= 0)
+      {
+       tmsg (SE, RPD_ERR, _("Beginning column for STARTS (%d) must be "
+                            "at least 1."),
+             starts_beg);
+       return -3;
+      }
+    starts_end = realize_value (&t->starts_end, c);
+    if (starts_end < starts_beg)
+      {
+       tmsg (SE, RPD_ERR, _("Ending column for STARTS (%d) is less than "
+                            "beginning column (%d)."),
+             starts_end, starts_beg);
+       skip_first_record = 1;
+      }
+    length = realize_value (&t->length, c);
+    if (length < 0)
+      {
+       tmsg (SE, RPD_ERR, _("Invalid value %d for LENGTH."), length);
+       length = 1;
+       occurs = occurs_left = 1;
+      }
+    cont_beg = realize_value (&t->cont_beg, c);
+    if (cont_beg < 0)
+      {
+       tmsg (SE, RPD_ERR, _("Beginning column for CONTINUED (%d) must be "
+                            "at least 1."),
+             cont_beg);
+       return -2;
+      }
+    cont_end = realize_value (&t->cont_end, c);
+    if (cont_end < cont_beg)
+      {
+       tmsg (SE, RPD_ERR, _("Ending column for CONTINUED (%d) is less than "
+                            "beginning column (%d)."),
+             cont_end, cont_beg);
+       return -2;
+      }
+
+    /* Parse the first record. */
+    if (!skip_first_record)
+      {
+       code = rpd_parse_record (starts_beg, starts_end, length, c, t, line,
+                                len, 0, occurs_left);
+       if (!code)
+         return -2;
+      }
+    else if (cont_beg == 0)
+      return -3;
+
+    /* Make sure, if some occurrences are left, that we have
+       continuation records. */
+    occurs_left -= code;
+    if (occurs_left != 0 && cont_beg == 0)
+      {
+       tmsg (SE, RPD_ERR,
+             _("Number of repetitions specified on OCCURS (%d) "
+               "exceed number of repetitions available in "
+               "space on STARTS (%d), and CONTINUED not specified."),
+             occurs, code);
+       return -2;
+      }
+
+    /* Go on to additional records. */
+    while (occurs_left != 0)
+      {
+       assert (occurs_left >= 0);
+
+       /* Read in another record. */
+       line = dfm_get_record (dlsp->handle, &len);
+       if (line == NULL)
+         {
+           tmsg (SE, RPD_ERR,
+                 _("Unexpected end of file with %d repetitions "
+                   "remaining out of %d."),
+                 occurs_left, occurs);
+           return -2;
+         }
+       dfm_fwd_record (dlsp->handle);
+
+       /* Parse this record. */
+       code = rpd_parse_record (cont_beg, cont_end, length, c, t, line,
+                                len, 1, occurs_left);
+       if (!code)
+         return -2;
+       occurs_left -= code;
+      }
+  }
+    
+  dfm_pop (dlsp->handle);
+
+  /* FIXME: This is a kluge until we've implemented multiplexing of
+     transformations. */
+  return -3;
+}
diff --git a/src/data-out.c b/src/data-out.c
new file mode 100644 (file)
index 0000000..24428c6
--- /dev/null
@@ -0,0 +1,1231 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+#include <float.h>
+#include <stdlib.h>
+#include <time.h>
+#include "approx.h"
+#include "error.h"
+#include "format.h"
+#include "julcal/julcal.h"
+#include "magic.h"
+#include "misc.h"
+#include "misc.h"
+#include "settings.h"
+#include "str.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* In older versions, numbers got their trailing zeros stripped.
+   Newer versions leave them on when there's room.  Comment this next
+   line out for retro styling. */
+#define NEW_STYLE 1
+\f
+/* Public functions. */
+
+typedef int convert_func (char *, const struct fmt_spec *,
+                         const union value *);
+
+static convert_func convert_F, convert_N, convert_E, convert_F_plus;
+static convert_func convert_Z, convert_A, convert_AHEX, convert_IB;
+static convert_func convert_P, convert_PIB, convert_PIBHEX, convert_PK;
+static convert_func convert_RB, convert_RBHEX, convert_CCx, convert_date;
+static convert_func convert_time, convert_WKDAY, convert_MONTH;
+static convert_func try_F;
+
+/* Converts binary value V into printable form in string S according
+   to format specification FP.  The string as written has exactly
+   FP->W characters.  It is not null-terminated.  Returns 1 on
+   success, 0 on failure. */
+int
+data_out (char *s, const struct fmt_spec *fp, const union value *v)
+{
+  union value tmp_val;
+  
+  {
+    int cat = formats[fp->type].cat;
+    if ((cat & FCAT_BLANKS_SYSMIS) && v->f == SYSMIS)
+      {
+       memset (s, ' ', fp->w);
+       s[fp->w - fp->d - 1] = '.';
+       return 1;
+      }
+    if ((cat & FCAT_SHIFT_DECIMAL) && v->f != SYSMIS && fp->d)
+      {
+       tmp_val.f = v->f * pow (10.0, fp->d);
+       v = &tmp_val;
+      }
+  }
+  
+  {
+    static convert_func *const handlers[FMT_NUMBER_OF_FORMATS] =
+      {
+       convert_F, convert_N, convert_E, convert_F_plus,
+       convert_F_plus, convert_F_plus, convert_F_plus,
+       convert_Z, convert_A, convert_AHEX, convert_IB, convert_P, convert_PIB,
+       convert_PIBHEX, convert_PK, convert_RB, convert_RBHEX,
+       convert_CCx, convert_CCx, convert_CCx, convert_CCx, convert_CCx,
+       convert_date, convert_date, convert_date, convert_date, convert_date,
+       convert_date, convert_date, convert_date, convert_date,
+       convert_time, convert_time,
+       convert_WKDAY, convert_MONTH,
+      };
+
+    return handlers[fp->type] (s, fp, v);
+  }
+}
+
+/* Converts V into S in F format with width W and D decimal places,
+   then deletes trailing zeros.  S is not null-terminated. */
+void
+num_to_string (double v, char *s, int w, int d)
+{
+  /* Dummies to pass to convert_F. */
+  union value val;
+  struct fmt_spec f;
+
+#if !NEW_STYLE
+  /* Pointer to `.' in S. */
+  char *decp;
+
+  /* Pointer to `E' in S. */
+  char *expp;
+
+  /* Number of characters to delete. */
+  int n = 0;
+#endif
+
+  f.w = w;
+  f.d = d;
+  val.f = v;
+
+  /* Cut out the jokers. */
+  if (!finite (v))
+    {
+      char temp[9];
+      int len;
+
+      if (isnan (v))
+       {
+         memcpy (temp, "NaN", 3);
+         len = 3;
+       }
+      else if (isinf (v))
+       {
+         memcpy (temp, "+Infinity", 9);
+         if (v < 0)
+           temp[0] = '-';
+         len = 9;
+       }
+      else
+       {
+         memcpy (temp, _("Unknown"), 7);
+         len = 7;
+       }
+      if (w > len)
+       {
+         int pad = w - len;
+         memset (s, ' ', pad);
+         s += pad;
+         w -= pad;
+       }
+      memcpy (s, temp, w);
+      return;
+    }
+
+  try_F (s, &f, &val);
+
+#if !NEW_STYLE
+  decp = memchr (s, set_decimal, w);
+  if (!decp)
+    return;
+
+  /* If there's an `E' we can only delete 0s before the E. */
+  expp = memchr (s, 'E', w);
+  if (expp)
+    {
+      while (expp[-n - 1] == '0')
+       n++;
+      if (expp[-n - 1] == set_decimal)
+       n++;
+      memmove (&s[n], s, expp - s - n);
+      memset (s, ' ', n);
+      return;
+    }
+
+  /* Otherwise delete all trailing 0s. */
+  n++;
+  while (s[w - n] == '0')
+    n++;
+  if (s[w - n] != set_decimal)
+    {
+      /* Avoid stripping `.0' to `'. */
+      if (w == n || !isdigit ((unsigned char) s[w - n - 1]))
+       n -= 2;
+    }
+  else
+    n--;
+  memmove (&s[n], s, w - n);
+  memset (s, ' ', n);
+#endif
+}
+\f
+/* Main conversion functions. */
+
+static void insert_commas (char *dst, const char *src,
+                          const struct fmt_spec *fp);
+static int year4 (int year);
+static int try_CCx (char *s, const struct fmt_spec *fp, double v);
+
+#if FLT_RADIX!=2
+#error Write your own floating-point output routines.
+#endif
+
+/* PORTME:
+
+   Some of the routines in this file are likely very specific to
+   base-2 representation of floating-point numbers, most notably the
+   routines that use frexp() or ldexp().  These attempt to extract
+   individual digits by setting the base-2 exponent and
+   multiplying/dividing by powers of 2.  In base-2 numeration systems,
+   this just nudges the exponent up or down, but in base-10 floating
+   point, such multiplications/division can cause catastrophic loss of
+   precision.
+
+   The author has never personally used a machine that didn't use
+   binary floating point formats, so he is unwilling, and perhaps
+   unable, to code around this "problem".  */
+
+/* Converts a number between 0 and 15 inclusive to a `hexit'
+   [0-9A-F]. */
+#define MAKE_HEXIT(X) ("0123456789ABCDEF"[X])
+
+/* Table of powers of 10. */
+static const double power10[] =
+  {
+    0, /* Not used. */
+    1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09, 1e10,
+    1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20,
+    1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30,
+    1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, 1e39, 1e40,
+  };
+
+/* Handles F format. */
+static int
+convert_F (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  if (!try_F (dst, fp, v))
+    convert_E (dst, fp, v);
+  return 1;
+}
+
+/* Handles N format. */
+static int
+convert_N (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  double d = floor (v->f);
+
+  if (d < 0 || d == SYSMIS)
+    {
+      msg (ME, _("The N output format cannot be used to output a "
+                "negative number or the system-missing value."));
+      return 0;
+    }
+  
+  if (d < power10[fp->w])
+    {
+      char buf[128];
+      sprintf (buf, "%0*.0f", fp->w, v->f);
+      memcpy (dst, buf, fp->w);
+    }
+  else
+    memset (dst, '*', fp->w);
+
+  return 1;
+}
+
+/* Handles E format.  Also operates as fallback for some other
+   formats. */
+static int
+convert_E (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  /* Temporary buffer. */
+  char buf[128];
+  
+  /* Ranged number of decimal places. */
+  int d;
+
+  /* Check that the format is width enough.
+     Although PSPP generally checks this, convert_E() can be called as
+     a fallback from other formats which do not check. */
+  if (fp->w < 6)
+    {
+      memset (dst, '*', fp->w);
+      return 1;
+    }
+
+  /* Put decimal places in usable range. */
+  d = min (fp->d, fp->w - 6);
+  if (v->f < 0)
+    d--;
+  if (d < 0)
+    d = 0;
+  sprintf (buf, "%*.*E", fp->w, d, v->f);
+
+  /* What we do here is force the exponent part to have four
+     characters whenever possible.  That is, 1.00E+99 is okay (`E+99')
+     but 1.00E+100 (`E+100') must be coerced to 1.00+100 (`+100').  On
+     the other hand, 1.00E1000 (`E+100') cannot be canonicalized.
+     Note that ANSI C guarantees at least two digits in the
+     exponent. */
+  if (fabs (v->f) > 1e99)
+    {
+      /* Pointer to the `E' in buf. */
+      char *cp;
+
+      cp = strchr (buf, 'E');
+      if (cp)
+       {
+         /* Exponent better not be bigger than an int. */
+         int exp = atoi (cp + 1); 
+
+         if (abs (exp) > 99 && abs (exp) < 1000)
+           {
+             /* Shift everything left one place: 1.00e+100 -> 1.00+100. */
+             cp[0] = cp[1];
+             cp[1] = cp[2];
+             cp[2] = cp[3];
+             cp[3] = cp[4];
+           }
+         else if (abs (exp) >= 1000)
+           memset (buf, '*', fp->w);
+       }
+    }
+
+  /* The C locale always uses a period `.' as a decimal point.
+     Translate to comma if necessary. */
+  if ((set_decimal == ',' && fp->type != FMT_DOT)
+      || (set_decimal == '.' && fp->type == FMT_DOT))
+    {
+      char *cp = strchr (buf, '.');
+      if (cp)
+       *cp = ',';
+    }
+
+  memcpy (dst, buf, fp->w);
+  return 1;
+}
+
+/* Handles COMMA, DOT, DOLLAR, and PCT formats. */
+static int
+convert_F_plus (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  char buf[40];
+  
+  if (try_F (buf, fp, v))
+    insert_commas (dst, buf, fp);
+  else
+    convert_E (dst, fp, v);
+
+  return 1;
+}
+
+static int
+convert_Z (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  static int warned = 0;
+
+  if (!warned)
+    {
+      msg (MW, _("Quality of zoned decimal (Z) output format code is "
+                "suspect.  Check your results, report bugs to author."));
+      warned = 1;
+    }
+
+  if (v->f == SYSMIS)
+    {
+      msg (ME, _("The system-missing value cannot be output as a zoned "
+                "decimal number."));
+      return 0;
+    }
+  
+  {
+    char buf[41];
+    double d;
+    int i;
+    
+    d = fabs (floor (v->f));
+    if (d >= power10[fp->w])
+      {
+       msg (ME, _("Number %g too big to fit in field with format Z%d.%d."),
+            v->f, fp->w, fp->d);
+       return 0;
+      }
+
+    sprintf (buf, "%*.0f", fp->w, v->f);
+    for (i = 0; i < fp->w; i++)
+      dst[i] = (buf[i] - '0') | 0xf0;
+    if (v->f < 0)
+      dst[fp->w - 1] &= 0xdf;
+  }
+
+  return 1;
+}
+
+static int
+convert_A (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  memcpy (dst, v->c, fp->w);
+  return 1;
+}
+
+static int
+convert_AHEX (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  int i;
+
+  for (i = 0; i < fp->w / 2; i++)
+    {
+      ((unsigned char *) dst)[i * 2] = MAKE_HEXIT ((v->c[i]) >> 4);
+      ((unsigned char *) dst)[i * 2 + 1] = MAKE_HEXIT ((v->c[i]) & 0xf);
+    }
+
+  return 1;
+}
+
+static int
+convert_IB (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  /* Strategy: Basically the same as convert_PIBHEX() but with base
+     256. Then it's necessary to negate the two's-complement result if
+     v->f is negative. */
+
+  /* Used for constructing the two's-complement result. */
+  unsigned temp[8];
+
+  /* Fraction (mantissa). */
+  double frac;
+
+  /* Exponent. */
+  int exp;
+
+  /* Difference between exponent and (-8*fp->w-1). */
+  int diff;
+
+  /* Counter. */
+  int i;
+
+  /* Make the exponent (-8*fp->w-1). */
+  frac = frexp (fabs (v->f), &exp);
+  diff = exp - (-8 * fp->w - 1);
+  exp -= diff;
+  frac *= ldexp (1.0, diff);
+
+  /* Extract each base-256 digit. */
+  for (i = 0; i < fp->w; i++)
+    {
+      modf (frac, &frac);
+      frac *= 256.0;
+      temp[i] = floor (frac);
+    }
+
+  /* Perform two's-complement negation if v->f is negative. */
+  if (v->f < 0)
+    {
+      /* Perform NOT operation. */
+      for (i = 0; i < fp->w; i++)
+       temp[i] = ~temp[i];
+      /* Add 1 to the whole number. */
+      for (i = fp->w - 1; i >= 0; i--)
+       {
+         temp[i]++;
+         if (temp[i])
+           break;
+       }
+    }
+  memcpy (dst, temp, fp->w);
+  if (endian == LITTLE)
+    mm_reverse (dst, fp->w);
+
+  return 1;
+}
+
+static int
+convert_P (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  /* Buffer for v->f*2-1 characters + a decimal point if library is
+     not quite compliant + a null. */
+  char buf[17];
+
+  /* Counter. */
+  int i;
+
+  /* Main extraction. */
+  sprintf (buf, "%0*.0f", fp->w * 2 - 1, floor (fabs (v->f)));
+
+  for (i = 0; i < fp->w; i++)
+    ((unsigned char *) dst)[i]
+      = ((buf[i * 2] - '0') << 4) + buf[i * 2 + 1] - '0';
+
+  /* Set sign. */
+  dst[fp->w - 1] &= 0xf0;
+  if (v->f >= 0.0)
+    dst[fp->w - 1] |= 0xf;
+  else
+    dst[fp->w - 1] |= 0xd;
+
+  return 1;
+}
+
+static int
+convert_PIB (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  /* Strategy: Basically the same as convert_IB(). */
+
+  /* Fraction (mantissa). */
+  double frac;
+
+  /* Exponent. */
+  int exp;
+
+  /* Difference between exponent and (-8*fp->w). */
+  int diff;
+
+  /* Counter. */
+  int i;
+
+  /* Make the exponent (-8*fp->w). */
+  frac = frexp (fabs (v->f), &exp);
+  diff = exp - (-8 * fp->w);
+  exp -= diff;
+  frac *= ldexp (1.0, diff);
+
+  /* Extract each base-256 digit. */
+  for (i = 0; i < fp->w; i++)
+    {
+      modf (frac, &frac);
+      frac *= 256.0;
+      ((unsigned char *) dst)[i] = floor (frac);
+    }
+  if (endian == LITTLE)
+    mm_reverse (dst, fp->w);
+
+  return 1;
+}
+
+static int
+convert_PIBHEX (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  /* Strategy: Use frexp() to create a normalized result (but mostly
+     to find the base-2 exponent), then change the base-2 exponent to
+     (-4*fp->w) using multiplication and division by powers of two.
+     Extract each hexit by multiplying by 16. */
+
+  /* Fraction (mantissa). */
+  double frac;
+
+  /* Exponent. */
+  int exp;
+
+  /* Difference between exponent and (-4*fp->w). */
+  int diff;
+
+  /* Counter. */
+  int i;
+
+  /* Make the exponent (-4*fp->w). */
+  frac = frexp (fabs (v->f), &exp);
+  diff = exp - (-4 * fp->w);
+  exp -= diff;
+  frac *= ldexp (1.0, diff);
+
+  /* Extract each hexit. */
+  for (i = 0; i < fp->w; i++)
+    {
+      modf (frac, &frac);
+      frac *= 16.0;
+      *dst++ = MAKE_HEXIT ((int) floor (frac));
+    }
+
+  return 1;
+}
+
+static int
+convert_PK (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  /* Buffer for v->f*2 characters + a decimal point if library is not
+     quite compliant + a null. */
+  char buf[18];
+
+  /* Counter. */
+  int i;
+
+  /* Main extraction. */
+  sprintf (buf, "%0*.0f", fp->w * 2, floor (fabs (v->f)));
+
+  for (i = 0; i < fp->w; i++)
+    ((unsigned char *) dst)[i]
+      = ((buf[i * 2] - '0') << 4) + buf[i * 2 + 1] - '0';
+
+  return 1;
+}
+
+static int
+convert_RB (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  union
+    {
+      double d;
+      char c[8];
+    }
+  u;
+
+  u.d = v->f;
+  memcpy (dst, u.c, fp->w);
+
+  return 1;
+}
+
+static int
+convert_RBHEX (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  union
+  {
+    double d;
+    char c[8];
+  }
+  u;
+
+  int i;
+
+  u.d = v->f;
+  for (i = 0; i < fp->w / 2; i++)
+    {
+      *dst++ = MAKE_HEXIT (u.c[i] >> 4);
+      *dst++ = MAKE_HEXIT (u.c[i] & 15);
+    }
+
+  return 1;
+}
+
+static int
+convert_CCx (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  if (try_CCx (dst, fp, v->f))
+    return 1;
+  else
+    {
+      struct fmt_spec f;
+      
+      f.type = FMT_COMMA;
+      f.w = fp->w;
+      f.d = fp->d;
+  
+      return convert_F (dst, &f, v);
+    }
+}
+
+static int
+convert_date (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  static const char *months[12] =
+    {
+      "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
+      "JUL", "AUG", "SEP", "OCT", "NOV", "DEC",
+    };
+
+  char buf[64] = {0};
+  int month, day, year;
+
+  julian_to_calendar (v->f / 86400., &year, &month, &day);
+  switch (fp->type)
+    {
+    case FMT_DATE:
+      if (fp->w >= 11)
+       sprintf (buf, "%02d-%s-%04d", day, months[month - 1], year);
+      else
+       sprintf (buf, "%02d-%s-%02d", day, months[month - 1], year % 100);
+      break;
+    case FMT_EDATE:
+      if (fp->w >= 10)
+       sprintf (buf, "%02d.%02d.%04d", day, month, year);
+      else
+       sprintf (buf, "%02d.%02d.%02d", day, month, year % 100);
+      break;
+    case FMT_SDATE:
+      if (fp->w >= 10)
+       sprintf (buf, "%04d/%02d/%02d", year, month, day);
+      else
+       sprintf (buf, "%02d/%02d/%02d", year % 100, month, day);
+      break;
+    case FMT_ADATE:
+      if (fp->w >= 10)
+       sprintf (buf, "%02d/%02d/%04d", month, day, year);
+      else
+       sprintf (buf, "%02d/%02d/%02d", month, day, year % 100);
+      break;
+    case FMT_JDATE:
+      {
+       int yday = (v->f / 86400.) - calendar_to_julian (year, 1, 1) + 1;
+       
+       if (fp->w >= 7)
+         {
+           if (year4 (year))
+             sprintf (buf, "%04d%03d", year, yday);
+         }
+       else
+         sprintf (buf, "%02d%03d", year % 100, yday);
+       break;
+      }
+    case FMT_QYR:
+      if (fp->w >= 8)
+       sprintf (buf, "%d Q% 04d", (month - 1) / 3 + 1, year);
+      else
+       sprintf (buf, "%d Q% 02d", (month - 1) / 3 + 1, year % 100);
+      break;
+    case FMT_MOYR:
+      if (fp->w >= 8)
+       sprintf (buf, "%s% 04d", months[month - 1], year);
+      else
+       sprintf (buf, "%s% 02d", months[month - 1], year % 100);
+      break;
+    case FMT_WKYR:
+      {
+       int yday = (v->f / 86400.) - calendar_to_julian (year, 1, 1) + 1;
+       
+       if (fp->w >= 10)
+         sprintf (buf, "%02d WK% 04d", (yday - 1) / 7 + 1, year);
+       else
+         sprintf (buf, "%02d WK% 02d", (yday - 1) / 7 + 1, year % 100);
+      }
+      break;
+    case FMT_DATETIME:
+      {
+       char *cp;
+
+       cp = spprintf (buf, "%02d-%s-%04d %02d:%02d",
+                      day, months[month - 1], year,
+                      (int) fmod (floor (v->f / 60. / 60.), 24.),
+                      (int) fmod (floor (v->f / 60.), 60.));
+       if (fp->w >= 20)
+         {
+           int w, d;
+
+           if (fp->w >= 22 && fp->d > 0)
+             {
+               d = min (fp->d, fp->w - 21);
+               w = 3 + d;
+             }
+           else
+             {
+               w = 2;
+               d = 0;
+             }
+
+           cp = spprintf (cp, ":%0*.*f", w, d, fmod (v->f, 60.));
+         }
+      }
+      break;
+#if __CHECKER__
+    case 42000:
+      assert (0);
+#endif
+    default:
+      assert (0);
+    }
+
+  if (buf[0] == 0)
+    return 0;
+  st_bare_pad_copy (dst, buf, fp->w);
+  return 1;
+}
+
+static int
+convert_time (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  char temp_buf[40];
+  char *cp;
+
+  double time;
+  int width;
+
+  if (fabs (v->f) > 1e20)
+    {
+      msg (ME, _("Time value %g too large in magnitude to convert to "
+          "alphanumeric time."), v->f);
+      return 0;
+    }
+
+  time = v->f;
+  width = fp->w;
+  cp = temp_buf;
+  if (time < 0)
+    *cp++ = '-', time = -time;
+  if (fp->type == FMT_DTIME)
+    {
+      double days = floor (time / 60. / 60. / 24.);
+      cp = spprintf (temp_buf, "%02.0f ", days);
+      time = time - days * 60. * 60. * 24.;
+      width -= 3;
+    }
+  else
+    cp = temp_buf;
+
+  cp = spprintf (cp, "%02.0f:%02.0f",
+                fmod (floor (time / 60. / 60.), 24.),
+                fmod (floor (time / 60.), 60.));
+
+  if (width >= 8)
+    {
+      int w, d;
+
+      if (width >= 10 && fp->d >= 0 && fp->d != 0)
+       d = min (fp->d, width - 9), w = 3 + d;
+      else
+       w = 2, d = 0;
+
+      cp = spprintf (cp, ":%0*.*f", w, d, fmod (time, 60.));
+    }
+  st_bare_pad_copy (dst, temp_buf, fp->w);
+
+  return 1;
+}
+
+static int
+convert_WKDAY (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  static const char *weekdays[7] =
+    {
+      "SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY",
+      "THURSDAY", "FRIDAY", "SATURDAY",
+    };
+
+  int x = v->f;
+
+  if (x < 1 || x > 7)
+    {
+      msg (ME, _("Weekday index %d does not lie between 1 and 7."), x);
+      return 0;
+    }
+  st_bare_pad_copy (dst, weekdays[x - 1], fp->w);
+
+  return 1;
+}
+
+static int
+convert_MONTH (char *dst, const struct fmt_spec *fp, const union value *v)
+{
+  static const char *months[12] =
+    {
+      "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE",
+      "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER",
+    };
+
+  int x = v->f;
+
+  if (x < 1 || x > 12)
+    {
+      msg (ME, _("Month index %d does not lie between 1 and 12."), x);
+      return 0;
+    }
+  
+  st_bare_pad_copy (dst, months[x - 1], fp->w);
+
+  return 1;
+}
+\f
+/* Helper functions. */
+
+/* Copies SRC to DST, inserting commas and dollar signs as appropriate
+   for format spec *FP.  */
+static void
+insert_commas (char *dst, const char *src, const struct fmt_spec *fp)
+{
+  /* Number of leading spaces in the number.  This is the amount of
+     room we have for inserting commas and dollar signs. */
+  int n_spaces;
+
+  /* Number of digits before the decimal point.  This is used to
+     determine the Number of commas to insert. */
+  int n_digits;
+
+  /* Number of commas to insert. */
+  int n_commas;
+
+  /* Number of items ,%$ to insert. */
+  int n_items;
+
+  /* Number of n_items items not to use for commas. */
+  int n_reserved;
+
+  /* Digit iterator. */
+  int i;
+
+  /* Source pointer. */
+  const char *sp;
+
+  /* Count spaces and digits. */
+  sp = src;
+  while (sp < src + fp->w && *sp == ' ')
+    sp++;
+  n_spaces = sp - src;
+  sp = src + n_spaces;
+  if (*sp == '-')
+    sp++;
+  n_digits = 0;
+  while (sp + n_digits < src + fp->w && isdigit ((unsigned char) sp[n_digits]))
+    n_digits++;
+  n_commas = (n_digits - 1) / 3;
+  n_items = n_commas + (fp->type == FMT_DOLLAR || fp->type == FMT_PCT);
+
+  /* Check whether we have enough space to do insertions. */
+  if (!n_spaces || !n_items)
+    {
+      memcpy (dst, src, fp->w);
+      return;
+    }
+  if (n_items > n_spaces)
+    {
+      n_items -= n_commas;
+      if (!n_items)
+       {
+         memcpy (dst, src, fp->w);
+         return;
+       }
+    }
+
+  /* Put spaces at the beginning if there's extra room. */
+  if (n_spaces > n_items)
+    {
+      memset (dst, ' ', n_spaces - n_items);
+      dst += n_spaces - n_items;
+    }
+
+  /* Insert $ and reserve space for %. */
+  n_reserved = 0;
+  if (fp->type == FMT_DOLLAR)
+    {
+      *dst++ = '$';
+      n_items--;
+    }
+  else if (fp->type == FMT_PCT)
+    n_reserved = 1;
+
+  /* Copy negative sign and digits, inserting commas. */
+  if (sp - src > n_spaces)
+    *dst++ = '-';
+  for (i = n_digits; i; i--)
+    {
+      if (i % 3 == 0 && n_digits > i && n_items > n_reserved)
+       {
+         n_items--;
+         *dst++ = fp->type == FMT_COMMA ? set_grouping : set_decimal;
+       }
+      *dst++ = *sp++;
+    }
+
+  /* Copy decimal places and insert % if necessary. */
+  memcpy (dst, sp, fp->w - (sp - src));
+  if (fp->type == FMT_PCT && n_items > 0)
+    dst[fp->w - (sp - src)] = '%';
+}
+
+/* Returns 1 if YEAR (i.e., 1987) can be represented in four digits, 0
+   otherwise. */
+static int
+year4 (int year)
+{
+  if (year >= 1 && year <= 9999)
+    return 1;
+  msg (ME, _("Year %d cannot be represented in four digits for "
+            "output formatting purposes."), year);
+  return 0;
+}
+
+static int
+try_CCx (char *dst, const struct fmt_spec *fp, double v)
+{
+  struct set_cust_currency *cc = &set_cc[fp->type - FMT_CCA];
+
+  struct fmt_spec f;
+
+  char buf[64];
+  char buf2[64];
+  char *cp;
+
+  /* Determine length available, decimal character for number
+     proper. */
+  f.type = cc->decimal == set_decimal ? FMT_COMMA : FMT_DOT;
+  f.w = fp->w - strlen (cc->prefix) - strlen (cc->suffix);
+  if (v < 0)
+    f.w -= strlen (cc->neg_prefix) + strlen (cc->neg_suffix) - 1;
+  else
+    /* Convert -0 to +0. */
+    v = fabs (v);
+  f.d = fp->d;
+
+  if (f.w <= 0)
+    return 0;
+
+  /* There's room for all that currency crap.  Let's do the F
+     conversion first. */
+  if (!convert_F (buf, &f, (union value *) &v) || *buf == '*')
+    return 0;
+  insert_commas (buf2, buf, &f);
+
+  /* Postprocess back into buf. */
+  cp = buf;
+  if (v < 0)
+    cp = stpcpy (cp, cc->neg_prefix);
+  cp = stpcpy (cp, cc->prefix);
+  {
+    char *bp = buf2;
+    while (*bp == ' ')
+      bp++;
+
+    assert ((v >= 0) ^ (*bp == '-'));
+    if (v < 0)
+      bp++;
+
+    memcpy (cp, bp, f.w - (bp - buf2));
+    cp += f.w - (bp - buf2);
+  }
+  cp = stpcpy (cp, cc->suffix);
+  if (v < 0)
+    cp = stpcpy (cp, cc->neg_suffix);
+
+  /* Copy into dst. */
+  assert (cp - buf <= fp->w);
+  if (cp - buf < fp->w)
+    {
+      memcpy (&dst[fp->w - (cp - buf)], buf, cp - buf);
+      memset (dst, ' ', fp->w - (cp - buf));
+    }
+  else
+    memcpy (dst, buf, fp->w);
+
+  return 1;
+}
+
+/* This routine relies on the underlying implementation of sprintf:
+
+   If the number has a magnitude 1e40 or greater, then we needn't
+   bother with it, since it's guaranteed to need processing in
+   scientific notation.
+
+   Otherwise, do a binary search for the base-10 magnitude of the
+   thing.  log10() is not accurate enough, and the alternatives are
+   frightful.  Besides, we never need as many as 6 (pairs of)
+   comparisons.  The algorithm used for searching is Knuth's Algorithm
+   6.2.1C (Uniform binary search).
+
+   DON'T CHANGE ANYTHING HERE UNLESS YOU'VE THOUGHT ABOUT IT FOR A
+   LONG TIME!  The rest of the program is heavily dependent on
+   specific properties of this routine's output.  LOG ALL CHANGES! */
+static int
+try_F (char *dst, const struct fmt_spec *fp, const union value *value)
+{
+  /* This is the DELTA array from Knuth.
+     DELTA[j] = floor((40+2**(j-1))/(2**j)). */
+  static const int delta[8] =
+  {
+    0, (40 + 1) / 2, (40 + 2) / 4, (40 + 4) / 8, (40 + 8) / 16,
+    (40 + 16) / 32, (40 + 32) / 64, (40 + 64) / 128,
+  };
+
+  /* The number of digits in floor(v), including sign.  This is `i'
+     from Knuth. */
+  int n_int = (40 + 1) / 2;
+
+  /* Used to step through delta[].  This is `j' from Knuth. */
+  int j = 2;
+
+  /* Value. */
+  double v = value->f;
+
+  /* Magnitude of v.  This is `K' from Knuth. */
+  double mag;
+
+  /* Number of characters for the fractional part, including the
+     decimal point. */
+  int n_dec;
+
+  /* Pointer into buf used for formatting. */
+  char *cp;
+
+  /* Used to count characters formatted by nsprintf(). */
+  int n;
+
+  /* Temporary buffer. */
+  char buf[128];
+
+  /* First check for infinities and NaNs.  12/13/96. */
+  if (!finite (v))
+    {
+      n = nsprintf (buf, "%f", v);
+      if (n > fp->w)
+       memset (buf, '*', fp->w);
+      else if (n < fp->w)
+       {
+         memmove (&buf[fp->w - n], buf, n);
+         memset (buf, ' ', fp->w - n);
+       }
+      memcpy (dst, buf, fp->w);
+      return 1;
+    }
+
+  /* Then check for radically out-of-range values. */
+  mag = fabs (v);
+  if (mag >= power10[fp->w])
+    return 0;
+
+  if (mag < 1.0)
+    {
+      n_int = 0;
+
+      /* Avoid printing `-.000'. 7/6/96. */
+      if (approx_eq (v, 0.0))
+       v = 0.0;
+    }
+  else
+    /* Now perform a `uniform binary search' based on the tables
+       power10[] and delta[].  After this step, nint is the number of
+       digits in floor(v), including any sign.  */
+    for (;;)
+      {
+       if (mag >= power10[n_int])      /* Should this be approx_ge()? */
+         {
+           assert (delta[j]);
+           n_int += delta[j++];
+         }
+       else if (mag < power10[n_int - 1])
+         {
+           assert (delta[j]);
+           n_int -= delta[j++];
+         }
+       else
+         break;
+      }
+
+  /* If we have any decimal places, then there is a decimal point,
+     too. */
+  n_dec = fp->d;
+  if (n_dec)
+    n_dec++;
+
+  /* 1/10/96: If there aren't any digits at all, add one.  This occurs
+     only when fabs(v) < 1.0. */
+  if (n_int + n_dec == 0)
+    n_int++;
+
+  /* Give space for a minus sign.  Moved 1/10/96. */
+  if (v < 0)
+    n_int++;
+
+  /* Normally we only go through the loop once; occasionally twice.
+     Three times or more indicates a very serious bug somewhere. */
+  for (;;)
+    {
+      /* Check out the total length of the string. */
+      cp = buf;
+      if (n_int + n_dec > fp->w)
+       {
+         /* The string is too long.  Let's see what can be done. */
+         if (n_int <= fp->w)
+           /* If we can, just reduce the number of decimal places. */
+           n_dec = fp->w - n_int;
+         else
+           return 0;
+       }
+      else if (n_int + n_dec < fp->w)
+       {
+         /* The string is too short.  Left-pad with spaces. */
+         int n_spaces = fp->w - n_int - n_dec;
+         memset (cp, ' ', n_spaces);
+         cp += n_spaces;
+       }
+
+      /* Finally, format the number. */
+      if (n_dec)
+       n = nsprintf (cp, "%.*f", n_dec - 1, v);
+      else
+       n = nsprintf (cp, "%.0f", v);
+
+      /* If v is positive and its magnitude is less than 1...  */
+      if (n_int == 0)
+       {
+         if (*cp == '0')
+           {
+             /* The value rounds to `.###'. */
+             memmove (cp, &cp[1], n - 1);
+             n--;
+           }
+         else
+           {
+             /* The value rounds to `1.###'. */
+             n_int = 1;
+             continue;
+           }
+       }
+      /* Else if v is negative and its magnitude is less than 1...  */
+      else if (v < 0 && n_int == 1)
+       {
+         if (cp[1] == '0')
+           {
+             /* The value rounds to `-.###'. */
+             memmove (&cp[1], &cp[2], n - 2);
+             n--;
+           }
+         else
+           {
+             /* The value rounds to `-1.###'. */
+             n_int = 2;
+             continue;
+           }
+       }
+
+      /* Check for a correct number of digits & decimal places & stuff.
+         This is just a desperation check.  Hopefully it won't fail too
+         often, because then we have to run through the whole loop again:
+         sprintf() is not a fast operation with floating-points! */
+      if (n == n_int + n_dec)
+       {
+         /* Convert periods `.' to commas `,' for our foreign friends. */
+         if ((set_decimal == ',' && fp->type != FMT_DOT)
+             || (set_decimal == '.' && fp->type == FMT_DOT))
+           {
+             cp = strchr (cp, '.');
+             if (cp)
+               *cp = ',';
+           }
+
+         memcpy (dst, buf, fp->w);
+         return 1;
+       }
+
+      n_int = n - n_dec; /* FIXME?  Need an idiot check on resulting n_int? */
+    }
+}
diff --git a/src/debug-print.h b/src/debug-print.h
new file mode 100644 (file)
index 0000000..061b219
--- /dev/null
@@ -0,0 +1,54 @@
+/* This file can be included multiple times.  It redeclares its macros
+   appropriately each time, like assert.h. */
+
+#undef debug_printf
+#undef debug_puts
+#undef debug_putc
+
+#if DEBUGGING
+
+#define debug_printf(args)                     \
+       do                                      \
+         {                                     \
+           printf args;                        \
+           fflush (stdout);                    \
+         }                                     \
+       while (0)
+       
+#define debug_puts(string)                     \
+       do                                      \
+         {                                     \
+           puts (string);                      \
+           fflush (stdout);                    \
+         }                                     \
+       while (0)
+
+#define debug_putc(char, stream)               \
+       do                                      \
+         {                                     \
+           putc (char, stream);                \
+           fflush (stdout);                    \
+         }                                     \
+       while (0)
+
+#else /* !DEBUGGING */
+
+#define debug_printf(args)                     \
+       do                                      \
+         {                                     \
+         }                                     \
+       while (0)
+
+#define debug_puts(string)                     \
+       do                                      \
+         {                                     \
+         }                                     \
+       while (0)
+
+#define debug_putc(char, stream)               \
+       do                                      \
+         {                                     \
+         }                                     \
+       while (0)
+
+#endif /* !DEBUGGING */
diff --git a/src/descript.q b/src/descript.q
new file mode 100644 (file)
index 0000000..168f29a
--- /dev/null
@@ -0,0 +1,866 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* FIXME: Many possible optimizations. */
+
+#include <config.h>
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "bitvector.h"
+#include "command.h"
+#include "lexer.h"
+#include "error.h"
+#include "approx.h"
+#include "magic.h"
+#include "stats.h"
+#include "som.h"
+#include "tab.h"
+#include "var.h"
+#include "vfm.h"
+
+/* (specification)
+   DESCRIPTIVES (dsc_):
+     *variables=custom;
+     +missing=miss:!variable/listwise,incl:!noinclude/include;
+     +format=labeled:!labels/nolabels,indexed:!noindex/index,lined:!line/serial;
+     +save=;
+     +options[op_]=1,2,3,4,5,6,7,8;
+     +statistics[st_]=all,1|mean,2|semean,5|stddev,6|variance,7|kurtosis,
+                     8|skewness,9|range,10|minimum,11|maximum,12|sum,
+                     13|default,14|seskewness,15|sekurtosis;
+     +sort=sortby:mean/semean/stddev/variance/kurtosis/skewness/range/
+          range/minimum/maximum/sum/name/seskewness/sekurtosis/!none, 
+          order:!a/d.
+*/
+/* (declarations) */
+/* (functions) */
+
+/* DESCRIPTIVES private data. */
+
+/* Describes properties of a distribution for the purpose of
+   calculating a Z-score. */
+struct dsc_z_score
+  {
+    struct variable *s, *d;    /* Source, destination variable. */
+    double mean;               /* Distribution mean. */
+    double std_dev;            /* Distribution standard deviation. */
+  };
+
+/* DESCRIPTIVES transformation (for calculating Z-scores). */
+struct descriptives_trns
+  {
+    struct trns_header h;
+    int n;                     /* Number of Z-scores. */
+    struct dsc_z_score *z;     /* Array of Z-scores. */
+  };
+
+/* These next three vars, see comment at top of display(). */
+/* Number of cases missing listwise, even if option 5 not selected. */
+static double d_glob_miss_list;
+
+/* Number of total *cases* valid or missing, as a double.  Unless
+   option 5 is selected, d_glob_missing is 0. */
+static double d_glob_valid, d_glob_missing;
+
+/* Set when a weighting variable is missing or <=0. */
+static int bad_weight;
+
+/* Number of generic zvarnames we've generated in this execution. */
+static int z_count;
+
+/* Variables specified on command. */
+static struct variable **v_variables;
+static int n_variables;
+
+/* Command specifications. */
+static struct cmd_descriptives cmd;
+
+/* Whether z-scores are computed. */
+static int z_scores;
+
+/* Statistic to sort by. */
+static int sortby_stat;
+
+/* Statistics to display. */
+static unsigned long stats;
+
+/* Easier access to long-named arrays. */
+#define stat cmd.a_statistics
+#define opt  cmd.a_options
+
+/* Groups of statistics. */
+#define BI          BIT_INDEX
+
+#define dsc_default                                                    \
+       (BI (dsc_mean) | BI (dsc_stddev) | BI (dsc_min) | BI (dsc_max))
+     
+#define dsc_all                                                        \
+       (BI (dsc_sum) | BI (dsc_min) | BI (dsc_max)             \
+        | BI (dsc_mean) | BI (dsc_semean) | BI (dsc_stddev)    \
+        | BI (dsc_variance) | BI (dsc_kurt) | BI (dsc_sekurt)  \
+        | BI (dsc_skew) | BI (dsc_seskew) | BI (dsc_range)     \
+        | BI (dsc_range))
+
+/* Table of options. */
+#define op_incl_miss   DSC_OP_1        /* Honored. */
+#define op_no_varlabs  DSC_OP_2        /* Ignored. */
+#define op_zscores     DSC_OP_3        /* Honored. */
+#define op_index       DSC_OP_4        /* FIXME. */
+#define op_excl_miss   DSC_OP_5        /* Honored. */
+#define op_serial      DSC_OP_6        /* Honored. */
+#define op_narrow      DSC_OP_7        /* Ignored. */
+#define op_no_varnames DSC_OP_8        /* Honored. */
+
+/* Describes one statistic that can be calculated. */
+/* FIXME: Currently sm,col_width are not used. */
+struct dsc_info
+  {
+    int st_indx;               /* Index into st_a_statistics[]. */
+    int sb_indx;               /* Sort-by index. */
+    const char *s10;           /* Name, stuffed into 10 columns. */
+    const char *s8;            /* Name, stuffed into 8 columns. */
+    const char *sm;            /* Name, stuffed minimally. */
+    const char *s;             /* Full name. */
+    int max_degree;            /* Highest degree necessary to calculate this
+                                  statistic. */
+    int col_width;             /* Column width (not incl. spacing between columns) */
+  };
+
+/* Table of statistics, indexed by dsc_*. */
+static struct dsc_info dsc_info[dsc_n_stats] =
+{
+  {DSC_ST_MEAN, DSC_MEAN, N_("Mean"), N_("Mean"), N_("Mean"), N_("mean"), 1, 10},
+  {DSC_ST_SEMEAN, DSC_SEMEAN, N_("S.E. Mean"), N_("S E Mean"), N_("SE"),
+   N_("standard error of mean"), 2, 10},
+  {DSC_ST_STDDEV, DSC_STDDEV, N_("Std Dev"), N_("Std Dev"), N_("SD"),
+   N_("standard deviation"), 2, 11},
+  {DSC_ST_VARIANCE, DSC_VARIANCE, N_("Variance"), N_("Variance"),
+   N_("Var"), N_("variance"), 2, 12},
+  {DSC_ST_KURTOSIS, DSC_KURTOSIS, N_("Kurtosis"), N_("Kurtosis"),
+   N_("Kurt"), N_("kurtosis"), 4, 9},
+  {DSC_ST_SEKURTOSIS, DSC_SEKURTOSIS, N_("S.E. Kurt"), N_("S E Kurt"), N_("SEKurt"),
+   N_("standard error of kurtosis"), 0, 9},
+  {DSC_ST_SKEWNESS, DSC_SKEWNESS, N_("Skewness"), N_("Skewness"), N_("Skew"),
+   N_("skewness"), 3, 9},
+  {DSC_ST_SESKEWNESS, DSC_SESKEWNESS, N_("S.E. Skew"), N_("S E Skew"), N_("SESkew"),
+   N_("standard error of skewness"), 0, 9},
+  {DSC_ST_RANGE, DSC_RANGE, N_("Range"), N_("Range"), N_("Rng"), N_("range"), 0, 10},
+  {DSC_ST_MINIMUM, DSC_MINIMUM, N_("Minimum"), N_("Minimum"), N_("Min"),
+   N_("minimum"), 0, 10},
+  {DSC_ST_MAXIMUM, DSC_MAXIMUM, N_("Maximum"), N_("Maximum"), N_("Max"),
+   N_("maximum"), 0, 10},
+  {DSC_ST_SUM, DSC_SUM, N_("Sum"), N_("Sum"), N_("Sum"), N_("sum"), 1, 13},
+};
+
+/* Z-score functions. */
+static int generate_z_varname (struct variable * v);
+static void dump_z_table (void);
+static void run_z_pass (void);
+
+/* Procedure execution functions. */
+static int calc (struct ccase *);
+static void precalc (void);
+static void postcalc (void);
+static void display (void);
+\f
+/* Parser and outline. */
+
+int
+cmd_descriptives (void)
+{
+  struct variable *v;
+  int i;
+
+  v_variables = NULL;
+  n_variables = 0;
+
+  lex_match_id ("DESCRIPTIVES");
+  lex_match_id ("CONDESCRIPTIVES");
+  if (!parse_descriptives (&cmd))
+    goto lossage;
+
+  if (n_variables == 0)
+    goto lossage;
+  for (i = 0; i < n_variables; i++)
+    {
+      v = v_variables[i];
+      v->p.dsc.dup = 0;
+      v->p.dsc.zname[0] = 0;
+    }
+
+  if (n_variables < 0)
+    {
+      msg (SE, _("No variables specified."));
+      goto lossage;
+    }
+
+  if (cmd.sbc_options && (cmd.sbc_save || cmd.sbc_format || cmd.sbc_missing))
+    {
+      msg (SE, _("OPTIONS may not be used with SAVE, FORMAT, or MISSING."));
+      goto lossage;
+    }
+  
+  if (!cmd.sbc_options)
+    {
+      if (cmd.incl == DSC_INCLUDE)
+       opt[op_incl_miss] = 1;
+      if (cmd.labeled == DSC_NOLABELS)
+       opt[op_no_varlabs] = 1;
+      if (cmd.sbc_save)
+       opt[op_zscores] = 1;
+      if (cmd.miss == DSC_LISTWISE)
+       opt[op_excl_miss] = 1;
+      if (cmd.lined == DSC_SERIAL)
+       opt[op_serial] = 1;
+    }
+
+  /* Construct z-score varnames, show translation table. */
+  if (opt[op_zscores])
+    {
+      z_count = 0;
+      for (i = 0; i < n_variables; i++)
+       {
+         v = v_variables[i];
+         if (v->p.dsc.dup++)
+           continue;
+
+         if (v->p.dsc.zname[0] == 0)
+           if (!generate_z_varname (v))
+             goto lossage;
+       }
+      dump_z_table ();
+      z_scores = 1;
+    }
+
+  /* Figure out statistics to calculate. */
+  stats = 0;
+  if (stat[DSC_ST_DEFAULT] || !cmd.sbc_statistics)
+    stats |= dsc_default;
+  if (stat[DSC_ST_ALL])
+    stats |= dsc_all;
+  for (i = 0; i < dsc_n_stats; i++)
+    if (stat[dsc_info[i].st_indx])
+      stats |= BIT_INDEX (i);
+  if (stats & dsc_kurt)
+    stats |= dsc_sekurt;
+  if (stats & dsc_skew)
+    stats |= dsc_seskew;
+
+  /* Check the sort order. */
+  sortby_stat = -1;
+  if (cmd.sortby == DSC_NONE)
+    sortby_stat = -2;
+  else if (cmd.sortby != DSC_NAME)
+    {
+      for (i = 0; i < n_variables; i++)
+       if (dsc_info[i].sb_indx == cmd.sortby)
+         {
+           sortby_stat = i;
+           if (!(stats & BIT_INDEX (i)))
+             {
+               msg (SE, _("It's not possible to sort on `%s' without "
+                          "displaying `%s'."),
+                    gettext (dsc_info[i].s), gettext (dsc_info[i].s));
+               goto lossage;
+             }
+         }
+      assert (sortby_stat != -1);
+    }
+
+  /* Data pass! */
+  update_weighting (&default_dict);
+  bad_weight = 0;
+  procedure (precalc, calc, postcalc);
+
+  if (bad_weight)
+    msg (SW, _("At least one case in the data file had a weight value "
+        "that was system-missing, zero, or negative.  These case(s) "
+        "were ignored."));
+
+  /* Z-scoring! */
+  if (z_scores)
+    run_z_pass ();
+
+  if (v_variables)
+    free (v_variables);
+  return CMD_SUCCESS;
+
+ lossage:
+  if (v_variables)
+    free (v_variables);
+  return CMD_FAILURE;
+}
+
+/* Parses the VARIABLES subcommand. */
+static int
+dsc_custom_variables (struct cmd_descriptives *cmd unused)
+{
+  if (!lex_match_id ("VARIABLES")
+      && (token != T_ID || !is_varname (tokid))
+      && token != T_ALL)
+    return 2;
+  lex_match ('=');
+
+  while (token == T_ID || token == T_ALL)
+    {
+      int i, n;
+
+      n = n_variables;
+      if (!parse_variables (NULL, &v_variables, &n_variables,
+                           PV_DUPLICATE | PV_SINGLE | PV_APPEND | PV_NUMERIC
+                           | PV_NO_SCRATCH))
+       return 0;
+      if (lex_match ('('))
+       {
+         if (n_variables - n > 1)
+           {
+             msg (SE, _("Names for z-score variables must be given for "
+                        "individual variables, not for groups of "
+                        "variables."));
+             return 0;
+           }
+         assert (n_variables - n <= 0);
+         if (token != T_ID)
+           {
+             msg (SE, _("Name for z-score variable expected."));
+             return 0;
+           }
+         if (is_varname (tokid))
+           {
+             msg (SE, _("Z-score variable name `%s' is a "
+                        "duplicate variable name with a current variable."),
+                  tokid);
+             return 0;
+           }
+         for (i = 0; i < n_variables; i++)
+           if (v_variables[i]->p.dsc.zname[0]
+               && !strcmp (v_variables[i]->p.dsc.zname, tokid))
+             {
+               msg (SE, _("Z-score variable name `%s' is "
+                          "used multiple times."), tokid);
+               return 0;
+             }
+         strcpy (v_variables[n_variables - 1]->p.dsc.zname, tokid);
+         lex_get ();
+         if (token != ')')
+           {
+             msg (SE, _("`)' expected after z-score variable name."));
+             return 0;
+           }
+
+         z_scores = 1;
+       }
+      lex_match (',');
+    }
+  return 1;
+}
+\f
+/* Z scores. */
+
+/* Returns 0 if NAME is a duplicate of any existing variable name or
+   of any previously-declared z-var name; otherwise returns 1. */
+static int
+try_name (char *name)
+{
+  int i;
+
+  if (is_varname (name))
+    return 0;
+  for (i = 0; i < n_variables; i++)
+    {
+      struct variable *v = v_variables[i];
+      if (!strcmp (v->p.dsc.zname, name))
+       return 0;
+    }
+  return 1;
+}
+
+static int
+generate_z_varname (struct variable * v)
+{
+  char zname[10];
+
+  strcpy (&zname[1], v->name);
+  zname[0] = 'Z';
+  zname[8] = '\0';
+  if (try_name (zname))
+    {
+      strcpy (v->p.dsc.zname, zname);
+      return 1;
+    }
+
+  for (;;)
+    {
+      /* Generate variable name. */
+      z_count++;
+
+      if (z_count <= 99)
+       sprintf (zname, "ZSC%03d", z_count);
+      else if (z_count <= 108)
+       sprintf (zname, "STDZ%02d", z_count - 99);
+      else if (z_count <= 117)
+       sprintf (zname, "ZZZZ%02d", z_count - 108);
+      else if (z_count <= 126)
+       sprintf (zname, "ZQZQ%02d", z_count - 117);
+      else
+       {
+         msg (SE, _("Ran out of generic names for Z-score variables.  "
+                    "There are only 126 generic names: ZSC001-ZSC0999, "
+                    "STDZ01-STDZ09, ZZZZ01-ZZZZ09, ZQZQ01-ZQZQ09."));
+         return 0;
+       }
+      
+      if (try_name (zname))
+       {
+         strcpy (v->p.dsc.zname, zname);
+         return 1;
+       }
+    }
+}
+
+static void
+dump_z_table (void)
+{
+  int count;
+  struct tab_table *t;
+  
+  {
+    int i;
+    
+    for (count = i = 0; i < n_variables; i++)
+      if (v_variables[i]->p.dsc.zname)
+       count++;
+  }
+  
+  t = tab_create (2, count + 1, 0);
+  tab_title (t, 0, _("Mapping of variables to corresponding Z-scores."));
+  tab_columns (t, SOM_COL_DOWN, 1);
+  tab_headers (t, 0, 0, 1, 0);
+  tab_box (t, TAL_1, TAL_1, TAL_0, TAL_1, 0, 0, 1, count);
+  tab_hline (t, TAL_2, 0, 1, 1);
+  tab_text (t, 0, 0, TAB_CENTER | TAT_TITLE, _("Source"));
+  tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("Target"));
+  tab_dim (t, tab_natural_dimensions);
+
+  {
+    int i, y;
+    
+    for (i = 0, y = 1; i < n_variables; i++)
+      if (v_variables[i]->p.dsc.zname)
+       {
+         tab_text (t, 0, y, TAB_LEFT, v_variables[i]->name);
+         tab_text (t, 1, y++, TAB_LEFT, v_variables[i]->p.dsc.zname);
+       }
+  }
+  
+  tab_submit (t);
+}
+
+/* Transformation function to calculate Z-scores. */
+static int
+descriptives_trns_proc (struct trns_header * trns, struct ccase * c)
+{
+  struct descriptives_trns *t = (struct descriptives_trns *) trns;
+  struct dsc_z_score *z;
+  int i;
+
+  for (i = t->n, z = t->z; i--; z++)
+    {
+      double score = c->data[z->s->fv].f;
+
+      if (z->mean == SYSMIS || score == SYSMIS)
+       c->data[z->d->fv].f = SYSMIS;
+      else
+       c->data[z->d->fv].f = (score - z->mean) / z->std_dev;
+    }
+  return -1;
+}
+
+/* Frees a descriptives_trns struct. */
+static void
+descriptives_trns_free (struct trns_header * trns)
+{
+  struct descriptives_trns *t = (struct descriptives_trns *) trns;
+
+  free (t->z);
+}
+
+/* The name is a misnomer: actually this function sets up a
+   transformation by which scores can be converted into Z-scores. */
+static void
+run_z_pass (void)
+{
+  struct descriptives_trns *t;
+  int count, i;
+
+  for (i = 0; i < n_variables; i++)
+    v_variables[i]->p.dsc.dup = 0;
+  for (count = i = 0; i < n_variables; i++)
+    {
+      if (v_variables[i]->p.dsc.dup++)
+       continue;
+      if (v_variables[i]->p.dsc.zname)
+       count++;
+    }
+
+  t = xmalloc (sizeof *t);
+  t->h.proc = descriptives_trns_proc;
+  t->h.free = descriptives_trns_free;
+  t->n = count;
+  t->z = xmalloc (count * sizeof *t->z);
+
+  for (i = 0; i < n_variables; i++)
+    v_variables[i]->p.dsc.dup = 0;
+  for (count = i = 0; i < n_variables; i++)
+    {
+      struct variable *v = v_variables[i];
+      if (v->p.dsc.dup++ == 0 && v->p.dsc.zname[0])
+       {
+         char *cp;
+         struct variable *d;
+
+         t->z[count].s = v;
+         t->z[count].d = d = force_create_variable (&default_dict,
+                                                    v->p.dsc.zname,
+                                                    NUMERIC, 0);
+         if (v->label)
+           {
+             d->label = xmalloc (strlen (v->label) + 12);
+             cp = stpcpy (d->label, _("Z-score of "));
+             strcpy (cp, v->label);
+           }
+         else
+           {
+             d->label = xmalloc (strlen (v->name) + 12);
+             cp = stpcpy (d->label, _("Z-score of "));
+             strcpy (cp, v->name);
+           }
+         t->z[count].mean = v->p.dsc.stats[dsc_mean];
+         t->z[count].std_dev = v->p.dsc.stats[dsc_stddev];
+         if (t->z[count].std_dev == SYSMIS
+             || approx_eq (t->z[count].std_dev, 0.0))
+           t->z[count].mean = SYSMIS;
+         count++;
+       }
+    }
+
+  add_transformation ((struct trns_header *) t);
+}
+\f
+/* Statistical calculation. */
+
+static void
+precalc (void)
+{
+  int i;
+
+  for (i = 0; i < n_variables; i++)
+    v_variables[i]->p.dsc.dup = -2;
+  for (i = 0; i < n_variables; i++)
+    {
+      struct descriptives_proc *dsc = &v_variables[i]->p.dsc;
+
+      /* Don't need to initialize more than once. */
+      if (dsc->dup == -1)
+       continue;
+      dsc->dup = -1;
+
+      dsc->valid = dsc->miss = 0.0;
+      dsc->X_bar = dsc->M2 = dsc->M3 = dsc->M4 = 0.0;
+      dsc->min = DBL_MAX;
+      dsc->max = -DBL_MAX;
+    }
+  d_glob_valid = d_glob_missing = 0.0;
+}
+
+static int
+calc (struct ccase * c)
+{
+  int i;
+
+  /* Unique case identifier. */
+  static int case_id;
+
+  /* Get the weight for this case. */
+  double w;
+
+  if (default_dict.weight_index == -1)
+    w = 1.0;
+  else
+    {
+      w = c->data[default_dict.weight_index].f;
+      if (w <= 0.0 || w == SYSMIS)
+       {
+         w = 0.0;
+         bad_weight = 1;
+       }
+    }
+
+  case_id++;
+
+  /* Handle missing values. */
+  for (i = 0; i < n_variables; i++)
+    {
+      struct variable *v = v_variables[i];
+      double X = c->data[v->fv].f;
+
+      if (X == SYSMIS || (!opt[op_incl_miss] && is_num_user_missing (X, v)))
+       {
+         if (opt[op_excl_miss])
+           {
+             d_glob_missing += w;
+             return 1;
+           }
+         else
+           {
+             d_glob_miss_list += w;
+             goto iterate;
+           }
+       }
+    }
+  d_glob_valid += w;
+
+iterate:
+  for (i = 0; i < n_variables; i++)
+    {
+      struct descriptives_proc *inf = &v_variables[i]->p.dsc;
+
+      double X, v;
+      double W_old, W_new;
+      double v2, v3, v4;
+      double w2, w3, w4;
+
+      if (inf->dup == case_id)
+       continue;
+      inf->dup = case_id;
+
+      X = c->data[v_variables[i]->fv].f;
+      if (X == SYSMIS
+         || (!opt[op_incl_miss] && is_num_user_missing (X, v_variables[i])))
+       {
+         inf->miss += w;
+         continue;
+       }
+
+      /* These formulas taken from _SPSS Statistical Algorithms_.  The
+         names W_old, and W_new are used for W_j-1 and W_j,
+         respectively, and other variables simply have the subscripts
+         trimmed off, except for X_bar.
+
+         I am happy that mathematical formulas may not be
+         copyrighted. */
+      W_old = inf->valid;
+      W_new = inf->valid += w;
+      v = (w / W_new) * (X - inf->X_bar);
+      v2 = v * v;
+      v3 = v2 * v;
+      v4 = v3 * v;
+      w2 = w * w;
+      w3 = w2 * w;
+      w4 = w3 * w;
+      inf->M4 += (-4.0 * v * inf->M3 + 6.0 * v2 * inf->M2
+              + (W_new * W_new - 3 * w * W_old / w3) * v4 * W_old * W_new);
+      inf->M3 += (-3.0 * v * inf->M2 + W_new * W_old / w2
+                 * (W_new - 2 * w) * v3);
+      inf->M2 += W_new * W_old / w * v2;
+      inf->X_bar += v;
+      if (X < inf->min)
+       inf->min = X;
+      if (X > inf->max)
+       inf->max = X;
+    }
+  return 1;
+}
+
+static void
+postcalc (void)
+{
+  int i;
+
+  if (opt[op_excl_miss])
+    d_glob_miss_list = d_glob_missing;
+
+  for (i = 0; i < n_variables; i++)
+    {
+      struct descriptives_proc *dsc = &v_variables[i]->p.dsc;
+      double W;
+
+      /* Don't duplicate our efforts. */
+      if (dsc->dup == -2)
+       continue;
+      dsc->dup = -2;
+
+      W = dsc->valid;
+
+      dsc->stats[dsc_mean] = dsc->X_bar;
+      dsc->stats[dsc_variance] = dsc->M2 / (W - 1);
+      dsc->stats[dsc_stddev] = sqrt (dsc->stats[dsc_variance]);
+      dsc->stats[dsc_semean] = dsc->stats[dsc_stddev] / sqrt (W);
+      dsc->stats[dsc_min] = dsc->min == DBL_MAX ? SYSMIS : dsc->min;
+      dsc->stats[dsc_max] = dsc->max == -DBL_MAX ? SYSMIS : dsc->max;
+      dsc->stats[dsc_range] = ((dsc->min == DBL_MAX || dsc->max == -DBL_MAX)
+                              ? SYSMIS : dsc->max - dsc->min);
+      dsc->stats[dsc_sum] = W * dsc->X_bar;
+      if (W > 2.0 && dsc->stats[dsc_variance] >= 1e-20)
+       {
+         double S = dsc->stats[dsc_stddev];
+         dsc->stats[dsc_skew] = (W * dsc->M3 / ((W - 1.0) * (W - 2.0) * S * S * S));
+         dsc->stats[dsc_seskew] =
+           sqrt (6.0 * W * (W - 1.0) / ((W - 2.0) * (W + 1.0) * (W + 3.0)));
+       }
+      else
+       {
+         dsc->stats[dsc_skew] = dsc->stats[dsc_seskew] = SYSMIS;
+       }
+      if (W > 3.0 && dsc->stats[dsc_variance] >= 1e-20)
+       {
+         double S2 = dsc->stats[dsc_variance];
+         double SE_g1 = dsc->stats[dsc_seskew];
+
+         dsc->stats[dsc_kurt] =
+           (W * (W + 1.0) * dsc->M4 - 3.0 * dsc->M2 * dsc->M2 * (W - 1.0))
+           / ((W - 1.0) * (W - 2.0) * (W - 3.0) * S2 * S2);
+
+         /* Note that in _SPSS Statistical Algorithms_, the square
+            root symbol is missing from this formula. */
+         dsc->stats[dsc_sekurt] =
+           sqrt ((4.0 * (W * W - 1.0) * SE_g1 * SE_g1) / ((W - 3.0) * (W + 5.0)));
+       }
+      else
+       {
+         dsc->stats[dsc_kurt] = dsc->stats[dsc_sekurt] = SYSMIS;
+       }
+    }
+
+  display ();
+}
+\f
+/* Statistical display. */
+
+static int compare_func (struct variable ** a, struct variable ** b);
+
+static void
+display (void)
+{
+  int i, j;
+
+  int nc, n_stats;
+  struct tab_table *t;
+
+  /* If op_excl_miss is on, d_glob_valid and (potentially)
+     d_glob_missing are nonzero, and d_glob_missing equals
+     d_glob_miss_list.
+
+     If op_excl_miss is off, d_glob_valid is nonzero.  d_glob_missing
+     is zero.  d_glob_miss_list is (potentially) nonzero.  */
+
+  if (sortby_stat != -2)
+    qsort (v_variables, n_variables, sizeof (struct variable *),
+          (int (*)(const void *, const void *)) compare_func);
+
+  for (nc = i = 0; i < dsc_n_stats; i++)
+    if (stats & BIT_INDEX (i))
+      nc++;
+  n_stats = nc;
+  if (!opt[op_no_varnames])
+    nc++;
+  nc += opt[op_serial] ? 2 : 1;
+
+  t = tab_create (nc, n_variables + 1, 0);
+  tab_headers (t, 1, 0, 1, 0);
+  tab_box (t, TAL_1, TAL_1, -1, -1, 0, 0, nc - 1, n_variables);
+  tab_box (t, -1, -1, -1, TAL_1, 1, 0, nc - 1, n_variables);
+  tab_hline (t, TAL_2, 0, nc - 1, 1);
+  tab_vline (t, TAL_2, 1, 0, n_variables);
+  tab_dim (t, tab_natural_dimensions);
+
+  nc = 0;
+  if (!opt[op_no_varnames])
+    {
+      tab_text (t, nc++, 0, TAB_LEFT | TAT_TITLE, _("Variable"));
+    }
+  if (opt[op_serial])
+    {
+      tab_text (t, nc++, 0, TAB_CENTER | TAT_TITLE, _("Valid N"));
+      tab_text (t, nc++, 0, TAB_CENTER | TAT_TITLE, _("Missing N"));
+    } else {
+      tab_text (t, nc++, 0, TAB_CENTER | TAT_TITLE, "N");
+    }
+
+  for (i = 0; i < dsc_n_stats; i++)
+    if (stats & BIT_INDEX (i))
+      {
+       const char *title = gettext (dsc_info[i].s8);
+       tab_text (t, nc++, 0, TAB_CENTER | TAT_TITLE, title);
+      }
+
+  for (i = 0; i < n_variables; i++)
+    {
+      struct variable *v = v_variables[i];
+
+      nc = 0;
+      if (!opt[op_no_varnames])
+       tab_text (t, nc++, i + 1, TAB_LEFT, v->name);
+      tab_text (t, nc++, i + 1, TAT_PRINTF, "%g", v->p.dsc.valid);
+      if (opt[op_serial])
+       tab_text (t, nc++, i + 1, TAT_PRINTF, "%g", v->p.dsc.miss);
+      for (j = 0; j < dsc_n_stats; j++)
+       if (stats & BIT_INDEX (j))
+         tab_float (t, nc++, i + 1, TAB_NONE, v->p.dsc.stats[j], 10, 3);
+    }
+
+  tab_title (t, 1, _("Valid cases = %g; cases with missing value(s) = %g."),
+            d_glob_valid, d_glob_miss_list);
+
+  tab_submit (t);
+}
+
+static int
+compare_func (struct variable ** a, struct variable ** b)
+{
+  double temp;
+
+  if (cmd.order == DSC_D)
+    {
+      struct variable **t;
+      t = a;
+      a = b;
+      b = t;
+    }
+
+  if (cmd.sortby == DSC_NAME)
+    return strcmp ((*a)->name, (*b)->name);
+  temp = ((*a)->p.dsc.stats[sortby_stat]
+         - (*b)->p.dsc.stats[sortby_stat]);
+  if (temp > 0)
+    return 1;
+  else if (temp < 0)
+    return -1;
+  else
+    return 0;
+}
+
+/*
+   Local variables:
+   mode: c
+   End:
+*/
diff --git a/src/dfm.c b/src/dfm.c
new file mode 100644 (file)
index 0000000..9e9d080
--- /dev/null
+++ b/src/dfm.c
@@ -0,0 +1,718 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "error.h"
+#include "file-handle.h"
+#include "filename.h"
+#include "getline.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+#include "vfm.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* file_handle extension structure. */
+struct dfm_fhuser_ext
+  {
+    struct file_ext file;      /* Associated file. */
+
+    char *line;                        /* Current line, not null-terminated. */
+    size_t len;                        /* Length of line. */
+
+    char *ptr;                 /* Pointer into line that is returned by
+                                  dfm_get_record(). */
+    size_t size;               /* Number of bytes allocated for line. */
+    int advance;               /* Nonzero=dfm_get_record() reads a new
+                                  record; otherwise returns current record. */
+  };
+
+/* These are defined at the end of this file. */
+static struct fh_ext_class dfm_r_class;
+static struct fh_ext_class dfm_w_class;
+
+static void read_record (struct file_handle *h);
+\f
+/* Internal (low level). */
+
+/* Closes the file handle H which was opened by open_file_r() or
+   open_file_w(). */
+static void
+dfm_close (struct file_handle *h)
+{
+  struct dfm_fhuser_ext *ext = h->ext;
+
+  /* Skip any remaining data on the inline file. */
+  if (h == inline_file)
+    while (ext->line != NULL)
+      read_record (h);
+      
+  msg (VM (2), _("%s: Closing data-file handle %s."),
+       fh_handle_filename (h), fh_handle_name (h));
+  assert (h->class == &dfm_r_class || h->class == &dfm_w_class);
+  if (ext->file.file)
+    {
+      fn_close_ext (&ext->file);
+      free (ext->file.filename);
+      ext->file.filename = NULL;
+    }
+  free (ext->line);
+  free (ext);
+}
+
+/* Initializes EXT properly as an inline data file. */
+static void
+open_inline_file (struct dfm_fhuser_ext *ext)
+{
+  /* We want to indicate that the file is open, that we are not at
+     eof, and that another line needs to be read in.  */
+#if __CHECKER__
+  memset (&ext->file, 0, sizeof ext->file);
+#endif
+  ext->file.file = NULL;
+  ext->line = xmalloc (128);
+#if !PRODUCTION
+  strcpy (ext->line, _("<<Bug in dfm.c>>"));
+#endif
+  ext->len = strlen (ext->line);
+  ext->ptr = ext->line;
+  ext->size = 128;
+  ext->advance = 1;
+}
+
+/* Opens a file handle for reading as a data file. */
+static int
+open_file_r (struct file_handle *h)
+{
+  struct dfm_fhuser_ext ext;
+
+  h->where.line_number = 0;
+  ext.file.file = NULL;
+  ext.line = NULL;
+  ext.len = 0;
+  ext.ptr = NULL;
+  ext.size = 0;
+  ext.advance = 0;
+
+  msg (VM (1), _("%s: Opening data-file handle %s for reading."),
+       fh_handle_filename (h), fh_handle_name (h));
+  
+  assert (h != NULL);
+  if (h == inline_file)
+    {
+      char *s;
+
+      /* WTF can't this just be done with tokens?
+        Is this really a special case?
+        FIXME! */
+      do
+       {
+         char *cp;
+
+         if (!getl_read_line ())
+           {
+             msg (SE, _("BEGIN DATA expected."));
+             err_failure ();
+           }
+
+         /* Skip leading whitespace, separate out first word, so that
+            S points to a single word reduced to lowercase. */
+         s = ds_value (&getl_buf);
+         while (isspace ((unsigned char) *s))
+           s++;
+         for (cp = s; isalpha ((unsigned char) *cp); cp++)
+           *cp = tolower ((unsigned char) (*cp));
+         ds_truncate (&getl_buf, cp - s);
+       }
+      while (*s == '\0');
+
+      if (!lex_id_match_len ("begin", 5, s, strcspn (s, " \t\r\v\n")))
+       {
+         msg (SE, _("BEGIN DATA expected."));
+         err_cond_fail ();
+         lex_preprocess_line ();
+         return 0;
+       }
+      getl_prompt = GETL_PRPT_DATA;
+
+      open_inline_file (&ext);
+    }
+  else
+    {
+      ext.file.filename = xstrdup (h->norm_fn);
+      ext.file.mode = "rb";
+      ext.file.file = NULL;
+      ext.file.sequence_no = NULL;
+      ext.file.param = NULL;
+      ext.file.postopen = NULL;
+      ext.file.preclose = NULL;
+      if (!fn_open_ext (&ext.file))
+       {
+         msg (ME, _("An error occurred while opening \"%s\" for reading "
+              "as a data file: %s."), h->fn, strerror (errno));
+         err_cond_fail ();
+         return 0;
+       }
+    }
+
+  h->class = &dfm_r_class;
+  h->ext = xmalloc (sizeof (struct dfm_fhuser_ext));
+  memcpy (h->ext, &ext, sizeof (struct dfm_fhuser_ext));
+
+  return 1;
+}
+
+/* Opens a file handle for writing as a data file. */
+static int
+open_file_w (struct file_handle *h)
+{
+  struct dfm_fhuser_ext ext;
+  
+  ext.file.file = NULL;
+  ext.line = NULL;
+  ext.len = 0;
+  ext.ptr = NULL;
+  ext.size = 0;
+  ext.advance = 0;
+
+  h->where.line_number = 0;
+
+  msg (VM (1), _("%s: Opening data-file handle %s for writing."),
+       fh_handle_filename (h), fh_handle_name (h));
+  
+  assert (h != NULL);
+  if (h == inline_file)
+    {
+      msg (ME, _("Cannot open the inline file for writing."));
+      err_cond_fail ();
+      return 0;
+    }
+
+  ext.file.filename = xstrdup (h->norm_fn);
+  ext.file.mode = "wb";
+  ext.file.file = NULL;
+  ext.file.sequence_no = NULL;
+  ext.file.param = NULL;
+  ext.file.postopen = NULL;
+  ext.file.preclose = NULL;
+      
+  if (!fn_open_ext (&ext.file))
+    {
+      msg (ME, _("An error occurred while opening \"%s\" for writing "
+          "as a data file: %s."), h->fn, strerror (errno));
+      err_cond_fail ();
+      return 0;
+    }
+
+  h->class = &dfm_w_class;
+  h->ext = xmalloc (sizeof (struct dfm_fhuser_ext));
+  memcpy (h->ext, &ext, sizeof (struct dfm_fhuser_ext));
+
+  return 1;
+}
+
+/* Ensures that the line buffer in file handle with extension EXT is
+   big enough to hold a line of length EXT->LEN characters not
+   including null terminator. */
+#define force_line_buffer_expansion()                          \
+       do                                                      \
+         {                                                     \
+           if (ext->len + 1 > ext->size)                       \
+             {                                                 \
+               ext->size = ext->len * 2;                       \
+               ext->line = xrealloc (ext->line, ext->size);    \
+             }                                                 \
+         }                                                     \
+       while (0)
+
+/* Counts the number of tabs in string STRING of length LEN. */
+static inline int
+count_tabs (char *s, size_t len)
+{
+  int n_tabs = 0;
+  
+  for (;;)
+    {
+      char *cp = memchr (s, '\t', len);
+      if (cp == NULL)
+       return n_tabs;
+      n_tabs++;
+      len -= cp - s + 1;
+      s = cp + 1;
+    }
+}
+   
+/* Converts all the tabs in H->EXT->LINE to an equivalent number of
+   spaces, if necessary. */
+static void
+tabs_to_spaces (struct file_handle *h)
+{
+  struct dfm_fhuser_ext *ext = h->ext;
+  
+  char *first_tab;             /* Location of first tab (if any). */
+  char *second_tab;            /* Location of second tab (if any). */
+  size_t orig_len;     /* Line length at function entry. */
+
+  /* If there aren't any tabs then there's nothing to do. */
+  first_tab = memchr (ext->line, '\t', ext->len);
+  if (first_tab == NULL)
+    return;
+  orig_len = ext->len;
+  
+  /* If there's just one tab then expand it inline.  Otherwise do a
+     full string copy to another buffer. */
+  second_tab = memchr (first_tab + 1, '\t',
+                      ext->len - (first_tab - ext->line + 1));
+  if (second_tab == NULL)
+    {
+      int n_spaces = 8 - (first_tab - ext->line) % 8;
+
+      ext->len += n_spaces - 1;
+
+      /* Expand the line if necessary, keeping the first_tab pointer
+         valid. */
+      {
+       size_t ofs = first_tab - ext->line;
+       force_line_buffer_expansion ();
+       first_tab = ext->line + ofs;
+      }
+      
+      memmove (first_tab + n_spaces, first_tab + 1,
+              orig_len - (first_tab - ext->line + 1));
+      memset (first_tab, ' ', n_spaces);
+    } else {
+      /* Make a local copy of original text. */
+      char *orig_line = local_alloc (ext->len + 1);
+      memcpy (orig_line, ext->line, ext->len);
+             
+      /* Allocate memory assuming we need to add 8 spaces for every tab. */
+      ext->len += 2 + count_tabs (second_tab + 1,
+                                 ext->len - (second_tab - ext->line + 1));
+      
+      /* Expand the line if necessary, keeping the first_tab pointer
+         valid. */
+      {
+       size_t ofs = first_tab - ext->line;
+       force_line_buffer_expansion ();
+       first_tab = ext->line + ofs;
+      }
+
+      /* Walk through orig_line, expanding tabs into ext->line. */
+      {
+       char *src_p = orig_line + (first_tab - ext->line);
+       char *dest_p = first_tab;
+
+       for (; src_p < orig_line + orig_len; src_p++)
+         {
+           /* Most characters simply pass through untouched. */
+           if (*src_p != '\t')
+             {
+               *dest_p++ = *src_p;
+               continue;
+             }
+
+           /* Tabs are expanded into an equivalent number of
+               spaces. */
+           {
+             int n_spaces = 8 - (dest_p - ext->line) % 8;
+
+             memset (dest_p, ' ', n_spaces);
+             dest_p += n_spaces;
+           }
+         }
+
+       /* Supply null terminator and actual string length. */
+       *dest_p = 0;
+       ext->len = dest_p - ext->line;
+      }
+
+      local_free (orig_line);
+    }
+}
+
+/* Reads a record from H->EXT->FILE into H->EXT->LINE, setting
+   H->EXT->PTR to H->EXT->LINE, and setting H->EXT-LEN to the length
+   of the line.  The line is not null-terminated.  If an error occurs
+   or end-of-file is encountered, H->EXT->LINE is set to NULL. */
+static void
+read_record (struct file_handle *h)
+{
+  struct dfm_fhuser_ext *ext = h->ext;
+
+  if (h == inline_file)
+    {
+      if (!getl_read_line ())
+       {
+         msg (SE, _("Unexpected end-of-file while reading data in BEGIN "
+              "DATA.  This probably indicates "
+              "a missing or misformatted END DATA command.  "
+              "END DATA must appear by itself on a single line "
+              "with exactly one space between words."));
+         err_failure ();
+       }
+
+      h->where.line_number++;
+
+      if (ds_length (&getl_buf) >= 8
+         && !strncasecmp (ds_value (&getl_buf), "end data", 8))
+       {
+         lex_set_prog (ds_value (&getl_buf) + ds_length (&getl_buf));
+         goto eof;
+       }
+
+      ext->len = ds_length (&getl_buf);
+      force_line_buffer_expansion ();
+      strcpy (ext->line, ds_value (&getl_buf));
+    }
+  else
+    {
+      if (h->recform == FH_RF_VARIABLE)
+       {
+         /* PORTME: here you should adapt the routine to your
+            system's concept of a "line" of text. */
+         int read_len = getline (&ext->line, &ext->size, ext->file.file);
+
+         if (read_len == -1)
+           {
+             if (ferror (ext->file.file))
+               {
+                 msg (ME, _("Error reading file %s: %s."),
+                      fh_handle_name (h), strerror (errno));
+                 err_cond_fail ();
+               }
+             goto eof;
+           }
+         ext->len = (size_t) read_len;
+       }
+      else if (h->recform == FH_RF_FIXED)
+       {
+         size_t amt;
+
+         if (ext->size < h->lrecl)
+           {
+             ext->size = h->lrecl;
+             ext->line = xmalloc (ext->size);
+           }
+         amt = fread (ext->line, 1, h->lrecl, ext->file.file);
+         if (h->lrecl != amt)
+           {
+             if (ferror (ext->file.file))
+               msg (ME, _("Error reading file %s: %s."),
+                    fh_handle_name (h), strerror (errno));
+             else if (amt != 0)
+               msg (ME, _("%s: Partial record at end of file."),
+                    fh_handle_name (h));
+             else
+               goto eof;
+
+             err_cond_fail ();
+             goto eof;
+           }
+       }
+      else
+       assert (0);
+
+      h->where.line_number++;
+    }
+
+  /* Strip trailing whitespace, I forget why.  But there's a good
+     reason, I'm sure.  I'm too scared to eliminate this code.  */
+  if (h->recform == FH_RF_VARIABLE)
+    {
+      while (ext->len && isspace ((unsigned char) ext->line[ext->len - 1]))
+       ext->len--;
+
+      /* Convert tabs to spaces. */
+      tabs_to_spaces (h);
+               
+      ext->ptr = ext->line;
+    }
+  return;
+
+eof:
+  /*hit eof or an error, clean up everything. */
+  if (ext->line)
+    free (ext->line);
+  ext->size = 0;
+  ext->line = ext->ptr = NULL;
+  return;
+}
+\f
+/* Public (high level). */
+
+/* Returns the current record in the file corresponding to HANDLE.
+   Opens files and reads records, etc., as necessary.  Sets *LEN to
+   the length of the line.  The line returned is not null-terminated.
+   Returns NULL at end of file.  Calls fail() on attempt to read past
+   end of file.  */
+char *
+dfm_get_record (struct file_handle *h, int *len)
+{
+  if (h->class == NULL)
+    {
+      if (!open_file_r (h))
+       return NULL;
+      read_record (h);
+    }
+  else if (h->class != &dfm_r_class)
+    {
+      msg (ME, _("Cannot read from file %s already opened for %s."),
+          fh_handle_name (h), gettext (h->class->name));
+      goto lossage;
+    }
+  else
+    {
+      struct dfm_fhuser_ext *ext = h->ext;
+
+      if (ext->advance)
+       {
+         if (ext->line)
+           read_record (h);
+         else
+           {
+             msg (SE, _("Attempt to read beyond end-of-file on file %s."),
+                  fh_handle_name (h));
+             goto lossage;
+           }
+       }
+    }
+
+  {
+    struct dfm_fhuser_ext *ext = h->ext;
+
+    if (ext)
+      {
+       ext->advance = 0;
+       if (len)
+         *len = ext->len - (ext->ptr - ext->line);
+       return ext->ptr;
+      }
+  }
+
+  return NULL;
+
+lossage:
+  /* Come here on reading beyond eof or reading from a file already
+     open for something else. */
+  err_cond_fail ();
+
+  return NULL;
+}
+
+/* Causes dfm_get_record() to read in the next record the next time it
+   is executed on file HANDLE. */
+void
+dfm_fwd_record (struct file_handle *h)
+{
+  struct dfm_fhuser_ext *ext = h->ext;
+
+  assert (h->class == &dfm_r_class);
+  ext->advance = 1;
+}
+
+/* Cancels the effect of any previous dfm_fwd_record() executed on
+   file HANDLE.  Sets the current line to begin in the 1-based column
+   COLUMN, as with dfm_set_record but based on a column number instead
+   of a character pointer. */
+void
+dfm_bkwd_record (struct file_handle *h, int column)
+{
+  struct dfm_fhuser_ext *ext = h->ext;
+
+  assert (h->class == &dfm_r_class);
+  ext->advance = 0;
+  ext->ptr = ext->line + min ((int) ext->len + 1, column) - 1;
+}
+
+/* Sets the current line in HANDLE to NEW_LINE, which must point
+   somewhere in the line last returned by dfm_get_record().  Used by
+   DATA LIST FREE to strip the leading portion off the current line.  */
+void
+dfm_set_record (struct file_handle *h, char *new_line)
+{
+  struct dfm_fhuser_ext *ext = h->ext;
+
+  assert (h->class == &dfm_r_class);
+  ext->ptr = new_line;
+}
+
+/* Returns the 0-based current column to which the line pointer in
+   HANDLE is set.  Unless dfm_set_record() or dfm_bkwd_record() have
+   been called, this is 0. */
+int
+dfm_get_cur_col (struct file_handle *h)
+{
+  struct dfm_fhuser_ext *ext = h->ext;
+
+  assert (h->class == &dfm_r_class);
+  return ext->ptr - ext->line;
+}
+
+/* Writes record REC having length LEN to the file corresponding to
+   HANDLE.  REC is not null-terminated.  Returns nonzero on success,
+   zero on failure. */
+int
+dfm_put_record (struct file_handle *h, const char *rec, size_t len)
+{
+  char *ptr;
+  size_t amt;
+
+  if (h->class == NULL)
+    {
+      if (!open_file_w (h))
+       return 0;
+    }
+  else if (h->class != &dfm_w_class)
+    {
+      msg (ME, _("Cannot write to file %s already opened for %s."),
+          fh_handle_name (h), gettext (h->class->name));
+      err_cond_fail ();
+      return 0;
+    }
+
+  if (h->recform == FH_RF_FIXED && len < h->lrecl)
+    {
+      int ch;
+
+      amt = h->lrecl;
+      ptr = local_alloc (amt);
+      memcpy (ptr, rec, len);
+      ch = h->mode == FH_MD_CHARACTER ? ' ' : 0;
+      memset (&ptr[len], ch, amt - len);
+    }
+  else
+    {
+      ptr = (char *) rec;
+      amt = len;
+    }
+
+  if (1 != fwrite (ptr, amt, 1, ((struct dfm_fhuser_ext *) h->ext)->file.file))
+    {
+      msg (ME, _("Error writing file %s: %s."), fh_handle_name (h),
+          strerror (errno));
+      err_cond_fail ();
+      return 0;
+    }
+
+  if (ptr != rec)
+    local_free (ptr);
+
+  return 1;
+}
+
+/* Pushes the filename and line number on the fn/ln stack. */
+void
+dfm_push (struct file_handle *h)
+{
+  if (h != inline_file)
+    err_push_file_locator (&h->where);
+}
+
+/* Pops the filename and line number from the fn/ln stack. */
+void
+dfm_pop (struct file_handle *h)
+{
+  if (h != inline_file)
+    err_pop_file_locator (&h->where);
+}
+\f
+/* BEGIN DATA...END DATA procedure. */
+
+/* Perform BEGIN DATA...END DATA as a procedure in itself. */
+int
+cmd_begin_data (void)
+{
+  struct dfm_fhuser_ext *ext;
+
+  /* FIXME: figure out the *exact* conditions, not these really
+     lenient conditions. */
+  if (vfm_source == NULL
+      || vfm_source == &vfm_memory_stream
+      || vfm_source == &vfm_disk_stream
+      || vfm_source == &sort_stream)
+    {
+      msg (SE, _("This command is not valid here since the current "
+          "input program does not access the inline file."));
+      err_cond_fail ();
+      return CMD_FAILURE;
+    }
+
+  /* Initialize inline_file. */
+  msg (VM (1), _("inline file: Opening for reading."));
+  inline_file->class = &dfm_r_class;
+  inline_file->ext = xmalloc (sizeof (struct dfm_fhuser_ext));
+  open_inline_file (inline_file->ext);
+
+  /* We don't actually read from the inline file.  The input procedure
+     is what reads from it. */
+  getl_prompt = GETL_PRPT_DATA;
+  procedure (NULL, NULL, NULL);
+
+  ext = inline_file->ext;
+
+  if (ext && ext->line)
+    {
+      msg (MW, _("Skipping remaining inline data."));
+      for (read_record (inline_file); ext->line; read_record (inline_file))
+       ;
+    }
+  assert (inline_file->ext == NULL);
+
+  return CMD_SUCCESS;
+}
+
+static struct fh_ext_class dfm_r_class =
+{
+  1,
+  N_("reading as a data file"),
+  dfm_close,
+};
+
+static struct fh_ext_class dfm_w_class =
+{
+  2,
+  N_("writing as a data file"),
+  dfm_close,
+};
diff --git a/src/dfm.h b/src/dfm.h
new file mode 100644 (file)
index 0000000..ef91569
--- /dev/null
+++ b/src/dfm.h
@@ -0,0 +1,44 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !dfm_h
+#define dfm_h 1
+
+/* Data file manager (dfm).
+
+   This module is in charge of reading and writing data files (other
+   than system files).  dfm is an fhuser, so see file-handle.h for the
+   fhuser interface. */
+
+/* I/O utilities. */
+struct file_handle;
+char *dfm_get_record (struct file_handle *handle, int *len);
+int dfm_put_record (struct file_handle *handle, const char *rec, size_t len);
+
+/* Motion control. */
+void dfm_fwd_record (struct file_handle *handle);
+void dfm_bkwd_record (struct file_handle *handle, int column);
+
+/* Weirdness. */
+void dfm_set_record (struct file_handle *handle, char *new_line);
+int dfm_get_cur_col (struct file_handle *handle);
+void dfm_push (struct file_handle *handle);
+void dfm_pop (struct file_handle *handle);
+
+#endif /* dfm_h */
diff --git a/src/do-if.c b/src/do-if.c
new file mode 100644 (file)
index 0000000..b42e0eb
--- /dev/null
@@ -0,0 +1,333 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "error.h"
+#include "expr.h"
+#include "lexer.h"
+#include "str.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+#if DEBUGGING
+#include <stdio.h>
+#endif
+
+/* *INDENT-OFF* */
+/* Description of DO IF transformations:
+
+   DO IF has two transformations.  One is a conditional jump around
+   a false condition.  The second is an unconditional jump around
+   the rest of the code after a true condition.  Both of these types
+   have their destinations backpatched in by the next clause (ELSE IF,
+   END IF).
+
+   The characters `^V<>' are meant to represent arrows.
+
+   1. DO IF
+ V<<<<if false
+ V
+ V *. Transformations executed when the condition on DO IF is true.
+ V
+ V 2. GOTO>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>V
+ V                                                                    V
+ >>1. ELSE IF                                                         V
+ V<<<<if false                                                        V
+ V                                                                    V
+ V *. Transformations executed when condition on 1st ELSE IF is true.  V
+ V                                                                    V
+ V 2. GOTO>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>V
+ V                                                                    V
+ >>1. ELSE IF                                                         V
+ V<<<<if false                                                        V
+ V                                                                    V
+ V *. Transformations executed when condition on 2nd ELSE IF is true.  V
+ V                                                                    V
+ V 2. GOTO>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>V
+ V                                                                    V
+ >>*. Transformations executed when no condition is true. (ELSE)       V
+                                                                      V
+   *. Transformations after DO IF structure.<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+
+*/
+/* *INDENT-ON* */
+
+#include "do-ifP.h"
+
+static struct do_if_trns *parse_do_if (void);
+static void add_ELSE_IF (struct do_if_trns *);
+static int goto_trns_proc (struct trns_header *, struct ccase *);
+static int do_if_trns_proc (struct trns_header *, struct ccase *);
+static void do_if_trns_free (struct trns_header *);
+
+/* Parse DO IF. */
+int
+cmd_do_if (void)
+{
+  struct do_if_trns *t;
+
+  /* Parse the transformation. */
+  t = parse_do_if ();
+  if (!t)
+    return CMD_FAILURE;
+
+  /* Finish up the transformation, add to control stack, add to
+     transformation list. */
+  t->brk = NULL;
+  t->ctl.type = CST_DO_IF;
+  t->ctl.down = ctl_stack;
+  t->ctl.trns = (struct trns_header *) t;
+  t->ctl.brk = NULL;
+  t->has_else = 0;
+  ctl_stack = &t->ctl;
+  add_transformation ((struct trns_header *) t);
+
+  return CMD_SUCCESS;
+}
+
+/* Parse ELSE IF. */
+int
+cmd_else_if (void)
+{
+  /* Transformation created. */
+  struct do_if_trns *t;
+
+  /* Check that we're in a pleasing situation. */
+  if (!ctl_stack || ctl_stack->type != CST_DO_IF)
+    {
+      msg (SE, _("There is no DO IF to match with this ELSE IF."));
+      return CMD_FAILURE;
+    }
+  if (((struct do_if_trns *) ctl_stack->trns)->has_else)
+    {
+      msg (SE, _("The ELSE command must follow all ELSE IF commands "
+                "in a DO IF structure."));
+      return CMD_FAILURE;
+    }
+
+  /* Parse the transformation. */
+  t = parse_do_if ();
+  if (!t)
+    return CMD_FAILURE;
+
+  /* Stick in the breakout transformation. */
+  t->brk = xmalloc (sizeof *t->brk);
+  t->brk->h.proc = goto_trns_proc;
+  t->brk->h.free = NULL;
+
+  /* Add to list of transformations, add to string of ELSE IFs. */
+  add_transformation ((struct trns_header *) t->brk);
+  add_transformation ((struct trns_header *) t);
+
+  add_ELSE_IF (t);
+
+  if (token != '.')
+    {
+      msg (SE, _("End of command expected."));
+      return CMD_TRAILING_GARBAGE;
+    }
+
+  return CMD_SUCCESS;
+}
+
+/* Parse ELSE. */
+int
+cmd_else (void)
+{
+  struct do_if_trns *t;
+
+  lex_match_id ("ELSE");
+
+  /* Check that we're in a pleasing situation. */
+  if (!ctl_stack || ctl_stack->type != CST_DO_IF)
+    {
+      msg (SE, _("There is no DO IF to match with this ELSE."));
+      return CMD_FAILURE;
+    }
+  
+  if (((struct do_if_trns *) ctl_stack->trns)->has_else)
+    {
+      msg (SE, _("There may be at most one ELSE clause in each DO IF "
+                "structure.  It must be the last clause."));
+      return CMD_FAILURE;
+    }
+
+  /* Note that the ELSE transformation is *not* added to the list of
+     transformations.  That's because it doesn't need to do anything.
+     Its goto transformation *is* added, because that's necessary.
+     The main DO IF do_if_trns is the destructor for this ELSE
+     do_if_trns. */
+  t = xmalloc (sizeof *t);
+  t->next = NULL;
+  t->brk = xmalloc (sizeof *t->brk);
+  t->brk->h.proc = goto_trns_proc;
+  t->brk->h.free = NULL;
+  t->cond = NULL;
+  add_transformation ((struct trns_header *) t->brk);
+  t->h.index = t->brk->h.index + 1;
+
+  /* Add to string of ELSE IFs. */
+  add_ELSE_IF (t);
+
+  return lex_end_of_command ();
+}
+
+/* Parse END IF. */
+int
+cmd_end_if (void)
+{
+  /* List iterator. */
+  struct do_if_trns *iter;
+
+  lex_match_id ("IF");
+
+  /* Check that we're in a pleasing situation. */
+  if (!ctl_stack || ctl_stack->type != CST_DO_IF)
+    {
+      msg (SE, _("There is no DO IF to match with this END IF."));
+      return CMD_FAILURE;
+    }
+
+  /* Chain down the list, backpatching destinations for gotos. */
+  iter = (struct do_if_trns *) ctl_stack->trns;
+  for (;;)
+    {
+      if (iter->brk)
+       iter->brk->dest = n_trns;
+      iter->missing_jump = n_trns;
+      if (iter->next)
+       iter = iter->next;
+      else
+       break;
+    }
+  iter->false_jump = n_trns;
+
+  /* Pop control stack. */
+  ctl_stack = ctl_stack->down;
+
+  return lex_end_of_command ();
+}
+
+/* Adds an ELSE IF or ELSE to the chain of them that hangs off the
+   main DO IF. */
+static void
+add_ELSE_IF (struct do_if_trns * t)
+{
+  /* List iterator. */
+  struct do_if_trns *iter;
+
+  iter = (struct do_if_trns *) ctl_stack->trns;
+  while (iter->next)
+    iter = iter->next;
+  assert (iter != NULL);
+
+  iter->next = t;
+  iter->false_jump = t->h.index;
+}
+
+/* Parses a DO IF or ELSE IF command and returns a pointer to a mostly
+   filled in transformation. */
+static struct do_if_trns *
+parse_do_if (void)
+{
+  struct do_if_trns *t;
+  struct expression *e;
+
+  lex_match_id ("IF");
+
+  e = expr_parse (PXP_BOOLEAN);
+  if (!e)
+    return NULL;
+  if (token != '.')
+    {
+      expr_free (e);
+      lex_error (_("expecting end of command"));
+      return NULL;
+    }
+
+  t = xmalloc (sizeof *t);
+  t->h.proc = do_if_trns_proc;
+  t->h.free = do_if_trns_free;
+  t->next = NULL;
+  t->cond = e;
+
+  return t;
+}
+
+/* Executes a goto transformation. */
+static int 
+goto_trns_proc (struct trns_header * t, struct ccase * c unused)
+{
+  return ((struct goto_trns *) t)->dest;
+}
+
+static int 
+do_if_trns_proc (struct trns_header * trns, struct ccase * c)
+{
+  struct do_if_trns *t = (struct do_if_trns *) trns;
+  union value bool;
+
+  expr_evaluate (t->cond, c, &bool);
+  if (bool.f == 1.0)
+    {
+      debug_printf ((_("DO IF %d: true\n"), t->h.index));
+      return -1;
+    }
+  else if (bool.f == 0.0)
+    {
+      debug_printf ((_("DO IF %d: false\n"), t->h.index));
+      return t->false_jump;
+    }
+  else
+    {
+      debug_printf ((_("DO IF %d: missing\n"), t->h.index));
+      return t->missing_jump;
+    }
+}
+
+static void 
+do_if_trns_free (struct trns_header * trns)
+{
+  struct do_if_trns *t = (struct do_if_trns *) trns;
+  expr_free (t->cond);
+
+  /* If brk is NULL then this is the main DO IF; therefore we
+     need to chain down to the ELSE and delete it. */
+  if (t->brk == NULL)
+    {
+      struct do_if_trns *iter = t->next;
+      while (iter)
+       {
+         if (!iter->cond)
+           {
+             /* This is the ELSE. */
+             free (iter);
+             break;
+           }
+         iter = iter->next;
+       }
+    }
+}
diff --git a/src/do-ifP.h b/src/do-ifP.h
new file mode 100644 (file)
index 0000000..6ea1b94
--- /dev/null
@@ -0,0 +1,83 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !do_ifP_h
+#define do_ifP_h 1
+
+#include "var.h"
+
+/* BREAK transformation. */
+struct break_trns
+  {
+    struct trns_header h;
+
+    struct break_trns *next;   /* Next in chain of BREAKs associated
+                                  with a single LOOP. */
+    int loop_term;             /* t_trns[] index to jump to; backpatched
+                                  in by END LOOP. */
+  };
+
+/* Types of control structures. */
+enum
+  {
+    CST_LOOP,
+    CST_DO_IF
+  };
+
+/* Control structure info. */
+struct ctl_stmt
+  {
+    int type;                  /* One of CST_*. */
+    struct ctl_stmt *down;     /* Points toward the bottom of ctl_stack. */
+    struct trns_header *trns;  /* Associated transformation. */
+    struct break_trns *brk;    /* (LOOP only): Chain of associated BREAKs. */
+  };                           /* ctl_stmt */
+
+/* Goto transformation. */
+struct goto_trns
+  {
+    struct trns_header h;
+
+    int dest;                  /* t_trns[] index of destination of jump. */
+  };
+
+/* DO IF/ELSE IF/ELSE transformation. */
+struct do_if_trns
+  {
+    struct trns_header h;
+
+    struct ctl_stmt ctl;       /* DO IF: Control information for nesting. */
+
+    /* Keeping track of clauses. */
+    struct do_if_trns *next;   /* Points toward next ELSE IF. */
+    struct goto_trns *brk;     /* ELSE IF: jumps out of DO IF structure. */
+    int has_else;              /* DO IF: 1=there's been an ELSE. */
+
+    /* Runtime info. */
+    struct expression *cond;   /* Condition. */
+    int false_jump;            /* t_trns[] index of destination when false. */
+    int missing_jump;          /* t_trns[] index to break out of DO IF. */
+  };
+
+/* Top of the control structure stack. */
+extern struct ctl_stmt *ctl_stack;
+
+void discard_ctl_stack (void);
+
+#endif /* !do_ifP_h */
diff --git a/src/error.c b/src/error.c
new file mode 100644 (file)
index 0000000..1983df4
--- /dev/null
@@ -0,0 +1,517 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "error.h"
+#include "getline.h"
+#include "main.h"
+#include "output.h"
+#include "settings.h"
+#include "str.h"
+#include "var.h"
+
+int err_error_count;
+int err_warning_count;
+
+int err_already_flagged;
+
+int err_verbosity;
+
+/* File locator stack. */
+static const struct file_locator **file_loc;
+static int nfile_loc, mfile_loc;
+\f
+/* Fairly common public functions. */
+
+/* Writes error message in CLASS, with title TITLE and text FORMAT,
+   formatted with printf, to the standard places. */
+void
+tmsg (int class, const char *title, const char *format, ...)
+{
+  char buf[1024];
+  
+  /* Format the message into BUF. */
+  {
+    va_list args;
+
+    va_start (args, format);
+    vsnprintf (buf, 1024, format, args);
+    va_end (args);
+  }
+  
+  /* Output the message. */
+  {
+    struct error e;
+
+    e.class = class;
+    err_location (&e.where);
+    e.title = title;
+    e.text = buf;
+    err_vmsg (&e);
+  }
+}
+
+/* Writes error message in CLASS, with text FORMAT, formatted with
+   printf, to the standard places. */
+void
+msg (int class, const char *format, ...)
+{
+  char buf[1024];
+  
+  /* Format the message into BUF. */
+  {
+    va_list args;
+
+    va_start (args, format);
+    vsnprintf (buf, 1024, format, args);
+    va_end (args);
+  }
+  
+  /* Output the message. */
+  {
+    struct error e;
+
+    e.class = class;
+    err_location (&e.where);
+    e.title = NULL;
+    e.text = buf;
+    err_vmsg (&e);
+  }
+}
+
+/* Terminate due to fatal error in input. */
+void
+err_failure (void)
+{
+  fflush (stdout);
+  fflush (stderr);
+
+  fprintf (stderr, "%s: %s\n", pgmname,
+          _("Terminating NOW due to a fatal error!"));
+
+  err_hcf (0);
+}
+
+/* Terminate unless we're interactive or will go interactive when the
+   file is over with. */
+void
+err_cond_fail (void)
+{
+  if (getl_reading_script)
+    {
+      if (getl_interactive)
+       getl_close_all ();
+      else
+       err_failure ();
+    }
+}
+\f
+/* File locator stack functions. */
+
+/* Pushes F onto the stack of file locations. */
+void
+err_push_file_locator (const struct file_locator *f)
+{
+  if (nfile_loc >= mfile_loc)
+    {
+      if (mfile_loc == 0)
+       mfile_loc = 8;
+      else
+       mfile_loc *= 2;
+
+      file_loc = xrealloc (file_loc, mfile_loc * sizeof *file_loc);
+    }
+
+  file_loc[nfile_loc++] = f;
+}
+
+/* Pops F off the stack of file locations.
+   Argument F is only used for verification that that is actually the
+   item on top of the stack. */
+void
+err_pop_file_locator (const struct file_locator *f)
+{
+  assert (nfile_loc >= 0 && file_loc[nfile_loc - 1] == f);
+  nfile_loc--;
+}
+
+/* Puts the current file and line number in F, or NULL and -1 if
+   none. */
+void
+err_location (struct file_locator *f)
+{
+  if (nfile_loc)
+    *f = *file_loc[nfile_loc - 1];
+  else
+    getl_location (&f->filename, &f->line_number);
+}
+\f
+/* Obscure public functions. */
+
+/* Writes a blank line to the error device(s).
+   FIXME: currently a no-op. */
+void
+err_break (void)
+{
+}
+
+/* Checks whether we've had so many errors that it's time to quit
+   processing this syntax file.  If so, then take appropriate
+   action. */
+void
+err_check_count (void)
+{
+  int error_class = getl_interactive ? MM : FE;
+
+  if (set_errorbreak && err_error_count)
+    msg (error_class, _("Terminating execution of syntax file due to error."));
+  else if (err_error_count > set_mxerrs)
+    msg (error_class, _("Errors (%d) exceeds limit (%d)."),
+        err_error_count, set_mxerrs);
+  else if (err_error_count + err_warning_count > set_mxwarns)
+    msg (error_class, _("Warnings (%d) exceed limit (%d)."),
+        err_error_count + err_warning_count, set_mxwarns);
+  else
+    return;
+
+  getl_close_all ();
+}
+
+#if __CHECKER__
+static void induce_segfault (void);
+#endif
+
+/* Some machines are broken.  Compensate. */
+#ifndef EXIT_SUCCESS
+#define EXIT_SUCCESS 0
+#endif
+
+#ifndef EXIT_FAILURE
+#define EXIT_FAILURE 1
+#endif
+
+static int terminating;
+
+/* Halt-catch-fire.  SUCCESS should be nonzero if exiting successfully
+   or zero if not.  Despite the name, this is the usual way to finish,
+   successfully or not. */
+void
+err_hcf (int success)
+{
+  terminating = 1;
+
+  getl_uninitialize ();
+
+  outp_done ();
+
+#if __CHECKER__
+  if (!success)
+    induce_segfault ();
+#endif
+
+  exit (success ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+static void puts_stdout (const char *s);
+static void dump_message (char *errbuf, unsigned indent,
+                         void (*func) (const char *), unsigned width);
+
+void
+err_vmsg (const struct error *e)
+{
+  /* Class flags. */
+  enum
+    {
+      ERR_IN_PROCEDURE = 01,   /* 1=Display name of current procedure. */
+      ERR_WITH_FILE = 02,      /* 1=Display filename and line number. */
+    };
+
+  /* Describes one class of error. */
+  struct error_class
+    {
+      int flags;               /* Zero or more of MSG_*. */
+      int *count;              /* Counting category. */
+      const char *banner;      /* Banner. */
+    };
+
+  static const struct error_class error_classes[ERR_CLASS_COUNT] =
+    {
+      {0, NULL, N_("fatal")},                  /* FE */
+
+      {3, &err_error_count, N_("error")},      /* SE */
+      {3, &err_warning_count, N_("warning")},  /* SW */
+      {3, NULL, N_("note")},                   /* SM */
+
+      {0, NULL, N_("installation error")},     /* IE */
+      {2, NULL, N_("installation error")},     /* IS */
+
+      {2, NULL, N_("error")},                  /* DE */
+      {2, NULL, N_("warning")},                        /* DW */
+
+      {0, NULL, N_("error")},                  /* ME */
+      {0, NULL, N_("warning")},                        /* MW */
+      {0, NULL, N_("note")},                   /* MM */
+    };
+
+  struct string msg;
+  int class;
+
+  /* Check verbosity level. */
+  class = e->class;
+  if (((class >> ERR_VERBOSITY_SHIFT) & ERR_VERBOSITY_MASK) > err_verbosity)
+    return;
+  class &= ERR_CLASS_MASK;
+  
+  assert (class >= 0 && class < ERR_CLASS_COUNT);
+  assert (e->text != NULL);
+  
+  ds_init (NULL, &msg, 64);
+  if (e->where.filename && (error_classes[class].flags & ERR_WITH_FILE))
+    {
+      ds_printf (&msg, "%s:", e->where.filename);
+      if (e->where.line_number != -1)
+       ds_printf (&msg, "%d:", e->where.line_number);
+      ds_putchar (&msg, ' ');
+    }
+
+  ds_printf (&msg, "%s: ", gettext (error_classes[class].banner));
+  
+  {
+    int *count = error_classes[class].count;
+    if (count)
+      (*count)++;
+  }
+  
+  if (cur_proc && (error_classes[class].flags & ERR_IN_PROCEDURE))
+    ds_printf (&msg, "%s: ", cur_proc);
+
+  if (e->title)
+    ds_concat (&msg, e->title);
+
+  ds_concat (&msg, e->text);
+
+  /* FIXME: Check set_messages and set_errors to determine where to
+     send errors and messages.
+
+     Please note that this is not trivial.  We have to avoid an
+     infinite loop in reporting errors that originate in the output
+     section. */
+  dump_message (ds_value (&msg), 8, puts_stdout, set_viewwidth);
+
+  ds_destroy (&msg);
+
+  if (e->class == FE && !terminating)
+    err_hcf (0);
+}
+\f
+/* Private functions. */
+
+#if 0
+/* Write S followed by a newline to stderr. */
+static void
+puts_stderr (const char *s)
+{
+  fputs (s, stderr);
+  fputc ('\n', stderr);
+}
+#endif
+
+/* Write S followed by a newline to stdout. */
+static void
+puts_stdout (const char *s)
+{
+  puts (s);
+}
+
+/* Returns 1 if C is a `break character', that is, if it is a good
+   place to break a message into lines. */
+static inline int
+char_is_break (int quote, int c)
+{
+  return ((quote && c == DIR_SEPARATOR)
+         || (!quote && (isspace (c) || c == '-' || c == '/')));
+}
+
+/* Returns 1 if C is a break character where the break should be made
+   BEFORE the character. */
+static inline int
+break_before (int quote, int c)
+{
+  return !quote && isspace (c);
+}
+
+/* If C is a break character, returns 1 if the break should be made
+   AFTER the character.  Does not return a meaningful result if C is
+   not a break character. */
+static inline int
+break_after (int quote, int c)
+{
+  return !break_before (quote, c);
+}
+
+/* If you want very long words that occur at a bad break point to be
+   broken into two lines even if they're shorter than a whole line by
+   themselves, define as 2/3, or 4/5, or whatever fraction of a whole
+   line you think is necessary in order to consider a word long enough
+   to break into pieces.  Otherwise, define as 0.  See code to grok
+   the details.  Do NOT parenthesize the expression!  */
+#define BREAK_LONG_WORD 0
+/* #define BREAK_LONG_WORD 2/3 */
+/* #define BREAK_LONG_WORD 4/5 */
+
+/* Divides MSG into lines of WIDTH width for the first line and WIDTH
+   - INDENT width for each succeeding line.  Each line is dumped
+   through FUNC, which may do with the string what it will. */
+static void
+dump_message (char *msg, unsigned indent, void (*func) (const char *),
+             unsigned width)
+{
+  char *cp;
+
+  /* 1 when at a position inside double quotes ("). */
+  int quote = 0;
+
+  /* Buffer for a single line. */
+  char *buf;
+
+  /* If the message is short, just print the full thing. */
+  if (strlen (msg) < width)
+    {
+      func (msg);
+      return;
+    }
+
+  /* Make sure the indent isn't too big relative to the page width. */
+  if (indent > width / 3)
+    indent = width / 3;
+  
+  buf = local_alloc (width + 1);
+
+  /* Advance WIDTH characters into MSG.
+     If that's a valid breakpoint, keep it; otherwise, back up.
+     Output the line. */
+  for (cp = msg; (unsigned) (cp - msg) < width - 1; cp++)
+    if (*cp == '"')
+      quote ^= 1;
+
+  if (break_after (quote, (unsigned char) *cp))
+    {
+      for (cp--; !char_is_break (quote, (unsigned char) *cp) && cp > msg; cp--)
+       if (*cp == '"')
+         quote ^= 1;
+      
+      if (break_after (quote, (unsigned char) *cp))
+       cp++;
+    }
+
+  if (cp <= msg + width * BREAK_LONG_WORD)
+    for (; cp < msg + width - 1; cp++)
+      if (*cp == '"')
+       quote ^= 1;
+  
+  {
+    int c = *cp;
+    *cp = '\0';
+    func (msg);
+    *cp = c;
+  }
+
+  /* Repeat above procedure for remaining lines. */
+  for (;;)
+    {
+      char *cp2;
+
+      /* Advance past whitespace. */
+      while (isspace ((unsigned char) *cp))
+       cp++;
+      if (*cp == 0)
+       break;
+
+      /* Advance WIDTH - INDENT characters. */
+      for (cp2 = cp; (unsigned) (cp2 - cp) < width - indent && *cp2; cp2++)
+       if (*cp2 == '"')
+         quote ^= 1;
+
+      /* Back up if this isn't a breakpoint. */
+      {
+       unsigned w = cp2 - cp;
+       if (*cp2)
+         for (cp2--; !char_is_break (quote, (unsigned char) *cp2) && cp2 > cp;
+              cp2--)
+           if (*cp2 == '"')
+             quote ^= 1;
+
+       if (w == width - indent
+           && (unsigned) (cp2 - cp) <= (width - indent) * BREAK_LONG_WORD)
+         for (; (unsigned) (cp2 - cp) < width - indent && *cp2; cp2++)
+           if (*cp2 == '"')
+             quote ^= 1;
+      }
+
+      /* Write out the line. */
+      memset (buf, ' ', indent);
+      memcpy (&buf[indent], cp, cp2 - cp);
+      buf[indent + cp2 - cp] = '\0';
+      func (buf);
+
+      cp = cp2;
+    }
+
+  local_free (buf);
+}
+
+#if __CHECKER__
+/* Causes a segfault in order to force Checker to print a stack
+   backtrace. */
+static void
+induce_segfault (void)
+{
+  fputs (_("\n"
+          "\t*********************\n"
+          "\t* INDUCING SEGFAULT *\n"
+          "\t*********************\n"), stdout);
+  fflush (stdout);
+  fflush (stderr);
+  abort ();
+}
+#endif
diff --git a/src/error.h b/src/error.h
new file mode 100644 (file)
index 0000000..6eb77a1
--- /dev/null
@@ -0,0 +1,90 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !error_h
+#define error_h 1
+
+#include <stdarg.h>
+
+/* Message classes. */
+enum
+  {
+    FE,                                /* Fatal errors. */
+    SE, SW, SM,                        /* Script error/warning/message. */
+    IE, IS,                    /* Installation error/script error. */
+    DE, DW,                    /* Data-file error/warning. */
+    ME, MW, MM,                        /* General error/warning/message. */
+    ERR_CLASS_COUNT,           /* Number of message classes. */
+    ERR_CLASS_MASK = 0xf,      /* Bitmask for class. */
+    ERR_VERBOSITY_SHIFT = 4,   /* Shift count for verbosity. */
+    ERR_VERBOSITY_MASK = 0xf,  /* Bitmask for verbosity. */
+  };
+
+/* If passed to msg() as CLASS, the return value will cause the message
+   to be displayed only if `verbosity' is at least LEVEL. */
+#define VM(LEVEL) (MM | ((LEVEL) << ERR_VERBOSITY_SHIFT))
+
+/* A file location.  */
+struct file_locator
+  {
+    const char *filename;              /* Filename. */
+    int line_number;                   /* Line number. */
+  };
+
+/* An error message. */
+struct error
+  {
+    int class;                 /* One of the classes above. */
+    struct file_locator where; /* File location, or (NULL, -1). */
+    const char *title;         /* Special text inserted if not null. */
+    const char *text;          /* Error text. */
+  };
+
+/* Number of errors, warnings reported. */
+extern int err_error_count;
+extern int err_warning_count;
+
+/* If number of allowable errors/warnings is exceeded, then a message
+   is displayed and this flag is set to suppress subsequent
+   messages. */
+extern int err_already_flagged;
+
+/* Nonnegative verbosity level.  Higher value == more verbose. */
+extern int err_verbosity;
+
+/* Functions. */
+void msg (int class, const char *format, ...)
+     __attribute__ ((format (printf, 2, 3)));
+void tmsg (int class, const char *title, const char *format, ...)
+     __attribute__ ((format (printf, 3, 4)));
+void err_failure (void);
+void err_cond_fail (void);
+
+/* File-locator stack. */
+void err_push_file_locator (const struct file_locator *);
+void err_pop_file_locator (const struct file_locator *);
+void err_location (struct file_locator *);
+
+/* Obscure functions. */
+void err_break (void);
+void err_check_count (void);
+void err_hcf (int exit_code) __attribute__ ((noreturn));
+void err_vmsg (const struct error *);
+
+#endif /* error.h */
diff --git a/src/expr-evl.c b/src/expr-evl.c
new file mode 100644 (file)
index 0000000..15b4434
--- /dev/null
@@ -0,0 +1,1395 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#if TIME_WITH_SYS_TIME
+#include <sys/time.h>
+#include <time.h>
+#else
+#if HAVE_SYS_TIME_H
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#endif
+
+#include <ctype.h>
+#include <assert.h>
+#include <math.h>
+#include <errno.h>
+#include <stdio.h>
+#include "approx.h"
+#include "data-in.h"
+#include "error.h"
+#include "expr.h"
+#include "exprP.h"
+#include "julcal/julcal.h"
+#include "magic.h"
+#include "random.h"
+#include "stats.h"
+#include "str.h"
+#include "var.h"
+#include "vector.h"
+#include "vfm.h"
+#include "vfmP.h"
+
+/* FIXME: This could be even more efficient if we caught SYSMIS when
+   it first reared its ugly head, then threw it into an entirely new
+   switch that handled SYSMIS aggressively like all the code does now.
+   But I've spent a couple of weeks on the expression code, and that's
+   enough to make anyone sick.  For that matter, it could be more
+   efficient if I hand-coded it in assembly for a dozen processors,
+   but I'm not going to do that either. */
+
+/* These macros are defined differently depending on the way that
+   the stack is managed.  (i.e., I have to adapt the code to inferior
+   environments.)
+
+   void CHECK_STRING_SPACE(int x): Assure that at least X+1 bytes of
+   space are available in the string evaluation stack.
+
+   unsigned char *ALLOC_STRING_SPACE(int x): Return a pointer to X+1
+   bytes of space.  CHECK_STRING_SPACE must have previously been
+   called with an argument of at least X. */
+
+#if PAGED_STACK
+#define CHECK_STRING_SPACE(X)  /* nothing to do! */
+#define ALLOC_STRING_SPACE(X)                  \
+       alloca((X) + 1)
+#else /* !PAGED_STACK */
+#define CHECK_STRING_SPACE(X)                                          \
+       do                                                              \
+          {                                                            \
+           if (str_stk + X >= str_end)                                 \
+             {                                                         \
+               e->str_size += 1024;                                    \
+               e->str_stk = xrealloc (e->str_stk, e->str_size);        \
+               str_end = e->str_stk + e->str_size - 1;                 \
+             }                                                         \
+         }                                                             \
+       while (0)
+     
+#define ALLOC_STRING_SPACE(X)                  \
+       (str_stk += X + 1, str_stk - X - 1)
+#endif /* !PAGED_STACK */
+
+double
+expr_evaluate (struct expression *e, struct ccase *c, union value *v)
+{
+  unsigned char *op = e->op;
+  double *dbl = e->num;
+  unsigned char *str = e->str;
+#if !PAGED_STACK
+  unsigned char *str_stk = e->str_stk;
+  unsigned char *str_end = e->str_stk + e->str_size - 1;
+#endif
+  struct variable **vars = e->var;
+  int i, j;
+
+  /* Stack pointer. */
+  union value *sp = e->stack;
+
+  for (;;)
+    {
+      switch (*op++)
+       {
+       case OP_PLUS:
+         sp -= *op - 1;
+         if (sp->f != SYSMIS)
+           for (i = 1; i < *op; i++)
+             {
+               if (sp[i].f == SYSMIS)
+                 {
+                   sp->f = SYSMIS;
+                   break;
+                 }
+               else
+                 sp->f += sp[i].f;
+             }
+         op++;
+         break;
+       case OP_MUL:
+         sp -= *op - 1;
+         if (sp->f != SYSMIS)
+           for (i = 1; i < *op; i++)
+             {
+               if (sp[i].f == SYSMIS)
+                 {
+                   sp->f = SYSMIS;
+                   break;
+                 }
+               else
+                 sp->f *= sp[i].f;
+             }
+         op++;
+         break;
+       case OP_POW:
+         sp--;
+         if (sp[0].f == SYSMIS)
+           {
+             if (approx_eq (sp[1].f, 0.0))
+               sp->f = 1.0;
+           }
+         else if (sp[1].f == SYSMIS)
+           {
+             if (sp[0].f == 0.0)
+               /* SYSMIS**0 */
+               sp->f = 0.0;
+             else
+               sp->f = SYSMIS;
+           }
+         else if (approx_eq (sp[0].f, 0.0) && approx_eq (sp[1].f, 0.0))
+           sp->f = SYSMIS;
+         else
+           sp->f = pow (sp[0].f, sp[1].f);
+         break;
+
+       case OP_AND:
+         /* Note that the equality operator (==) may be used here
+            (instead of approx_eq) because booleans are always
+            *exactly* 0, 1, or SYSMIS.
+
+            Truth table (in order of detection):
+
+            1:
+            0 and 0 = 0   
+            0 and 1 = 0         
+            0 and SYSMIS = 0
+            
+            2:
+            1 and 0 = 0   
+            SYSMIS and 0 = 0
+            
+            3:
+            1 and SYSMIS = SYSMIS
+            SYSMIS and SYSMIS = SYSMIS
+            
+            4:
+            1 and 1 = 1
+            SYSMIS and 1 = SYSMIS
+
+          */
+         sp--;
+         if (sp[0].f == 0.0);  /* 1 */
+         else if (sp[1].f == 0.0)
+           sp->f = 0.0;        /* 2 */
+         else if (sp[1].f == SYSMIS)
+           sp->f = SYSMIS;     /* 3 */
+         break;
+       case OP_OR:
+         /* Truth table (in order of detection):
+
+            1:
+            1 or 1 = 1
+            1 or 0 = 1
+            1 or SYSMIS = 1
+        
+            2:
+            0 or 1 = 1
+            SYSMIS or 1 = 1
+        
+            3:
+            0 or SYSMIS = SYSMIS
+            SYSMIS or SYSMIS = SYSMIS
+        
+            4:
+            0 or 0 = 0
+            SYSMIS or 0 = SYSMIS
+
+          */
+         sp--;
+         if (sp[0].f == 1.0);  /* 1 */
+         else if (sp[1].f == 1.0)
+           sp->f = 1.0;        /* 2 */
+         else if (sp[1].f == SYSMIS)
+           sp->f = SYSMIS;     /* 3 */
+         break;
+       case OP_NOT:
+         if (sp[0].f == 0.0)
+           sp->f = 1.0;
+         else if (sp[0].f == 1.0)
+           sp->f = 0.0;
+         break;
+
+       case OP_EQ:
+         sp--;
+         if (sp[0].f != SYSMIS)
+           {
+             if (sp[1].f == SYSMIS)
+               sp->f = SYSMIS;
+             else
+               sp->f = approx_eq (sp[0].f, sp[1].f);
+           }
+         break;
+       case OP_GE:
+         sp--;
+         if (sp[0].f != SYSMIS)
+           {
+             if (sp[1].f == SYSMIS)
+               sp->f = SYSMIS;
+             else
+               sp->f = approx_ge (sp[0].f, sp[1].f);
+           }
+         break;
+       case OP_GT:
+         sp--;
+         if (sp[0].f != SYSMIS)
+           {
+             if (sp[1].f == SYSMIS)
+               sp->f = SYSMIS;
+             else
+               sp->f = approx_gt (sp[0].f, sp[1].f);
+           }
+         break;
+       case OP_LE:
+         sp--;
+         if (sp[0].f != SYSMIS)
+           {
+             if (sp[1].f == SYSMIS)
+               sp->f = SYSMIS;
+             else
+               sp->f = approx_le (sp[0].f, sp[1].f);
+           }
+         break;
+       case OP_LT:
+         sp--;
+         if (sp[0].f != SYSMIS)
+           {
+             if (sp[1].f == SYSMIS)
+               sp->f = SYSMIS;
+             else
+               sp->f = approx_lt (sp[0].f, sp[1].f);
+           }
+         break;
+       case OP_NE:
+         sp--;
+         if (sp[0].f != SYSMIS)
+           {
+             if (sp[1].f == SYSMIS)
+               sp->f = SYSMIS;
+             else
+               sp->f = approx_ne (sp[0].f, sp[1].f);
+           }
+         break;
+
+         /* String operators. */
+       case OP_STRING_EQ:
+         sp--;
+         sp[0].f = st_compare_pad (&sp[0].c[1], sp[0].c[0],
+                                   &sp[1].c[1], sp[1].c[0]) == 0;
+         break;
+       case OP_STRING_GE:
+         sp--;
+         sp[0].f = st_compare_pad (&sp[0].c[1], sp[0].c[0],
+                                   &sp[1].c[1], sp[1].c[0]) >= 0;
+         break;
+       case OP_STRING_GT:
+         sp--;
+         sp[0].f = st_compare_pad (&sp[0].c[1], sp[0].c[0],
+                                   &sp[1].c[1], sp[1].c[0]) > 0;
+         break;
+       case OP_STRING_LE:
+         sp--;
+         sp[0].f = st_compare_pad (&sp[0].c[1], sp[0].c[0],
+                                   &sp[1].c[1], sp[1].c[0]) <= 0;
+         break;
+       case OP_STRING_LT:
+         sp--;
+         sp[0].f = st_compare_pad (&sp[0].c[1], sp[0].c[0],
+                                   &sp[1].c[1], sp[1].c[0]) < 0;
+         break;
+       case OP_STRING_NE:
+         sp--;
+         sp[0].f = st_compare_pad (&sp[0].c[1], sp[0].c[0],
+                                   &sp[1].c[1], sp[1].c[0]) != 0;
+         break;
+
+         /* Unary functions. */
+       case OP_NEG:
+         if (sp->f != SYSMIS)
+           sp->f = -sp->f;
+         break;
+       case OP_ABS:
+         if (sp->f != SYSMIS)
+           sp->f = fabs (sp->f);
+         break;
+       case OP_ARCOS:
+         if (sp->f != SYSMIS)
+           {
+             errno = 0;
+             sp->f = acos (sp->f);
+             if (errno)
+               sp->f = SYSMIS;
+           }
+         break;
+       case OP_ARSIN:
+         if (sp->f != SYSMIS)
+           {
+             errno = 0;
+             sp->f = asin (sp->f);
+             if (errno)
+               sp->f = SYSMIS;
+           }
+         break;
+       case OP_ARTAN:
+         if (sp->f != SYSMIS)
+           sp->f = atan (sp->f);
+         break;
+       case OP_COS:
+         if (sp->f != SYSMIS)
+           sp->f = cos (sp->f);
+         break;
+       case OP_EXP:
+         if (sp->f != SYSMIS)
+           {
+             errno = 0;
+             sp->f = exp (sp->f);
+             if (errno)
+               sp->f = SYSMIS;
+           }
+         break;
+       case OP_LG10:
+         if (sp->f != SYSMIS)
+           {
+             errno = 0;
+             sp->f = log10 (sp->f);
+             if (errno)
+               sp->f = SYSMIS;
+           }
+         break;
+       case OP_LN:
+         if (sp->f != SYSMIS)
+           {
+             errno = 0;
+             sp->f = log10 (sp->f);
+             if (errno)
+               sp->f = SYSMIS;
+           }
+         break;
+       case OP_MOD10:
+         if (sp->f != SYSMIS)
+           sp->f = fmod (sp->f, 10);
+         break;
+       case OP_RND:
+         if (sp->f != SYSMIS)
+           {
+             if (sp->f >= 0.0)
+               sp->f = floor (sp->f + 0.5);
+             else
+               sp->f = -floor (-sp->f + 0.5);
+           }
+         break;
+       case OP_SIN:
+         if (sp->f != SYSMIS)
+           sp->f = sin (sp->f);
+         break;
+       case OP_SQRT:
+         if (sp->f != SYSMIS)
+           {
+             errno = 0;
+             sp->f = sqrt (sp->f);
+             if (errno)
+               sp->f = SYSMIS;
+           }
+         break;
+       case OP_TAN:
+         if (sp->f != SYSMIS)
+           {
+             errno = 0;
+             sp->f = tan (sp->f);
+             if (errno)
+               sp->f = SYSMIS;
+           }
+         break;
+       case OP_TRUNC:
+         if (sp->f != SYSMIS)
+           {
+             if (sp->f >= 0.0)
+               sp->f = floor (sp->f);
+             else
+               sp->f = -floor (-sp->f);
+           }
+         break;
+
+         /* N-ary numeric functions. */
+       case OP_ANY:
+         {
+           int n_args = *op++;
+           int sysmis = 1;
+
+           sp -= n_args - 1;
+           if (sp->f == SYSMIS)
+             break;
+           for (i = 1; i <= n_args; i++)
+             if (approx_eq (sp[0].f, sp[i].f))
+               {
+                 sp->f = 1.0;
+                 goto main_loop;
+               }
+             else if (sp[i].f != SYSMIS)
+               sysmis = 0;
+           sp->f = sysmis ? SYSMIS : 0.0;
+         }
+         break;
+       case OP_ANY_STRING:
+         {
+           int n_args = *op++;
+
+           sp -= n_args - 1;
+           for (i = 1; i <= n_args; i++)
+             if (!st_compare_pad (&sp[0].c[1], sp[0].c[0],
+                                  &sp[i].c[1], sp[i].c[0]))
+               {
+                 sp->f = 1.0;
+                 goto main_loop;
+               }
+           sp->f = 0.0;
+         }
+         break;
+       case OP_CFVAR:
+         {
+           int n_args = *op++;
+           int nv = 0;
+           double sum[2] =
+           {0.0, 0.0};
+
+           sp -= n_args - 1;
+           for (i = 0; i < n_args; i++)
+             if (sp[i].f != SYSMIS)
+               {
+                 nv++;
+                 sum[0] += sp[i].f;
+                 sum[1] += sp[i].f * sp[i].f;
+               }
+           if (nv < *op++)
+             sp->f = SYSMIS;
+           else
+             sp->f = calc_cfvar (sum, nv);
+         }
+         break;
+       case OP_MAX:
+         {
+           int n_args = *op++;
+           int nv = 0;
+           double max = -DBL_MAX;
+
+           sp -= n_args - 1;
+           for (i = 0; i < n_args; i++)
+             if (sp[i].f != SYSMIS)
+               {
+                 nv++;
+                 if (sp[i].f > max)
+                   max = sp[i].f;
+               }
+           if (nv < *op++)
+             sp->f = SYSMIS;
+           else
+             sp->f = max;
+         }
+         break;
+       case OP_MEAN:
+         {
+           int n_args = *op++;
+           int nv = 0;
+           double sum[1] =
+           {0.0};
+
+           sp -= n_args - 1;
+           for (i = 0; i < n_args; i++)
+             if (sp[i].f != SYSMIS)
+               {
+                 nv++;
+                 sum[0] += sp[i].f;
+               }
+           if (nv < *op++)
+             sp->f = SYSMIS;
+           else
+             sp->f = calc_mean (sum, nv);
+         }
+         break;
+       case OP_MIN:
+         {
+           int n_args = *op++;
+           int nv = 0;
+           double min = DBL_MAX;
+
+           sp -= n_args - 1;
+           for (i = 0; i < n_args; i++)
+             if (sp[i].f != SYSMIS)
+               {
+                 nv++;
+                 if (sp[i].f < min)
+                   min = sp[i].f;
+               }
+           if (nv < *op++)
+             sp->f = SYSMIS;
+           else
+             sp->f = min;
+         }
+         break;
+       case OP_NMISS:
+         {
+           int n_args = *op++;
+           int n_missing = 0;
+
+           sp -= n_args - 1;
+           for (i = 0; i < n_args; i++)
+             if (sp[i].f == SYSMIS)
+               n_missing++;
+           sp->f = n_missing;
+         }
+         break;
+       case OP_NVALID:
+         {
+           int n_args = *op++;
+           int n_valid = 0;
+
+           sp -= n_args - 1;
+           for (i = 0; i < n_args; i++)
+             if (sp[i].f != SYSMIS)
+               n_valid++;
+           sp->f = n_valid;
+         }
+         break;
+       case OP_RANGE:
+         {
+           int n_args = *op++;
+           int sysmis = 1;
+
+           sp -= n_args - 1;
+           if (sp->f == SYSMIS)
+             break;
+           for (i = 1; i <= n_args; i += 2)
+             if (sp[i].f == SYSMIS || sp[i + 1].f == SYSMIS)
+               continue;
+             else if (approx_ge (sp[0].f, sp[i].f)
+                      && approx_le (sp[0].f, sp[i + 1].f))
+               {
+                 sp->f = 1.0;
+                 goto main_loop;
+               }
+             else
+               sysmis = 0;
+           sp->f = sysmis ? SYSMIS : 0.0;
+         }
+         break;
+       case OP_RANGE_STRING:
+         {
+           int n_args = *op++;
+
+           sp -= n_args - 1;
+           for (i = 1; i <= n_args; i += 2)
+             if (st_compare_pad (&sp[0].c[1], sp[0].c[0],
+                                 &sp[i].c[1], sp[i].c[0]) >= 0
+                 && st_compare_pad (&sp[0].c[1], sp[0].c[0],
+                                    &sp[i + 1].c[1], sp[i + 1].c[0]) <= 0)
+               {
+                 sp->f = 1.0;
+                 goto main_loop;
+               }
+           sp->f = 0.0;
+         }
+         break;
+       case OP_SD:
+         {
+           int n_args = *op++;
+           int nv = 0;
+           double sum[2];
+
+           sum[0] = sum[1] = 0.0;
+
+           sp -= n_args - 1;
+           for (i = 0; i < n_args; i++)
+             if (sp[i].f != SYSMIS)
+               {
+                 nv++;
+                 sum[0] += sp[i].f;
+                 sum[1] += sp[i].f * sp[i].f;
+               }
+           if (nv < *op++)
+             sp->f = SYSMIS;
+           else
+             sp->f = calc_stddev (calc_variance (sum, nv));
+         }
+         break;
+       case OP_SUM:
+         {
+           int n_args = *op++;
+           int nv = 0;
+           double sum = 0.0;
+
+           sp -= n_args - 1;
+           for (i = 0; i < n_args; i++)
+             if (sp[i].f != SYSMIS)
+               {
+                 nv++;
+                 sum += sp[i].f;
+               }
+           if (nv < *op++)
+             sp->f = SYSMIS;
+           else
+             sp->f = sum;
+         }
+         break;
+       case OP_VARIANCE:
+         {
+           int n_args = *op++;
+           int nv = 0;
+           double sum[2];
+
+           sum[0] = sum[1] = 0.0;
+
+           sp -= n_args - 1;
+           for (i = 0; i < n_args; i++)
+             if (sp[i].f != SYSMIS)
+               {
+                 nv++;
+                 sum[0] += sp[i].f;
+                 sum[1] += sp[i].f * sp[i].f;
+               }
+           if (nv < *op++)
+             sp->f = SYSMIS;
+           else
+             sp->f = calc_variance (sum, nv);
+         }
+         break;
+
+         /* Time construction function. */
+       case OP_TIME_HMS:
+         sp -= 2;
+         if (sp[0].f == SYSMIS || sp[1].f == SYSMIS || sp[2].f == SYSMIS)
+           sp->f = SYSMIS;
+         else
+           sp->f = 60. * (60. * sp[0].f + sp[1].f) + sp[2].f;
+         break;
+
+         /* Date construction functions. */
+       case OP_DATE_DMY:
+         sp -= 2;
+         sp->f = yrmoda (sp[2].f, sp[1].f, sp[0].f);
+         if (sp->f != SYSMIS)
+           sp->f *= 60. * 60. * 24.;
+         break;
+       case OP_DATE_MDY:
+         sp -= 2;
+         sp->f = yrmoda (sp[2].f, sp[0].f, sp[1].f);
+         if (sp->f != SYSMIS)
+           sp->f *= 60. * 60. * 24.;
+         break;
+       case OP_DATE_MOYR:
+         (--sp)->f = yrmoda (sp[1].f, sp[0].f, 1);
+         if (sp->f != SYSMIS)
+           sp->f *= 60. * 60. * 24.;
+         break;
+       case OP_DATE_QYR:
+         sp--;
+         if (sp[0].f == SYSMIS)
+           sp->f = SYSMIS;
+         else
+           {
+             sp->f = yrmoda (sp[1].f, sp[0].f * 3 - 2, 1);
+             if (sp->f != SYSMIS)
+               sp->f *= 60. * 60. * 24.;
+           }
+         break;
+       case OP_DATE_WKYR:
+         sp--;
+         if (sp[0].f == SYSMIS)
+           sp->f = SYSMIS;
+         else
+           {
+             sp[1].f = yrmoda (sp[1].f, 1, 1);
+             if (sp->f != SYSMIS)
+               sp[1].f = 60. * 60. * 24. * (sp[1].f + 7. * (floor (sp[0].f) - 1.));
+             sp->f = sp[1].f;
+           }
+         break;
+       case OP_DATE_YRDAY:
+         sp--;
+         if (sp[1].f == SYSMIS)
+           sp->f = SYSMIS;
+         else
+           {
+             sp->f = yrmoda (sp[0].f, 1, 1);
+             if (sp->f != SYSMIS)
+               sp->f = 60. * 60. * 24. * (sp->f + floor (sp[1].f) - 1);
+           }
+         break;
+       case OP_YRMODA:
+         sp -= 2;
+         sp->f = yrmoda (sp[0].f, sp[1].f, sp[2].f);
+         break;
+
+         /* Date extraction functions. */
+       case OP_XDATE_DATE:
+         if (sp->f != SYSMIS)
+           sp->f = floor (sp->f / 60. / 60. / 24.) * 60. * 60. * 24.;
+         break;
+       case OP_XDATE_HOUR:
+         if (sp->f != SYSMIS)
+           sp->f = fmod (floor (sp->f / 60. / 60.), 24.);
+         break;
+       case OP_XDATE_JDAY:
+         if (sp->f != SYSMIS)
+           sp->f = 86400. * julian_to_jday (sp->f / 86400.);
+         break;
+       case OP_XDATE_MDAY:
+         if (sp->f != SYSMIS)
+           {
+             int day;
+             julian_to_calendar (sp->f / 86400., NULL, NULL, &day);
+             sp->f = day;
+           }
+         break;
+       case OP_XDATE_MINUTE:
+         if (sp->f != SYSMIS)
+           sp->f = fmod (floor (sp->f / 60.), 60.);
+         break;
+       case OP_XDATE_MONTH:
+         if (sp->f != SYSMIS)
+           {
+             int month;
+             julian_to_calendar (sp->f / 86400., NULL, &month, NULL);
+             sp->f = month;
+           }
+         break;
+       case OP_XDATE_QUARTER:
+         if (sp->f != SYSMIS)
+           {
+             int month;
+             julian_to_calendar (sp->f / 86400., NULL, &month, NULL);
+             sp->f = (month - 1) / 3 + 1;
+           }
+         break;
+       case OP_XDATE_SECOND:
+         if (sp->f != SYSMIS)
+           sp->f = fmod (sp->f, 60.);
+         break;
+       case OP_XDATE_TDAY:
+         if (sp->f != SYSMIS)
+           sp->f = floor (sp->f / 60. / 60. / 24.);
+         break;
+       case OP_XDATE_TIME:
+         if (sp->f != SYSMIS)
+           sp->f -= floor (sp->f / 60. / 60. / 24.) * 60. * 60. * 24.;
+         break;
+       case OP_XDATE_WEEK:
+         if (sp->f != SYSMIS)
+           sp->f = (julian_to_jday (sp->f / 86400.) - 1) / 7 + 1;
+         break;
+       case OP_XDATE_WKDAY:
+         if (sp->f != SYSMIS)
+           sp->f = julian_to_wday (sp->f / 86400.);
+         break;
+       case OP_XDATE_YEAR:
+         if (sp->f != SYSMIS)
+           {
+             int year;
+             julian_to_calendar (sp->f / 86400., &year, NULL, NULL);
+             sp->f = year;
+           }
+         break;
+
+         /* String functions. */
+       case OP_CONCAT:
+         {
+           int n_args = *op++;
+           unsigned char *dest;
+
+           CHECK_STRING_SPACE (255);
+           dest = ALLOC_STRING_SPACE (255);
+           dest[0] = 0;
+
+           sp -= n_args - 1;
+           for (i = 0; i < n_args; i++)
+             if (sp[i].c[0] != 0)
+               {
+                 if (sp[i].c[0] + dest[0] < 255)
+                   {
+                     memcpy (&dest[dest[0] + 1], &sp[i].c[1], sp[i].c[0]);
+                     dest[0] += sp[i].c[0];
+                   }
+                 else
+                   {
+                     memcpy (&dest[dest[0] + 1], &sp[i].c[1], 255 - dest[0]);
+                     dest[0] = 255;
+                     break;
+                   }
+               }
+           sp[0].c = dest;
+         }
+         break;
+       case OP_INDEX:
+         sp--;
+         if (sp[1].c[0] == 0)
+           sp->f = SYSMIS;
+         else
+           {
+             int last = sp[0].c[0] - sp[1].c[0];
+             for (i = 0; i <= last; i++)
+               if (!memcmp (&sp[0].c[i + 1], &sp[0].c[1], sp[0].c[0]))
+                 {
+                   sp->f = i + 1;
+                   goto main_loop;
+                 }
+             sp->f = 0.0;
+           }
+         break;
+       case OP_INDEX_OPT:
+         {
+           /* Length of each search string. */
+           int part_len = sp[2].f;
+
+           sp -= 2;
+           if (sp[1].c[0] == 0 || part_len <= 0 || sp[2].f == SYSMIS
+               || sp[1].c[0] % part_len != 0)
+             sp->f = SYSMIS;
+           else
+             {
+               /* Last possible index. */
+               int last = sp[0].c[0] - part_len;
+
+               for (i = 0; i <= last; i++)
+                 for (j = 0; j < sp[1].c[0]; j += part_len)
+                   if (!memcmp (&sp[0].c[i], &sp[1].c[j], part_len))
+                     {
+                       sp->f = i + 1;
+                       goto main_loop;
+                     }
+               sp->f = 0.0;
+             }
+         }
+         break;
+       case OP_RINDEX:
+         sp--;
+         if (sp[1].c[0] == 0)
+           sp->f = SYSMIS;
+         else
+           {
+             for (i = sp[0].c[0] - sp[1].c[0]; i >= 0; i--)
+               if (!memcmp (&sp[0].c[i + 1], &sp[0].c[1], sp[0].c[0]))
+                 {
+                   sp->f = i + 1;
+                   goto main_loop;
+                 }
+             sp->f = 0.0;
+           }
+         break;
+       case OP_RINDEX_OPT:
+         {
+           /* Length of each search string. */
+           int part_len = sp[2].f;
+
+           sp -= 2;
+           if (sp[1].c[0] == 0 || part_len <= 0 || sp[2].f == SYSMIS
+               || sp[1].c[0] % part_len != 0)
+             sp->f = SYSMIS;
+           else
+             {
+               for (i = sp[0].c[0] - part_len; i >= 0; i--)
+                 for (j = 0; j < sp[1].c[0]; j += part_len)
+                   if (!memcmp (&sp[0].c[i], &sp[1].c[j], part_len))
+                     {
+                       sp->f = i + 1;
+                       goto main_loop;
+                     }
+               sp->f = 0.0;
+             }
+         }
+         break;
+       case OP_LENGTH:
+         sp->f = sp[0].c[0];
+         break;
+       case OP_LOWER:
+         for (i = sp[0].c[0]; i >= 1; i--)
+           sp[0].c[i] = tolower ((unsigned char) (sp[0].c[i]));
+         break;
+       case OP_UPPER:
+         for (i = sp[0].c[0]; i >= 1; i--)
+           sp[0].c[i] = toupper ((unsigned char) (sp[0].c[i]));
+         break;
+       case OP_LPAD:
+         {
+           int len;
+           sp--;
+           len = sp[1].f;
+           if (sp[1].f == SYSMIS || len < 0 || len > 255)
+             sp->c[0] = 0;
+           else if (len > sp[0].c[0])
+             {
+               unsigned char *dest;
+
+               CHECK_STRING_SPACE (len);
+               dest = ALLOC_STRING_SPACE (len);
+               dest[0] = len;
+               memset (&dest[1], ' ', len - sp->c[0]);
+               memcpy (&dest[len - sp->c[0] + 1], &sp->c[1], sp->c[0]);
+               sp->c = dest;
+             }
+         }
+         break;
+       case OP_LPAD_OPT:
+         {
+           int len;
+           sp -= 2;
+           len = sp[1].f;
+           if (sp[1].f == SYSMIS || len < 0 || len > 255 || sp[2].c[0] != 1)
+             sp->c[0] = 0;
+           else if (len > sp[0].c[0])
+             {
+               unsigned char *dest;
+
+               CHECK_STRING_SPACE (len);
+               dest = ALLOC_STRING_SPACE (len);
+               dest[0] = len;
+               memset (&dest[1], sp[2].c[1], len - sp->c[0]);
+               memcpy (&dest[len - sp->c[0] + 1], &sp->c[1], sp->c[0]);
+               sp->c = dest;
+             }
+         }
+         break;
+       case OP_RPAD:
+         {
+           int len;
+           sp--;
+           len = sp[1].f;
+           if (sp[1].f == SYSMIS || len < 0 || len > 255)
+             sp->c[0] = 0;
+           else if (len > sp[0].c[0])
+             {
+               unsigned char *dest;
+
+               CHECK_STRING_SPACE (len);
+               dest = ALLOC_STRING_SPACE (len);
+               dest[0] = len;
+               memcpy (&dest[1], &sp->c[1], sp->c[0]);
+               memset (&dest[sp->c[0] + 1], ' ', len - sp->c[0]);
+               sp->c = dest;
+             }
+         }
+         break;
+       case OP_RPAD_OPT:
+         {
+           int len;
+           sp -= 2;
+           len = sp[1].f;
+           if (len < 0 || len > 255 || sp[2].c[0] != 1)
+             sp->c[0] = 0;
+           else if (len > sp[0].c[0])
+             {
+               unsigned char *dest;
+
+               CHECK_STRING_SPACE (len);
+               dest = ALLOC_STRING_SPACE (len);
+               dest[0] = len;
+               memcpy (&dest[1], &sp->c[1], sp->c[0]);
+               memset (&dest[sp->c[0] + 1], sp[2].c[1], len - sp->c[0]);
+               sp->c = dest;
+             }
+         }
+         break;
+       case OP_LTRIM:
+         {
+           int len = sp[0].c[0];
+
+           i = 1;
+           while (i <= len && sp[0].c[i] == ' ')
+             i++;
+           if (--i)
+             {
+               sp[0].c[i] = sp[0].c[0] - i;
+               sp->c = &sp[0].c[i];
+             }
+         }
+         break;
+       case OP_LTRIM_OPT:
+         {
+           sp--;
+           if (sp[1].c[0] != 1)
+             sp[0].c[0] = 0;
+           else
+             {
+               int len = sp[0].c[0];
+               int cmp = sp[1].c[1];
+
+               i = 1;
+               while (i <= len && sp[0].c[i] == cmp)
+                 i++;
+               if (--i)
+                 {
+                   sp[0].c[i] = sp[0].c[0] - i;
+                   sp->c = &sp[0].c[i];
+                 }
+             }
+         }
+         break;
+       case OP_RTRIM:
+         assert (' ' != 0);
+         while (sp[0].c[sp[0].c[0]] == ' ')
+           sp[0].c[0]--;
+         break;
+       case OP_RTRIM_OPT:
+         sp--;
+         if (sp[1].c[0] != 1)
+           sp[0].c[0] = 0;
+         else
+           {
+             /* Note that NULs are not allowed in strings.  This code
+                needs to change if this decision is changed. */
+             int cmp = sp[1].c[1];
+             while (sp[0].c[sp[0].c[0]] == cmp)
+               sp[0].c[0]--;
+           }
+         break;
+       case OP_NUMBER:
+         {
+           struct data_in di;
+
+           di.s = &sp->c[1];
+           di.e = &sp->c[1] + sp->c[0];
+           di.v = sp;
+           di.flags = DI_IGNORE_ERROR;
+           di.f1 = 1;
+           di.format.type = FMT_F;
+           di.format.w = sp->c[0];
+           di.format.d = 0;
+           data_in (&di);
+         }
+         break;
+       case OP_NUMBER_OPT:
+         {
+           struct data_in di;
+           di.s = &sp->c[1];
+           di.e = &sp->c[1] + sp->c[0];
+           di.v = sp;
+           di.flags = DI_IGNORE_ERROR;
+           di.f1 = 1;
+           di.format.type = *op++;
+           di.format.w = *op++;
+           di.format.d = *op++;
+           data_in (&di);
+         }
+         break;
+       case OP_STRING:
+         {
+           struct fmt_spec f;
+           unsigned char *dest;
+
+           f.type = *op++;
+           f.w = *op++;
+           f.d = *op++;
+
+           CHECK_STRING_SPACE (f.w);
+           dest = ALLOC_STRING_SPACE (f.w);
+           dest[0] = f.w;
+
+           data_out (&dest[1], &f, sp);
+           sp->c = dest;
+         }
+         break;
+       case OP_SUBSTR:
+         {
+           int index;
+
+           sp--;
+           index = sp[1].f;
+           if (index < 1 || index > sp[0].c[0])
+             sp->c[0] = 0;
+           else if (index > 1)
+             {
+               index--;
+               sp->c[index] = sp->c[0] - index;
+               sp->c += index;
+             }
+         }
+         break;
+       case OP_SUBSTR_OPT:
+         {
+           int index;
+           int n;
+
+           sp -= 2;
+           index = sp[1].f;
+           n = sp[2].f;
+           if (sp[1].f == SYSMIS || sp[2].f == SYSMIS || index < 1
+               || index > sp[0].c[0] || n < 1)
+             sp->c[0] = 0;
+           else
+             {
+               if (index > 1)
+                 {
+                   index--;
+                   sp->c[index] = sp->c[0] - index;
+                   sp->c += index;
+                 }
+               if (sp->c[0] > n)
+                 sp->c[0] = n;
+             }
+         }
+         break;
+
+         /* Artificial. */
+       case OP_INV:
+         if (sp->f != SYSMIS)
+           sp->f = 1. / sp->f;
+         break;
+       case OP_SQUARE:
+         if (sp->f != SYSMIS)
+           sp->f *= sp->f;
+         break;
+       case OP_NUM_TO_BOOL:
+         if (approx_eq (sp->f, 0.0))
+           sp->f = 0.0;
+         else if (approx_eq (sp->f, 1.0))
+           sp->f = 1.0;
+         else if (sp->f != SYSMIS)
+           {
+             msg (SE, _("A number being treated as a Boolean in an "
+                        "expression was found to have a value other than "
+                        "0 (false), 1 (true), or the system-missing value.  "
+                        "The result was forced to 0."));
+             sp->f = 0.0;
+           }
+         break;
+
+         /* Weirdness. */
+       case OP_MOD:
+         sp--;
+         if (sp[0].f != SYSMIS)
+           {
+             if (sp[1].f == SYSMIS)
+               {
+                 if (approx_ne (sp[0].f, 0.0))
+                   sp->f = SYSMIS;
+               }
+             else
+               sp->f = fmod (sp[0].f, sp[1].f);
+           }
+         break;
+       case OP_NORMAL:
+         if (sp->f != SYSMIS)
+           sp->f = rand_normal (sp->f);
+         break;
+       case OP_UNIFORM:
+         if (sp->f != SYSMIS)
+           sp->f = rand_uniform (sp->f);
+         break;
+       case OP_SYSMIS:
+         if (sp[0].f == SYSMIS || !finite (sp[0].f))
+           sp->f = 1.0;
+         else
+           sp->f = 0.0;
+         break;
+       case OP_VEC_ELEM_NUM:
+         {
+           int rindx = sp[0].f + EPSILON;
+           struct vector *v = &vec[*op++];
+
+           if (sp[0].f == SYSMIS || rindx < 1 || rindx > v->nv)
+             {
+               if (sp[0].f == SYSMIS)
+                 msg (SE, _("SYSMIS is not a valid index value for vector "
+                            "%s.  The result will be set to SYSMIS."),
+                      v->name);
+               else
+                 msg (SE, _("%g is not a valid index value for vector %s.  "
+                            "The result will be set to SYSMIS."),
+                      sp[0].f, v->name);
+               sp->f = SYSMIS;
+               break;
+             }
+           sp->f = c->data[v->v[rindx - 1]->fv].f;
+         }
+         break;
+       case OP_VEC_ELEM_STR:
+         {
+           int rindx = sp[0].f + EPSILON;
+           struct vector *vect = &vec[*op++];
+           struct variable *v;
+
+           if (sp[0].f == SYSMIS || rindx < 1 || rindx > vect->nv)
+             {
+               if (sp[0].f == SYSMIS)
+                 msg (SE, _("SYSMIS is not a valid index value for vector "
+                            "%s.  The result will be set to the empty "
+                            "string."),
+                      vect->name);
+               else
+                 msg (SE, _("%g is not a valid index value for vector %s.  "
+                            "The result will be set to the empty string."),
+                      sp[0].f, vect->name);
+               CHECK_STRING_SPACE (0);
+               sp->c = ALLOC_STRING_SPACE (0);
+               sp->c[0] = 0;
+               break;
+             }
+
+           v = vect->v[rindx - 1];
+           CHECK_STRING_SPACE (v->width);
+           sp->c = ALLOC_STRING_SPACE (v->width);
+           sp->c[0] = v->width;
+           memcpy (&sp->c[1], c->data[v->fv].s, v->width);
+         }
+         break;
+
+         /* Terminals. */
+       case OP_NUM_CON:
+         sp++;
+         sp->f = *dbl++;
+         break;
+       case OP_STR_CON:
+         sp++;
+         CHECK_STRING_SPACE (*str);
+         sp->c = ALLOC_STRING_SPACE (*str);
+         memcpy (sp->c, str, *str + 1);
+         str += *str + 1;
+         break;
+       case OP_NUM_VAR:
+         sp++;
+         sp->f = c->data[(*vars)->fv].f;
+         if (is_num_user_missing (sp->f, *vars))
+           sp->f = SYSMIS;
+         vars++;
+         break;
+       case OP_STR_VAR:
+         {
+           int width = (*vars)->width;
+
+           sp++;
+           CHECK_STRING_SPACE (width);
+           sp->c = ALLOC_STRING_SPACE (width);
+           sp->c[0] = width;
+           memcpy (&sp->c[1], &c->data[(*vars)->fv], width);
+           vars++;
+         }
+         break;
+       case OP_NUM_LAG:
+         {
+           struct ccase *c = lagged_case (*op++);
+
+           sp++;
+           if (c == NULL)
+             sp->f = SYSMIS;
+           else
+             {
+               sp->f = c->data[(*vars)->fv].f;
+               if (is_num_user_missing (sp->f, *vars))
+                 sp->f = SYSMIS;
+             }
+           vars++;
+           break;
+         }
+       case OP_STR_LAG:
+         {
+           struct ccase *c = lagged_case (*op++);
+           int width = (*vars)->width;
+
+           sp++;
+           CHECK_STRING_SPACE (width);
+           sp->c = ALLOC_STRING_SPACE (width);
+           sp->c[0] = width;
+           
+           if (c == NULL)
+             memset (sp->c, ' ', width);
+           else
+             memcpy (&sp->c[1], &c->data[(*vars)->fv], width);
+           
+           vars++;
+         }
+         break;
+       case OP_NUM_SYS:
+         sp++;
+         sp->f = c->data[*op++].f == SYSMIS;
+         break;
+       case OP_STR_MIS:
+         sp++;
+         sp->f = is_str_user_missing (c->data[(*vars)->fv].s, *vars);
+         vars++;
+         break;
+       case OP_NUM_VAL:
+         sp++;
+         sp->f = c->data[*op++].f;
+         break;
+       case OP_CASENUM:
+         sp++;
+         sp->f = vfm_sink_info.ncases + 1;
+         break;
+
+       case OP_SENTINEL:
+         goto finished;
+
+#if __CHECKER__
+         /* This case prevents Checker from choking. */
+       case 42000:
+         assert (0);
+#endif
+
+       default:
+#if GLOBAL_DEBUGGING
+         printf (_("evaluate_expression(): not implemented: %s\n"),
+                 ops[op[-1]].name);
+#else
+         printf (_("evaluate_expression(): not implemented: %d\n"), op[-1]);
+#endif
+         assert (0);
+       }
+
+    main_loop: ;
+    }
+finished:
+  if (e->type != EX_STRING)
+    {
+      double value = sp->f;
+      if (!finite (value))
+       value = SYSMIS;
+      if (v)
+       v->f = value;
+      return value;
+    }
+  else
+    {
+      assert (v);
+
+#if PAGED_STACK
+      memcpy (e->str_stack, sp->c, sp->c[0] + 1);
+      v->c = e->str_stack;
+#else
+      v->c = sp->c;
+#endif
+
+      return 0.0;
+    }
+}
diff --git a/src/expr-opt.c b/src/expr-opt.c
new file mode 100644 (file)
index 0000000..e221d4e
--- /dev/null
@@ -0,0 +1,1142 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <math.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "approx.h"
+#include "data-in.h"
+#include "error.h"
+#include "expr.h"
+#include "exprP.h"
+#include "julcal/julcal.h"
+#include "misc.h"
+#include "stats.h"
+#include "str.h"
+#include "var.h"
+
+/*
+   Expression "optimizer"
+
+   Operates on the tree representation of expressions.
+   optimize_expression() performs the optimizations listed below:
+
+   1. Constant folding
+   Any operation with constant operands is replaced by its value.
+   (Exception: random-number-generator functions.)
+
+   2. Strength reduction (x is any expression; a is a numeric constant)
+   x/0 => SYSMIS
+   x*0 => 0
+   x**0 => 1
+   x**1, x+0, x-0, x*1 => x
+   x**2 => sqr(x)
+   x/a => x*(1/a)   (where 1/a is evaluated at optimization time)
+
+   I thought about adding additional optimizations but decided that what
+   is here could already be considered overkill.
+ */
+
+static struct nonterm_node *evaluate_tree (struct nonterm_node * n);
+static struct nonterm_node *optimize_tree (struct nonterm_node * n);
+
+struct nonterm_node *
+optimize_expression (struct nonterm_node * n)
+{
+  int i;
+
+  /* Set to 1 if a child is nonconstant. */
+  int nonconst = 0;
+
+  /* Number of system-missing children. */
+  int sysmis = 0;
+
+  /* We can't optimize a terminal node. */
+  if (n->type > OP_TERMINAL)
+    return n;
+
+  /* Start by optimizing all the children. */
+  for (i = 0; i < n->n; i++)
+    {
+      n->arg[i] = ((union any_node *)
+                  optimize_expression ((struct nonterm_node *) n->arg[i]));
+      if (n->arg[i]->type == OP_NUM_CON)
+       {
+         if (n->arg[i]->num_con.value == SYSMIS)
+           sysmis++;
+       }
+      else if (n->arg[i]->type != OP_STR_CON)
+       nonconst = 1;
+    }
+
+  if (sysmis && !(ops[n->type].flags & OP_ABSORB_MISS))
+    /* Just about any operation produces SYSMIS when given any SYSMIS
+       arguments. */
+    {
+      struct num_con_node *num = xmalloc (sizeof *num);
+      free_node ((union any_node *) n);
+      num->type = OP_NUM_CON;
+      num->value = SYSMIS;
+      n = (struct nonterm_node *) num;
+    }
+  else if (!nonconst)
+    /* If all the children of this node are constants, then there are
+       obvious optimizations. */
+    n = evaluate_tree (n);
+  else
+    /* Otherwise, we may be able to make certain optimizations
+       anyway. */
+    n = optimize_tree (n);
+  return n;
+}
+
+static struct nonterm_node *repl_num_con (struct nonterm_node *, double);
+static struct nonterm_node *force_repl_num_con (struct nonterm_node *, double);
+static struct nonterm_node *repl_str_con (struct nonterm_node *, char *, int);
+
+#define n0 n->arg[0]->num_con.value
+#define n1 n->arg[1]->num_con.value
+#define n2 n->arg[2]->num_con.value
+
+#define s0 n->arg[0]->str_con.s
+#define s0l n->arg[0]->str_con.len
+#define s1 n->arg[1]->str_con.s
+#define s1l n->arg[1]->str_con.len
+#define s2 n->arg[2]->str_con.s
+#define s2l n->arg[2]->str_con.len
+#define s(X) n->arg[X]->str_con.s
+#define sl(X) n->arg[X]->str_con.len
+
+static struct nonterm_node *
+optimize_tree (struct nonterm_node * n)
+{
+  int i;
+
+  errno = 0;
+  if (n->type == OP_PLUS || n->type == OP_MUL)
+    {
+      /* Default constant value. */
+      double def = n->type == OP_MUL ? 1.0 : 0.0;
+
+      /* Total value of all the constants. */
+      double cval = def;
+
+      /* Number of nonconst arguments. */
+      int nvar = 0;
+
+      /* New node. */
+      struct nonterm_node *m;
+
+      /* Argument copying counter. */
+      int c;
+
+      /* 1=SYSMIS encountered */
+      int sysmis = 0;
+
+      for (i = 0; i < n->n; i++)
+       if (n->arg[i]->type == OP_NUM_CON)
+         {
+           if (n->arg[i]->num_con.value != SYSMIS)
+             {
+               if (n->type == OP_MUL)
+                 cval *= n->arg[i]->num_con.value;
+               else
+                 cval += n->arg[i]->num_con.value;
+             }
+           else
+             sysmis++;
+         }
+       else
+         nvar++;
+
+      /* 0*SYSMIS=0, 0/SYSMIS=0; otherwise, SYSMIS and infinities
+         produce SYSMIS. */
+      if (approx_eq (cval, 0.0) && n->type == OP_MUL)
+       nvar = 0;
+      else if (sysmis || !finite (cval))
+       {
+         nvar = 0;
+         cval = SYSMIS;
+       }
+
+      /* If no nonconstant terms, replace with a constant node. */
+      if (nvar == 0)
+       return force_repl_num_con (n, cval);
+
+      if (nvar == 1 && cval == def)
+       {
+         /* If there is exactly one nonconstant term and no constant
+            terms, replace with the nonconstant term. */
+         for (i = 0; i < n->n; i++)
+           if (n->arg[i]->type != OP_NUM_CON)
+             m = (struct nonterm_node *) n->arg[i];
+           else
+             free_node (n->arg[i]);
+       }
+      else
+       {
+         /* Otherwise consolidate all the nonconstant terms. */
+         m = xmalloc (sizeof (struct nonterm_node)
+                      + ((nvar + approx_ne (cval, def) - 1)
+                         * sizeof (union any_node *)));
+         for (i = c = 0; i < n->n; i++)
+           if (n->arg[i]->type != OP_NUM_CON)
+             m->arg[c++] = n->arg[i];
+           else
+             free_node (n->arg[i]);
+
+         if (approx_ne (cval, def))
+           {
+             m->arg[c] = xmalloc (sizeof (struct num_con_node));
+             m->arg[c]->num_con.type = OP_NUM_CON;
+             m->arg[c]->num_con.value = cval;
+             c++;
+           }
+
+         m->type = n->type;
+         m->n = c;
+       }
+      free (n);
+      n = m;
+    }
+  else if (n->type == OP_POW)
+    {
+      if (n->arg[1]->type == OP_NUM_CON)
+       {
+         if (approx_eq (n1, 1.0))
+           {
+             struct nonterm_node *m = (struct nonterm_node *) n->arg[0];
+
+             free_node (n->arg[1]);
+             free (n);
+             return m;
+           }
+         else if (approx_eq (n1, 2.0))
+           {
+             n = xrealloc (n, sizeof (struct nonterm_node));
+             n->type = OP_SQUARE;
+             n->n = 1;
+           }
+       }
+    }
+  return n;
+}
+
+#define rnc(D)                                 \
+       (n = repl_num_con (n, D))
+     
+#define frnc(D)                                        \
+       (n = force_repl_num_con (n, D))
+
+/* Finds the first NEEDLE of length NEEDLE_LEN in a HAYSTACK of length
+   HAYSTACK_LEN.  Returns a 1-based index, 0 on failure. */
+static inline int
+str_search (char *haystack, int haystack_len, char *needle, int needle_len)
+{
+  char *p = memmem (haystack, haystack_len, needle, needle_len);
+  return p ? p - haystack + 1 : 0;
+}
+
+/* Finds the last NEEDLE of length NEEDLE_LEN in a HAYSTACK of length
+   HAYSTACK_LEN.  Returns a 1-based index, 0 on failure. */
+static inline int
+str_rsearch (char *haystack, int haystack_len, char *needle, int needle_len)
+{
+  char *p = mm_find_reverse (haystack, haystack_len, needle, needle_len);
+  return p ? p - haystack + 1 : 0;
+}
+
+static struct nonterm_node *
+evaluate_tree (struct nonterm_node * n)
+{
+  static char *strbuf;
+  int add;
+  int len;
+  int i;
+
+  if (!strbuf)
+    strbuf = xmalloc (256);
+  errno = 0;
+
+  switch (n->type)
+    {
+    case OP_PLUS:
+    case OP_MUL:
+      return optimize_tree (n);
+
+    case OP_POW:
+      if (approx_eq (n0, 0.0) && approx_eq (n1, 0.0))
+       frnc (SYSMIS);
+      else if (n0 == SYSMIS && n1 == 0.0)
+       frnc (1.0);
+      else if (n0 == 0.0 && n1 == SYSMIS)
+       frnc (0.0);
+      else
+       rnc (pow (n0, n1));
+      break;
+
+    case OP_AND:
+      if (n0 == 0.0 || n1 == 0.0)
+       frnc (0.0);
+      else if (n0 == SYSMIS || n1 == SYSMIS)
+       frnc (SYSMIS);
+      else
+       frnc (1.0);
+      break;
+    case OP_OR:
+      if (n0 == 1.0 || n1 == 1.0)
+       frnc (1.0);
+      else if (n0 == SYSMIS || n1 == SYSMIS)
+       frnc (SYSMIS);
+      else
+       frnc (0.0);
+      break;
+    case OP_NOT:
+      rnc (n0 == 0.0 ? 1.0 : 0.0);
+      break;
+
+    case OP_EQ:
+      rnc (approx_eq (n0, n1));
+      break;
+    case OP_GE:
+      rnc (approx_ge (n0, n1));
+      break;
+    case OP_GT:
+      rnc (approx_gt (n0, n1));
+      break;
+    case OP_LE:
+      rnc (approx_le (n0, n1));
+      break;
+    case OP_LT:
+      rnc (approx_lt (n0, n1));
+      break;
+    case OP_NE:
+      rnc (approx_ne (n0, n1));
+      break;
+
+      /* String operators. */
+    case OP_STRING_EQ:
+      rnc (st_compare_pad (s0, s0l, s1, s1l) == 0);
+      break;
+    case OP_STRING_GE:
+      rnc (st_compare_pad (s0, s0l, s1, s1l) >= 0);
+      break;
+    case OP_STRING_GT:
+      rnc (st_compare_pad (s0, s0l, s1, s1l) > 0);
+      break;
+    case OP_STRING_LE:
+      rnc (st_compare_pad (s0, s0l, s1, s1l) <= 0);
+      break;
+    case OP_STRING_LT:
+      rnc (st_compare_pad (s0, s0l, s1, s1l) < 0);
+      break;
+    case OP_STRING_NE:
+      rnc (st_compare_pad (s0, s0l, s1, s1l) != 0);
+      break;
+
+      /* Unary functions. */
+    case OP_NEG:
+      rnc (-n0);
+      break;
+    case OP_ABS:
+      rnc (fabs (n0));
+      break;
+    case OP_ARCOS:
+      rnc (acos (n0));
+      break;
+    case OP_ARSIN:
+      rnc (asin (n0));
+      break;
+    case OP_ARTAN:
+      rnc (atan (n0));
+      break;
+    case OP_COS:
+      rnc (cos (n0));
+      break;
+    case OP_EXP:
+      rnc (exp (n0));
+      break;
+    case OP_LG10:
+      rnc (log10 (n0));
+      break;
+    case OP_LN:
+      rnc (log (n0));
+      break;
+    case OP_MOD10:
+      rnc (fmod (n0, 10));
+      break;
+    case OP_RND:
+      rnc (n0 >= 0.0 ? floor (n0 + 0.5) : -floor (-n0 + 0.5));
+      break;
+    case OP_SIN:
+      rnc (sin (n0));
+      break;
+    case OP_SQRT:
+      rnc (sqrt (n0));
+      break;
+    case OP_TAN:
+      rnc (tan (n0));
+      break;
+    case OP_TRUNC:
+      rnc (n0 >= 0.0 ? floor (n0) : -floor (-n0));
+      break;
+
+      /* N-ary numeric functions. */
+    case OP_ANY:
+      if (n0 == SYSMIS)
+       frnc (SYSMIS);
+      else
+       {
+         int sysmis = 1;
+         double ni;
+
+         for (i = 1; i < n->n; i++)
+           {
+             ni = n->arg[i]->num_con.value;
+             if (approx_eq (n0, ni))
+               {
+                 frnc (1.0);
+                 goto any_done;
+               }
+             if (ni != SYSMIS)
+               sysmis = 0;
+           }
+         frnc (sysmis ? SYSMIS : 0.0);
+       }
+    any_done:
+      break;
+    case OP_ANY_STRING:
+      for (i = 1; i < n->n; i++)
+       if (!st_compare_pad (n->arg[0]->str_con.s, n->arg[0]->str_con.len,
+                            n->arg[i]->str_con.s, n->arg[i]->str_con.len))
+         {
+           frnc (1.0);
+           goto any_string_done;
+         }
+      frnc (0.0);
+    any_string_done:
+      break;
+
+    case OP_CFVAR:
+    case OP_MAX:
+    case OP_MEAN:
+    case OP_MIN:
+    case OP_NMISS:
+    case OP_NVALID:
+    case OP_SD:
+    case OP_SUM:
+    case OP_VARIANCE:
+      {
+       double d[2] =
+       {0.0, 0.0};             /* sum, sum of squares */
+       double min = DBL_MAX;   /* minimum value */
+       double max = -DBL_MAX;  /* maximum value */
+       double ni;              /* value of i'th argument */
+       int nv = 0;             /* number of valid arguments */
+
+       for (i = 0; i < n->n; i++)
+         {
+           ni = n->arg[i]->num_con.value;
+           if (ni != SYSMIS)
+             {
+               nv++;
+               d[0] += ni;
+               d[1] += ni * ni;
+               if (ni < min)
+                 min = ni;
+               if (ni > max)
+                 max = ni;
+             }
+         }
+       if (n->type == OP_NMISS)
+         frnc (i - nv);
+       else if (n->type == OP_NVALID)
+         frnc (nv);
+       else if (nv >= (int) n->arg[i])
+         {
+           switch (n->type)
+             {
+             case OP_CFVAR:
+               frnc (calc_cfvar (d, nv));
+               break;
+             case OP_MAX:
+               frnc (max);
+               break;
+             case OP_MEAN:
+               frnc (calc_mean (d, nv));
+               break;
+             case OP_MIN:
+               frnc (min);
+               break;
+             case OP_SD:
+               frnc (calc_stddev (calc_variance (d, nv)));
+               break;
+             case OP_SUM:
+               frnc (d[0]);
+               break;
+             case OP_VARIANCE:
+               frnc (calc_variance (d, nv));
+               break;
+             }
+         }
+       else
+         frnc (SYSMIS);
+      }
+      break;
+    case OP_RANGE:
+      if (n0 == SYSMIS)
+       frnc (SYSMIS);
+      else
+       {
+         double min, max;
+         int sysmis = 1;
+
+         for (i = 1; i < n->n; i += 2)
+           {
+             min = n->arg[i]->num_con.value;
+             max = n->arg[i + 1]->num_con.value;
+             if (min == SYSMIS || max == SYSMIS)
+               continue;
+             sysmis = 0;
+             if (approx_ge (n0, min) && approx_le (n0, max))
+               {
+                 frnc (1.0);
+                 goto range_done;
+               }
+           }
+         frnc (sysmis ? SYSMIS : 0.0);
+       }
+    range_done:
+      break;
+    case OP_RANGE_STRING:
+      for (i = 1; i < n->n; i += 2)
+       if (st_compare_pad (n->arg[0]->str_con.s, n->arg[0]->str_con.len,
+                           n->arg[i]->str_con.s, n->arg[i]->str_con.len) >= 0
+           && st_compare_pad (n->arg[0]->str_con.s, n->arg[0]->str_con.len,
+                              n->arg[i + 1]->str_con.s,
+                              n->arg[i + 1]->str_con.len) <= 0)
+         {
+           frnc (1.0);
+           goto range_str_done;
+         }
+      frnc (0.0);
+    range_str_done:
+      break;
+
+      /* Time function. */
+    case OP_TIME_HMS:
+      rnc (60. * (60. * n0 + n1) + n2);
+      break;
+
+      /* Date construction functions. */
+    case OP_DATE_DMY:
+      rnc (60. * 60. * 24. * yrmoda (n2, n1, n0));
+      break;
+    case OP_DATE_MDY:
+      rnc (60. * 60. * 24. * yrmoda (n2, n0, n1));
+      break;
+    case OP_DATE_MOYR:
+      rnc (60. * 60. * 24. * yrmoda (n1, n0, 1));
+      break;
+    case OP_DATE_QYR:
+      rnc (60. * 60. * 24. * yrmoda (n1, 3 * (int) n0 - 2, 1));
+      break;
+    case OP_DATE_WKYR:
+      {
+       double t = yrmoda (n1, 1, 1);
+       if (t != SYSMIS)
+         t = 60. * 60. * 24. * (t + 7. * (n0 - 1));
+       rnc (t);
+      }
+      break;
+    case OP_DATE_YRDAY:
+      {
+       double t = yrmoda (n0, 1, 1);
+       if (t != SYSMIS)
+         t = 60. * 60. * 24. * (t + n0 - 1);
+       rnc (t);
+      }
+      break;
+    case OP_YRMODA:
+      rnc (yrmoda (n0, n1, n2));
+      break;
+      /* Date extraction functions. */
+    case OP_XDATE_DATE:
+      rnc (floor (n0 / 60. / 60. / 24.) * 60. * 60. * 24.);
+      break;
+    case OP_XDATE_HOUR:
+      rnc (fmod (floor (n0 / 60. / 60.), 24.));
+      break;
+    case OP_XDATE_JDAY:
+      rnc (julian_to_jday (n0 / 86400.));
+      break;
+    case OP_XDATE_MDAY:
+      {
+       int day;
+       julian_to_calendar (n0 / 86400., NULL, NULL, &day);
+       rnc (day);
+      }
+      break;
+    case OP_XDATE_MINUTE:
+      rnc (fmod (floor (n0 / 60.), 60.));
+      break;
+    case OP_XDATE_MONTH:
+      {
+       int month;
+       julian_to_calendar (n0 / 86400., NULL, &month, NULL);
+       rnc (month);
+      }
+      break;
+    case OP_XDATE_QUARTER:
+      {
+       int month;
+       julian_to_calendar (n0 / 86400., NULL, &month, NULL);
+       rnc ((month - 1) / 3 + 1);
+      }
+      break;
+    case OP_XDATE_SECOND:
+      rnc (fmod (n0, 60.));
+      break;
+    case OP_XDATE_TDAY:
+      rnc (floor (n0 / 60. / 60. / 24.));
+      break;
+    case OP_XDATE_TIME:
+      rnc (n0 - floor (n0 / 60. / 60. / 24.) * 60. * 60. * 24.);
+      break;
+    case OP_XDATE_WEEK:
+      rnc ((julian_to_jday (n0) - 1) / 7 + 1);
+      break;
+    case OP_XDATE_WKDAY:
+      rnc (julian_to_wday (n0));
+      break;
+    case OP_XDATE_YEAR:
+      {
+       int year;
+       julian_to_calendar (n0 / 86400., &year, NULL, NULL);
+       rnc (year);
+      }
+      break;
+
+      /* String functions. */
+    case OP_CONCAT:
+      {
+       len = s0l;
+       memcpy (strbuf, s0, len);
+       for (i = 1; i < n->n; i++)
+         {
+           add = sl (i);
+           if (add + len > 255)
+             add = 255 - len;
+           memcpy (&strbuf[len], s (i), add);
+           len += add;
+         }
+       n = repl_str_con (n, strbuf, len);
+      }
+      break;
+    case OP_INDEX:
+      rnc (s1l ? str_search (s0, s0l, s1, s1l) : SYSMIS);
+      break;
+    case OP_INDEX_OPT:
+      if (n2 == SYSMIS || (int) n2 <= 0 || s1l % (int) n2)
+       {
+         msg (SW, _("While optimizing a constant expression, there was "
+              "a bad value for the third argument to INDEX."));
+         frnc (SYSMIS);
+       }
+      else
+       {
+         int pos = 0;
+         int c = s1l / (int) n2;
+         int r;
+
+         for (i = 0; i < c; i++)
+           {
+             r = str_search (s0, s0l, s (i), sl (i));
+             if (r < pos || pos == 0)
+               pos = r;
+           }
+         frnc (pos);
+       }
+      break;
+    case OP_RINDEX:
+      rnc (str_rsearch (s0, s0l, s1, s1l));
+      break;
+    case OP_RINDEX_OPT:
+      if (n2 == SYSMIS || (int) n2 <= 0 || s1l % (int) n2)
+       {
+         msg (SE, _("While optimizing a constant expression, there was "
+              "a bad value for the third argument to RINDEX."));
+         frnc (SYSMIS);
+       }
+      else
+       {
+         int pos = 0;
+         int c = s1l / n2;
+         int r;
+
+         for (i = 0; i < c; i++)
+           {
+             r = str_rsearch (s0, s0l, s (i), sl (i));
+             if (r > pos)
+               pos = r;
+           }
+         frnc (pos);
+       }
+      break;
+    case OP_LENGTH:
+      frnc (s0l);
+      break;
+    case OP_LOWER:
+      {
+       char *cp;
+       for (cp = &s0[s0l]; cp >= s0; cp--)
+         *cp = tolower ((unsigned char) (*cp));
+       n = repl_str_con (n, s0, s0l);
+      }
+      break;
+    case OP_UPPER:
+      {
+       char *cp;
+       for (cp = &s0[s0[0] + 1]; cp > s0; cp--)
+         *cp = toupper ((unsigned char) (*cp));
+       n = repl_str_con (n, s0, s0l);
+      }
+      break;
+    case OP_LPAD:
+    case OP_LPAD_OPT:
+    case OP_RPAD:
+    case OP_RPAD_OPT:
+      {
+       int c;
+
+       if (n1 == SYSMIS)
+         {
+           n = repl_str_con (n, NULL, 0);
+           break;
+         }
+       len = n1;
+       len = range (len, 1, 255);
+       add = max (n1 - s0l, 0);
+
+       if (n->type == OP_LPAD_OPT || n->type == OP_RPAD_OPT)
+         {
+           if (s2l < 1)
+             {
+               c = n->type == OP_LPAD_OPT ? 'L' : 'R';
+               msg (SE, _("Third argument to %cPAD() must be at least one "
+                    "character in length."), c);
+               c = ' ';
+             }
+           else
+             c = s2[0];
+         }
+       else
+         c = ' ';
+
+       if (n->type == OP_LPAD || n->type == OP_LPAD_OPT)
+         memmove (&s0[add], s0, len);
+       if (n->type == OP_LPAD || n->type == OP_LPAD_OPT)
+         memset (s0, c, add);
+       else
+         memset (&s0[s0l], c, add);
+
+       n = repl_str_con (n, s0, len);
+      }
+      break;
+    case OP_LTRIM:
+    case OP_LTRIM_OPT:
+    case OP_RTRIM:
+    case OP_RTRIM_OPT:
+      {
+       int c;
+       char *cp = s0;
+
+       if (n->type == OP_LTRIM_OPT || n->type == OP_RTRIM_OPT)
+         {
+           if (s1l < 1)
+             {
+               c = n->type == OP_LTRIM_OPT ? 'L' : 'R';
+               msg (SE, _("Second argument to %cTRIM() must be at least one "
+                    "character in length."), c);
+               c = ' ';
+             }
+           else
+             c = s1[0];
+         }
+       len = s0l;
+       if (n->type == OP_LTRIM || n->type == OP_LTRIM_OPT)
+         {
+           while (*cp == c && cp < &s0[len])
+             cp++;
+           len -= cp - s0;
+         }
+       else
+         while (len > 0 && s0[len - 1] == c)
+           len--;
+       n = repl_str_con (n, cp, len);
+      }
+      break;
+    case OP_NUMBER:
+    case OP_NUMBER_OPT:
+      {
+       union value v;
+       struct data_in di;
+
+       di.s = s0;
+       di.e = s0 + s0l;
+       di.v = &v;
+       di.flags = DI_IGNORE_ERROR;
+       di.f1 = 1;
+
+       if (n->type == OP_NUMBER_OPT)
+         {
+           di.format.type = (int) n->arg[1];
+           di.format.w = (int) n->arg[2];
+           di.format.d = (int) n->arg[3];
+         }
+       else
+         {
+           di.format.type = FMT_F;
+           di.format.w = s0l;
+           di.format.d = 0;
+         }
+       
+       data_in (&di);
+       frnc (v.f);
+      }
+      break;
+    case OP_STRING:
+      {
+       union value v;
+       struct fmt_spec f;
+       f.type = (int) n->arg[1];
+       f.w = (int) n->arg[2];
+       f.d = (int) n->arg[3];
+       v.f = n0;
+
+       data_out (strbuf, &f, &v);
+       n = repl_str_con (n, strbuf, f.w);
+      }
+      break;
+    case OP_SUBSTR:
+    case OP_SUBSTR_OPT:
+      {
+       int pos = (int) n1;
+       if (pos > s0l || pos <= 0 || n1 == SYSMIS
+           || (n->type == OP_SUBSTR_OPT && n2 == SYSMIS))
+         n = repl_str_con (n, NULL, 0);
+       else
+         {
+           if (n->type == OP_SUBSTR_OPT)
+             {
+               len = (int) n2;
+               if (len + pos - 1 > s0l)
+                 len = s0l - pos + 1;
+             }
+           else
+             len = s0l - pos + 1;
+           n = repl_str_con (n, &s0[pos - 1], len);
+         }
+      }
+      break;
+
+      /* Weirdness. */
+    case OP_INV:
+      rnc (1.0 / n0);
+      break;
+    case OP_MOD:
+      if (approx_eq (n0, 0.0) && n1 == SYSMIS)
+       frnc (0.0);
+      else
+       rnc (fmod (n0, n1));
+      break;
+    case OP_NUM_TO_BOOL:
+      if (approx_eq (n0, 0.0))
+       n0 = 0.0;
+      else if (approx_eq (n0, 1.0))
+       n0 = 1.0;
+      else if (n0 != SYSMIS)
+       {
+         msg (SE, _("When optimizing a constant expression, an integer "
+              "that was being used as an Boolean value was found "
+              "to have a constant value other than 0, 1, or SYSMIS."));
+         n0 = 0.0;
+       }
+      rnc (n0);
+      break;
+
+#if __CHECKER__
+      /* This case prevents Checker from choking. */
+    case 42000:
+      assert (0);
+#endif
+    }
+  return n;
+}
+
+#undef n0
+#undef n1
+#undef n2
+
+#undef s0
+#undef s0l
+#undef s1
+#undef s1l
+#undef s2
+#undef s2l
+#undef s
+#undef sl
+
+#undef rnc
+#undef frnc
+
+static struct nonterm_node *
+repl_num_con (struct nonterm_node * n, double d)
+{
+  int i;
+  if (!finite (d) || errno)
+    d = SYSMIS;
+  else
+    for (i = 0; i < n->n; i++)
+      if (n->arg[i]->type == OP_NUM_CON && n->arg[i]->num_con.value == SYSMIS)
+       {
+         d = SYSMIS;
+         break;
+       }
+  return force_repl_num_con (n, d);
+}
+
+static struct nonterm_node *
+force_repl_num_con (struct nonterm_node * n, double d)
+{
+  struct num_con_node *num;
+
+  if (!finite (d) || errno)
+    d = SYSMIS;
+  free_node ((union any_node *) n);
+  num = xmalloc (sizeof *num);
+  num->type = OP_NUM_CON;
+  num->value = d;
+  return (struct nonterm_node *) num;
+}
+
+static struct nonterm_node *
+repl_str_con (struct nonterm_node * n, char *s, int len)
+{
+  struct str_con_node *str;
+
+  /* The ordering here is important since the source string may be
+     part of a subnode of n. */
+  str = xmalloc (sizeof *str + len - 1);
+  str->type = OP_STR_CON;
+  str->len = len;
+  memcpy (str->s, s, len);
+  free_node ((union any_node *) n);
+  return (struct nonterm_node *) str;
+}
+
+/* Returns the number of days since 10 Oct 1582 for the date
+   YEAR/MONTH/DAY, where YEAR is in range 0..199 or 1582..19999, MONTH
+   is in 1..12, and DAY is in 1..31. */
+double
+yrmoda (double year, double month, double day)
+{
+  if (year == SYSMIS || month == SYSMIS || day == SYSMIS)
+    return SYSMIS;
+
+  /* The addition of EPSILON avoids converting, for example,
+     1991.9999997=>1991. */
+  year = floor (year + EPSILON);
+  month = floor (month + EPSILON);
+  day = floor (day + EPSILON);
+
+  if (year >= 0. && year <= 199.)
+    year += 1900.;
+  if ((year < 1582. || year > 19999.)
+      || (year == 1582. && (month < 10. || (month == 10. && day < 15.)))
+      || (month < -1 || month > 13)
+      || (day < -1 || day > 32))
+    return SYSMIS;
+  return calendar_to_julian (year, month, day);
+}
+\f
+/* Expression dumper. */
+
+static struct expression *e;
+static int nop, mop;
+static int ndbl, mdbl;
+static int nstr, mstr;
+static int nvars, mvars;
+
+static void dump_node (union any_node * n);
+static void emit (int);
+static void emit_num_con (double);
+static void emit_str_con (char *, int);
+static void emit_var (struct variable *);
+
+void
+dump_expression (union any_node * n, struct expression * expr)
+{
+  unsigned char *o;
+
+  int height = 0;
+
+  int max_height = 0;
+
+  e = expr;
+  e->op = NULL;
+  e->num = NULL;
+  e->str = NULL;
+  e->var = NULL;
+  nop = mop = 0;
+  ndbl = mdbl = 0;
+  nstr = mstr = 0;
+  nvars = mvars = 0;
+  dump_node (n);
+  emit (OP_SENTINEL);
+
+  /* Now compute the stack height needed to evaluate the expression. */
+  for (o = e->op; *o != OP_SENTINEL; o++)
+    {
+      if (ops[*o].flags & OP_VAR_ARGS)
+       height += 1 - o[1];
+      else
+       height += ops[*o].height;
+      o += ops[*o].skip;
+      if (height > max_height)
+       max_height = height;
+    }
+
+  /* ANSI says we have to waste space for one `value' since pointers
+     are not guaranteed to be able to point to a spot *before* a
+     block.  If only all the world were a VAX... */
+  max_height++;
+
+  e->stack = xmalloc (max_height * sizeof *e->stack);
+
+#if PAGED_STACK
+  e->str_stack = e->type == EX_STRING ? xmalloc (256) : NULL;
+#else
+  e->str_stack = xmalloc (256);
+  e->str_size = 256;
+#endif
+}
+
+static void
+dump_node (union any_node * n)
+{
+  if (n->type == OP_AND || n->type == OP_OR)
+    {
+      int i;
+
+      dump_node (n->nonterm.arg[0]);
+      for (i = 1; i < n->nonterm.n; i++)
+       {
+         dump_node (n->nonterm.arg[i]);
+         emit (n->type);
+       }
+      return;
+    }
+  else if (n->type < OP_TERMINAL)
+    {
+      int i;
+      for (i = 0; i < n->nonterm.n; i++)
+       dump_node (n->nonterm.arg[i]);
+      emit (n->type);
+      if (ops[n->type].flags & OP_VAR_ARGS)
+       emit (n->nonterm.n);
+      if (ops[n->type].flags & OP_MIN_ARGS)
+       emit ((int) n->nonterm.arg[n->nonterm.n]);
+      if (ops[n->type].flags & OP_FMT_SPEC)
+       {
+         emit ((int) n->nonterm.arg[n->nonterm.n]);
+         emit ((int) n->nonterm.arg[n->nonterm.n + 1]);
+         emit ((int) n->nonterm.arg[n->nonterm.n + 2]);
+       }
+      return;
+    }
+
+  emit (n->type);
+  if (n->type == OP_NUM_CON)
+    emit_num_con (n->num_con.value);
+  else if (n->type == OP_STR_CON)
+    emit_str_con (n->str_con.s, n->str_con.len);
+  else if (n->type == OP_NUM_VAR || n->type == OP_STR_VAR
+          || n->type == OP_STR_MIS)
+    emit_var (n->var.v);
+  else if (n->type == OP_NUM_LAG || n->type == OP_STR_LAG)
+    {
+      emit_var (n->lag.v);
+      emit (n->lag.lag);
+    }
+  else if (n->type == OP_NUM_SYS || n->type == OP_NUM_VAL)
+    emit (n->var.v->fv);
+  else
+    assert (n->type == OP_CASENUM);
+}
+
+static void
+emit (int op)
+{
+  if (nop >= mop)
+    {
+      mop += 16;
+      e->op = xrealloc (e->op, mop * sizeof *e->op);
+    }
+  e->op[nop++] = op;
+}
+
+static void
+emit_num_con (double dbl)
+{
+  if (ndbl >= mdbl)
+    {
+      mdbl += 16;
+      e->num = xrealloc (e->num, mdbl * sizeof *e->num);
+    }
+  e->num[ndbl++] = dbl;
+}
+
+static void
+emit_str_con (char *str, int len)
+{
+  if (nstr + len + 1 > mstr)
+    {
+      mstr += 256;
+      e->str = xrealloc (e->str, mstr);
+    }
+  e->str[nstr++] = len;
+  memcpy (&e->str[nstr], str, len);
+  nstr += len;
+}
+
+static void
+emit_var (struct variable * v)
+{
+  if (nvars >= mvars)
+    {
+      mvars += 16;
+      e->var = xrealloc (e->var, mvars * sizeof *e->var);
+    }
+  e->var[nvars++] = v;
+}
diff --git a/src/expr-prs.c b/src/expr-prs.c
new file mode 100644 (file)
index 0000000..d447afd
--- /dev/null
@@ -0,0 +1,1805 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <ctype.h>
+#include <float.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "error.h"
+#include "expr.h"
+#include "exprP.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+#include "var.h"
+#include "vector.h"
+#include "vfm.h"
+\f
+/* Declarations. */
+
+/* Lowest precedence. */
+static int parse_or (union any_node **n);
+static int parse_and (union any_node **n);
+static int parse_not (union any_node **n);
+static int parse_rel (union any_node **n);
+static int parse_add (union any_node **n);
+static int parse_mul (union any_node **n);
+static int parse_neg (union any_node **n);
+static int parse_exp (union any_node **n);
+static int parse_primary (union any_node **n);
+static int parse_function (union any_node **n);
+/* Highest precedence. */
+
+/* Utility functions. */
+static const char *expr_type_name (int type);
+static const char *type_name (int type);
+static void make_bool (union any_node **n);
+static union any_node *allocate_nonterminal (int op, union any_node *n);
+static union any_node *append_nonterminal_arg (union any_node *,
+                                              union any_node *);
+static int type_check (union any_node **n, int type, int flags);
+
+static void init_func_tab (void);
+static int cmp_func (const void *a, const void *b);
+
+#if DEBUGGING
+static void debug_print_tree (union any_node *, int);
+#endif
+
+#if GLOBAL_DEBUGGING
+static void debug_print_postfix (struct expression *);
+#endif
+\f
+/* Public functions. */
+
+void
+expr_free (struct expression *e)
+{
+  if (e == NULL)
+    return;
+
+  free (e->op);
+  free (e->var);
+  free (e->num);
+  free (e->str);
+  free (e->stack);
+  free (e->str_stack);
+  free (e);
+}
+
+struct expression *
+expr_parse (int flags)
+{
+  struct expression *e;
+  union any_node *n;
+  int type;
+
+  /* Make sure the table of functions is initialized. */
+  init_func_tab ();
+
+  /* Parse the expression. */
+  type = parse_or (&n);
+  if (type == EX_ERROR)
+    return NULL;
+
+  /* Enforce type rules. */
+  if (!type_check (&n, type, flags))
+    {
+      free_node (n);
+      return NULL;
+    }
+
+  /* Optimize the expression as best we can. */
+  n = (union any_node *) optimize_expression ((struct nonterm_node *) n);
+
+  /* Dump the tree-based expression to a postfix representation for
+     best evaluation speed, and destroy the tree. */
+  e = xmalloc (sizeof *e);
+  e->type = type;
+  dump_expression (n, e);
+  free_node (n);
+
+  /* If we're debugging or the user requested it, print the postfix
+     representation. */
+#if GLOBAL_DEBUGGING
+#if !DEBUGGING
+  if (flags & PXP_DUMP)
+#endif
+    debug_print_postfix (e);
+#endif
+
+  return e;
+}
+
+static int
+type_check (union any_node **n, int type, int flags)
+{
+  /* Enforce PXP_BOOLEAN flag. */
+  if (flags & PXP_BOOLEAN)
+    {
+      if (type == EX_STRING)
+       {
+         msg (SE, _("A string expression was supplied in a place "
+                    "where a Boolean expression was expected."));
+         return 0;
+       }
+      else if (type == EX_NUMERIC)
+       *n = allocate_nonterminal (OP_NUM_TO_BOOL, *n);
+    }
+  
+  /* Enforce PXP_NUMERIC flag. */
+  if ((flags & PXP_NUMERIC) && (type != EX_NUMERIC))
+    {
+      msg (SE, _("A numeric expression was expected in a place "
+                "where one was not supplied."));
+      return 0;
+    }
+
+  /* Enforce PXP_STRING flag. */
+  if ((flags & PXP_STRING) && (type != EX_STRING))
+    {
+      msg (SE, _("A string expression was expected in a place "
+                "where one was not supplied."));
+      return 0;
+    }
+
+  return 1;
+}
+\f
+/* Recursive-descent expression parser. */
+
+/* Parses the OR level. */
+static int
+parse_or (union any_node **n)
+{
+  char typ[] = N_("The OR operator cannot take string operands.");
+  union any_node *c;
+  int type;
+
+  type = parse_and (n);
+  if (type == EX_ERROR || token != T_OR)
+    return type;
+  if (type == EX_STRING)
+    {
+      free_node (*n);
+      msg (SE, gettext (typ));
+      return 0;
+    }
+  else if (type == EX_NUMERIC)
+    make_bool (n);
+
+  c = allocate_nonterminal (OP_OR, *n);
+  for (;;)
+    {
+      lex_get ();
+      type = parse_and (n);
+      if (type == EX_ERROR)
+       goto fail;
+      else if (type == EX_STRING)
+       {
+         msg (SE, gettext (typ));
+         goto fail;
+       }
+      else if (type == EX_NUMERIC)
+       make_bool (n);
+      c = append_nonterminal_arg (c, *n);
+
+      if (token != T_OR)
+       break;
+    }
+  *n = c;
+  return EX_BOOLEAN;
+
+fail:
+  free_node (c);
+  return EX_ERROR;
+}
+
+/* Parses the AND level. */
+static int
+parse_and (union any_node ** n)
+{
+  static const char typ[]
+    = N_("The AND operator cannot take string operands.");
+  union any_node *c;
+  int type = parse_not (n);
+
+  if (type == EX_ERROR)
+    return EX_ERROR;
+  if (token != T_AND)
+    return type;
+  if (type == EX_STRING)
+    {
+      free_node (*n);
+      msg (SE, gettext (typ));
+      return 0;
+    }
+  else if (type == EX_NUMERIC)
+    make_bool (n);
+
+  c = allocate_nonterminal (OP_AND, *n);
+  for (;;)
+    {
+      lex_get ();
+      type = parse_not (n);
+      if (type == EX_ERROR)
+       goto fail;
+      else if (type == EX_STRING)
+       {
+         msg (SE, gettext (typ));
+         goto fail;
+       }
+      else if (type == EX_NUMERIC)
+       make_bool (n);
+      c = append_nonterminal_arg (c, *n);
+
+      if (token != T_AND)
+       break;
+    }
+  *n = c;
+  return EX_BOOLEAN;
+
+fail:
+  free_node (c);
+  return EX_ERROR;
+}
+
+/* Parses the NOT level. */
+static int
+parse_not (union any_node ** n)
+{
+  static const char typ[]
+    = N_("The NOT operator cannot take a string operand.");
+  int not = 0;
+  int type;
+
+  while (lex_match (T_NOT))
+    not ^= 1;
+  type = parse_rel (n);
+  if (!not || type == EX_ERROR)
+    return type;
+
+  if (type == EX_STRING)
+    {
+      free_node (*n);
+      msg (SE, gettext (typ));
+      return 0;
+    }
+  else if (type == EX_NUMERIC)
+    make_bool (n);
+
+  *n = allocate_nonterminal (OP_NOT, *n);
+  return EX_BOOLEAN;
+}
+
+static int
+parse_rel (union any_node ** n)
+{
+  static const char typ[]
+    = N_("Strings cannot be compared with numeric or Boolean "
+        "values with the relational operators "
+        "= >= > <= < <>.");
+  union any_node *c;
+  int type = parse_add (n);
+
+  if (type == EX_ERROR)
+    return EX_ERROR;
+  if (token == '=')
+    token = T_EQ;
+  if (token < T_EQ || token > T_NE)
+    return type;
+
+  for (;;)
+    {
+      int t;
+
+      c = allocate_nonterminal (token - T_EQ
+                               + (type == EX_NUMERIC ? OP_EQ : OP_STRING_EQ),
+                               *n);
+      lex_get ();
+
+      t = parse_add (n);
+      if (t == EX_ERROR)
+       goto fail;
+      if (t == EX_BOOLEAN && type == EX_NUMERIC)
+       make_bool (&c->nonterm.arg[0]);
+      else if (t == EX_NUMERIC && type == EX_BOOLEAN)
+       make_bool (n);
+      else if (t != type)
+       {
+         msg (SE, gettext (typ));
+         goto fail;
+       }
+
+      c = append_nonterminal_arg (c, *n);
+      *n = c;
+
+      if (token == '=')
+       token = T_EQ;
+      if (token < T_EQ || token > T_NE)
+       break;
+
+      type = EX_BOOLEAN;
+    }
+  return EX_BOOLEAN;
+
+fail:
+  free_node (c);
+  return EX_ERROR;
+}
+
+/* Parses the addition and subtraction level. */
+static int
+parse_add (union any_node **n)
+{
+  static const char typ[]
+    = N_("The `+' and `-' operators may only be used with "
+        "numeric operands.");
+  union any_node *c;
+  int type;
+  int op;
+
+  type = parse_mul (n);
+  lex_negative_to_dash ();
+  if (type == EX_ERROR || (token != '+' && token != '-'))
+    return type;
+  if (type != EX_NUMERIC)
+    {
+      free_node (*n);
+      msg (SE, gettext (typ));
+      return 0;
+    }
+
+  c = allocate_nonterminal (OP_PLUS, *n);
+  for (;;)
+    {
+      op = token;
+      lex_get ();
+
+      type = parse_mul (n);
+      if (type == EX_ERROR)
+       goto fail;
+      else if (type != EX_NUMERIC)
+       {
+         msg (SE, gettext (typ));
+         goto fail;
+       }
+      if (op == '-')
+       *n = allocate_nonterminal (OP_NEG, *n);
+      c = append_nonterminal_arg (c, *n);
+
+      lex_negative_to_dash ();
+      if (token != '+' && token != '-')
+       break;
+    }
+  *n = c;
+  return EX_NUMERIC;
+
+fail:
+  free_node (c);
+  return EX_ERROR;
+}
+
+/* Parses the multiplication and division level. */
+static int
+parse_mul (union any_node ** n)
+{
+  static const char typ[]
+    = N_("The `*' and `/' operators may only be used with "
+        "numeric operands.");
+
+  union any_node *c;
+  int type;
+  int op;
+
+  type = parse_neg (n);
+  if (type == EX_ERROR || (token != '*' && token != '/'))
+    return type;
+  if (type != EX_NUMERIC)
+    {
+      free_node (*n);
+      msg (SE, gettext (typ));
+      return 0;
+    }
+
+  c = allocate_nonterminal (OP_MUL, *n);
+  for (;;)
+    {
+      op = token;
+      lex_get ();
+
+      type = parse_neg (n);
+      if (type == EX_ERROR)
+       goto fail;
+      else if (type != EX_NUMERIC)
+       {
+         msg (SE, gettext (typ));
+         goto fail;
+       }
+      if (op == '/')
+       *n = allocate_nonterminal (OP_INV, *n);
+      c = append_nonterminal_arg (c, *n);
+
+      if (token != '*' && token != '/')
+       break;
+    }
+  *n = c;
+  return EX_NUMERIC;
+
+fail:
+  free_node (c);
+  return EX_ERROR;
+}
+
+/* Parses the unary minus level. */
+static int
+parse_neg (union any_node **n)
+{
+  static const char typ[]
+    = N_("The unary minus (-) operator can only take a numeric operand.");
+
+  int neg = 0;
+  int type;
+
+  for (;;)
+    {
+      lex_negative_to_dash ();
+      if (!lex_match ('-'))
+       break;
+      neg ^= 1;
+    }
+  type = parse_exp (n);
+  if (!neg || type == EX_ERROR)
+    return type;
+  if (type != EX_NUMERIC)
+    {
+      free_node (*n);
+      msg (SE, gettext (typ));
+      return 0;
+    }
+
+  *n = allocate_nonterminal (OP_NEG, *n);
+  return EX_NUMERIC;
+}
+
+static int
+parse_exp (union any_node **n)
+{
+  static const char typ[]
+    = N_("Both operands to the ** operator must be numeric.");
+
+  union any_node *c;
+  int type;
+
+  type = parse_primary (n);
+  if (type == EX_ERROR || token != T_EXP)
+    return type;
+  if (type != EX_NUMERIC)
+    {
+      free_node (*n);
+      msg (SE, gettext (typ));
+      return 0;
+    }
+
+  for (;;)
+    {
+      c = allocate_nonterminal (OP_POW, *n);
+      lex_get ();
+
+      type = parse_primary (n);
+      if (type == EX_ERROR)
+       goto fail;
+      else if (type != EX_NUMERIC)
+       {
+         msg (SE, gettext (typ));
+         goto fail;
+       }
+      *n = append_nonterminal_arg (c, *n);
+
+      if (token != T_EXP)
+       break;
+    }
+  return EX_NUMERIC;
+
+fail:
+  free_node (c);
+  return EX_ERROR;
+}
+
+/* Parses system variables. */
+static int
+parse_sysvar (union any_node **n)
+{
+  if (!strcmp (tokid, "$CASENUM"))
+    {
+      *n = xmalloc (sizeof (struct casenum_node));
+      (*n)->casenum.type = OP_CASENUM;
+      return EX_NUMERIC;
+    }
+  else
+    {
+      double d;
+
+      if (!strcmp (tokid, "$SYSMIS"))
+       d = SYSMIS;
+      else if (!strcmp (tokid, "$JDATE"))
+       {
+         struct tm *time = localtime (&last_vfm_invocation);
+         d = yrmoda (time->tm_year + 1900, time->tm_mon + 1, time->tm_mday);
+       }
+      else if (!strcmp (tokid, "$DATE"))
+       {
+         static const char *months[12] =
+           {
+             "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
+             "JUL", "AUG", "SEP", "OCT", "NOV", "DEC",
+           };
+
+         struct tm *time;
+         char temp_buf[10];
+
+         time = localtime (&last_vfm_invocation);
+         sprintf (temp_buf, "%02d %s %02d", abs (time->tm_mday) % 100,
+                  months[abs (time->tm_mon) % 12], abs (time->tm_year) % 100);
+
+         *n = xmalloc (sizeof (struct str_con_node) + 8);
+         (*n)->str_con.type = OP_STR_CON;
+         (*n)->str_con.len = 9;
+         memcpy ((*n)->str_con.s, temp_buf, 9);
+         return EX_STRING;
+       }
+      else if (!strcmp (tokid, "$TIME"))
+       {
+         struct tm *time;
+         time = localtime (&last_vfm_invocation);
+         d = (yrmoda (time->tm_year + 1900, time->tm_mon + 1,
+                      time->tm_mday) * 60. * 60. * 24.
+              + time->tm_hour * 60 * 60.
+              + time->tm_min * 60.
+              + time->tm_sec);
+       }
+      else if (!strcmp (tokid, "$LENGTH"))
+       {
+         msg (SW, _("Use of $LENGTH is obsolete, returning default of 66."));
+         d = 66.0;
+       }
+      else if (!strcmp (tokid, "$WIDTH"))
+       {
+         msg (SW, _("Use of $WIDTH is obsolete, returning default of 131."));
+         d = 131.0;
+       }
+      else
+       {
+         msg (SE, _("Unknown system variable %s."), tokid);
+         return EX_ERROR;
+       }
+
+      *n = xmalloc (sizeof (struct num_con_node));
+      (*n)->num_con.type = OP_NUM_CON;
+      (*n)->num_con.value = d;
+      return EX_NUMERIC;
+    }
+}
+
+/* Parses numbers, varnames, etc. */
+static int
+parse_primary (union any_node **n)
+{
+  switch (token)
+    {
+    case T_ID:
+      {
+       struct variable *v;
+
+       /* An identifier followed by a left parenthesis is a function
+          call. */
+       if (lex_look_ahead () == '(')
+         return parse_function (n);
+
+       /* $ at the beginning indicates a system variable. */
+       if (tokid[0] == '$')
+         {
+           int type = parse_sysvar (n);
+           lex_get ();
+           return type;
+         }
+
+       /* Otherwise, it must be a user variable. */
+       v = find_variable (tokid);
+       lex_get ();
+       if (v == NULL)
+         {
+           lex_error (_("expecting variable name"));
+           return EX_ERROR;
+         }
+
+       *n = xmalloc (sizeof (struct var_node));
+       (*n)->var.type = v->type == NUMERIC ? OP_NUM_VAR : OP_STR_VAR;
+       (*n)->var.v = v;
+       return v->type == NUMERIC ? EX_NUMERIC : EX_STRING;
+      }
+
+    case T_NUM:
+      *n = xmalloc (sizeof (struct num_con_node));
+      (*n)->num_con.type = OP_NUM_CON;
+      (*n)->num_con.value = tokval;
+      lex_get ();
+      return EX_NUMERIC;
+
+    case T_STRING:
+      {
+       *n = xmalloc (sizeof (struct str_con_node) + ds_length (&tokstr) - 1);
+       (*n)->str_con.type = OP_STR_CON;
+       (*n)->str_con.len = ds_length (&tokstr);
+       memcpy ((*n)->str_con.s, ds_value (&tokstr), ds_length (&tokstr));
+       lex_get ();
+       return EX_STRING;
+      }
+
+    case '(':
+      {
+       int t;
+       lex_get ();
+       t = parse_or (n);
+       if (!lex_match (')'))
+         {
+           lex_error (_("expecting `)'"));
+           free_node (*n);
+           return EX_ERROR;
+         }
+       return t;
+      }
+
+    default:
+      lex_error (_("in expression"));
+      return EX_ERROR;
+    }
+}
+\f
+/* Individual function parsing. */
+
+struct function
+  {
+    const char *s;
+    int t;
+    int (*func) (struct function *, int, union any_node **);
+    const char *desc;
+  };
+
+static struct function func_tab[];
+static int func_count;
+
+static int get_num_args (struct function *, int, union any_node **);
+
+static int
+unary_func (struct function * f, int x unused, union any_node ** n)
+{
+  double divisor;
+  struct nonterm_node *c;
+
+  if (!get_num_args (f, 1, n))
+    return EX_ERROR;
+
+  switch (f->t)
+    {
+    case OP_CTIME_DAYS:
+      divisor = 1 / 60. / 60. / 24.;
+      goto multiply;
+    case OP_CTIME_HOURS:
+      divisor = 1 / 60. / 60.;
+      goto multiply;
+    case OP_CTIME_MINUTES:
+      divisor = 1 / 60.;
+      goto multiply;
+    case OP_TIME_DAYS:
+      divisor = 60. * 60. * 24.;
+      goto multiply;
+
+    case OP_CTIME_SECONDS:
+      c = &(*n)->nonterm;
+      *n = (*n)->nonterm.arg[0];
+      free (c);
+      return EX_NUMERIC;
+    }
+  return EX_NUMERIC;
+
+multiply:
+  /* Arrive here when we encounter an operation that is just a
+     glorified version of a multiplication or division.  Converts the
+     operation directly into that multiplication. */
+  c = xmalloc (sizeof (struct nonterm_node) + sizeof (union any_node *));
+  c->type = OP_MUL;
+  c->n = 2;
+  c->arg[0] = (*n)->nonterm.arg[0];
+  c->arg[1] = xmalloc (sizeof (struct num_con_node));
+  c->arg[1]->num_con.type = OP_NUM_CON;
+  c->arg[1]->num_con.value = divisor;
+  free (*n);
+  *n = (union any_node *) c;
+  return EX_NUMERIC;
+}
+
+static int
+binary_func (struct function * f, int x unused, union any_node ** n)
+{
+  if (!get_num_args (f, 2, n))
+    return EX_ERROR;
+  return EX_NUMERIC;
+}
+
+static int
+ternary_func (struct function * f, int x unused, union any_node ** n)
+{
+  if (!get_num_args (f, 3, n))
+    return EX_ERROR;
+  return EX_NUMERIC;
+}
+
+static int
+MISSING_func (struct function * f, int x unused, union any_node ** n)
+{
+  if (token == T_ID && is_varname (tokid) && lex_look_ahead () == ')')
+    {
+      struct var_node *c = xmalloc (sizeof *c);
+      c->v = parse_variable ();
+      c->type = c->v->type == ALPHA ? OP_STR_MIS : OP_NUM_SYS;
+      *n = (union any_node *) c;
+      return EX_BOOLEAN;
+    }
+  if (!get_num_args (f, 1, n))
+    return EX_ERROR;
+  return EX_BOOLEAN;
+}
+
+static int
+SYSMIS_func (struct function * f unused, int x unused, union any_node ** n)
+{
+  int t;
+  
+  if (token == T_ID && is_varname (tokid) && lex_look_ahead () == ')')
+    {
+      struct variable *v;
+      v = parse_variable ();
+      if (v->type == ALPHA)
+       {
+         struct num_con_node *c = xmalloc (sizeof *c);
+         c->type = OP_NUM_CON;
+         c->value = 0;
+         return EX_BOOLEAN;
+       }
+      else
+       {
+         struct var_node *c = xmalloc (sizeof *c);
+         c->type = OP_NUM_SYS;
+         c->v = v;
+         return EX_BOOLEAN;
+       }
+    }
+  
+  t = parse_or (n);
+  if (t == EX_ERROR)
+    return t;
+  else if (t == EX_NUMERIC)
+    {
+      *n = allocate_nonterminal (OP_SYSMIS, *n);
+      return EX_BOOLEAN;
+    }
+  else /* EX_STRING or EX_BOOLEAN */
+    {
+      /* Return constant `true' value. */
+      free_node (*n);
+      *n = xmalloc (sizeof (struct num_con_node));
+      (*n)->num_con.type = OP_NUM_CON;
+      (*n)->num_con.value = 1.0;
+      return EX_BOOLEAN;
+    }
+}
+
+static int
+VALUE_func (struct function *f unused, int x unused, union any_node **n)
+{
+  struct variable *v = parse_variable ();
+
+  if (!v)
+    return EX_ERROR;
+  *n = xmalloc (sizeof (struct var_node));
+  (*n)->var.v = v;
+  if (v->type == NUMERIC)
+    {
+      (*n)->var.type = OP_NUM_VAL;
+      return EX_NUMERIC;
+    }
+  else
+    {
+      (*n)->var.type = OP_STR_VAR;
+      return EX_STRING;
+    }
+}
+
+static int
+LAG_func (struct function *f unused, int x unused, union any_node **n)
+{
+  struct variable *v = parse_variable ();
+  int nlag = 1;
+
+  if (!v)
+    return EX_ERROR;
+  if (lex_match (','))
+    {
+      if (!lex_integer_p () || lex_integer () <= 0 || lex_integer () > 1000)
+       {
+         msg (SE, _("Argument 2 to LAG must be a small positive "
+                    "integer constant."));
+         return 0;
+       }
+      
+      nlag = lex_integer ();
+      lex_get ();
+    }
+  n_lag = max (nlag, n_lag);
+  *n = xmalloc (sizeof (struct lag_node));
+  (*n)->lag.type = (v->type == NUMERIC ? OP_NUM_LAG : OP_STR_LAG);
+  (*n)->lag.v = v;
+  (*n)->lag.lag = nlag;
+  return (v->type == NUMERIC ? EX_NUMERIC : EX_STRING);
+}
+
+/* This screwball function parses n-ary operators:
+   1. NMISS, NVALID, SUM, MEAN, MIN, MAX: any number of (numeric) arguments.
+   2. SD, VARIANCE, CFVAR: at least two (numeric) arguments.
+   3. RANGE: An odd number of arguments, but at least three.
+   All arguments must be the same type.
+   4. ANY: At least two arguments.  All arguments must be the same type.
+ */
+static int
+nary_num_func (struct function *f, int min_args, union any_node **n)
+{
+  /* Argument number of current argument (used for error messages). */
+  int argn = 1;
+
+  /* Number of arguments. */
+  int nargs;
+
+  /* Number of arguments allocated. */
+  int m = 16;
+
+  /* Type of arguments. */
+  int type = (f->t == OP_ANY || f->t == OP_RANGE) ? -1 : NUMERIC;
+
+  *n = xmalloc (sizeof (struct nonterm_node) + sizeof (union any_node *[15]));
+  (*n)->nonterm.type = f->t;
+  (*n)->nonterm.n = 0;
+  for (;;)
+    {
+      /* Special case: vara TO varb. */
+
+      /* FIXME: Is this condition failsafe?  Can we _ever_ have two
+         juxtaposed identifiers otherwise?  */
+      if (token == T_ID && is_varname (tokid)
+         && toupper (lex_look_ahead ()) == 'T')
+       {
+         struct variable **v;
+         int nv;
+         int j;
+         int opts = PV_SINGLE;
+
+         if (type == NUMERIC)
+           opts |= PV_NUMERIC;
+         else if (type == ALPHA)
+           opts |= PV_STRING;
+         if (!parse_variables (NULL, &v, &nv, opts))
+           goto fail;
+         if (nv + (*n)->nonterm.n >= m)
+           {
+             m += nv + 16;
+             *n = xrealloc (*n, (sizeof (struct nonterm_node)
+                                 + (m - 1) * sizeof (union any_node *)));
+           }
+         if (type == -1)
+           {
+             type = v[0]->type;
+             for (j = 1; j < nv; j++)
+               if (type != v[j]->type)
+                 {
+                   msg (SE, _("Type mismatch in argument %d of %s, which was "
+                              "expected to be of %s type.  It was actually "
+                              "of %s type. "),
+                        argn, f->s, type_name (type), type_name (v[j]->type));
+                   free (v);
+                   goto fail;
+                 }
+           }
+         for (j = 0; j < nv; j++)
+           {
+             union any_node **c = &(*n)->nonterm.arg[(*n)->nonterm.n++];
+             *c = xmalloc (sizeof (struct var_node));
+             (*c)->var.type = (type == NUMERIC ? OP_NUM_VAR : OP_STR_VAR);
+             (*c)->var.v = v[j];
+           }
+       }
+      else
+       {
+         union any_node *c;
+         int t = parse_or (&c);
+
+         if (t == EX_ERROR)
+           goto fail;
+         if (t == EX_BOOLEAN)
+           {
+             free_node (c);
+             msg (SE, _("%s cannot take Boolean operands."), f->s);
+             goto fail;
+           }
+         if (type == -1)
+           {
+             if (t == EX_NUMERIC)
+               type = NUMERIC;
+             else if (t == EX_STRING)
+               type = ALPHA;
+           }
+         else if ((t == EX_NUMERIC) ^ (type == NUMERIC))
+           {
+             free_node (c);
+             msg (SE, _("Type mismatch in argument %d of %s, which was "
+                        "expected to be of %s type.  It was actually "
+                        "of %s type. "),
+                  argn, f->s, type_name (type), expr_type_name (t));
+             goto fail;
+           }
+         if ((*n)->nonterm.n + 1 >= m)
+           {
+             m += 16;
+             *n = xrealloc (*n, (sizeof (struct nonterm_node)
+                                 + (m - 1) * sizeof (union any_node *)));
+           }
+         (*n)->nonterm.arg[(*n)->nonterm.n++] = c;
+       }
+
+      if (token == ')')
+       break;
+      if (!lex_match (','))
+       {
+         lex_error (_("in function call"));
+         goto fail;
+       }
+
+      argn++;
+    }
+  *n = xrealloc (*n, (sizeof (struct nonterm_node)
+                     + ((*n)->nonterm.n) * sizeof (union any_node *)));
+
+  nargs = (*n)->nonterm.n;
+  if (f->t == OP_RANGE)
+    {
+      if (nargs < 3 || (nargs & 1) == 0)
+       {
+         msg (SE, _("RANGE requires an odd number of arguments, but "
+                    "at least three."));
+         return 0;
+       }
+    }
+  else if (f->t == OP_SD || f->t == OP_VARIANCE
+          || f->t == OP_CFVAR || f->t == OP_ANY)
+    {
+      if (nargs < 2)
+       {
+         msg (SE, _("%s requires at least two arguments."), f->s);
+         return 0;
+       }
+    }
+
+  if (f->t == OP_CFVAR || f->t == OP_SD || f->t == OP_VARIANCE)
+    min_args = max (min_args, 2);
+  else
+    min_args = max (min_args, 1);
+
+  /* Yes, this is admittedly a terrible crock, but it works. */
+  (*n)->nonterm.arg[(*n)->nonterm.n] = (union any_node *) min_args;
+
+  if (min_args > nargs)
+    {
+      msg (SE, _("%s.%d requires at least %d arguments."),
+          f->s, min_args, min_args);
+      return 0;
+    }
+
+  if (f->t == OP_ANY || f->t == OP_RANGE)
+    {
+      if (type == T_STRING)
+       f->t++;
+      return EX_BOOLEAN;
+    }
+  else
+    return EX_NUMERIC;
+
+fail:
+  free_node (*n);
+  return EX_ERROR;
+}
+
+static int
+CONCAT_func (struct function * f unused, int x unused, union any_node ** n)
+{
+  int m = 0;
+
+  int type;
+
+  *n = xmalloc (sizeof (struct nonterm_node) + sizeof (union any_node *[15]));
+  (*n)->nonterm.type = OP_CONCAT;
+  (*n)->nonterm.n = 0;
+  for (;;)
+    {
+      if ((*n)->nonterm.n >= m)
+       {
+         m += 16;
+         *n = xrealloc (*n, (sizeof (struct nonterm_node)
+                             + (m - 1) * sizeof (union any_node *)));
+       }
+      type = parse_or (&(*n)->nonterm.arg[(*n)->nonterm.n]);
+      if (type == EX_ERROR)
+       goto fail;
+      if (type != EX_STRING)
+       {
+         msg (SE, _("Argument %d to CONCAT is type %s.  All arguments "
+                    "to CONCAT must be strings."),
+              (*n)->nonterm.n + 1, expr_type_name (type));
+         goto fail;
+       }
+      (*n)->nonterm.n++;
+
+      if (!lex_match (','))
+       break;
+    }
+  *n = xrealloc (*n, (sizeof (struct nonterm_node)
+                     + ((*n)->nonterm.n - 1) * sizeof (union any_node *)));
+  return EX_STRING;
+
+fail:
+  free_node (*n);
+  return EX_ERROR;
+}
+
+/* Parses a string function according to f->desc.  f->desc[0] is the
+   return type of the function.  Succeeding characters represent
+   successive args.  Optional args are separated from the required
+   args by a slash (`/').  Codes are `n', numeric arg; `s', string
+   arg; and `f', format spec (this must be the last arg).  If the
+   optional args are included, the type becomes f->t+1. */
+static int
+generic_str_func (struct function *f, int x unused, union any_node ** n)
+{
+  int max_args = 0;
+  int type;
+  const char *cp;
+
+  /* Count max number of arguments. */
+  cp = &f->desc[1];
+  while (*cp)
+    {
+      if (*cp == 'n' || *cp == 's')
+       max_args++;
+      else if (*cp == 'f')
+       max_args += 3;
+      cp++;
+    }
+  cp = &f->desc[1];
+
+  *n = xmalloc (sizeof (struct nonterm_node)
+               + (max_args - 1) * sizeof (union any_node *));
+  (*n)->nonterm.type = f->t;
+  (*n)->nonterm.n = 0;
+  for (;;)
+    {
+      if (*cp == 'n' || *cp == 's')
+       {
+         int t = *cp == 'n' ? EX_NUMERIC : EX_STRING;
+         type = parse_or (&(*n)->nonterm.arg[(*n)->nonterm.n]);
+
+         if (type == EX_ERROR)
+           goto fail;
+         if (type != t)
+           {
+             msg (SE, _("Argument %d to %s was expected to be of %s type.  "
+                        "It was actually of type %s."),
+                  (*n)->nonterm.n + 1, f->s,
+                  *cp == 'n' ? _("numeric") : _("string"),
+                  expr_type_name (type));
+             goto fail;
+           }
+         (*n)->nonterm.n++;
+       }
+      else if (*cp == 'f')
+       {
+         /* This is always the very last argument.  Also, this code
+            is a crock.  However, it works. */
+         struct fmt_spec fmt;
+
+         if (!parse_format_specifier (&fmt, 0))
+           goto fail;
+         if (formats[fmt.type].cat & FCAT_STRING)
+           {
+             msg (SE, _("%s is not a numeric format."), fmt_to_string (&fmt));
+             goto fail;
+           }
+         (*n)->nonterm.arg[(*n)->nonterm.n + 0] = (union any_node *) fmt.type;
+         (*n)->nonterm.arg[(*n)->nonterm.n + 1] = (union any_node *) fmt.w;
+         (*n)->nonterm.arg[(*n)->nonterm.n + 2] = (union any_node *) fmt.d;
+         break;
+       }
+      else
+       assert (0);
+
+      if (*++cp == 0)
+       break;
+      if (*cp == '/')
+       {
+         cp++;
+         if (lex_match (','))
+           {
+             (*n)->nonterm.type++;
+             continue;
+           }
+         else
+           break;
+       }
+      else if (!lex_match (','))
+       {
+         msg (SE, _("Too few arguments to function %s."), f->s);
+         goto fail;
+       }
+    }
+
+  return f->desc[0] == 'n' ? EX_NUMERIC : EX_STRING;
+
+fail:
+  free_node (*n);
+  return EX_ERROR;
+}
+\f
+/* General function parsing. */
+
+static int
+get_num_args (struct function *f, int num_args, union any_node **n)
+{
+  int t;
+  int i;
+
+  *n = xmalloc (sizeof (struct nonterm_node)
+               + (num_args - 1) * sizeof (union any_node *));
+  (*n)->nonterm.type = f->t;
+  (*n)->nonterm.n = 0;
+  for (i = 0;;)
+    {
+      t = parse_or (&(*n)->nonterm.arg[i]);
+      if (t == EX_ERROR)
+       goto fail;
+      (*n)->nonterm.n++;
+      if (t != EX_NUMERIC)
+       {
+         msg (SE, _("Type mismatch in argument %d of %s, which was expected "
+                    "to be numeric.  It was actually type %s."),
+              i + 1, f->s, expr_type_name (t));
+         goto fail;
+       }
+      if (++i >= num_args)
+       return 1;
+      if (!lex_match (','))
+       {
+         msg (SE, _("Missing comma following argument %d of %s."), i + 1, f->s);
+         goto fail;
+       }
+    }
+
+fail:
+  free_node (*n);
+  return 0;
+}
+
+static int
+parse_function (union any_node ** n)
+{
+  struct function *fp;
+  char fname[32], *cp;
+  int t;
+  int min_args;
+  struct vector *v;
+
+  /* Check for a vector with this name. */
+  v = find_vector (tokid);
+  if (v)
+    {
+      lex_get ();
+      assert (token == '(');
+      lex_get ();
+
+      *n = xmalloc (sizeof (struct nonterm_node)
+                   + sizeof (union any_node *[2]));
+      (*n)->nonterm.type = (v->v[0]->type == NUMERIC
+                       ? OP_VEC_ELEM_NUM : OP_VEC_ELEM_STR);
+      (*n)->nonterm.n = 0;
+
+      t = parse_or (&(*n)->nonterm.arg[0]);
+      if (t == EX_ERROR)
+       goto fail;
+      if (t != EX_NUMERIC)
+       {
+         msg (SE, _("The index value after a vector name must be numeric."));
+         goto fail;
+       }
+      (*n)->nonterm.n++;
+
+      if (!lex_match (')'))
+       {
+         msg (SE, _("`)' expected after a vector index value."));
+         goto fail;
+       }
+      ((*n)->nonterm.arg[1]) = (union any_node *) v->index;
+
+      return v->v[0]->type == NUMERIC ? EX_NUMERIC : EX_STRING;
+    }
+
+  ds_truncate (&tokstr, 31);
+  strcpy (fname, ds_value (&tokstr));
+  cp = strrchr (fname, '.');
+  if (cp && isdigit ((unsigned char) cp[1]))
+    {
+      min_args = atoi (&cp[1]);
+      *cp = 0;
+    }
+  else
+    min_args = 0;
+
+  lex_get ();
+  if (!lex_force_match ('('))
+    return 0;
+  
+  {
+    struct function f;
+    f.s = fname;
+    
+    fp = bsearch (&f, func_tab, func_count, sizeof *func_tab, cmp_func);
+  }
+  
+  if (!fp)
+    {
+      msg (SE, _("There is no function named %s."), fname);
+      return 0;
+    }
+  if (min_args && fp->func != nary_num_func)
+    {
+      msg (SE, _("Function %s may not be given a minimum number of "
+                "arguments."), fname);
+      return 0;
+    }
+  t = fp->func (fp, min_args, n);
+  if (t == EX_ERROR)
+    return EX_ERROR;
+  if (!lex_match (')'))
+    {
+      lex_error (_("expecting `)' after %s function"), fname);
+      goto fail;
+    }
+
+  return t;
+
+fail:
+  free_node (*n);
+  return EX_ERROR;
+}
+
+#if GLOBAL_DEBUGGING
+#define op(a,b,c,d) {a,b,c,d}
+#else
+#define op(a,b,c,d) {b,c,d}
+#endif
+
+#define varies 0
+
+struct op_desc ops[OP_SENTINEL + 1] =
+{
+  op ("!?ERROR?!", 000, 0, 0),
+
+  op ("plus", 001, varies, 1),
+  op ("mul", 011, varies, 1),
+  op ("pow", 010, -1, 0),
+  op ("and", 010, -1, 0),
+  op ("or", 010, -1, 0),
+  op ("not", 000, 0, 0),
+  op ("eq", 000, -1, 0),
+  op ("ge", 000, -1, 0),
+  op ("gt", 000, -1, 0),
+  op ("le", 000, -1, 0),
+  op ("lt", 000, -1, 0),
+  op ("ne", 000, -1, 0),
+
+  op ("string-eq", 000, -1, 0),
+  op ("string-ge", 000, -1, 0),
+  op ("string-gt", 000, -1, 0),
+  op ("string-le", 000, -1, 0),
+  op ("string-lt", 000, -1, 0),
+  op ("string-ne", 000, -1, 0),
+
+  op ("neg", 000, 0, 0),
+  op ("abs", 000, 0, 0),
+  op ("arcos", 000, 0, 0),
+  op ("arsin", 000, 0, 0),
+  op ("artan", 000, 0, 0),
+  op ("cos", 000, 0, 0),
+  op ("exp", 000, 0, 0),
+  op ("lg10", 000, 0, 0),
+  op ("ln", 000, 0, 0),
+  op ("mod10", 000, 0, 0),
+  op ("rnd", 000, 0, 0),
+  op ("sin", 000, 0, 0),
+  op ("sqrt", 000, 0, 0),
+  op ("tan", 000, 0, 0),
+  op ("trunc", 000, 0, 0),
+
+  op ("any", 011, varies, 1),
+  op ("any-string", 001, varies, 1),
+  op ("cfvar", 013, varies, 2),
+  op ("max", 013, varies, 2),
+  op ("mean", 013, varies, 2),
+  op ("min", 013, varies, 2),
+  op ("nmiss", 011, varies, 1),
+  op ("nvalid", 011, varies, 1),
+  op ("range", 011, varies, 1),
+  op ("range-string", 001, varies, 1),
+  op ("sd", 013, varies, 2),
+  op ("sum", 013, varies, 2),
+  op ("variance", 013, varies, 2),
+
+  op ("time_hms", 000, -2, 0),
+  op ("ctime_days?!", 000, 0, 0),
+  op ("ctime_hours?!", 000, 0, 0),
+  op ("ctime_minutes?!", 000, 0, 0),
+  op ("ctime_seconds?!", 000, 0, 0),
+  op ("time_days?!", 000, 0, 0),
+
+  op ("date_dmy", 000, -2, 0),
+  op ("date_mdy", 000, -2, 0),
+  op ("date_moyr", 000, -1, 0),
+  op ("date_qyr", 000, -1, 0),
+  op ("date_wkyr", 000, -1, 0),
+  op ("date_yrday", 000, -1, 0),
+  op ("yrmoda", 000, -2, 0),
+
+  op ("xdate_date", 000, 0, 0),
+  op ("xdate_hour", 000, 0, 0),
+  op ("xdate_jday", 000, 0, 0),
+  op ("xdate_mday", 000, 0, 0),
+  op ("xdate_minute", 000, 0, 0),
+  op ("xdate_month", 000, 0, 0),
+  op ("xdate_quarter", 000, 0, 0),
+  op ("xdate_second", 000, 0, 0),
+  op ("xdate_tday", 000, 0, 0),
+  op ("xdate_time", 000, 0, 0),
+  op ("xdate_week", 000, 0, 0),
+  op ("xdate_wkday", 000, 0, 0),
+  op ("xdate_year", 000, 0, 0),
+
+  op ("concat", 001, varies, 1),
+  op ("index-2", 000, -1, 0),
+  op ("index-3", 000, -2, 0),
+  op ("rindex-2", 000, -1, 0),
+  op ("rindex-3", 000, -2, 0),
+  op ("length", 000, 0, 0),
+  op ("lower", 000, 0, 0),
+  op ("upcas", 000, 0, 0),
+  op ("lpad-2", 010, -1, 0),
+  op ("lpad-3", 010, -2, 0),
+  op ("rpad-2", 010, -1, 0),
+  op ("rpad-3", 010, -2, 0),
+  op ("ltrim-1", 000, 0, 0),
+  op ("ltrim-2", 000, -1, 0),
+  op ("rtrim-1", 000, 0, 0),
+  op ("rtrim-2", 000, -1, 0),
+  op ("number-1", 010, 0, 0),
+  op ("number-2", 014, 0, 3),
+  op ("string", 004, 0, 3),
+  op ("substr-2", 010, -1, 0),
+  op ("substr-3", 010, -2, 0),
+
+  op ("inv", 000, 0, 0),
+  op ("square", 000, 0, 0),
+  op ("num-to-Bool", 000, 0, 0),
+
+  op ("mod", 010, -1, 0),
+  op ("normal", 000, 0, 0),
+  op ("uniform", 000, 0, 0),
+  op ("sysmis", 010, 0, 0),
+  op ("vec-elem-num", 002, 0, 1),
+  op ("vec-elem-str", 002, 0, 1),
+
+  op ("!?TERMINAL?!", 000, 0, 0),
+  op ("num-con", 000, +1, 0),
+  op ("str-con", 000, +1, 0),
+  op ("num-var", 000, +1, 0),
+  op ("str-var", 000, +1, 0),
+  op ("num-lag", 000, +1, 1),
+  op ("str-lag", 000, +1, 1),
+  op ("num-sys", 000, +1, 1),
+  op ("num-val", 000, +1, 1),
+  op ("str-mis", 000, +1, 1),
+  op ("$casenum", 000, +1, 0),
+  op ("!?SENTINEL?!", 000, 0, 0),
+};
+
+#undef op
+#undef varies
+\f
+\f
+/* Utility functions. */
+
+static const char *
+expr_type_name (int type)
+{
+  switch (type)
+    {
+    case EX_ERROR:
+      return _("error");
+
+    case EX_BOOLEAN:
+      return _("Boolean");
+
+    case EX_NUMERIC:
+      return _("numeric");
+
+    case EX_STRING:
+      return _("string");
+
+    default:
+      assert (0);
+    }
+#if __GNUC__ || __BORLANDC__
+  return 0;
+#endif
+}
+
+static const char *
+type_name (int type)
+{
+  switch (type)
+    {
+    case NUMERIC:
+      return _("numeric");
+    case ALPHA:
+      return _("string");
+    default:
+      assert (0);
+    }
+#if __GNUC__ || __BORLANDC__
+  return 0;
+#endif
+}
+
+static void
+make_bool (union any_node **n)
+{
+  union any_node *c;
+
+  c = xmalloc (sizeof (struct nonterm_node));
+  c->nonterm.type = OP_NUM_TO_BOOL;
+  c->nonterm.n = 1;
+  c->nonterm.arg[0] = *n;
+  *n = c;
+}
+
+void
+free_node (union any_node *n)
+{
+  if (n->type < OP_TERMINAL)
+    {
+      int i;
+
+      for (i = 0; i < n->nonterm.n; i++)
+       free_node (n->nonterm.arg[i]);
+    }
+  free (n);
+}
+
+union any_node *
+allocate_nonterminal (int op, union any_node *n)
+{
+  union any_node *c;
+
+  c = xmalloc (sizeof c->nonterm);
+  c->nonterm.type = op;
+  c->nonterm.n = 1;
+  c->nonterm.arg[0] = n;
+
+  return c;
+}
+
+union any_node *
+append_nonterminal_arg (union any_node *a, union any_node *b)
+{
+  a = xrealloc (a, sizeof *a + sizeof *a->nonterm.arg * a->nonterm.n);
+  a->nonterm.arg[a->nonterm.n++] = b;
+  return a;
+}
+\f
+static struct function func_tab[] =
+{
+  {"ABS", OP_ABS, unary_func, NULL},
+  {"ACOS", OP_ARCOS, unary_func, NULL},
+  {"ARCOS", OP_ARCOS, unary_func, NULL},
+  {"ARSIN", OP_ARSIN, unary_func, NULL},
+  {"ARTAN", OP_ARTAN, unary_func, NULL},
+  {"ASIN", OP_ARSIN, unary_func, NULL},
+  {"ATAN", OP_ARTAN, unary_func, NULL},
+  {"COS", OP_COS, unary_func, NULL},
+  {"EXP", OP_EXP, unary_func, NULL},
+  {"LG10", OP_LG10, unary_func, NULL},
+  {"LN", OP_LN, unary_func, NULL},
+  {"MOD10", OP_MOD10, unary_func, NULL},
+  {"NORMAL", OP_NORMAL, unary_func, NULL},
+  {"RND", OP_RND, unary_func, NULL},
+  {"SIN", OP_SIN, unary_func, NULL},
+  {"SQRT", OP_SQRT, unary_func, NULL},
+  {"TAN", OP_TAN, unary_func, NULL},
+  {"TRUNC", OP_TRUNC, unary_func, NULL},
+  {"UNIFORM", OP_UNIFORM, unary_func, NULL},
+
+  {"TIME.DAYS", OP_TIME_DAYS, unary_func, NULL},
+  {"TIME.HMS", OP_TIME_HMS, ternary_func, NULL},
+
+  {"CTIME.DAYS", OP_CTIME_DAYS, unary_func, NULL},
+  {"CTIME.HOURS", OP_CTIME_HOURS, unary_func, NULL},
+  {"CTIME.MINUTES", OP_CTIME_MINUTES, unary_func, NULL},
+  {"CTIME.SECONDS", OP_CTIME_SECONDS, unary_func, NULL},
+
+  {"DATE.DMY", OP_DATE_DMY, ternary_func, NULL},
+  {"DATE.MDY", OP_DATE_MDY, ternary_func, NULL},
+  {"DATE.MOYR", OP_DATE_MOYR, binary_func, NULL},
+  {"DATE.QYR", OP_DATE_QYR, binary_func, NULL},
+  {"DATE.WKYR", OP_DATE_WKYR, binary_func, NULL},
+  {"DATE.YRDAY", OP_DATE_YRDAY, binary_func, NULL},
+
+  {"XDATE.DATE", OP_XDATE_DATE, unary_func, NULL},
+  {"XDATE.HOUR", OP_XDATE_HOUR, unary_func, NULL},
+  {"XDATE.JDAY", OP_XDATE_JDAY, unary_func, NULL},
+  {"XDATE.MDAY", OP_XDATE_MDAY, unary_func, NULL},
+  {"XDATE.MINUTE", OP_XDATE_MINUTE, unary_func, NULL},
+  {"XDATE.MONTH", OP_XDATE_MONTH, unary_func, NULL},
+  {"XDATE.QUARTER", OP_XDATE_QUARTER, unary_func, NULL},
+  {"XDATE.SECOND", OP_XDATE_SECOND, unary_func, NULL},
+  {"XDATE.TDAY", OP_XDATE_TDAY, unary_func, NULL},
+  {"XDATE.TIME", OP_XDATE_TIME, unary_func, NULL},
+  {"XDATE.WEEK", OP_XDATE_WEEK, unary_func, NULL},
+  {"XDATE.WKDAY", OP_XDATE_WKDAY, unary_func, NULL},
+  {"XDATE.YEAR", OP_XDATE_YEAR, unary_func, NULL},
+
+  {"MISSING", OP_SYSMIS, MISSING_func, NULL},
+  {"MOD", OP_MOD, binary_func, NULL},
+  {"SYSMIS", OP_SYSMIS, SYSMIS_func, NULL},
+  {"VALUE", OP_NUM_VAL, VALUE_func, NULL},
+  {"LAG", OP_NUM_LAG, LAG_func, NULL},
+  {"YRMODA", OP_YRMODA, ternary_func, NULL},
+
+  {"ANY", OP_ANY, nary_num_func, NULL},
+  {"CFVAR", OP_CFVAR, nary_num_func, NULL},
+  {"MAX", OP_MAX, nary_num_func, NULL},
+  {"MEAN", OP_MEAN, nary_num_func, NULL},
+  {"MIN", OP_MIN, nary_num_func, NULL},
+  {"NMISS", OP_NMISS, nary_num_func, NULL},
+  {"NVALID", OP_NVALID, nary_num_func, NULL},
+  {"RANGE", OP_RANGE, nary_num_func, NULL},
+  {"SD", OP_SD, nary_num_func, NULL},
+  {"SUM", OP_SUM, nary_num_func, NULL},
+  {"VARIANCE", OP_VARIANCE, nary_num_func, NULL},
+
+  {"CONCAT", OP_CONCAT, CONCAT_func, NULL},
+  {"INDEX", OP_INDEX, generic_str_func, "nss/n"},
+  {"RINDEX", OP_RINDEX, generic_str_func, "nss/n"},
+  {"LENGTH", OP_LENGTH, generic_str_func, "ns"},
+  {"LOWER", OP_LOWER, generic_str_func, "ss"},
+  {"UPCAS", OP_UPPER, generic_str_func, "ss"},
+  {"LPAD", OP_LPAD, generic_str_func, "ssn/s"},
+  {"RPAD", OP_RPAD, generic_str_func, "ssn/s"},
+  {"LTRIM", OP_LTRIM, generic_str_func, "ss/s"},
+  {"RTRIM", OP_RTRIM, generic_str_func, "ss/s"},
+  {"NUMBER", OP_NUMBER, generic_str_func, "ns/f"},
+  {"STRING", OP_STRING, generic_str_func, "snf"},
+  {"SUBSTR", OP_SUBSTR, generic_str_func, "ssn/n"},
+};
+
+static int
+cmp_func (const void *a, const void *b)
+{
+  return strcmp (*(char **) a, *(char **) b);
+}
+
+static void
+init_func_tab (void)
+{
+  {
+    static int inited;
+
+    if (inited)
+      return;
+    inited = 1;
+  }
+
+  func_count = sizeof func_tab / sizeof *func_tab;
+  qsort (func_tab, func_count, sizeof *func_tab, cmp_func);
+}
+\f
+/* Debug output. */
+
+#if DEBUGGING
+static void
+print_type (union any_node * n)
+{
+  const char *s;
+  size_t len;
+
+  s = ops[n->type].name;
+  len = strlen (s);
+  if (ops[n->type].flags & OP_MIN_ARGS)
+    printf ("%s.%d\n", s, (int) n->nonterm.arg[n->nonterm.n]);
+  else if (ops[n->type].flags & OP_FMT_SPEC)
+    {
+      struct fmt_spec f;
+
+      f.type = (int) n->nonterm.arg[n->nonterm.n + 0];
+      f.w = (int) n->nonterm.arg[n->nonterm.n + 1];
+      f.d = (int) n->nonterm.arg[n->nonterm.n + 2];
+      printf ("%s(%s)\n", s, fmt_to_string (&f));
+    }
+  else
+    printf ("%s\n", s);
+}
+
+static void
+debug_print_tree (union any_node * n, int level)
+{
+  int i;
+  for (i = 0; i < level; i++)
+    printf ("  ");
+  if (n->type < OP_TERMINAL)
+    {
+      print_type (n);
+      for (i = 0; i < n->nonterm.n; i++)
+       debug_print_tree (n->nonterm.arg[i], level + 1);
+    }
+  else
+    {
+      switch (n->type)
+       {
+       case OP_TERMINAL:
+         printf (_("!!TERMINAL!!"));
+         break;
+       case OP_NUM_CON:
+         if (n->num_con.value == SYSMIS)
+           printf ("SYSMIS");
+         else
+           printf ("%f", n->num_con.value);
+         break;
+       case OP_STR_CON:
+         printf ("\"%.*s\"", n->str_con.len, n->str_con.s);
+         break;
+       case OP_NUM_VAR:
+       case OP_STR_VAR:
+         printf ("%s", n->var.v->name);
+         break;
+       case OP_NUM_LAG:
+       case OP_STR_LAG:
+         printf ("LAG(%s,%d)", n->lag.v->name, n->lag.lag);
+         break;
+       case OP_NUM_SYS:
+         printf ("SYSMIS(%s)", n->var.v->name);
+         break;
+       case OP_NUM_VAL:
+         printf ("VALUE(%s)", n->var.v->name);
+         break;
+       case OP_SENTINEL:
+         printf (_("!!SENTINEL!!"));
+         break;
+       default:
+         printf (_("!!ERROR%d!!"), n->type);
+         assert (0);
+       }
+      printf ("\n");
+    }
+}
+#endif /* DEBUGGING */
+
+#if GLOBAL_DEBUGGING
+static void
+debug_print_postfix (struct expression * e)
+{
+  unsigned char *o;
+  double *num = e->num;
+  unsigned char *str = e->str;
+  struct variable **v = e->var;
+  int t;
+
+  debug_printf ((_("postfix:")));
+  for (o = e->op; *o != OP_SENTINEL;)
+    {
+      t = *o++;
+      if (t < OP_TERMINAL)
+       {
+         debug_printf ((" %s", ops[t].name));
+
+         if (ops[t].flags & OP_VAR_ARGS)
+           {
+             debug_printf (("(%d)", *o));
+             o++;
+           }
+         if (ops[t].flags & OP_MIN_ARGS)
+           {
+             debug_printf ((".%d", *o));
+             o++;
+           }
+         if (ops[t].flags & OP_FMT_SPEC)
+           {
+             struct fmt_spec f;
+             f.type = (int) *o++;
+             f.w = (int) *o++;
+             f.d = (int) *o++;
+             debug_printf (("(%s)", fmt_to_string (&f)));
+           }
+       }
+      else if (t == OP_NUM_CON)
+       {
+         if (*num == SYSMIS)
+           debug_printf ((" SYSMIS"));
+         else
+           debug_printf ((" %f", *num));
+         num++;
+       }
+      else if (t == OP_STR_CON)
+       {
+         debug_printf ((" \"%.*s\"", *str, &str[1]));
+         str += str[0] + 1;
+       }
+      else if (t == OP_NUM_VAR || t == OP_STR_VAR)
+       {
+         debug_printf ((" %s", (*v)->name));
+         v++;
+       }
+      else if (t == OP_NUM_SYS)
+       {
+         debug_printf ((" SYSMIS(#%d)", *o));
+         o++;
+       }
+      else if (t == OP_NUM_VAL)
+       {
+         debug_printf ((" VALUE(#%d)", *o));
+         o++;
+       }
+      else if (t == OP_NUM_LAG || t == OP_STR_LAG)
+       {
+         debug_printf ((" LAG(%s,%d)", (*v)->name, *o));
+         o++;
+         v++;
+       }
+      else
+       {
+         printf ("debug_print_postfix(): %d\n", t);
+         assert (0);
+       }
+    }
+  debug_putc ('\n', stdout);
+}
+#endif /* GLOBAL_DEBUGGING */
diff --git a/src/expr.h b/src/expr.h
new file mode 100644 (file)
index 0000000..3082496
--- /dev/null
@@ -0,0 +1,44 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !expr_h
+#define expr_h 1
+
+/* Expression parsing flags. */
+enum
+  {
+    PXP_NONE = 000,            /* No flags. */
+    PXP_DUMP = 001,            /* Dump postfix representation to screen;
+                                  only for use by EVALUATE. */
+
+    /* Specify expression type. */
+    PXP_BOOLEAN = 002,         /* Coerce return value to Boolean. */
+    PXP_NUMERIC = 004,         /* Must be numeric result type. */
+    PXP_STRING = 010           /* Must be string result type. */
+  };
+
+struct expression;
+struct ccase;
+union value;
+
+struct expression *expr_parse (int flags);
+double expr_evaluate (struct expression *, struct ccase *, union value *);
+void expr_free (struct expression *);
+
+#endif /* expr.h */
diff --git a/src/exprP.h b/src/exprP.h
new file mode 100644 (file)
index 0000000..873f0ce
--- /dev/null
@@ -0,0 +1,296 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !exprP_h
+#define exprP_h 1
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+#if GLOBAL_DEBUGGING
+void debug_print_expr (struct expression *);
+void debug_print_op (short int *);
+#endif
+
+/* Expression types. */
+enum
+  {
+    EX_ERROR,          /* Error value for propagation. */
+    EX_BOOLEAN,                /* Numeric value that's 0, 1, or SYSMIS. */
+    EX_NUMERIC,                /* Numeric value. */
+    EX_STRING          /* String value. */
+  };
+
+/* Expression operators.
+   The ordering below is important.  Do not change it. */
+enum
+  {
+    OP_ERROR,
+
+    /* Basic operators. */
+    OP_PLUS,
+    OP_MUL,
+    OP_POW,
+    OP_AND,
+    OP_OR,
+    OP_NOT,
+
+    /* Numeric relational operators. */
+    OP_EQ,
+    OP_GE,
+    OP_GT,
+    OP_LE,
+    OP_LT,
+    OP_NE,
+
+    /* String relational operators. */
+    OP_STRING_EQ,
+    OP_STRING_GE,
+    OP_STRING_GT,
+    OP_STRING_LE,
+    OP_STRING_LT,
+    OP_STRING_NE,
+
+    /* Unary functions. */
+    OP_NEG,
+    OP_ABS,
+    OP_ARCOS,
+    OP_ARSIN,
+    OP_ARTAN,
+    OP_COS,
+    OP_EXP,
+    OP_LG10,
+    OP_LN,
+    OP_MOD10,
+    OP_RND,
+    OP_SIN,
+    OP_SQRT,
+    OP_TAN,
+    OP_TRUNC,
+
+    /* N-ary numeric functions. */
+    OP_ANY,
+    OP_ANY_STRING,
+    OP_CFVAR,
+    OP_MAX,
+    OP_MEAN,
+    OP_MIN,
+    OP_NMISS,
+    OP_NVALID,
+    OP_RANGE,
+    OP_RANGE_STRING,
+    OP_SD,
+    OP_SUM,
+    OP_VARIANCE,
+
+    /* Time construction & extraction functions. */
+    OP_TIME_HMS,
+
+    /* These never appear in a tree or an expression.
+       They disappear in parse.c:unary_func(). */
+    OP_CTIME_DAYS,
+    OP_CTIME_HOURS,
+    OP_CTIME_MINUTES,
+    OP_CTIME_SECONDS,
+    OP_TIME_DAYS,
+
+    /* Date construction functions. */
+    OP_DATE_DMY,
+    OP_DATE_MDY,
+    OP_DATE_MOYR,
+    OP_DATE_QYR,
+    OP_DATE_WKYR,
+    OP_DATE_YRDAY,
+    OP_YRMODA,
+
+    /* Date extraction functions. */
+    OP_XDATE_DATE,
+    OP_XDATE_HOUR,
+    OP_XDATE_JDAY,
+    OP_XDATE_MDAY,
+    OP_XDATE_MINUTE,
+    OP_XDATE_MONTH,
+    OP_XDATE_QUARTER,
+    OP_XDATE_SECOND,
+    OP_XDATE_TDAY,
+    OP_XDATE_TIME,
+    OP_XDATE_WEEK,
+    OP_XDATE_WKDAY,
+    OP_XDATE_YEAR,
+
+    /* String functions. */
+    OP_CONCAT,
+    OP_INDEX,
+    OP_INDEX_OPT,
+    OP_RINDEX,
+    OP_RINDEX_OPT,
+    OP_LENGTH,
+    OP_LOWER,
+    OP_UPPER,
+    OP_LPAD,
+    OP_LPAD_OPT,
+    OP_RPAD,
+    OP_RPAD_OPT,
+    OP_LTRIM,
+    OP_LTRIM_OPT,
+    OP_RTRIM,
+    OP_RTRIM_OPT,
+    OP_NUMBER,
+    OP_NUMBER_OPT,
+    OP_STRING,
+    OP_SUBSTR,
+    OP_SUBSTR_OPT,
+
+    /* Artificial. */
+    OP_INV,                    /* Reciprocal. */
+    OP_SQUARE,                 /* Squares the argument. */
+    OP_NUM_TO_BOOL,            /* Converts ~0=>0, ~1=>1, SYSMIS=>SYSMIS,
+                                  others=>0 with a warning. */
+
+    /* Weirdness. */
+    OP_MOD,                    /* Modulo function. */
+    OP_NORMAL,                 /* Normally distributed PRNG. */
+    OP_UNIFORM,                        /* Uniformly distributed PRNG. */
+    OP_SYSMIS,                 /* Tests whether for SYSMIS argument. */
+    OP_VEC_ELEM_NUM,           /* Element of a numeric vector. */
+    OP_VEC_ELEM_STR,           /* Element of a string vector. */
+
+    /* Terminals. */
+    OP_TERMINAL,               /* Not a valid type.  Boundary
+                                  between terminals and nonterminals. */
+
+    OP_NUM_CON,                        /* Numeric constant. */
+    OP_STR_CON,                        /* String literal. */
+    OP_NUM_VAR,                        /* Numeric variable reference. */
+    OP_STR_VAR,                        /* String variable reference. */
+    OP_NUM_LAG,                        /* Numeric variable from an earlier case. */
+    OP_STR_LAG,                        /* String variable from an earlier case. */
+    OP_NUM_SYS,                        /* SYSMIS(numvar). */
+    OP_NUM_VAL,                        /* VALUE(numvar). */
+    OP_STR_MIS,                        /* MISSING(strvar). */
+    OP_CASENUM,                        /* $CASENUM. */
+    OP_SENTINEL                        /* Sentinel. */
+  };
+
+/* Flags that describe operators. */
+enum
+  {
+    OP_VAR_ARGS = 001,         /* 1=Variable number of args. */
+    OP_MIN_ARGS = 002,         /* 1=Can specific min args with .X. */
+    OP_FMT_SPEC = 004,         /* 1=Includes a format specifier. */
+    OP_ABSORB_MISS = 010,      /* 1=May return other than SYSMIS if
+                                  given a SYSMIS argument. */
+  };
+
+/* Describes an operator. */
+struct op_desc
+  {
+#if GLOBAL_DEBUGGING
+    const char *name;          /* Operator name. */
+#endif
+    unsigned char flags;       /* Flags. */
+    signed char height;                /* Effect on stack height. */
+    unsigned char skip;                /* Number of operator item arguments. */
+  };
+
+extern struct op_desc ops[];
+
+/* Tree structured expressions. */ 
+
+/* Numeric constant. */
+struct num_con_node
+  {
+    int type;                  /* Always OP_NUM_CON. */
+    double value;              /* Numeric value. */
+  };
+
+/* String literal. */
+struct str_con_node
+  {
+    int type;                  /* Always OP_STR_CON. */
+    int len;                   /* Length of string. */
+    char s[1];                 /* String value. */
+  };
+
+/* Variable or test for missing values or cancellation of
+   user-missing. */
+struct var_node
+  {
+    int type;                  /* OP_NUM_VAR, OP_NUM_SYS, OP_NUM_VAL,
+                                  OP_STR_MIS, or OP_STR_VAR. */
+    struct variable *v;                /* Variable. */
+  };
+
+/* Variable from an earlier case. */
+struct lag_node
+  {
+    int type;                  /* Always OP_NUM_LAG. */
+    struct variable *v;                /* Relevant variable. */
+    int lag;                   /* Number of cases to lag. */
+  };
+
+/* $CASENUM. */
+struct casenum_node
+  {
+    int type;                  /* Always OP_CASENUM. */
+  };
+
+/* Any nonterminal node. */
+struct nonterm_node
+  {
+    int type;                  /* Always greater than OP_TERMINAL. */
+    int n;                     /* Number of arguments. */
+    union any_node *arg[1];    /* Arguments. */
+  };
+
+/* Any node. */
+union any_node
+  {
+    int type;
+    struct nonterm_node nonterm;
+    struct num_con_node num_con;
+    struct str_con_node str_con;
+    struct var_node var;
+    struct lag_node lag;
+    struct casenum_node casenum;
+  };
+
+/* An expression. */
+struct expression
+  {
+    int type;                  /* Type of expression result. */
+    unsigned char *op;         /* Operators. */
+    struct variable **var;     /* Variables. */
+    double *num;               /* Numeric operands. */
+    unsigned char *str;                /* String operands. */
+    union value *stack;                /* Evaluation stack. */
+    unsigned char *str_stack;  /* String evaluation stack. */
+#if !PAGED_STACK
+    size_t str_size;           /* Size of string eval stack. */
+#endif
+  };
+
+struct nonterm_node *optimize_expression (struct nonterm_node *);
+void dump_expression (union any_node *, struct expression *);
+void free_node (union any_node *);
+
+double yrmoda (double year, double month, double day);
+
+#endif /* exprP.h */
diff --git a/src/file-handle.h b/src/file-handle.h
new file mode 100644 (file)
index 0000000..34a8595
--- /dev/null
@@ -0,0 +1,99 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !file_handle_h
+#define file_handle_h 1
+
+/* File handle provider (fhp).
+
+   This module provides file handles in the form of file_handle
+   structures to the dfm and sfm modules, which are known as file
+   handle users (fhusers).  fhp does not know anything about file
+   contents. */
+
+#include <stddef.h>
+#include "error.h"
+
+/* Record formats. */
+enum
+  {
+    FH_RF_FIXED,               /* Fixed length records. */
+    FH_RF_VARIABLE,            /* Variable length records. */
+    FH_RF_SPANNED              /* ? */
+  };
+
+/* File modes. */
+enum
+  {
+    FH_MD_CHARACTER,           /* Character data. */
+    FH_MD_IMAGE,               /* ? */
+    FH_MD_BINARY,              /* Character and/or binary data. */
+    FH_MD_MULTIPUNCH,          /* Column binary data (not supported). */
+    FH_MD_360                  /* ? */
+  };
+
+struct file_handle;
+
+/* Services that fhusers provide to fhp. */
+struct fh_ext_class
+  {
+    int magic;                 /* Magic identifier for fhuser. */
+    const char *name;          /* String identifier for fhuser. */
+
+    void (*close) (struct file_handle *);
+                               /* Closes any associated file, etc. */
+  };
+
+/* Opaque structure.  The `ext' member is an exception for use by
+   subclasses.  `where.ln' is also acceptable. */
+struct file_handle
+  {
+    /* name must be the first member. */
+    const char *name;          /* File handle identifier. */
+    char *norm_fn;             /* Normalized filename. */
+    char *fn;                  /* Filename as provided by user. */
+    struct file_locator where; /* Used for reporting error messages. */
+
+    int recform;               /* One of FH_RF_*. */
+    size_t lrecl;              /* Length of records for FH_RF_FIXED. */
+    int mode;                  /* One of FH_MD_*. */
+
+    struct fh_ext_class *class;        /* Polymorphism support. */
+    void *ext;                 /* Extension struct for fhuser use. */
+  };
+
+/* All the file handles in the system. */
+extern struct avl_tree *files;
+
+/* Pointer to the file handle that corresponds to data in the command
+   file entered via BEGIN DATA/END DATA. */
+extern struct file_handle *inline_file;
+
+/* Opening and closing handles. */
+struct file_handle *fh_get_handle_by_name (const char name[9]);
+struct file_handle *fh_get_handle_by_filename (const char *filename);
+struct file_handle *fh_parse_file_handle (void);
+void fh_close_handle (struct file_handle *handle);
+
+/* Handle info. */
+const char *fh_handle_name (struct file_handle *handle);
+char *fh_handle_filename (struct file_handle *handle);
+size_t fh_record_width (struct file_handle *handle);
+
+#endif /* !file_handle.h */
diff --git a/src/file-handle.q b/src/file-handle.q
new file mode 100644 (file)
index 0000000..309c409
--- /dev/null
@@ -0,0 +1,362 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "avl.h"
+#include "filename.h"
+#include "file-handle.h"
+#include "command.h"
+#include "lexer.h"
+#include "getline.h"
+#include "error.h"
+#include "magic.h"
+#include "var.h"
+/* (headers) */
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+avl_tree *files;
+struct file_handle *inline_file;
+
+static void init_file_handle (struct file_handle * handle);
+
+/* (specification)
+   "FILE HANDLE" (fh_):
+     name=string;
+     recform=recform:fixed/!variable/spanned;
+     lrecl=integer;
+     mode=mode:!character/image/binary/multipunch/_360.
+*/
+/* (declarations) */
+/* (functions) */
+
+int
+cmd_file_handle (void)
+{
+  char handle_name[9];
+  char *handle_name_p = handle_name;
+
+  struct cmd_file_handle cmd;
+  struct file_handle *fp;
+
+  lex_get ();
+  if (!lex_force_id ())
+    return CMD_FAILURE;
+  strcpy (handle_name, tokid);
+
+  fp = NULL;
+  if (files)
+    fp = avl_find (files, &handle_name_p);
+  if (fp)
+    {
+      msg (SE, _("File handle %s had already been defined to refer to "
+                "file %s.  It is not possible to redefine a file "
+                "handle within a session."),
+          tokid, fp->fn);
+      return CMD_FAILURE;
+    }
+
+  lex_get ();
+  if (!lex_force_match ('/'))
+    return CMD_FAILURE;
+
+  if (!parse_file_handle (&cmd))
+    return CMD_FAILURE;
+
+  if (token != '.')
+    {
+      lex_error (_("expecting end of command"));
+      goto lossage;
+    }
+
+  if (cmd.s_name == NULL)
+    {
+      msg (SE, _("The FILE HANDLE required subcommand NAME "
+                "is not present."));
+      goto lossage;
+    }
+
+  fp = xmalloc (sizeof *fp);
+  init_file_handle (fp);
+
+  switch (cmd.recform)
+    {
+    case FH_FIXED:
+      if (cmd.n_lrecl == NOT_LONG)
+       {
+         msg (SE, _("Fixed length records were specified on /RECFORM, but "
+              "record length was not specified on /LRECL.  80-character "
+              "records will be assumed."));
+         cmd.n_lrecl = 80;
+       }
+      else if (cmd.n_lrecl < 1)
+       {
+         msg (SE, _("Record length (%ld) must be at least one byte.  "
+                    "80-character records will be assumed."), cmd.n_lrecl);
+         cmd.n_lrecl = 80;
+       }
+      fp->recform = FH_RF_FIXED;
+      fp->lrecl = cmd.n_lrecl;
+      break;
+    case FH_VARIABLE:
+      fp->recform = FH_RF_VARIABLE;
+      break;
+    case FH_SPANNED:
+      msg (SE, _("/RECFORM SPANNED is not implemented, as the author doesn't "
+          "know what it is supposed to do.  Send the author a note."));
+      break;
+    default:
+      assert (0);
+    }
+
+  switch (cmd.mode)
+    {
+    case FH_CHARACTER:
+      fp->mode = FH_MD_CHARACTER;
+      break;
+    case FH_IMAGE:
+      msg (SE, _("/MODE IMAGE is not implemented, as the author doesn't know "
+          "what it is supposed to do.  Send the author a note."));
+      break;
+    case FH_BINARY:
+      fp->mode = FH_MD_BINARY;
+      break;
+    case FH_MULTIPUNCH:
+      msg (SE, _("/MODE MULTIPUNCH is not implemented.  If you care, "
+                "complain."));
+      break;
+    case FH__360:
+      msg (SE, _("/MODE 360 is not implemented.  If you care, complain."));
+      break;
+    default:
+      assert (0);
+    }
+
+  fp->name = xstrdup (handle_name);
+  fp->norm_fn = fn_normalize (cmd.s_name);
+  fp->where.filename = fp->fn = cmd.s_name;
+  avl_force_insert (files, fp);
+
+  return CMD_SUCCESS;
+
+ lossage:
+  free_file_handle (&cmd);
+  return CMD_FAILURE;
+}
+\f
+/* File handle functions. */
+
+/* Sets up some fields in H; caller should fill in
+   H->{NAME,NORM_FN,FN}. */
+static void
+init_file_handle (struct file_handle *h)
+{
+  h->recform = FH_RF_VARIABLE;
+  h->mode = FH_MD_CHARACTER;
+  h->ext = NULL;
+  h->class = NULL;
+}
+
+/* Returns the handle corresponding to FILENAME.  Creates the handle
+   if no handle exists for that file.  All filenames are normalized
+   first, so different filenames referring to the same file will
+   return the same file handle. */
+struct file_handle *
+fh_get_handle_by_filename (const char *filename)
+{
+  struct file_handle f, *fp;
+  char *fn;
+  char *name;
+  int len;
+
+  /* Get filename. */
+  fn = fn_normalize (filename);
+  len = strlen (fn);
+
+  /* Create handle name with invalid identifier character to prevent
+     conflicts with handles created with FILE HANDLE. */
+  name = xmalloc (len + 2);
+  name[0] = '*';
+  strcpy (&name[1], fn);
+
+  f.name = name;
+  fp = avl_find (files, &f);
+  if (!fp)
+    {
+      fp = xmalloc (sizeof *fp);
+      init_file_handle (fp);
+      fp->name = name;
+      fp->norm_fn = fn;
+      fp->where.filename = fp->fn = xstrdup (filename);
+      avl_force_insert (files, fp);
+    }
+  else
+    {
+      free (fn);
+      free (name);
+    }
+  return fp;
+}
+
+/* Returns the handle with identifier NAME, if it exists; otherwise
+   reports error to user and returns NULL. */
+struct file_handle *
+fh_get_handle_by_name (const char name[9])
+{
+  struct file_handle f, *fp;
+  f.name = (char *) name;
+  fp = avl_find (files, &f);
+
+  if (!fp)
+    msg (SE, _("File handle `%s' has not been previously declared on "
+        "FILE HANDLE."), name);
+  return fp;
+}
+
+/* Returns the identifier of file HANDLE.  If HANDLE was created by
+   referring to a filename (i.e., DATA LIST FILE='yyy' instead of FILE
+   HANDLE XXX='yyy'), returns the filename, enclosed in double quotes.
+   Return value is in a static buffer.
+
+   Useful for printing error messages about use of file handles.  */
+const char *
+fh_handle_name (struct file_handle *h)
+{
+  static char *buf = NULL;
+
+  if (buf)
+    {
+      free (buf);
+      buf = NULL;
+    }
+  if (!h)
+    return NULL;
+
+  if (h->name[0] == '*')
+    {
+      int len = strlen (h->fn);
+
+      buf = xmalloc (len + 3);
+      strcpy (&buf[1], h->fn);
+      buf[0] = buf[len + 1] = '"';
+      buf[len + 2] = 0;
+      return buf;
+    }
+  return h->name;
+}
+
+/* Closes the stdio FILE associated with handle H.  Frees internal
+   buffers associated with that file.  Does *not* destroy the file
+   handle H.  (File handles are permanent during a session.)  */
+void
+fh_close_handle (struct file_handle *h)
+{
+  if (h == NULL)
+    return;
+
+  debug_printf (("Closing %s%s.\n", fh_handle_name (h),
+                h->class == NULL ? " (already closed)" : ""));
+
+  if (h->class)
+    h->class->close (h);
+  h->class = NULL;
+  h->ext = NULL;
+}
+
+/* Compares names of file handles A and B. */
+static int
+cmp_file_handle (const void *a, const void *b, void *foo unused)
+{
+  return strcmp (((struct file_handle *) a)->name,
+                ((struct file_handle *) b)->name);
+}
+
+/* Initialize the AVL tree of file handles; inserts the "inline file"
+   inline_file. */
+void
+fh_init_files (void)
+{
+  /* Create AVL tree. */
+  files = avl_create (NULL, cmp_file_handle, NULL);
+
+  /* Insert inline file. */
+  inline_file = xmalloc (sizeof *inline_file);
+  init_file_handle (inline_file);
+  inline_file->name = "INLINE";
+  inline_file->where.filename
+    = inline_file->fn = inline_file->norm_fn = (char *) _("<Inline File>");
+  inline_file->where.line_number = 0;
+  avl_force_insert (files, inline_file);
+}
+
+/* Parses a file handle name, which may be a filename as a string or
+   a file handle name as an identifier.  Returns the file handle or
+   NULL on failure. */
+struct file_handle *
+fh_parse_file_handle (void)
+{
+  struct file_handle *handle;
+
+  if (token == T_ID)
+    handle = fh_get_handle_by_name (tokid);
+  else if (token == T_STRING)
+    handle = fh_get_handle_by_filename (ds_value (&tokstr));
+  else
+    {
+      lex_error (_("expecting a file name or handle"));
+      return NULL;
+    }
+
+  if (!handle)
+    return NULL;
+  lex_get ();
+
+  return handle;
+}
+
+/* Returns the (normalized) filename associated with file handle H. */
+char *
+fh_handle_filename (struct file_handle * h)
+{
+  return h->norm_fn;
+}
+
+/* Returns the width of a logical record on file handle H. */
+size_t
+fh_record_width (struct file_handle *h)
+{
+  if (h == inline_file)
+    return 80;
+  else if (h->recform == FH_RF_FIXED)
+    return h->lrecl;
+  else
+    return 1024;
+}
+
+/*
+   Local variables:
+   mode: c
+   End:
+*/
diff --git a/src/file-type.c b/src/file-type.c
new file mode 100644 (file)
index 0000000..3400c8a
--- /dev/null
@@ -0,0 +1,729 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "approx.h"
+#include "command.h"
+#include "data-in.h"
+#include "dfm.h"
+#include "file-handle.h"
+#include "format.h"
+#include "lexer.h"
+#include "str.h"
+#include "var.h"
+#include "vfm.h"
+
+/* Defines the three types of complex files read by FILE TYPE. */
+enum
+  {
+    FTY_MIXED,
+    FTY_GROUPED,
+    FTY_NESTED
+  };
+
+/* Limited variable column specifications. */
+struct col_spec
+  {
+   char name[9];               /* Variable name. */
+    int fc, nc;                        /* First column (1-based), # of columns. */
+    int fmt;                   /* Format type. */
+    struct variable *v;                /* Variable. */
+  };
+
+/* RCT_* record type constants. */
+enum
+  {
+    RCT_OTHER = 001,           /* 1=OTHER. */
+    RCT_SKIP = 002,            /* 1=SKIP. */
+    RCT_DUPLICATE = 004,       /* DUPLICATE: 0=NOWARN, 1=WARN. */
+    RCT_MISSING = 010,         /* MISSING: 0=NOWARN, 1=WARN. */
+    RCT_SPREAD = 020           /* SPREAD: 0=NO, 1=YES. */
+  };
+
+/* Represents a RECORD TYPE command. */
+struct record_type
+  {
+    struct record_type *next;
+    unsigned flags;            /* RCT_* constants. */
+    union value *v;            /* Vector of values for this record type. */
+    int nv;                    /* Length of vector V. */
+    struct col_spec case_sbc;  /* CASE subcommand. */
+    int ft, lt;                        /* First, last transformation index. */
+  };                           /* record_type */
+
+/* Represents a FILE TYPE input program.  Does not contain a
+   trns_header because it's never submitted as a transformation. */
+struct file_type_pgm
+  {
+    int type;                  /* One of the FTY_* constants. */
+    struct file_handle *handle;        /* File handle of input file. */
+    struct col_spec record;    /* RECORD subcommand. */
+    struct col_spec case_sbc;  /* CASE subcommand. */
+    int wild;                  /* 0=NOWARN, 1=WARN. */
+    int duplicate;             /* 0=NOWARN, 1=WARN. */
+    int missing;               /* 0=NOWARN, 1=WARN, 2=CASE. */
+    int ordered;               /* 0=NO, 1=YES. */
+    int had_rec_type;          /* 1=Had a RECORD TYPE command.
+                                  RECORD TYPE must precede the first
+                                  DATA LIST. */
+    struct record_type *recs_head;     /* List of record types. */
+    struct record_type *recs_tail;     /* Last in list of record types. */
+  };
+
+/* Current FILE TYPE input program. */
+static struct file_type_pgm fty;
+
+static int parse_col_spec (struct col_spec *, const char *);
+static void create_col_var (struct col_spec *c);
+
+/* Parses FILE TYPE command. */
+int
+cmd_file_type (void)
+{
+  /* Initialize. */
+  discard_variables ();
+  fty.handle = inline_file;
+  fty.record.name[0] = 0;
+  fty.case_sbc.name[0] = 0;
+  fty.wild = fty.duplicate = fty.missing = fty.ordered = 0;
+  fty.had_rec_type = 0;
+  fty.recs_head = fty.recs_tail = NULL;
+
+  lex_match_id ("TYPE");
+  if (lex_match_id ("MIXED"))
+    fty.type = FTY_MIXED;
+  else if (lex_match_id ("GROUPED"))
+    {
+      fty.type = FTY_GROUPED;
+      fty.wild = 1;
+      fty.duplicate = 1;
+      fty.missing = 1;
+      fty.ordered = 1;
+    }
+  else if (lex_match_id ("NESTED"))
+    fty.type = FTY_NESTED;
+  else
+    {
+      msg (SE, _("MIXED, GROUPED, or NESTED expected."));
+      return CMD_FAILURE;
+    }
+
+  while (token != '.')
+    {
+      if (lex_match_id ("FILE"))
+       {
+         lex_match ('=');
+         fty.handle = fh_parse_file_handle ();
+         if (!fty.handle)
+           return CMD_FAILURE;
+       }
+      else if (lex_match_id ("RECORD"))
+       {
+         lex_match ('=');
+         if (!parse_col_spec (&fty.record, "####RECD"))
+           return CMD_FAILURE;
+       }
+      else if (lex_match_id ("CASE"))
+       {
+         if (fty.type == FTY_MIXED)
+           {
+             msg (SE, _("The CASE subcommand is not valid on FILE TYPE "
+                        "MIXED."));
+             return CMD_FAILURE;
+           }
+         
+         lex_match ('=');
+         if (!parse_col_spec (&fty.case_sbc, "####CASE"))
+           return CMD_FAILURE;
+       }
+      else if (lex_match_id ("WILD"))
+       {
+         lex_match ('=');
+         if (lex_match_id ("WARN"))
+           fty.wild = 1;
+         else if (lex_match_id ("NOWARN"))
+           fty.wild = 0;
+         else
+           {
+             msg (SE, _("WARN or NOWARN expected after WILD."));
+             return CMD_FAILURE;
+           }
+       }
+      else if (lex_match_id ("DUPLICATE"))
+       {
+         if (fty.type == FTY_MIXED)
+           {
+             msg (SE, _("The DUPLICATE subcommand is not valid on "
+                        "FILE TYPE MIXED."));
+             return CMD_FAILURE;
+           }
+
+         lex_match ('=');
+         if (lex_match_id ("WARN"))
+           fty.duplicate = 1;
+         else if (lex_match_id ("NOWARN"))
+           fty.duplicate = 0;
+         else if (lex_match_id ("CASE"))
+           {
+             if (fty.type != FTY_NESTED)
+               {
+                 msg (SE, _("DUPLICATE=CASE is only valid on "
+                            "FILE TYPE NESTED."));
+                 return CMD_FAILURE;
+               }
+             
+             fty.duplicate = 2;
+           }
+         else
+           {
+             msg (SE, _("WARN%s expected after DUPLICATE."),
+                  (fty.type == FTY_NESTED ? _(", NOWARN, or CASE")
+                   : _(" or NOWARN")));
+             return CMD_FAILURE;
+           }
+       }
+      else if (lex_match_id ("MISSING"))
+       {
+         if (fty.type == FTY_MIXED)
+           {
+             msg (SE, _("The MISSING subcommand is not valid on "
+                        "FILE TYPE MIXED."));
+             return CMD_FAILURE;
+           }
+         
+         lex_match ('=');
+         if (lex_match_id ("NOWARN"))
+           fty.missing = 0;
+         else if (lex_match_id ("WARN"))
+           fty.missing = 1;
+         else
+           {
+             msg (SE, _("WARN or NOWARN after MISSING."));
+             return CMD_FAILURE;
+           }
+       }
+      else if (lex_match_id ("ORDERED"))
+       {
+         if (fty.type != FTY_GROUPED)
+           {
+             msg (SE, _("ORDERED is only valid on FILE TYPE GROUPED."));
+             return CMD_FAILURE;
+           }
+         
+         lex_match ('=');
+         if (lex_match_id ("YES"))
+           fty.ordered = 1;
+         else if (lex_match_id ("NO"))
+           fty.ordered = 0;
+         else
+           {
+             msg (SE, _("YES or NO expected after ORDERED."));
+             return CMD_FAILURE;
+           }
+       }
+      else
+       {
+         lex_error (_("while expecting a valid subcommand"));
+         return CMD_FAILURE;
+       }
+    }
+
+  if (fty.record.name[0] == 0)
+    {
+      msg (SE, _("The required RECORD subcommand was not present."));
+      return CMD_FAILURE;
+    }
+
+  if (fty.type == FTY_GROUPED)
+    {
+      if (fty.case_sbc.name[0] == 0)
+       {
+         msg (SE, _("The required CASE subcommand was not present."));
+         return CMD_FAILURE;
+       }
+      
+      if (!strcmp (fty.case_sbc.name, fty.record.name))
+       {
+         msg (SE, _("CASE and RECORD must specify different variable "
+                    "names."));
+         return CMD_FAILURE;
+       }
+    }
+
+  default_handle = fty.handle;
+
+  vfm_source = &file_type_source;
+  create_col_var (&fty.record);
+  if (fty.case_sbc.name[0])
+    create_col_var (&fty.case_sbc);
+
+  return CMD_SUCCESS;
+}
+
+/* Creates a variable with attributes specified by struct col_spec C, and
+   stores it into C->V. */
+static void
+create_col_var (struct col_spec *c)
+{
+  int type;
+  int width;
+
+  type = (formats[c->fmt].cat & FCAT_STRING) ? ALPHA : NUMERIC;
+  if (type == ALPHA)
+    width = c->nc;
+  else
+    width = 0;
+  c->v = force_create_variable (&default_dict, c->name, type, width);
+}
+
+/* Parses variable, column, type specifications for a variable. */
+static int
+parse_col_spec (struct col_spec *c, const char *def_name)
+{
+  struct fmt_spec spec;
+
+  if (token == T_ID)
+    {
+      strcpy (c->name, tokid);
+      lex_get ();
+    }
+  else
+    strcpy (c->name, def_name);
+
+  if (!lex_force_int ())
+    return 0;
+  c->fc = lex_integer ();
+  if (c->fc < 1)
+    {
+      msg (SE, _("Column value must be positive."));
+      return 0;
+    }
+  lex_get ();
+
+  lex_negative_to_dash ();
+  if (lex_match ('-'))
+    {
+      if (!lex_force_int ())
+       return 0;
+      c->nc = lex_integer ();
+      lex_get ();
+
+      if (c->nc < c->fc)
+       {
+         msg (SE, _("Ending column precedes beginning column."));
+         return 0;
+       }
+      
+      c->nc -= c->fc - 1;
+    }
+  else
+    c->nc = 1;
+
+  if (lex_match ('('))
+    {
+      const char *cp;
+      if (!lex_force_id ())
+       return 0;
+      c->fmt = parse_format_specifier_name (&cp, 0);
+      if (c->fmt == -1)
+       return 0;
+      if (*cp)
+       {
+         msg (SE, _("Bad format specifier name."));
+         return 0;
+       }
+      lex_get ();
+      if (!lex_force_match (')'))
+       return 0;
+    }
+  else
+    c->fmt = FMT_F;
+
+  spec.type = c->fmt;
+  spec.w = c->nc;
+  spec.d = 0;
+  return check_input_specifier (&spec);
+}
+\f
+/* RECORD TYPE. */
+
+/* Structure being filled in by internal_cmd_record_type. */
+static struct record_type rct;
+
+static int internal_cmd_record_type (void);
+
+/* Parse the RECORD TYPE command. */
+int
+cmd_record_type (void)
+{
+  int result = internal_cmd_record_type ();
+
+  if (result == CMD_FAILURE)
+    {
+      int i;
+
+      if (formats[fty.record.fmt].cat & FCAT_STRING)
+       for (i = 0; i < rct.nv; i++)
+         free (rct.v[i].c);
+      free (rct.v);
+    }
+
+  return result;
+}
+
+static int
+internal_cmd_record_type (void)
+{
+  /* Initialize the record_type structure. */
+  rct.next = NULL;
+  rct.flags = 0;
+  if (fty.duplicate)
+    rct.flags |= RCT_DUPLICATE;
+  if (fty.missing)
+    rct.flags |= RCT_MISSING;
+  rct.v = NULL;
+  rct.nv = 0;
+  rct.ft = n_trns;
+  if (fty.case_sbc.name[0])
+    rct.case_sbc = fty.case_sbc;
+#if __CHECKER__
+  else
+    memset (&rct.case_sbc, 0, sizeof rct.case_sbc);
+  rct.lt = -1;
+#endif
+
+  /* Make sure we're inside a FILE TYPE structure. */
+  if (pgm_state != STATE_INPUT || vfm_source != &file_type_source)
+    {
+      msg (SE, _("This command may only appear within a "
+                "FILE TYPE/END FILE TYPE structure."));
+      return CMD_FAILURE;
+    }
+
+  if (fty.recs_tail && (fty.recs_tail->flags & RCT_OTHER))
+    {
+      msg (SE, _("OTHER may appear only on the last RECORD TYPE command."));
+      return CMD_FAILURE;
+    }
+      
+  if (fty.recs_tail)
+    {
+      fty.recs_tail->lt = n_trns - 1;
+      if (!(fty.recs_tail->flags & RCT_SKIP)
+         && fty.recs_tail->ft == fty.recs_tail->lt)
+       {
+         msg (SE, _("No input commands (DATA LIST, REPEATING DATA) "
+                    "for above RECORD TYPE."));
+         return CMD_FAILURE;
+       }
+    }
+
+  lex_match_id ("RECORD");
+  lex_match_id ("TYPE");
+
+  /* Parse record type values. */
+  if (lex_match_id ("OTHER"))
+    rct.flags |= RCT_OTHER;
+  else
+    {
+      int mv = 0;
+
+      while (token == T_NUM || token == T_STRING)
+       {
+         if (rct.nv >= mv)
+           {
+             mv += 16;
+             rct.v = xrealloc (rct.v, mv * sizeof *rct.v);
+           }
+
+         if (formats[fty.record.fmt].cat & FCAT_STRING)
+           {
+             if (!lex_force_string ())
+               return CMD_FAILURE;
+             rct.v[rct.nv].c = xmalloc (fty.record.nc + 1);
+             st_bare_pad_copy (rct.v[rct.nv].c, ds_value (&tokstr),
+                               fty.record.nc + 1);
+           }
+         else
+           {
+             if (!lex_force_num ())
+               return CMD_FAILURE;
+             rct.v[rct.nv].f = tokval;
+           }
+         rct.nv++;
+         lex_get ();
+
+         lex_match (',');
+       }
+    }
+
+  /* Parse the rest of the subcommands. */
+  while (token != '.')
+    {
+      if (lex_match_id ("SKIP"))
+       rct.flags |= RCT_SKIP;
+      else if (lex_match_id ("CASE"))
+       {
+         if (fty.type == FTY_MIXED)
+           {
+             msg (SE, _("The CASE subcommand is not allowed on "
+                        "the RECORD TYPE command for FILE TYPE MIXED."));
+             return CMD_FAILURE;
+           }
+
+         lex_match ('=');
+         if (!parse_col_spec (&rct.case_sbc, ""))
+           return CMD_FAILURE;
+         if (rct.case_sbc.name[0])
+           {
+             msg (SE, _("No variable name may be specified for the "
+                        "CASE subcommand on RECORD TYPE."));
+             return CMD_FAILURE;
+           }
+         
+         if ((formats[rct.case_sbc.fmt].cat ^ formats[fty.case_sbc.fmt].cat)
+             & FCAT_STRING)
+           {
+             msg (SE, _("The CASE column specification on RECORD TYPE "
+                        "must give a format specifier that is the "
+                        "same type as that of the CASE column "
+                        "specification given on FILE TYPE."));
+             return CMD_FAILURE;
+           }
+       }
+      else if (lex_match_id ("DUPLICATE"))
+       {
+         lex_match ('=');
+         if (lex_match_id ("WARN"))
+           rct.flags |= RCT_DUPLICATE;
+         else if (lex_match_id ("NOWARN"))
+           rct.flags &= ~RCT_DUPLICATE;
+         else
+           {
+             msg (SE, _("WARN or NOWARN expected on DUPLICATE "
+                        "subcommand."));
+             return CMD_FAILURE;
+           }
+       }
+      else if (lex_match_id ("MISSING"))
+       {
+         lex_match ('=');
+         if (lex_match_id ("WARN"))
+           rct.flags |= RCT_MISSING;
+         else if (lex_match_id ("NOWARN"))
+           rct.flags &= ~RCT_MISSING;
+         else
+           {
+             msg (SE, _("WARN or NOWARN expected on MISSING subcommand."));
+             return CMD_FAILURE;
+           }
+       }
+      else if (lex_match_id ("SPREAD"))
+       {
+         lex_match ('=');
+         if (lex_match_id ("YES"))
+           rct.flags |= RCT_SPREAD;
+         else if (lex_match_id ("NO"))
+           rct.flags &= ~RCT_SPREAD;
+         else
+           {
+             msg (SE, _("YES or NO expected on SPREAD subcommand."));
+             return CMD_FAILURE;
+           }
+       }
+      else
+       {
+         lex_error (_("while expecting a valid subcommand"));
+         return CMD_FAILURE;
+       }
+    }
+
+  if (fty.recs_head)
+    fty.recs_tail = fty.recs_tail->next = xmalloc (sizeof *fty.recs_tail);
+  else
+    fty.recs_head = fty.recs_tail = xmalloc (sizeof *fty.recs_tail);
+  memcpy (fty.recs_tail, &rct, sizeof *fty.recs_tail);
+
+  return CMD_SUCCESS;
+}
+\f
+/* END FILE TYPE. */
+
+int
+cmd_end_file_type (void)
+{
+  if (pgm_state != STATE_INPUT || vfm_source != &file_type_source)
+    {
+      msg (SE, _("This command may only appear within a "
+                "FILE TYPE/END FILE TYPE structure."));
+      return CMD_FAILURE;
+    }
+
+  lex_match_id ("TYPE");
+
+  if (fty.recs_tail)
+    {
+      fty.recs_tail->lt = n_trns - 1;
+      if (!(fty.recs_tail->flags & RCT_SKIP)
+         && fty.recs_tail->ft == fty.recs_tail->lt)
+       {
+         msg (SE, _("No input commands (DATA LIST, REPEATING DATA) "
+                    "on above RECORD TYPE."));
+         goto fail;
+       }
+    }
+  else
+    {
+      msg (SE, _("No commands between FILE TYPE and END FILE TYPE."));
+      goto fail;
+    }
+
+  f_trns = n_trns;
+
+  return lex_end_of_command ();
+
+ fail:
+  /* Come here on discovering catastrophic error. */
+  err_cond_fail ();
+  discard_variables ();
+  return CMD_FAILURE;
+}
+\f
+/* FILE TYPE runtime. */
+
+/*static void read_from_file_type_mixed(void);
+   static void read_from_file_type_grouped(void);
+   static void read_from_file_type_nested(void); */
+
+/* Reads any number of cases into temp_case and calls write_case() for
+   each one.  Compare data-list.c:read_from_data_list. */
+static void
+file_type_source_read (void)
+{
+  char *line;
+  int len;
+
+  struct fmt_spec format;
+
+  dfm_push (fty.handle);
+
+  format.type = fty.record.fmt;
+  format.w = fty.record.nc;
+  format.d = 0;
+  while (NULL != (line = dfm_get_record (fty.handle, &len)))
+    {
+      struct record_type *iter;
+      union value v;
+      int i;
+
+      if (formats[fty.record.fmt].cat & FCAT_STRING)
+       {
+         struct data_in di;
+         
+         v.c = temp_case->data[fty.record.v->fv].s;
+
+         data_in_finite_line (&di, line, len,
+                              fty.record.fc, fty.record.fc + fty.record.nc);
+         di.v = (union value *) v.c;
+         di.flags = 0;
+         di.f1 = fty.record.fc;
+         di.format = format;
+         data_in (&di);
+
+         for (iter = fty.recs_head; iter; iter = iter->next)
+           {
+             if (iter->flags & RCT_OTHER)
+               goto found;
+             for (i = 0; i < iter->nv; i++)
+               if (!memcmp (iter->v[i].c, v.c, fty.record.nc))
+                 goto found;
+           }
+         if (fty.wild)
+           msg (SW, _("Unknown record type \"%.*s\"."), fty.record.nc, v.c);
+       }
+      else
+       {
+         struct data_in di;
+
+         data_in_finite_line (&di, line, len,
+                              fty.record.fc, fty.record.fc + fty.record.nc);
+         di.v = &v;
+         di.flags = 0;
+         di.f1 = fty.record.fc;
+         di.format = format;
+         data_in (&di);
+
+         memcpy (&temp_case->data[fty.record.v->fv].f, &v.f, sizeof v.f);
+         for (iter = fty.recs_head; iter; iter = iter->next)
+           {
+             if (iter->flags & RCT_OTHER)
+               goto found;
+             for (i = 0; i < iter->nv; i++)
+               if (approx_eq (iter->v[i].f, v.f))
+                 goto found;
+           }
+         if (fty.wild)
+           msg (SW, _("Unknown record type %g."), v.f);
+       }
+      dfm_fwd_record (fty.handle);
+      continue;
+
+    found:
+      /* Arrive here if there is a matching record_type, which is in
+         iter. */
+      dfm_fwd_record (fty.handle);
+    }
+
+/*  switch(fty.type)
+   {
+   case FTY_MIXED: read_from_file_type_mixed(); break;
+   case FTY_GROUPED: read_from_file_type_grouped(); break;
+   case FTY_NESTED: read_from_file_type_nested(); break;
+   default: assert(0);
+   } */
+
+  dfm_pop (fty.handle);
+}
+
+static void
+file_type_source_destroy_source (void)
+{
+  struct record_type *iter, *next;
+
+  cancel_transformations ();
+  for (iter = fty.recs_head; iter; iter = next)
+    {
+      next = iter->next;
+      free (iter);
+    }
+}
+
+struct case_stream file_type_source =
+  {
+    NULL,
+    file_type_source_read,
+    NULL,
+    NULL,
+    file_type_source_destroy_source,
+    NULL,
+    "FILE TYPE",
+  };
diff --git a/src/filename.c b/src/filename.c
new file mode 100644 (file)
index 0000000..4aaf7d1
--- /dev/null
@@ -0,0 +1,881 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include "alloc.h"
+#include "error.h"
+#include "filename.h"
+#include "settings.h"
+#include "str.h"
+#include "version.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1 */
+#include "debug-print.h"
+
+/* PORTME: Everything in this file is system dependent. */
+
+#if unix
+#include <pwd.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include "stat.h"
+#endif
+
+#if __WIN32__
+#define NOGDI
+#define NOUSER
+#define NONLS
+#include <win32/windows.h>
+#endif
+
+#if __DJGPP__
+#include <sys/stat.h>
+#endif
+\f
+/* Initialization. */
+
+const char *config_path;
+
+void
+fn_init (void)
+{
+  config_path = fn_getenv_default ("STAT_CONFIG_PATH", default_config_path);
+}
+\f
+/* Functions for performing operations on filenames. */
+
+/* Substitutes $variables as defined by GETENV into INPUT and returns
+   a copy of the resultant string.  Supports $var and ${var} syntaxes;
+   $$ substitutes as $. */
+char *
+fn_interp_vars (const char *input, const char *(*getenv) (const char *))
+{
+  struct string output;
+
+  if (NULL == strchr (input, '$'))
+    return xstrdup (input);
+
+  ds_init (NULL, &output, strlen (input));
+
+  for (;;)
+    switch (*input)
+      {
+      case '\0':
+       return ds_value (&output);
+       
+      case '$':
+       input++;
+
+       if (*input == '$')
+         {
+           ds_putchar (&output, '$');
+           input++;
+         }
+       else
+         {
+           int stop;
+           int start;
+           const char *value;
+
+           start = ds_length (&output);
+
+           if (*input == '(')
+             {
+               stop = ')';
+               input++;
+             }
+           else if (*input == '{')
+             {
+               stop = '}';
+               input++;
+             }
+           else
+             stop = 0;
+
+           while (*input && *input != stop
+                  && (stop || isalpha ((unsigned char) *input)))
+             ds_putchar (&output, *input++);
+           
+           value = getenv (ds_value (&output) + start);
+           ds_truncate (&output, start);
+           ds_concat (&output, value);
+
+           if (stop && *input == stop)
+             input++;
+         }
+
+      default:
+       ds_putchar (&output, *input++);
+      }
+}
+
+#if unix
+/* Expands csh tilde notation from the path INPUT into a malloc()'d
+   returned string. */
+char *
+fn_tilde_expand (const char *input)
+{
+  const char *ip;
+  struct string output;
+
+  if (NULL == strchr (input, '~'))
+    return xstrdup (input);
+  ds_init (NULL, &output, strlen (input));
+
+  ip = input;
+
+  for (ip = input; *ip; )
+    if (*ip != '~' || (ip != input && ip[-1] != PATH_DELIMITER))
+      ds_putchar (&output, *ip++);
+    else
+      {
+       static const char stop_set[3] = {DIR_SEPARATOR, PATH_DELIMITER, 0};
+       const char *cp;
+       
+       ip++;
+
+       cp = ip + strcspn (ip, stop_set);
+
+       if (cp > ip)
+         {
+           struct passwd *pwd;
+           char username[9];
+
+           strncpy (username, ip, cp - ip + 1);
+           username[8] = 0;
+           pwd = getpwnam (username);
+
+           if (!pwd || !pwd->pw_dir)
+             ds_putchar (&output, *ip++);
+           else
+             ds_concat (&output, pwd->pw_dir);
+         }
+       else
+         {
+           const char *home = fn_getenv ("HOME");
+           if (!home)
+             ds_putchar (&output, *ip++);
+           else
+             ds_concat (&output, home);
+         }
+
+       ip = cp;
+      }
+
+  return ds_value (&output);
+}
+#else /* !unix */
+char *
+fn_tilde_expand (char *input)
+{
+  return xstrdup (input);
+}
+#endif /* !unix */
+
+/* Searches for a configuration file with name NAME in the path given
+   by PATH, which is tilde- and environment-interpolated.  Directories
+   in PATH are delimited by PATH_DELIMITER, defined in <pref.h>.
+   Returns the malloc'd full name of the first file found, or NULL if
+   none is found.
+
+   If PREPEND is non-NULL, then it is prepended to each filename;
+   i.e., it looks like PREPEND/PATH_COMPONENT/NAME.  This is not done
+   with absolute directories in the path. */
+#if unix || __MSDOS__ || __WIN32__
+char *
+fn_search_path (const char *basename, const char *path, const char *prepend)
+{
+  char *subst_path;
+  struct string filename;
+  const char *bp;
+
+  if (fn_absolute_p (basename))
+    return fn_tilde_expand (basename);
+  
+  {
+    char *temp = fn_interp_vars (path, fn_getenv);
+    bp = subst_path = fn_tilde_expand (temp);
+    free (temp);
+  }
+
+  msg (VM (4), _("Searching for `%s'..."), basename);
+  ds_init (NULL, &filename, 64);
+
+  for (;;)
+    {
+      const char *ep;
+      if (0 == *bp)
+       {
+         msg (VM (4), _("Search unsuccessful!"));
+         ds_destroy (&filename);
+         free (subst_path);
+         return NULL;
+       }
+
+      for (ep = bp; *ep && *ep != PATH_DELIMITER; ep++)
+       ;
+
+      /* Paste together PREPEND/PATH/BASENAME. */
+      ds_clear (&filename);
+      if (prepend && !fn_absolute_p (bp))
+       {
+         ds_concat (&filename, prepend);
+         ds_putchar (&filename, DIR_SEPARATOR);
+       }
+      ds_concat_buffer (&filename, bp, ep - bp);
+      if (ep - bp
+         && ds_value (&filename)[ds_length (&filename) - 1] != DIR_SEPARATOR)
+       ds_putchar (&filename, DIR_SEPARATOR);
+      ds_concat (&filename, basename);
+      
+      msg (VM (5), " - %s", ds_value (&filename));
+      if (fn_exists_p (ds_value (&filename)))
+       {
+         msg (VM (4), _("Found `%s'."), ds_value (&filename));
+         free (subst_path);
+         return ds_value (&filename);
+       }
+
+      if (0 == *ep)
+       {
+         msg (VM (4), _("Search unsuccessful!"));
+         free (subst_path);
+         ds_destroy (&filename);
+         return NULL;
+       }
+      bp = ep + 1;
+    }
+}
+#else /* not unix, msdog, lose32 */
+char *
+fn_search_path (const char *basename, const char *path, const char *prepend)
+{
+  size_t size = strlen (path) + 1 + strlen (basename) + 1;
+  char *string;
+  char *cp;
+  
+  if (prepend)
+    size += strlen (prepend) + 1;
+  string = xmalloc (size);
+  
+  cp = string;
+  if (prepend)
+    {
+      cp = stpcpy (cp, prepend);
+      *cp++ = DIR_SEPARATOR;
+    }
+  cp = stpcpy (cp, path);
+  *cp++ = DIR_SEPARATOR;
+  strcpy (cp, basename);
+
+  return string;
+}
+#endif /* not unix, msdog, lose32 */
+
+/* Prepends directory DIR to filename FILE and returns a malloc()'d
+   copy of it. */
+char *
+fn_prepend_dir (const char *file, const char *dir)
+{
+  char *temp;
+  char *cp;
+  
+  if (fn_absolute_p (file))
+    return xstrdup (file);
+
+  temp = xmalloc (strlen (file) + 1 + strlen (dir) + 1);
+  cp = stpcpy (temp, dir);
+  if (cp != temp && cp[-1] != DIR_SEPARATOR)
+    *cp++ = DIR_SEPARATOR;
+  cp = stpcpy (cp, file);
+
+  return temp;
+}
+
+/* fn_normalize(): This very OS-dependent routine canonicalizes
+   filename FN1.  The filename should not need to be the name of an
+   existing file.  Returns a malloc()'d copy of the canonical name.
+   This function must always succeed; if it needs to bail out then it
+   should return xstrdup(FN1).  */
+#if unix
+char *
+fn_normalize (const char *filename)
+{
+  const char *src;
+  char *fn1, *fn2, *dest;
+  int maxlen;
+
+  if (fn_special_p (filename))
+    return xstrdup (filename);
+  
+  fn1 = fn_tilde_expand (filename);
+
+  /* Follow symbolic links. */
+  for (;;)
+    {
+      fn2 = fn1;
+      fn1 = fn_readlink (fn1);
+      if (!fn1)
+       {
+         fn1 = fn2;
+         break;
+       }
+      free (fn2);
+    }
+
+  maxlen = strlen (fn1) * 2;
+  if (maxlen < 31)
+    maxlen = 31;
+  dest = fn2 = xmalloc (maxlen + 1);
+  src = fn1;
+
+  if (*src == DIR_SEPARATOR)
+    *dest++ = *src++;
+  else
+    {
+      errno = 0;
+#if __CHECKER__
+      memset (dest, 0, maxlen);
+#endif
+      while (getcwd (dest, maxlen - (dest - fn2)) == NULL && errno == ERANGE)
+       {
+         maxlen *= 2;
+         dest = fn2 = xrealloc (fn2, maxlen + 1);
+#if __CHECKER__
+         memset (dest, 0, maxlen);
+#endif
+         errno = 0;
+       }
+      if (errno)
+       {
+         free (fn1);
+         free (fn2);
+         return NULL;
+       }
+      dest = strchr (fn2, '\0');
+      if (dest - fn2 >= maxlen)
+       {
+         int ofs = dest - fn2;
+         maxlen *= 2;
+         fn2 = xrealloc (fn2, maxlen + 1);
+         dest = fn2 + ofs;
+       }
+      if (dest[-1] != DIR_SEPARATOR)
+       *dest++ = DIR_SEPARATOR;
+    }
+
+  for (;;)
+    {
+      int c, f;
+
+      c = *src++;
+
+      f = 0;
+      if (c == DIR_SEPARATOR || c == 0)
+       {
+         /* remove `./', `../' from directory */
+         if (dest[-1] == '.' && dest[-2] == DIR_SEPARATOR)
+           dest--;
+         else if (dest[-1] == '.' && dest[-2] == '.' && dest[-3] == DIR_SEPARATOR)
+           {
+             dest -= 3;
+             if (dest == fn2)
+               dest++;
+             while (dest[-1] != DIR_SEPARATOR)
+               dest--;
+           }
+         else if (dest[-1] != DIR_SEPARATOR)   /* remove extra slashes */
+           f = 1;
+
+         if (c == 0)
+           {
+             if (dest[-1] == DIR_SEPARATOR && dest > fn2 + 1)
+               dest--;
+             *dest = 0;
+             free (fn1);
+
+             return xrealloc (fn2, strlen (fn2) + 1);
+           }
+       }
+      else
+       f = 1;
+
+      if (f)
+       {
+         if (dest - fn2 >= maxlen)
+           {
+             int ofs = dest - fn2;
+             maxlen *= 2;
+             fn2 = xrealloc (fn2, maxlen + 1);
+             dest = fn2 + ofs;
+           }
+         *dest++ = c;
+       }
+    }
+}
+#elif __WIN32__
+char *
+fn_normalize (const char *fn1)
+{
+  DWORD len;
+  DWORD success;
+  char *fn2;
+
+  /* Don't change special filenames. */
+  if (is_special_filename (filename))
+    return xstrdup (filename);
+
+  /* First find the required buffer length. */
+  len = GetFullPathName (fn1, 0, NULL, NULL);
+  if (!len)
+    {
+      fn2 = xstrdup (fn1);
+      return fn2;
+    }
+
+  /* Then make a buffer that big. */
+  fn2 = xmalloc (len);
+  success = GetFullPathName (fn1, len, fn2, NULL);
+  if (success >= len || success == 0)
+    {
+      free (fn2);
+      fn2 = xstrdup (fn1);
+      return fn2;
+    }
+  return fn2;
+}
+#elif __BORLANDC__
+char *
+fn_normalize (const char *fn1)
+{
+  char *fn2 = _fullpath (NULL, fn1, 0);
+  if (fn2)
+    {
+      char *cp;
+      for (cp = fn2; *cp; cp++)
+       *cp = toupper ((unsigned char) (*cp));
+      return fn2;
+    }
+  return xstrdup (fn1);
+}
+#elif __DJGPP__
+char *
+fn_normalize (const char *fn1)
+{
+  char *fn2 = xmalloc (1024);
+  _fixpath (fn1, fn2);
+  fn2 = xrealloc (fn2, strlen (fn2) + 1);
+  return fn2;
+}
+#else /* not Lose32, Unix, or DJGPP */
+char *
+fn_normalize (const char *fn)
+{
+  return xstrdup (fn);
+}
+#endif /* not Lose32, Unix, or DJGPP */
+
+/* Returns the directory part of FILENAME, as a malloc()'d
+   string. */
+char *
+fn_dirname (const char *filename)
+{
+  const char *p;
+  char *s;
+  size_t len;
+
+  len = strlen (filename);
+  if (len == 1 && filename[0] == '/')
+    p = filename + 1;
+  else if (len && filename[len - 1] == DIR_SEPARATOR)
+    p = mm_find_reverse (filename, len - 1, filename + len - 1, 1);
+  else
+    p = strrchr (filename, DIR_SEPARATOR);
+  if (p == NULL)
+    p = filename;
+
+  s = xmalloc (p - filename + 1);
+  memcpy (s, filename, p - filename);
+  s[p - filename] = 0;
+
+  return s;
+}
+
+/* Returns the basename part of FILENAME as a malloc()'d string. */
+#if 0
+char *
+fn_basename (const char *filename)
+{
+  /* Not used, not implemented. */
+  abort ();
+}
+#endif
+\f
+/* Returns the current working directory, as a malloc()'d string.
+   From libc.info. */
+char *
+fn_get_cwd (void)
+{
+  int size = 100;
+  char *buffer = xmalloc (size);
+     
+  for (;;)
+    {
+      char *value = getcwd (buffer, size);
+      if (value != 0)
+       return buffer;
+
+      size *= 2;
+      free (buffer);
+      buffer = xmalloc (size);
+    }
+}
+\f
+/* Find out information about files. */
+
+/* Returns nonzero iff NAME specifies an absolute filename. */
+int
+fn_absolute_p (const char *name)
+{
+#if unix
+  if (name[0] == '/'
+      || !strncmp (name, "./", 2)
+      || !strncmp (name, "../", 3)
+      || name[0] == '~')
+    return 1;
+#elif __MSDOS__
+  if (name[0] == '\\'
+      || !strncmp (name, ".\\", 2)
+      || !strncmp (name, "..\\", 3)
+      || (name[0] && name[1] == ':'))
+    return 1;
+#endif
+  
+  return 0;
+}
+  
+/* Returns 1 if the filename specified is a virtual file that doesn't
+   really exist on disk, 0 if it's a real filename. */
+int
+fn_special_p (const char *filename)
+{
+  if (!strcmp (filename, "-") || !strcmp (filename, "stdin")
+      || !strcmp (filename, "stdout") || !strcmp (filename, "stderr")
+#if unix
+      || filename[0] == '|'
+      || (*filename && filename[strlen (filename) - 1] == '|')
+#endif
+      )
+    return 1;
+
+  return 0;
+}
+
+/* Returns nonzero if file with name NAME exists. */
+int
+fn_exists_p (const char *name)
+{
+#if unix
+  struct stat temp;
+
+  return stat (name, &temp) == 0;
+#else
+  FILE *f = fopen (name, "r");
+  if (!f)
+    return 0;
+  fclose (f);
+  return 1;
+#endif
+}
+
+#if unix
+/* Stolen from libc.info but heavily modified, this is a wrapper
+   around readlink() that allows for arbitrary filename length. */
+char *
+fn_readlink (const char *filename)
+{
+  int size = 128;
+
+  for (;;)
+    {
+      char *buffer = xmalloc (size);
+      int nchars  = readlink (filename, buffer, size);
+      if (nchars == -1)
+       {
+         free (buffer);
+         return NULL;
+       }
+
+      if (nchars < size - 1)
+       {
+         buffer[nchars] = 0;
+         return buffer;
+       }
+      free (buffer);
+      size *= 2;
+    }
+}
+#else /* Not UNIX. */
+char *
+fn_readlink (const char *filename)
+{
+  return NULL;
+}
+#endif /* Not UNIX. */
+\f
+/* Environment variables. */
+
+/* Simulates $VER and $ARCH environment variables. */
+const char *
+fn_getenv (const char *s)
+{
+  if (!strcmp (s, "VER"))
+    return fn_getenv_default ("STAT_VER", bare_version);
+  else if (!strcmp (s, "ARCH"))
+    return fn_getenv_default ("STAT_ARCH", host_system);
+  else
+    return getenv (s);
+}
+
+/* Returns getenv(KEY) if that's non-NULL; else returns DEF. */
+const char *
+fn_getenv_default (const char *key, const char *def)
+{
+  const char *value = getenv (key);
+  return value ? value : def;
+}
+\f
+/* Basic file handling. */
+
+/* Used for giving an error message on a set_safer security
+   violation. */
+static FILE *
+safety_violation (const char *fn)
+{
+  msg (SE, _("Not opening pipe file `%s' because SAFER option set."), fn);
+  errno = EPERM;
+  return NULL;
+}
+
+/* As a general comment on the following routines, a `sensible value'
+   for errno includes 0 if there is no associated system error.  The
+   routines will only set errno to 0 if there is an error in a
+   callback that sets errno to 0; they themselves won't. */
+
+/* File open routine that understands `-' as stdin/stdout and `|cmd'
+   as a pipe to command `cmd'.  Returns resultant FILE on success,
+   NULL on failure.  If NULL is returned then errno is set to a
+   sensible value.  */
+FILE *
+fn_open (const char *fn, const char *mode)
+{
+  assert (mode[0] == 'r' || mode[0] == 'w');
+
+  if (mode[0] == 'r' && (!strcmp (fn, "stdin") || !strcmp (fn, "-"))) 
+    return stdin;
+  else if (mode[0] == 'w' && (!strcmp (fn, "stdout") || !strcmp (fn, "-")))
+    return stdout;
+  else if (mode[0] == 'w' && !strcmp (fn, "stderr"))
+    return stderr;
+  
+#if unix
+  if (fn[0] == '|')
+    {
+      if (set_safer)
+       return safety_violation (fn);
+
+      return popen (&fn[1], mode);
+    }
+  else if (*fn && fn[strlen (fn) - 1] == '|')
+    {
+      char *s;
+      FILE *f;
+
+      if (set_safer)
+       return safety_violation (fn);
+      
+      s = local_alloc (strlen (fn));
+      memcpy (s, fn, strlen (fn) - 1);
+      s[strlen (fn) - 1] = 0;
+      
+      f = popen (s, mode);
+
+      local_free (s);
+
+      return f;
+    }
+  else
+#endif
+    {
+      FILE *f = fopen (fn, mode);
+
+      if (f && mode[0] == 'w')
+       setvbuf (f, NULL, _IOLBF, 0);
+
+      return f;
+    }
+}
+
+/* Counterpart to fn_open that closes file F with name FN; returns 0
+   on success, EOF on failure.  If EOF is returned, errno is set to a
+   sensible value. */
+int
+fn_close (const char *fn, FILE *f)
+{
+  if (!strcmp (fn, "-"))
+    return 0;
+#if unix
+  else if (fn[0] == '|' || (*fn && fn[strlen (fn) - 1] == '|'))
+    {
+      pclose (f);
+      return 0;
+    }
+#endif
+  else
+    return fclose (f);
+}
+\f
+/* More extensive file handling. */
+
+/* File open routine that extends fn_open().  Opens or reopens a
+   file according to the contents of file_ext F.  Returns nonzero on
+   success.  If 0 is returned, errno is set to a sensible value. */
+int
+fn_open_ext (struct file_ext *f)
+{
+  char *p;
+
+  p = strstr (f->filename, "%d");
+  if (p)
+    {
+      char *s = local_alloc (strlen (f->filename) + INT_DIGITS - 1);
+      char *cp;
+
+      memcpy (s, f->filename, p - f->filename);
+      cp = spprintf (&s[p - f->filename], "%d", *f->sequence_no);
+      strcpy (cp, &p[2]);
+
+      if (f->file)
+       {
+         int error = 0;
+
+         if (f->preclose)
+           if (f->preclose (f) == 0)
+             error = errno;
+
+         if (EOF == fn_close (f->filename, f->file) || error)
+           {
+             f->file = NULL;
+             local_free (s);
+
+             if (error)
+               errno = error;
+
+             return 0;
+           }
+
+         f->file = NULL;
+       }
+
+      f->file = fn_open (s, f->mode);
+      local_free (s);
+
+      if (f->file && f->postopen)
+       if (f->postopen (f) == 0)
+         {
+           int error = errno;
+           fn_close (f->filename, f->file);
+           errno = error;
+
+           return 0;
+         }
+
+      return (f->file != NULL);
+    }
+  else if (f->file)
+    return 1;
+  else
+    {
+      f->file = fn_open (f->filename, f->mode);
+
+      if (f->file && f->postopen)
+       if (f->postopen (f) == 0)
+         {
+           int error = errno;
+           fn_close (f->filename, f->file);
+           errno = error;
+
+           return 0;
+         }
+
+      return (f->file != NULL);
+    }
+}
+
+/* Properly closes the file associated with file_ext F, if any.
+   Return nonzero on success.  If zero is returned, errno is set to a
+   sensible value. */
+int
+fn_close_ext (struct file_ext *f)
+{
+  if (f->file)
+    {
+      int error = 0;
+
+      if (f->preclose)
+       if (f->preclose (f) == 0)
+         error = errno;
+
+      if (EOF == fn_close (f->filename, f->file) || error)
+       {
+         f->file = NULL;
+
+         if (error)
+           errno = error;
+
+         return 0;
+       }
+
+      f->file = NULL;
+    }
+  return 1;
+}
diff --git a/src/filename.h b/src/filename.h
new file mode 100644 (file)
index 0000000..499e693
--- /dev/null
@@ -0,0 +1,73 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !filename_h
+#define filename_h 1
+
+#include <stdio.h>
+
+/* Search path for configuration files. */
+extern const char *config_path;
+
+void fn_init (void);
+
+char *fn_interp_vars (const char *input, const char *(*getenv) (const char *));
+char *fn_tilde_expand (const char *fn);
+char *fn_search_path (const char *basename, const char *path,
+                     const char *prepend);
+char *fn_prepend_dir (const char *filename, const char *directory);
+char *fn_normalize (const char *fn);
+char *fn_dirname (const char *fn);
+char *fn_basename (const char *fn);
+
+char *fn_get_cwd (void);
+
+int fn_absolute_p (const char *fn);
+int fn_special_p (const char *fn);
+int fn_exists_p (const char *fn);
+char *fn_readlink (const char *fn);
+
+const char *fn_getenv (const char *variable);
+const char *fn_getenv_default (const char *variable, const char *def);
+
+FILE *fn_open (const char *fn, const char *mode);
+int fn_close (const char *fn, FILE *file);
+\f
+/* Extended file routines. */
+struct file_ext;
+
+typedef int (*file_callback) (struct file_ext *);
+
+/* File callbacks may not return zero to indicate failure unless they
+   set errno to a sensible value. */
+struct file_ext
+  {
+    char *filename;            /* Filename. */
+    const char *mode;          /* Open mode, i.e, "wb". */
+    FILE *file;                        /* File. */
+    int *sequence_no;          /* Page number, etc. */
+    void *param;               /* User data. */
+    file_callback postopen;    /* Called after FILE opened. */
+    file_callback preclose;    /* Called before FILE closed. */
+  };
+
+int fn_open_ext (struct file_ext *file);
+int fn_close_ext (struct file_ext *file);
+
+#endif /* filename_h */
diff --git a/src/flip.c b/src/flip.c
new file mode 100644 (file)
index 0000000..7ee4e70
--- /dev/null
@@ -0,0 +1,549 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+#include "var.h"
+#include "vfm.h"
+
+/* Variables to transpose. */
+static struct variable **var;
+static int nvar;
+
+/* Variable containing new variable names. */
+static struct variable *newnames;
+
+/* List of variable names. */
+struct varname
+  {
+    struct varname *next;
+    char name[1];
+  };
+
+/* New variable names. */
+static struct varname *new_names_head, *new_names_tail;
+static int case_count;
+
+static int build_dictionary (void);
+
+/* Parses and executes FLIP. */
+int
+cmd_flip (void)
+{
+  lex_match_id ("FLIP");
+  lex_match ('/');
+  if (lex_match_id ("VARIABLES"))
+    {
+      lex_match ('=');
+      if (!parse_variables (&default_dict, &var, &nvar, PV_NO_DUPLICATE))
+       return CMD_FAILURE;
+      lex_match ('/');
+    }
+  else
+    fill_all_vars (&var, &nvar, FV_NO_SYSTEM);
+
+  lex_match ('/');
+  if (lex_match_id ("NEWNAMES"))
+    {
+      lex_match ('=');
+      newnames = parse_variable ();
+      if (!newnames)
+       {
+         free (var);
+         return CMD_FAILURE;
+       }
+    }
+  else
+    newnames = find_variable ("CASE_LBL");
+
+  if (newnames)
+    {
+      int i;
+      
+      for (i = 0; i < nvar; i++)
+       if (var[i] == newnames)
+         {
+           memcpy (&var[i], &var[i + 1], sizeof *var * (nvar - i - 1));
+           nvar--;
+           break;
+         }
+    }
+
+  case_count = 0;
+  temp_trns = temporary = 0;
+  vfm_sink = &flip_stream;
+  new_names_tail = NULL;
+  procedure (NULL, NULL, NULL);
+
+  clear_default_dict ();
+  if (!build_dictionary ())
+    {
+      discard_variables ();
+      free (var);
+      return CMD_FAILURE;
+    }
+
+  free (var);
+  return lex_end_of_command ();
+}
+
+/* Make a new variable with base name NAME, which is bowdlerized and
+   mangled until acceptable, and returns success. */
+static int
+make_new_var (char name[])
+{
+  /* Fix invalid characters. */
+  {
+    char *cp;
+  
+    for (cp = name; *cp && !isspace (*cp); cp++)
+      {
+       *cp = toupper ((unsigned char) *cp);
+       if (!isalpha (*cp) && *cp != '@' && *cp != '#'
+           && (cp == name || (*cp != '.' && *cp != '$' && *cp != '_')))
+         {
+           if (cp == name)
+             *cp = 'V';        /* _ not valid in first position. */
+           else
+             *cp = '_';
+         }
+      }
+    *cp = 0;
+  }
+  
+  if (create_variable (&default_dict, name, NUMERIC, 0))
+    return 1;
+
+  /* Add numeric extensions until acceptable. */
+  {
+    int len = (int) strlen (name);
+    char n[9];
+    int i;
+
+    for (i = 1; i < 10000000; i++)
+      {
+       int ofs = min (7 - intlog10 (i), len);
+       memcpy (n, name, ofs);
+       sprintf (&n[ofs], "%d", i);
+
+       if (create_variable (&default_dict, n, NUMERIC, 0))
+         return 1;
+      }
+  }
+
+  msg (SE, _("Could not create acceptable variant for variable %s."), name);
+  return 0;
+}
+
+/* Make a new dictionary for all the new variable names. */
+static int
+build_dictionary (void)
+{
+  force_create_variable (&default_dict, "CASE_LBL", ALPHA, 8);
+
+  if (!new_names_tail)
+    {
+      int i;
+      
+      if (case_count > 99999)
+       {
+         msg (SE, _("Cannot create more than 99999 variable names."));
+         return 0;
+       }
+      
+      for (i = 0; i < case_count; i++)
+       {
+         char s[9];
+
+         sprintf (s, "VAR%03d", i);
+         force_create_variable (&default_dict, s, NUMERIC, 0);
+       }
+    }
+  else
+    {
+      struct varname *v, *n;
+
+      new_names_tail->next = NULL;
+      for (v = new_names_head; v; v = n)
+       {
+         n = v->next;
+         if (!make_new_var (v->name))
+           {
+             for (; v; v = n)
+               {
+                 n = v->next;
+                 free (v);
+               }
+             return 0;
+           }
+         free (v);
+       }
+    }
+  
+  return 1;
+}
+     
+
+/* Each case to be transposed. */
+struct flip_case
+  {
+    struct flip_case *next;
+    double v[1];
+  };
+
+/* Sink: Cases during transposition. */
+static int internal;                   /* Internal vs. external flipping. */
+static char *sink_old_names;           /* Old variable names. */
+static unsigned long sink_cases;       /* Number of cases. */
+static struct flip_case *head, *tail;  /* internal == 1: Cases. */
+static FILE *sink_file;                        /* internal == 0: Temporary file. */
+
+/* Source: Cases after transposition. */
+static struct flip_case *src;          /* Internal transposition records. */
+static char *src_old_names;            /* Old variable names. */
+static unsigned long src_cases;                /* Number of cases. */
+static FILE *src_file;                 /* src == NULL: Temporary file. */
+
+/* Initialize the FLIP stream. */
+static void 
+flip_stream_init (void)
+{
+  internal = 1;
+  sink_cases = 0;
+  tail = NULL;
+  
+  {
+    size_t n = nvar;
+    char *p;
+    int i;
+    
+    for (i = 0; i < nvar; i++)
+      n += strlen (var[i]->name);
+    p = sink_old_names = xmalloc (n);
+    for (i = 0; i < nvar; i++)
+      p = stpcpy (p, var[i]->name) + 1;
+  }
+}
+
+/* Reads the FLIP stream and passes it to write_case(). */
+static void
+flip_stream_read (void)
+{
+  if (src || (src == NULL && src_file == NULL))
+    {
+      /* Internal transposition, or empty file. */
+      int i, j;
+      char *p = src_old_names;
+      
+      for (i = 0; i < nvar; i++)
+       {
+         struct flip_case *iter;
+         
+         st_bare_pad_copy (temp_case->data[0].s, p, 8);
+         p = strchr (p, 0) + 1;
+
+         for (iter = src, j = 1; iter; iter = iter->next, j++)
+           temp_case->data[j].f = iter->v[i];
+
+         if (!write_case ())
+           return;
+       }
+    }
+  else
+    {
+      int i;
+      char *p = src_old_names;
+      
+      for (i = 0; i < nvar; i++)
+       {
+         st_bare_pad_copy (temp_case->data[0].s, p, 8);
+         p = strchr (p, 0) + 1;
+
+         if (fread (&temp_case->data[1], sizeof (double), src_cases,
+                    src_file) != src_cases)
+           msg (FE, _("Error reading FLIP source file: %s."),
+                strerror (errno));
+
+         if (!write_case ())
+           return;
+       }
+    }
+}
+
+/* Writes temp_case to the FLIP stream. */
+static void
+flip_stream_write (void)
+{
+  sink_cases++;
+
+  if (newnames)
+    {
+      struct varname *v;
+      char name[INT_DIGITS + 2];
+
+      if (newnames->type == NUMERIC)
+       sprintf (name, "V%d", (int) temp_case->data[newnames->fv].f);
+      else
+       {
+         int width = min (newnames->width, 8);
+         memcpy (name, temp_case->data[newnames->fv].s, width);
+         name[width] = 0;
+       }
+
+      v = xmalloc (sizeof (struct varname) + strlen (name) - 1);
+      strcpy (v->name, name);
+      
+      if (new_names_tail == NULL)
+       new_names_head = v;
+      else
+       new_names_tail->next = v;
+      new_names_tail = v;
+    }
+  else
+    case_count++;
+
+  if (internal)
+    {
+#if 0
+      flip_case *c = malloc (sizeof (flip_case)
+                            + sizeof (double) * (nvar - 1));
+      
+      if (c != NULL)
+       {
+         /* Write to internal file. */
+         int i;
+
+         for (i = 0; i < nvar; i++)
+           if (var[i]->type == NUMERIC)
+             c->v[i] = temp_case->data[var[i]->fv].f;
+           else
+             c->v[i] = SYSMIS;
+
+         if (tail == NULL)
+           head = c;
+         else
+           tail->next = c;
+         tail = c;
+         
+         return;
+       }
+      else
+#endif
+       {
+         /* Initialize external file. */
+         struct flip_case *iter, *next;
+
+         internal = 0;
+
+         sink_file = tmpfile ();
+         if (!sink_file)
+           msg (FE, _("Could not create temporary file for FLIP."));
+
+         if (tail)
+           tail->next = NULL;
+         for (iter = head; iter; iter = next)
+           {
+             next = iter->next;
+
+             if (fwrite (iter->v, sizeof (double), nvar, sink_file)
+                 != (size_t) nvar)
+               msg (FE, _("Error writing FLIP file: %s."),
+                    strerror (errno));
+             free (iter);
+           }
+       }
+    }
+
+  /* Write to external file. */
+  {
+    double *d = local_alloc (sizeof *d * nvar);
+    int i;
+
+    for (i = 0; i < nvar; i++)
+      if (var[i]->type == NUMERIC)
+       d[i] = temp_case->data[var[i]->fv].f;
+      else
+       d[i] = SYSMIS;
+         
+    if (fwrite (d, sizeof *d, nvar, sink_file) != (size_t) nvar)
+      msg (FE, _("Error writing FLIP file: %s."),
+          strerror (errno));
+
+    local_free (d);
+  }
+}
+      
+/* Transpose the external file. */
+static void
+transpose_external_file (void)
+{
+  unsigned long n_cases;
+  unsigned long cur_case;
+  double *case_buf, *temp_buf;
+
+  n_cases = 4 * 1024 * 1024 / ((nvar + 1) * sizeof *case_buf);
+  if (n_cases < 2)
+    n_cases = 2;
+  for (;;)
+    {
+      assert (n_cases >= 2 /* 1 */);
+      case_buf = ((n_cases <= 2 ? xmalloc : (void *(*)(size_t)) malloc)
+                 ((nvar + 1) * sizeof *case_buf * n_cases));
+      if (case_buf)
+       break;
+
+      n_cases /= 2;
+      if (n_cases < 2)
+       n_cases = 2;
+    }
+
+  /* A temporary buffer that holds n_cases elements. */
+  temp_buf = &case_buf[nvar * n_cases];
+
+  src_file = tmpfile ();
+  if (!src_file)
+    msg (FE, _("Error creating FLIP source file."));
+  
+  if (fseek (sink_file, 0, SEEK_SET) != 0)
+    msg (FE, _("Error rewinding FLIP file: %s."), strerror (errno));
+
+  for (cur_case = 0; cur_case < sink_cases; )
+    {
+      unsigned long read_cases = min (sink_cases - cur_case, n_cases);
+      int i;
+
+      if (read_cases != fread (case_buf, sizeof *case_buf * nvar,
+                              read_cases, sink_file))
+       msg (FE, _("Error reading FLIP file: %s."), strerror (errno));
+
+      for (i = 0; i < nvar; i++)
+       {
+         unsigned long j;
+         
+         for (j = 0; j < read_cases; j++)
+           temp_buf[j] = case_buf[i + j * nvar];
+
+         if (fseek (src_file,
+                    sizeof *case_buf * (cur_case + i * sink_cases),
+                    SEEK_SET) != 0)
+           msg (FE, _("Error seeking FLIP source file: %s."),
+                      strerror (errno));
+
+         if (fwrite (temp_buf, sizeof *case_buf, read_cases, src_file)
+             != read_cases)
+           msg (FE, _("Error writing FLIP source file: %s."),
+                strerror (errno));
+       }
+
+      cur_case += read_cases;
+    }
+
+  if (fseek (src_file, 0, SEEK_SET) != 0)
+    msg (FE, _("Error rewind FLIP source file: %s."), strerror (errno));
+
+  fclose (sink_file);
+
+  free (case_buf);
+}
+
+/* Change the FLIP stream from sink to source mode. */
+static void
+flip_stream_mode (void)
+{
+  src_cases = sink_cases;
+  src_old_names = sink_old_names;
+  sink_old_names = NULL;
+  
+  if (internal)
+    {
+      if (tail)
+       {
+         tail->next = NULL;
+         src = head;
+       }
+      else
+       {
+         src = NULL;
+         src_file = NULL;
+       }
+    }
+  else
+    {
+      src = NULL;
+      transpose_external_file ();
+    }
+}
+
+/* Destroy source's internal data. */
+static void
+flip_stream_destroy_source (void)
+{
+  free (src_old_names);
+  if (internal)
+    {
+      struct flip_case *iter, *next;
+
+      for (iter = src; iter; iter = next)
+       {
+         next = iter->next;
+         free (iter);
+       }
+    }
+  else
+    fclose (src_file);
+}
+
+/* Destroy sink's internal data. */
+static void
+flip_stream_destroy_sink (void)
+{
+  struct flip_case *iter, *next;
+  
+  free (sink_old_names);
+  if (tail == NULL)
+    return;
+
+  tail->next = NULL;
+  for (iter = head; iter; iter = next)
+    {
+      next = iter->next;
+      free (iter);
+    }
+}
+
+struct case_stream flip_stream = 
+  {
+    flip_stream_init,
+    flip_stream_read,
+    flip_stream_write,
+    flip_stream_mode,
+    flip_stream_destroy_source,
+    flip_stream_destroy_sink,
+    "FLIP",
+  };
diff --git a/src/font.h b/src/font.h
new file mode 100644 (file)
index 0000000..67c276c
--- /dev/null
@@ -0,0 +1,141 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !font_h
+#define font_h 1
+
+/* Possible ligatures. */
+#define LIG_ff  001
+#define LIG_ffi 002
+#define LIG_ffl 004
+#define LIG_fi  010
+#define LIG_fl  020
+
+/* Character type constants. */
+#define CTYP_NONE      000     /* Neither ascenders nor descenders. */
+#define CTYP_ASCENDER  001     /* Character has an ascender. */
+#define CTYP_DESCENDER 002     /* Character has a descender. */
+
+/* Font metrics for a single character.  */
+struct char_metrics
+  {
+    int code;                  /* Character code. */
+    int type;                  /* CTYP_* constants. */
+    int width;                 /* Width. */
+    int height;                        /* Height above baseline, never negative. */
+    int depth;                 /* Depth below baseline, never negative. */
+
+    /* These fields are not yet used, so to save memory, they are left
+       out. */
+#if 0
+    int italic_correction;     /* Italic correction. */
+    int left_italic_correction;        /* Left italic correction. */
+    int subscript_correction;  /* Subscript correction. */
+#endif
+  };
+
+/* Kerning for a pair of characters.  */
+struct kern_pair
+  {
+    int ch1;                   /* First character. */
+    int ch2;                   /* Second character. */
+    int adjust;                        /* Kern amount. */
+  };
+
+/* Font description.  */
+struct font_desc
+  {
+    /* Housekeeping data. */
+    struct pool *owner;                /* Containing pool. */
+    char *name;                        /* Font name.  FIXME: this field's
+                                  role is uncertain. */
+    char *filename;            /* Normalized filename. */
+
+    /* PostScript-specific courtesy data. */
+    char *internal_name;       /* Font internal name. */
+    char *encoding;            /* Name of encoding file. */
+
+    /* Basic font characteristics. */
+    int space_width;           /* Width of a space character. */
+    double slant;              /* Slant angle, in degrees of forward slant. */
+    unsigned ligatures;                /* Characters that have ligatures. */
+    int special;               /* 1=This is a special font that will be
+                                  searched when a character is not present in
+                                  another font. */
+    int ascent, descent;       /* Height above, below the baseline. */
+
+    /* First dereferencing level is font_char_name_to_index(NAME). */
+    /* Second dereferencing level. */
+    short *deref;              /* Each entry is an index into metric.
+                                  metric[deref[lookup(NAME)]] is the metric
+                                  for character with name NAME. */
+    int deref_size;            /* Number of spaces for entries in deref. */
+
+    /* Third dereferencing level. */
+    struct char_metrics **metric;      /* Metrics for font characters. */
+    int metric_size;           /* Number of spaces for entries in metric. */
+    int metric_used;           /* Number of spaces used in metric. */
+
+    /* Kern pairs. */
+    struct kern_pair *kern;    /* Hash table for kerns. */
+    int kern_size;             /* Number of spaces for kerns in kern. */
+    int *kern_size_p;          /* Next larger hash table size. */
+    int kern_used;             /* Number of used spaces in kern. */
+    int kern_max_used;         /* Max number used before rehashing. */
+  };
+
+/* Index into deref[] of character with name "space". */
+extern int space_index;
+
+/* A set of fonts. */
+struct font_set
+  {
+    struct font_set *next, *prev;      /* Next, previous in chain. */
+    struct font_desc *font;            /* Current font. */
+  };
+
+/* Functions to work with any font. */
+#define destroy_font(FONT)                     \
+       pool_destroy (FONT->owner)
+
+int font_char_name_to_index (const char *);
+struct char_metrics *font_get_char_metrics (const struct font_desc *font,
+                                           int ch);
+int font_get_kern_adjust (const struct font_desc *font, int ch1, int ch2);
+
+/* groff fonts. */
+struct groff_device_info
+  {
+    /* See groff_font(5). */
+    int res, horiz, vert;
+    int size_scale, unit_width;
+    int (*sizes)[2], n_sizes;
+    char *font_name[4];                /* Names of 4 default fonts. */
+    char *family;              /* Name of default font family. */
+  };
+
+struct outp_driver;
+struct font_desc *groff_read_font (const char *fn);
+struct font_desc *groff_find_font (const char *dev, const char *name);
+int groff_read_DESC (const char *dev_name, struct groff_device_info * dev);
+void groff_init (void);
+
+struct font_desc *default_font (void);
+
+#endif /* font_h */
diff --git a/src/format.c b/src/format.c
new file mode 100644 (file)
index 0000000..61b223e
--- /dev/null
@@ -0,0 +1,343 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "error.h"
+#include "format.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+
+#define DEFFMT(LABEL, NAME, N_ARGS, IMIN_W, IMAX_W, OMIN_W, OMAX_W, CAT, \
+              OUTPUT, SPSS_FMT) \
+       {NAME, N_ARGS, IMIN_W, IMAX_W, OMIN_W, OMAX_W, CAT, OUTPUT, SPSS_FMT},
+struct fmt_desc formats[FMT_NUMBER_OF_FORMATS + 1] =
+{
+#include "format.def"
+  {"",         -1, -1,  -1, -1,   -1, 0000, -1, -1},
+};
+
+const int translate_fmt[40] =
+  {
+    -1, FMT_A, FMT_AHEX, FMT_COMMA, FMT_DOLLAR, FMT_F, FMT_IB,
+    FMT_PIBHEX, FMT_P, FMT_PIB, FMT_PK, FMT_RB, FMT_RBHEX, -1,
+    -1, FMT_Z, FMT_N, FMT_E, -1, -1, FMT_DATE, FMT_TIME,
+    FMT_DATETIME, FMT_ADATE, FMT_JDATE, FMT_DTIME, FMT_WKDAY,
+    FMT_MONTH, FMT_MOYR, FMT_QYR, FMT_WKYR, FMT_PCT, FMT_DOT,
+    FMT_CCA, FMT_CCB, FMT_CCC, FMT_CCD, FMT_CCE, FMT_EDATE,
+    FMT_SDATE,
+  };
+
+int
+parse_format_specifier_name (const char **cp, int allow_xt)
+{
+  struct fmt_desc *f;
+  char *ep;
+  int x;
+
+  ep = ds_value (&tokstr);
+  while (isalpha ((unsigned char) *ep))
+    ep++;
+  x = *ep;
+  *ep = 0;
+
+  for (f = formats; f->name[0]; f++)
+    if (!strcmp (f->name, ds_value (&tokstr)))
+      {
+       int indx = f - formats;
+
+       *ep = x;
+       if (cp)
+         *cp = ep;
+
+       if (!allow_xt && (indx == FMT_T || indx == FMT_X))
+         {
+           msg (SE, _("X and T format specifiers not allowed here."));
+           return -1;
+         }
+       return indx;
+      }
+
+  msg (SE, _("%s is not a valid data format."), ds_value (&tokstr));
+  *ep = x;
+  return -1;
+}
+
+/* Converts F to its string representation (for instance, "F8.2") and
+   returns a pointer to a static buffer containing that string. */
+char *
+fmt_to_string (const struct fmt_spec *f)
+{
+  static char buf[32];
+
+  if (formats[f->type].n_args >= 2)
+    sprintf (buf, "%s%d.%d", formats[f->type].name, f->w, f->d);
+  else
+    sprintf (buf, "%s%d", formats[f->type].name, f->w);
+  return buf;
+}
+
+int
+check_input_specifier (const struct fmt_spec *spec)
+{
+  struct fmt_desc *f;
+  char *str;
+
+  f = &formats[spec->type];
+  str = fmt_to_string (spec);
+  if (spec->type == FMT_X)
+    return 1;
+  if (f->cat & FCAT_OUTPUT_ONLY)
+    {
+      msg (SE, _("Format %s may not be used as an input format."), f->name);
+      return 0;
+    }
+  if (spec->w < f->Imin_w || spec->w > f->Imax_w)
+    {
+      msg (SE, _("Input format %s specifies a bad width %d.  "
+                "Format %s requires a width between %d and %d."),
+          str, spec->w, f->name, f->Imin_w, f->Imax_w);
+      return 0;
+    }
+  if ((f->cat & FCAT_EVEN_WIDTH) && spec->w % 2)
+    {
+      msg (SE, _("Input format %s specifies an odd width %d, but "
+                "format %s requires an even width between %d and "
+                "%d."), str, spec->w, f->name, f->Imin_w, f->Imax_w);
+      return 0;
+    }
+  if (f->n_args > 1 && (spec->d < 0 || spec->d > 16))
+    {
+      msg (SE, _("Input format %s specifies a bad number of "
+                "implied decimal places %d.  Input format %s allows "
+                "up to 16 implied decimal places."), str, spec->d, f->name);
+      return 0;
+    }
+  return 1;
+}
+
+int
+check_output_specifier (const struct fmt_spec *spec)
+{
+  struct fmt_desc *f;
+  char *str;
+
+  f = &formats[spec->type];
+  str = fmt_to_string (spec);
+  if (spec->type == FMT_X)
+    return 1;
+  if (spec->w < f->Omin_w || spec->w > f->Omax_w)
+    {
+      msg (SE, _("Output format %s specifies a bad width %d.  "
+                "Format %s requires a width between %d and %d."),
+          str, spec->w, f->name, f->Omin_w, f->Omax_w);
+      return 0;
+    }
+  if (spec->d > 1
+      && (spec->type == FMT_F || spec->type == FMT_COMMA
+         || spec->type == FMT_DOLLAR)
+      && spec->w < f->Omin_w + 1 + spec->d)
+    {
+      msg (SE, _("Output format %s requires minimum width %d to allow "
+                "%d decimal places.  Try %s%d.%d instead of %s."),
+          f->name, f->Omin_w + 1 + spec->d, spec->d, f->name,
+          f->Omin_w + 1 + spec->d, spec->d, str);
+      return 0;
+    }
+  if ((f->cat & FCAT_EVEN_WIDTH) && spec->w % 2)
+    {
+      msg (SE, _("Output format %s specifies an odd width %d, but "
+                "output format %s requires an even width between %d and "
+                "%d."), str, spec->w, f->name, f->Omin_w, f->Omax_w);
+      return 0;
+    }
+  if (f->n_args > 1 && (spec->d < 0 || spec->d > 16))
+    {
+      msg (SE, _("Output format %s specifies a bad number of "
+                "implied decimal places %d.  Output format %s allows "
+                "a number of implied decimal places between 1 "
+                "and 16."), str, spec->d, f->name);
+      return 0;
+    }
+  return 1;
+}
+
+/* If a string variable has width W, you can't display it with a
+   format specifier with a required width MIN_LEN>W. */
+int
+check_string_specifier (const struct fmt_spec *f, int min_len)
+{
+  if ((f->type == FMT_A && min_len > f->w)
+      || (f->type == FMT_AHEX && min_len * 2 > f->w))
+    {
+      msg (SE, _("Can't display a string variable of width %d with "
+                "format specifier %s."), min_len, fmt_to_string (f));
+      return 0;
+    }
+  return 1;
+}
+
+void
+convert_fmt_ItoO (const struct fmt_spec *input, struct fmt_spec *output)
+{
+  output->type = formats[input->type].output;
+  output->w = input->w;
+  if (output->w > formats[output->type].Omax_w)
+    output->w = formats[output->type].Omax_w;
+  output->d = input->d;
+
+  switch (input->type)
+    {
+    case FMT_F:
+    case FMT_N:
+      if (output->d > 1 && output->w < 2 + output->d)
+       output->w = 2 + output->d;
+      break;
+    case FMT_E:
+      output->w = max (max (input->w, input->d+7), 10);
+      output->d = max (input->d, 3);
+      break;
+    case FMT_COMMA:
+    case FMT_DOT:
+      /* nothing is necessary */
+      break;
+    case FMT_DOLLAR:
+    case FMT_PCT:
+      if (output->w < 2)
+       output->w = 2;
+      break;
+    case FMT_PIBHEX:
+      {
+       static const int map[] = {4, 6, 9, 11, 14, 16, 18, 21};
+       assert (input->w % 2 == 0 && input->w >= 2 && input->w <= 16);
+       output->w = map[input->w / 2 - 1];
+       break;
+      }
+    case FMT_RBHEX:
+      output->w = 8, output->d = 2;    /* FIXME */
+      break;
+    case FMT_IB:
+    case FMT_PIB:
+    case FMT_P:
+    case FMT_PK:
+    case FMT_RB:
+      if (input->d < 1)
+       output->w = 8, output->d = 2;
+      else
+       output->w = 9 + input->d;
+      break;
+    case FMT_CCA:
+    case FMT_CCB:
+    case FMT_CCC:
+    case FMT_CCD:
+    case FMT_CCE:
+      assert (0);
+    case FMT_Z:
+    case FMT_A:
+      /* nothing is necessary */
+      break;
+    case FMT_AHEX:
+      output->w = input->w / 2;
+      break;
+    case FMT_DATE:
+    case FMT_EDATE:
+    case FMT_SDATE:
+    case FMT_ADATE:
+    case FMT_JDATE:
+      /* nothing is necessary */
+      break;
+    case FMT_QYR:
+      if (output->w < 6)
+       output->w = 6;
+      break;
+    case FMT_MOYR:
+      /* nothing is necessary */
+      break;
+    case FMT_WKYR:
+      if (output->w < 8)
+       output->w = 8;
+      break;
+    case FMT_TIME:
+    case FMT_DTIME:
+    case FMT_DATETIME:
+    case FMT_WKDAY:
+    case FMT_MONTH:
+      /* nothing is necessary */
+      break;
+    default:
+      assert (0);
+    }
+}
+
+int
+parse_format_specifier (struct fmt_spec *input, int allow_xt)
+{
+  struct fmt_spec spec;
+  struct fmt_desc *f;
+  const char *cp;
+  char *cp2;
+  int type, w, d;
+
+  if (token != T_ID)
+    {
+      msg (SE, _("Format specifier expected."));
+      return 0;
+    }
+  type = parse_format_specifier_name (&cp, allow_xt);
+  if (type == -1)
+    return 0;
+  f = &formats[type];
+
+  w = strtol (cp, &cp2, 10);
+  if (cp2 == cp && type != FMT_X)
+    {
+      msg (SE, _("Data format %s does not specify a width."),
+          ds_value (&tokstr));
+      return 0;
+    }
+
+  cp = cp2;
+  if (f->n_args > 1 && *cp == '.')
+    {
+      cp++;
+      d = strtol (cp, &cp2, 10);
+      cp = cp2;
+    }
+  else
+    d = 0;
+
+  if (*cp)
+    {
+      msg (SE, _("Data format %s is not valid."), ds_value (&tokstr));
+      return 0;
+    }
+  lex_get ();
+
+  spec.type = type;
+  spec.w = w;
+  spec.d = d;
+  *input = spec;
+
+  return 1;
+}
+
diff --git a/src/format.def b/src/format.def
new file mode 100644 (file)
index 0000000..fda776d
--- /dev/null
@@ -0,0 +1,65 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* Numeric and string formats. */
+DEFFMT (FMT_F,            "F",         2,  1,  40,  1,   40, 0001, FMT_F, 5)
+DEFFMT (FMT_N,           "N",         2,  1,  40,  1,   40, 0011, FMT_F, 16)
+DEFFMT (FMT_E,           "E",         2,  1,  40,  6,   40, 0001, FMT_E, 17)
+DEFFMT (FMT_COMMA,       "COMMA",     2,  1,  40,  1,   40, 0001, FMT_COMMA, 3)
+DEFFMT (FMT_DOT,         "DOT",       2,  1,  40,  1,   40, 0001, FMT_DOT, 32)
+DEFFMT (FMT_DOLLAR,      "DOLLAR",    2,  1,  40,  2,   40, 0001, FMT_DOLLAR, 4)
+DEFFMT (FMT_PCT,         "PCT",       2,  1,  40,  2,   40, 0001, FMT_PCT, 31)
+DEFFMT (FMT_Z,           "Z",         2,  1,  40,  1,   40, 0011, FMT_F, 15)
+DEFFMT (FMT_A,           "A",         1,  1, 255,  1,  254, 0004, FMT_A, 1)
+DEFFMT (FMT_AHEX,        "AHEX",      1,  2, 254,  2,  510, 0006, FMT_A, 2)
+DEFFMT (FMT_IB,                  "IB",        2,  1,   8,  1,    8, 0010, FMT_F, 6)
+DEFFMT (FMT_P,           "P",         2,  1,  16,  1,   16, 0011, FMT_F, 8)
+DEFFMT (FMT_PIB,         "PIB",       2,  1,   8,  1,    8, 0010, FMT_F, 9)
+DEFFMT (FMT_PIBHEX,      "PIBHEX",    2,  2,  16,  2,   16, 0002, FMT_F, 7)
+DEFFMT (FMT_PK,                  "PK",        2,  1,  16,  1,   16, 0010, FMT_F, 10)
+DEFFMT (FMT_RB,                  "RB",        1,  2,   8,  2,    8, 0002, FMT_F, 11)
+DEFFMT (FMT_RBHEX,       "RBHEX",     1,  4,  16,  4,   16, 0002, FMT_F, 12)
+                                                                   
+/* Custom currency. */                                             
+DEFFMT (FMT_CCA,         "CCA",       2, -1,  -1,  1,   40, 0020, FMT_CCA, 33)
+DEFFMT (FMT_CCB,         "CCB",       2, -1,  -1,  1,   40, 0020, FMT_CCB, 34)
+DEFFMT (FMT_CCC,         "CCC",       2, -1,  -1,  1,   40, 0020, FMT_CCC, 35)
+DEFFMT (FMT_CCD,         "CCD",       2, -1,  -1,  1,   40, 0020, FMT_CCD, 36)
+DEFFMT (FMT_CCE,         "CCE",       2, -1,  -1,  1,   40, 0020, FMT_CCE, 37)
+                                                                    
+/* Date/time formats. */                                            
+DEFFMT (FMT_DATE,        "DATE",      1,  9,  40,  9,   40, 0001, FMT_DATE, 20)
+DEFFMT (FMT_EDATE,       "EDATE",     1,  8,  40,  8,   40, 0001, FMT_EDATE, 23)
+DEFFMT (FMT_SDATE,       "SDATE",     1,  8,  40,  8,   40, 0001, FMT_SDATE, 24)
+DEFFMT (FMT_ADATE,       "ADATE",     1,  8,  40,  8,   40, 0001, FMT_ADATE, 29)
+DEFFMT (FMT_JDATE,       "JDATE",     1,  5,  40,  5,   40, 0001, FMT_JDATE, 28)
+DEFFMT (FMT_QYR,         "QYR",       1,  4,  40,  6,   40, 0001, FMT_QYR, 30)
+DEFFMT (FMT_MOYR,        "MOYR",      1,  6,  40,  6,   40, 0001, FMT_MOYR, 22)
+DEFFMT (FMT_WKYR,        "WKYR",      1,  6,  40,  8,   40, 0001, FMT_WKYR, 21)
+DEFFMT (FMT_DATETIME,    "DATETIME",  2, 17,  40, 17,   40, 0001, FMT_DATETIME, 38)
+DEFFMT (FMT_TIME,        "TIME",      2,  5,  40,  5,   40, 0001, FMT_TIME, 39)
+DEFFMT (FMT_DTIME,       "DTIME",     2, 11,  40,  8,   40, 0001, FMT_DTIME, 25)
+DEFFMT (FMT_WKDAY,       "WKDAY",     1,  2,  40,  2,   40, 0001, FMT_WKDAY, 26)
+DEFFMT (FMT_MONTH,       "MONTH",     1,  3,  40,  3,   40, 0001, FMT_MONTH, 27)
+                                                                    
+/* These aren't real formats.  They're used by DATA LIST. */        
+DEFFMT (FMT_T,            "T",         1,  1,99999, 1,99999, 0000, FMT_T, -1)
+DEFFMT (FMT_X,           "X",         1,  1,99999, 1,99999, 0000, FMT_X, -1)
+DEFFMT (FMT_DESCEND,      "***",       1,  1,99999, 1,99999, 0000, -1, -1)
+DEFFMT (FMT_NEWREC,      "***",       1,  1,99999, 1,99999, 0000, -1, -1)
diff --git a/src/format.h b/src/format.h
new file mode 100644 (file)
index 0000000..ad6d2f7
--- /dev/null
@@ -0,0 +1,92 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !format_h
+#define format_h 1
+
+/* Display format types. */
+/* See the definitions of these functions and variables when modifying
+   this list:
+   misc.c:convert_fmt_ItoO()
+   sfm-read.c:parse_format_spec()
+   data-in.c:parse_string_as_format()
+   data-out.c:convert_format_to_string(). */
+#define DEFFMT(LABEL, NAME, N_ARGS, IMIN_W, IMAX_W, OMIN_W, OMAX_W,    \
+              CAT, OUTPUT, SPSS_FMT)                                   \
+       LABEL,
+enum
+  {
+#include "format.def"
+    FMT_NUMBER_OF_FORMATS
+  };
+#undef DEFFMT
+
+/* Describes one of the display formats above. */
+struct fmt_desc
+  {
+    char name[9];              /* `DATETIME' is the longest name. */
+    int n_args;                        /* 1=width; 2=width.decimals. */
+    int Imin_w, Imax_w;                /* Bounds on input width. */
+    int Omin_w, Omax_w;                /* Bounds on output width. */
+    int cat;                   /* Categories. */
+    int output;                        /* Output format. */
+    int spss;                  /* Equivalent SPSS output format. */
+  };
+
+/* Display format categories. */
+enum
+  {
+    FCAT_BLANKS_SYSMIS = 001,  /* 1=All-whitespace means SYSMIS. */
+    FCAT_EVEN_WIDTH = 002,     /* 1=Width must be even. */
+    FCAT_STRING = 004,         /* 1=String input/output format. */
+    FCAT_SHIFT_DECIMAL = 010,  /* 1=Automatically shift decimal point
+                                  on output--used for fixed-point
+                                  formats. */
+    FCAT_OUTPUT_ONLY = 020     /* 1=This is not an input format. */
+  };
+
+/* Display format. */
+struct fmt_spec
+  {
+    int type;                  /* One of the above constants. */
+    int w;                     /* Width. */
+    int d;                     /* Number of implied decimal places. */
+  };
+
+/* Descriptions of all the display formats above. */
+extern struct fmt_desc formats[];
+
+/* Translates SPSS formats to PSPP formats. */
+extern const int translate_fmt[40];
+
+union value;
+
+int parse_format_specifier (struct fmt_spec *input, int allow_xt);
+int parse_format_specifier_name (const char **cp, int allow_xt);
+int check_input_specifier (const struct fmt_spec *spec);
+int check_output_specifier (const struct fmt_spec *spec);
+int check_string_specifier (const struct fmt_spec *spec, int min_len);
+void convert_fmt_ItoO (const struct fmt_spec *input, struct fmt_spec *output);
+int parse_string_as_format (const char *s, int len, const struct fmt_spec *fp,
+                           int fc, union value *v);
+int data_out (char *s, const struct fmt_spec *fp, const union value *v);
+char *fmt_to_string (const struct fmt_spec *);
+void num_to_string (double v, char *s, int w, int d);
+
+#endif /* !format_h */
diff --git a/src/formats.c b/src/formats.c
new file mode 100644 (file)
index 0000000..8d2918a
--- /dev/null
@@ -0,0 +1,165 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+#if DEBUGGING
+static void debug_print (void);
+#endif
+
+enum
+  {
+    FORMATS_PRINT = 001,
+    FORMATS_WRITE = 002
+  };
+
+static int internal_cmd_formats (int);
+
+int
+cmd_print_formats (void)
+{
+  lex_match_id ("FORMATS");
+  return internal_cmd_formats (FORMATS_PRINT);
+}
+
+int
+cmd_write_formats (void)
+{
+  lex_match_id ("FORMATS");
+  return internal_cmd_formats (FORMATS_WRITE);
+}
+
+int
+cmd_formats (void)
+{
+  lex_match_id ("FORMATS");
+  return internal_cmd_formats (FORMATS_PRINT | FORMATS_WRITE);
+}
+
+int
+internal_cmd_formats (int which)
+{
+  /* Variables. */
+  struct variable **v;
+  int cv;
+
+  /* Format to set the variables to. */
+  struct fmt_spec f;
+
+  /* Numeric or string. */
+  int type;
+
+  /* Counter. */
+  int i;
+
+  for (;;)
+    {
+      if (token == '.')
+       break;
+
+      if (!parse_variables (NULL, &v, &cv, PV_SAME_TYPE))
+       return CMD_PART_SUCCESS_MAYBE;
+      type = v[0]->type;
+
+      if (!lex_match ('('))
+       {
+         msg (SE, _("`(' expected after variable list"));
+         goto fail;
+       }
+      if (!parse_format_specifier (&f, 0) || !check_output_specifier (&f))
+       goto fail;
+
+      /* Catch type mismatch errors. */
+      if ((type == ALPHA) ^ (0 != (formats[f.type].cat & FCAT_STRING)))
+       {
+         msg (SE, _("Format %s may not be assigned to a %s variable."),
+              fmt_to_string (&f), type == NUMERIC ? _("numeric") : _("string"));
+         goto fail;
+       }
+
+      /* This is an additional check for string variables.  We can't
+         let the user specify an A8 format for a string variable with
+         width 4. */
+      if (type == ALPHA)
+       {
+         /* Shortest string so far. */
+         int min_len = INT_MAX;
+
+         for (i = 0; i < cv; i++)
+           min_len = min (min_len, v[i]->width);
+         if (!check_string_specifier (&f, min_len))
+           goto fail;
+       }
+
+      if (!lex_match (')'))
+       {
+         msg (SE, _("`)' expected after output format."));
+         goto fail;
+       }
+
+      for (i = 0; i < cv; i++)
+       {
+         if (which & FORMATS_PRINT)
+           v[i]->print = f;
+         if (which & FORMATS_WRITE)
+           v[i]->write = f;
+       }
+      free (v);
+      v = NULL;
+    }
+#if DEBUGGING
+  debug_print ();
+#endif
+  return CMD_SUCCESS;
+
+fail:
+  free (v);
+  return CMD_PART_SUCCESS_MAYBE;
+}
+
+#if DEBUGGING
+static void
+debug_print (void)
+{
+  int i;
+
+  printf (_("Formats:\n"));
+  printf (_("  Name      Print         Write\n"));
+  printf ("  --------  ------------  ------------\n");
+  for (i = 0; i < default_dict.nvar; i++)
+    {
+      struct variable *v = default_dict.var[i];
+      printf ("  %-8s  %-12s", v->name, fmt_to_string (&v->print));
+      printf ("  %-12s\n", fmt_to_string (&v->write));
+    }
+}
+#endif /* DEBUGGING */
diff --git a/src/frequencies.g b/src/frequencies.g
new file mode 100644 (file)
index 0000000..eebb20c
--- /dev/null
@@ -0,0 +1,89 @@
+/* PSPP - computes sample statistics.                  -*- C -*-
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+   
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+   
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* Included by frequencies.q. */
+
+#if WEIGHTING
+  #define WEIGHT w
+  #define FUNCNAME calc_weighting
+#else /* !WEIGHTING */
+  #define WEIGHT 1.0
+  #define FUNCNAME calc_no_weight
+#endif /* !WEIGHTING */
+
+static int
+FUNCNAME (struct ccase *c)
+{
+  int i;
+#if WEIGHTING
+  double w;
+
+  w = c->data[default_dict.var[default_dict.weight_index]->fv].f;
+#endif
+
+  for (i = 0; i < n_variables; i++)
+    {
+      struct variable *v = v_variables[i];
+      union value *val = &c->data[v->fv];
+      struct freq_tab *ft = &v->p.frq.tab;
+
+      switch (v->p.frq.tab.mode)
+       {
+         case FRQM_GENERAL:
+           {
+             /* General mode.  This declaration and initialization are
+                strictly conforming: see C89 section 6.5.2.1. */
+             struct freq *fp = avl_find (ft->tree, (struct freq *) val);
+         
+             if (fp)
+               fp->c += WEIGHT;
+             else
+               {
+                 fp = pool_alloc (gen_pool, sizeof *fp);
+                 fp->v = *val;
+                 fp->c = WEIGHT;
+                 avl_insert (ft->tree, fp);
+                 if (is_missing (val, v))
+                   v->p.frq.tab.n_missing++;
+               }
+           }
+         break;
+       case FRQM_INTEGER:
+         /* Integer mode. */
+         if (val->f == SYSMIS)
+           v->p.frq.tab.sysmis += WEIGHT;
+         else if (val->f > INT_MIN+1 && val->f < INT_MAX-1)
+           {
+             int i = val->f;
+             if (i >= v->p.frq.tab.min && i <= v->p.frq.tab.max)
+               v->p.frq.tab.vector[i - v->p.frq.tab.min] += WEIGHT;
+           }
+         else
+           v->p.frq.tab.out_of_range += WEIGHT;
+         break;
+       default:
+         assert (0);
+       }
+    }
+  return 1;
+}
+
+#undef WEIGHT
+#undef WEIGHTING
+#undef FUNCNAME
diff --git a/src/frequencies.q b/src/frequencies.q
new file mode 100644 (file)
index 0000000..d40267e
--- /dev/null
@@ -0,0 +1,1818 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/*
+  TODO:
+
+  * Remember that histograms, bar charts need mean, stddev.
+*/
+
+#include <config.h>
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "avl.h"
+#include "bitvector.h"
+#include "hash.h"
+#include "pool.h"
+#include "command.h"
+#include "lexer.h"
+#include "error.h"
+#include "approx.h"
+#include "magic.h"
+#include "misc.h"
+#include "stats.h"
+#include "output.h"
+#include "som.h"
+#include "tab.h"
+#include "var.h"
+#include "vfm.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1 */
+#include "debug-print.h"
+
+/* (specification)
+   FREQUENCIES (frq_):
+     *variables=custom;
+     format=cond:condense/onepage(*n:onepage_limit,"%s>=0")/!standard,
+           table:limit(n:limit,"%s>0")/notable/!table, 
+           labels:!labels/nolabels,
+           sort:!avalue/dvalue/afreq/dfreq,
+           spaces:!single/double,
+           paging:newpage/!oldpage;
+     missing=miss:include/!exclude;
+     barchart(ba_)=:minimum(d:min),
+           :maximum(d:max),
+           scale:freq(*n:freq,"%s>0")/percent(*n:pcnt,"%s>0");
+     histogram(hi_)=:minimum(d:min),
+           :maximum(d:max),
+           scale:freq(*n:freq,"%s>0")/percent(*n:pcnt,"%s>0"),
+           norm:!nonormal/normal,
+           incr:increment(d:inc,"%s>0");
+     hbar(hb_)=:minimum(d:min),
+           :maximum(d:max),
+           scale:freq(*n:freq,"%s>0")/percent(*n:pcnt,"%s>0"),
+           norm:!nonormal/normal,
+           incr:increment(d:inc,"%s>0");
+     grouped=custom;
+     ntiles=custom;
+     percentiles=custom;
+     statistics[st_]=1|mean,2|semean,3|median,4|mode,5|stddev,6|variance,
+           7|kurtosis,8|skewness,9|range,10|minimum,11|maximum,12|sum,
+           13|default,14|seskewness,15|sekurtosis,all,none.
+*/
+/* (declarations) */
+/* (functions) */
+
+/* Description of a statistic. */
+struct frq_info
+  {
+    int st_indx;               /* Index into a_statistics[]. */
+    const char *s10;           /* Identifying string. */
+  };
+
+/* Table of statistics, indexed by dsc_*. */
+static struct frq_info st_name[frq_n_stats + 1] =
+{
+  {FRQ_ST_MEAN, N_("Mean")},
+  {FRQ_ST_SEMEAN, N_("S.E. Mean")},
+  {FRQ_ST_MEDIAN, N_("Median")},
+  {FRQ_ST_MODE, N_("Mode")},
+  {FRQ_ST_STDDEV, N_("Std Dev")},
+  {FRQ_ST_VARIANCE, N_("Variance")},
+  {FRQ_ST_KURTOSIS, N_("Kurtosis")},
+  {FRQ_ST_SEKURTOSIS, N_("S.E. Kurt")},
+  {FRQ_ST_SKEWNESS, N_("Skewness")},
+  {FRQ_ST_SESKEWNESS, N_("S.E. Skew")},
+  {FRQ_ST_RANGE, N_("Range")},
+  {FRQ_ST_MINIMUM, N_("Minimum")},
+  {FRQ_ST_MAXIMUM, N_("Maximum")},
+  {FRQ_ST_SUM, N_("Sum")},
+  {-1, 0},
+};
+
+/* Percentiles to calculate. */
+static double *percentiles;
+static int n_percentiles;
+
+/* Groups of statistics. */
+#define BI          BIT_INDEX
+#define frq_default                                                    \
+       (BI (frq_mean) | BI (frq_stddev) | BI (frq_min) | BI (frq_max))
+#define frq_all                                                        \
+       (BI (frq_sum) | BI(frq_min) | BI(frq_max)               \
+        | BI(frq_mean) | BI(frq_semean) | BI(frq_stddev)       \
+        | BI(frq_variance) | BI(frq_kurt) | BI(frq_sekurt)     \
+        | BI(frq_skew) | BI(frq_seskew) | BI(frq_range)        \
+        | BI(frq_range) | BI(frq_mode) | BI(frq_median))
+
+/* Statistics; number of statistics. */
+static unsigned long stats;
+static int n_stats;
+
+/* Types of graphs. */
+enum
+  {
+    GFT_NONE,                  /* Don't draw graphs. */
+    GFT_BAR,                   /* Draw bar charts. */
+    GFT_HIST,                  /* Draw histograms. */
+    GFT_HBAR                   /* Draw bar charts or histograms at our discretion. */
+  };
+
+/* Parsed command. */
+static struct cmd_frequencies cmd;
+
+/* Summary of the barchart, histogram, and hbar subcommands. */
+static int chart;              /* NONE/BAR/HIST/HBAR. */
+static double min, max;                /* Minimum, maximum on y axis. */
+static int format;             /* FREQ/PERCENT: Scaling of y axis. */
+static double scale, incr;     /* FIXME */
+static int normal;             /* FIXME */
+
+/* Variables for which to calculate statistics. */
+static int n_variables;
+static struct variable **v_variables;
+
+/* Arenas used to store semi-permanent storage. */
+static struct pool *int_pool;  /* Integer mode. */
+static struct pool *gen_pool;  /* General mode. */
+
+/* Easier access to a_statistics. */
+#define stat cmd.a_statistics
+
+static void determine_charts (void);
+
+static void precalc (void);
+static int calc_weighting (struct ccase *);
+static int calc_no_weight (struct ccase *);
+static void postcalc (void);
+
+static void postprocess_freq_tab (struct variable *);
+static void dump_full (struct variable *);
+static void dump_condensed (struct variable *);
+static void dump_statistics (struct variable *, int show_varname);
+static void cleanup_freq_tab (struct variable *);
+
+static int compare_value_numeric_a (const void *, const void *, void *);
+static int compare_value_alpha_a (const void *, const void *, void *);
+static int compare_value_numeric_d (const void *, const void *, void *);
+static int compare_value_alpha_d (const void *, const void *, void *);
+static int compare_freq_numeric_a (const void *, const void *, void *);
+static int compare_freq_alpha_a (const void *, const void *, void *);
+static int compare_freq_numeric_d (const void *, const void *, void *);
+static int compare_freq_alpha_d (const void *, const void *, void *);
+\f
+/* Parser and outline. */
+
+static int internal_cmd_frequencies (void);
+
+int
+cmd_frequencies (void)
+{
+  int result;
+
+  int_pool = pool_create ();
+  result = internal_cmd_frequencies ();
+  pool_destroy (int_pool);
+  pool_destroy (gen_pool);
+  free (v_variables);
+  return result;
+}
+
+static int
+internal_cmd_frequencies (void)
+{
+  int (*calc) (struct ccase *);
+  int i;
+
+  n_percentiles = 0;
+  percentiles = NULL;
+
+  n_variables = 0;
+  v_variables = NULL;
+
+  for (i = 0; i < default_dict.nvar; i++)
+    default_dict.var[i]->foo = 0;
+
+  lex_match_id ("FREQUENCIES");
+  if (!parse_frequencies (&cmd))
+    return CMD_FAILURE;
+
+  if (cmd.onepage_limit == NOT_LONG)
+    cmd.onepage_limit = 50;
+
+  /* Figure out statistics to calculate. */
+  stats = 0;
+  if (stat[FRQ_ST_DEFAULT] || !cmd.sbc_statistics)
+    stats |= frq_default;
+  if (stat[FRQ_ST_ALL])
+    stats |= frq_all;
+  if (cmd.sort != FRQ_AVALUE && cmd.sort != FRQ_DVALUE)
+    stats &= ~frq_median;
+  for (i = 0; i < frq_n_stats; i++)
+    if (stat[st_name[i].st_indx])
+      stats |= BIT_INDEX (i);
+  if (stats & frq_kurt)
+    stats |= frq_sekurt;
+  if (stats & frq_skew)
+    stats |= frq_seskew;
+
+  /* Calculate n_stats. */
+  n_stats = 0;
+  for (i = 0; i < frq_n_stats; i++)
+    if ((stats & BIT_INDEX (i)))
+      n_stats++;
+
+  /* Charting. */
+  determine_charts ();
+  if (chart != GFT_NONE || cmd.sbc_ntiles)
+    cmd.sort = FRQ_AVALUE;
+
+  /* Do it! */
+  update_weighting (&default_dict);
+  calc = default_dict.weight_index == -1 ? calc_no_weight : calc_weighting;
+  procedure (precalc, calc, postcalc);
+
+  return CMD_SUCCESS;
+}
+
+/* Figure out which charts the user requested.  */
+static void
+determine_charts (void)
+{
+  int count = (!!cmd.sbc_histogram) + (!!cmd.sbc_barchart) + (!!cmd.sbc_hbar);
+
+  if (!count)
+    {
+      chart = GFT_NONE;
+      return;
+    }
+  else if (count > 1)
+    {
+      chart = GFT_HBAR;
+      msg (SW, _("At most one of BARCHART, HISTOGRAM, or HBAR should be "
+          "given.  HBAR will be assumed.  Argument values will be "
+          "given precedence increasing along the order given."));
+    }
+  else if (cmd.sbc_histogram)
+    chart = GFT_HIST;
+  else if (cmd.sbc_barchart)
+    chart = GFT_BAR;
+  else
+    chart = GFT_HBAR;
+
+  min = max = SYSMIS;
+  format = FRQ_FREQ;
+  scale = SYSMIS;
+  incr = SYSMIS;
+  normal = 0;
+
+  if (cmd.sbc_barchart)
+    {
+      if (cmd.ba_min != SYSMIS)
+       min = cmd.ba_min;
+      if (cmd.ba_max != SYSMIS)
+       max = cmd.ba_max;
+      if (cmd.ba_scale == FRQ_FREQ)
+       {
+         format = FRQ_FREQ;
+         scale = cmd.ba_freq;
+       }
+      else if (cmd.ba_scale == FRQ_PERCENT)
+       {
+         format = FRQ_PERCENT;
+         scale = cmd.ba_pcnt;
+       }
+    }
+
+  if (cmd.sbc_histogram)
+    {
+      if (cmd.hi_min != SYSMIS)
+       min = cmd.hi_min;
+      if (cmd.hi_max != SYSMIS)
+       max = cmd.hi_max;
+      if (cmd.hi_scale == FRQ_FREQ)
+       {
+         format = FRQ_FREQ;
+         scale = cmd.hi_freq;
+       }
+      else if (cmd.hi_scale == FRQ_PERCENT)
+       {
+         format = FRQ_PERCENT;
+         scale = cmd.ba_pcnt;
+       }
+      if (cmd.hi_norm)
+       normal = 1;
+      if (cmd.hi_incr == FRQ_INCREMENT)
+       incr = cmd.hi_inc;
+    }
+
+  if (cmd.sbc_hbar)
+    {
+      if (cmd.hb_min != SYSMIS)
+       min = cmd.hb_min;
+      if (cmd.hb_max != SYSMIS)
+       max = cmd.hb_max;
+      if (cmd.hb_scale == FRQ_FREQ)
+       {
+         format = FRQ_FREQ;
+         scale = cmd.hb_freq;
+       }
+      else if (cmd.hb_scale == FRQ_PERCENT)
+       {
+         format = FRQ_PERCENT;
+         scale = cmd.ba_pcnt;
+       }
+      if (cmd.hb_norm)
+       normal = 1;
+      if (cmd.hb_incr == FRQ_INCREMENT)
+       incr = cmd.hb_inc;
+    }
+
+  if (min != SYSMIS && max != SYSMIS && min >= max)
+    {
+      msg (SE, _("MAX must be greater than or equal to MIN, if both are "
+          "specified.  However, MIN was specified as %g and MAX as %g.  "
+          "MIN and MAX will be ignored."), min, max);
+      min = max = SYSMIS;
+    }
+}
+
+/* Generate each calc_*(). */
+#define WEIGHTING 0
+#include "frequencies.g"
+
+#define WEIGHTING 1
+#include "frequencies.g"
+
+/* Prepares each variable that is the target of FREQUENCIES by setting
+   up its hash table. */
+static void
+precalc (void)
+{
+  int i;
+
+  pool_destroy (gen_pool);
+  gen_pool = pool_create ();
+  
+  for (i = 0; i < n_variables; i++)
+    {
+      struct variable *v = v_variables[i];
+
+      if (v->p.frq.tab.mode == FRQM_GENERAL)
+       {
+         avl_comparison_func compare;
+         if (v->type == NUMERIC)
+           compare = compare_value_numeric_a;
+         else
+           compare = compare_value_alpha_a;
+         v->p.frq.tab.tree = avl_create (gen_pool, compare,
+                                         (void *) v->width);
+         v->p.frq.tab.n_missing = 0;
+       }
+      else
+       {
+         int j;
+
+         for (j = (v->p.frq.tab.max - v->p.frq.tab.min); j >= 0; j--)
+           v->p.frq.tab.vector[j] = 0.0;
+         v->p.frq.tab.out_of_range = 0.0;
+         v->p.frq.tab.sysmis = 0.0;
+       }
+    }
+}
+
+/* Finishes up with the variables after frequencies have been
+   calculated.  Displays statistics, percentiles, ... */
+static void
+postcalc (void)
+{
+  int i;
+
+  for (i = 0; i < n_variables; i++)
+    {
+      struct variable *v = v_variables[i];
+      int n_categories;
+      int dumped_freq_tab = 1;
+
+      postprocess_freq_tab (v);
+
+      /* Frequencies tables. */
+      n_categories = v->p.frq.tab.n_valid + v->p.frq.tab.n_missing;
+      if (cmd.table == FRQ_TABLE
+         || (cmd.table == FRQ_LIMIT && n_categories <= cmd.limit))
+       switch (cmd.cond)
+         {
+         case FRQ_CONDENSE:
+           dump_condensed (v);
+           break;
+         case FRQ_STANDARD:
+           dump_full (v);
+           break;
+         case FRQ_ONEPAGE:
+           if (n_categories > cmd.onepage_limit)
+             dump_condensed (v);
+           else
+             dump_full (v);
+           break;
+         default:
+           assert (0);
+         }
+      else
+       dumped_freq_tab = 0;
+
+      /* Statistics. */
+      if (n_stats)
+       dump_statistics (v, !dumped_freq_tab);
+
+      cleanup_freq_tab (v);
+    }
+}
+
+/* Comparison function called by comparison_helper(). */
+static avl_comparison_func comparison_func;
+
+/* Passed to comparison function by comparison_helper(). */
+static void *comparison_param;
+
+/* Used by postprocess_freq_tab to re-sort frequency tables. */
+static int
+comparison_helper (const void *a, const void *b)
+{
+  return comparison_func (&((struct freq *) a)->v,
+                         &((struct freq *) b)->v, comparison_param);
+}
+
+/* Used by postprocess_freq_tab to construct the array members valid,
+   missing of freq_tab. */
+static void
+add_freq (void *data, void *param)
+{
+  struct freq *f = data;
+  struct variable *v = param;
+
+  v->p.frq.tab.total_cases += f->c;
+
+  if ((v->type == NUMERIC && f->v.f == SYSMIS)
+      || (cmd.miss == FRQ_EXCLUDE && is_user_missing (&f->v, v)))
+    {
+      *v->p.frq.tab.missing++ = *f;
+      v->p.frq.tab.valid_cases -= f->c;
+    }
+  else
+    *v->p.frq.tab.valid++ = *f;
+}
+
+static void
+postprocess_freq_tab (struct variable * v)
+{
+  avl_comparison_func compare;
+
+  switch (cmd.sort | (v->type << 16))
+    {
+      /* Note that q2c generates tags beginning with 1000. */
+    case FRQ_AVALUE | (NUMERIC << 16):
+      compare = NULL;
+      break;
+    case FRQ_AVALUE | (ALPHA << 16):
+      compare = NULL;
+      break;
+    case FRQ_DVALUE | (NUMERIC << 16):
+      comparison_func = compare_value_numeric_d;
+      break;
+    case FRQ_DVALUE | (ALPHA << 16):
+      compare = compare_value_alpha_d;
+      break;
+    case FRQ_AFREQ | (NUMERIC << 16):
+      compare = compare_freq_numeric_a;
+      break;
+    case FRQ_AFREQ | (ALPHA << 16):
+      compare = compare_freq_alpha_a;
+      break;
+    case FRQ_DFREQ | (NUMERIC << 16):
+      compare = compare_freq_numeric_d;
+      break;
+    case FRQ_DFREQ | (ALPHA << 16):
+      compare = compare_freq_alpha_d;
+      break;
+    default:
+      assert (0);
+    }
+  comparison_func = compare;
+
+  if (v->p.frq.tab.mode == FRQM_GENERAL)
+    {
+      int total;
+      struct freq_tab *ft = &v->p.frq.tab;
+
+      total = avl_count (ft->tree);
+      ft->n_valid = total - ft->n_missing;
+      ft->valid = xmalloc (sizeof (struct freq) * total);
+      ft->missing = &ft->valid[ft->n_valid];
+      ft->valid_cases = ft->total_cases = 0.0;
+
+      avl_walk (ft->tree, add_freq, (void *) v);
+
+      ft->valid -= ft->n_valid;
+      ft->missing -= ft->n_missing;
+      ft->valid_cases += ft->total_cases;
+
+      if (compare)
+       {
+         qsort (ft->valid, ft->n_valid, sizeof (struct freq), comparison_helper);
+         qsort (ft->missing, ft->n_missing, sizeof (struct freq), comparison_helper);
+       }
+    }
+  else
+    assert (0);
+}
+
+static void
+cleanup_freq_tab (struct variable * v)
+{
+  if (v->p.frq.tab.mode == FRQM_GENERAL)
+    {
+      struct freq_tab *ft = &v->p.frq.tab;
+
+      free (ft->valid);
+    }
+  else
+    assert (0);
+}
+
+/* Parses the VARIABLES subcommand, adding to
+   {n_variables,v_variables}. */
+static int
+frq_custom_variables (struct cmd_frequencies *cmd unused)
+{
+  int mode;
+  int min, max;
+
+  int old_n_variables = n_variables;
+  int i;
+
+  lex_match ('=');
+  if (token != T_ALL && (token != T_ID || !is_varname (tokid)))
+    return 2;
+
+  if (!parse_variables (NULL, &v_variables, &n_variables,
+                       PV_APPEND | PV_NO_SCRATCH))
+    return 0;
+
+  for (i = old_n_variables; i < n_variables; i++)
+    v_variables[i]->p.frq.tab.mode = FRQM_GENERAL;
+
+  if (!lex_match ('('))
+    mode = FRQM_GENERAL;
+  else
+    {
+      mode = FRQM_INTEGER;
+      if (!lex_force_int ())
+       return 0;
+      min = lex_integer ();
+      lex_get ();
+      if (!lex_force_match (','))
+       return 0;
+      if (!lex_force_int ())
+       return 0;
+      max = lex_integer ();
+      lex_get ();
+      if (!lex_force_match (')'))
+       return 0;
+      if (max < min)
+       {
+         msg (SE, _("Upper limit of integer mode value range must be "
+                    "greater than lower limit."));
+         return 0;
+       }
+    }
+
+  for (i = old_n_variables; i < n_variables; i++)
+    {
+      struct variable *v = v_variables[i];
+
+      if (v->foo != 0)
+       {
+         msg (SE, _("Variable %s specified multiple times on VARIABLES "
+                    "subcommand."), v->name);
+         return 0;
+       }
+      
+      v->foo = 1;              /* Used simply as a marker. */
+
+      v->p.frq.tab.valid = v->p.frq.tab.missing = NULL;
+
+      if (mode == FRQM_INTEGER)
+       {
+         if (v->type != NUMERIC)
+           {
+             msg (SE, _("Integer mode specified, but %s is not a numeric "
+                        "variable."), v->name);
+             return 0;
+           }
+         
+         v->p.frq.tab.min = min;
+         v->p.frq.tab.max = max;
+         v->p.frq.tab.vector = pool_alloc (int_pool,
+                                           sizeof (struct freq) * (max - min + 1));
+       }
+      else
+       v->p.frq.tab.vector = NULL;
+
+      v->p.frq.n_groups = 0;
+      v->p.frq.groups = NULL;
+    }
+  return 1;
+}
+
+/* Parses the GROUPED subcommand, setting the frq.{n_grouped,grouped}
+   fields of specified variables. */
+static int
+frq_custom_grouped (struct cmd_frequencies *cmd unused)
+{
+  lex_match ('=');
+  if ((token == T_ID && is_varname (tokid)) || token == T_ID)
+    for (;;)
+      {
+       int i;
+
+       /* Max, current size of list; list itself. */
+       int nl, ml;
+       double *dl;
+
+       /* Variable list. */
+       int n;
+       struct variable **v;
+
+       if (!parse_variables (NULL, &v, &n, PV_NO_DUPLICATE | PV_NUMERIC))
+         return 0;
+       if (lex_match ('('))
+         {
+           nl = ml = 0;
+           dl = NULL;
+           while (token == T_NUM)
+             {
+               if (nl >= ml)
+                 {
+                   ml += 16;
+                   dl = pool_realloc (int_pool, dl, ml * sizeof (double));
+                 }
+               dl[nl++] = tokval;
+               lex_get ();
+               lex_match (',');
+             }
+           /* Note that nl might still be 0 and dl might still be
+              NULL.  That's okay. */
+           if (!lex_match (')'))
+             {
+               free (v);
+               msg (SE, _("`)' expected after GROUPED interval list."));
+               return 0;
+             }
+         }
+       else
+         nl = 0;
+
+       for (i = 0; i < n; i++)
+         {
+           if (v[i]->foo == 0)
+             msg (SE, _("Variables %s specified on GROUPED but not on "
+                  "VARIABLES."), v[i]->name);
+           if (v[i]->p.frq.groups != NULL)
+             msg (SE, _("Variables %s specified multiple times on GROUPED "
+                  "subcommand."), v[i]->name);
+           else
+             {
+               v[i]->p.frq.n_groups = nl;
+               v[i]->p.frq.groups = dl;
+             }
+         }
+       free (v);
+       if (!lex_match ('/'))
+         break;
+       if ((token != T_ID || !is_varname (tokid)) && token != T_ALL)
+         {
+           lex_put_back ('/');
+           break;
+         }
+      }
+
+  return 1;
+}
+
+/* Adds X to the list of percentiles, keeping the list in proper
+   order. */
+static void
+add_percentile (double x)
+{
+  int i;
+
+  for (i = 0; i < n_percentiles; i++)
+    if (x <= percentiles[i])
+      break;
+  if (i >= n_percentiles || tokval != percentiles[i])
+    {
+      percentiles = pool_realloc (int_pool, percentiles,
+                                 (n_percentiles + 1) * sizeof (double));
+      if (i < n_percentiles)
+       memmove (&percentiles[i + 1], &percentiles[i],
+                (n_percentiles - i) * sizeof (double));
+      percentiles[i] = x;
+      n_percentiles++;
+    }
+}
+
+/* Parses the PERCENTILES subcommand, adding user-specified
+   percentiles to the list. */
+static int
+frq_custom_percentiles (struct cmd_frequencies *cmd unused)
+{
+  lex_match ('=');
+  if (token != T_NUM)
+    {
+      msg (SE, _("Percentile list expected after PERCENTILES."));
+      return 0;
+    }
+  
+  do
+    {
+      if (tokval <= 0 || tokval >= 100)
+       {
+         msg (SE, _("Percentiles must be greater than "
+                    "0 and less than 100."));
+         return 0;
+       }
+      
+      add_percentile (tokval / 100.0);
+      lex_get ();
+      lex_match (',');
+    }
+  while (token == T_NUM);
+  return 1;
+}
+
+/* Parses the NTILES subcommand, adding the percentiles that
+   correspond to the specified evenly-distributed ntiles. */
+static int
+frq_custom_ntiles (struct cmd_frequencies *cmd unused)
+{
+  int i;
+
+  lex_match ('=');
+  if (!lex_force_int ())
+    return 0;
+  for (i = 1; i < lex_integer (); i++)
+    add_percentile (1.0 / lex_integer () * i);
+  lex_get ();
+  return 1;
+}
+\f
+/* Comparison functions. */
+
+/* Ascending numeric compare of values. */
+static int
+compare_value_numeric_a (const void *a, const void *b, void *foo unused)
+{
+  return approx_compare (((struct freq *) a)->v.f, ((struct freq *) b)->v.f);
+}
+
+/* Ascending string compare of values. */
+static int
+compare_value_alpha_a (const void *a, const void *b, void *len)
+{
+  return memcmp (((struct freq *) a)->v.s, ((struct freq *) b)->v.s, (int) len);
+}
+
+/* Descending numeric compare of values. */
+static int
+compare_value_numeric_d (const void *a, const void *b, void *foo unused)
+{
+  return approx_compare (((struct freq *) b)->v.f, ((struct freq *) a)->v.f);
+}
+
+/* Descending string compare of values. */
+static int
+compare_value_alpha_d (const void *a, const void *b, void *len)
+{
+  return memcmp (((struct freq *) b)->v.s, ((struct freq *) a)->v.s, (int) len);
+}
+
+/* Ascending numeric compare of frequency;
+   secondary key on ascending numeric value. */
+static int
+compare_freq_numeric_a (const void *a, const void *b, void *foo unused)
+{
+  int x = approx_compare (((struct freq *) a)->c, ((struct freq *) b)->c);
+  return x ? x : approx_compare (((struct freq *) a)->v.f, ((struct freq *) b)->v.f);
+}
+
+/* Ascending numeric compare of frequency;
+   secondary key on ascending string value. */
+static int
+compare_freq_alpha_a (const void *a, const void *b, void *len)
+{
+  int x = approx_compare (((struct freq *) a)->c, ((struct freq *) b)->c);
+  return x ? x : memcmp (((struct freq *) a)->v.s, ((struct freq *) b)->v.s, (int) len);
+}
+
+/* Descending numeric compare of frequency;
+   secondary key on ascending numeric value. */
+static int
+compare_freq_numeric_d (const void *a, const void *b, void *foo unused)
+{
+  int x = approx_compare (((struct freq *) b)->c, ((struct freq *) a)->c);
+  return x ? x : approx_compare (((struct freq *) a)->v.f, ((struct freq *) b)->v.f);
+}
+
+/* Descending numeric compare of frequency;
+   secondary key on ascending string value. */
+static int
+compare_freq_alpha_d (const void *a, const void *b, void *len)
+{
+  int x = approx_compare (((struct freq *) b)->c, ((struct freq *) a)->c);
+  return x ? x : memcmp (((struct freq *) a)->v.s, ((struct freq *) b)->v.s, (int) len);
+}
+\f
+/* Frequency table display. */
+
+/* Sets the widths of all the columns and heights of all the rows in
+   table T for driver D. */
+static void
+full_dim (struct tab_table *t, struct outp_driver *d)
+{
+  int lab = cmd.labels == FRQ_LABELS;
+  int i;
+
+  if (lab)
+    t->w[0] = min (tab_natural_width (t, d, 0), d->prop_em_width * 15);
+  for (i = lab; i < lab + 5; i++)
+    t->w[i] = max (tab_natural_width (t, d, i), d->prop_em_width * 8);
+  for (i = 0; i < t->nr; i++)
+    t->h[i] = d->font_height;
+}
+
+/* Displays a full frequency table for variable V. */
+static void
+dump_full (struct variable * v)
+{
+  int n_categories;
+  struct freq *f;
+  struct tab_table *t;
+  int r;
+  double cum_percent = 0.0;
+  double cum_freq = 0.0;
+
+  struct init
+    {
+      int c, r;
+      const char *s;
+    };
+
+  struct init *p;
+
+  static struct init vec[] =
+  {
+    {4, 0, N_("Valid")},
+    {5, 0, N_("Cum")},
+    {1, 1, N_("Value")},
+    {2, 1, N_("Frequency")},
+    {3, 1, N_("Percent")},
+    {4, 1, N_("Percent")},
+    {5, 1, N_("Percent")},
+    {0, 0, NULL},
+    {1, 0, NULL},
+    {2, 0, NULL},
+    {3, 0, NULL},
+    {-1, -1, NULL},
+  };
+
+  int lab = cmd.labels == FRQ_LABELS;
+
+  n_categories = v->p.frq.tab.n_valid + v->p.frq.tab.n_missing;
+  t = tab_create (5 + lab, n_categories + 3, 0);
+  tab_headers (t, 0, 0, 2, 0);
+  tab_dim (t, full_dim);
+
+  if (lab)
+    tab_text (t, 0, 1, TAB_CENTER | TAT_TITLE, _("Value Label"));
+  for (p = vec; p->s; p++)
+    tab_text (t, p->c - (p->r ? !lab : 0), p->r,
+                 TAB_CENTER | TAT_TITLE, gettext (p->s));
+
+  r = 2;
+  for (f = v->p.frq.tab.valid; f < v->p.frq.tab.missing; f++)
+    {
+      double percent, valid_percent;
+
+      cum_freq += f->c;
+
+      percent = f->c / v->p.frq.tab.total_cases * 100.0;
+      valid_percent = f->c / v->p.frq.tab.valid_cases * 100.0;
+      cum_percent += valid_percent;
+
+      if (lab)
+       {
+         char *label = get_val_lab (v, f->v, 0);
+         if (label != NULL)
+           tab_text (t, 0, r, TAB_LEFT, label);
+       }
+
+      tab_value (t, 0 + lab, r, TAB_NONE, &f->v, &v->print);
+      tab_float (t, 1 + lab, r, TAB_NONE, f->c, 8, 0);
+      tab_float (t, 2 + lab, r, TAB_NONE, percent, 5, 1);
+      tab_float (t, 3 + lab, r, TAB_NONE, valid_percent, 5, 1);
+      tab_float (t, 4 + lab, r, TAB_NONE, cum_percent, 5, 1);
+      r++;
+    }
+  for (; f < &v->p.frq.tab.valid[n_categories]; f++)
+    {
+      cum_freq += f->c;
+
+      if (lab)
+       {
+         char *label = get_val_lab (v, f->v, 0);
+         if (label != NULL)
+           tab_text (t, 0, r, TAB_LEFT, label);
+       }
+
+      tab_value (t, 0 + lab, r, TAB_NONE, &f->v, &v->print);
+      tab_float (t, 1 + lab, r, TAB_NONE, f->c, 8, 0);
+      tab_float (t, 2 + lab, r, TAB_NONE,
+                    f->c / v->p.frq.tab.total_cases * 100.0, 5, 1);
+      tab_text (t, 3 + lab, r, TAB_NONE, _("Missing"));
+      r++;
+    }
+
+  tab_box (t, TAL_1, TAL_1,
+          cmd.spaces == FRQ_SINGLE ? -1 : (TAL_1 | TAL_SPACING), TAL_1,
+          0, 0, 4 + lab, r);
+  tab_hline (t, TAL_2, 0, 4 + lab, 2);
+  tab_hline (t, TAL_2, 0, 4 + lab, r);
+  tab_joint_text (t, 0, r, 0 + lab, r, TAB_RIGHT | TAT_TITLE, _("Total"));
+  tab_vline (t, TAL_0, 1, r, r);
+  tab_float (t, 1 + lab, r, TAB_NONE, cum_freq, 8, 0);
+  tab_float (t, 2 + lab, r, TAB_NONE, 100.0, 5, 1);
+  tab_float (t, 3 + lab, r, TAB_NONE, 100.0, 5, 1);
+
+  tab_title (t, 1, "%s: %s", v->name, v->label ? v->label : "");
+  tab_submit (t);
+}
+
+/* Sets the widths of all the columns and heights of all the rows in
+   table T for driver D. */
+static void
+condensed_dim (struct tab_table *t, struct outp_driver *d)
+{
+  int cum_w = max (outp_string_width (d, _("Cum")),
+                  max (outp_string_width (d, _("Cum")),
+                       outp_string_width (d, "000")));
+
+  int i;
+
+  for (i = 0; i < 2; i++)
+    t->w[i] = max (tab_natural_width (t, d, i), d->prop_em_width * 8);
+  for (i = 2; i < 4; i++)
+    t->w[i] = cum_w;
+  for (i = 0; i < t->nr; i++)
+    t->h[i] = d->font_height;
+}
+
+/* Display condensed frequency table for variable V. */
+static void
+dump_condensed (struct variable * v)
+{
+  int n_categories;
+  struct freq *f;
+  struct tab_table *t;
+  int r;
+  double cum_percent = 0.0;
+
+  n_categories = v->p.frq.tab.n_valid + v->p.frq.tab.n_missing;
+  t = tab_create (4, n_categories + 2, 0);
+
+  tab_headers (t, 0, 0, 2, 0);
+  tab_text (t, 0, 1, TAB_CENTER | TAT_TITLE, _("Value"));
+  tab_text (t, 1, 1, TAB_CENTER | TAT_TITLE, _("Freq"));
+  tab_text (t, 2, 1, TAB_CENTER | TAT_TITLE, _("Pct"));
+  tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("Cum"));
+  tab_text (t, 3, 1, TAB_CENTER | TAT_TITLE, _("Pct"));
+  tab_dim (t, condensed_dim);
+
+  r = 2;
+  for (f = v->p.frq.tab.valid; f < v->p.frq.tab.missing; f++)
+    {
+      double percent;
+
+      percent = f->c / v->p.frq.tab.total_cases * 100.0;
+      cum_percent += f->c / v->p.frq.tab.valid_cases * 100.0;
+
+      tab_value (t, 0, r, TAB_NONE, &f->v, &v->print);
+      tab_float (t, 1, r, TAB_NONE, f->c, 8, 0);
+      tab_float (t, 2, r, TAB_NONE, percent, 3, 0);
+      tab_float (t, 3, r, TAB_NONE, cum_percent, 3, 0);
+      r++;
+    }
+  for (; f < &v->p.frq.tab.valid[n_categories]; f++)
+    {
+      tab_value (t, 0, r, TAB_NONE, &f->v, &v->print);
+      tab_float (t, 1, r, TAB_NONE, f->c, 8, 0);
+      tab_float (t, 2, r, TAB_NONE,
+                f->c / v->p.frq.tab.total_cases * 100.0, 3, 0);
+      r++;
+    }
+
+  tab_box (t, TAL_1, TAL_1,
+          cmd.spaces == FRQ_SINGLE ? -1 : (TAL_1 | TAL_SPACING), TAL_1,
+          0, 0, 3, r - 1);
+  tab_hline (t, TAL_2, 0, 3, 2);
+  tab_title (t, 1, "%s: %s", v->name, v->label ? v->label : "");
+  tab_columns (t, SOM_COL_DOWN, 1);
+  tab_submit (t);
+}
+\f
+/* Statistical display. */
+
+/* Calculates all the pertinent statistics for variable V, putting
+   them in array D[].  FIXME: This could be made much more optimal. */
+static void
+calc_stats (struct variable * v, double d[frq_n_stats])
+{
+  double W = v->p.frq.tab.valid_cases;
+  double X_bar, M2, M3, M4;
+  struct freq *f;
+
+  /* Calculate the mean. */
+  X_bar = 0.0;
+  for (f = v->p.frq.tab.valid; f < v->p.frq.tab.missing; f++)
+    X_bar += f->v.f * f->c;
+  X_bar /= W;
+
+  /* Calculate moments about the mean. */
+  M2 = M3 = M4 = 0.0;
+  for (f = v->p.frq.tab.valid; f < v->p.frq.tab.missing; f++)
+    {
+      double dev = f->v.f - X_bar;
+      double tmp;
+      tmp = dev * dev;
+      M2 += f->c * tmp;
+      tmp *= dev;
+      M3 += f->c * tmp;
+      tmp *= dev;
+      M4 += f->c * tmp;
+    }
+
+  /* Formulas below are taken from _SPSS Statistical Algorithms_. */
+  d[frq_min] = v->p.frq.tab.valid[0].v.f;
+  d[frq_max] = v->p.frq.tab.missing[-1].v.f;
+  d[frq_mode] = 0.0;
+  d[frq_range] = d[frq_max] - d[frq_min];
+  d[frq_median] = 0.0;
+  d[frq_mean] = X_bar;
+  d[frq_sum] = X_bar * W;
+  d[frq_variance] = M2 / (W - 1);
+  d[frq_stddev] = sqrt (d[frq_variance]);
+  d[frq_semean] = d[frq_stddev] / sqrt (W);
+  if (W >= 3.0 && d[frq_variance] > 0)
+    {
+      double S = d[frq_stddev];
+      d[frq_skew] = (W * M3 / ((W - 1.0) * (W - 2.0) * S * S * S));
+      d[frq_seskew] = sqrt (6.0 * W * (W - 1.0)
+                           / ((W - 2.0) * (W + 1.0) * (W + 3.0)));
+    }
+  else
+    {
+      d[frq_skew] = d[frq_seskew] = SYSMIS;
+    }
+  if (W >= 4.0 && d[frq_variance] > 0)
+    {
+      double S2 = d[frq_variance];
+      double SE_g1 = d[frq_seskew];
+
+      d[frq_kurt] = ((W * (W + 1.0) * M4 - 3.0 * M2 * M2 * (W - 1.0))
+                    / ((W - 1.0) * (W - 2.0) * (W - 3.0) * S2 * S2));
+      d[frq_sekurt] = sqrt ((4.0 * (W * W - 1.0) * SE_g1 * SE_g1)
+                           / ((W - 3.0) * (W + 5.0)));
+    }
+  else
+    {
+      d[frq_kurt] = d[frq_sekurt] = SYSMIS;
+    }
+}
+
+/* Displays a table of all the statistics requested for variable V. */
+static void
+dump_statistics (struct variable * v, int show_varname)
+{
+  double stat_value[frq_n_stats];
+  struct tab_table *t;
+  int i, r;
+
+  if (v->type == ALPHA)
+    return;
+  if (v->p.frq.tab.n_valid == 0)
+    {
+      msg (SW, _("No valid data for variable %s; statistics not displayed."),
+          v->name);
+      return;
+    }
+  calc_stats (v, stat_value);
+
+  t = tab_create (2, n_stats, 0);
+  tab_dim (t, tab_natural_dimensions);
+  tab_vline (t, TAL_1 | TAL_SPACING, 1, 0, n_stats - 1);
+  for (i = r = 0; i < frq_n_stats; i++)
+    if (stats & BIT_INDEX (i))
+      {
+       tab_text (t, 0, r, TAB_LEFT | TAT_TITLE,
+                     gettext (st_name[i].s10));
+       tab_float (t, 1, r, TAB_NONE, stat_value[i], 11, 3);
+       r++;
+      }
+
+  tab_columns (t, SOM_COL_DOWN, 1);
+  if (show_varname)
+    {
+      if (v->label)
+       tab_title (t, 1, "%s: %s", v->name, v->label);
+      else
+       tab_title (t, 0, v->name);
+    }
+  else
+    tab_flags (t, SOMF_NO_TITLE);
+  
+  tab_submit (t);
+}
+\f
+#if 0
+/* Statistical calculation. */
+
+static int degree[6];
+static int maxdegree, minmax;
+
+static void stat_func (struct freq *, VISIT, int);
+static void calc_stats (int);
+static void display_stats (int);
+
+/* mapping of data[]:
+ * 0=>8
+ * 1=>9
+ * 2=>10
+ * index 3: number of modes found (detects multiple modes)
+ * index 4: number of nodes processed, for calculation of median
+ * 5=>11
+ * 
+ * mapping of dbl[]:
+ * index 0-3: sum of X**i
+ * index 4: minimum
+ * index 5: maximum
+ * index 6: mode
+ * index 7: median
+ * index 8: number of cases, valid and missing
+ * index 9: number of valid cases
+ * index 10: maximum frequency found, for calculation of mode
+ * index 11: maximum frequency
+ */
+static void
+out_stats (int i)
+{
+  int j;
+
+  if (cur_var->type == ALPHA)
+    return;
+  for (j = 0; j < 8; j++)
+    cur_var->dbl[j] = 0.;
+  cur_var->dbl[10] = 0;
+  cur_var->dbl[4] = DBL_MAX;
+  cur_var->dbl[5] = -DBL_MAX;
+  for (j = 2; j < 5; j++)
+    cur_var->data[j] = 0;
+  cur_var->p.frq.median_ncases = cur_var->p.frq.t.valid_cases / 2;
+  avlwalk (cur_var->p.frq.t.f, stat_func, LEFT_TO_RIGHT);
+  calc_stats (i);
+  display_stats (i);
+}
+
+static void
+calc_stats (int i)
+{
+  struct variable *v;
+  double n;
+  double *d;
+
+  v = v_variables[i];
+  n = v->p.frq.t.valid_cases;
+  d = v->dbl;
+
+  if (n < 2 || (n < 3 && stat[FRQ_ST_7]))
+    {
+      warn (_("only %g case%s for variable %s, statistics not "
+           "computed"), n, n == 1 ? "" : "s", v->name);
+      return;
+    }
+  if (stat[FRQ_ST_9])
+    v->res[FRQ_ST_9] = d[5] - d[4];
+  if (stat[FRQ_ST_10])
+    v->res[FRQ_ST_10] = d[4];
+  if (stat[FRQ_ST_11])
+    v->res[FRQ_ST_11] = d[5];
+  if (stat[FRQ_ST_12])
+    v->res[FRQ_ST_12] = d[0];
+  if (stat[FRQ_ST_1] || stat[FRQ_ST_2] || stat[FRQ_ST_5] || stat[FRQ_ST_6] || stat[FRQ_ST_7])
+    {
+      v->res[FRQ_ST_1] = calc_mean (d, n);
+      v->res[FRQ_ST_6] = calc_variance (d, n);
+    }
+  if (stat[FRQ_ST_2] || stat[FRQ_ST_5] || stat[FRQ_ST_7])
+    v->res[FRQ_ST_5] = calc_stddev (v->res[FRQ_ST_6]);
+  if (stat[FRQ_ST_2])
+    v->res[FRQ_ST_2] = calc_semean (v->res[FRQ_ST_5], n);
+  if (stat[FRQ_ST_7])
+    {
+      v->res[FRQ_ST_7] = calc_kurt (d, n, v->res[FRQ_ST_6]);
+      v->res[FRQ_ST_14] = calc_sekurt (n);
+    }
+  if (stat[FRQ_ST_8])
+    {
+      v->res[FRQ_ST_8] = calc_skew (d, n, v->res[FRQ_ST_5]);
+      v->res[FRQ_ST_15] = calc_seskew (n);
+    }
+  if (stat[FRQ_ST_MODE])
+    {
+      v->res[FRQ_ST_MODE] = v->dbl[6];
+      if (v->data[3] > 1)
+       warn (_("The variable %s has %d modes.  The lowest of these "
+             "is the one given in the table."), v->name, v->data[3]);
+    }
+  if (stat[FRQ_ST_MEDIAN])
+    v->res[FRQ_ST_MEDIAN] = v->dbl[7];
+}
+
+static void
+stat_func (struct freq * x, VISIT order, int param)
+{
+  double d, f;
+
+  if (order != INORDER)
+    return;
+  f = d = x->v.f;
+  cur_var->dbl[0] += (d * x->c);
+  switch (maxdegree)
+    {
+    case 1:
+      f *= d;
+      cur_var->dbl[1] += (f * x->c);
+      break;
+    case 2:
+      f *= d;
+      cur_var->dbl[1] += (f * x->c);
+      f *= d;
+      cur_var->dbl[2] += (f * x->c);
+      break;
+    case 3:
+      f *= d;
+      cur_var->dbl[1] += (f * x->c);
+      f *= d;
+      cur_var->dbl[2] += (f * x->c);
+      f *= d;
+      cur_var->dbl[3] += (f * x->c);
+      break;
+    }
+  if (minmax)
+    {
+      if (d < cur_var->dbl[4])
+       cur_var->dbl[4] = d;
+      if (d > cur_var->dbl[5])
+       cur_var->dbl[5] = d;
+    }
+  if (x->c > cur_var->dbl[10])
+    {
+      cur_var->data[3] = 1;
+      cur_var->dbl[10] = x->c;
+      cur_var->dbl[6] = x->v.f;
+    }
+  else if (x->c == cur_var->dbl[10])
+    cur_var->data[3]++;
+  if (cur_var->data[4] < cur_var->p.frq.median_ncases
+      && cur_var->data[4] + x->c >= cur_var->p.frq.median_ncases)
+    cur_var->dbl[7] = x->v.f;
+  cur_var->data[4] += x->c;
+}
+\f
+/* Statistical display. */
+static int column, ncolumns;
+
+static void outstat (char *, double);
+
+static void
+display_stats (int i)
+{
+  statname *sp;
+  struct variable *v;
+  int nlines;
+
+  v = v_variables[i];
+  ncolumns = (margin_width + 3) / 26;
+  if (ncolumns < 1)
+    ncolumns = 1;
+  nlines = sc / ncolumns + (sc % ncolumns > 0);
+  if (nlines == 2 && sc == 4)
+    ncolumns = 2;
+  if (nlines == 3 && sc == 9)
+    ncolumns = 3;
+  if (nlines == 4 && sc == 12)
+    ncolumns = 3;
+  column = 0;
+  for (sp = st_name; sp->s != -1; sp++)
+    if (stat[sp->s] == 1)
+      outstat (gettext (sp->s10), v->res[sp->s]);
+  if (column)
+    out_eol ();
+  blank_line ();
+}
+
+static void
+outstat (char *label, double value)
+{
+  char buf[128], *cp;
+  int dw, n;
+
+  cp = &buf[0];
+  if (!column)
+    out_header ();
+  else
+    {
+      memset (buf, ' ', 3);
+      cp = &buf[3];
+    }
+  dw = 4;
+  n = nsprintf (cp, "%-10s %12.4f", label, value);
+  while (n > 23 && dw > 0)
+    n = nsprintf (cp, "%-10s %12.*f", label, --dw, value);
+  outs (buf);
+  column++;
+  if (column == ncolumns)
+    {
+      column = 0;
+      out_eol ();
+    }
+}
+\f
+/* Graphs. */
+
+static rect pb, gb;            /* Page border, graph border. */
+static int px, py;             /* Page width, height. */
+static int ix, iy;             /* Inch width, height. */
+
+static void draw_bar_chart (int);
+static void draw_histogram (int);
+static int scale_dep_axis (int);
+
+static void
+out_graphs (int i)
+{
+  struct variable *v;
+
+  v = v_variables[i];
+  if (avlcount (cur_var->p.frq.t.f) < 2
+      || (chart == HIST && v_variables[i]->type == ALPHA))
+    return;
+  if (driver_id && set_highres == 1)
+    {
+      char *text;
+
+      graf_page_size (&px, &py, &ix, &iy);
+      graf_feed_page ();
+
+      /* Calculate borders. */
+      pb.x1 = ix;
+      pb.y1 = iy;
+      pb.x2 = px - ix;
+      pb.y2 = py - iy;
+      gb.x1 = pb.x1 + ix;
+      gb.y1 = pb.y1 + iy;
+      gb.x2 = pb.x2 - ix / 2;
+      gb.y2 = pb.y2 - iy;
+
+      /* Draw borders. */
+      graf_frame_rect (COMPONENTS (pb));
+      graf_frame_rect (COMPONENTS (gb));
+
+      /* Draw axis labels. */
+      graf_font_size (iy / 4); /* 18-point text */
+      text = format == PERCENT ? _("Percentage") : _("Frequency");
+      graf_text (pb.x1 + max (ix, iy) / 4 + max (ix, iy) / 16, gb.y2, text,
+                SIDEWAYS);
+      text = v->label ? v->label : v->name;
+      graf_text (gb.x1, pb.y2 - iy / 4, text, UPRIGHT);
+
+      /* Draw axes, chart proper. */
+      if (chart == BAR ||
+         (chart == HBAR
+       && (avlcount (cur_var->p.frq.t.f) || v_variables[i]->type == ALPHA)))
+       draw_bar_chart (i);
+      else
+       draw_histogram (i);
+
+      graf_eject_page ();
+    }
+  if (set_lowres == 1 || (set_lowres == 2 && (!driver_id || !set_highres)))
+    {
+      static warned;
+
+      /* Do character-based graphs. */
+      if (!warned)
+       {
+         warn (_("low-res graphs not implemented"));
+         warned = 1;
+       }
+    }
+}
+
+#if __GNUC__ && !__CHECKER__
+#define BIG_TYPE long long
+#else /* !__GNUC__ */
+#define BIG_TYPE double
+#endif /* !__GNUC__ */
+
+static void
+draw_bar_chart (int i)
+{
+  int bar_width, bar_spacing;
+  int w, max, row;
+  double val;
+  struct freq *f;
+  rect r;
+  AVLtraverser *t = NULL;
+
+  w = (px - ix * 7 / 2) / avlcount (cur_var->p.frq.t.f);
+  bar_width = w * 2 / 3;
+  bar_spacing = w - bar_width;
+
+#if !ALLOW_HUGE_BARS
+  if (bar_width > ix / 2)
+    bar_width = ix / 2;
+#endif /* !ALLOW_HUGE_BARS */
+
+  max = scale_dep_axis (cur_var->p.frq.t.max_freq);
+
+  row = 0;
+  r.x1 = gb.x1 + bar_spacing / 2;
+  r.x2 = r.x1 + bar_width;
+  r.y2 = gb.y2;
+  graf_fill_color (255, 0, 0);
+  for (f = avltrav (cur_var->p.frq.t.f, &t); f;
+       f = avltrav (cur_var->p.frq.t.f, &t))
+    {
+      char buf2[64];
+      char *buf;
+
+      val = f->c;
+      if (format == PERCENT)
+       val = val * 100 / cur_var->p.frq.t.valid_cases;
+      r.y1 = r.y2 - val * (height (gb) - 1) / max;
+      graf_fill_rect (COMPONENTS (r));
+      graf_frame_rect (COMPONENTS (r));
+      buf = get_val_lab (cur_var, f->v, 0);
+      if (!buf)
+       if (cur_var->type == ALPHA)
+         buf = f->v.s;
+       else
+         {
+           sprintf (buf2, "%g", f->v.f);
+           buf = buf2;
+         }
+      graf_text (r.x1 + bar_width / 2,
+                gb.y2 + iy / 32 + row * iy / 9, buf, TCJUST);
+      row ^= 1;
+      r.x1 += bar_width + bar_spacing;
+      r.x2 += bar_width + bar_spacing;
+    }
+  graf_fill_color (0, 0, 0);
+}
+
+#define round_down(X, V)                       \
+       (floor ((X) / (V)) * (V))
+#define round_up(X, V)                                 \
+       (ceil ((X) / (V)) * (V))
+
+static void
+draw_histogram (int i)
+{
+  double lower, upper, interval;
+  int bars[MAX_HIST_BARS + 1], top, j;
+  int err, addend, rem, nbars, row, max_freq;
+  char buf[25];
+  rect r;
+  struct freq *f;
+  AVLtraverser *t = NULL;
+
+  lower = min == SYSMIS ? cur_var->dbl[4] : min;
+  upper = max == SYSMIS ? cur_var->dbl[5] : max;
+  if (upper - lower >= 10)
+    {
+      double l, u;
+
+      u = round_up (upper, 5);
+      l = round_down (lower, 5);
+      nbars = (u - l) / 5;
+      if (nbars * 2 + 1 <= MAX_HIST_BARS)
+       {
+         nbars *= 2;
+         u = round_up (upper, 2.5);
+         l = round_down (lower, 2.5);
+         if (l + 1.25 <= lower && u - 1.25 >= upper)
+           nbars--, lower = l + 1.25, upper = u - 1.25;
+         else if (l + 1.25 <= lower)
+           lower = l + 1.25, upper = u + 1.25;
+         else if (u - 1.25 >= upper)
+           lower = l - 1.25, upper = u - 1.25;
+         else
+           nbars++, lower = l - 1.25, upper = u + 1.25;
+       }
+      else if (nbars < MAX_HIST_BARS)
+       {
+         if (l + 2.5 <= lower && u - 2.5 >= upper)
+           nbars--, lower = l + 2.5, upper = u - 2.5;
+         else if (l + 2.5 <= lower)
+           lower = l + 2.5, upper = u + 2.5;
+         else if (u - 2.5 >= upper)
+           lower = l - 2.5, upper = u - 2.5;
+         else
+           nbars++, lower = l - 2.5, upper = u + 2.5;
+       }
+      else
+       nbars = MAX_HIST_BARS;
+    }
+  else
+    {
+      nbars = avlcount (cur_var->p.frq.t.f);
+      if (nbars > MAX_HIST_BARS)
+       nbars = MAX_HIST_BARS;
+    }
+  if (nbars < MIN_HIST_BARS)
+    nbars = MIN_HIST_BARS;
+  interval = (upper - lower) / nbars;
+
+  memset (bars, 0, sizeof (int[nbars + 1]));
+  if (lower >= upper)
+    {
+      msg (SE, _("Could not make histogram for %s for specified "
+          "minimum %g and maximum %g; please discard graph."), cur_var->name,
+          lower, upper);
+      return;
+    }
+  for (f = avltrav (cur_var->p.frq.t.f, &t); f;
+       f = avltrav (cur_var->p.frq.t.f, &t))
+    if (f->v.f == upper)
+      bars[nbars - 1] += f->c;
+    else if (f->v.f >= lower && f->v.f < upper)
+      bars[(int) ((f->v.f - lower) / interval)] += f->c;
+  bars[nbars - 1] += bars[nbars];
+  for (j = top = 0; j < nbars; j++)
+    if (bars[j] > top)
+      top = bars[j];
+  max_freq = top;
+  top = scale_dep_axis (top);
+
+  err = row = 0;
+  addend = width (gb) / nbars;
+  rem = width (gb) % nbars;
+  r.x1 = gb.x1;
+  r.x2 = r.x1 + addend;
+  r.y2 = gb.y2;
+  err += rem;
+  graf_fill_color (255, 0, 0);
+  for (j = 0; j < nbars; j++)
+    {
+      int w;
+
+      r.y1 = r.y2 - (BIG_TYPE) bars[j] * (height (gb) - 1) / top;
+      graf_fill_rect (COMPONENTS (r));
+      graf_frame_rect (COMPONENTS (r));
+      sprintf (buf, "%g", lower + interval / 2 + interval * j);
+      graf_text (r.x1 + addend / 2,
+                gb.y2 + iy / 32 + row * iy / 9, buf, TCJUST);
+      row ^= 1;
+      w = addend;
+      err += rem;
+      while (err >= addend)
+       {
+         w++;
+         err -= addend;
+       }
+      r.x1 = r.x2;
+      r.x2 = r.x1 + w;
+    }
+  if (normal)
+    {
+      double x, y, variance, mean, step, factor;
+
+      variance = cur_var->res[FRQ_ST_VARIANCE];
+      mean = cur_var->res[FRQ_ST_MEAN];
+      factor = (1. / (sqrt (2. * PI * variance))
+               * cur_var->p.frq.t.valid_cases * interval);
+      graf_polyline_begin ();
+      for (x = lower, step = (upper - lower) / (POLYLINE_DENSITY);
+          x <= upper; x += step)
+       {
+         y = factor * exp (-square (x - mean) / (2. * variance));
+         debug_printf (("(%20.10f, %20.10f)\n", x, y));
+         graf_polyline_point (gb.x1 + (x - lower) / (upper - lower) * width (gb),
+                              gb.y2 - y * (height (gb) - 1) / top);
+       }
+      graf_polyline_end ();
+    }
+  graf_fill_color (0, 0, 0);
+}
+
+static int
+scale_dep_axis (int max)
+{
+  int j, s, x, y, ty, by;
+  char buf[10];
+
+  x = 10, s = 2;
+  if (scale != SYSMIS && max < scale)
+    x = scale, s = scale / 5;
+  else if (format == PERCENT)
+    {
+      max = ((BIG_TYPE) 100 * cur_var->p.frq.t.max_freq
+            / cur_var->p.frq.t.valid_cases + 1);
+      if (max < 5)
+       x = 5, s = 1;
+      else if (max < 10)
+       x = 10, s = 2;
+      else if (max < 25)
+       x = 25, s = 5;
+      else if (max < 50)
+       x = 50, s = 10;
+      else
+       max = 100, s = 20;
+    }
+  else                         /* format==FREQ */
+    /* Uses a progression of 10, 20, 50, 100, 200, 500, ... */
+    for (;;)
+      {
+       if (x > max)
+         break;
+       x *= 2;
+       s *= 2;
+       if (x > max)
+         break;
+       x = x / 2 * 5;
+       s = s / 2 * 5;
+       if (x > max)
+         break;
+       x *= 2;
+       s *= 2;
+      }
+  graf_font_size (iy / 9);     /* 8-pt text */
+  for (j = 0; j <= x; j += s)
+    {
+      y = gb.y2 - (BIG_TYPE) j *(height (gb) - 1) / x;
+      ty = y - iy / 64;
+      by = y + iy / 64;
+      if (ty < gb.y1)
+       ty += iy / 64, by += iy / 64;
+      else if (by > gb.y2)
+       ty -= iy / 64, by -= iy / 64;
+      graf_fill_rect (gb.x1 - ix / 16, ty, gb.x1, by);
+      sprintf (buf, "%d", j);
+      graf_text (gb.x1 - ix / 8, (ty + by) / 2, buf, CRJUST);
+    }
+  return x;
+}
+\f
+/* Percentiles. */
+
+static void ungrouped_pcnt (int i);
+static int grouped_interval_pcnt (int i);
+static void out_pcnt (double, double);
+
+static void
+out_percentiles (int i)
+{
+  if (cur_var->type == ALPHA || !n_percentiles)
+    return;
+
+  outs_line (_("Percentile    Value     "
+            "Percentile    Value     "
+            "Percentile    Value"));
+  blank_line ();
+
+  column = 0;
+  if (!g_var[i])
+    ungrouped_pcnt (i);
+  else if (g_var[i] == 1)
+    grouped_interval_pcnt (i);
+#if 0
+  else if (g_var[i] == -1)
+    grouped_pcnt (i);
+  else
+    grouped_boundaries_pcnt (i);
+#else /* !0 */
+  else
+    warn (_("this form of percentiles not supported"));
+#endif
+  if (column)
+    out_eol ();
+}
+
+static void
+out_pcnt (double pcnt, double value)
+{
+  if (!column)
+    out_header ();
+  else
+    outs ("     ");
+  out ("%7.2f%13.3f", pcnt * 100., value);
+  column++;
+  if (column == 3)
+    {
+      out_eol ();
+      column = 0;
+    }
+}
+
+static void
+ungrouped_pcnt (int i)
+{
+  AVLtraverser *t = NULL;
+  struct freq *f;
+  double *p, *e;
+  int sum;
+
+  p = percentiles;
+  e = &percentiles[n_percentiles];
+  sum = 0;
+  for (f = avltrav (cur_var->p.frq.t.f, &t);
+       f && p < e; f = avltrav (cur_var->p.frq.t.f, &t))
+    {
+      sum += f->c;
+      while (sum >= p[0] * cur_var->p.frq.t.valid_cases && p < e)
+       out_pcnt (*p++, f->v.f);
+    }
+}
+
+
+static int
+grouped_interval_pcnt (int i)
+{
+  AVLtraverser * t = NULL;
+  struct freq * f, *fp;
+  double *p, *e, w;
+  int sum, psum;
+
+  p = percentiles;
+  e = &percentiles[n_percentiles];
+  w = gl_var[i][0];
+  sum = psum = 0;
+  for (fp = 0, f = avltrav (cur_var->p.frq.t.f, &t);
+       f && p < e;
+       fp = f, f = avltrav (cur_var->p.frq.t.f, &t))
+    {
+      if (fp)
+       if (fabs (f->v.f - fp->v.f) < w)
+         {
+           out_eol ();
+           column = 0;
+           return msg (SE, _("Difference between %g and %g is "
+                             "too small for grouping interval %g."), f->v.f,
+                       fp->v.f, w);
+         }
+      psum = sum;
+      sum += f->c;
+      while (sum >= p[0] * cur_var->p.frq.t.valid_cases && p < e)
+       {
+         out_pcnt (p[0], (((p[0] * cur_var->p.frq.t.valid_cases) - psum) * w / f->c
+                          + (f->v.f - w / 2)));
+         p++;
+       }
+    }
+  return 1;
+}
+#endif
+
+/* 
+   Local Variables:
+   mode: c
+   End:
+*/
diff --git a/src/get.c b/src/get.c
new file mode 100644 (file)
index 0000000..8f4a05f
--- /dev/null
+++ b/src/get.c
@@ -0,0 +1,1610 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "avl.h"
+#include "command.h"
+#include "error.h"
+#include "file-handle.h"
+#include "lexer.h"
+#include "misc.h"
+#include "pfm.h"
+#include "settings.h"
+#include "sfm.h"
+#include "str.h"
+#include "var.h"
+#include "vfm.h"
+#include "vfmP.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* XSAVE transformation (and related SAVE, EXPORT procedures). */
+struct save_trns
+  {
+    struct trns_header h;
+    struct file_handle *f;     /* Associated system file. */
+    int nvar;                  /* Number of variables. */
+    int *var;                  /* Indices of variables. */
+    flt64 *case_buf;           /* Case transfer buffer. */
+  };
+
+/* Options bits set by trim_dictionary(). */
+#define GTSV_OPT_COMPRESSED    001     /* Compression; (X)SAVE only. */
+#define GTSV_OPT_SAVE          002     /* The SAVE/XSAVE/EXPORT procedures. */
+#define GTSV_OPT_MATCH_FILES   004     /* The MATCH FILES procedure. */
+#define GTSV_OPT_NONE          0
+
+/* The file being read by the input program. */
+static struct file_handle *get_file;
+
+/* The transformation being used by the SAVE procedure. */
+static struct save_trns *trns;
+
+static int trim_dictionary (struct dictionary * dict, int *options);
+static int save_write_case_func (struct ccase *);
+static int save_trns_proc (struct trns_header *, struct ccase *);
+static void save_trns_free (struct trns_header *);
+
+#if DEBUGGING
+void dump_dict_variables (struct dictionary *);
+#endif
+
+/* Parses the GET command. */
+int
+cmd_get (void)
+{
+  struct file_handle *handle;
+  struct dictionary *dict;
+  int options = GTSV_OPT_NONE;
+
+  int i;
+  int nval;
+
+  lex_match_id ("GET");
+  discard_variables ();
+
+  lex_match ('/');
+  if (lex_match_id ("FILE"))
+    lex_match ('=');
+
+  handle = fh_parse_file_handle ();
+  if (handle == NULL)
+    return CMD_FAILURE;
+
+  dict = sfm_read_dictionary (handle, NULL);
+  if (dict == NULL)
+    return CMD_FAILURE;
+
+#if DEBUGGING
+  dump_dict_variables (dict);
+#endif
+  if (0 == trim_dictionary (dict, &options))
+    {
+      fh_close_handle (handle);
+      return CMD_FAILURE;
+    }
+#if DEBUGGING
+  dump_dict_variables (dict);
+#endif
+
+  /* Set the fv and lv elements of all variables remaining in the
+     dictionary. */
+  nval = 0;
+  for (i = 0; i < dict->nvar; i++)
+    {
+      struct variable *v = dict->var[i];
+
+      v->fv = nval;
+      nval += v->nv;
+    }
+  dict->nval = nval;
+  assert (nval);
+
+#if DEBUGGING
+  printf (_("GET translation table from file to memory:\n"));
+  for (i = 0; i < dict->nvar; i++)
+    {
+      struct variable *v = dict->var[i];
+
+      printf (_("  %8s from %3d,%3d to %3d,%3d\n"), v->name,
+             v->get.fv, v->get.nv, v->fv, v->nv);
+    }
+#endif
+
+  restore_dictionary (dict);
+
+  vfm_source = &get_source;
+  get_file = handle;
+
+  return CMD_SUCCESS;
+}
+
+/* Parses the SAVE (for X==0) and XSAVE (for X==1) commands.  */
+/* FIXME: save_dictionary() is too expensive.  It would make more
+   sense to copy just the first few fields of each variables (up to
+   `foo'): that's a SMOP. */
+int
+cmd_save_internal (int x)
+{
+  struct file_handle *handle;
+  struct dictionary *dict;
+  int options = GTSV_OPT_SAVE;
+
+  struct save_trns *t;
+  struct sfm_write_info inf;
+
+  int i;
+
+  lex_match_id ("SAVE");
+
+  lex_match ('/');
+  if (lex_match_id ("OUTFILE"))
+    lex_match ('=');
+
+  handle = fh_parse_file_handle ();
+  if (handle == NULL)
+    return CMD_FAILURE;
+
+  dict = save_dictionary ();
+#if DEBUGGING
+  dump_dict_variables (dict);
+#endif
+  for (i = 0; i < dict->nvar; i++)
+    dict->var[i]->foo = i;
+  if (0 == trim_dictionary (dict, &options))
+    {
+      fh_close_handle (handle);
+      return CMD_FAILURE;
+    }
+
+#if DEBUGGING
+  dump_dict_variables (dict);
+#endif
+
+  /* Write dictionary. */
+  inf.h = handle;
+  inf.dict = dict;
+  inf.compress = !!(options & GTSV_OPT_COMPRESSED);
+  if (!sfm_write_dictionary (&inf))
+    {
+      free_dictionary (dict);
+      fh_close_handle (handle);
+      return CMD_FAILURE;
+    }
+
+  /* Fill in transformation structure. */
+  t = trns = xmalloc (sizeof *t);
+  t->h.proc = save_trns_proc;
+  t->h.free = save_trns_free;
+  t->f = handle;
+  t->nvar = dict->nvar;
+  t->var = xmalloc (sizeof *t->var * dict->nvar);
+  for (i = 0; i < dict->nvar; i++)
+    t->var[i] = dict->var[i]->foo;
+  t->case_buf = xmalloc (sizeof *t->case_buf * inf.case_size);
+  free_dictionary (dict);
+
+  if (x == 0)
+    /* SAVE. */
+    {
+      procedure (NULL, save_write_case_func, NULL);
+      save_trns_free ((struct trns_header *) t);
+    }
+  else
+    /* XSAVE. */
+    add_transformation ((struct trns_header *) t);
+
+  return CMD_SUCCESS;
+}
+
+/* Parses and performs the SAVE procedure. */
+int
+cmd_save (void)
+{
+  return cmd_save_internal (0);
+}
+
+/* Parses the XSAVE transformation command. */
+int
+cmd_xsave (void)
+{
+  return cmd_save_internal (1);
+}
+
+static int
+save_write_case_func (struct ccase * c)
+{
+  save_trns_proc ((struct trns_header *) trns, c);
+  return 1;
+}
+
+static int
+save_trns_proc (struct trns_header * t unused, struct ccase * c)
+{
+  flt64 *p = trns->case_buf;
+  int i;
+
+  for (i = 0; i < trns->nvar; i++)
+    {
+      struct variable *v = default_dict.var[trns->var[i]];
+      if (v->type == NUMERIC)
+       {
+         double src = c->data[v->fv].f;
+         if (src == SYSMIS)
+           *p++ = -FLT64_MAX;
+         else
+           *p++ = src;
+       }
+      else
+       {
+         memcpy (p, c->data[v->fv].s, v->width);
+         memset (&((char *) p)[v->width], ' ',
+                 REM_RND_UP (v->width, sizeof *p));
+         p += DIV_RND_UP (v->width, sizeof *p);
+       }
+    }
+
+  sfm_write_case (trns->f, trns->case_buf, p - trns->case_buf);
+  return -1;
+}
+
+static void
+save_trns_free (struct trns_header *pt)
+{
+  struct save_trns *t = (struct save_trns *) pt;
+
+  fh_close_handle (t->f);
+  free (t->var);
+  free (t->case_buf);
+  free (t);
+}
+
+/* Deletes NV variables from DICT, starting at index FIRST.  The
+   variables must have consecutive indices.  The variables are cleared
+   and freed. */
+static void
+dict_delete_run (struct dictionary *dict, int first, int nv)
+{
+  int i;
+
+  for (i = first; i < first + nv; i++)
+    {
+      clear_variable (dict, dict->var[i]);
+      free (dict->var[i]);
+    }
+  for (i = first; i < dict->nvar - nv; i++)
+    {
+      dict->var[i] = dict->var[i + nv];
+      dict->var[i]->index -= nv;
+    }
+  dict->nvar -= nv;
+}
+
+static int rename_variables (struct dictionary * dict);
+
+/* The GET and SAVE commands have a common structure after the
+   FILE/OUTFILE subcommand.  This function parses this structure and
+   returns nonzero on success, zero on failure.  It both reads
+   *OPTIONS, for the GTSV_OPT_SAVE bit, and writes it, for the
+   GTSV_OPT_COMPRESSED bit. */
+/* FIXME: IN, FIRST, LAST, MAP. */
+static int
+trim_dictionary (struct dictionary *dict, int *options)
+{
+  if (set_scompression)
+    *options |= GTSV_OPT_COMPRESSED;
+
+  if (*options & GTSV_OPT_SAVE)
+    {
+      int i;
+
+      /* Delete all the scratch variables. */
+      for (i = 0; i < dict->nvar; i++)
+       {
+         int j;
+         
+         if (dict->var[i]->name[0] != '#')
+           continue;
+
+         /* Find a run of variables to be deleted. */
+         for (j = i + 1; j < dict->nvar; j++)
+           if (dict->var[j]->name[0] != '#')
+             break;
+
+         /* Actually delete 'em. */
+         dict_delete_run (dict, i, j - i);
+       }
+    }
+  
+  while ((*options & GTSV_OPT_MATCH_FILES) || lex_match ('/'))
+    {
+      if (!(*options & GTSV_OPT_MATCH_FILES) && lex_match_id ("COMPRESSED"))
+       *options |= GTSV_OPT_COMPRESSED;
+      else if (!(*options & GTSV_OPT_MATCH_FILES) && lex_match_id ("UNCOMPRESSED"))
+       *options &= ~GTSV_OPT_COMPRESSED;
+      else if (lex_match_id ("DROP"))
+       {
+         struct variable **v;
+         int nv;
+         int i;
+
+         lex_match ('=');
+         if (!parse_variables (dict, &v, &nv, PV_NONE))
+           return 0;
+
+         /* Loop through the variables to delete. */
+         for (i = 0; i < nv;)
+           {
+             int j;
+
+             /* Find a run of variables to be deleted. */
+             for (j = i + 1; j < nv; j++)
+               if (v[j]->index != v[j - 1]->index + 1)
+                 break;
+
+             /* Actually delete 'em. */
+             dict_delete_run (dict, v[i]->index, j - i);
+             i = j;
+           }
+       }
+      else if (lex_match_id ("KEEP"))
+       {
+         struct variable **v;
+         int nv;
+
+         lex_match ('=');
+         if (!parse_variables (dict, &v, &nv, PV_NONE))
+           return 0;
+
+         /* Reorder the dictionary so that the kept variables are at
+            the beginning. */
+         {
+           int i1;
+           
+           for (i1 = 0; i1 < nv; i1++)
+             {
+               int i2 = v[i1]->index;
+
+               /* Swap variables with indices i1 and i2. */
+               struct variable *t = dict->var[i1];
+               dict->var[i1] = dict->var[i2];
+               dict->var[i2] = t;
+               dict->var[i1]->index = i1;
+               dict->var[i2]->index = i2;
+             }
+
+           free (v);
+         }
+         
+         /* Delete all but the first NV variables from the
+            dictionary. */
+         {
+           int i;
+           for (i = nv; i < dict->nvar; i++)
+             {
+               clear_variable (dict, dict->var[i]);
+               free (dict->var[i]);
+             }
+         }
+         dict->var = xrealloc (dict->var, sizeof *dict->var * nv);
+         dict->nvar = nv;
+       }
+      else if (lex_match_id ("RENAME"))
+       {
+         if (!rename_variables (dict))
+           return 0;
+       }
+      else
+       {
+         lex_error (_("while expecting a valid subcommand"));
+         return 0;
+       }
+
+      if (dict->nvar == 0)
+       {
+         msg (SE, _("All variables deleted from system file dictionary."));
+         return 0;
+       }
+
+      if (*options & GTSV_OPT_MATCH_FILES)
+       return 1;
+    }
+
+  if (token != '.')
+    {
+      lex_error (_("expecting end of command"));
+      return 0;
+    }
+  
+  return 1;
+}
+
+/* Parses and performs the RENAME subcommand of GET and SAVE. */
+static int
+rename_variables (struct dictionary * dict)
+{
+  int i;
+
+  int success = 0;
+
+  struct variable **v;
+  char **new_names;
+  int nv, nn;
+
+  int group;
+
+  lex_match ('=');
+  if (token != '(')
+    {
+      struct variable *v;
+
+      v = parse_dict_variable (dict);
+      if (v == NULL)
+       return 0;
+      if (!lex_force_match ('=')
+         || !lex_force_id ())
+       return 0;
+      if (!strncmp (tokid, v->name, 8))
+       return 1;
+      if (is_dict_varname (dict, tokid))
+       {
+         msg (SE, _("Cannot rename %s as %s because there already exists "
+                    "a variable named %s.  To rename variables with "
+                    "overlapping names, use a single RENAME subcommand "
+                    "such as \"/RENAME (A=B)(B=C)(C=A)\", or equivalently, "
+                    "\"/RENAME (A B C=B C A)\"."), v->name, tokid, tokid);
+         return 0;
+       }
+      
+      rename_variable (dict, v, tokid);
+      lex_get ();
+      return 1;
+    }
+
+  nv = nn = 0;
+  v = NULL;
+  new_names = 0;
+  group = 1;
+  while (lex_match ('('))
+    {
+      int old_nv = nv;
+
+      if (!parse_variables (dict, &v, &nv, PV_NO_DUPLICATE | PV_APPEND))
+       goto lossage;
+      if (!lex_match ('='))
+       {
+         msg (SE, _("`=' expected after variable list."));
+         goto lossage;
+       }
+      if (!parse_DATA_LIST_vars (&new_names, &nn, PV_APPEND | PV_NO_SCRATCH))
+       goto lossage;
+      if (nn != nv)
+       {
+         msg (SE, _("Number of variables on left side of `=' (%d) do not "
+              "match number of variables on right side (%d), in "
+              "parenthesized group %d of RENAME subcommand."),
+              nv - old_nv, nn - old_nv, group);
+         goto lossage;
+       }
+      if (!lex_force_match (')'))
+       goto lossage;
+      group++;
+    }
+
+  for (i = 0; i < nv; i++)
+    avl_force_delete (dict->var_by_name, v[i]);
+  for (i = 0; i < nv; i++)
+    {
+      strcpy (v[i]->name, new_names[i]);
+      if (NULL != avl_insert (dict->var_by_name, v[i]))
+       {
+         msg (SE, _("Duplicate variables name %s."), v[i]->name);
+         goto lossage;
+       }
+    }
+  success = 1;
+
+lossage:
+  /* The label is a bit of a misnomer, we actually come here on any
+     sort of return. */
+  for (i = 0; i < nn; i++)
+    free (new_names[i]);
+  free (new_names);
+  free (v);
+
+  return success;
+}
+
+#if DEBUGGING
+void
+dump_dict_variables (struct dictionary * dict)
+{
+  int i;
+
+  printf (_("\nVariables in dictionary:\n"));
+  for (i = 0; i < dict->nvar; i++)
+    printf ("%s, ", dict->var[i]->name);
+  printf ("\n");
+}
+#endif
+\f
+/* Clears internal state related to GET input procedure. */
+static void
+get_source_destroy_source (void)
+{
+  /* It is not necessary to destroy the dictionary because if we get
+     to this point then the dictionary is default_dict. */
+  fh_close_handle (get_file);
+}
+
+/* Reads all the cases from the data file and passes them to
+   write_case(). */
+static void
+get_source_read (void)
+{
+  while (sfm_read_case (get_file, temp_case->data, &default_dict)
+        && write_case ())
+    ;
+  get_source_destroy_source ();
+}
+
+struct case_stream get_source =
+  {
+    NULL,
+    get_source_read,
+    NULL,
+    NULL,
+    get_source_destroy_source,
+    NULL,
+    "GET",
+  };
+
+\f
+/* MATCH FILES. */
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* File types. */
+enum
+  {
+    MTF_FILE,                  /* Specified on FILE= subcommand. */
+    MTF_TABLE                  /* Specified on TABLE= subcommand. */
+  };
+
+/* One of the files on MATCH FILES. */
+struct mtf_file
+  {
+    struct mtf_file *next, *prev;
+                               /* Next, previous in the list of files. */
+    struct mtf_file *next_min; /* Next in the chain of minimums. */
+    
+    int type;                  /* One of MTF_*. */
+    struct variable **by;      /* List of BY variables for this file. */
+    struct file_handle *handle;        /* File handle for the file. */
+    struct dictionary *dict;   /* Dictionary from system file. */
+    char in[9];                        /* Name of the variable from IN=. */
+    char first[9], last[9];    /* Name of the variables from FIRST=, LAST=. */
+    union value *input;                /* Input record. */
+  };
+
+/* All the files mentioned on FILE= or TABLE=. */
+static struct mtf_file *mtf_head, *mtf_tail;
+
+/* Variables on the BY subcommand. */
+static struct variable **mtf_by;
+static int mtf_n_by;
+
+/* Master dictionary. */
+static struct dictionary *mtf_master;
+
+static void mtf_free (void);
+static void mtf_free_file (struct mtf_file *file);
+static int mtf_merge_dictionary (struct mtf_file *f);
+static void mtf_delete_file_in_place (struct mtf_file **file);
+
+static void mtf_read_nonactive_records (void);
+static void mtf_processing_finish (void);
+static int mtf_processing (struct ccase *);
+
+static char *var_type_description (struct variable *);
+
+/* Parse and execute the MATCH FILES command. */
+int
+cmd_match_files (void)
+{
+  struct mtf_file *first_table = NULL;
+  
+  int seen = 0;
+  
+  lex_match_id ("MATCH");
+  lex_match_id ("FILES");
+
+  mtf_head = mtf_tail = NULL;
+  mtf_by = NULL;
+  mtf_n_by = 0;
+  mtf_master = new_dictionary (0);
+  mtf_master->N = default_dict.N;
+  
+  do
+    {
+      lex_match ('/');
+
+      if (lex_match (T_BY))
+       {
+         if (seen & 1)
+           {
+             msg (SE, _("The BY subcommand may be given once at most."));
+             goto lossage;
+           }
+         seen |= 1;
+             
+         lex_match ('=');
+         if (!parse_variables (mtf_master, &mtf_by, &mtf_n_by,
+                               PV_NO_DUPLICATE | PV_NO_SCRATCH))
+           goto lossage;
+       }
+      else if (token != T_ID)
+       {
+         lex_error (NULL);
+         goto lossage;
+       }
+      else if (lex_id_match ("FILE", tokid) || lex_id_match ("TABLE", tokid))
+       {
+         struct mtf_file *file = xmalloc (sizeof *file);
+
+         file->in[0] = file->first[0] = file->last[0] = '\0';
+         file->dict = NULL;
+         file->by = NULL;
+         file->input = NULL;
+
+         if (lex_match_id ("FILE"))
+           file->type = MTF_FILE;
+         else if (lex_match_id ("TABLE"))
+           {
+             file->type = MTF_TABLE;
+             seen |= 4;
+           }
+         else
+           assert (0);
+
+         /* FILEs go first, then TABLEs. */
+         if (file->type == MTF_TABLE || first_table == NULL)
+           {
+             file->next = NULL;
+             file->prev = mtf_tail;
+             if (mtf_tail)
+               mtf_tail->next = file;
+             mtf_tail = file;
+             if (mtf_head == NULL)
+               mtf_head = file;
+             if (file->type == MTF_TABLE && first_table == NULL)
+               first_table = file;
+           }
+         else 
+           {
+             assert (file->type == MTF_FILE);
+             file->next = first_table;
+             file->prev = first_table->prev;
+             if (first_table->prev)
+               first_table->prev->next = file;
+             else
+               mtf_head = file;
+             first_table->prev = file;
+           }
+         
+         lex_match ('=');
+         
+         if (lex_match ('*'))
+           {
+             file->handle = NULL;
+
+             if (seen & 2)
+               {
+                 msg (SE, _("The active file may not be specified more "
+                            "than once."));
+                 goto lossage;
+               }
+             seen |= 2;
+
+             assert (pgm_state != STATE_INPUT);
+             if (pgm_state == STATE_INIT)
+               {
+                 msg (SE, _("Cannot specify the active file since no active "
+                            "file has been defined."));
+                 goto lossage;
+               }
+           }
+         else
+           {
+             file->handle = fh_parse_file_handle ();
+             if (!file->handle)
+               goto lossage;
+           }
+
+         if (file->handle)
+           {
+             file->dict = sfm_read_dictionary (file->handle, NULL);
+             if (!file->dict)
+               goto lossage;
+           }
+         else
+           file->dict = &default_dict;
+         if (!mtf_merge_dictionary (file))
+           goto lossage;
+       }
+      else if (lex_id_match ("IN", tokid)
+              || lex_id_match ("FIRST", tokid)
+              || lex_id_match ("LAST", tokid))
+       {
+         const char *sbc;
+         char *name;
+         
+         if (mtf_tail == NULL)
+           {
+             msg (SE, _("IN, FIRST, and LAST subcommands may not occur "
+                        "before the first FILE or TABLE."));
+             goto lossage;
+           }
+
+         if (lex_match_id ("IN"))
+           {
+             name = mtf_tail->in;
+             sbc = "IN";
+           }
+         else if (lex_match_id ("FIRST"))
+           {
+             name = mtf_tail->first;
+             sbc = "FIRST";
+           }
+         else if (lex_match_id ("LAST"))
+           {
+             name = mtf_tail->last;
+             sbc = "LAST";
+           }
+         else
+           assert (0);
+
+         lex_match ('=');
+         if (token != T_ID)
+           {
+             lex_error (NULL);
+             goto lossage;
+           }
+
+         if (*name)
+           {
+             msg (SE, _("Multiple %s subcommands for a single FILE or "
+                        "TABLE."),
+                  sbc);
+             goto lossage;
+           }
+         strcpy (name, tokid);
+         lex_get ();
+
+         if (!create_variable (mtf_master, name, NUMERIC, 0))
+           {
+             msg (SE, _("Duplicate variable name %s while creating %s "
+                        "variable."),
+                  name, sbc);
+             goto lossage;
+           }
+       }
+      else if (lex_id_match ("RENAME", tokid)
+              || lex_id_match ("KEEP", tokid)
+              || lex_id_match ("DROP", tokid))
+       {
+         int options = GTSV_OPT_MATCH_FILES;
+         
+         if (mtf_tail == NULL)
+           {
+             msg (SE, _("RENAME, KEEP, and DROP subcommands may not occur "
+                        "before the first FILE or TABLE."));
+             goto lossage;
+           }
+
+         if (!trim_dictionary (mtf_tail->dict, &options))
+           goto lossage;
+       }
+      else if (lex_match_id ("MAP"))
+       {
+         /* FIXME. */
+       }
+      else
+       {
+         lex_error (NULL);
+         goto lossage;
+       }
+    }
+  while (token != '.');
+
+  if (seen & 4)
+    {
+      if (!(seen & 1))
+       {
+         msg (SE, _("The BY subcommand is required when a TABLE subcommand "
+                    "is given."));
+         goto lossage;
+       }
+    }
+
+  if (seen & 1)
+    {
+      struct mtf_file *iter;
+
+      for (iter = mtf_head; iter; iter = iter->next)
+       {
+         int i;
+         
+         iter->by = xmalloc (sizeof *iter->by * mtf_n_by);
+
+         for (i = 0; i < mtf_n_by; i++)
+           {
+             iter->by[i] = find_dict_variable (iter->dict, mtf_by[i]->name);
+             if (iter->by[i] == NULL)
+               {
+                 msg (SE, _("File %s lacks BY variable %s."),
+                      iter->handle ? fh_handle_name (iter->handle) : "*",
+                      mtf_by[i]->name);
+                 goto lossage;
+               }
+           }
+       }
+    }
+
+#if DEBUGGING
+  {
+    /* From sfm-read.c. */
+    extern void dump_dictionary (struct dictionary *);
+
+    dump_dictionary (mtf_master);
+  }
+#endif
+
+  /* MATCH FILES performs an n-way merge on all its input files.
+     Abstract algorithm:
+
+     1. Read one input record from every input FILE.
+
+     2. If no FILEs are left, stop.  Otherwise, proceed to step 3.
+
+     3. Find the FILE input record with minimum BY values.  Store all
+     the values from this input record into the output record.
+
+     4. Find all the FILE input records with BY values identical to
+     the minimums.  Store all the values from these input records into
+     the output record.
+
+     5. For every TABLE, read another record as long as the BY values
+     on the TABLE's input record are less than the FILEs' BY values.
+     If an exact match is found, store all the values from the TABLE
+     input record into the output record.
+
+     6. Write the output record.
+
+     7. Read another record from each input file FILE and TABLE that
+     we stored values from above.  If we come to the end of one of the
+     input files, remove it from the list of input files.
+
+     8. Repeat from step 2.
+
+     Unfortunately, this algorithm can't be directly implemented
+     because there's no function to read a record from the active
+     file; instead, it has to be done using callbacks.
+
+     FIXME: A better algorithm would use a heap for finding minimum
+     values, or replacement selection, as described by Knuth in _Art
+     of Computer Programming, Vol. 3_.  The SORT CASES procedure does
+     this, and perhaps some of its code could be adapted. */
+
+  if (!(seen & 2))
+    discard_variables ();
+
+  temporary = 2;
+  temp_dict = mtf_master;
+  temp_trns = n_trns;
+
+  process_active_file (mtf_read_nonactive_records, mtf_processing,
+                      mtf_processing_finish);
+  mtf_master = NULL;
+  
+  mtf_free ();
+  return CMD_SUCCESS;
+  
+lossage:
+  mtf_free ();
+  return CMD_FAILURE;
+}
+
+/* Repeats 2...8 an arbitrary number of times. */
+static void
+mtf_processing_finish (void)
+{
+  /* Find the active file and delete it. */
+  {
+    struct mtf_file *iter;
+    
+    for (iter = mtf_head; iter; iter = iter->next)
+      if (iter->handle == NULL)
+       {
+         mtf_delete_file_in_place (&iter);
+         break;
+       }
+  }
+  
+  while (mtf_head && mtf_head->type == MTF_FILE)
+    if (!mtf_processing (temp_case))
+      break;
+}
+
+/* Return a string in a static buffer describing V's variable type and
+   width. */
+static char *
+var_type_description (struct variable *v)
+{
+  static char buf[2][32];
+  static int x = 0;
+  char *s;
+
+  x ^= 1;
+  s = buf[x];
+
+  if (v->type == NUMERIC)
+    strcpy (s, "numeric");
+  else
+    {
+      assert (v->type == ALPHA);
+      sprintf (s, "string with width %d", v->width);
+    }
+  return s;
+}
+
+/* Free FILE and associated data. */
+static void
+mtf_free_file (struct mtf_file *file)
+{
+  fh_close_handle (file->handle);
+  if (file->dict && file->dict != &default_dict)
+    free_dictionary (file->dict);
+  free (file->by);
+  if (file->handle)
+    free (file->input);
+  free (file);
+}
+
+/* Free all the data for the MATCH FILES procedure. */
+static void
+mtf_free (void)
+{
+  struct mtf_file *iter, *next;
+
+  for (iter = mtf_head; iter; iter = next)
+    {
+      next = iter->next;
+
+      mtf_free_file (iter);
+    }
+  
+  free (mtf_by);
+  if (mtf_master)
+    free_dictionary (mtf_master);
+}
+
+/* Remove *FILE from the mtf_file chain.  Make *FILE point to the next
+   file in the chain, or to NULL if was the last in the chain. */
+static void
+mtf_delete_file_in_place (struct mtf_file **file)
+{
+  struct mtf_file *f = *file;
+
+  if (f->prev)
+    f->prev->next = f->next;
+  if (f->next)
+    f->next->prev = f->prev;
+  if (f == mtf_head)
+    mtf_head = f->next;
+  if (f == mtf_tail)
+    mtf_tail = f->prev;
+  *file = f->next;
+
+  {
+    int i;
+
+    for (i = 0; i < f->dict->nvar; i++)
+      {
+       struct variable *v = f->dict->var[i];
+         
+       if (v->type == NUMERIC)
+         compaction_case->data[v->p.mtf.master->fv].f = SYSMIS;
+       else
+         memset (compaction_case->data[v->p.mtf.master->fv].s, ' ',
+                 v->width);
+      }
+  }
+  
+  mtf_free_file (f);
+}
+
+/* Read a record from every input file except the active file. */
+static void
+mtf_read_nonactive_records (void)
+{
+  struct mtf_file *iter;
+
+  for (iter = mtf_head; iter; )
+    {
+      if (iter->handle)
+       {
+         assert (iter->input == NULL);
+         iter->input = xmalloc (sizeof *iter->input * iter->dict->nval);
+         
+         if (!sfm_read_case (iter->handle, iter->input, iter->dict))
+           mtf_delete_file_in_place (&iter);
+         else
+           iter = iter->next;
+       }
+      else
+       {
+         iter->input = temp_case->data;
+         iter = iter->next;
+       }
+    }
+}
+
+/* Compare the BY variables for files A and B; return -1 if A < B, 0
+   if A == B, 1 if A > B. */
+static inline int
+mtf_compare_BY_values (struct mtf_file *a, struct mtf_file *b)
+{
+  int i;
+  
+  for (i = 0; i < mtf_n_by; i++)
+    {
+      assert (a->by[i]->type == b->by[i]->type);
+      assert (a->by[i]->width == b->by[i]->width);
+      
+      if (a->by[i]->type == NUMERIC)
+       {
+         double af = a->input[a->by[i]->fv].f;
+         double bf = b->input[b->by[i]->fv].f;
+
+         if (af < bf)
+           return -1;
+         else if (af > bf)
+           return 1;
+       }
+      else 
+       {
+         int result;
+         
+         assert (a->by[i]->type == ALPHA);
+         result = memcmp (a->input[a->by[i]->fv].s,
+                          b->input[b->by[i]->fv].s,
+                          a->by[i]->width);
+         if (result < 0)
+           return -1;
+         else if (result > 0)
+           return 1;
+       }
+    }
+  return 0;
+}
+
+/* Used to determine whether we've already initialized this
+   variable. */
+static int mtf_seq_no = 0;
+
+/* Perform one iteration of steps 3...7 above. */
+static int
+mtf_processing (struct ccase *c unused)
+{
+  /* List of files with minimum BY values. */
+  struct mtf_file *min_head, *min_tail;
+
+  /* List of files with non-minimum BY values. */
+  struct mtf_file *max_head, *max_tail;
+
+  /* Iterator. */
+  struct mtf_file *iter;
+
+  for (;;)
+    {
+      /* If the active file doesn't have the minimum BY values, don't
+        return because that would cause a record to be skipped. */
+      int advance = 1;
+
+      if (mtf_head->type == MTF_TABLE)
+       return 0;
+      
+      /* 3. Find the FILE input record with minimum BY values.  Store
+        all the values from this input record into the output record.
+
+        4. Find all the FILE input records with BY values identical
+        to the minimums.  Store all the values from these input
+        records into the output record. */
+      min_head = min_tail = mtf_head;
+      max_head = max_tail = NULL;
+      for (iter = mtf_head->next; iter && iter->type == MTF_FILE;
+          iter = iter->next)
+       switch (mtf_compare_BY_values (min_head, iter))
+         {
+         case -1:
+           if (max_head)
+             max_tail = max_tail->next_min = iter;
+           else
+             max_head = max_tail = iter;
+           break;
+
+         case 0:
+           min_tail = min_tail->next_min = iter;
+           break;
+
+         case 1:
+           if (max_head)
+             {
+               max_tail->next_min = min_head;
+               max_tail = min_tail;
+             }
+           else
+             {
+               max_head = min_head;
+               max_tail = min_tail;
+             }
+           min_head = min_tail = iter;
+           break;
+
+         default:
+           assert (0);
+         }
+
+      /* 5. For every TABLE, read another record as long as the BY
+        values on the TABLE's input record are less than the FILEs'
+        BY values.  If an exact match is found, store all the values
+        from the TABLE input record into the output record. */
+      while (iter)
+       {
+         struct mtf_file *next = iter->next;
+         
+         assert (iter->type == MTF_TABLE);
+      
+         if (iter->handle == NULL)
+           advance = 0;
+
+       again:
+         switch (mtf_compare_BY_values (min_head, iter))
+           {
+           case -1:
+             if (max_head)
+               max_tail = max_tail->next_min = iter;
+             else
+               max_head = max_tail = iter;
+             break;
+
+           case 0:
+             min_tail = min_tail->next_min = iter;
+             break;
+
+           case 1:
+             if (iter->handle == NULL)
+               return 1;
+             if (sfm_read_case (iter->handle, iter->input, iter->dict))
+               goto again;
+             mtf_delete_file_in_place (&iter);
+             break;
+
+           default:
+             assert (0);
+           }
+
+         iter = next;
+       }
+
+      /* Next sequence number. */
+      mtf_seq_no++;
+  
+      /* Store data to all the records we are using. */
+      if (min_tail)
+       min_tail->next_min = NULL;
+      for (iter = min_head; iter; iter = iter->next_min)
+       {
+         int i;
+
+         for (i = 0; i < iter->dict->nvar; i++)
+           {
+             struct variable *v = iter->dict->var[i];
+         
+             if (v->p.mtf.master->foo == mtf_seq_no)
+               continue;
+             v->p.mtf.master->foo = mtf_seq_no;
+
+#if 0
+             printf ("%s/%s: dest-fv=%d, src-fv=%d\n",
+                     fh_handle_name (iter->handle),
+                     v->name,
+                     v->p.mtf.master->fv, v->fv);
+#endif
+             if (v->type == NUMERIC)
+               compaction_case->data[v->p.mtf.master->fv].f
+                 = iter->input[v->fv].f;
+             else
+               {
+                 assert (v->type == ALPHA);
+                 memcpy (compaction_case->data[v->p.mtf.master->fv].s,
+                         iter->input[v->fv].s, v->width);
+#if __CHECKER__
+                 memset (&compaction_case
+                         ->data[v->p.mtf.master->fv].s[v->width],
+                         0, REM_RND_UP (v->width, MAX_SHORT_STRING));
+#endif
+               }
+           }
+       }
+
+      /* Store missing values to all the records we're not using. */
+      if (max_tail)
+       max_tail->next_min = NULL;
+      for (iter = max_head; iter; iter = iter->next_min)
+       {
+         int i;
+
+         for (i = 0; i < iter->dict->nvar; i++)
+           {
+             struct variable *v = iter->dict->var[i];
+         
+             if (v->p.mtf.master->foo == mtf_seq_no)
+               continue;
+             v->p.mtf.master->foo = mtf_seq_no;
+
+#if 0
+             printf ("%s/%s: dest-fv=%d\n",
+                     fh_handle_name (iter->handle),
+                     v->name,
+                     v->p.mtf.master->fv);
+#endif
+             if (v->type == NUMERIC)
+               compaction_case->data[v->p.mtf.master->fv].f = SYSMIS;
+             else
+               {
+                 memset (compaction_case->data[v->p.mtf.master->fv].s, ' ',
+                         v->width);
+#if __CHECKER__
+                 memset (&compaction_case
+                         ->data[v->p.mtf.master->fv].s[v->width],
+                         0, REM_RND_UP (v->width, MAX_SHORT_STRING));
+#endif
+               }
+           }
+
+         if (iter->handle == NULL)
+           advance = 0;
+       }
+
+      /* 6. Write the output record. */
+      process_active_file_output_case ();
+
+      /* 7. Read another record from each input file FILE and TABLE
+        that we stored values from above.  If we come to the end of
+        one of the input files, remove it from the list of input
+        files. */
+      for (iter = min_head; iter && iter->type == MTF_FILE; )
+       {
+         struct mtf_file *next = iter->next_min;
+         
+         if (iter->handle)
+           {
+             assert (iter->input != NULL);
+
+             if (!sfm_read_case (iter->handle, iter->input, iter->dict))
+               mtf_delete_file_in_place (&iter);
+           }
+
+         iter = next;
+       }
+      
+      if (advance)
+       break;
+    }
+
+  return (mtf_head && mtf_head->type != MTF_TABLE);
+}
+
+/* Merge the dictionary for file F into the master dictionary
+   mtf_master. */
+static int
+mtf_merge_dictionary (struct mtf_file *f)
+{
+  struct dictionary *const m = mtf_master;
+  struct dictionary *d = f->dict;
+      
+  if (d->label && m->label == NULL)
+    m->label = xstrdup (d->label);
+
+  if (d->documents)
+    {
+      m->documents = xrealloc (m->documents,
+                              80 * (m->n_documents + d->n_documents));
+      memcpy (&m->documents[80 * m->n_documents],
+             d->documents, 80 * d->n_documents);
+      m->n_documents += d->n_documents;
+    }
+      
+  {
+    int i;
+
+    d->nval = 0;
+    for (i = 0; i < d->nvar; i++)
+      {
+       struct variable *dv = d->var[i];
+       struct variable *mv = find_dict_variable (m, dv->name);
+
+       dv->fv = d->nval;
+       d->nval += dv->nv;
+       
+       assert (dv->type == ALPHA || dv->width == 0);
+       assert (!mv || mv->type == ALPHA || mv->width == 0);
+       if (mv && dv->width == mv->width)
+         {
+           if (dv->val_lab && !mv->val_lab)
+             mv->val_lab = copy_value_labels (dv->val_lab);
+           if (dv->miss_type != MISSING_NONE && mv->miss_type == MISSING_NONE)
+             copy_missing_values (mv, dv);
+         }
+       if (mv && dv->label && !mv->label)
+         mv->label = xstrdup (dv->label);
+       if (!mv)
+         {
+           mv = force_dup_variable (m, dv, dv->name);
+
+           /* Used to make sure we initialize each variable in the
+              master dictionary exactly once per case. */
+           mv->foo = mtf_seq_no;
+         }
+       else if (mv->width != dv->width)
+         {
+           msg (SE, _("Variable %s in file %s (%s) has different "
+                      "type or width from the same variable in "
+                      "earlier file (%s)."),
+                dv->name, fh_handle_name (f->handle),
+                var_type_description (dv), var_type_description (mv));
+           return 0;
+         }
+       dv->p.mtf.master = mv;
+      }
+  }
+
+  return 1;
+}
+\f
+/* IMPORT command. */
+
+/* Parses the IMPORT command. */
+int
+cmd_import (void)
+{
+  struct file_handle *handle = NULL;
+  struct dictionary *dict;
+  int options = GTSV_OPT_NONE;
+  int type;
+
+  int i;
+  int nval;
+
+  lex_match_id ("IMPORT");
+
+  for (;;)
+    {
+      lex_match ('/');
+      
+      if (lex_match_id ("FILE") || token == T_STRING)
+       {
+         lex_match ('=');
+
+         handle = fh_parse_file_handle ();
+         if (handle == NULL)
+           return CMD_FAILURE;
+       }
+      else if (lex_match_id ("TYPE"))
+       {
+         lex_match ('=');
+
+         if (lex_match_id ("COMM"))
+           type = PFM_COMM;
+         else if (lex_match_id ("TAPE"))
+           type = PFM_TAPE;
+         else
+           {
+             lex_error (_("expecting COMM or TAPE"));
+             return CMD_FAILURE;
+           }
+       }
+      else break;
+    }
+  if (!lex_match ('/') && token != '.')
+    {
+      lex_error (NULL);
+      return CMD_FAILURE;
+    }
+
+  discard_variables ();
+
+  dict = pfm_read_dictionary (handle, NULL);
+  if (dict == NULL)
+    return CMD_FAILURE;
+
+#if DEBUGGING
+  dump_dict_variables (dict);
+#endif
+  if (0 == trim_dictionary (dict, &options))
+    {
+      fh_close_handle (handle);
+      return CMD_FAILURE;
+    }
+#if DEBUGGING
+  dump_dict_variables (dict);
+#endif
+
+  /* Set the fv and lv elements of all variables remaining in the
+     dictionary. */
+  nval = 0;
+  for (i = 0; i < dict->nvar; i++)
+    {
+      struct variable *v = dict->var[i];
+
+      v->fv = nval;
+      nval += v->nv;
+    }
+  dict->nval = nval;
+  assert (nval);
+
+#if DEBUGGING
+  printf (_("IMPORT translation table from file to memory:\n"));
+  for (i = 0; i < dict->nvar; i++)
+    {
+      struct variable *v = dict->var[i];
+
+      printf (_("  %8s from %3d,%3d to %3d,%3d\n"), v->name,
+             v->get.fv, v->get.nv, v->fv, v->nv);
+    }
+#endif
+
+  restore_dictionary (dict);
+
+  vfm_source = &import_source;
+  get_file = handle;
+
+  return CMD_SUCCESS;
+}
+
+/* Reads all the cases from the data file and passes them to
+   write_case(). */
+static void
+import_source_read (void)
+{
+  while (pfm_read_case (get_file, temp_case->data, &default_dict)
+        && write_case ())
+    ;
+  get_source_destroy_source ();
+}
+
+struct case_stream import_source =
+  {
+    NULL,
+    import_source_read,
+    NULL,
+    NULL,
+    get_source_destroy_source,
+    NULL,
+    "IMPORT",
+  };
+\f
+static int export_write_case_func (struct ccase *c);
+     
+/* Parses the EXPORT command.  */
+/* FIXME: same as cmd_save_internal(). */
+int
+cmd_export (void)
+{
+  struct file_handle *handle;
+  struct dictionary *dict;
+  int options = GTSV_OPT_SAVE;
+
+  struct save_trns *t;
+
+  int i;
+
+  lex_match_id ("EXPORT");
+
+  lex_match ('/');
+  if (lex_match_id ("OUTFILE"))
+    lex_match ('=');
+
+  handle = fh_parse_file_handle ();
+  if (handle == NULL)
+    return CMD_FAILURE;
+
+  dict = save_dictionary ();
+#if DEBUGGING
+  dump_dict_variables (dict);
+#endif
+  for (i = 0; i < dict->nvar; i++)
+    dict->var[i]->foo = i;
+  if (0 == trim_dictionary (dict, &options))
+    {
+      fh_close_handle (handle);
+      return CMD_FAILURE;
+    }
+
+#if DEBUGGING
+  dump_dict_variables (dict);
+#endif
+
+  /* Write dictionary. */
+  if (!pfm_write_dictionary (handle, dict))
+    {
+      free_dictionary (dict);
+      fh_close_handle (handle);
+      return CMD_FAILURE;
+    }
+
+  /* Fill in transformation structure. */
+  t = trns = xmalloc (sizeof *t);
+  t->h.proc = save_trns_proc;
+  t->h.free = save_trns_free;
+  t->f = handle;
+  t->nvar = dict->nvar;
+  t->var = xmalloc (sizeof *t->var * dict->nvar);
+  for (i = 0; i < dict->nvar; i++)
+    t->var[i] = dict->var[i]->foo;
+  t->case_buf = xmalloc (sizeof *t->case_buf * dict->nvar);
+  free_dictionary (dict);
+
+  procedure (NULL, export_write_case_func, NULL);
+  save_trns_free ((struct trns_header *) t);
+
+  return CMD_SUCCESS;
+}
+
+static int
+export_write_case_func (struct ccase *c)
+{
+  union value *p = (union value *) trns->case_buf;
+  int i;
+
+  for (i = 0; i < trns->nvar; i++)
+    {
+      struct variable *v = default_dict.var[trns->var[i]];
+
+      if (v->type == NUMERIC)
+       *p++ = c->data[v->fv];
+      else
+       (*p++).c = c->data[v->fv].s;
+    }
+
+  printf (".");
+  fflush (stdout);
+  
+  pfm_write_case (trns->f, (union value *) trns->case_buf);
+  return 1;
+}
diff --git a/src/getline.c b/src/getline.c
new file mode 100644 (file)
index 0000000..5b8588c
--- /dev/null
@@ -0,0 +1,519 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "error.h"
+#include "filename.h"
+#include "getline.h"
+#include "lexer.h"
+#include "settings.h"
+#include "str.h"
+#include "tab.h"
+#include "var.h"
+#include "version.h"
+
+/* Global variables. */
+struct string getl_buf;
+struct getl_script *getl_head;
+struct getl_script *getl_tail;
+int getl_interactive;
+int getl_welcomed;
+int getl_mode;
+int getl_prompt;
+
+#if HAVE_LIBHISTORY
+static char *history_file;
+
+#if HAVE_READLINE_HISTORY_H
+#include <readline/history.h>
+#else /* no readline/history.h */
+extern void add_history (char *);
+extern void using_history (void);
+extern int read_history (char *);
+extern void stifle_history (int);
+extern int write_history (char *);
+#endif /* no readline/history.h */
+#endif /* -lhistory */
+
+static struct string getl_include_path;
+
+/* Number of levels of DO REPEAT structures we're nested inside.  If
+   this is greater than zero then DO REPEAT macro substitutions are
+   performed. */
+static int DO_REPEAT_level;
+
+static int read_console (void);
+
+/* Initialize getline. */
+void
+getl_initialize (void)
+{
+  ds_create (NULL, &getl_include_path,
+            fn_getenv_default ("STAT_INCLUDE_PATH", include_path));
+  ds_init (NULL, &getl_buf, 256);
+}
+
+/* Close getline. */
+void
+getl_uninitialize (void)
+{
+#if HAVE_LIBHISTORY && unix
+  if (history_file)
+    write_history (history_file);
+#endif
+}
+
+/* Returns a string that represents the directory that the syntax file
+   currently being read resides in.  If there is no syntax file then
+   returns the OS current working directory.  Return value must be
+   free()'d. */
+char *
+getl_get_current_directory (void)
+{
+  return getl_head ? fn_dirname (getl_head->fn) : fn_get_cwd ();
+}
+
+/* Delete everything from the include path. */
+void
+getl_clear_include_path (void)
+{
+  ds_clear (&getl_include_path);
+}
+
+/* Add to the include path. */
+void
+getl_add_include_dir (const char *path)
+{
+  if (ds_length (&getl_include_path))
+    ds_putchar (&getl_include_path, PATH_DELIMITER);
+
+  ds_concat (&getl_include_path, path);
+}
+
+/* Adds FN to the tail end of the list of script files to execute.
+   OPTIONS is the value to stick in the options field of the
+   getl_script struct.  If WHERE is zero then the file is added after
+   all other files; otherwise it is added before all other files (this
+   can be done only if parsing has not yet begun). */
+void
+getl_add_file (const char *fn, int separate, int where)
+{
+  struct getl_script *n = xmalloc (sizeof *n);
+
+  assert (fn != NULL);
+  n->next = NULL;
+  if (getl_tail == NULL)
+    getl_head = getl_tail = n;
+  else if (!where)
+    getl_tail = getl_tail->next = n;
+  else
+    {
+      assert (getl_head->f == NULL);
+      n->next = getl_head;
+      getl_head = n;
+    }
+  n->included_from = n->includes = NULL;
+  n->fn = xstrdup (fn);
+  n->ln = 0;
+  n->f = NULL;
+  n->separate = separate;
+  n->first_line = NULL;
+}
+
+/* Inserts the given file with filename FN into the current file after
+   the current line. */
+void
+getl_include (const char *fn)
+{
+  struct getl_script *n;
+  char *real_fn;
+
+  {
+    char *cur_dir = getl_get_current_directory ();
+    real_fn = fn_search_path (fn, ds_value (&getl_include_path), cur_dir);
+    free (cur_dir);
+  }
+
+  if (!real_fn)
+    {
+      msg (SE, _("Can't find `%s' in include file search path."), fn);
+      return;
+    }
+
+  if (!getl_head)
+    {
+      getl_add_file (real_fn, 0, 0);
+      free (real_fn);
+    }
+  else
+    {
+      n = xmalloc (sizeof *n);
+      n->included_from = getl_head;
+      getl_head = getl_head->includes = n;
+      n->includes = NULL;
+      n->next = NULL;
+      n->fn = real_fn;
+      n->ln = 0;
+      n->f = NULL;
+      n->separate = 0;
+      n->first_line = NULL;
+    }
+}
+
+/* Add the virtual file FILE to the list of files to be processed.
+   The first_line field in FILE must already have been initialized. */
+void 
+getl_add_virtual_file (struct getl_script *file)
+{
+  if (getl_tail == NULL)
+    getl_head = getl_tail = file;
+  else
+    getl_tail = getl_tail->next = file;
+  file->included_from = file->includes = NULL;
+  file->next = NULL;
+  file->fn = file->first_line->line;
+  file->ln = -file->first_line->len - 1;
+  file->separate = 0;
+  file->f = NULL;
+  file->cur_line = NULL;
+  file->remaining_loops = 1;
+  file->loop_index = -1;
+  file->macros = NULL;
+}
+
+/* Causes the DO REPEAT virtual file passed in FILE to be included in
+   the current file.  The first_line, cur_line, remaining_loops,
+   loop_index, and macros fields in FILE must already have been
+   initialized. */
+void
+getl_add_DO_REPEAT_file (struct getl_script *file)
+{
+  /* getl_head == NULL can't happen. */
+  assert (getl_head);
+
+  DO_REPEAT_level++;
+  file->included_from = getl_head;
+  getl_head = getl_head->includes = file;
+  file->includes = NULL;
+  file->next = NULL;
+  assert (file->first_line->len < 0);
+  file->fn = file->first_line->line;
+  file->ln = -file->first_line->len - 1;
+  file->separate = 0;
+  file->f = NULL;
+}
+
+/* Display a welcoming message. */
+void
+welcome (void)
+{
+  getl_welcomed = 1;
+  fputs ("PSPP is free software and you are welcome to distribute copies of"
+        "it\nunder certain conditions; type \"show copying.\" to see the "
+        "conditions.\nThere is ABSOLUTELY NO WARRANTY for PSPP; type \"show "
+        "warranty.\" for details.\n", stdout);
+  puts (stat_version);
+}
+
+/* Reads a single line from the user's terminal. */
+
+/* From repeat.c. */
+extern void perform_DO_REPEAT_substitutions (void);
+  
+/* Reads a single line from the line buffer associated with getl_head.
+   Returns 1 if a line was successfully read or 0 if no more lines are
+   available. */
+static int
+handle_line_buffer (void)
+{
+  struct getl_script *s = getl_head;
+
+  /* Check that we're not all done. */
+  do
+    {
+      if (s->cur_line == NULL)
+       {
+         s->loop_index++;
+         if (s->remaining_loops-- == 0)
+           return 0;
+         s->cur_line = s->first_line;
+       }
+
+      if (s->cur_line->len < 0)
+       {
+         s->ln = -s->cur_line->len - 1;
+         s->fn = s->cur_line->line;
+         s->cur_line = s->cur_line->next;
+         continue;
+       }
+    }
+  while (s->cur_line == NULL);
+
+  ds_concat_buffer (&getl_buf, s->cur_line->line, s->cur_line->len);
+
+  /* Advance pointers. */
+  s->cur_line = s->cur_line->next;
+  s->ln++;
+
+  return 1;
+}
+
+/* Reads a single line into getl_buf from the list of files.  Will not
+   read from the eof of one file to the beginning of another unless
+   the options field on the new file's getl_script is nonzero.  Return
+   zero on eof. */
+int
+getl_read_line (void)
+{
+  getl_mode = GETL_MODE_BATCH;
+  
+  while (getl_head)
+    {
+      struct getl_script *s = getl_head;
+
+      ds_clear (&getl_buf);
+      if (s->separate)
+       return 0;
+
+      if (s->first_line)
+       {
+         if (!handle_line_buffer ())
+           {
+             getl_close_file ();
+             continue;
+           }
+         perform_DO_REPEAT_substitutions ();
+         if (getl_head->print)
+           tab_output_text (TAB_LEFT | TAT_FIX | TAT_PRINTF, "+%s",
+                            ds_value (&getl_buf));
+         return 1;
+       }
+      
+      if (s->f == NULL)
+       {
+         msg (VM (1), _("%s: Opening as syntax file."), s->fn);
+         s->f = fn_open (s->fn, "r");
+
+         if (s->f == NULL)
+           {
+             msg (ME, _("Opening `%s': %s."), s->fn, strerror (errno));
+             getl_close_file ();
+             continue;
+           }
+       }
+
+      if (!ds_getline (&getl_buf, s->f))
+       {
+         if (ferror (s->f))
+           msg (ME, _("Reading `%s': %s."), s->fn, strerror (errno));
+         getl_close_file ();
+         continue;
+       }
+      if (ds_length (&getl_buf) > 0 && ds_end (&getl_buf)[-1] == '\n')
+       ds_truncate (&getl_buf, ds_length (&getl_buf) - 1);
+
+      if (set_echo)
+       tab_output_text (TAB_LEFT | TAT_FIX, ds_value (&getl_buf));
+
+      getl_head->ln++;
+
+      /* Allows shebang invocation: `#! /usr/local/bin/pspp'. */
+      if (ds_value (&getl_buf)[0] == '#'
+         && ds_value (&getl_buf)[1] == '!')
+       continue;
+
+      return 1;
+    }
+
+  if (getl_interactive == 0)
+    return 0;
+
+  getl_mode = GETL_MODE_INTERACTIVE;
+  
+  if (getl_welcomed == 0)
+    welcome ();
+
+  return read_console ();
+}
+
+/* Closes the current file, whether it be a main file or included
+   file, then moves getl_head to the next file in the chain. */
+void
+getl_close_file (void)
+{
+  struct getl_script *s = getl_head;
+
+  if (!s)
+    return;
+  assert (getl_tail != NULL);
+
+  if (s->first_line)
+    {
+      struct getl_line_list *cur, *next;
+
+      s->fn = NULL; /* It will be freed below. */
+      for (cur = s->first_line; cur; cur = next)
+       {
+         next = cur->next;
+         free (cur->line);
+         free (cur);
+       }
+
+      DO_REPEAT_level--;
+    }
+  
+  if (s->f && EOF == fn_close (s->fn, s->f))
+    msg (MW, _("Closing `%s': %s."), s->fn, strerror (errno));
+  free (s->fn);
+
+  if (s->included_from)
+    {
+      getl_head = s->included_from;
+      getl_head->includes = NULL;
+    }
+  else
+    {
+      getl_head = s->next;
+      if (NULL == getl_head)
+       getl_tail = NULL;
+    }
+  
+  free (s);
+}
+
+/* PORTME: Adapt to your local system's idea of the terminal. */
+#if HAVE_LIBREADLINE
+
+#if HAVE_READLINE_READLINE_H
+#include <readline/readline.h>
+#else /* no readline/readline.h */
+extern char *readline (char *);
+#endif /* no readline/readline.h */
+
+static int
+read_console (void)
+{
+  char *line;
+  char *prompt;
+
+  err_error_count = err_warning_count = 0;
+  err_already_flagged = 0;
+
+#if HAVE_LIBHISTORY
+  if (!history_file)
+    {
+#if unix
+      history_file = tilde_expand (HISTORY_FILE);
+#endif
+      using_history ();
+      read_history (history_file);
+      stifle_history (MAX_HISTORY);
+    }
+#endif /* -lhistory */
+
+  switch (getl_prompt)
+    {
+    case GETL_PRPT_STANDARD:
+      prompt = set_prompt;
+      break;
+
+    case GETL_PRPT_CONTINUATION:
+      prompt = set_cprompt;
+      break;
+
+    case GETL_PRPT_DATA:
+      prompt = set_dprompt;
+      break;
+
+    default:
+      assert (0);
+    }
+
+  line = readline (prompt);
+  if (!line)
+    return 0;
+
+#if HAVE_LIBHISTORY
+  if (*line)
+    add_history (line);
+#endif
+
+  ds_clear (&getl_buf);
+  ds_concat (&getl_buf, line);
+
+  return 1;
+}
+#else /* no -lreadline */
+static int
+read_console (void)
+{
+  err_error_count = err_warning_count = 0;
+  err_already_flagged = 0;
+
+  fputs (getl_prompt ? set_cprompt : set_prompt, stdout);
+  ds_clear (&getl_buf);
+  if (ds_getline (&getl_buf, stdin))
+    return 1;
+
+  if (ferror (stdin))
+    msg (FE, "stdin: fgets(): %s.", strerror (errno));
+
+  return 0;
+}
+#endif /* no -lreadline */
+
+/* Closes all files. */
+void
+getl_close_all (void)
+{
+  while (getl_head)
+    getl_close_file ();
+}
+
+/* Sets the options flag of the current script to 0, thus allowing it
+   to be read in.  Returns nonzero if this action was taken, zero
+   otherwise. */
+int
+getl_perform_delayed_reset (void)
+{
+  if (getl_head && getl_head->separate)
+    {
+      getl_head->separate = 0;
+      discard_variables ();
+      lex_reset_eof ();
+      return 1;
+    }
+  return 0;
+}
+
+/* Puts the current file and line number in *FN and *LN, respectively,
+   or NULL and -1 if none. */
+void
+getl_location (const char **fn, int *ln)
+{
+  if (fn != NULL)
+    *fn = getl_head ? getl_head->fn : NULL;
+  if (ln != NULL)
+    *ln = getl_head ? getl_head->ln : -1;
+}
diff --git a/src/getline.h b/src/getline.h
new file mode 100644 (file)
index 0000000..f04bacd
--- /dev/null
@@ -0,0 +1,117 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !getline_h
+#define getline_h 1
+
+#include <stdio.h>
+
+/* Defines a list of lines used by DO REPEAT. */
+/* Special case: if LEN is negative then it is a line number; in this
+   case LINE is a file name.  This is used to allow errors to be
+   reported for the correct file and line number when DO REPEAT spans
+   files. */
+struct getl_line_list
+  {
+    char *line;                                /* Line contents. */
+    int len;                           /* Line length. */
+    struct getl_line_list *next;       /* Next line. */
+  };
+
+/* Source file. */
+struct getl_script
+  {
+    struct getl_script *included_from; /* File that this is nested inside. */
+    struct getl_script *includes;      /* File nested inside this file. */
+    struct getl_script *next;          /* Next file in list. */
+    char *fn;                          /* Filename. */
+    int ln;                            /* Line number. */
+    int separate;                      /* !=0 means this is a separate job. */
+    FILE *f;                           /* File handle. */
+
+    /* Used only if F is NULL.  Used for DO REPEAT. */
+    struct getl_line_list *first_line; /* First line in line buffer. */
+    struct getl_line_list *cur_line;   /* Current line in line buffer. */
+    int remaining_loops;               /* Number of remaining loops through LINES. */
+    int loop_index;                    /* Number of loops through LINES so far. */
+    void *macros;                      /* Pointer to macro table. */
+    int print;                         /* 1=Print lines as executed. */
+  };
+
+/* List of script files. */
+extern struct getl_script *getl_head;  /* Current file. */
+extern struct getl_script *getl_tail;  /* End of list. */
+
+/* If getl_head==0 and getl_interactive!=0, lines will be read from
+   the console rather than terminating. */
+extern int getl_interactive;
+
+/* 1=the welcome message has been printed. */
+extern int getl_welcomed;
+
+/* Prompt styles. */
+enum
+  {
+    GETL_PRPT_STANDARD,                /* Just asks for a command. */
+    GETL_PRPT_CONTINUATION,    /* Continuation lines for a single command. */
+    GETL_PRPT_DATA             /* Between BEGIN DATA and END DATA. */
+  };
+
+/* Current mode. */
+enum
+  {
+    GETL_MODE_BATCH,           /* Batch mode. */
+    GETL_MODE_INTERACTIVE      /* Interactive mode. */
+  };
+
+/* One of GETL_MODE_*, representing the current mode. */
+extern int getl_mode;
+
+/* Current prompting style: one of GETL_PRPT_*. */
+extern int getl_prompt;
+
+/* Are we reading a script? Are we interactive? */
+#define getl_am_interactive (getl_head == NULL)
+#define getl_reading_script (getl_head != NULL)
+
+/* Current line.  This line may be modified by modules other than
+   getline.c, and by lexer.c in particular. */
+extern struct string getl_buf;
+
+/* Name of the command history file. */
+#if HAVE_LIBREADLINE && HAVE_LIBHISTORY
+extern char *getl_history;
+#endif
+
+void getl_initialize (void);
+void getl_uninitialize (void);
+void getl_clear_include_path (void);
+char *getl_get_current_directory (void);
+void getl_add_include_dir (const char *);
+void getl_add_file (const char *fn, int separate, int where);
+void getl_include (const char *fn);
+int getl_read_line (void);
+void getl_close_file (void);
+void getl_close_all (void);
+int getl_perform_delayed_reset (void);
+void getl_add_DO_REPEAT_file (struct getl_script *);
+void getl_add_virtual_file (struct getl_script *);
+void getl_location (const char **, int *);
+
+#endif /* getline_h */
diff --git a/src/glob.c b/src/glob.c
new file mode 100644 (file)
index 0000000..f9d443b
--- /dev/null
@@ -0,0 +1,431 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+
+#include <assert.h>
+#include <stdlib.h>
+
+#if TIME_WITH_SYS_TIME
+#include <sys/time.h>
+#include <time.h>
+#else
+#if HAVE_SYS_TIME_H
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#endif
+
+#if HAVE_LIBTERMCAP
+#if HAVE_TERMCAP_H
+#include <termcap.h>
+#else /* !HAVE_TERMCAP_H */
+int tgetent (char *, char *);
+int tgetnum (char *);
+#endif /* !HAVE_TERMCAP_H */
+#endif /* !HAVE_LIBTERMCAP */
+
+#if HAVE_LIBHISTORY
+#if HAVE_READLINE_HISTORY_H
+#include <readline/history.h>
+#else /* no readline/history.h */
+extern void using_history ();
+extern int read_history ();
+extern void stifle_history ();
+#endif /* no readline/history.h */
+#endif /* -lhistory */
+
+#if HAVE_FPU_CONTROL_H
+#include <fpu_control.h>
+#elif __BORLANDC__
+#include <float.h>
+#include <math.h>
+#endif
+
+#if __DJGPP__
+#include <conio.h>
+#elif __WIN32__ && __BORLANDC__
+#undef gettext
+#include <conio.h>
+#define gettext(STRING)                                \
+       STRING
+#endif
+
+#if HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#if HAVE_FENV_H
+#include <fenv.h>
+#endif
+
+#include "alloc.h"
+#include "avl.h"
+#include "command.h"
+#include "do-ifP.h"
+#include "error.h"
+#include "expr.h"
+#include "filename.h"
+#include "getline.h"
+#include "julcal/julcal.h"
+#include "lexer.h"
+#include "main.h"
+#include "settings.h"
+#include "str.h"
+#include "var.h"
+#include "version.h"
+#include "vfm.h"
+
+/* var.h */
+struct dictionary default_dict;
+struct expression *process_if_expr;
+
+struct ccase *temp_case;
+
+struct trns_header **t_trns;
+int n_trns;
+int m_trns;
+int f_trns;
+
+int FILTER_before_TEMPORARY;
+
+struct file_handle *default_handle;
+
+void (*read_active_file) (void);
+void (*cancel_input_pgm) (void);
+
+struct ctl_stmt *ctl_stack;
+
+/* log.h */
+char *logfn;
+FILE *logfile;
+int logging;
+\f
+/* Functions. */
+
+static void get_date (void);
+
+#if HAVE_LIBTERMCAP && !__CHECKER__
+static char *term_buffer;
+#endif
+
+void
+init_glob (int argc unused, char **argv)
+{
+  /* FIXME: Allow i18n of other locale items (besides LC_MESSAGES). */
+#if ENABLE_NLS
+#if LC_MESSAGE
+  setlocale (LC_MESSAGES, "");
+#endif
+  setlocale (LC_MONETARY, "");
+  bindtextdomain (PACKAGE, locale_dir);
+  textdomain (PACKAGE);
+#endif /* ENABLE_NLS */
+
+  /* Workable defaults before we determine the real terminal size. */
+  set_viewwidth = 79;
+  set_viewlength = 24;
+
+  fn_init ();
+  getl_initialize ();
+
+  /* PORTME: If your system/OS has the nasty tendency to halt with a
+     SIGFPE whenever there's a floating-point overflow (or other
+     exception), be sure to mask off those bits in the FPU here.
+     PSPP wants a guarantee that, no matter what boneheaded
+     floating-point operation it performs, the process will not halt.  */
+#if HAVE_FEHOLDEXCEPT
+  {
+    fenv_t foo;
+
+    feholdexcept (&foo);
+  }
+#elif HAVE___SETFPUCW && defined(_FPU_IEEE)
+  __setfpucw (_FPU_IEEE);
+#elif __BORLANDC__
+  _control87 (0xffff, 0x137f);
+#endif
+
+#if ENDIAN==UNKNOWN
+  {
+    /* Test for endianness borrowed from acspecific.m4, which was in
+     turn borrowed from Harbison&Steele. */
+    union
+      {
+       long l;
+       char c[sizeof (long)];
+      }
+    u;
+
+    u.l = 1;
+    if (u.c[sizeof u.l - 1] == 1)
+      endian = BIG;
+    else if (u.c[0] == 1)
+      endian = LITTLE;
+    else
+      msg (FE, _("Your machine does not appear to be either big- or little-"
+                "endian.  At the moment, PSPP only supports machines of "
+                "these standard endiannesses.  If you want to hack in "
+                "others, contact the author."));
+  }
+#endif
+
+  /* PORTME: Set the value for second_lowest_value, which is the
+     "second lowest" possible value for a double.  This is the value
+     for LOWEST on MISSING VALUES, etc. */
+#ifndef SECOND_LOWEST_VALUE
+#if FPREP == FPREP_IEEE754
+  {
+    union
+      {
+       unsigned char c[8];
+       double d;
+      }
+    second_lowest_little = {{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xff}},
+    second_lowest_big = {{0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}};
+
+    if (endian == LITTLE)
+      second_lowest_value = second_lowest_little.d;
+    else if (endian == BIG)
+      second_lowest_value = second_lowest_big.d;
+  }
+#else /* FPREP != FPREP_IEEE754 */
+#error Unknown floating-point representation.
+#endif /* FPREP != FPREP_IEEE754 */
+#endif /* !SECOND_LOWEST_VALUE */
+
+  /* var.h */
+  default_dict.var_by_name = avl_create (NULL, cmp_variable, NULL);
+
+  vec_init (&reinit_sysmis);
+  vec_init (&reinit_blanks);
+  vec_init (&init_zero);
+  vec_init (&init_blanks);
+
+  last_vfm_invocation = time (NULL);
+
+  /* lexer.h */
+  ds_init (NULL, &tokstr, 64);
+
+  /* common.h */
+  {
+    char *cp;
+    
+    pgmname = argv[0];
+    for (;;)
+      {
+       cp = strchr (pgmname, DIR_SEPARATOR);
+       if (!cp)
+         break;
+       pgmname = &cp[1];
+      }
+    cur_proc = NULL;
+  }
+
+  /* settings.h */
+#if !USE_INTERNAL_PAGER
+  {
+    char *pager;
+
+    pager = getenv ("STAT_PAGER");
+    if (!pager)
+      pager = getenv ("PAGER");
+    if (pager)
+      set_pager = xstrdup (pager);
+#if DEFAULT_PAGER
+    else
+      set_pager = xstrdup (DEFAULT_PAGER);
+#endif /* DEFAULT_PAGER */
+  }
+#endif /* !USE_INTERNAL_PAGER */
+
+  set_blanks = SYSMIS;
+  set_scompression = 1;
+  set_format.type = FMT_F;
+  set_format.w = 8;
+  set_format.d = 2;
+  set_cpi = 6;
+  set_lpi = 10;
+  set_results_file = xstrdup ("pspp.prc");
+  set_dprompt = xstrdup (_("data> "));
+  
+  {
+    int i;
+    
+    for (i = 0; i < 5; i++)
+      {
+       struct set_cust_currency *cc = &set_cc[i];
+       strcpy (cc->buf, "-");
+       cc->neg_prefix = cc->buf;
+       cc->prefix = &cc->buf[1];
+       cc->suffix = &cc->buf[1];
+       cc->neg_suffix = &cc->buf[1];
+       cc->decimal = '.';
+       cc->grouping = ',';
+      }
+  }
+  
+  set_decimal = '.';
+  set_grouping = ',';
+  set_headers = 1;
+  set_journaling = 1;
+  set_journal = xstrdup ("pspp.jnl");
+  set_messages = 1;
+  set_mexpand = 1;
+  set_mprint = 1;
+  set_mxerrs = 50;
+  set_mxwarns = 100;
+  set_printback = 1;
+  set_undefined = 1;
+
+  set_cprompt = xstrdup ("    > ");
+  set_echo = 0;
+  set_endcmd = '.';
+  set_errorbreak = 0;
+  set_include = 1;
+  set_nullline = 1;
+  set_more = 1;
+  set_prompt = xstrdup ("PSPP> ");
+  set_seed = 2000000;
+
+#if __DJGPP__ || __BORLANDC__
+  {
+    struct text_info ti;
+
+    gettextinfo (&ti);
+    set_viewlength = max (ti.screenheight, 25);
+    set_viewwidth = max (ti.screenwidth, 79);
+  }
+#elif HAVE_LIBTERMCAP
+  {
+    char *termtype;
+    int success;
+
+    /* This code stolen from termcap.info, though modified. */
+#if !__CHECKER__
+    term_buffer = xmalloc (2048);
+#endif
+
+    termtype = getenv ("TERM");
+    if (!termtype)
+      msg (FE, _("Specify a terminal type with `setenv TERM <yourtype>'."));
+
+#if __CHECKER__
+    success = tgetent (NULL, termtype);
+#else
+    success = tgetent (term_buffer, termtype);
+#endif
+
+    if (success <= 0)
+      {
+       if (success < 0)
+         msg (IE, _("Could not access the termcap data base."));
+       else
+         msg (IE, _("Terminal type `%s' is not defined."), termtype);
+       msg (MM, _("Assuming screen of size 79x25."));
+       set_viewlength = 25;
+       set_viewwidth = 79;
+      }
+    else
+      {
+       set_viewlength = tgetnum ("li");
+       set_viewwidth = tgetnum ("co") - 1;
+      }
+  }
+#else /* !HAVE_LIBTERMCAP */
+  set_viewlength = 25;
+  set_viewwidth = 79;
+#endif /* !HAVE_LIBTERMCAP */
+
+  /* log.h */
+  logging = 1;
+  logfn = xstrdup ("pspp.log");
+  logfile = NULL;
+
+  /* file-handle.h */
+  {
+    extern void fh_init_files (void);
+    
+    fh_init_files ();
+  }
+  
+  get_date ();
+}
+
+static void
+get_date ()
+{
+  static const char *months[12] =
+    {
+      N_("Jan"), N_("Feb"), N_("Mar"), N_("Apr"), N_("May"), N_("Jun"),
+      N_("Jul"), N_("Aug"), N_("Sep"), N_("Oct"), N_("Nov"), N_("Dec"),
+    };
+
+  time_t t;
+  int mn, dy, yr;
+  struct tm *tmp;
+
+  if ((time_t) -1 == time (&t))
+    {
+      strcpy (curdate, "1 Jan 1970");
+      return;
+    }
+  tmp = localtime (&t);
+
+  mn = tmp->tm_mon;
+  if (mn < 0)
+    mn = 0;
+  if (mn > 11)
+    mn = 11;
+
+  dy = tmp->tm_mday;
+  if (dy < 0)
+    dy = 0;
+  if (dy > 99)
+    dy = 99;
+
+  yr = tmp->tm_year + 1900;
+  if (yr < 0)
+    yr = 0;
+  if (yr > 9999)
+    yr = 9999;
+
+  sprintf (curdate, "%2d %s %04d", dy, gettext (months[mn]), yr);
+}
+
+int
+cmp_variable (const void *a, const void *b, void *foo unused)
+{
+  return strcmp (((struct variable *) a)->name, ((struct variable *) b)->name);
+}
+
+#if __BORLANDC__
+int
+_RTLENTRY _EXPFUNC _matherr (struct exception _FAR *__e)
+{
+  return 1;
+}
+
+int
+_RTLENTRY _EXPFUNC _matherrl (struct _exceptionl _FAR *__e)
+{
+  return 1;
+}
+#endif
diff --git a/src/groff-font.c b/src/groff-font.c
new file mode 100644 (file)
index 0000000..2510b89
--- /dev/null
@@ -0,0 +1,1010 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdarg.h>
+#include "alloc.h"
+#include "error.h"
+#include "filename.h"
+#include "font.h"
+#include "hash.h"
+#include "pool.h"
+#include "str.h"
+#include "version.h"
+
+int font_number_to_index (int);
+
+int space_index;
+
+static int font_msg (int, const char *,...)
+     __attribute__ ((format (printf, 2, 3)));
+static void scan_badchars (char *, int);
+static void dup_char_metric (struct font_desc * font, int dest, int src);
+static void add_char_metric (struct font_desc * font, struct char_metrics *metrics,
+                            int code);
+static void add_kern (struct font_desc * font, int ch1, int ch2, int adjust);
+
+/* Typical whitespace characters for tokenizing. */
+static const char whitespace[] = " \t\n\r\v";
+
+void
+groff_init (void)
+{
+  space_index = font_char_name_to_index ("space");
+}
+
+/* Some notes on the groff_font(8) manpage:
+
+   DESC file format: A typical PostScript `res' would be 72000, with
+   `hor' and `vert' set to 1 to indicate that all those positions are
+   valid.  `sizescale' of 1000 would indicate that a scaled point is
+   1/1000 of a point (which is 1/72000 of an inch, the same as the
+   number of machine units per inch indicated on `res').  `unitwidth'
+   of 1000 would indicate that font files are set up for fonts with
+   point size of 1000 scaled points, which would equal 1/72 inch or 1
+   point (this would tell Groff's postprocessor that it needs to scale
+   the font 12 times larger to get a 12-point font). */
+
+/* Reads a Groff font description file and converts it to a usable
+   binary format in memory.  Installs the binary format in the global
+   font table.  See groff_font(8) for a description of the font
+   description format supported.  Returns nonzero on success. */
+struct font_desc *
+groff_read_font (const char *fn)
+{
+  struct char_metrics *metrics;
+
+  /* Pool created for font, font being created, font file. */
+  struct pool *font_pool = NULL;
+  struct font_desc *font = NULL;
+  FILE *f = NULL;
+
+  /* Current line, size of line buffer, length of line. */
+  char *line = NULL;
+  size_t size;
+  int len;
+
+  /* Tokenization saved pointer. */
+  char *sp;
+  
+  /* First token on line. */
+  char *key;
+
+  /* 0=kernpairs section, 1=charset section. */
+  int charset;
+
+  /* Index for previous line. */
+  int prev_index = -1;
+
+  /* Current location in file, used for error reporting. */
+  struct file_locator where;
+
+#if unix
+  fn = fn_tilde_expand (fn);
+#endif
+
+  msg (VM (1), _("%s: Opening Groff font file..."), fn);
+
+  where.filename = fn;
+  where.line_number = 1;
+  err_push_file_locator (&where);
+
+  f = fopen (fn, "r");
+  if (!f)
+    goto file_lossage;
+
+  font_pool = pool_create ();
+  font = pool_alloc (font_pool, sizeof *font);
+  font->owner = font_pool;
+  font->name = NULL;
+  font->internal_name = NULL;
+  font->encoding = NULL;
+  font->space_width = 0;
+  font->slant = 0.0;
+  font->ligatures = 0;
+  font->special = 0;
+  font->deref = NULL;
+  font->deref_size = 0;
+  font->metric = NULL;
+  font->metric_size = 0;
+  font->metric_used = 0;
+  font->kern = NULL;
+  font->kern_size = 0;
+  font->kern_size_p = hsh_next_prime (64);
+  font->kern_used = 0;
+  font->kern_max_used = 0;
+
+  /* Parses first section of font file. */
+  for (;;)
+    {
+      /* Location of '#' in line. */
+      char *p;
+
+      len = getline (&line, &size, f);
+      if (len == -1)
+       break;
+      
+      scan_badchars (line, len);
+      p = strchr (line, '#');
+      if (p)
+       *p = '\0';              /* Reject comments. */
+
+      key = strtok_r (line, whitespace, &sp);
+      if (!key)
+       goto next_iteration;
+
+      if (!strcmp (key, "internalname"))
+       {
+         font->internal_name = strtok_r (NULL, whitespace, &sp);
+         if (font->internal_name == NULL)
+           {
+             font_msg (SE, _("Missing font name."));
+             goto lose;
+           }
+         font->internal_name = pool_strdup (font_pool, font->internal_name);
+       }
+      else if (!strcmp (key, "encoding"))
+       {
+         font->encoding = strtok_r (NULL, whitespace, &sp);
+         if (font->encoding == NULL)
+           {
+             font_msg (SE, _("Missing encoding filename."));
+             goto lose;
+           }
+         font->encoding = pool_strdup (font_pool, font->encoding);
+       }
+      else if (!strcmp (key, "spacewidth"))
+       {
+         char *n = strtok_r (NULL, whitespace, &sp);
+         char *tail;
+         if (n)
+           font->space_width = strtol (n, &tail, 10);
+         if (n == NULL || tail == n)
+           {
+             font_msg (SE, _("Bad spacewidth value."));
+             goto lose;
+           }
+       }
+      else if (!strcmp (key, "slant"))
+       {
+         char *n = strtok_r (NULL, whitespace, &sp);
+         char *tail;
+         if (n)
+           font->slant = strtod (n, &tail);
+         if (n == NULL || tail == n)
+           {
+             font_msg (SE, _("Bad slant value."));
+             goto lose;
+           }
+       }
+      else if (!strcmp (key, "ligatures"))
+       {
+         char *lig;
+
+         for (;;)
+           {
+             lig = strtok_r (NULL, whitespace, &sp);
+             if (!lig || !strcmp (lig, "0"))
+               break;
+             else if (!strcmp (lig, "ff"))
+               font->ligatures |= LIG_ff;
+             else if (!strcmp (lig, "ffi"))
+               font->ligatures |= LIG_ffi;
+             else if (!strcmp (lig, "ffl"))
+               font->ligatures |= LIG_ffl;
+             else if (!strcmp (lig, "fi"))
+               font->ligatures |= LIG_fi;
+             else if (!strcmp (lig, "fl"))
+               font->ligatures |= LIG_fl;
+             else
+               {
+                 font_msg (SE, _("Unknown ligature `%s'."), lig);
+                 goto lose;
+               }
+           }
+       }
+      else if (!strcmp (key, "special"))
+       font->special = 1;
+      else if (!strcmp (key, "charset") || !strcmp (key, "kernpairs"))
+       break;
+
+      where.line_number++;
+    }
+  if (ferror (f))
+    goto file_lossage;
+
+  /* Parses second section of font file (metrics & kerning data). */
+  do
+    {
+      key = strtok_r (line, whitespace, &sp);
+      if (!key)
+       goto next_iteration;
+
+      if (!strcmp (key, "charset"))
+       charset = 1;
+      else if (!strcmp (key, "kernpairs"))
+       charset = 0;
+      else if (charset)
+       {
+         struct char_metrics *metrics = pool_alloc (font_pool,
+                                                    sizeof *metrics);
+         char *m, *type, *code, *tail;
+
+         m = strtok_r (NULL, whitespace, &sp);
+         if (!m)
+           {
+             font_msg (SE, _("Unexpected end of line reading character "
+                             "set."));
+             goto lose;
+           }
+         if (!strcmp (m, "\""))
+           {
+             if (!prev_index)
+               {
+                 font_msg (SE, _("Can't use ditto mark for first character."));
+                 goto lose;
+               }
+             if (!strcmp (key, "---"))
+               {
+                 font_msg (SE, _("Can't ditto into an unnamed character."));
+                 goto lose;
+               }
+             dup_char_metric (font, font_char_name_to_index (key), prev_index);
+             where.line_number++;
+             goto next_iteration;
+           }
+
+         if (m)
+           {
+             metrics->code = metrics->width
+               = metrics->height = metrics->depth = 0;
+           }
+         
+         if (m == NULL || 1 > sscanf (m, "%d,%d,%d", &metrics->width,
+                                      &metrics->height, &metrics->depth))
+           {
+             font_msg (SE, _("Missing metrics for character `%s'."), key);
+             goto lose;
+           }
+
+         type = strtok_r (NULL, whitespace, &sp);
+         if (type)
+           metrics->type = strtol (type, &tail, 10);
+         if (!type || tail == type)
+           {
+             font_msg (SE, _("Missing type for character `%s'."), key);
+             goto lose;
+           }
+
+         code = strtok_r (NULL, whitespace, &sp);
+         if (code)
+           metrics->code = strtol (code, &tail, 0);
+         if (tail == code)
+           {
+             font_msg (SE, _("Missing code for character `%s'."), key);
+             goto lose;
+           }
+
+         if (strcmp (key, "---"))
+           prev_index = font_char_name_to_index (key);
+         else
+           prev_index = font_number_to_index (metrics->code);
+         add_char_metric (font, metrics, prev_index);
+       }
+      else
+       {
+         char *c1 = key;
+         char *c2 = strtok_r (NULL, whitespace, &sp);
+         char *n, *tail;
+         int adjust;
+
+         if (c2 == NULL)
+           {
+             font_msg (SE, _("Malformed kernpair."));
+             goto lose;
+           }
+
+         n = strtok_r (NULL, whitespace, &sp);
+         if (!n)
+           {
+             font_msg (SE, _("Unexpected end of line reading kernpairs."));
+             goto lose;
+           }
+         adjust = strtol (n, &tail, 10);
+         if (tail == n || *tail)
+           {
+             font_msg (SE, _("Bad kern value."));
+             goto lose;
+           }
+         add_kern (font, font_char_name_to_index (c1),
+                   font_char_name_to_index (c2), adjust);
+       }
+
+    next_iteration:
+      where.line_number++;
+
+      len = getline (&line, &size, f);
+    }
+  while (len != -1);
+  
+  if (ferror (f))
+    goto file_lossage;
+  if (fclose (f) == EOF)
+    {
+      f = NULL;
+      goto file_lossage;
+    }
+  free (line);
+#if unix
+  free ((char *) fn);
+#endif
+
+  /* Get font ascent and descent. */
+  metrics = font_get_char_metrics (font, font_char_name_to_index ("d"));
+  font->ascent = metrics ? metrics->height : 0;
+  metrics = font_get_char_metrics (font, font_char_name_to_index ("p"));
+  font->descent = metrics ? metrics->depth : 0;
+
+  msg (VM (2), _("Font read successfully with internal name %s."),
+       font->internal_name == NULL ? "<none>" : font->internal_name);
+  
+  err_pop_file_locator (&where);
+
+  return font;
+
+  /* Come here on a file error. */
+file_lossage:
+  msg (ME, "%s: %s", fn, strerror (errno));
+
+  /* Come here on any error. */
+lose:
+  fclose (f);
+  pool_destroy (font_pool);
+#if unix
+  free ((char *) fn);
+#endif
+  err_pop_file_locator (&where);
+
+  msg (VM (1), _("Error reading font."));
+  return NULL;
+}
+
+/* Prints a font error on stderr. */
+static int
+font_msg (int class, const char *format,...)
+{
+  va_list args;
+
+  va_start (args, format);
+  tmsg (class, format, args, _("installation error: Groff font error: "));
+  va_end (args);
+
+  return 0;
+}
+
+/* Scans string LINE of length LEN (not incl. null terminator) for bad
+   characters, converts to spaces; reports warnings on file FN. */
+static void
+scan_badchars (char *line, int len)
+{
+  unsigned char *cp = line;
+
+  /* Same bad characters as Groff. */
+  static unsigned char badchars[32] =
+  {
+    0x01, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  };
+
+  for (; len--; cp++)
+    if (badchars[*cp >> 3] & (1 << (*cp & 7)))
+      {
+       font_msg (SE, _("Bad character \\%3o."), *cp);
+       *cp = ' ';
+      }
+}
+\f
+/* Character name hashing. */
+
+/* Associates a character index with a character name. */
+struct index_hash
+  {
+    char *name;
+    int index;
+  };
+
+/* Character index hash table. */
+static struct
+  {
+    int size;                  /* Size of table. */
+    int *size_p;               /* Next larger table size. */
+    int used;                  /* Number of full entries. */
+    int max_used;              /* # used entries where we enlarge & rehash. */
+    int next_index;            /* Next index to allocate. */
+    struct index_hash *tab;    /* Hash table proper. */
+    struct pool *ar;           /* Pool for names. */
+  }
+hash;
+
+/* Searches for NAME in the global character code table, returns the
+   index if found; otherwise inserts NAME and returns the new
+   index. */
+int
+font_char_name_to_index (const char *name)
+{
+  int i;
+
+  if (name[0] == ' ')
+    return space_index;
+  if (name[0] == '\0' || name[1] == '\0')
+    return name[0];
+  if (0 == strncmp (name, "char", 4))
+    {
+      char *tail;
+      int x = strtol (name + 4, &tail, 10);
+      if (tail != name + 4 && *tail == 0 && x >= 0 && x <= 255)
+       return x;
+    }
+
+  if (!hash.tab)
+    {
+      hash.size_p = hsh_next_prime (128);
+      hash.size = *hash.size_p++;
+      hash.used = 0;
+      hash.max_used = hash.size / 2;
+      hash.next_index = 256;
+      hash.tab = xmalloc (sizeof *hash.tab * hash.size);
+      hash.ar = pool_create ();
+      for (i = 0; i < hash.size; i++)
+       hash.tab[i].name = NULL;
+    }
+
+  for (i = hashpjw (name) % hash.size; hash.tab[i].name;)
+    {
+      if (!strcmp (hash.tab[i].name, name))
+       return hash.tab[i].index;
+      if (++i >= hash.size)
+       i = 0;
+    }
+
+  hash.used++;
+  if (hash.used >= hash.max_used)
+    {
+      struct index_hash *old_tab = hash.tab;
+      int old_size = hash.size;
+      int i, j;
+
+      hash.size = *hash.size_p++;
+      hash.max_used = hash.size / 2;
+      hash.tab = xmalloc (sizeof *hash.tab * hash.size);
+      for (i = 0; i < hash.size; i++)
+       hash.tab[i].name = NULL;
+      for (i = 0; i < old_size; i++)
+       if (old_tab[i].name)
+         {
+           for (j = hashpjw (old_tab[i].name) % hash.size; hash.tab[j].name;)
+             if (++j >= hash.size)
+               j = 0;
+           hash.tab[j] = old_tab[i];
+         }
+      free (old_tab);
+    }
+
+  hash.tab[i].name = pool_strdup (hash.ar, name);
+  hash.tab[i].index = hash.next_index;
+  return hash.next_index++;
+}
+
+/* Returns an index for a character that has only a code, not a
+   name. */
+int
+font_number_to_index (int x)
+{
+  char name[INT_DIGITS + 2];
+
+  /* Note that space is the only character that can't appear in a
+     character name.  That makes it an excellent choice for a name
+     that won't conflict. */
+  sprintf (name, " %d", x);
+  return font_char_name_to_index (name);
+}
+\f
+/* Font character metric entries. */
+
+/* Ensures room for at least MIN_SIZE metric indexes in deref of
+   FONT. */
+static void
+check_deref_space (struct font_desc *font, int min_size)
+{
+  if (min_size >= font->deref_size)
+    {
+      int i = font->deref_size;
+
+      font->deref_size = min_size + 16;
+      if (font->deref_size < 256)
+       font->deref_size = 256;
+      font->deref = pool_realloc (font->owner, font->deref,
+                                 sizeof *font->deref * font->deref_size);
+      for (; i < font->deref_size; i++)
+       font->deref[i] = -1;
+    }
+}
+
+/* Inserts METRICS for character with code CODE into FONT. */
+static void
+add_char_metric (struct font_desc *font, struct char_metrics *metrics, int code)
+{
+  check_deref_space (font, code);
+  if (font->metric_used >= font->metric_size)
+    {
+      font->metric_size += 64;
+      font->metric = pool_realloc (font->owner, font->metric,
+                                  sizeof *font->metric * font->metric_size);
+    }
+  font->metric[font->metric_used] = metrics;
+  font->deref[code] = font->metric_used++;
+}
+
+/* Copies metric in FONT from character with code SRC to character
+   with code DEST. */
+static void
+dup_char_metric (struct font_desc *font, int dest, int src)
+{
+  check_deref_space (font, dest);
+  assert (font->deref[src] != -1);
+  font->deref[dest] = font->deref[src];
+}
+\f
+/* Kerning. */
+
+/* Returns a hash value for characters with codes CH1 and CH2. */
+#define hash_kern(CH1, CH2)                    \
+       ((unsigned) (((CH1) << 16) ^ (CH2)))
+
+/* Adds an ADJUST-size kern to FONT between characters with codes CH1
+   and CH2. */
+static void
+add_kern (struct font_desc *font, int ch1, int ch2, int adjust)
+{
+  int i;
+
+  if (font->kern_used >= font->kern_max_used)
+    {
+      struct kern_pair *old_kern = font->kern;
+      int old_kern_size = font->kern_size;
+      int j;
+
+      font->kern_size = *font->kern_size_p++;
+      font->kern_max_used = font->kern_size / 2;
+      font->kern = pool_malloc (font->owner,
+                               sizeof *font->kern * font->kern_size);
+      for (i = 0; i < font->kern_size; i++)
+       font->kern[i].ch1 = -1;
+
+      for (i = 0; i < old_kern_size; i++)
+       {
+         if (old_kern[i].ch1 == -1)
+           continue;
+
+         j = hash_kern (old_kern[i].ch1, old_kern[i].ch2) % font->kern_size;
+         while (font->kern[j].ch1 != -1)
+           if (0 == j--)
+             j = font->kern_size - 1;
+         font->kern[j] = old_kern[i];
+       }
+      if (old_kern)
+       pool_free (font->owner, old_kern);
+    }
+
+  for (i = hash_kern (ch1, ch2) % font->kern_size; font->kern[i].ch1 != -1;)
+    if (0 == i--)
+      i = font->kern_size - 1;
+  font->kern[i].ch1 = ch1;
+  font->kern[i].ch2 = ch2;
+  font->kern[i].adjust = adjust;
+  font->kern_used++;
+}
+
+/* Finds a font file corresponding to font NAME for device DEV. */
+static char *
+find_font_file (const char *dev, const char *name)
+{
+  char *basename = xmalloc (3 + strlen (dev) + 1 + strlen (name) + 1);
+  char *cp;
+  char *filename;
+  char *path;
+
+  cp = stpcpy (basename, "dev");
+  cp = stpcpy (cp, dev);
+  *cp++ = DIR_SEPARATOR;
+  strcpy (cp, name);
+
+  /* Search order:
+     1. $STAT_GROFF_FONT_PATH
+     2. $GROFF_FONT_PATH
+     3. GROFF_FONT_PATH from pref.h
+     4. config_path
+   */
+  if ((path = getenv ("STAT_GROFF_FONT_PATH")) != NULL
+      && (filename = fn_search_path (basename, path, NULL)) != NULL)
+    goto win;
+
+  if ((path = getenv ("GROFF_FONT_PATH")) != NULL
+      && (filename = fn_search_path (basename, path, NULL)) != NULL)
+    goto win;
+
+  if ((filename = fn_search_path (basename, groff_font_path, NULL)) != NULL)
+    goto win;
+
+  if ((filename = fn_search_path (basename, config_path, NULL)) != NULL)
+    goto win;
+
+  msg (IE, _("Groff font error: Cannot find \"%s\"."), basename);
+
+win:
+  free (basename);
+  return filename;
+}
+
+/* Finds a font for device DEV with name NAME, reads it with
+   groff_read_font(), and returns the resultant font. */
+struct font_desc *
+groff_find_font (const char *dev, const char *name)
+{
+  char *filename = find_font_file (dev, name);
+  struct font_desc *fd;
+
+  if (!filename)
+    return NULL;
+  fd = groff_read_font (filename);
+  free (filename);
+  return fd;
+}
+
+/* Reads a DESC file for device DEV and sets the appropriate fields in
+   output driver *DRIVER, which must be previously allocated.  Returns
+   nonzero on success. */
+int
+groff_read_DESC (const char *dev_name, struct groff_device_info * dev)
+{
+  char *filename;              /* Full name of DESC file. */
+  FILE *f;                     /* DESC file. */
+
+  char *line = NULL;           /* Current line. */
+  int line_len;                        /* Number of chars in current line. */
+  size_t line_size = 0;                /* Number of chars allocated for line. */
+
+  char *token;                 /* strtok()'d token inside line. */
+
+  unsigned found = 0;          /* Bitmask showing what settings
+                                  have been encountered. */
+
+  int m_sizes = 0;             /* Number of int[2] items that
+                                  can fit in driver->sizes. */
+
+  char *sp;                    /* Tokenization string pointer. */
+  struct file_locator where;
+
+  int i;
+
+  dev->horiz = 1;
+  dev->vert = 1;
+  dev->size_scale = 1;
+  dev->n_sizes = 0;
+  dev->sizes = NULL;
+  dev->family = NULL;
+  for (i = 0; i < 4; i++)
+    dev->font_name[i] = NULL;
+
+  filename = find_font_file (dev_name, "DESC");
+  if (!filename)
+    return 0;
+
+  where.filename = filename;
+  where.line_number = 0;
+  err_push_file_locator (&where);
+
+  msg (VM (1), _("%s: Opening Groff description file..."), filename);
+  f = fopen (filename, "r");
+  if (!f)
+    goto file_lossage;
+
+  while ((line_len = getline (&line, &line_size, f)) != -1)
+    {
+      where.line_number++;
+
+      token = strtok_r (line, whitespace, &sp);
+      if (!token)
+       continue;
+
+      if (!strcmp (token, "sizes"))
+       {
+         if (found & 0x10000)
+           font_msg (SW, _("Multiple `sizes' declarations."));
+         for (;;)
+           {
+             char *tail;
+             int lower, upper;
+
+             for (;;)
+               {
+                 token = strtok_r (NULL, whitespace, &sp);
+                 if (token)
+                   break;
+
+                 where.line_number++;
+                 if ((line_len = getline (&line, &line_size, f)) != -1)
+                   {
+                     if (ferror (f))
+                       goto file_lossage;
+                     font_msg (SE, _("Unexpected end of file.  "
+                               "Missing 0 terminator to `sizes' command?"));
+                     goto lossage;
+                   }
+               }
+
+             if (!strcmp (token, "0"))
+               break;
+
+             errno = 0;
+             if (0 == (lower = strtol (token, &tail, 0)) || errno == ERANGE)
+               {
+                 font_msg (SE, _("Bad argument to `sizes'."));
+                 goto lossage;
+               }
+             if (*tail == '-')
+               {
+                 if (0 == (upper = strtol (&tail[1], &tail, 0)) || errno == ERANGE)
+                   {
+                     font_msg (SE, _("Bad argument to `sizes'."));
+                     goto lossage;
+                   }
+                 if (lower < upper)
+                   {
+                     font_msg (SE, _("Bad range in argument to `sizes'."));
+                     goto lossage;
+                   }
+               }
+             else
+               upper = lower;
+             if (*tail)
+               {
+                 font_msg (SE, _("Bad argument to `sizes'."));
+                 goto lossage;
+               }
+
+             if (dev->n_sizes + 2 >= m_sizes)
+               {
+                 m_sizes += 1;
+                 dev->sizes = xrealloc (dev->sizes,
+                                        m_sizes * sizeof *dev->sizes);
+               }
+             dev->sizes[dev->n_sizes++][0] = lower;
+             dev->sizes[dev->n_sizes][1] = upper;
+
+             found |= 0x10000;
+           }
+       }
+      else if (!strcmp (token, "family"))
+       {
+         token = strtok_r (NULL, whitespace, &sp);
+         if (!token)
+           {
+             font_msg (SE, _("Family name expected."));
+             goto lossage;
+           }
+         if (found & 0x20000)
+           {
+             font_msg (SE, _("This command already specified."));
+             goto lossage;
+           }
+         dev->family = xstrdup (token);
+       }
+      else if (!strcmp (token, "charset"))
+       break;
+      else
+       {
+         static const char *id[]
+           = {"res", "hor", "vert", "sizescale", "unitwidth", NULL};
+         const char **cp;
+         int value;
+
+         for (cp = id; *cp; cp++)
+           if (!strcmp (token, *cp))
+             break;
+         if (*cp == NULL)
+           continue;           /* completely ignore unrecognized lines */
+         if (found & (1 << (cp - id)))
+           font_msg (SW, _("%s: Device characteristic already defined."), *cp);
+
+         token = strtok_r (NULL, whitespace, &sp);
+         errno = 0;
+         if (!token || (value = strtol (token, NULL, 0)) <= 0 || errno == ERANGE)
+           {
+             font_msg (SE, _("%s: Invalid numeric format."), *cp);
+             goto lossage;
+           }
+         found |= (1 << (cp - id));
+         switch (cp - id)
+           {
+           case 0:
+             dev->res = value;
+             break;
+           case 1:
+             dev->horiz = value;
+             break;
+           case 2:
+             dev->vert = value;
+             break;
+           case 3:
+             dev->size_scale = value;
+             break;
+           case 4:
+             dev->unit_width = value;
+             break;
+           default:
+             assert (0);
+           }
+       }
+    }
+  if (ferror (f))
+    goto file_lossage;
+  if ((found & 0x10011) != 0x10011)
+    {
+      font_msg (SE, _("Missing `res', `unitwidth', and/or `sizes' line(s)."));
+      goto lossage;
+    }
+
+  /* Font name = family name + suffix. */
+  {
+    static const char *suffix[4] =
+      {"R", "I", "B", "BI"};   /* match OUTP_F_* */
+    int len;                   /* length of family name */
+    int i;
+
+    if (!dev->family)
+      dev->family = xstrdup ("");
+    len = strlen (dev->family);
+    for (i = 0; i < 4; i++)
+      {
+       char *cp;
+       dev->font_name[i] = xmalloc (len + strlen (suffix[i]) + 1);
+       cp = stpcpy (dev->font_name[i], dev->family);
+       strcpy (cp, suffix[i]);
+      }
+  }
+
+  dev->sizes[dev->n_sizes][0] = 0;
+  dev->sizes[dev->n_sizes][1] = 0;
+
+  msg (VM (2), _("Description file read successfully."));
+  
+  err_pop_file_locator (&where);
+  free (filename);
+  free (line);
+  return 1;
+
+  /* Come here on a file error. */
+file_lossage:
+  msg (ME, "%s: %s", filename, strerror (errno));
+
+  /* Come here on any error. */
+lossage:
+  fclose (f);
+  free (line);
+  free (dev->family);
+  dev->family = NULL;
+  free (filename);
+  free (dev->sizes);
+  dev->sizes = NULL;
+  dev->n_sizes = 0;
+#if 0                          /* at the moment, no errors can come here when dev->font_name[*] are
+                                  nonzero. */
+  for (i = 0; i < 4; i++)
+    {
+      free (dev->font_name[i]);
+      dev->font_name[i] = NULL;
+    }
+#endif
+
+  err_pop_file_locator (&where);
+  
+  msg (VM (1), _("Error reading description file."));
+  
+  return 0;
+}
+
+/* Finds character with index CH (as returned by name_to_index() or
+   number_to_index()) in font FONT and returns the associated metrics.
+   Nonexistent characters have width 0. */
+struct char_metrics *
+font_get_char_metrics (const struct font_desc *font, int ch)
+{
+  short index;
+
+  if (ch < 0 || ch >= font->deref_size)
+    return 0;
+
+  index = font->deref[ch];
+  if (index == -1)
+    return 0;
+
+  return font->metric[index];
+}
+
+/* Finds kernpair consisting of CH1 and CH2, in that order, in font
+   FONT and returns the associated kerning adjustment. */
+int
+font_get_kern_adjust (const struct font_desc *font, int ch1, int ch2)
+{
+  unsigned i;
+
+  if (!font->kern)
+    return 0;
+  for (i = hash_kern (ch1, ch2) % font->kern_size; font->kern[i].ch1 != -1;)
+    {
+      if (font->kern[i].ch1 == ch1 && font->kern[i].ch2 == ch2)
+       return font->kern[i].adjust;
+      if (0 == i--)
+       i = font->kern_size - 1;
+    }
+  return 0;
+}
+
+/* Returns a twelve-point fixed-pitch font that can be used as a
+   last-resort fallback. */
+struct font_desc *
+default_font (void)
+{
+  struct pool *font_pool;
+  static struct font_desc *font;
+
+  if (font)
+    return font;
+  font_pool = pool_create ();
+  font = pool_alloc (font_pool, sizeof *font);
+  font->owner = font_pool;
+  font->name = NULL;
+  font->internal_name = pool_strdup (font_pool, _("<<fallback>>"));
+  font->encoding = pool_strdup (font_pool, "text.enc");
+  font->space_width = 12000;
+  font->slant = 0.0;
+  font->ligatures = 0;
+  font->special = 0;
+  font->ascent = 8000;
+  font->descent = 4000;
+  font->deref = NULL;
+  font->deref_size = 0;
+  font->metric = NULL;
+  font->metric_size = 0;
+  font->metric_used = 0;
+  font->kern = NULL;
+  font->kern_size = 0;
+  font->kern_size_p = hsh_next_prime (64);
+  font->kern_used = 0;
+  font->kern_max_used = 0;
+  return font;
+}
diff --git a/src/hash.c b/src/hash.c
new file mode 100644 (file)
index 0000000..9ade76f
--- /dev/null
@@ -0,0 +1,344 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <limits.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "hash.h"
+
+/* Note for constructing hash functions:
+
+   You can store the hash values in the records, then compare hash
+   values (in the compare function) before bothering to compare keys.
+   Hash values can simply be returned from the records instead of
+   recalculating when rehashing. */
+
+/* Debugging note:
+
+   Since hash_probe and hash_find take void * pointers, it's easy to
+   pass a void ** to your data by accidentally inserting an `&'
+   reference operator where one shouldn't go.  It took me an hour to
+   hunt down a bug like that once. */
+\f
+/* Prime numbers and hash functions. */
+
+static int hsh_prime_tab[] =
+{
+  13, 31, 47, 67, 131, 257, 521, 1031, 2053, 4099, 8209, 16411,
+  32771, 65537, 131101, 262147, 524309, 1048583, 2097169, 4194319,
+  8388617, 16777259, 33554467, 67108879, 134217757, 268435459,
+  536870923, 1073741827, INT_MAX,
+};
+
+/* Returns pointer into hsh_prime_tab[], pointing to the first prime
+   in the table greater than X. */
+int *
+hsh_next_prime (int x)
+{
+  int *p;
+
+  assert (x >= 0);
+
+  for (p = hsh_prime_tab; *p < x; p++)
+    ;
+
+  assert (*p != INT_MAX);
+
+  return p;
+}
+
+/* P.J. Weinberger's hash function, recommended by the Red Dragon
+   Book.  Hashes the d-string between S1 and S2.  Returns unbounded
+   nonnegative result. */
+int
+hashpjw_d (const char *s1, const char *s2)
+{
+  const char *p;
+  unsigned g, h;
+
+  for (h = 0, p = s1; p < s2; p++)
+    {
+      h = (h << 4) + *(unsigned char *) p;
+      g = h & 0xf0000000;
+      h ^= (g >> 24) | g;
+    }
+  return abs ((int) h);
+}
+
+/* Alternate entry point for hashpjw_d() that takes an s-string. */
+int
+hashpjw (const char *s)
+{
+  return hashpjw_d (s, &s[strlen (s)]);
+}
+\f
+/*hash tables. */
+
+/* Creates a hash table with at least M entries.  COMPARE is a
+   function that compares two entries and returns 0 if they are
+   identical, nonzero otherwise; HASH returns a nonnegative hash value
+   for an entry; FREE destroys an entry. */
+struct hsh_table *
+hsh_create (int m,
+           int (*compare) (const void *, const void *, void *param),
+           unsigned (*hash) (const void *, void *param),
+           void (*free) (void *, void *param),
+           void *param)
+{
+  struct hsh_table *h = xmalloc (sizeof *h);
+  int i;
+
+  h->n = 0;
+  h->mp = hsh_next_prime (m);
+  h->m = *h->mp++;
+  h->table = xmalloc (sizeof *h->table * h->m);
+  for (i = 0; i < h->m; i++)
+    h->table[i] = NULL;
+  h->param = param;
+  h->compare = compare;
+  h->hash = hash;
+  h->free = free;
+  return h;
+}
+
+/* Destroys the contents of table H. */
+void
+hsh_clear (struct hsh_table *h)
+{
+  int i;
+
+  if (h->free)
+    for (i = 0; i < h->m; i++)
+      h->free (h->table[i], h->param);
+
+  if (h->m >= 128)
+    {
+      free (h->table);
+      h->mp = hsh_next_prime (31);
+      h->m = *h->mp++;
+      h->table = xmalloc (sizeof *h->table * h->m);
+    }
+
+  for (i = 0; i < h->m; i++)
+    h->table[i] = NULL;
+}
+
+/* Destroys table H and all its contents. */
+void
+hsh_destroy (struct hsh_table *h)
+{
+  int i;
+
+  if (h == NULL)
+    return;
+  if (h->free)
+    for (i = 0; i < h->m; i++)
+      {
+       void *p = h->table[i];
+       if (p)
+         h->free (p, h->param);
+      }
+  free (h->table);
+  free (h);
+}
+
+/* Increases the capacity of H. */
+void
+hsh_rehash (struct hsh_table *h)
+{
+  void **begin = h->table;
+  void **end = &h->table[h->m];
+  void **table_p;
+  int i;
+
+  h->m = *h->mp++;
+  h->table = xmalloc (sizeof *h->table * h->m);
+  for (i = 0; i < h->m; i++)
+    h->table[i] = NULL;
+  for (table_p = begin; table_p < end; table_p++)
+    {
+      void **entry;
+
+      if (*table_p == NULL)
+       continue;
+      entry = &h->table[h->hash (*table_p, h->param) % h->m];
+      while (*entry)
+       if (--entry < h->table)
+         entry = &h->table[h->m - 1];
+      *entry = *table_p;
+    }
+  free (begin);
+}
+
+/* Static variables for hsh_sort(). */
+static void *hsh_param;
+static int (*hsh_compare) (const void *, const void *, void *param);
+
+/* hsh_sort() helper function that ensures NULLs are sorted after the
+   rest of the table. */
+static int
+internal_comparison_fn (const void *pa, const void *pb)
+{
+  void *a = *(void **) pa;
+  void *b = *(void **) pb;
+  return a == NULL ? 1 : (b == NULL ? -1 : hsh_compare (a, b, hsh_param));
+}
+
+/* Sorts hash table H based on function COMPARE.  NULLs are sent to
+   the end of the table.  The resultant table is returned (it is
+   guaranteed to be NULL-terminated).  H should not be used again as a
+   hash table until and unless hsh_clear() called. */
+void **
+hsh_sort (struct hsh_table *h,
+         int (*compare) (const void *, const void *, void *param))
+{
+#if GLOBAL_DEBUGGING
+  static int reentrant;
+  if (reentrant)
+    abort ();
+  reentrant++;
+#endif
+  hsh_param = h->param;
+  hsh_compare = compare ? compare : h->compare;
+  qsort (h->table, h->m, sizeof *h->table, internal_comparison_fn);
+#if GLOBAL_DEBUGGING
+  reentrant--;
+#endif
+  return h->table;
+}
+\f
+/* Hash entries. */
+
+/* Searches hash table H for TARGET.  If found, returns a pointer to a
+   pointer to that entry; otherwise returns a pointer to a NULL entry
+   which _must_ be used to insert a new entry having the same key
+   data.  */
+inline void **
+hsh_probe (struct hsh_table *h, const void *target)
+{
+  void **entry;
+
+  /* Order of these statements is important! */
+  if (h->n > h->m / 2)
+    hsh_rehash (h);
+  entry = &h->table[h->hash (target, h->param) % h->m];
+
+  while (*entry)
+    {
+      if (!h->compare (*entry, target, h->param))
+       return entry;
+
+      if (--entry < h->table)
+       entry = &h->table[h->m - 1];
+    }
+  h->n++;
+  return entry;
+}
+
+/* Returns the entry in hash table H that matches TARGET, NULL if
+   there is none. */
+void *
+hsh_find (struct hsh_table *h, const void *target)
+{
+  void **entry = &h->table[h->hash (target, h->param) % h->m];
+
+  while (*entry)
+    {
+      if (!h->compare (*entry, target, h->param))
+       return *entry;
+      if (--entry < h->table)
+       entry = &h->table[h->m - 1];
+    }
+  return NULL;
+}
+
+/* Iterates throught hash table TABLE with iterator ITER.  Returns the
+   next non-NULL entry in TABLE, or NULL after the last non-NULL
+   entry.  After NULL is returned, ITER is returned to a condition in
+   which hsh_foreach() will return the first non-NULL entry if any on
+   the next call.  Do not add entries to TABLE between call to
+   hsh_foreach() between NULL returns.
+
+   Before calling hsh_foreach with a particular iterator for the first
+   time, you must initialize the iterator with a call to
+   hsh_iterator_init.  */
+void *
+hsh_foreach (struct hsh_table *table, struct hsh_iterator *iter)
+{
+  int i;
+
+  if (!table)
+    return NULL;
+  if (!iter->init)
+    {
+      iter->init = 1;
+      iter->next = 0;
+    }
+  for (i = iter->next; i < table->m; i++)
+    if (table->table[i])
+      {
+       iter->next = i + 1;
+       return table->table[i];
+      }
+  iter->init = 0;
+  return NULL;
+}
+
+#if GLOBAL_DEBUGGING
+#include <stdio.h>
+
+/* Displays contents of hash table H on stdout. */
+void
+hsh_dump (struct hsh_table *h)
+{
+  void **entry = h->table;
+  int i;
+
+  printf (_("hash table:"));
+  for (i = 0; i < h->m; i++)
+    printf (" %p", *entry++);
+  printf ("\n");
+}
+
+/* This wrapper around hsh_probe() assures that it returns a pointer
+   to a NULL pointer.  This function is used when it is known that the
+   entry to be inserted does not already exist in the table. */
+void
+force_hsh_insert (struct hsh_table *h, void *p)
+{
+  void **pp = hsh_probe (h, p);
+  if (*pp != NULL)
+    assert (0);
+  *pp = p;
+}
+
+/* This wrapper around hsh_find() assures that it returns non-NULL.
+   This function is for use when it is known that the entry being
+   searched for must exist in the table. */
+void *
+force_hsh_find (struct hsh_table *h, const void *p)
+{
+  p = hsh_find (h, p);
+  if (p == NULL)
+    assert (0);
+  return (void *) p;
+}
+#endif
diff --git a/src/hash.h b/src/hash.h
new file mode 100644 (file)
index 0000000..048d2f2
--- /dev/null
@@ -0,0 +1,95 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !hash_h
+#define hash_h 1
+
+/* Hash table (opaque). */
+struct hsh_table
+  {
+    int n;                     /* Number of filled entries. */
+    int m;                     /* Number of entries. */
+    int *mp;                   /* Pointer into hsh_prime_tab[]. */
+    void **table;              /* Hash table proper. */
+
+    void *param;
+    int (*compare) (const void *, const void *, void *param);
+    unsigned (*hash) (const void *, void *param);
+    void (*free) (void *, void *param);
+  };
+
+/* Hash table iterator (opaque). */
+struct hsh_iterator
+  {
+    int init;                  /* Initialized? */
+    int next;                  /* Index of next entry. */
+  };
+
+#define hsh_iterator_init(ITERATOR) (ITERATOR).init = 0
+
+/* Prime numbers and hash functions. */
+int *hsh_next_prime (int) __attribute__ ((const));
+int hashpjw_d (const char *s1, const char *s2);
+
+#if __GNUC__>=2 && __OPTIMIZE__
+extern inline int
+hashpjw (const char *s)
+{
+  return hashpjw_d (s, &s[strlen (s)]);
+}
+#else
+int hashpjw (const char *s);
+#endif
+
+/* Hash tables. */
+struct hsh_table *hsh_create (int m,
+                             int (*compare) (const void *, const void *,
+                                             void *param),
+                             unsigned (*hash) (const void *, void *param),
+                             void (*free) (void *, void *param),
+                             void *param);
+void hsh_clear (struct hsh_table *);
+void hsh_destroy (struct hsh_table *);
+void hsh_rehash (struct hsh_table *);
+void **hsh_sort (struct hsh_table *,
+                int (*compare) (const void *, const void *, void *param));
+#if GLOBAL_DEBUGGING
+void hsh_dump (struct hsh_table *);
+#endif
+
+/* Hash entries. */
+void **hsh_probe (struct hsh_table *, const void *);
+void *hsh_find (struct hsh_table *, const void *);
+void *hsh_foreach (struct hsh_table *, struct hsh_iterator *);
+
+#if GLOBAL_DEBUGGING
+void force_hsh_insert (struct hsh_table *, void *);
+void *force_hsh_find (struct hsh_table *, const void *);
+#else
+#define force_hsh_insert(A, B)                 \
+       do *hsh_probe (A, B) = B; while (0)
+#define force_hsh_find(A, B)                   \
+       hsh_find (A, B)
+#endif
+
+/* Returns number of used elements in hash table H. */
+#define hsh_count(H)                           \
+       ((H)->n)
+
+#endif /* hash_h */
diff --git a/src/heap.c b/src/heap.c
new file mode 100644 (file)
index 0000000..a90aefd
--- /dev/null
@@ -0,0 +1,269 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include "heap.h"
+
+#if STANDALONE
+#define GLOBAL_DEBUGGING 1
+#define _(x) (x)
+#endif
+
+/* Creates and returns a heap with an initial capacity of M_ELEM
+   elements.  Returns nonzero only if successful. */
+struct heap *
+heap_create (size_t m_elem)
+{
+  struct heap *h = malloc (sizeof *h);
+  if (h != NULL)
+    {
+      h->n_elem = 0;
+      h->m_elem = m_elem;
+      h->elem = malloc (h->m_elem * sizeof *h->elem);
+      if (h->elem == NULL)
+       {
+         free (h);
+         h = NULL;
+       }
+    }
+  return h;
+}
+
+/* Destroys the heap at *H. */
+void
+heap_destroy (struct heap *h)
+{
+  assert (h != NULL);
+  free (h->elem);
+  free (h);
+}
+
+/* Inserts into heap *H an element having index INDEX and key KEY.
+   Returns nonzero only if successful. */
+int
+heap_insert (struct heap *h, int index, int key)
+{
+  int i, j;
+
+  assert (h != NULL);
+  if (h->n_elem >= h->m_elem)
+    {
+      h->elem = realloc (h->elem, 2 * h->m_elem * sizeof *h->elem);
+      if (h->elem == NULL)
+       return 0;
+      h->m_elem *= 2;
+    }
+
+  /* Knuth's Algorithm 5.2.3-16.  Step 1. */
+  j = h->n_elem + 1;
+
+  for (;;)
+    {
+      /* Step 2. */
+      i = j / 2;
+
+      /* Step 3. */
+      if (i == 0 || h->elem[i - 1].key <= key)
+       {
+         h->elem[j - 1].index = index;
+         h->elem[j - 1].key = key;
+         h->n_elem++;
+         return 1;
+       }
+
+      /* Step 4. */
+      h->elem[j - 1] = h->elem[i - 1];
+      j = i;
+    }
+}
+
+/* Deletes the first element in the heap (the one with the greatest
+   index) and returns its index, or -1 if the heap is empty.  If KEY
+   is non-NULL then *KEY is set to the deleted element's key, if it
+   returns non-NULL. */
+int
+heap_delete (struct heap *h, int *key)
+{
+  /* Knuth's Algorithm 5.2.3H-19. */
+  int first, K, R, l, r, i, j;
+
+  if (h->n_elem == 0)
+    return -1;
+  first = h->elem[0].index;
+  if (key)
+    *key = h->elem[0].key;
+  K = h->elem[h->n_elem - 1].key;
+  R = h->elem[h->n_elem - 1].index;
+  l = 1;
+  r = h->n_elem - 1;
+
+  /* H3. */
+  j = 1;
+
+H4:
+  i = j;
+  j *= 2;
+  if (j == r)
+    goto H6;
+  else if (j > r)
+    goto H8;
+
+  /* H5. */
+  if (h->elem[j - 1].key > h->elem[j].key)
+    j++;
+
+H6:
+  if (K <= h->elem[j - 1].key)
+    goto H8;
+
+  /* H7. */
+  h->elem[i - 1] = h->elem[j - 1];
+  goto H4;
+
+H8:
+  h->elem[i - 1].key = K;
+  h->elem[i - 1].index = R;
+
+  h->n_elem--;
+  return first;
+}
+
+/* Returns the number of elements in heap H. */
+int
+heap_size (struct heap *h)
+{
+  return h->n_elem;
+}
+
+#if GLOBAL_DEBUGGING
+/* Checks that a heap is really a heap. */
+void
+heap_verify (const struct heap *h)
+{
+  size_t j;
+
+  for (j = 1; j <= h->n_elem; j++)
+    {
+      if (j / 2 >= 1 && h->elem[j / 2 - 1].key > h->elem[j - 1].key)
+       printf (_("bad ordering of keys %d and %d\n"), j / 2 - 1, j - 1);
+    }
+}
+
+/* Dumps out the heap on stdout. */
+void
+heap_dump (const struct heap *h)
+{
+  size_t j;
+
+  printf (_("Heap contents:\n"));
+  for (j = 1; j <= h->n_elem; j++)
+    {
+      int partner;
+      if (j / 2 >= 1)
+       partner = h->elem[j / 2 - 1].key;
+      else
+       partner = -1;
+      printf ("%6d-%5d", h->elem[j - 1].key, partner);
+    }
+}
+#endif /* GLOBAL_DEBUGGING */
+
+#if STANDALONE
+#include <time.h>
+
+/* To perform a fairly thorough test of the heap routines, define
+   STANDALONE to nonzero then compile this file by itself. */
+
+/* Compares the second elements of the integer arrays at _A and _B and
+   returns a strcmp()-type result. */
+int
+compare_int2 (const void *pa, const void *pb)
+{
+  int *a = (int *) pa;
+  int *b = (int *) pb;
+
+  return a[1] - b[1];
+}
+
+#define N_ELEM 16
+
+/* Arrange the N elements of ARRAY in random order. */
+void
+shuffle (int (*array)[2], int n)
+{
+  int i;
+  
+  for (i = 0; i < n; i++)
+    {
+      int j = i + rand () % (n - i);
+      int t = array[j][0], s = array[j][1];
+      array[j][0] = array[i][0], array[j][1] = array[i][1];
+      array[i][0] = t, array[i][1] = s;
+    }
+}
+
+/* Test routine. */
+int
+main (void)
+{
+  struct heap *h;
+  int i;
+  int array[N_ELEM][2];
+
+  srand (time (0));
+
+  h = heap_create (16);
+  for (i = 0; i < N_ELEM; i++)
+    {
+      array[i][0] = i;
+      array[i][1] = N_ELEM - i - 1;
+    }
+  shuffle (array, N_ELEM);
+
+  printf ("Insertion order:\n");
+  for (i = 0; i < N_ELEM; i++)
+    {
+      printf ("(%d,%d) ", array[i][0], array[i][1]);
+      heap_insert (h, array[i][0], array[i][1]);
+      heap_verify (h);
+    }
+  putchar ('\n');
+
+  /*heap_dump(&h); */
+
+  printf ("\nDeletion order:\n");
+  for (i = 0; i < N_ELEM; i++)
+    {
+      int index, key;
+      index = heap_delete (h, &key);
+      assert (index != -1);
+      printf ("(%d,%d) ", index, key);
+      fflush (stdout);
+      assert (index == N_ELEM - i - 1 && key == i);
+      heap_verify (h);
+    }
+  putchar ('\n');
+  heap_destroy (h);
+
+  return 0;
+}
+#endif
diff --git a/src/heap.h b/src/heap.h
new file mode 100644 (file)
index 0000000..7644356
--- /dev/null
@@ -0,0 +1,52 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !heap_h
+#define heap_h 1
+
+/* This module implements a priority queue as a heap as described in
+   Knuth 5.2.3.  This is a first-in-smallest-out priority queue. */
+
+/* One element of a heap. */
+struct heap_elem
+  {
+    int index;                 /* Data. */
+    int key;                   /* Key value. */
+  };
+
+/* An entire heap. */
+struct heap
+  {
+    size_t n_elem;             /* Number of elements in heap. */
+    size_t m_elem;             /* Number of elements allocated for heap. */
+    struct heap_elem *elem;    /* Heap elements. */
+  };
+
+struct heap *heap_create (size_t m_elem);
+void heap_destroy (struct heap *);
+int heap_insert (struct heap *, int index, int key);
+int heap_delete (struct heap *, int *key);
+int heap_size (struct heap *);
+
+#if GLOBAL_DEBUGGING
+void heap_verify (const struct heap *);
+void heap_dump (const struct heap *);
+#endif
+
+#endif /* heap_h */
diff --git a/src/html.c b/src/html.c
new file mode 100644 (file)
index 0000000..c647f34
--- /dev/null
@@ -0,0 +1,623 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* This #if encloses the rest of the file. */
+#if !NO_HTML
+
+#include <config.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <time.h>
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "alloc.h"
+#include "error.h"
+#include "filename.h"
+#include "getline.h"
+#include "htmlP.h"
+#include "output.h"
+#include "som.h"
+#include "tab.h"
+#include "version.h"
+
+/* Prototypes. */
+static int postopen (struct file_ext *);
+static int preclose (struct file_ext *);
+
+int
+html_open_global (struct outp_class *this unused)
+{
+  return 1;
+}
+
+int
+html_close_global (struct outp_class *this unused)
+{
+  return 1;
+}
+
+int
+html_preopen_driver (struct outp_driver *this)
+{
+  struct html_driver_ext *x;
+
+  assert (this->driver_open == 0);
+  msg (VM (1), _("HTML driver initializing as `%s'..."), this->name);
+
+  this->ext = x = xmalloc (sizeof *x);
+  this->res = 0;
+  this->horiz = this->vert = 0;
+  this->width = this->length = 0;
+
+  this->cp_x = this->cp_y = 0;
+
+  x->prologue_fn = NULL;
+
+  x->file.filename = NULL;
+  x->file.mode = "w";
+  x->file.file = NULL;
+  x->file.sequence_no = &x->sequence_no;
+  x->file.param = this;
+  x->file.postopen = postopen;
+  x->file.preclose = preclose;
+
+  x->sequence_no = 0;
+
+  return 1;
+}
+
+int
+html_postopen_driver (struct outp_driver *this)
+{
+  struct html_driver_ext *x = this->ext;
+
+  assert (this->driver_open == 0);
+  if (NULL == x->file.filename)
+    x->file.filename = xstrdup ("pspp.html");
+       
+  if (x->prologue_fn == NULL)
+    x->prologue_fn = xstrdup ("html-prologue");
+
+  msg (VM (2), _("%s: Initialization complete."), this->name);
+  this->driver_open = 1;
+
+  return 1;
+}
+
+int
+html_close_driver (struct outp_driver *this)
+{
+  struct html_driver_ext *x = this->ext;
+
+  assert (this->driver_open);
+  msg (VM (2), _("%s: Beginning closing..."), this->name);
+  fn_close_ext (&x->file);
+  free (x->prologue_fn);
+  free (x->file.filename);
+  free (x);
+  msg (VM (3), _("%s: Finished closing."), this->name);
+  this->driver_open = 0;
+  
+  return 1;
+}
+
+/* Generic option types. */
+enum
+{
+  boolean_arg = -10,
+  string_arg,
+  nonneg_int_arg
+};
+
+/* All the options that the HTML driver supports. */
+static struct outp_option option_tab[] =
+{
+  /* *INDENT-OFF* */
+  {"output-file",              1,              0},
+  {"prologue-file",            string_arg,     0},
+  {"", 0, 0},
+  /* *INDENT-ON* */
+};
+static struct outp_option_info option_info;
+
+void
+html_option (struct outp_driver *this, const char *key, const struct string *val)
+{
+  struct html_driver_ext *x = this->ext;
+  int cat, subcat;
+
+  cat = outp_match_keyword (key, option_tab, &option_info, &subcat);
+  switch (cat)
+    {
+    case 0:
+      msg (SE, _("Unknown configuration parameter `%s' for HTML device "
+          "driver."), key);
+      break;
+    case 1:
+      free (x->file.filename);
+      x->file.filename = xstrdup (ds_value (val));
+      break;
+    case string_arg:
+      {
+       char **dest;
+       switch (subcat)
+         {
+         case 0:
+           dest = &x->prologue_fn;
+           break;
+         default:
+           assert (0);
+         }
+       if (*dest)
+         free (*dest);
+       *dest = xstrdup (ds_value (val));
+      }
+      break;
+#if __CHECKER__
+    case 42000:
+      assert (0);
+#endif
+    default:
+      assert (0);
+    }
+}
+
+/* Variables for the prologue. */
+struct html_variable
+  {
+    const char *key;
+    const char *value;
+  };
+  
+static struct html_variable *html_var_tab;
+
+/* Searches html_var_tab for a html_variable with key KEY, and returns
+   the associated value. */
+static const char *
+html_get_var (const char *key)
+{
+  struct html_variable *v;
+
+  for (v = html_var_tab; v->key; v++)
+    if (!strcmp (key, v->key))
+      return v->value;
+  return NULL;
+}
+
+/* Writes the HTML prologue to file F. */
+static int
+postopen (struct file_ext *f)
+{
+  static struct html_variable dict[] =
+    {
+      {"generator", 0},
+      {"date", 0},
+      {"user", 0},
+      {"host", 0},
+      {"title", 0},
+      {"subtitle", 0},
+      {"source-file", 0},
+      {0, 0},
+    };
+#if HAVE_UNISTD_H
+  char host[128];
+#endif
+  time_t curtime;
+  struct tm *loctime;
+
+  struct outp_driver *this = f->param;
+  struct html_driver_ext *x = this->ext;
+
+  char *prologue_fn = fn_search_path (x->prologue_fn, config_path, NULL);
+  FILE *prologue_file;
+
+  char *buf = NULL;
+  int buf_size = 0;
+
+  if (prologue_fn == NULL)
+    {
+      msg (IE, _("Cannot find HTML prologue.  The use of `-vv' "
+                "on the command line is suggested as a debugging aid."));
+      return 0;
+    }
+
+  msg (VM (1), _("%s: %s: Opening HTML prologue..."), this->name, prologue_fn);
+  prologue_file = fopen (prologue_fn, "rb");
+  if (prologue_file == NULL)
+    {
+      fclose (prologue_file);
+      free (prologue_fn);
+      msg (IE, "%s: %s", prologue_fn, strerror (errno));
+      goto error;
+    }
+
+  dict[0].value = version;
+
+  curtime = time (NULL);
+  loctime = localtime (&curtime);
+  dict[1].value = asctime (loctime);
+  {
+    char *cp = strchr (dict[1].value, '\n');
+    if (cp)
+      *cp = 0;
+  }
+
+  /* PORTME: Determine username, net address. */
+#if HAVE_UNISTD_H
+  dict[2].value = getenv ("LOGNAME");
+  if (!dict[2].value)
+    dict[2].value = getlogin ();
+  if (!dict[2].value)
+    dict[2].value = _("nobody");
+
+  if (gethostname (host, 128) == -1)
+    {
+      if (errno == ENAMETOOLONG)
+       host[127] = 0;
+      else
+       strcpy (host, _("nowhere"));
+    }
+  dict[3].value = host;
+#else /* !HAVE_UNISTD_H */
+  dict[2].value = _("nobody");
+  dict[3].value = _("nowhere");
+#endif /* !HAVE_UNISTD_H */
+
+  dict[4].value = outp_title ? outp_title : "";
+  dict[5].value = outp_subtitle ? outp_subtitle : "";
+
+  getl_location (&dict[6].value, NULL);
+  if (dict[6].value == NULL)
+    dict[6].value = "<stdin>";
+
+  html_var_tab = dict;
+  while (-1 != getline (&buf, &buf_size, prologue_file))
+    {
+      char *buf2;
+      int len;
+
+      if (strstr (buf, "!!!"))
+       continue;
+      
+      {
+       char *cp = strstr (buf, "!title");
+       if (cp)
+         {
+           if (outp_title == NULL)
+             continue;
+           else
+             *cp = '\0';
+         }
+      }
+      
+      {
+       char *cp = strstr (buf, "!subtitle");
+       if (cp)
+         {
+           if (outp_subtitle == NULL)
+             continue;
+           else
+             *cp = '\0';
+         }
+      }
+      
+      /* PORTME: Line terminator. */
+      buf2 = fn_interp_vars (buf, html_get_var);
+      len = strlen (buf2);
+      fwrite (buf2, len, 1, f->file);
+      if (buf2[len - 1] != '\n')
+       putc ('\n', f->file);
+      free (buf2);
+    }
+  if (ferror (f->file))
+    msg (IE, _("Reading `%s': %s."), prologue_fn, strerror (errno));
+  fclose (prologue_file);
+
+  free (prologue_fn);
+  free (buf);
+
+  if (ferror (f->file))
+    goto error;
+
+  msg (VM (2), _("%s: HTML prologue read successfully."), this->name);
+  return 1;
+
+error:
+  msg (VM (1), _("%s: Error reading HTML prologue."), this->name);
+  return 0;
+}
+
+/* Writes the HTML epilogue to file F. */
+static int
+preclose (struct file_ext *f)
+{
+  fprintf (f->file,
+          "</BODY>\n"
+          "</HTML>\n"
+          "<!-- end of file -->\n");
+
+  if (ferror (f->file))
+    return 0;
+  return 1;
+}
+
+int
+html_open_page (struct outp_driver *this)
+{
+  struct html_driver_ext *x = this->ext;
+
+  assert (this->driver_open && this->page_open == 0);
+  x->sequence_no++;
+  if (!fn_open_ext (&x->file))
+    {
+      if (errno)
+       msg (ME, _("HTML output driver: %s: %s"), x->file.filename,
+            strerror (errno));
+      return 0;
+    }
+
+  if (!ferror (x->file.file))
+    this->page_open = 1;
+  return !ferror (x->file.file);
+}
+
+int
+html_close_page (struct outp_driver *this)
+{
+  struct html_driver_ext *x = this->ext;
+
+  assert (this->driver_open && this->page_open);
+  this->page_open = 0;
+  return !ferror (x->file.file);
+}
+
+static void output_tab_table (struct outp_driver *, struct tab_table *);
+
+void
+html_submit (struct outp_driver *this, struct som_table *s)
+{
+  extern struct som_table_class tab_table_class;
+  struct html_driver_ext *x = this->ext;
+  
+  assert (this->driver_open && this->page_open);
+  if (x->sequence_no == 0 && !html_open_page (this))
+    {
+      msg (ME, _("Cannot open first page on HTML device %s."), this->name);
+      return;
+    }
+
+  if (s->class == &tab_table_class)
+    output_tab_table (this, (struct tab_table *) s->ext);
+  else
+    assert (0);
+}
+
+/* Emit HTML to FILE to change from *OLD_ATTR attributes to NEW_ATTR.
+   Sets *OLD_ATTR to NEW_ATTR when done. */
+static void
+change_attributes (FILE *f, int *old_attr, int new_attr)
+{
+  if (*old_attr == new_attr)
+    return;
+
+  if (*old_attr & OUTP_F_B)
+    fputs ("</B>", f);
+  if (*old_attr & OUTP_F_I)
+    fputs ("</I>", f);
+  if (new_attr & OUTP_F_I)
+    fputs ("<I>", f);
+  if (new_attr & OUTP_F_B)
+    fputs ("<B>", f);
+
+  *old_attr = new_attr;
+}
+
+/* Write string S of length LEN to file F, escaping characters as
+   necessary for HTML. */
+static void
+escape_string (FILE *f, char *s, int len)
+{
+  char *ep = &s[len];
+  char *bp, *cp;
+  int attr = 0;
+
+  for (bp = cp = s; bp < ep; bp = cp)
+    {
+      while (cp < ep && *cp != '&' && *cp != '<' && *cp != '>' && *cp)
+       cp++;
+      if (cp > bp)
+       fwrite (bp, 1, cp - bp, f);
+      if (cp < ep)
+       switch (*cp++)
+         {
+         case '&':
+           fputs ("&amp;", f);
+           break;
+         case '<':
+           fputs ("&lt;", f);
+           break;
+         case '>':
+           fputs ("&gt;", f);
+           break;
+         case 0:
+           break;
+         default:
+           assert (0);
+         }
+    }
+
+  if (attr)
+    change_attributes (f, &attr, 0);
+}
+  
+/* Write table T to THIS output driver. */
+static void
+output_tab_table (struct outp_driver *this, struct tab_table *t)
+{
+  struct html_driver_ext *x = this->ext;
+  
+  tab_hit++;
+
+  if (t->nr == 1 && t->nc == 1)
+    {
+      fputs ("<P>", x->file.file);
+      if (!ls_empty_p (t->cc))
+       escape_string (x->file.file, ls_value (t->cc), ls_length (t->cc));
+      fputs ("</P>\n", x->file.file);
+      
+      return;
+    }
+
+  fputs ("<TABLE BORDER=1>\n", x->file.file);
+  
+  if (!ls_empty_p (&t->title))
+    {
+      fprintf (x->file.file, "  <TR>\n    <TH COLSPAN=%d>", t->nc);
+      escape_string (x->file.file, ls_value (&t->title),
+                    ls_length (&t->title));
+      fputs ("</TH>\n  </TR>\n", x->file.file);
+    }
+  
+  {
+    int r;
+    struct len_string *cc = t->cc;
+    unsigned char *ct = t->ct;
+
+    for (r = 0; r < t->nr; r++)
+      {
+       int c;
+       
+       fputs ("  <TR>\n", x->file.file);
+       for (c = 0; c < t->nc; c++, cc++, ct++)
+         {
+           int tag;
+           char header[128];
+           char *cp;
+
+           if ((*ct & TAB_JOIN)
+               && ((struct tab_joined_cell *) ls_value (cc))->hit == tab_hit)
+             continue;
+
+           if (r < t->t || r >= t->nr - t->b
+               || c < t->l || c >= t->nc - t->r)
+             tag = 'H';
+           else
+             tag = 'D';
+           cp = stpcpy (header, "    <T");
+           *cp++ = tag;
+           
+           switch (*ct & TAB_ALIGN_MASK)
+             {
+             case TAB_RIGHT:
+               cp = stpcpy (cp, " ALIGN=RIGHT");
+               break;
+             case TAB_LEFT:
+               break;
+             case TAB_CENTER:
+               cp = stpcpy (cp, " ALIGN=CENTER");
+               break;
+             default:
+               assert (0);
+             }
+
+           if (*ct & TAB_JOIN)
+             {
+               struct tab_joined_cell *j =
+                 (struct tab_joined_cell *) ls_value (cc);
+               j->hit = tab_hit;
+               
+               if (j->x2 - j->x1 > 1)
+                 cp = spprintf (cp, " COLSPAN=%d", j->x2 - j->x1);
+               if (j->y2 - j->y1 > 1)
+                 cp = spprintf (cp, " ROWSPAN=%d", j->y2 - j->y1);
+             }
+           
+           strcpy (cp, ">");
+           fputs (header, x->file.file);
+           
+           {
+             char *s = ls_value (cc);
+             size_t l = ls_length (cc);
+
+             while (l && isspace ((unsigned char) *s))
+               {
+                 l--;
+                 s++;
+               }
+             
+             escape_string (x->file.file, s, l);
+           }
+
+           fprintf (x->file.file, "</T%c>\n", tag);
+         }
+       fputs ("  </TR>\n", x->file.file);
+      }
+  }
+             
+  fputs ("</TABLE>\n\n", x->file.file);
+}
+
+/* HTML driver class. */
+struct outp_class html_class =
+{
+  "html",
+  0xfaeb,
+  1,
+
+  html_open_global,
+  html_close_global,
+  NULL,
+
+  html_preopen_driver,
+  html_option,
+  html_postopen_driver,
+  html_close_driver,
+
+  html_open_page,
+  html_close_page,
+
+  html_submit,
+
+  NULL,
+  NULL,
+  NULL,
+
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+};
+
+#endif /* !NO_HTML */
+
diff --git a/src/htmlP.h b/src/htmlP.h
new file mode 100644 (file)
index 0000000..28416fd
--- /dev/null
@@ -0,0 +1,38 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !htmlP_h
+#define htmlP_h 1
+
+#include "filename.h"
+
+/* HTML output driver extension record. */
+struct html_driver_ext
+  {
+    /* User parameters. */
+    char *prologue_fn;         /* Prologue's filename relative to font dir. */
+
+    /* Internal state. */
+    struct file_ext file;      /* Output file. */
+    int sequence_no;           /* Sequence number. */
+  };
+
+extern struct outp_class html_class;
+
+#endif /* !htmlP_h */
diff --git a/src/include.c b/src/include.c
new file mode 100644 (file)
index 0000000..78557bd
--- /dev/null
@@ -0,0 +1,76 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "error.h"
+#include "getline.h"
+#include "lexer.h"
+#include "str.h"
+
+int
+cmd_include_at (void)
+{
+  char *incfn, *s, *bp, *ep;
+
+  s = bp = lex_entire_line ();
+  while (isspace ((unsigned char) *bp))
+    bp++;
+  bp++;                                /* skip `@' */
+  while (isspace ((unsigned char) *bp))
+    bp++;
+  if (*bp == '\'')
+    bp++;
+
+  ep = bp + strlen (bp);
+  while (isspace ((unsigned char) *--ep));
+  if (*ep != '\'')
+    ep++;
+
+  if (ep <= bp)
+    {
+      msg (SE, _("Unrecognized filename format."));
+      return CMD_FAILURE;
+    }
+
+  /* Now the filename is trapped between bp and ep. */
+  incfn = xmalloc (ep - bp + 1);
+  strncpy (incfn, bp, ep - bp);
+  incfn[ep - bp] = 0;
+  getl_include (incfn);
+  free (incfn);
+
+  return CMD_SUCCESS;
+}
+
+int
+cmd_include (void)
+{
+  lex_get ();
+
+  if (!lex_force_string ())
+    return CMD_SUCCESS;
+  getl_include (ds_value (&tokstr));
+
+  lex_get ();
+  return lex_end_of_command ();
+}
diff --git a/src/inpt-pgm.c b/src/inpt-pgm.c
new file mode 100644 (file)
index 0000000..a4e9a18
--- /dev/null
@@ -0,0 +1,465 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <float.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "dfm.h"
+#include "error.h"
+#include "expr.h"
+#include "file-handle.h"
+#include "inpt-pgm.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+#include "var.h"
+#include "vfm.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* A bit-vector of two-bit entries.  The array tells INPUT PROGRAM how
+   to initialize each `value'.  Modified by envector(), devector(),
+   which are called by create_variable(), also by LEAVE, COMPUTE(!).  */
+unsigned char *inp_init;
+
+/* Number of bytes allocated for inp_init. */
+size_t inp_init_size;
+
+/* Number of `values' created inside INPUT PROGRAM. */
+static int inp_nval;
+
+static int end_case_trns_proc (struct trns_header *, struct ccase *);
+static int end_file_trns_proc (struct trns_header * t, struct ccase * c);
+static int reread_trns_proc (struct trns_header *, struct ccase *);
+static void reread_trns_free (struct trns_header *);
+
+int
+cmd_input_program (void)
+{
+  lex_match_id ("INPUT");
+  lex_match_id ("PROGRAM");
+  discard_variables ();
+
+  vfm_source = &input_program_source;
+
+  inp_init = NULL;
+  inp_init_size = 0;
+
+  return lex_end_of_command ();
+}
+
+int
+cmd_end_input_program (void)
+{
+  lex_match_id ("END");
+  lex_match_id ("INPUT");
+  lex_match_id ("PROGRAM");
+
+  if (vfm_source != &input_program_source)
+    {
+      msg (SE, _("No matching INPUT PROGRAM command."));
+      return CMD_FAILURE;
+    }
+  
+  if (default_dict.nval == 0)
+    msg (SW, _("No data-input or transformation commands specified "
+        "between INPUT PROGRAM and END INPUT PROGRAM."));
+
+  /* Mark the boundary between INPUT PROGRAM and more-mundane
+     transformations. */
+  f_trns = n_trns;
+
+  /* Mark the boundary between input program `values' and
+     later-created `values'. */
+  inp_nval = default_dict.nval;
+
+  return lex_end_of_command ();
+}
+
+/* Initializes temp_case.  Called before the first case is read. */
+static void
+init_case (void)
+{
+  union value *val = temp_case->data;
+  unsigned char *cp = inp_init;
+  unsigned char c;
+  int i, j;
+
+  /* This code is 2-3X the complexity it might be, but I felt like
+     it.  It initializes temp_case union values to 0, or SYSMIS, or
+     blanks, as appropriate. */
+  for (i = 0; i < inp_nval / 4; i++)
+    {
+      c = *cp++;
+      for (j = 0; j < 4; j++)
+       {
+         switch (c & INP_MASK)
+           {
+           case INP_NUMERIC | INP_RIGHT:
+             val++->f = SYSMIS;
+             break;
+           case INP_NUMERIC | INP_LEFT:
+             val++->f = 0.0;
+             break;
+           case INP_STRING | INP_RIGHT:
+           case INP_STRING | INP_LEFT:
+             memset (val++->s, ' ', MAX_SHORT_STRING);
+             break;
+           }
+         c >>= 2;
+       }
+    }
+  if (inp_nval % 4)
+    {
+      c = *cp;
+      for (j = 0; j < inp_nval % 4; j++)
+       {
+         switch (c & INP_MASK)
+           {
+           case INP_NUMERIC | INP_RIGHT:
+             val++->f = SYSMIS;
+             break;
+           case INP_NUMERIC | INP_LEFT:
+             val++->f = 0.0;
+             break;
+           case INP_STRING | INP_RIGHT:
+           case INP_STRING | INP_LEFT:
+             memset (val++->s, ' ', MAX_SHORT_STRING);
+             break;
+           }
+         c >>= 2;
+       }
+    }
+}
+
+/* Clears temp_case.  Called between reading successive records. */
+static void
+clear_case (void)
+{
+  union value *val = temp_case->data;
+  unsigned char *cp = inp_init;
+  unsigned char c;
+  int i, j;
+
+  /* This code is 2-3X the complexity it might be, but I felt like
+     it.  It initializes temp_case values to SYSMIS, or
+     blanks, or does nothing, as appropriate. */
+  for (i = 0; i < inp_nval / 4; i++)
+    {
+      c = *cp++;
+      for (j = 0; j < 4; j++)
+       {
+         if (!(c & INP_LEFT))
+           {
+             if (c & INP_STRING)
+               memset (val->s, ' ', MAX_SHORT_STRING);
+             else
+               val->f = SYSMIS;
+           }
+         val++;
+         c >>= 2;
+       }
+    }
+  
+  if (inp_nval % 4)
+    {
+      c = *cp;
+      for (j = 0; j < inp_nval % 4; j++)
+       {
+         if (!(c & INP_LEFT))
+           {
+             if (c & INP_STRING)
+               memset (val->s, ' ', MAX_SHORT_STRING);
+             else
+               val->f = SYSMIS;
+           }
+         val++;
+         c >>= 2;
+       }
+    }
+}
+
+/* Executes each transformation in turn on a `blank' case.  When a
+   transformation fails, returning -2, then that's the end of the
+   file.  -1 means go on to the next transformation.  Otherwise the
+   return value is the index of the transformation to go to next. */
+void
+input_program_source_read (void)
+{
+  int i;
+
+  /* Nonzero if there were any END CASE commands in the set of
+     transformations. */
+  int end_case = 0;
+
+  /* We don't automatically write out cases if the user took over
+     that prerogative.  */
+  for (i = 0; i < f_trns; i++)
+    if (t_trns[i]->proc == end_case_trns_proc)
+      end_case = 1;
+
+  init_case ();
+  for (;;)
+    {
+      /* Index of current transformation. */
+      int i;
+
+      /* Return value of last-called transformation. */
+      int code;
+
+      debug_printf (("input-program: "));
+
+      /* Perform transformations on `blank' case. */
+      for (i = 0; i < f_trns;)
+       {
+#if DEBUGGING
+         printf ("/%d", i);
+         if (t_trns[i]->proc == end_case_trns_proc)
+           printf ("\n");
+#endif
+         code = t_trns[i]->proc (t_trns[i], temp_case);
+         switch (code)
+           {
+           case -1:
+             i++;
+             break;
+           case -2:
+             return;
+           case -3:
+             goto next_case;
+           default:
+             i = code;
+             break;
+           }
+       }
+
+#if DEBUGGING
+      if (!end_case)
+       printf ("\n");
+#endif
+
+      /* Write the case if appropriate. */
+      if (!end_case)
+       if (!write_case ())
+         return;
+
+      /* Blank out the case for the next iteration. */
+    next_case:
+      clear_case ();
+    }
+}
+
+static void
+input_program_source_destroy_source (void)
+{
+  cancel_transformations ();
+  free (inp_init);
+  inp_init = NULL;
+}
+
+struct case_stream input_program_source =
+  {
+    NULL,
+    input_program_source_read,
+    NULL,
+    NULL,
+    input_program_source_destroy_source,
+    NULL,
+    "INPUT PROGRAM",
+  };
+\f
+int
+cmd_end_case (void)
+{
+  struct trns_header *t;
+
+  lex_match_id ("END");
+  lex_match_id ("CASE");
+
+  if (vfm_source != &input_program_source)
+    {
+      msg (SE, _("This command may only be executed between INPUT PROGRAM "
+                "and END INPUT PROGRAM."));
+      return CMD_FAILURE;
+    }
+
+  t = xmalloc (sizeof *t);
+  t->proc = end_case_trns_proc;
+  t->free = NULL;
+  add_transformation ((struct trns_header *) t);
+
+  return lex_end_of_command ();
+}
+
+int
+end_case_trns_proc (struct trns_header *t unused, struct ccase * c unused)
+{
+#if DEBUGGING
+  printf ("END CASE\n");
+#endif
+  if (!write_case ())
+    return -2;
+  clear_case ();
+  return -1;
+}
+
+/* REREAD transformation. */
+struct reread_trns
+  {
+    struct trns_header h;
+
+    struct file_handle *handle;        /* File to move file pointer back on. */
+    struct expression *column; /* Column to reset file pointer to. */
+  };
+
+/* Parses REREAD command. */
+int
+cmd_reread (void)
+{
+  /* File to be re-read. */
+  struct file_handle *h;
+  
+  /* Expression for column to set file pointer to. */
+  struct expression *e;
+
+  /* Created transformation. */
+  struct reread_trns *t;
+
+  lex_match_id ("REREAD");
+
+  h = default_handle;
+  e = NULL;
+  while (token != '.')
+    {
+      if (lex_match_id ("COLUMN"))
+       {
+         lex_match ('=');
+         
+         if (e)
+           {
+             msg (SE, _("COLUMN subcommand multiply specified."));
+             expr_free (e);
+             return CMD_FAILURE;
+           }
+         
+         e = expr_parse (PXP_NUMERIC);
+         if (!e)
+           return CMD_FAILURE;
+       }
+      else if (lex_match_id ("FILE"))
+       {
+         lex_match ('=');
+         if (token != T_ID)
+           {
+             lex_error (_("expecting file handle name"));
+             expr_free (e);
+             return CMD_FAILURE;
+           }
+         h = fh_get_handle_by_name (tokid);
+         if (!h)
+           {
+             expr_free (e);
+             return CMD_FAILURE;
+           }
+         lex_get ();
+       }
+      else
+       {
+         lex_error (NULL);
+         expr_free (e);
+       }
+    }
+
+  t = xmalloc (sizeof *t);
+  t->h.proc = reread_trns_proc;
+  t->h.free = reread_trns_free;
+  t->handle = h;
+  t->column = e;
+  add_transformation ((struct trns_header *) t);
+
+  return CMD_SUCCESS;
+}
+
+static int
+reread_trns_proc (struct trns_header * pt, struct ccase * c)
+{
+  struct reread_trns *t = (struct reread_trns *) pt;
+
+  if (t->column == NULL)
+    dfm_bkwd_record (t->handle, 1);
+  else
+    {
+      union value column;
+
+      expr_evaluate (t->column, c, &column);
+      if (!finite (column.f) || column.f < 1)
+       {
+         msg (SE, _("REREAD: Column numbers must be positive finite "
+              "numbers.  Column set to 1."));
+         dfm_bkwd_record (t->handle, 1);
+       }
+      else
+       dfm_bkwd_record (t->handle, column.f);
+    }
+  return -1;
+}
+
+static void
+reread_trns_free (struct trns_header * t)
+{
+  expr_free (((struct reread_trns *) t)->column);
+}
+
+/* Parses END FILE command. */
+int
+cmd_end_file (void)
+{
+  struct trns_header *t;
+
+  lex_match_id ("END");
+  lex_match_id ("FILE");
+
+  if (vfm_source != &input_program_source)
+    {
+      msg (SE, _("This command may only be executed between INPUT PROGRAM "
+                "and END INPUT PROGRAM."));
+      return CMD_FAILURE;
+    }
+
+  t = xmalloc (sizeof *t);
+  t->proc = end_file_trns_proc;
+  t->free = NULL;
+  add_transformation ((struct trns_header *) t);
+
+  return lex_end_of_command ();
+}
+
+static int
+end_file_trns_proc (struct trns_header * t unused, struct ccase * c unused)
+{
+#if DEBUGGING
+  printf ("END FILE\n");
+#endif
+  return -2;
+}
diff --git a/src/inpt-pgm.h b/src/inpt-pgm.h
new file mode 100644 (file)
index 0000000..c46fe7a
--- /dev/null
@@ -0,0 +1,38 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !inpt_pgm_h
+#define inpt_pgm_h 1
+
+/* Bitmasks to indicate variable type. */
+enum
+  {
+    INP_MASK = 03,             /* 2#11. */
+    
+    INP_NUMERIC = 0,           /* Numeric. */
+    INP_STRING = 01,           /* String. */
+    
+    INP_RIGHT = 0,             /* Ordinary. */
+    INP_LEFT = 02              /* Scratch or LEAVE. */
+  };
+
+extern unsigned char *inp_init;
+extern size_t inp_init_size;
+
+#endif /* !inpt_pgm_h */
diff --git a/src/lexer.c b/src/lexer.c
new file mode 100644 (file)
index 0000000..6bcb46e
--- /dev/null
@@ -0,0 +1,1195 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "error.h"
+#include "getline.h"
+#include "lexer.h"
+#include "magic.h"
+#include "settings.h"
+#include "str.h"
+
+/*#define DUMP_TOKENS 1*/
+
+\f
+/* Global variables. */
+
+/* Current token. */
+int token;
+
+/* T_NUM: the token's value. */
+double tokval;
+
+/* T_ID: the identifier. */
+char tokid[9];
+
+/* T_ID, T_STRING: token string value.
+   For T_ID, this is not truncated to 8 characters as is tokid. */
+struct string tokstr;
+\f
+/* Static variables. */
+
+/* Table of keywords. */
+static const char *keywords[T_N_KEYWORDS + 1] = 
+  {
+    "AND", "OR", "NOT",
+    "EQ", "GE", "GT", "LE", "LT", "NE",
+    "ALL", "BY", "TO", "WITH",
+    NULL,
+  };
+
+/* Pointer to next token in getl_buf. */
+static char *prog;
+
+/* Nonzero only if this line ends with a terminal dot. */
+static int dot;
+
+/* Nonzero only if the last token returned was T_EOF. */
+static int eof;
+
+/* If nonzero, next token returned by lex_get().
+   Used only in exceptional circumstances. */
+static int put;                        
+
+static void unexpected_eof (void);
+static inline int check_id (const char *id, size_t len);
+static void convert_numeric_string_to_char_string (int type);
+static int parse_string (int type);
+
+#if DUMP_TOKENS
+static void dump_token (void);
+#endif
+\f
+/* Initialization. */
+
+/* Initializes the lexer. */
+void
+lex_init (void)
+{
+  if (!lex_get_line ())
+    unexpected_eof ();
+}
+\f
+/* Common functions. */
+
+/* Parses a single token, setting appropriate global variables to
+   indicate the token's attributes. */
+void
+lex_get (void)
+{
+  /* If a token was pushed ahead, return it. */
+  if (put)
+    {
+      token = put;
+      put = 0;
+#if DUMP_TOKENS
+      dump_token ();
+#endif
+      return;
+    }
+
+  /* Find a token. */
+  for (;;)
+    {
+      char *cp;
+
+      /* Skip whitespace. */
+      if (eof)
+       unexpected_eof ();
+
+      for (;;)
+       {
+         while (isspace ((unsigned char) *prog))
+           prog++;
+         if (*prog)
+           break;
+
+         if (dot)
+           {
+             dot = 0;
+             token = '.';
+#if DUMP_TOKENS
+             dump_token ();
+#endif
+             return;
+           }
+         else if (!lex_get_line ())
+           {
+             eof = 1;
+             token = T_STOP;
+#if DUMP_TOKENS
+             dump_token ();
+#endif
+             return;
+           }
+
+         if (put)
+           {
+             token = put;
+             put = 0;
+#if DUMP_TOKENS
+             dump_token ();
+#endif
+             return;
+           }
+       }
+
+      /* Actually parse the token. */
+      cp = prog;
+      ds_clear (&tokstr);
+      
+      switch (*prog)
+       {
+       case '-': case '.':
+       case '0': case '1': case '2': case '3': case '4':
+       case '5': case '6': case '7': case '8': case '9':
+         {
+           char *tail;
+
+           /* `-' can introduce a negative number, or it can be a
+              token by itself.  If it is not followed by a digit or a
+              decimal point, it is definitely not a number.
+              Otherwise, it might be either, but most of the time we
+              want it as a number.  When the syntax calls for a `-'
+              token, lex_negative_to_dash() must be used to break
+              negative numbers into two tokens. */
+           if (*cp == '-')
+             {
+               ds_putchar (&tokstr, *prog++);
+               while (isspace ((unsigned char) *prog))
+                 prog++;
+
+               if (!isdigit ((unsigned char) *prog) && *prog != '.')
+                 {
+                   token = '-';
+                   break;
+                 }
+             }
+
+           /* Parse the number, copying it into tokstr. */
+           while (isdigit ((unsigned char) *prog))
+             ds_putchar (&tokstr, *prog++);
+           if (*prog == '.')
+             {
+               ds_putchar (&tokstr, *prog++);
+               while (isdigit ((unsigned char) *prog))
+                 ds_putchar (&tokstr, *prog++);
+             }
+           if (*prog == 'e' || *prog == 'E')
+             {
+               ds_putchar (&tokstr, *prog++);
+               if (*prog == '+' || *prog == '-')
+                 ds_putchar (&tokstr, *prog++);
+               while (isdigit ((unsigned char) *prog))
+                 ds_putchar (&tokstr, *prog++);
+             }
+
+           /* Parse as floating point. */
+           tokval = strtod (ds_value (&tokstr), &tail);
+           if (*tail)
+             {
+               msg (SE, _("%s does not form a valid number."),
+                    ds_value (&tokstr));
+               tokval = 0.0;
+
+               ds_clear (&tokstr);
+               ds_putchar (&tokstr, '0');
+             }
+
+           token = T_NUM;
+           break;
+         }
+
+       case '\'': case '"':
+         token = parse_string (0);
+         break;
+
+       case '(': case ')': case ',': case '=': case '+': case '/':
+         token = *prog++;
+         break;
+
+       case '*':
+         if (*++prog == '*')
+           {
+             prog++;
+             token = T_EXP;
+           }
+         else
+           token = '*';
+         break;
+
+       case '<':
+         if (*++prog == '=')
+           {
+             prog++;
+             token = T_LE;
+           }
+         else if (*prog == '>')
+           {
+             prog++;
+             token = T_NE;
+           }
+         else
+           token = T_LT;
+         break;
+
+       case '>':
+         if (*++prog == '=')
+           {
+             prog++;
+             token = T_GE;
+           }
+         else
+           token = T_GT;
+         break;
+
+       case '~':
+         if (*++prog == '=')
+           {
+             prog++;
+             token = T_NE;
+           }
+         else
+           token = T_NOT;
+         break;
+
+       case '&':
+         prog++;
+         token = T_AND;
+         break;
+
+       case '|':
+         prog++;
+         token = T_OR;
+         break;
+
+       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 '#': case '$': case '@': 
+         /* Strings can be specified in binary, octal, or hex using
+              this special syntax. */
+         if (prog[1] == '\'' || prog[1] == '"')
+           {
+             static const char special[3] = "box";
+             const char *p;
+
+             p = strchr (special, tolower ((unsigned char) *prog));
+             if (p)
+               {
+                 prog++;
+                 token = parse_string (p - special + 1);
+                 break;
+               }
+           }
+
+         /* Copy id to tokstr. */
+         ds_putchar (&tokstr, toupper ((unsigned char) *prog++));
+         while (CHAR_IS_IDN (*prog))
+           ds_putchar (&tokstr, toupper ((unsigned char) *prog++));
+
+         /* Copy tokstr to tokid, truncating it to 8 characters. */
+         strncpy (tokid, ds_value (&tokstr), 8);
+         tokid[8] = 0;
+
+         token = check_id (ds_value (&tokstr), ds_length (&tokstr));
+         break;
+
+       default:
+         if (isgraph ((unsigned char) *prog))
+           msg (SE, _("Bad character in input: `%c'."), *prog++);
+         else
+           msg (SE, _("Bad character in input: `\\%o'."), *prog++);
+         continue;
+       }
+
+      break;
+    }
+
+#if DUMP_TOKENS
+  dump_token ();
+#endif
+}
+
+/* Prints a syntax error message containing the current token and
+   given message MESSAGE (if non-null). */
+void
+lex_error (const char *message, ...)
+{
+  char *token_rep;
+
+  token_rep = lex_token_representation ();
+  if (token_rep[0] == 0)
+    msg (SE, _("Syntax error at end of file."));
+  else if (message)
+    {
+      char buf[1024];
+      va_list args;
+      
+      va_start (args, message);
+      vsnprintf (buf, 1024, message, args);
+      va_end (args);
+
+      msg (SE, _("Syntax error %s at `%s'."), buf, token_rep);
+    }
+  else
+    msg (SE, _("Syntax error at `%s'."), token_rep);
+  
+  free (token_rep);
+}
+
+/* Checks that we're at end of command.
+   If so, returns a successful command completion code.
+   If not, flags a syntax error and returns an error command
+   completion code. */
+int
+lex_end_of_command (void)
+{
+  if (token != '.')
+    {
+      lex_error (_("expecting end of command"));
+      return CMD_TRAILING_GARBAGE;
+    }
+  else
+    return CMD_SUCCESS;
+}
+\f
+/* Token testing functions. */
+
+/* Returns nonzero if the current token is an integer. */
+int
+lex_integer_p (void)
+{
+  return (token == T_NUM
+         && tokval != NOT_LONG
+         && tokval >= LONG_MIN
+         && tokval <= LONG_MAX
+         && floor (tokval) == tokval);
+}
+
+/* Returns the value of the current token, which must be an
+   integer. */
+long
+lex_integer (void)
+{
+  assert (lex_integer_p ());
+  return tokval;
+}
+\f  
+/* Token matching functions. */
+
+/* If TOK is the current token, skips it and returns nonzero.
+   Otherwise, returns zero. */
+int
+lex_match (int t)
+{
+  if (token == t)
+    {
+      lex_get ();
+      return 1;
+    }
+  else
+    return 0;
+}
+
+/* If the current token is the identifier S, skips it and returns
+   nonzero.
+   Otherwise, returns zero. */
+int
+lex_match_id (const char *s)
+{
+  if (token == T_ID && lex_id_match (s, tokid))
+    {
+      lex_get ();
+      return 1;
+    }
+  else
+    return 0;
+}
+
+/* If the current token is integer N, skips it and returns nonzero.
+   Otherwise, returns zero. */
+int
+lex_match_int (int x)
+{
+  if (lex_integer_p () && lex_integer () == x)
+    {
+      lex_get ();
+      return 1;
+    }
+  else
+    return 0;
+}
+\f
+/* Forced matches. */
+
+/* If this token is identifier S, fetches the next token and returns
+   nonzero.
+   Otherwise, reports an error and returns zero. */
+int
+lex_force_match_id (const char *s)
+{
+  if (token == T_ID && lex_id_match (s, tokid))
+    {
+      lex_get ();
+      return 1;
+    }
+  else
+    {
+      lex_error (_("expecting `%s'"), s);
+      return 0;
+    }
+}
+
+/* If the current token is T, skips the token.  Otherwise, reports an
+   error and returns from the current function with return value 0. */
+int
+lex_force_match (int t)
+{
+  if (token == t)
+    {
+      lex_get ();
+      return 1;
+    }
+  else
+    {
+      lex_error (_("expecting %s"), lex_token_name (t));
+      return 0;
+    }
+}
+
+/* If this token is a string, does nothing and returns nonzero.
+   Otherwise, reports an error and returns zero. */
+int
+lex_force_string (void)
+{
+  if (token == T_STRING)
+    return 1;
+  else
+    {
+      lex_error (_("expecting string"));
+      return 0;
+    }
+}
+
+/* If this token is an integer, does nothing and returns nonzero.
+   Otherwise, reports an error and returns zero. */
+int
+lex_force_int (void)
+{
+  if (lex_integer_p ())
+    return 1;
+  else
+    {
+      lex_error (_("expecting integer"));
+      return 0;
+    }
+}
+       
+/* If this token is a number, does nothing and returns nonzero.
+   Otherwise, reports an error and returns zero. */
+int
+lex_force_num (void)
+{
+  if (token == T_NUM)
+    return 1;
+  else
+    {
+      lex_error (_("expecting number"));
+      return 0;
+    }
+}
+       
+/* If this token is an identifier, does nothing and returns nonzero.
+   Otherwise, reports an error and returns zero. */
+int
+lex_force_id (void)
+{
+  if (token == T_ID)
+    return 1;
+  else
+    {
+      lex_error (_("expecting identifier"));
+      return 0;
+    }
+}
+\f
+/* Comparing identifiers. */
+
+/* Keywords match if one of the following is true: KW and TOK are
+   identical (barring differences in case), or TOK is at least 3
+   characters long and those characters are identical to KW.  KW_LEN
+   is the length of KW, TOK_LEN is the length of TOK. */
+int
+lex_id_match_len (const char *kw, size_t kw_len,
+                 const char *tok, size_t tok_len)
+{
+  size_t i = 0;
+
+  assert (kw && tok);
+  for (;;)
+    {
+      if (i == kw_len && i == tok_len)
+       return 1;
+      else if (i == tok_len)
+       return i >= 3;
+      else if (i == kw_len)
+       return 0;
+      else if (toupper ((unsigned char) kw[i])
+              != toupper ((unsigned char) tok[i]))
+       return 0;
+
+      i++;
+    }
+}
+
+/* Same as lex_id_match_len() minus the need to pass in the lengths. */
+int
+lex_id_match (const char *kw, const char *tok)
+{
+  return lex_id_match_len (kw, strlen (kw), tok, strlen (tok));
+}
+\f
+/* Weird token functions. */
+
+/* Returns the first character of the next token, except that if the
+   next token is not an identifier, the character returned will not be
+   a character that can begin an identifier.  Specifically, the
+   hexstring lead-in X' causes lookahead() to return '.  Note that an
+   alphanumeric return value doesn't guarantee an ID token, it could
+   also be a reserved-word token. */
+int
+lex_look_ahead (void)
+{
+  if (put)
+    return put;
+
+  for (;;)
+    {
+      if (eof)
+       unexpected_eof ();
+
+      for (;;)
+       {
+         while (isspace ((unsigned char) *prog))
+           prog++;
+         if (*prog)
+           break;
+
+         if (dot)
+           return '.';
+         else if (!lex_get_line ())
+           unexpected_eof ();
+
+         if (put)
+           return put;
+       }
+
+      if ((toupper ((unsigned char) *prog) == 'X'
+          || toupper ((unsigned char) *prog) == 'B')
+         && (prog[1] == '\'' || prog[1] == '"'))
+       return '\'';
+
+      return *prog;
+    }
+}
+
+/* Makes the current token become the next token to be read; the
+   current token is set to T. */
+void
+lex_put_back (int t)
+{
+  put = token;
+  token = t;
+}
+
+/* Makes T the next token read. */
+void
+lex_put_forward (int t)
+{
+  put = t;
+}
+\f
+/* Weird line processing functions. */
+
+/* Discards the rest of the current input line for tokenization
+   purposes, but returns the entire contents of the line for use by
+   the caller. */
+char *
+lex_entire_line (void)
+{
+  prog = ds_end (&getl_buf);
+  dot = 0;
+  return ds_value (&getl_buf);
+}
+
+/* As lex_entire_line(), but only returns the part of the current line
+   that hasn't already been tokenized.
+   If HAD_DOT is non-null, stores nonzero into *HAD_DOT if the line
+   ends with a terminal dot, or zero if it doesn't. */
+char *
+lex_rest_of_line (int *had_dot)
+{
+  char *s = prog;
+  prog = ds_end (&getl_buf);
+
+  if (had_dot)
+    *had_dot = dot;
+  dot = 0;
+
+  return s;
+}
+
+/* Causes the rest of the current input line to be ignored for
+   tokenization purposes. */
+void
+lex_discard_line (void)
+{
+  msg (SW, _("The rest of this command has been discarded."));
+
+  ds_clear (&getl_buf);
+  prog = ds_value (&getl_buf);
+  dot = put = 0;
+}
+
+/* Sets the current position in the current line to P, which must be
+   in getl_buf. */
+void
+lex_set_prog (char *p)
+{
+  prog = p;
+}
+\f
+/* Weird line reading functions. */
+
+/* Read a line for use by the tokenizer. */
+int
+lex_get_line (void)
+{
+  if (!getl_read_line ())
+    return 0;
+
+  lex_preprocess_line ();
+  return 1;
+}
+
+/* Preprocesses getl_buf by removing comments, stripping trailing
+   whitespace and the terminal dot, and removing leading indentors. */
+void
+lex_preprocess_line (void)
+{
+  /* Strips comments. */
+  {
+    /* getl_buf iterator. */
+    char *cp;
+
+    /* Nonzero inside a comment. */
+    int comment;
+
+    /* Nonzero inside a quoted string. */
+    int quote;
+
+    /* Remove C-style comments begun by slash-star and terminated by
+     star-slash or newline. */
+    quote = comment = 0;
+    for (cp = ds_value (&getl_buf); *cp; )
+      {
+       /* If we're not commented out, toggle quoting. */
+       if (!comment)
+         {
+           if (*cp == quote)
+             quote = 0;
+           else if (*cp == '\'' || *cp == '"')
+             quote = *cp;
+         }
+      
+       /* If we're not quoting, toggle commenting. */
+       if (!quote)
+         {
+           if (cp[0] == '/' && cp[1] == '*')
+             {
+               comment = 1;
+               *cp++ = ' ';
+               *cp++ = ' ';
+               continue;
+             }
+           else if (cp[0] == '*' && cp[1] == '/' && comment)
+             {
+               comment = 0;
+               *cp++ = ' ';
+               *cp++ = ' ';
+               continue;
+             }
+         }
+      
+       /* Check commenting. */
+       if (!comment)
+         cp++;
+       else
+         *cp++ = ' ';
+      }
+  }
+  
+  /* Strip trailing whitespace and terminal dot. */
+  {
+    size_t len = ds_length (&getl_buf);
+    char *s = ds_value (&getl_buf);
+    
+    /* Strip trailing whitespace. */
+    while (len > 0 && isspace ((unsigned char) s[len - 1]))
+      len--;
+
+    /* Check for and remove terminal dot. */
+    if (len > 0 && s[len - 1] == set_endcmd)
+      {
+       dot = 1;
+       len--;
+      }
+    else if (len == 0 && set_nullline)
+      dot = 1;
+    else
+      dot = 0;
+
+    /* Set length. */
+    ds_truncate (&getl_buf, len);
+  }
+  
+  /* In batch mode, strip leading indentors and insert a terminal dot
+     as necessary. */
+  if (getl_interactive != 2 && getl_mode == GETL_MODE_BATCH)
+    {
+      char *s = ds_value (&getl_buf);
+      
+      if (s[0] == '+' || s[0] == '-' || s[0] == '.')
+       s[0] = ' ';
+      else if (s[0] && !isspace ((unsigned char) s[0]))
+       lex_put_forward ('.');
+    }
+
+  prog = ds_value (&getl_buf);
+}
+\f
+/* Token names. */
+
+/* Returns the name of a token in a static buffer. */
+const char *
+lex_token_name (int token)
+{
+  if (token >= T_FIRST_KEYWORD && token <= T_LAST_KEYWORD)
+    return keywords[token - T_FIRST_KEYWORD];
+
+  if (token < 256)
+    {
+      static char t[2];
+      t[0] = token;
+      return t;
+    }
+
+  return _("<ERROR>");
+}
+
+/* Returns an ASCII representation of the current token as a
+   malloc()'d string. */
+char *
+lex_token_representation (void)
+{
+  char *token_rep;
+  
+  switch (token)
+    {
+    case T_ID:
+    case T_NUM:
+      return xstrdup (ds_value (&tokstr));
+      break;
+
+    case T_STRING:
+      {
+       int hexstring = 0;
+       char *sp, *dp;
+
+       for (sp = ds_value (&tokstr); sp < ds_end (&tokstr); sp++)
+         if (!isprint ((unsigned char) *sp))
+           {
+             hexstring = 1;
+             break;
+           }
+             
+       token_rep = xmalloc (2 + ds_length (&tokstr) * 2 + 1 + 1);
+
+       dp = token_rep;
+       if (hexstring)
+         *dp++ = 'X';
+       *dp++ = '\'';
+
+       if (!hexstring)
+         for (sp = ds_value (&tokstr); *sp; )
+           {
+             if (*sp == '\'')
+               *dp++ = '\'';
+             *dp++ = (unsigned char) *sp++;
+           }
+       else
+         for (sp = ds_value (&tokstr); sp < ds_end (&tokstr); sp++)
+           {
+             *dp++ = (((unsigned char) *sp) >> 4)["0123456789ABCDEF"];
+             *dp++ = (((unsigned char) *sp) & 15)["0123456789ABCDEF"];
+           }
+       *dp++ = '\'';
+       *dp = '\0';
+       
+       return token_rep;
+      }
+    break;
+
+    case T_STOP:
+      token_rep = xmalloc (1);
+      *token_rep = '\0';
+      return token_rep;
+
+    case T_EXP:
+      return xstrdup ("**");
+
+    default:
+      if (token >= T_FIRST_KEYWORD && token <= T_LAST_KEYWORD)
+       return xstrdup (keywords [token - T_FIRST_KEYWORD]);
+      else
+       {
+         token_rep = xmalloc (2);
+         token_rep[0] = token;
+         token_rep[1] = '\0';
+         return token_rep;
+       }
+    }
+       
+  assert (0);
+}
+\f
+/* Really weird functions. */
+
+/* Most of the time, a `-' is a lead-in to a negative number.  But
+   sometimes it's actually part of the syntax.  If a dash can be part
+   of syntax then this function is called to rip it off of a
+   number. */
+void
+lex_negative_to_dash (void)
+{
+  if (token == T_NUM && tokval < 0.0)
+    {
+      token = '-';
+      tokval = -tokval;
+      ds_replace (&tokstr, ds_value (&tokstr) + 1);
+      lex_put_forward (T_NUM);
+    }
+}
+   
+/* We're not at eof any more. */
+void
+lex_reset_eof (void)
+{
+  eof = 0;
+}
+
+/* Skip a COMMENT command. */
+void
+lex_skip_comment (void)
+{
+  for (;;)
+    {
+      lex_get_line ();
+      if (put == '.')
+       break;
+
+      prog = ds_end (&getl_buf);
+      if (dot)
+       break;
+    }
+}
+\f
+/* Private functions. */
+
+/* Unexpected end of file. */
+static void
+unexpected_eof (void)
+{
+  msg (FE, _("Unexpected end of file."));
+}
+
+/* Returns the proper token type, either T_ID or a reserved keyword
+   enum, for ID[], which must contain LEN characters. */
+static inline int
+check_id (const char *id, size_t len)
+{
+  const char **kwp;
+
+  if (len < 2 || len > 4)
+    return T_ID;
+  
+  for (kwp = keywords; *kwp; kwp++)
+    if (!strcmp (*kwp, id))
+      return T_FIRST_KEYWORD + (kwp - keywords);
+
+  return T_ID;
+}
+
+/* When invoked, tokstr contains a string of binary, octal, or hex
+   digits, for values of TYPE of 0, 1, or 2, respectively.  The string
+   is converted to characters having the specified values. */
+static void
+convert_numeric_string_to_char_string (int type)
+{
+  static const char *base_names[] = {N_("binary"), N_("octal"), N_("hex")};
+  static const int bases[] = {2, 8, 16};
+  static const int chars_per_byte[] = {8, 3, 2};
+
+  const char *const base_name = base_names[type];
+  const int base = bases[type];
+  const int cpb = chars_per_byte[type];
+  const int nb = ds_length (&tokstr) / cpb;
+  int i;
+  char *p;
+
+  assert (type >= 0 && type <= 2);
+
+  if (ds_length (&tokstr) % cpb)
+    msg (SE, _("String of %s digits has %d characters, which is not a "
+              "multiple of %d."),
+        gettext (base_name), ds_length (&tokstr), cpb);
+
+  p = ds_value (&tokstr);
+  for (i = 0; i < nb; i++)
+    {
+      int value;
+      int j;
+         
+      value = 0;
+      for (j = 0; j < cpb; j++, p++)
+       {
+         int v;
+
+         if (*p >= '0' && *p <= '9')
+           v = *p - '0';
+         else
+           {
+             static const char alpha[] = "abcdef";
+             const char *q = strchr (alpha, tolower ((unsigned char) *p));
+
+             if (q)
+               v = q - alpha + 10;
+             else
+               v = base;
+           }
+
+         if (v >= base)
+           msg (SE, _("`%c' is not a valid %s digit."), *p, base_name);
+
+         value = value * base + v;
+       }
+
+      ds_value (&tokstr)[i] = (unsigned char) value;
+    }
+
+  ds_truncate (&tokstr, nb);
+}
+
+/* Parses a string from the input buffer into tokstr.  The input
+   buffer pointer prog must point to the initial single or double
+   quote.  TYPE is 0 if it is an ordinary string, or 1, 2, or 3 for a
+   binary, octal, or hexstring, respectively.  Returns token type. */
+static int 
+parse_string (int type)
+{
+  /* Accumulate the entire string, joining sections indicated by +
+     signs. */
+  for (;;)
+    {
+      /* Single or double quote. */
+      int c = *prog++;
+      
+      /* Accumulate section. */
+      for (;;)
+       {
+         /* Check end of line. */
+         if (*prog == 0)
+           {
+             msg (SE, _("Unterminated string constant."));
+             goto finish;
+           }
+         
+         /* Double quote characters to embed them in strings. */
+         if (*prog == c)
+           {
+             if (prog[1] == c)
+               prog++;
+             else
+               break;
+           }
+
+         ds_putchar (&tokstr, *prog++);
+       }
+      prog++;
+
+      /* Skip whitespace after final quote mark. */
+      if (eof)
+       break;
+      for (;;)
+       {
+         while (isspace ((unsigned char) *prog))
+           prog++;
+         if (*prog)
+           break;
+
+         if (dot)
+           goto finish;
+
+         if (!lex_get_line ())
+           unexpected_eof ();
+       }
+
+      /* Skip plus sign. */
+      if (*prog != '+')
+       break;
+      prog++;
+
+      /* Skip whitespace after plus sign. */
+      if (eof)
+       break;
+      for (;;)
+       {
+         while (isspace ((unsigned char) *prog))
+           prog++;
+         if (*prog)
+           break;
+
+         if (dot)
+           goto finish;
+
+         if (!lex_get_line ())
+           unexpected_eof ();
+       }
+
+      /* Ensure that a valid string follows. */
+      if (*prog != '\'' && *prog != '"')
+       {
+         msg (SE, "String expected following `+'.");
+         goto finish;
+       }
+    }
+
+  /* We come here when we've finished concatenating all the string sections
+     into one large string. */
+finish:
+  if (type != 0)
+    convert_numeric_string_to_char_string (type - 1);
+
+  if (ds_length (&tokstr) > 255)
+    {
+      msg (SE, _("String exceeds 255 characters in length (%d characters)."),
+          ds_length (&tokstr));
+      ds_truncate (&tokstr, 255);
+    }
+      
+  {
+    /* FIXME. */
+    size_t i;
+    int warned = 0;
+
+    for (i = 0; i < ds_length (&tokstr); i++)
+      if (ds_value (&tokstr)[i] == 0)
+       {
+         if (!warned)
+           {
+             msg (SE, _("Sorry, literal strings may not contain null "
+                        "characters.  Replacing with spaces."));
+             warned = 1;
+           }
+         ds_value (&tokstr)[i] = ' ';
+       }
+  }
+
+  return T_STRING;
+}
+\f      
+#if DUMP_TOKENS
+/* Reads one token from the lexer and writes a textual representation
+   on stdout for debugging purposes. */
+static void
+dump_token (void)
+{
+  {
+    const char *curfn;
+    int curln;
+
+    getl_location (&curfn, &curln);
+    if (curfn)
+      printf ("%s:%d\t", curfn, curln);
+  }
+  
+  switch (token)
+    {
+    case T_ID:
+      printf ("ID\t%s\n", tokid);
+      break;
+
+    case T_NUM:
+      printf ("NUM\t%f\n", tokval);
+      break;
+
+    case T_STRING:
+      printf ("STRING\t\"%s\"\n", ds_value (&tokstr));
+      break;
+
+    case T_STOP:
+      printf ("STOP\n");
+      break;
+
+    case T_EXP:
+      puts ("MISC\tEXP");
+      break;
+
+    case 0:
+      puts ("MISC\tEOF");
+      break;
+
+    default:
+      if (token >= T_FIRST_KEYWORD && token <= T_LAST_KEYWORD)
+       printf ("KEYWORD\t%s\n", lex_token_name (token));
+      else
+       printf ("PUNCT\t%c\n", token);
+      break;
+    }
+}
+#endif /* DEBUGGING */
diff --git a/src/lexer.h b/src/lexer.h
new file mode 100644 (file)
index 0000000..542721b
--- /dev/null
@@ -0,0 +1,133 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !lexer_h
+#define lexer_h 1
+
+/* Returns nonzero if character CH may be the first character in an
+   identifier. */
+#define CHAR_IS_ID1(CH)                                \
+       (isalpha ((unsigned char) (CH))         \
+        || (CH) == '@'                         \
+        || (CH) == '#'                         \
+        || (CH) == '$')
+
+/* Returns nonzero if character CH may be a character in an
+   identifier other than the first. */
+#define CHAR_IS_IDN(CH)                                \
+       (CHAR_IS_ID1 (CH)                       \
+         || isdigit ((unsigned char) (CH))     \
+        || (CH) == '.'                         \
+        || (CH) == '_')
+
+/* Token types. */
+/* The order of the enumerals below is important.  Do not change it. */
+enum
+  {
+    T_ID = 256, /* Identifier. */
+    T_NUM,     /* Number. */
+    T_STRING,  /* Quoted string. */
+    T_STOP,    /* End of input. */
+
+    T_AND,     /* AND */
+    T_OR,      /* OR */
+    T_NOT,     /* NOT */
+
+    T_EQ,      /* EQ */
+    T_GE,      /* GE or >= */
+    T_GT,      /* GT or > */
+    T_LE,      /* LE or <= */
+    T_LT,      /* LT or < */
+    T_NE,      /* NE or ~= */
+
+    T_ALL,     /* ALL */
+    T_BY,      /* BY */
+    T_TO,      /* TO */
+    T_WITH,    /* WITH */
+
+    T_EXP,     /* ** */
+
+    T_FIRST_KEYWORD = T_AND,
+    T_LAST_KEYWORD = T_WITH,
+    T_N_KEYWORDS = T_LAST_KEYWORD - T_FIRST_KEYWORD + 1,
+  };
+
+
+extern int token;
+extern double tokval;
+extern char tokid[9];
+extern struct string tokstr;
+
+#include <stddef.h>
+
+/* Initialization. */
+void lex_init (void);
+
+/* Common functions. */
+void lex_get (void);
+void lex_error (const char *, ...);
+int lex_end_of_command (void);
+
+/* Token testing functions. */
+int lex_integer_p (void);
+long lex_integer (void);
+
+/* Token matching functions. */
+int lex_match (int);
+int lex_match_id (const char *);
+int lex_match_int (int);
+
+/* Forcible matching functions. */
+int lex_force_match (int);
+int lex_force_match_id (const char *);
+int lex_force_int (void);
+int lex_force_num (void);
+int lex_force_id (void);
+int lex_force_string (void);
+
+/* Comparing identifiers. */
+int lex_id_match_len (const char *keyword_string, size_t keyword_len,
+                     const char *token_string, size_t token_len);
+int lex_id_match (const char *keyword_string, const char *token_string);
+       
+/* Weird token functions. */
+int lex_look_ahead (void);
+void lex_put_back (int);
+void lex_put_forward (int);
+
+/* Weird line processing functions. */
+char *lex_entire_line (void);
+char *lex_rest_of_line (int *had_dot);
+void lex_discard_line (void);
+void lex_set_prog (char *p);
+
+/* Weird line reading functions. */
+int lex_get_line (void);
+void lex_preprocess_line (void);
+
+/* Token names. */
+const char *lex_token_name (int);
+char *lex_token_representation (void);
+
+/* Really weird functions. */
+void lex_negative_to_dash (void);
+void lex_reset_eof (void);
+void lex_skip_comment (void);
+
+#endif /* !lexer_h */
diff --git a/src/list.q b/src/list.q
new file mode 100644 (file)
index 0000000..45046a0
--- /dev/null
@@ -0,0 +1,781 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "lexer.h"
+#include "error.h"
+#include "magic.h"
+#include "misc.h"
+#include "htmlP.h"
+#include "output.h"
+#include "som.h"
+#include "var.h"
+#include "vfm.h"
+#include "format.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+#if DEBUGGING
+static void debug_print (void);
+#endif
+
+/* (specification)
+   list (lst_):
+     *variables=varlist("PV_NO_SCRATCH");
+     cases=:from n:first,"%s>0"/by n:step,"%s>0"/ *to n:last,"%s>0";
+     format=numbering:numbered/!unnumbered,
+            wrap:!wrap/single,
+            weight:weight/!noweight.
+*/
+/* (declarations) */
+/* (functions) */
+
+/* Layout for one output driver. */
+struct list_ext
+  {
+    int type;          /* 0=Values and labels fit across the page. */
+    int n_vertical;    /* Number of labels to list vertically. */
+    int header_rows;   /* Number of header rows. */
+    char **header;     /* The header itself. */
+  };
+
+/* Parsed command. */
+static struct cmd_list cmd;
+
+/* Current case number. */
+static int case_num;
+
+/* Line buffer. */
+static char *line_buf;
+
+/* TTY-style output functions. */
+static int n_lines_remaining (struct outp_driver *d);
+static int n_chars_width (struct outp_driver *d);
+static void write_line (struct outp_driver *d, char *s);
+
+/* Other functions. */
+static int list_cases (struct ccase *);
+static void determine_layout (void);
+static void clean_up (void);
+static void write_header (struct outp_driver *);
+static void write_all_headers (void);
+
+/* Returns the number of text lines that can fit on the remainder of
+   the page. */
+static inline int
+n_lines_remaining (struct outp_driver *d)
+{
+  int diff;
+
+  diff = d->length - d->cp_y;
+  return (diff > 0) ? (diff / d->font_height) : 0;
+}
+
+/* Returns the number of fixed-width character that can fit across the
+   page. */
+static inline int
+n_chars_width (struct outp_driver *d)
+{
+  return d->width / d->fixed_width;
+}
+
+/* Writes the line S at the current position and advances to the next
+   line.  */
+static void
+write_line (struct outp_driver *d, char *s)
+{
+  struct outp_text text;
+  
+  assert (d->cp_y + d->font_height <= d->length);
+  text.options = OUTP_T_JUST_LEFT;
+  ls_init (&text.s, s, strlen (s));
+  text.x = d->cp_x;
+  text.y = d->cp_y;
+  d->class->text_draw (d, &text);
+  d->cp_x = 0;
+  d->cp_y += d->font_height;
+}
+    
+/* Parses and executes the LIST procedure. */
+int
+cmd_list (void)
+{
+  struct variable casenum_var;
+
+  lex_match_id ("LIST");
+  if (!parse_list (&cmd))
+    return CMD_FAILURE;
+  
+  /* Fill in defaults. */
+  if (cmd.step == NOT_LONG)
+    cmd.step = 1;
+  if (cmd.first == NOT_LONG)
+    cmd.first = 1;
+  if (cmd.last == NOT_LONG)
+    cmd.last = LONG_MAX;
+  if (!cmd.sbc_variables)
+    fill_all_vars (&cmd.v_variables, &cmd.n_variables,
+                  FV_NO_SYSTEM | FV_NO_SCRATCH);
+  if (cmd.n_variables == 0)
+    {
+      msg (SE, _("No variables specified."));
+      return CMD_FAILURE;
+    }
+
+  /* Verify arguments. */
+  if (cmd.first > cmd.last)
+    {
+      int t;
+      msg (SW, _("The first case (%ld) specified precedes the last case (%ld) "
+          "specified.  The values will be swapped."), cmd.first, cmd.last);
+      t = cmd.first;
+      cmd.first = cmd.last;
+      cmd.last = t;
+    }
+  if (cmd.first < 1)
+    {
+      msg (SW, _("The first case (%ld) to list is less than 1.  The value is "
+          "being reset to 1."), cmd.first);
+      cmd.first = 1;
+    }
+  if (cmd.last < 1)
+    {
+      msg (SW, _("The last case (%ld) to list is less than 1.  The value is "
+          "being reset to 1."), cmd.last);
+      cmd.last = 1;
+    }
+  if (cmd.step < 1)
+    {
+      msg (SW, _("The step value %ld is less than 1.  The value is being "
+          "reset to 1."), cmd.step);
+      cmd.step = 1;
+    }
+
+  /* Weighting variable. */
+  if (cmd.weight == LST_WEIGHT)
+    {
+      update_weighting (&default_dict);
+      if (default_dict.weight_index != -1)
+       {
+         int i;
+
+         for (i = 0; i < cmd.n_variables; i++)
+           if (cmd.v_variables[i]->index == default_dict.weight_index)
+             break;
+         if (i >= cmd.n_variables)
+           {
+             /* Add the weight variable to the end of the variable list. */
+             cmd.n_variables++;
+             cmd.v_variables = xrealloc (cmd.v_variables,
+                                         (cmd.n_variables
+                                          * sizeof *cmd.v_variables));
+             cmd.v_variables[cmd.n_variables - 1]
+               = default_dict.var[default_dict.weight_index];
+           }
+       }
+      else
+       msg (SW, _("`/FORMAT WEIGHT' specified, but weighting is not on."));
+    }
+
+  /* Case number. */
+  if (cmd.numbering == LST_NUMBERED)
+    {
+      /* Initialize the case-number variable. */
+      strcpy (casenum_var.name, "Case#");
+      casenum_var.type = NUMERIC;
+      casenum_var.fv = -1;
+      casenum_var.print.type = FMT_F;
+      casenum_var.print.w = (cmd.last == LONG_MAX ? 5 : intlog10 (cmd.last));
+      casenum_var.print.d = 0;
+
+      /* Add the weight variable at the beginning of the variable list. */
+      cmd.n_variables++;
+      cmd.v_variables = xrealloc (cmd.v_variables,
+                                 cmd.n_variables * sizeof *cmd.v_variables);
+      memmove (&cmd.v_variables[1], &cmd.v_variables[0],
+              (cmd.n_variables - 1) * sizeof *cmd.v_variables);
+      cmd.v_variables[0] = &casenum_var;
+    }
+
+#if DEBUGGING
+  /* Print out command. */
+  debug_print ();
+#endif
+
+  determine_layout ();
+
+  case_num = 0;
+  procedure (write_all_headers, list_cases, NULL);
+  free (line_buf);
+
+  clean_up ();
+
+  return CMD_SUCCESS;
+}
+
+/* Writes headers to all devices.  This is done at the beginning of
+   each SPLIT FILE group. */
+static void
+write_all_headers (void)
+{
+  struct outp_driver *d;
+
+  for (d = outp_drivers (NULL); d; d = outp_drivers (d))
+    {
+      if (!d->class->special)
+       {
+         d->cp_y += d->font_height;            /* Blank line. */
+         write_header (d);
+       }
+      else if (d->class == &html_class)
+       {
+         struct html_driver_ext *x = d->ext;
+  
+         assert (d->driver_open && d->page_open);
+         if (x->sequence_no == 0 && !d->class->open_page (d))
+           {
+             msg (ME, _("Cannot open first page on HTML device %s."),
+                  d->name);
+             return;
+           }
+
+         fputs ("<TABLE BORDER=1>\n  <TR>\n", x->file.file);
+         
+         {
+           int i;
+
+           for (i = 0; i < cmd.n_variables; i++)
+             fprintf (x->file.file, "    <TH><I><B>%s</B></I></TH>\n",
+                      cmd.v_variables[i]->name);
+         }
+
+         fputs ("  <TR>\n", x->file.file);
+       }
+      else
+       assert (0);
+    }
+}
+
+/* Writes the headers.  Some of them might be vertical; most are
+   probably horizontal. */
+static void
+write_header (struct outp_driver *d)
+{
+  struct list_ext *prc = d->prc;
+
+  if (!prc->header_rows)
+    return;
+  
+  if (n_lines_remaining (d) < prc->header_rows + 1)
+    {
+      outp_eject_page (d);
+      assert (n_lines_remaining (d) >= prc->header_rows + 1);
+    }
+
+  /* Design the header. */
+  if (!prc->header)
+    {
+      int i, x;
+
+      /* Allocate, initialize header. */
+      prc->header = xmalloc (sizeof (char *) * prc->header_rows);
+      {
+       int w = n_chars_width (d);
+       for (i = 0; i < prc->header_rows; i++)
+         {
+           prc->header[i] = xmalloc (w + 1);
+           memset (prc->header[i], ' ', w);
+         }
+      }
+
+      /* Put in vertical names. */
+      for (i = x = 0; i < prc->n_vertical; i++)
+       {
+         struct variable *v = cmd.v_variables[i];
+         int j;
+
+         memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
+         x += v->print.w - 1;
+         for (j = 0; j < (int) strlen (v->name); j++)
+           prc->header[strlen (v->name) - j - 1][x] = v->name[j];
+         x += 2;
+       }
+
+      /* Put in horizontal names. */
+      for (; i < cmd.n_variables; i++)
+       {
+         struct variable *v = cmd.v_variables[i];
+         
+         memset (&prc->header[prc->header_rows - 1][x], '-',
+                 max (v->print.w, (int) strlen (v->name)));
+         if ((int) strlen (v->name) < v->print.w)
+           x += v->print.w - strlen (v->name);
+         memcpy (&prc->header[0][x], v->name, strlen (v->name));
+         x += strlen (v->name) + 1;
+       }
+
+      /* Add null bytes. */
+      for (i = 0; i < prc->header_rows; i++)
+       {
+         for (x = n_chars_width (d); x >= 1; x--)
+           if (prc->header[i][x - 1] != ' ')
+             {
+               prc->header[i][x] = 0;
+               break;
+             }
+         assert (x);
+       }
+    }
+
+  /* Write out the header, in back-to-front order except for the last line. */
+  {
+    int i;
+    
+    for (i = prc->header_rows - 2; i >= 0; i--)
+      write_line (d, prc->header[i]);
+    write_line (d, prc->header[prc->header_rows - 1]);
+  }
+}
+      
+  
+/* Frees up all the memory we've allocated. */
+static void
+clean_up (void)
+{
+  struct outp_driver *d;
+  
+  for (d = outp_drivers (NULL); d; d = outp_drivers (d))
+    if (d->class->special == 0)
+      {
+       struct list_ext *prc = d->prc;
+       int i;
+
+       if (prc->header)
+         {
+           for (i = 0; i < prc->header_rows; i++)
+             free (prc->header[i]);
+           free (prc->header);
+         }
+       free (prc);
+      
+       d->class->text_set_font_by_name (d, "PROP");
+      }
+    else if (d->class == &html_class)
+      {
+       if (d->driver_open && d->page_open)
+         {
+           struct html_driver_ext *x = d->ext;
+
+           fputs ("</TABLE>\n", x->file.file);
+         }
+      }
+    else
+      assert (0);
+  
+  free (cmd.v_variables);
+}
+
+/* Writes string STRING at the current position.  If the text would
+   fall off the side of the page, then advance to the next line,
+   indenting by amount INDENT. */
+static void
+write_varname (struct outp_driver *d, char *string, int indent)
+{
+  struct outp_text text;
+
+  text.options = OUTP_T_JUST_LEFT;
+  ls_init (&text.s, string, strlen (string));
+  d->class->text_metrics (d, &text);
+  
+  if (d->cp_x + text.h > d->width)
+    {
+      d->cp_y += d->font_height;
+      if (d->cp_y + d->font_height > d->length)
+       outp_eject_page (d);
+      d->cp_x = indent;
+    }
+
+  text.x = d->cp_x;
+  text.y = d->cp_y;
+  d->class->text_draw (d, &text);
+  d->cp_x += text.h;
+}
+
+/* When we can't fit all the values across the page, we write out all
+   the variable names just once.  This is where we do it. */
+static void
+write_fallback_headers (struct outp_driver *d)
+{
+  const int max_width = n_chars_width(d) - 10;
+  
+  int index = 0;
+  int width = 0;
+  int line_number = 0;
+
+  const char *Line = _("Line");
+  char *leader = local_alloc (strlen (Line) + INT_DIGITS + 1 + 1);
+      
+  while (index < cmd.n_variables)
+    {
+      struct outp_text text;
+
+      /* Ensure that there is enough room for a line of text. */
+      if (d->cp_y + d->font_height > d->length)
+       outp_eject_page (d);
+      
+      /* The leader is a string like `Line 1: '.  Write the leader. */
+      sprintf(leader, "%s %d:", Line, ++line_number);
+      text.options = OUTP_T_JUST_LEFT;
+      ls_init (&text.s, leader, strlen (leader));
+      text.x = 0;
+      text.y = d->cp_y;
+      d->class->text_draw (d, &text);
+      d->cp_x = text.h;
+
+      goto entry;
+      do
+       {
+         width++;
+
+       entry:
+         {
+           int var_width = cmd.v_variables[index]->print.w;
+           if (width + var_width > max_width && width != 0)
+             {
+               width = 0;
+               d->cp_x = 0;
+               d->cp_y += d->font_height;
+               break;
+             }
+           width += var_width;
+         }
+         
+         {
+           char varname[10];
+           sprintf (varname, " %s", cmd.v_variables[index]->name);
+           write_varname (d, varname, text.h);
+         }
+       }
+      while (++index < cmd.n_variables);
+
+    }
+  d->cp_x = 0;
+  d->cp_y += d->font_height;
+  
+  local_free (leader);
+}
+
+/* There are three possible layouts for the LIST procedure:
+
+   1. If the values and their variables' name fit across the page,
+   then they are listed across the page in that way.
+
+   2. If the values can fit across the page, but not the variable
+   names, then as many variable names as necessary are printed
+   vertically to compensate.
+
+   3. If not even the values can fit across the page, the variable
+   names are listed just once, at the beginning, in a compact format,
+   and the values are listed with a variable name label at the
+   beginning of each line for easier reference.
+
+   This is complicated by the fact that we have to do all this for
+   every output driver, not just once.  */
+static void
+determine_layout (void)
+{
+  struct outp_driver *d;
+  
+  /* This is the largest page width of any driver, so we can tell what
+     size buffer to allocate. */
+  int largest_page_width = 0;
+  
+  for (d = outp_drivers (NULL); d; d = outp_drivers (d))
+    {
+      int column;      /* Current column. */
+      int width;       /* Accumulated width. */
+      int max_width;   /* Page width. */
+      
+      struct list_ext *prc;
+
+      if (d->class == &html_class)
+       continue;
+      
+      assert (d->class->special == 0);
+
+      if (!d->page_open)
+       d->class->open_page (d);
+      
+      max_width = n_chars_width (d);
+      largest_page_width = max (largest_page_width, max_width);
+
+      prc = d->prc = xmalloc (sizeof *prc);
+      prc->type = 0;
+      prc->n_vertical = 0;
+      prc->header = NULL;
+
+      /* Try layout #1. */
+      for (width = cmd.n_variables - 1, column = 0; column < cmd.n_variables; column++)
+       {
+         struct variable *v = cmd.v_variables[column];
+         width += max (v->print.w, (int) strlen (v->name));
+       }
+      if (width <= max_width)
+       {
+         prc->header_rows = 2;
+         d->class->text_set_font_by_name (d, "FIXED");
+         continue;
+       }
+
+      /* Try layout #2. */
+      for (width = cmd.n_variables - 1, column = 0;
+          column < cmd.n_variables && width <= max_width;
+          column++)
+         width += cmd.v_variables[column]->print.w;
+      
+      /* If it fit then we need to determine how many labels can be
+         written horizontally. */
+      if (width <= max_width)
+       {
+#ifndef NDEBUG
+         prc->n_vertical = -1;
+#endif
+         for (column = cmd.n_variables - 1; column >= 0; column--)
+           {
+             struct variable *v = cmd.v_variables[column];
+             int trial_width = (width - v->print.w
+                                + max (v->print.w, (int) strlen (v->name)));
+             
+             if (trial_width > max_width)
+               {
+                 prc->n_vertical = column + 1;
+                 break;
+               }
+             width = trial_width;
+           }
+         assert(prc->n_vertical != -1);
+
+         prc->n_vertical = cmd.n_variables;
+         /* Finally determine the length of the headers. */
+         for (prc->header_rows = 0, column = 0;
+              column < prc->n_vertical;
+              column++)
+           prc->header_rows = max (prc->header_rows,
+                                   (int) strlen (cmd.v_variables[column]->name));
+         prc->header_rows++;
+
+         d->class->text_set_font_by_name (d, "FIXED");
+         continue;
+       }
+
+      /* Otherwise use the ugly fallback listing format. */
+      prc->type = 1;
+      prc->header_rows = 0;
+
+      d->cp_y += d->font_height;
+      write_fallback_headers (d);
+      d->cp_y += d->font_height;
+      d->class->text_set_font_by_name (d, "FIXED");
+    }
+
+  line_buf = xmalloc (max (1022, largest_page_width) + 2);
+}
+
+static int
+list_cases (struct ccase *c)
+{
+  struct outp_driver *d;
+  
+  case_num++;
+  if (case_num < cmd.first || case_num > cmd.last
+      || (cmd.step != 1 && (case_num - cmd.first) % cmd.step))
+    return 1;
+
+  for (d = outp_drivers (NULL); d; d = outp_drivers (d))
+    if (d->class->special == 0)
+      {
+       const struct list_ext *prc = d->prc;
+       const int max_width = n_chars_width (d);
+       int column;
+       int x = 0;
+
+       if (!prc->header_rows)
+         x = nsprintf (line_buf, "%8s: ", cmd.v_variables[0]->name);
+      
+       for (column = 0; column < cmd.n_variables; column++)
+         {
+           struct variable *v = cmd.v_variables[column];
+           int width;
+
+           if (prc->type == 0 && column >= prc->n_vertical)
+             width = max ((int) strlen (v->name), v->print.w);
+           else
+             width = v->print.w;
+
+           if (width + x > max_width && x != 0)
+             {
+               if (!n_lines_remaining (d))
+                 {
+                   outp_eject_page (d);
+                   write_header (d);
+                 }
+             
+               line_buf[x] = 0;
+               write_line (d, line_buf);
+
+               x = 0;
+               if (!prc->header_rows)
+                 x = nsprintf (line_buf, "%8s: ", v->name);
+             }
+
+           if (width > v->print.w)
+             {
+               memset(&line_buf[x], ' ', width - v->print.w);
+               x += width - v->print.w;
+             }
+         
+           {
+             union value value;
+           
+             if (formats[v->print.type].cat & FCAT_STRING)
+               value.c = c->data[v->fv].s;
+             else if (v->fv == -1)
+               value.f = case_num;
+             else
+               value.f = c->data[v->fv].f;
+               
+             data_out (&line_buf[x], &v->print, &value);
+           }
+           x += v->print.w;
+         
+           line_buf[x++] = ' ';
+         }
+      
+       if (!n_lines_remaining (d))
+         {
+           outp_eject_page (d);
+           write_header (d);
+         }
+             
+       line_buf[x] = 0;
+       write_line (d, line_buf);
+      }
+    else if (d->class == &html_class)
+      {
+       struct html_driver_ext *x = d->ext;
+       int column;
+
+       fputs ("  <TR>\n", x->file.file);
+       
+       for (column = 0; column < cmd.n_variables; column++)
+         {
+           struct variable *v = cmd.v_variables[column];
+           union value value;
+           char buf[41];
+           
+           if (formats[v->print.type].cat & FCAT_STRING)
+             value.c = c->data[v->fv].s;
+           else if (v->fv == -1)
+             value.f = case_num;
+           else
+             value.f = c->data[v->fv].f;
+               
+           data_out (buf, &v->print, &value);
+           buf[v->print.w] = 0;
+
+           fprintf (x->file.file, "    <TD ALIGN=RIGHT>%s</TD>\n",
+                    &buf[strspn (buf, " ")]);
+         }
+         
+       fputs ("  </TR>\n", x->file.file);
+      }
+    else
+      assert (0);
+
+  return 1;
+}
+\f
+/* Debugging output. */
+
+#if DEBUGGING
+/* Prints out the command as parsed by cmd_list(). */
+static void
+debug_print (void)
+{
+  int i;
+
+  puts ("LIST");
+  printf ("  VARIABLES=");
+  for (i = 0; i < cmd.n_variables; i++)
+    {
+      if (i)
+       putc (' ', stdout);
+      fputs (cmd.v_variables[i]->name, stdout);
+    }
+
+  printf ("\n  /CASES=FROM %ld TO %ld BY %ld\n", first, last, step);
+
+  fputs ("  /FORMAT=", stdout);
+  if (numbering == NUMBERED)
+    fputs ("NUMBERED", stdout);
+  else
+    fputs ("UNNUMBERED", stdout);
+  putc (' ', stdout);
+  if (wrap == WRAP)
+    fputs ("WRAP", stdout);
+  else
+    fputs ("SINGLE", stdout);
+  putc (' ', stdout);
+  if (weight == WEIGHT)
+    fputs ("WEIGHT", stdout);
+  else
+    fputs ("NOWEIGHT", stdout);
+  puts (".");
+}
+#endif /* DEBUGGING */
+
+/* 
+   Local Variables:
+   mode: c
+   End:
+*/
diff --git a/src/log.h b/src/log.h
new file mode 100644 (file)
index 0000000..0598206
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,38 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !log_h
+#define log_h 1
+
+#include <stdio.h>
+
+/* Whether logging is on. */
+extern int logging;
+
+/* The name of the log file. */
+extern char *logfn;
+
+/* The log file stream. */
+extern FILE *logfile;
+
+/* Log file management. */
+void open_logfile (void);
+void close_logfile (void);
+
+#endif /* !log_h */
diff --git a/src/loop.c b/src/loop.c
new file mode 100644 (file)
index 0000000..b7dac89
--- /dev/null
@@ -0,0 +1,612 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include "alloc.h"
+#include "approx.h"
+#include "command.h"
+#include "do-ifP.h"
+#include "error.h"
+#include "expr.h"
+#include "lexer.h"
+#include "settings.h"
+#include "str.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* *INDENT-OFF* */
+/* LOOP strategy:
+
+   Each loop causes 3 different transformations to be output.  The
+   first two are output when the LOOP command is encountered; the last
+   is output when the END LOOP command is encountered.
+
+   The first to be output resets the pass number in the second
+   transformation to -1.  This ensures that the pass number is set to
+   -1 every time the loop is encountered, before the first iteration.
+
+   The second transformation increments the pass number.  If there is
+   no indexing or test clause on either LOOP or END LOOP, then the
+   pass number is checked against MXLOOPS and control may pass out of
+   the loop; otherwise the indexing or test clause(s) on LOOP are
+   checked, and again control may pass out of the loop.
+
+   After the second transformation the body of the loop is executed.
+
+   The last transformation checks the test clause if present and
+   either jumps back up to the second transformation or terminates the
+   loop.
+
+   Flow of control: (The characters ^V<> represents arrows.)
+
+     1. LOOP (sets pass # to -1)
+        V
+        V
+   >>2. LOOP (increment pass number)
+   ^         (test optional indexing clause)
+   ^         (test optional IF clause)
+   ^    if we need another trip     if we're done with the loop>>V
+   ^     V                                                       V
+   ^     V                                                         V
+   ^ *. execute loop body                                          V
+   ^    .                                                          V
+   ^    .   (any number of transformations)                        V
+   ^    .                                                          V
+   ^                                                             V
+   ^ 3. END LOOP (test optional IF clause)                         V
+   ^<<<<if we need another trip     if we're done with the loop>>V
+                                                                V
+                                                                V
+     *. transformations after loop body<<<<<<<<<<<<<<<<<<<<<<<<<<<
+
+ */
+/* *INDENT-ON* */
+
+/* Types of limits on loop execution. */
+enum
+  {
+    LPC_INDEX = 001,           /* Limited by indexing clause. */
+    LPC_COND = 002,            /* Limited by IF clause. */
+    LPC_RINDEX = 004           /* Indexing clause counts downward, at least
+                                  for this pass thru the loop. */
+  };
+
+/* LOOP transformation 1. */
+struct loop_1_trns
+  {
+    struct trns_header h;
+
+    struct loop_2_trns *two;   /* Allows modification of associated
+                                  second transformation. */
+
+    struct expression *init;   /* Starting index. */
+    struct expression *incr;   /* Index increment. */
+    struct expression *term;   /* Terminal index. */
+  };
+
+/* LOOP transformation 2. */
+struct loop_2_trns
+  {
+    struct trns_header h;
+
+    struct ctl_stmt ctl;       /* Nesting control info. */
+
+    int flags;                 /* Types of limits on loop execution. */
+    int pass;                  /* Number of passes thru the loop so far. */
+
+    struct variable *index;    /* Index variable. */
+    double curr;               /* Current index. */
+    double incr;               /* Increment. */
+    double term;               /* Terminal index. */
+
+    struct expression *cond;   /* Optional IF condition when non-NULL. */
+
+    int loop_term;             /* 1+(t_trns[] index of transformation 3);
+                                  backpatched in by END LOOP. */
+  };
+
+/* LOOP transformation 3.  (Actually output by END LOOP.)  */
+struct loop_3_trns
+  {
+    struct trns_header h;
+
+    struct expression *cond;   /* Optional IF condition when non-NULL. */
+
+    int loop_start;            /* t_trns[] index of transformation 2. */
+  };
+
+/* LOOP transformations being created. */
+static struct loop_1_trns *one;
+static struct loop_2_trns *two;
+static struct loop_3_trns *thr;
+
+static int internal_cmd_loop (void);
+static int internal_cmd_end_loop (void);
+static int break_trns_proc (struct trns_header *, struct ccase *);
+static int loop_1_trns_proc (struct trns_header *, struct ccase *);
+static void loop_1_trns_free (struct trns_header *);
+static int loop_2_trns_proc (struct trns_header *, struct ccase *);
+static void loop_2_trns_free (struct trns_header *);
+static int loop_3_trns_proc (struct trns_header *, struct ccase *);
+static void loop_3_trns_free (struct trns_header *);
+static void pop_ctl_stack (void);
+\f
+/* LOOP. */
+
+/* Parses a LOOP command.  Passes the real work off to
+   internal_cmd_loop(). */
+int
+cmd_loop (void)
+{
+  if (!internal_cmd_loop ())
+    {
+      loop_1_trns_free ((struct trns_header *) one);
+      loop_2_trns_free ((struct trns_header *) two);
+      return CMD_FAILURE;
+    }
+
+  return CMD_SUCCESS;
+}
+
+/* Parses a LOOP command, returns success. */
+static int
+internal_cmd_loop (void)
+{
+  /* Name of indexing variable if applicable. */
+  char name[9];
+
+  lex_match_id ("LOOP");
+
+  /* Create and initialize transformations to facilitate
+     error-handling. */
+  two = xmalloc (sizeof *two);
+  two->h.proc = loop_2_trns_proc;
+  two->h.free = loop_2_trns_free;
+  two->cond = NULL;
+  two->flags = 0;
+
+  one = xmalloc (sizeof *one);
+  one->h.proc = loop_1_trns_proc;
+  one->h.free = loop_1_trns_free;
+  one->init = one->incr = one->term = NULL;
+  one->two = two;
+
+  /* Parse indexing clause. */
+  if (token == T_ID && lex_look_ahead () == '=')
+    {
+      struct variable *v = find_variable (tokid);
+
+      two->flags |= LPC_INDEX;
+
+      if (v && v->type == ALPHA)
+       {
+         msg (SE, _("The index variable may not be a string variable."));
+         return 0;
+       }
+      strcpy (name, tokid);
+
+      lex_get ();
+      assert (token == '=');
+      lex_get ();
+
+      one->init = expr_parse (PXP_NUMERIC);
+      if (!one->init)
+       return 0;
+
+      if (!lex_force_match (T_TO))
+       {
+         expr_free (one->init);
+         return 0;
+       }
+      one->term = expr_parse (PXP_NUMERIC);
+      if (!one->term)
+       {
+         expr_free (one->init);
+         return 0;
+       }
+
+      if (lex_match (T_BY))
+       {
+         one->incr = expr_parse (PXP_NUMERIC);
+         if (!one->incr)
+           return 0;
+       }
+    }
+  else
+    name[0] = 0;
+
+  /* Parse IF clause. */
+  if (lex_match_id ("IF"))
+    {
+      two->flags |= LPC_COND;
+
+      two->cond = expr_parse (PXP_BOOLEAN);
+      if (!two->cond)
+       return 0;
+    }
+
+  if (token != '.')
+    {
+      lex_error (_("expecting end of command"));
+      return 0;
+    }
+
+  /* Find variable; create if necessary. */
+  if (name[0])
+    {
+      two->index = find_variable (name);
+      if (!two->index)
+       {
+         two->index = force_create_variable (&default_dict, name, NUMERIC, 0);
+#if DEBUGGING
+         envector (two->index);
+#endif
+       }
+    }
+  
+  /* Push on control stack. */
+  two->ctl.down = ctl_stack;
+  two->ctl.type = CST_LOOP;
+  two->ctl.trns = (struct trns_header *) two;
+  two->ctl.brk = NULL;
+  ctl_stack = &two->ctl;
+
+  /* Dump out the transformations. */
+  add_transformation ((struct trns_header *) one);
+  add_transformation ((struct trns_header *) two);
+
+#if DEBUGGING
+  printf ("LOOP");
+  if (two->flags & LPC_INDEX)
+    printf ("(INDEX)");
+  if (two->flags & LPC_COND)
+    printf ("(IF)");
+  printf ("\n");
+#endif
+
+  return 1;
+}
+
+/* Parses the END LOOP command by passing the buck off to
+   cmd_internal_end_loop(). */
+int
+cmd_end_loop (void)
+{
+  if (!internal_cmd_end_loop ())
+    {
+      loop_3_trns_free ((struct trns_header *) thr);
+      if (ctl_stack && ctl_stack->type == CST_LOOP)
+       pop_ctl_stack ();
+      return CMD_FAILURE;
+    }
+
+  return CMD_SUCCESS;
+}
+
+/* Parses the END LOOP command. */
+int
+internal_cmd_end_loop (void)
+{
+  /* Backpatch pointer for BREAK commands. */
+  struct break_trns *brk;
+
+  /* Allocate, initialize transformation to facilitate
+     error-handling. */
+  thr = xmalloc (sizeof *thr);
+  thr->h.proc = loop_3_trns_proc;
+  thr->h.free = loop_3_trns_free;
+  thr->cond = NULL;
+
+  /* There must be a matching LOOP command. */
+  if (!ctl_stack || ctl_stack->type != CST_LOOP)
+    {
+      msg (SE, _("There is no LOOP command that corresponds to this "
+                "END LOOP."));
+      return 0;
+    }
+  thr->loop_start = ((struct loop_2_trns *) ctl_stack->trns)->h.index;
+
+  /* Parse the expression if any. */
+  if (lex_match_id ("IF"))
+    {
+      thr->cond = expr_parse (PXP_BOOLEAN);
+      if (!thr->cond)
+       return 0;
+    }
+
+  add_transformation ((struct trns_header *) thr);
+
+  /* Backpatch. */
+  ((struct loop_2_trns *) ctl_stack->trns)->loop_term = n_trns;
+  for (brk = ctl_stack->brk; brk; brk = brk->next)
+    brk->loop_term = n_trns;
+
+  /* Pop off the top of stack. */
+  ctl_stack = ctl_stack->down;
+
+#if DEBUGGING
+  printf ("END LOOP");
+  if (thr->cond)
+    printf ("(IF)");
+  printf ("\n");
+#endif
+
+  return 1;
+}
+
+/* Performs transformation 1. */
+static int
+loop_1_trns_proc (struct trns_header * trns, struct ccase * c)
+{
+  struct loop_1_trns *one = (struct loop_1_trns *) trns;
+  struct loop_2_trns *two = one->two;
+
+  two->pass = -1;
+  if (two->flags & LPC_INDEX)
+    {
+      union value t1, t2, t3;
+
+      expr_evaluate (one->init, c, &t1);
+      if (one->incr)
+       expr_evaluate (one->incr, c, &t2);
+      else
+       t2.f = 1.0;
+      expr_evaluate (one->term, c, &t3);
+
+      /* Even if the loop is never entered, force the index variable
+         to assume the initial value. */
+      c->data[two->index->fv].f = t1.f;
+
+      /* Throw out various pathological cases. */
+      if (!finite (t1.f) || !finite (t2.f) || !finite (t3.f)
+         || approx_eq (t2.f, 0.0))
+       return two->loop_term;
+      debug_printf (("LOOP %s=%g TO %g BY %g.\n", two->index->name,
+                    t1.f, t3.f, t2.f));
+      if (t2.f > 0.0)
+       {
+         /* Loop counts upward: I=1 TO 5 BY 1. */
+         two->flags &= ~LPC_RINDEX;
+
+         /* incr>0 but init>term */
+         if (approx_gt (t1.f, t3.f))
+           return two->loop_term;
+       }
+      else
+       {
+         /* Loop counts downward: I=5 TO 1 BY -1. */
+         two->flags |= LPC_RINDEX;
+
+         /* incr<0 but init<term */
+         if (approx_lt (t1.f, t3.f))
+           return two->loop_term;
+       }
+
+      two->curr = t1.f;
+      two->incr = t2.f;
+      two->term = t3.f;
+    }
+
+  return -1;
+}
+
+/* Frees transformation 1. */
+static void
+loop_1_trns_free (struct trns_header * trns)
+{
+  struct loop_1_trns *one = (struct loop_1_trns *) trns;
+
+  expr_free (one->init);
+  expr_free (one->incr);
+  expr_free (one->term);
+}
+
+/* Performs transformation 2. */
+static int
+loop_2_trns_proc (struct trns_header * trns, struct ccase * c)
+{
+  struct loop_2_trns *two = (struct loop_2_trns *) trns;
+
+  /* MXLOOPS limiter. */
+  if (two->flags == 0)
+    {
+      two->pass++;
+      if (two->pass > set_mxloops)
+         return two->loop_term;
+    }
+
+  /* Indexing clause limiter: counting downward. */
+  if (two->flags & LPC_RINDEX)
+    {
+      /* Test if we're at the end of the looping. */
+      if (approx_lt (two->curr, two->term))
+       return two->loop_term;
+
+      /* Set the current value into the case. */
+      c->data[two->index->fv].f = two->curr;
+
+      /* Decrement the current value. */
+      two->curr += two->incr;
+    }
+  /* Indexing clause limiter: counting upward. */
+  else if (two->flags & LPC_INDEX)
+    {
+      /* Test if we're at the end of the looping. */
+      if (approx_gt (two->curr, two->term))
+       return two->loop_term;
+
+      /* Set the current value into the case. */
+      c->data[two->index->fv].f = two->curr;
+
+      /* Increment the current value. */
+      two->curr += two->incr;
+    }
+
+  /* Conditional clause limiter. */
+  if ((two->flags & LPC_COND)
+      && expr_evaluate (two->cond, c, NULL) != 1.0)
+    return two->loop_term;
+
+  return -1;
+}
+
+/* Frees transformation 2. */
+static void
+loop_2_trns_free (struct trns_header * trns)
+{
+  struct loop_2_trns *two = (struct loop_2_trns *) trns;
+
+  expr_free (two->cond);
+}
+
+/* Performs transformation 3. */
+static int
+loop_3_trns_proc (struct trns_header * trns, struct ccase * c)
+{
+  struct loop_3_trns *thr = (struct loop_3_trns *) trns;
+
+  /* Note that it breaks out of the loop if the expression is true *or
+     missing*.  This is conformant. */
+  if (thr->cond && expr_evaluate (two->cond, c, NULL) != 0.0)
+    return -1;
+
+  return thr->loop_start;
+}
+
+/* Frees transformation 3. */
+static void
+loop_3_trns_free (struct trns_header * trns)
+{
+  struct loop_3_trns *thr = (struct loop_3_trns *) trns;
+
+  expr_free (thr->cond);
+}
+\f
+/* BREAK. */
+
+/* Parses the BREAK command. */
+int
+cmd_break (void)
+{
+  /* Climbs down the stack to find a LOOP. */
+  struct ctl_stmt *loop;
+
+  /* New transformation. */
+  struct break_trns *t;
+
+  lex_match_id ("BREAK");
+
+  for (loop = ctl_stack; loop; loop = loop->down)
+    if (loop->type == CST_LOOP)
+      break;
+  if (!loop)
+    {
+      msg (SE, _("This command may only appear enclosed in a LOOP/"
+                "END LOOP control structure."));
+      return CMD_FAILURE;
+    }
+  
+  if (ctl_stack->type != CST_DO_IF)
+    msg (SW, _("BREAK not enclosed in DO IF structure."));
+
+  t = xmalloc (sizeof *t);
+  t->h.proc = break_trns_proc;
+  t->h.free = NULL;
+  t->next = loop->brk;
+  loop->brk = t;
+  add_transformation ((struct trns_header *) t);
+
+  return lex_end_of_command ();
+}
+
+static int
+break_trns_proc (struct trns_header * trns, struct ccase * c unused)
+{
+  return ((struct break_trns *) trns)->loop_term;
+}
+\f
+/* Control stack operations. */
+
+/* Pops the top of stack element off of ctl_stack.  Does not
+   check that ctl_stack is indeed non-NULL. */
+static void
+pop_ctl_stack (void)
+{
+  switch (ctl_stack->type)
+    {
+    case CST_LOOP:
+      {
+       /* Pointer for chasing down and backpatching BREAKs. */
+       struct break_trns *brk;
+
+       /* Terminate the loop. */
+       thr = xmalloc (sizeof *thr);
+       thr->h.proc = loop_3_trns_proc;
+       thr->h.free = loop_3_trns_free;
+       thr->cond = NULL;
+       thr->loop_start = ((struct loop_2_trns *) ctl_stack->trns)->h.index;
+       add_transformation ((struct trns_header *) thr);
+
+       /* Backpatch. */
+       ((struct loop_2_trns *) ctl_stack->trns)->loop_term = n_trns;
+       for (brk = ctl_stack->brk; brk; brk = brk->next)
+         brk->loop_term = n_trns;
+      }
+      break;
+    case CST_DO_IF:
+      {
+       /* List iterator. */
+       struct do_if_trns *iter;
+
+       iter = ((struct do_if_trns *) ctl_stack->trns);
+       for (;;)
+         {
+           if (iter->brk)
+             iter->brk->dest = n_trns;
+           iter->missing_jump = n_trns;
+           if (iter->next)
+             iter = iter->next;
+           else
+             break;
+         }
+       iter->false_jump = n_trns;
+      }
+      break;
+    default:
+      assert (0);
+    }
+  ctl_stack = ctl_stack->down;
+}
+
+/* Checks for unclosed LOOPs and DO IFs and closes them out. */
+void
+discard_ctl_stack (void)
+{
+  if (!ctl_stack)
+    return;
+  msg (SE, _("%s without %s."), ctl_stack->type == CST_LOOP ? "LOOP" : "DO IF",
+       ctl_stack->type == CST_LOOP ? "END LOOP" : "END IF");
+  while (ctl_stack)
+    pop_ctl_stack ();
+  ctl_stack = NULL;
+}
diff --git a/src/magic.c b/src/magic.c
new file mode 100644 (file)
index 0000000..d114a91
--- /dev/null
@@ -0,0 +1,33 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include "magic.h"
+
+#if ENDIAN==UNKNOWN
+/* BIG or LITTLE, depending on this machine's endianness, as detected
+   at program startup. */
+int endian;
+#endif
+
+#ifndef SECOND_LOWEST_VALUE
+/* "Second lowest" value for a flt64; that is, (-FLT64_MAX) + epsilon. */
+double second_lowest_value;
+#endif
+
diff --git a/src/magic.h b/src/magic.h
new file mode 100644 (file)
index 0000000..3693f23
--- /dev/null
@@ -0,0 +1,45 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !magic_h
+#define magic_h 1
+
+/* Magic numbers. */
+
+#include <float.h>
+#include <limits.h>
+
+#if ENDIAN != UNKNOWN
+#define endian ENDIAN
+#else
+extern int endian;
+#endif
+
+#ifdef SECOND_LOWEST_VALUE
+#define second_lowest_value SECOND_LOWEST_VALUE
+#else
+extern double second_lowest_value;
+#endif
+
+/* Used when we want a "missing value". */
+#define NOT_DOUBLE (-DBL_MAX)
+#define NOT_LONG LONG_MIN
+#define NOT_INT INT_MIN
+
+#endif /* magic.h */
diff --git a/src/main.c b/src/main.c
new file mode 100644 (file)
index 0000000..e259918
--- /dev/null
@@ -0,0 +1,154 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdio.h>
+#include "command.h"
+#include "error.h"
+#include "getline.h"
+#include "lexer.h"
+#include "output.h"
+
+#include <stdlib.h>
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+static void parse_script (void) __attribute__ ((noreturn));
+static void handle_error (int code);
+static int execute_command (void);
+
+/* argv[0] with stripped leading directories. */
+char *pgmname;
+
+/* Whether FINISH. has been executed. */
+int finished;
+
+/* The current date in the form DD MMM YYYY. */
+char curdate[12];
+
+/* Whether we're dropping down to interactive mode immediately because
+   we hit end-of-file unexpectedly (or whatever). */
+int start_interactive;
+
+/* Program entry point. */
+int
+main (int argc, char **argv)
+{
+  void init_glob (int, char **);       /* Exported by glob.c. */
+  void parse_command_line (int, char **);      /* Exported by cmdline.c */
+
+  /* Initialization. */
+  if (!outp_init ())
+    err_hcf (0);
+  init_glob (argc, argv);
+  parse_command_line (argc, argv);
+  if (!outp_read_devices ())
+    msg (FE, _("Error initializing output drivers."));
+
+  lex_init ();
+  cmd_init ();
+
+  /* Execution. */
+  parse_script ();
+}
+
+/* Parses the entire script. */
+static void
+parse_script (void)
+{
+  while (!finished)
+    {
+      err_check_count ();
+      handle_error (execute_command ());
+    }
+
+  err_hcf (1);
+}
+
+/* Parse and execute a command, returning its return code. */
+static int
+execute_command (void)
+{
+  /* Read the command's first token.
+     We may hit end of file.
+     If so, give the line reader a chance to proceed to the next file.
+     End of file is not handled transparently since the user may want
+     the dictionary cleared between files. */
+  getl_prompt = GETL_PRPT_STANDARD;
+  for (;;)
+    {
+      lex_get ();
+      if (token != T_STOP)
+       break;
+
+      if (!getl_perform_delayed_reset ())
+       err_hcf (1);
+    }
+
+  /* Parse the command. */
+  getl_prompt = GETL_PRPT_CONTINUATION;
+  return cmd_parse ();
+}
+
+/* Print an error message corresponding to the command return code
+   CODE. */
+static void
+handle_error (int code)
+{
+  switch (code)
+    {
+    case CMD_SUCCESS:
+      return;
+         
+    case CMD_FAILURE:
+      msg (SW,  _("This command not executed."));
+      break;
+
+    case CMD_PART_SUCCESS_MAYBE:
+      msg (SW, _("Skipping the rest of this command.  Part of "
+                "this command may have been executed."));
+      break;
+                 
+    case CMD_PART_SUCCESS:
+      msg (SW, _("Skipping the rest of this command.  This "
+                "command was fully executed up to this point."));
+      break;
+
+    case CMD_TRAILING_GARBAGE:
+      msg (SW, _("Trailing garbage was encountered following "
+                "this command.  The command was fully executed "
+                "to this point."));
+      break;
+
+    default:
+      assert (0);
+    }
+
+  if (getl_reading_script)
+    {
+      err_break ();
+      while (token != T_STOP && token != '.')
+       lex_get ();
+    }
+  else
+    lex_discard_line ();
+}
diff --git a/src/main.h b/src/main.h
new file mode 100644 (file)
index 0000000..076882b
--- /dev/null
@@ -0,0 +1,28 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !main_h
+#define main_h 1
+
+extern char *pgmname;
+extern char curdate[];
+extern int start_interactive;
+extern int finished;
+
+#endif /* main.h */
diff --git a/src/matrix-data.c b/src/matrix-data.c
new file mode 100644 (file)
index 0000000..5e4d5f5
--- /dev/null
@@ -0,0 +1,2020 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <float.h>
+#include "alloc.h"
+#include "command.h"
+#include "data-in.h"
+#include "dfm.h"
+#include "error.h"
+#include "file-handle.h"
+#include "lexer.h"
+#include "misc.h"
+#include "pool.h"
+#include "str.h"
+#include "var.h"
+#include "vfm.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* FIXME: /N subcommand not implemented.  It should be pretty simple,
+   too. */
+
+/* Format type enums. */
+enum
+  {
+    LIST,
+    FREE
+  };
+
+/* Matrix section enums. */
+enum
+  {
+    LOWER,
+    UPPER,
+    FULL
+  };
+
+/* Diagonal inclusion enums. */
+enum
+  {
+    DIAGONAL,
+    NODIAGONAL
+  };
+
+/* CONTENTS types. */
+enum
+  {
+    N_VECTOR,
+    N_SCALAR,
+    N_MATRIX,
+    MEAN,
+    STDDEV,
+    COUNT,
+    MSE,
+    DFE,
+    MAT,
+    COV,
+    CORR,
+    PROX,
+    
+    LPAREN,
+    RPAREN,
+    EOC
+  };
+
+/* 0=vector, 1=matrix, 2=scalar. */
+static int content_type[PROX + 1] = 
+  {
+    0, 2, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1,
+  };
+
+/* Name of each content type. */
+static const char *content_names[PROX + 1] =
+  {
+    "N", "N", "N_MATRIX", "MEAN", "STDDEV", "COUNT", "MSE",
+    "DFE", "MAT", "COV", "CORR", "PROX",
+  };
+
+/* The data file to be read. */
+static struct file_handle *data_file;
+
+/* Format type. */
+static int fmt;                        /* LIST or FREE. */
+static int section;            /* LOWER or UPPER or FULL. */
+static int diag;               /* DIAGONAL or NODIAGONAL. */
+
+/* Arena used for all the MATRIX DATA allocations. */
+static struct pool *container;
+
+/* ROWTYPE_ specified explicitly in data? */
+static int explicit_rowtype;
+
+/* ROWTYPE_, VARNAME_ variables. */
+static struct variable *rowtype_, *varname_;
+
+/* Is is per-factor data? */
+int is_per_factor[PROX + 1];
+
+/* Single SPLIT FILE variable. */
+static struct variable *single_split;
+
+/* Factor variables.  */
+static int n_factors;
+static struct variable **factors;
+
+/* Number of cells, or -1 if none. */
+static int cells;
+
+/* Population N specified by user. */
+static int pop_n;
+
+/* CONTENTS subcommand. */
+static int contents[EOC * 3 + 1];
+static int n_contents;
+
+/* Number of continuous variables. */
+static int n_continuous;
+
+/* Index into default_dict.var of first continuous variables. */
+static int first_continuous;
+
+static int compare_variables_by_mxd_vartype (const void *pa,
+                                            const void *pb);
+static void read_matrices_without_rowtype (void);
+static void read_matrices_with_rowtype (void);
+static int string_to_content_type (char *, int *);
+
+#if DEBUGGING
+static void debug_print (void);
+#endif
+
+int
+cmd_matrix_data (void)
+{
+  unsigned seen = 0;
+  
+  lex_match_id ("MATRIX");
+  lex_match_id ("DATA");
+
+  container = pool_create ();
+
+  discard_variables ();
+
+  data_file = inline_file;
+  fmt = LIST;
+  section = LOWER;
+  diag = DIAGONAL;
+  single_split = NULL;
+  n_factors = 0;
+  factors = NULL;
+  cells = -1;
+  pop_n = -1;
+  n_contents = 0;
+  while (token != '.')
+    {
+      lex_match ('/');
+
+      if (lex_match_id ("VARIABLES"))
+       {
+         char **v;
+         int nv;
+
+         if (seen & 1)
+           {
+             msg (SE, _("VARIABLES subcommand multiply specified."));
+             goto lossage;
+           }
+         seen |= 1;
+         
+         lex_match ('=');
+         if (!parse_DATA_LIST_vars (&v, &nv, PV_NO_DUPLICATE))
+           goto lossage;
+         
+         {
+           int i;
+
+           for (i = 0; i < nv; i++)
+             if (!strcmp (v[i], "VARNAME_"))
+               {
+                 msg (SE, _("VARNAME_ cannot be explicitly specified on "
+                            "VARIABLES."));
+                 for (i = 0; i < nv; i++)
+                   free (v[i]);
+                 free (v);
+                 goto lossage;
+               }
+         }
+         
+         {
+           int i;
+
+           for (i = 0; i < nv; i++)
+             {
+               struct variable *new_var;
+               
+               if (strcmp (v[i], "ROWTYPE_"))
+                 {
+                   new_var = force_create_variable (&default_dict, v[i],
+                                                    NUMERIC, 0);
+                   new_var->p.mxd.vartype = MXD_CONTINUOUS;
+                   new_var->p.mxd.subtype = i;
+                 }
+               else
+                 explicit_rowtype = 1;
+               free (v[i]);
+             }
+           free (v);
+         }
+         
+         {
+           rowtype_ = force_create_variable (&default_dict, "ROWTYPE_",
+                                             ALPHA, 8);
+           rowtype_->p.mxd.vartype = MXD_ROWTYPE;
+           rowtype_->p.mxd.subtype = 0;
+         }
+       }
+      else if (lex_match_id ("FILE"))
+       {
+         lex_match ('=');
+         data_file = fh_parse_file_handle ();
+         if (!data_file)
+           goto lossage;
+       }
+      else if (lex_match_id ("FORMAT"))
+       {
+         lex_match ('=');
+
+         while (token == T_ID)
+           {
+             if (lex_match_id ("LIST"))
+               fmt = LIST;
+             else if (lex_match_id ("FREE"))
+               fmt = FREE;
+             else if (lex_match_id ("LOWER"))
+               section = LOWER;
+             else if (lex_match_id ("UPPER"))
+               section = UPPER;
+             else if (lex_match_id ("FULL"))
+               section = FULL;
+             else if (lex_match_id ("DIAGONAL"))
+               diag = DIAGONAL;
+             else if (lex_match_id ("NODIAGONAL"))
+               diag = NODIAGONAL;
+             else 
+               {
+                 lex_error (_("in FORMAT subcommand"));
+                 goto lossage;
+               }
+           }
+       }
+      else if (lex_match_id ("SPLIT"))
+       {
+         lex_match ('=');
+
+         if (seen & 2)
+           {
+             msg (SE, _("SPLIT subcommand multiply specified."));
+             goto lossage;
+           }
+         seen |= 2;
+         
+         if (token != T_ID)
+           {
+             lex_error (_("in SPLIT subcommand"));
+             goto lossage;
+           }
+         
+         if (!is_varname (tokid)
+             && (lex_look_ahead () == '.' || lex_look_ahead () == '/'))
+           {
+             if (!strcmp (tokid, "ROWTYPE_") || !strcmp (tokid, "VARNAME_"))
+               {
+                 msg (SE, _("Split variable may not be named ROWTYPE_ "
+                            "or VARNAME_."));
+                 goto lossage;
+               }
+
+             single_split = force_create_variable (&default_dict, tokid,
+                                                   NUMERIC, 0);
+             lex_get ();
+
+             single_split->p.mxd.vartype = MXD_CONTINUOUS;
+
+             default_dict.n_splits = 1;
+             default_dict.splits = xmalloc (2 * sizeof *default_dict.splits);
+             default_dict.splits[0] = single_split;
+             default_dict.splits[1] = NULL;
+           }
+         else
+           {
+             struct variable **v;
+             int n;
+
+             if (!parse_variables (NULL, &v, &n, PV_NO_DUPLICATE))
+               goto lossage;
+
+             default_dict.n_splits = n;
+             default_dict.splits = v = xrealloc (v, sizeof *v * (n + 1));
+             v[n] = NULL;
+           }
+         
+         {
+           int i;
+           
+           for (i = 0; i < default_dict.n_splits; i++)
+             {
+               if (default_dict.splits[i]->p.mxd.vartype != MXD_CONTINUOUS)
+                 {
+                   msg (SE, _("Split variable %s is already another type."),
+                        tokid);
+                   goto lossage;
+                 }
+               default_dict.splits[i]->p.mxd.vartype = MXD_SPLIT;
+               default_dict.splits[i]->p.mxd.subtype = i;
+             }
+         }
+       }
+      else if (lex_match_id ("FACTORS"))
+       {
+         lex_match ('=');
+         
+         if (seen & 4)
+           {
+             msg (SE, _("FACTORS subcommand multiply specified."));
+             goto lossage;
+           }
+         seen |= 4;
+
+         if (!parse_variables (NULL, &factors, &n_factors, PV_NONE))
+           goto lossage;
+         
+         {
+           int i;
+           
+           for (i = 0; i < n_factors; i++)
+             {
+               if (factors[i]->p.mxd.vartype != MXD_CONTINUOUS)
+                 {
+                   msg (SE, _("Factor variable %s is already another type."),
+                        tokid);
+                   goto lossage;
+                 }
+               factors[i]->p.mxd.vartype = MXD_FACTOR;
+               factors[i]->p.mxd.subtype = i;
+             }
+         }
+       }
+      else if (lex_match_id ("CELLS"))
+       {
+         lex_match ('=');
+         
+         if (cells != -1)
+           {
+             msg (SE, _("CELLS subcommand multiply specified."));
+             goto lossage;
+           }
+
+         if (!lex_integer_p () || lex_integer () < 1)
+           {
+             lex_error (_("expecting positive integer"));
+             goto lossage;
+           }
+
+         cells = lex_integer ();
+         lex_get ();
+       }
+      else if (lex_match_id ("N"))
+       {
+         lex_match ('=');
+
+         if (pop_n != -1)
+           {
+             msg (SE, _("N subcommand multiply specified."));
+             goto lossage;
+           }
+
+         if (!lex_integer_p () || lex_integer () < 1)
+           {
+             lex_error (_("expecting positive integer"));
+             goto lossage;
+           }
+
+         pop_n = lex_integer ();
+         lex_get ();
+       }
+      else if (lex_match_id ("CONTENTS"))
+       {
+         int inside_parens = 0;
+         unsigned collide = 0;
+         int item;
+         
+         if (seen & 8)
+           {
+             msg (SE, _("CONTENTS subcommand multiply specified."));
+             goto lossage;
+           }
+         seen |= 8;
+
+         lex_match ('=');
+         
+         {
+           int i;
+           
+           for (i = 0; i <= PROX; i++)
+             is_per_factor[i] = 0;
+         }
+
+         for (;;)
+           {
+             if (lex_match ('('))
+               {
+                 if (inside_parens)
+                   {
+                     msg (SE, _("Nested parentheses not allowed."));
+                     goto lossage;
+                   }
+                 inside_parens = 1;
+                 item = LPAREN;
+               }
+             else if (lex_match (')'))
+               {
+                 if (!inside_parens)
+                   {
+                     msg (SE, _("Mismatched right parenthesis (`(')."));
+                     goto lossage;
+                   }
+                 if (contents[n_contents - 1] == LPAREN)
+                   {
+                     msg (SE, _("Empty parentheses not allowed."));
+                     goto lossage;
+                   }
+                 inside_parens = 0;
+                 item = RPAREN;
+               }
+             else 
+               {
+                 int content_type;
+                 int collide_index;
+                 
+                 if (token != T_ID)
+                   {
+                     lex_error (_("in CONTENTS subcommand"));
+                     goto lossage;
+                   }
+
+                 content_type = string_to_content_type (tokid,
+                                                        &collide_index);
+                 if (content_type == -1)
+                   {
+                     lex_error (_("in CONTENTS subcommand"));
+                     goto lossage;
+                   }
+                 lex_get ();
+
+                 if (collide & (1 << collide_index))
+                   {
+                     msg (SE, _("Content multiply specified for %s."),
+                          content_names[content_type]);
+                     goto lossage;
+                   }
+                 collide |= (1 << collide_index);
+                 
+                 item = content_type;
+                 is_per_factor[item] = inside_parens;
+               }
+             contents[n_contents++] = item;
+
+             if (token == '/' || token == '.')
+               break;
+           }
+
+         if (inside_parens)
+           {
+             msg (SE, _("Missing right parenthesis."));
+             goto lossage;
+           }
+         contents[n_contents] = EOC;
+       }
+      else 
+       {
+         lex_error (NULL);
+         goto lossage;
+       }
+    }
+  
+  if (token != '.')
+    {
+      lex_error (_("expecting end of command"));
+      goto lossage;
+    }
+  
+  if (!(seen & 1))
+    {
+      msg (SE, _("Missing VARIABLES subcommand."));
+      goto lossage;
+    }
+  
+  if (!n_contents && !explicit_rowtype)
+    {
+      msg (SW, _("CONTENTS subcommand not specified: assuming file "
+                "contains only CORR matrix."));
+
+      contents[0] = CORR;
+      contents[1] = EOC;
+      n_contents = 0;
+    }
+
+  if (n_factors && !explicit_rowtype && cells == -1)
+    {
+      msg (SE, _("Missing CELLS subcommand.  CELLS is required "
+                "when ROWTYPE_ is not given in the data and "
+                "factors are present."));
+      goto lossage;
+    }
+
+  if (explicit_rowtype && single_split)
+    {
+      msg (SE, _("Split file values must be present in the data when "
+                "ROWTYPE_ is present."));
+      goto lossage;
+    }
+      
+  /* Create VARNAME_. */
+  {
+    varname_ = force_create_variable (&default_dict, "VARNAME_",
+                                     ALPHA, 8);
+    varname_->p.mxd.vartype = MXD_VARNAME;
+    varname_->p.mxd.subtype = 0;
+  }
+  
+  /* Sort the dictionary variables into the desired order for the
+     system file output. */
+  {
+    int i;
+    
+    qsort (default_dict.var, default_dict.nvar, sizeof *default_dict.var,
+          compare_variables_by_mxd_vartype);
+
+    for (i = 0; i < default_dict.nvar; i++)
+      default_dict.var[i]->index = i;
+  }
+
+  /* Set formats. */
+  {
+    static const struct fmt_spec fmt_tab[MXD_COUNT] =
+      {
+       {FMT_F, 4, 0},
+        {FMT_A, 8, 0},
+        {FMT_F, 4, 0},
+       {FMT_A, 8, 0},
+       {FMT_F, 10, 4},
+      };
+    
+    int i;
+
+    first_continuous = -1;
+    for (i = 0; i < default_dict.nvar; i++)
+      {
+       struct variable *v = default_dict.var[i];
+       int type = v->p.mxd.vartype;
+       
+       assert (type >= 0 && type < MXD_COUNT);
+       v->print = v->write = fmt_tab[type];
+
+       if (type == MXD_CONTINUOUS)
+         n_continuous++;
+       if (first_continuous == -1 && type == MXD_CONTINUOUS)
+         first_continuous = i;
+      }
+  }
+
+  if (n_continuous == 0)
+    {
+      msg (SE, _("No continuous variables specified."));
+      goto lossage;
+    }
+
+#if DEBUGGING
+  debug_print ();
+#endif
+
+  if (explicit_rowtype)
+    read_matrices_with_rowtype ();
+  else
+    read_matrices_without_rowtype ();
+
+  pool_destroy (container);
+
+  return CMD_SUCCESS;
+
+lossage:
+  discard_variables ();
+  free (factors);
+  pool_destroy (container);
+  return CMD_FAILURE;
+}
+
+/* Look up string S as a content-type name and return the
+   corresponding enumerated value, or -1 if there is no match.  If
+   COLLIDE is non-NULL then *COLLIDE returns a value (suitable for use
+   as a bit-index) which can be used for determining whether a related
+   statistic has already been used. */
+static int
+string_to_content_type (char *s, int *collide)
+{
+  static const struct
+    {
+      int value;
+      int collide;
+      const char *string;
+    }
+  *tp,
+  tab[] = 
+    {
+      {N_VECTOR, 0, "N_VECTOR"},
+      {N_VECTOR, 0, "N"},
+      {N_SCALAR, 0, "N_SCALAR"},
+      {N_MATRIX, 1, "N_MATRIX"},
+      {MEAN, 2, "MEAN"},
+      {STDDEV, 3, "STDDEV"},
+      {STDDEV, 3, "SD"},
+      {COUNT, 4, "COUNT"},
+      {MSE, 5, "MSE"},
+      {DFE, 6, "DFE"},
+      {MAT, 7, "MAT"},
+      {COV, 8, "COV"},
+      {CORR, 9, "CORR"},
+      {PROX, 10, "PROX"},
+      {-1, -1, NULL},
+    };
+
+  for (tp = tab; tp->value != -1; tp++)
+    if (!strcmp (s, tp->string))
+      {
+       if (collide)
+         *collide = tp->collide;
+       
+       return tp->value;
+      }
+  return -1;
+}
+
+/* Compare two variables using p.mxd.vartype and p.mxd.subtype
+   fields. */
+static int
+compare_variables_by_mxd_vartype (const void *pa, const void *pb)
+{
+  struct matrix_data_proc *a = &(*((struct variable **) pa))->p.mxd;
+  struct matrix_data_proc *b = &(*((struct variable **) pb))->p.mxd;
+  
+  return (a->vartype != b->vartype
+         ? a->vartype - b->vartype
+         : a->subtype - b->subtype);
+}
+
+#if DEBUGGING
+/* Print out the command as input. */
+static void
+debug_print (void)
+{
+  printf ("MATRIX DATA\n\t/VARIABLES=");
+  
+  {
+    int i;
+    
+    for (i = 0; i < default_dict.nvar; i++)
+      printf ("%s ", default_dict.var[i]->name);
+  }
+  printf ("\n");
+
+  printf ("\t/FORMAT=");
+  if (fmt == LIST)
+    printf ("LIST");
+  else if (fmt == FREE)
+    printf ("FREE");
+  else
+    assert (0);
+  if (section == LOWER)
+    printf (" LOWER");
+  else if (section == UPPER)
+    printf (" UPPER");
+  else if (section == FULL)
+    printf (" FULL");
+  else
+    assert (0);
+  if (diag == DIAGONAL)
+    printf (" DIAGONAL\n");
+  else if (diag == NODIAGONAL)
+    printf (" NODIAGONAL\n");
+  else
+    assert (0);
+
+  if (default_dict.n_splits)
+    {
+      int i;
+
+      printf ("\t/SPLIT=");
+      for (i = 0; i < default_dict.n_splits; i++)
+       printf ("%s ", default_dict.splits[i]->name);
+      if (single_split)
+       printf ("\t/* single split");
+      printf ("\n");
+    }
+  
+  if (n_factors)
+    {
+      int i;
+
+      printf ("\t/FACTORS=");
+      for (i = 0; i < n_factors; i++)
+       printf ("%s ", factors[i]->name);
+      printf ("\n");
+    }
+
+  if (cells != -1)
+    printf ("\t/CELLS=%d\n", cells);
+
+  if (pop_n != -1)
+    printf ("\t/N=%d\n", pop_n);
+
+  if (n_contents)
+    {
+      int i;
+      int space = 0;
+      
+      printf ("\t/CONTENTS=");
+      for (i = 0; i < n_contents; i++)
+       {
+         if (contents[i] == LPAREN)
+           {
+             if (space)
+               printf (" ");
+             printf ("(");
+             space = 0;
+           }
+         else if (contents[i] == RPAREN)
+           {
+             printf (")");
+             space = 1;
+           }
+         else 
+           {
+
+             assert (contents[i] >= 0 && contents[i] <= PROX);
+             if (space)
+               printf (" ");
+             printf ("%s", content_names[contents[i]]);
+             space = 1;
+           }
+       }
+      printf ("\n");
+    }
+}
+#endif /* DEBUGGING */
+\f
+/* Matrix tokenizer. */
+
+/* Matrix token types. */
+enum
+  {
+    MNULL,             /* No token. */
+    MNUM,              /* Number. */
+    MSTR,              /* String. */
+    MSTOP              /* End of file. */
+  };
+
+/* Current matrix token. */
+static int mtoken;
+
+/* Token string if applicable; not null-terminated. */
+static char *mtokstr;
+
+/* Length of mtokstr in characters. */
+static int mtoklen;
+
+/* Token value if applicable. */
+static double mtokval;
+
+static int mget_token (void);
+
+#if DEBUGGING
+#define mget_token() mget_token_dump()
+
+static int
+mget_token_dump (void)
+{
+  int result = (mget_token) ();
+  mdump_token ();
+  return result;
+}
+
+static void
+mdump_token (void)
+{
+  switch (mtoken)
+    {
+    case MNULL:
+      printf (" <NULLTOK>");
+      break;
+    case MNUM:
+      printf (" #%g", mtokval);
+      break;
+    case MSTR:
+      printf (" #'%.*s'", mtoklen, mtokstr);
+      break;
+    case MSTOP:
+      printf (" <STOP>");
+      break;
+    default:
+      assert (0);
+    }
+  fflush (stdout);
+}
+#endif
+
+/* Return the current position in the data file. */
+static const char *
+context (void)
+{
+  static char buf[32];
+  int len;
+  char *p = dfm_get_record (data_file, &len);
+  
+  if (!p || !len)
+    strcpy (buf, "at end of line");
+  else
+    {
+      char *cp = buf;
+      int n_copy = min (10, len);
+      cp = stpcpy (buf, "before `");
+      while (n_copy && isspace ((unsigned char) *p))
+       p++, n_copy++;
+      while (n_copy && !isspace ((unsigned char) *p))
+       *cp++ = *p++, n_copy--;
+      *cp++ = '\'';
+      *cp = 0;
+    }
+  
+  return buf;
+}
+
+/* Is there at least one token left in the data file? */
+static int
+another_token (void)
+{
+  char *cp, *ep;
+  int len;
+
+  if (mtoken == MSTOP)
+    return 0;
+  
+  for (;;)
+    {
+      cp = dfm_get_record (data_file, &len);
+      if (!cp)
+       return 0;
+
+      ep = cp + len;
+      while (isspace ((unsigned char) *cp) && cp < ep)
+       cp++;
+
+      if (cp < ep)
+       break;
+
+      dfm_fwd_record (data_file);
+    }
+  
+  dfm_set_record (data_file, cp);
+
+  return 1;
+}
+
+/* Parse a MATRIX DATA token from data_file into mtok*. */
+static int
+(mget_token) (void)
+{
+  char *cp, *ep;
+  int len;
+  int first_column;
+    
+  for (;;)
+    {
+      cp = dfm_get_record (data_file, &len);
+      if (!cp)
+       {
+         if (mtoken == MSTOP)
+           return 0;
+         mtoken = MSTOP;
+         return 1;
+       }
+
+      ep = cp + len;
+      while (isspace ((unsigned char) *cp) && cp < ep)
+       cp++;
+
+      if (cp < ep)
+       break;
+
+      dfm_fwd_record (data_file);
+    }
+  
+  dfm_set_record (data_file, cp);
+  first_column = dfm_get_cur_col (data_file) + 1;
+
+  /* Three types of fields: quoted with ', quoted with ", unquoted. */
+  if (*cp == '\'' || *cp == '"')
+    {
+      int quote = *cp;
+
+      mtoken = MSTR;
+      mtokstr = ++cp;
+      while (cp < ep && *cp != quote)
+       cp++;
+      mtoklen = cp - mtokstr;
+      if (cp < ep)
+       cp++;
+      else
+       msg (SW, _("Scope of string exceeds line."));
+    }
+  else
+    {
+      int is_num = isdigit ((unsigned char) *cp) || *cp == '.';
+
+      mtokstr = cp++;
+      while (cp < ep && !isspace ((unsigned char) *cp) && *cp != ','
+            && *cp != '-' && *cp != '+')
+       {
+         if (isdigit ((unsigned char) *cp))
+           is_num = 1;
+         
+         if ((tolower ((unsigned char) *cp) == 'd'
+              || tolower ((unsigned char) *cp) == 'e')
+             && (cp[1] == '+' || cp[1] == '-'))
+           cp += 2;
+         else
+           cp++;
+       }
+      
+      mtoklen = cp - mtokstr;
+      assert (mtoklen);
+
+      if (is_num)
+       {
+         struct data_in di;
+
+         di.s = mtokstr;
+         di.e = mtokstr + mtoklen;
+         di.v = (union value *) &mtokval;
+         di.f1 = first_column;
+         di.format.type = FMT_F;
+         di.format.w = mtoklen;
+         di.format.d = 0;
+
+         if (!data_in (&di))
+           return 0;
+       }
+      else
+       mtoken = MSTR;
+    }
+
+  dfm_set_record (data_file, cp);
+    
+  return 1;
+}
+
+/* Forcibly skip the end of a line for content type CONTENT in
+   data_file. */
+static int
+force_eol (const char *content)
+{
+  char *cp;
+  int len;
+  
+  if (fmt == FREE)
+    return 1;
+
+  cp = dfm_get_record (data_file, &len);
+  if (!cp)
+    return 0;
+  while (len && isspace (*cp))
+    cp++, len--;
+  
+  if (len)
+    {
+      msg (SE, _("End of line expected %s while reading %s."),
+          context (), content);
+      return 0;
+    }
+  
+  dfm_fwd_record (data_file);
+  
+  return 1;
+}
+\f
+/* Back end, omitting ROWTYPE_. */
+
+/* MATRIX DATA data. */
+static double ***nr_data;
+
+/* Factor values. */
+static double *nr_factor_values;
+
+/* Largest-numbered cell that we have read in thus far, plus one. */
+static int max_cell_index;
+
+/* SPLIT FILE variable values. */
+static double *split_values;
+
+static int nr_read_splits (int compare);
+static int nr_read_factors (int cell);
+static void nr_output_data (void);
+static int matrix_data_read_without_rowtype (void);
+
+/* Read from the data file and write it to the active file. */
+static void
+read_matrices_without_rowtype (void)
+{
+  if (cells == -1)
+    cells = 1;
+  
+  mtoken = MNULL;
+  split_values = xmalloc (sizeof *split_values * default_dict.n_splits);
+  nr_factor_values = xmalloc (sizeof *nr_factor_values * n_factors * cells);
+  max_cell_index = 0;
+
+  matrix_data_source.read = (void (*)(void)) matrix_data_read_without_rowtype;
+  vfm_source = &matrix_data_source;
+  
+  procedure (NULL, NULL, NULL);
+
+  free (split_values);
+  free (nr_factor_values);
+
+  fh_close_handle (data_file);
+}
+
+/* Mirror data across the diagonal of matrix CP which contains
+   CONTENT type data. */
+static void
+fill_matrix (int content, double *cp)
+{
+  int type = content_type[content];
+
+  if (type == 1 && section != FULL)
+    {
+      if (diag == NODIAGONAL)
+       {
+         const double fill = content == CORR ? 1.0 : SYSMIS;
+         int i;
+
+         for (i = 0; i < n_continuous; i++)
+           cp[i * (1 + n_continuous)] = fill;
+       }
+      
+      {
+       int c, r;
+       
+       if (section == LOWER)
+         {
+           int n_lines = n_continuous;
+           if (section != FULL && diag == NODIAGONAL)
+             n_lines--;
+           
+           for (r = 1; r < n_lines; r++)
+             for (c = 0; c < r; c++)
+               cp[r + c * n_continuous] = cp[c + r * n_continuous];
+         }
+       else 
+         {
+           assert (section == UPPER);
+           for (r = 1; r < n_continuous; r++)
+             for (c = 0; c < r; c++)
+               cp[c + r * n_continuous] = cp[r + c * n_continuous];
+         }
+      }
+    }
+  else if (type == 2)
+    {
+      int c;
+
+      for (c = 1; c < n_continuous; c++)
+       cp[c] = cp[0];
+    }
+}
+
+/* Read data lines for content type CONTENT from the data file.  If
+   PER_FACTOR is nonzero, then factor information is read from the
+   data file.  Data is for cell number CELL. */
+static int
+nr_read_data_lines (int per_factor, int cell, int content, int compare)
+{
+  /* Content type. */
+  const int type = content_type[content];
+  
+  /* Number of lines that must be parsed from the data file for this
+     content type. */
+  int n_lines;
+  
+  /* Current position in vector or matrix. */
+  double *cp;
+
+  /* Counter. */
+  int i;
+
+  if (type != 1)
+    n_lines = 1;
+  else
+    {
+      n_lines = n_continuous;
+      if (section != FULL && diag == NODIAGONAL)
+       n_lines--;
+    }
+
+  cp = nr_data[content][cell];
+  if (type == 1 && section == LOWER && diag == NODIAGONAL)
+    cp += n_continuous;
+
+  for (i = 0; i < n_lines; i++)
+    {
+      int n_cols;
+      
+      if (!nr_read_splits (1))
+       return 0;
+      if (per_factor && !nr_read_factors (cell))
+       return 0;
+      compare = 1;
+
+      switch (type)
+       {
+       case 0:
+         n_cols = n_continuous;
+         break;
+       case 1:
+         switch (section)
+           {
+           case LOWER:
+             n_cols = i + 1;
+             break;
+           case UPPER:
+             cp += i;
+             n_cols = n_continuous - i;
+             if (diag == NODIAGONAL)
+               {
+                 n_cols--;
+                 cp++;
+               }
+             break;
+           case FULL:
+             n_cols = n_continuous;
+             break;
+           default:
+             assert (0);
+           }
+         break;
+       case 2:
+         n_cols = 1;
+         break;
+       default:
+         assert (0);
+       }
+
+      {
+       int j;
+       
+       for (j = 0; j < n_cols; j++)
+         {
+           if (!mget_token ())
+             return 0;
+           if (mtoken != MNUM)
+             {
+               msg (SE, _("expecting value for %s %s"),
+                    default_dict.var[j]->name, context ());
+               return 0;
+             }
+
+           *cp++ = mtokval;
+         }
+       if (!force_eol (content_names[content]))
+         return 0;
+       debug_printf (("\n"));
+      }
+
+      if (section == LOWER)
+       cp += n_continuous - n_cols;
+    }
+
+  fill_matrix (content, nr_data[content][cell]);
+
+  return 1;
+}
+
+/* When ROWTYPE_ does not appear in the data, reads the matrices and
+   writes them to the output file.  Returns success. */
+static int
+matrix_data_read_without_rowtype (void)
+{
+  {
+    int *cp;
+
+    nr_data = pool_alloc (container, (PROX + 1) * sizeof *nr_data);
+    
+    {
+      int i;
+
+      for (i = 0; i <= PROX; i++)
+       nr_data[i] = NULL;
+    }
+    
+    for (cp = contents; *cp != EOC; cp++)
+      if (*cp != LPAREN && *cp != RPAREN)
+       {
+         int per_factor = is_per_factor[*cp];
+         int n_entries;
+         
+         n_entries = n_continuous;
+         if (content_type[*cp] == 1)
+           n_entries *= n_continuous;
+         
+         {
+           int n_vectors = per_factor ? cells : 1;
+           int i;
+           
+           nr_data[*cp] = pool_alloc (container,
+                                      n_vectors * sizeof **nr_data);
+           
+           for (i = 0; i < n_vectors; i++)
+             nr_data[*cp][i] = pool_alloc (container,
+                                           n_entries * sizeof ***nr_data);
+         }
+       }
+  }
+  
+  for (;;)
+    {
+      int *bp, *ep, *np;
+      
+      if (!nr_read_splits (0))
+       return 0;
+      
+      for (bp = contents; *bp != EOC; bp = np)
+       {
+         int per_factor;
+
+         /* Trap the CONTENTS that we should parse in this pass
+            between bp and ep.  Set np to the starting bp for next
+            iteration. */
+         if (*bp == LPAREN)
+           {
+             ep = ++bp;
+             while (*ep != RPAREN)
+               ep++;
+             np = &ep[1];
+             per_factor = 1;
+           }
+         else
+           {
+             ep = &bp[1];
+             while (*ep != EOC && *ep != LPAREN)
+               ep++;
+             np = ep;
+             per_factor = 0;
+           }
+         
+         {
+           int i;
+             
+           for (i = 0; i < (per_factor ? cells : 1); i++)
+             {
+               int *cp;
+
+               for (cp = bp; cp < ep; cp++) 
+                 if (!nr_read_data_lines (per_factor, i, *cp, cp != bp))
+                   return 0;
+             }
+         }
+       }
+
+      nr_output_data ();
+
+      if (default_dict.n_splits == 0 || !another_token ())
+       return 1;
+    }
+}
+
+/* Read the split file variables.  If COMPARE is 1, compares the
+   values read to the last values read and returns 1 if they're equal,
+   0 otherwise. */
+static int
+nr_read_splits (int compare)
+{
+  static int just_read = 0;
+
+  if (compare && just_read)
+    {
+      just_read = 0;
+      return 1;
+    }
+  
+  if (default_dict.n_splits == 0)
+    return 1;
+
+  if (single_split)
+    {
+      if (!compare)
+       split_values[0] = ++default_dict.splits[0]->p.mxd.subtype;
+      return 1;
+    }
+
+  if (!compare)
+    just_read = 1;
+  
+  {
+    int i;
+    
+    for (i = 0; i < default_dict.n_splits; i++)
+      {
+       if (!mget_token ())
+         return 0;
+       if (mtoken != MNUM)
+         {
+           msg (SE, _("Syntax error expecting SPLIT FILE value %s."),
+                context ());
+           return 0;
+         }
+
+       if (!compare)
+         split_values[i] = mtokval;
+       else if (split_values[i] != mtokval)
+         {
+           msg (SE, _("Expecting value %g for %s."),
+                 split_values[i], default_dict.splits[i]->name);
+           return 0;
+         }
+      }
+  }
+
+  return 1;
+}
+
+/* Read the factors for cell CELL.  If COMPARE is 1, compares the
+   values read to the last values read and returns 1 if they're equal,
+   0 otherwise. */
+static int
+nr_read_factors (int cell)
+{
+  int compare;
+  
+  if (n_factors == 0)
+    return 1;
+
+  assert (max_cell_index >= cell);
+  if (cell != max_cell_index)
+    compare = 1;
+  else
+    {
+      compare = 0;
+      max_cell_index++;
+    }
+      
+  {
+    int i;
+    
+    for (i = 0; i < n_factors; i++)
+      {
+       if (!mget_token ())
+         return 0;
+       if (mtoken != MNUM)
+         {
+           msg (SE, _("Syntax error expecting factor value %s."),
+                context ());
+           return 0;
+         }
+       
+       if (!compare)
+         nr_factor_values[i + n_factors * cell] = mtokval;
+       else if (nr_factor_values[i + n_factors * cell] != mtokval)
+         {
+           msg (SE, _("Syntax error expecting value %g for %s %s."),
+                nr_factor_values[i + n_factors * cell],
+                factors[i]->name, context ());
+           return 0;
+         }
+      }
+  }
+
+  return 1;
+}
+
+/* Write the contents of a cell having content type CONTENT and data
+   CP to the active file. */
+static void
+dump_cell_content (int content, double *cp)
+{
+  int type = content_type[content];
+
+  {
+    st_bare_pad_copy (temp_case->data[rowtype_->fv].s,
+                     content_names[content], 8);
+    
+    if (type != 1)
+      memset (&temp_case->data[varname_->fv].s, ' ', 8);
+  }
+
+  {
+    int n_lines = (type == 1) ? n_continuous : 1;
+    int i;
+               
+    for (i = 0; i < n_lines; i++)
+      {
+       int j;
+
+       for (j = 0; j < n_continuous; j++)
+         {
+           temp_case->data[(default_dict.var
+                            [first_continuous + j]->fv)].f = *cp;
+           debug_printf (("c:%s(%g) ",
+                          default_dict.var[first_continuous + j]->name,
+                          *cp));
+           cp++;
+         }
+       if (type == 1)
+         st_bare_pad_copy (temp_case->data[varname_->fv].s,
+                           default_dict.var[first_continuous + i]->name,
+                           8);
+       debug_printf (("\n"));
+       write_case ();
+      }
+  }
+}
+
+/* Finally dump out everything from nr_data[] to the output file. */
+static void
+nr_output_data (void)
+{
+  {
+    int i;
+
+    for (i = 0; i < default_dict.n_splits; i++)
+      temp_case->data[default_dict.splits[i]->fv].f = split_values[i];
+  }
+
+  if (n_factors)
+    {
+      int cell;
+
+      for (cell = 0; cell < cells; cell++)
+       {
+         {
+           int factor;
+
+           for (factor = 0; factor < n_factors; factor++)
+             {
+               temp_case->data[factors[factor]->fv].f
+                 = nr_factor_values[factor + cell * n_factors];
+               debug_printf (("f:%s ", factors[factor]->name));
+             }
+         }
+         
+         {
+           int content;
+           
+           for (content = 0; content <= PROX; content++)
+             if (is_per_factor[content])
+               {
+                 assert (nr_data[content] != NULL
+                         && nr_data[content][cell] != NULL);
+
+                 dump_cell_content (content, nr_data[content][cell]);
+               }
+         }
+       }
+    }
+
+  {
+    int content;
+    
+    {
+      int factor;
+
+      for (factor = 0; factor < n_factors; factor++)
+       temp_case->data[factors[factor]->fv].f = SYSMIS;
+    }
+    
+    for (content = 0; content <= PROX; content++)
+      if (!is_per_factor[content] && nr_data[content] != NULL)
+       dump_cell_content (content, nr_data[content][0]);
+  }
+}
+\f
+/* Back end, with ROWTYPE_. */
+
+/* Type of current row. */
+static int wr_content;
+
+/* All the data for one set of factor values. */
+struct factor_data
+  {
+    double *factors;
+    int n_rows[PROX + 1];
+    double *data[PROX + 1];
+    struct factor_data *next;
+  };
+
+/* All the data, period. */
+struct factor_data *wr_data;
+
+/* Current factor. */
+struct factor_data *wr_current;
+
+static int wr_read_splits (void);
+static int wr_output_data (void);
+static int wr_read_rowtype (void);
+static int wr_read_factors (void);
+static int wr_read_indeps (void);
+static int matrix_data_read_with_rowtype (void);
+
+/* When ROWTYPE_ appears in the data, reads the matrices and writes
+   them to the output file. */
+static void
+read_matrices_with_rowtype (void)
+{
+  mtoken = MNULL;
+  wr_data = wr_current = NULL;
+  split_values = NULL;
+  cells = 0;
+
+  matrix_data_source.read = (void (*)(void)) matrix_data_read_with_rowtype;
+  vfm_source = &matrix_data_source;
+  
+  procedure (NULL, NULL, NULL);
+
+  free (split_values);
+  fh_close_handle (data_file);
+}
+
+/* Read from the data file and write it to the active file. */
+static int
+matrix_data_read_with_rowtype (void)
+{
+  do
+    {
+      if (!wr_read_splits ())
+       return 0;
+
+      if (!wr_read_factors ())
+       return 0;
+
+      if (!wr_read_indeps ())
+       return 0;
+    }
+  while (another_token ());
+
+  wr_output_data ();
+  return 1;
+}
+
+/* Read the split file variables.  If they differ from the previous
+   set of split variables then output the data.  Returns success. */
+static int 
+wr_read_splits (void)
+{
+  int compare;
+  
+  if (default_dict.n_splits == 0)
+    return 1;
+
+  if (split_values)
+    compare = 1;
+  else
+    {
+      compare = 0;
+      split_values = xmalloc (sizeof *split_values * default_dict.n_splits);
+    }
+  
+  {
+    int different = 0;
+    int i;
+    
+    for (i = 0; i < default_dict.n_splits; i++)
+      {
+       if (!mget_token ())
+         return 0;
+       if (mtoken != MNUM)
+         {
+           msg (SE, _("Syntax error %s expecting SPLIT FILE value."),
+                context ());
+           return 0;
+         }
+
+       if (compare && split_values[i] != mtokval && !different)
+         {
+           if (!wr_output_data ())
+             return 0;
+           different = 1;
+           cells = 0;
+         }
+       split_values[i] = mtokval;
+      }
+  }
+
+  return 1;
+}
+
+/* Return strcmp()-type comparison of the n_factors factors at _A and
+   _B.  Sort missing values toward the end. */
+static int
+compare_factors (const void *pa, const void *pb)
+{
+  const double *a = (*(struct factor_data **) pa)->factors;
+  const double *b = (*(struct factor_data **) pb)->factors;
+  int i;
+
+  for (i = 0; i < n_factors; i++, a++, b++)
+    {
+      if (*a == *b)
+       continue;
+      
+      if (*a == SYSMIS)
+       return 1;
+      else if (*b == SYSMIS)
+       return -1;
+      else
+       return *a - *b < 0 ? -1 : 1;
+    }
+
+  return 0;
+}
+
+/* Write out the data for the current split file to the active
+   file. */
+static int 
+wr_output_data (void)
+{
+  {
+    int i;
+
+    for (i = 0; i < default_dict.n_splits; i++)
+      temp_case->data[default_dict.splits[i]->fv].f = split_values[i];
+  }
+
+  /* Sort the wr_data list. */
+  {
+    struct factor_data **factors;
+    struct factor_data *iter;
+    int i;
+
+    factors = xmalloc (sizeof *factors * cells);
+
+    for (i = 0, iter = wr_data; iter; iter = iter->next, i++)
+      factors[i] = iter;
+
+    qsort (factors, cells, sizeof *factors, compare_factors);
+
+    wr_data = factors[0];
+    for (i = 0; i < cells - 1; i++)
+      factors[i]->next = factors[i + 1];
+    factors[cells - 1]->next = NULL;
+
+    free (factors);
+  }
+
+  /* Write out records for every set of factor values. */
+  {
+    struct factor_data *iter;
+    
+    for (iter = wr_data; iter; iter = iter->next)
+      {
+       {
+         int factor;
+
+         for (factor = 0; factor < n_factors; factor++)
+           {
+             temp_case->data[factors[factor]->fv].f
+               = iter->factors[factor];
+             debug_printf (("f:%s ", factors[factor]->name));
+           }
+       }
+       
+       {
+         int content;
+
+         for (content = 0; content <= PROX; content++)
+           {
+             if (!iter->n_rows[content])
+               continue;
+             
+             {
+               int type = content_type[content];
+               int n_lines = (type == 1
+                              ? (n_continuous
+                                 - (section != FULL && diag == NODIAGONAL))
+                              : 1);
+               
+               if (n_lines != iter->n_rows[content])
+                 {
+                   msg (SE, _("Expected %d lines of data for %s content; "
+                              "actually saw %d lines.  No data will be "
+                              "output for this content."),
+                        n_lines, content_names[content],
+                        iter->n_rows[content]);
+                   continue;
+                 }
+             }
+
+             fill_matrix (content, iter->data[content]);
+
+             dump_cell_content (content, iter->data[content]);
+           }
+       }
+      }
+  }
+  
+  pool_destroy (container);
+  container = pool_create ();
+  
+  wr_data = wr_current = NULL;
+  
+  return 1;
+}
+
+/* Read ROWTYPE_ from the data file.  Return success. */
+static int 
+wr_read_rowtype (void)
+{
+  if (wr_content != -1)
+    {
+      msg (SE, _("Multiply specified ROWTYPE_ %s."), context ());
+      return 0;
+    }
+  if (mtoken != MSTR)
+    {
+      msg (SE, _("Syntax error %s expecting ROWTYPE_ string."), context ());
+      return 0;
+    }
+  
+  {
+    char s[16];
+    char *cp;
+    
+    memcpy (s, mtokstr, min (15, mtoklen));
+    s[min (15, mtoklen)] = 0;
+
+    for (cp = s; *cp; cp++)
+      *cp = toupper ((unsigned char) *cp);
+
+    wr_content = string_to_content_type (s, NULL);
+  }
+
+  if (wr_content == -1)
+    {
+      msg (SE, _("Syntax error %s."), context ());
+      return 0;
+    }
+
+  return 1;
+}
+
+/* Read the factors for the current row.  Select a set of factors and
+   point wr_current to it. */
+static int 
+wr_read_factors (void)
+{
+  double *factor_values = local_alloc (sizeof *factor_values * n_factors);
+
+  wr_content = -1;
+  {
+    int i;
+  
+    for (i = 0; i < n_factors; i++)
+      {
+       if (!mget_token ())
+         goto lossage;
+       if (mtoken == MSTR)
+         {
+           if (!wr_read_rowtype ())
+             goto lossage;
+           if (!mget_token ())
+             goto lossage;
+         }
+       if (mtoken != MNUM)
+         {
+           msg (SE, _("Syntax error expecting factor value %s."),
+                context ());
+           goto lossage;
+         }
+       
+       factor_values[i] = mtokval;
+      }
+  }
+  if (wr_content == -1)
+    {
+      if (!mget_token ())
+       goto lossage;
+      if (!wr_read_rowtype ())
+       goto lossage;
+    }
+  
+  /* Try the most recent factor first as a simple caching
+     mechanism. */
+  if (wr_current)
+    {
+      int i;
+      
+      for (i = 0; i < n_factors; i++)
+       if (factor_values[i] != wr_current->factors[i])
+         goto cache_miss;
+      goto winnage;
+    }
+
+  /* Linear search through the list. */
+cache_miss:
+  {
+    struct factor_data *iter;
+
+    for (iter = wr_data; iter; iter = iter->next)
+      {
+       int i;
+
+       for (i = 0; i < n_factors; i++)
+         if (factor_values[i] != iter->factors[i])
+           goto next_item;
+       
+       wr_current = iter;
+       goto winnage;
+       
+      next_item: ;
+      }
+  }
+
+  /* Not found.  Make a new item. */
+  {
+    struct factor_data *new = pool_alloc (container, sizeof *new);
+
+    new->factors = pool_alloc (container, sizeof *new->factors * n_factors);
+    
+    {
+      int i;
+
+      for (i = 0; i < n_factors; i++)
+       new->factors[i] = factor_values[i];
+    }
+    
+    {
+      int i;
+
+      for (i = 0; i <= PROX; i++)
+       {
+         new->n_rows[i] = 0;
+         new->data[i] = NULL;
+       }
+    }
+
+    new->next = wr_data;
+    wr_data = wr_current = new;
+    cells++;
+  }
+
+winnage:
+  local_free (factor_values);
+  return 1;
+
+lossage:
+  local_free (factor_values);
+  return 0;
+}
+
+/* Read the independent variables into wr_current. */
+static int 
+wr_read_indeps (void)
+{
+  struct factor_data *c = wr_current;
+  const int type = content_type[wr_content];
+  const int n_rows = c->n_rows[wr_content];
+  double *cp;
+  int n_cols;
+
+  /* Allocate room for data if necessary. */
+  if (c->data[wr_content] == NULL)
+    {
+      int n_items = n_continuous;
+      if (type == 1)
+       n_items *= n_continuous;
+      
+      c->data[wr_content] = pool_alloc (container,
+                                       sizeof **c->data * n_items);
+    }
+
+  cp = &c->data[wr_content][n_rows * n_continuous];
+
+  /* Figure out how much to read from this line. */
+  switch (type)
+    {
+    case 0:
+    case 2:
+      if (n_rows > 0)
+       {
+         msg (SE, _("Duplicate specification for %s."),
+              content_names[wr_content]);
+         return 0;
+       }
+      if (type == 0)
+       n_cols = n_continuous;
+      else
+       n_cols = 1;
+      break;
+    case 1:
+      if (n_rows >= n_continuous - (section != FULL && diag == NODIAGONAL))
+       {
+         msg (SE, _("Too many rows of matrix data for %s."),
+              content_names[wr_content]);
+         return 0;
+       }
+      
+      switch (section)
+       {
+       case LOWER:
+         n_cols = n_rows + 1;
+         if (diag == NODIAGONAL)
+           cp += n_continuous;
+         break;
+       case UPPER:
+         cp += n_rows;
+         n_cols = n_continuous - n_rows;
+         if (diag == NODIAGONAL)
+           {
+             n_cols--;
+             cp++;
+           }
+         break;
+       case FULL:
+         n_cols = n_continuous;
+         break;
+       default:
+         assert (0);
+       }
+      break;
+    default:
+      assert (0);
+    }
+  c->n_rows[wr_content]++;
+
+  debug_printf ((" (c=%p,r=%d,n=%d)", c, n_rows + 1, n_cols));
+
+  /* Read N_COLS items at CP. */
+  {
+    int j;
+       
+    for (j = 0; j < n_cols; j++)
+      {
+       if (!mget_token ())
+         return 0;
+       if (mtoken != MNUM)
+         {
+           msg (SE, _("Syntax error expecting value for %s %s."),
+                default_dict.var[first_continuous + j]->name, context ());
+           return 0;
+         }
+
+       *cp++ = mtokval;
+      }
+    if (!force_eol (content_names[wr_content]))
+      return 0;
+    debug_printf (("\n"));
+  }
+
+  return 1;
+}
+\f
+/* Matrix source. */
+
+struct case_stream matrix_data_source = 
+  {
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    "MATRIX DATA",
+  };
+
diff --git a/src/matrix.c b/src/matrix.c
new file mode 100644 (file)
index 0000000..7e47467
--- /dev/null
@@ -0,0 +1,302 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "matrix.h"
+\f
+/* Kahan summation formula, Thm. 8, _What Every Computer Scientist
+   Should Know About Floating-Point Arithmetic_, David Goldberg,
+   orig. March 1991 issue of Computing Surveys, also at
+   <URL:http://www.wam.umd.edu/whats_new/workshop3.0/common-tools/numerical_comp_guide/goldberg1.doc.html>.
+   Hopefully your compiler won't try to optimize the code below too
+   much, because that will ruin the precision. */
+#define KAHAN_SUMMATION_FORMULA(S)                             \
+       do                                                      \
+         {                                                     \
+           double S_c;                                         \
+           int S_j;                                            \
+                                                               \
+           S = SUMMATION_ELEMENT (0);                          \
+           S_c = 0.;                                           \
+           for (S_j = 1; S_j < SUMMATION_COUNT; S_j++)         \
+             {                                                 \
+               double S_y = SUMMATION_ELEMENT (S_j) - S_c;     \
+               double S_t = S + S_y;                           \
+               S_c = (S_t - S) - S_y;                          \
+               S = S_t;                                        \
+             }                                                 \
+         }                                                     \
+       while (0)
+
+\f
+/* Vectors. */
+
+/* Allocate a new vector of length N. */
+struct vector *
+vec_alloc (int n)
+{
+  struct vector *vec = xmalloc (sizeof *vec);
+  vec->data = xmalloc (sizeof *vec->data * n);
+  vec->n = vec->m = n;
+  return vec;
+}
+
+/* Change the length of VEC to N.  The amount of space allocated will
+   not be lowered, but may be enlarged. */
+void
+vec_realloc (struct vector *vec, int n)
+{
+  if (n < vec->m)
+    {
+      vec->m = n;
+      vec->data = xrealloc (vec->data, sizeof *vec->data * n);
+    }
+  vec->n = n;
+}
+
+/* Free vector VEC. */
+void
+vec_free (struct vector *vec)
+{
+  free (vec->data);
+  free (vec);
+}
+
+/* Set the values in vector VEC to constant VALUE. */
+#if 0
+void
+vec_init (struct vector *vec, double value)
+{
+  double *p;
+  int i;
+
+  p = vec->data;
+  for (i = 0; i < vec->n; i++)
+    *p++ = value;
+}
+#endif
+
+/* Print out vector VEC to stdout for debugging purposes. */
+#if GLOBAL_DEBUGGING
+#include <stdio.h>
+#include "settings.h"
+
+void
+vec_print (const struct vector *vec)
+{
+  int i;
+
+  for (i = 0; i < vec->n; i++)
+    {
+      if (i % ((set_viewwidth - 4) / 8) == 0)
+       {
+         if (i)
+           putchar ('\n');
+         printf ("%3d:", i);
+       }
+      
+      printf ("%8g", vec_elem (vec, i));
+    }
+}
+#endif
+
+/* Return the sum of the values in VEC. */
+double
+vec_total (const struct vector *vec)
+{
+  double sum;
+
+#define SUMMATION_COUNT (vec->n)
+#define SUMMATION_ELEMENT(INDEX) (vec_elem (vec, (INDEX)))
+  KAHAN_SUMMATION_FORMULA (sum);
+#undef SUMMATION_COUNT
+#undef SUMMATION_ELEMENT
+
+  return sum;
+}
+\f
+/* Matrices. */
+
+/* Allocate a new matrix with NR rows and NC columns. */
+struct matrix *
+mat_alloc (int nr, int nc)
+{
+  struct matrix *mat = xmalloc (sizeof *mat);
+  mat->nr = nr;
+  mat->nc = nc;
+  mat->m = nr * nc;
+  mat->data = xmalloc (sizeof *mat->data * nr * nc);
+  return mat;
+}
+
+/* Set the size of matrix MAT to NR rows and NC columns.  The matrix
+   data array will be enlarged if necessary but will not be shrunk. */
+void
+mat_realloc (struct matrix *mat, int nr, int nc)
+{
+  if (nc * nr > mat->m)
+    {
+      mat->m = nc * nr;
+      mat->data = xrealloc (mat->data, sizeof *mat->data * mat->m);
+    }
+  mat->nr = nr;
+  mat->nc = nc;
+}
+
+/* Free matrix MAT. */
+void
+mat_free (struct matrix *mat)
+{
+  free (mat->data);
+  free (mat);
+}
+
+/* Set all matrix MAT entries to VALUE. */
+void
+mat_init (struct matrix *mat, double value)
+{
+  double *p;
+  int i;
+
+  p = mat->data;
+  for (i = 0; i < mat->nr * mat->nc; i++)
+    *p++ = value;
+}
+
+/* Set all MAT entries in row R to VALUE. */
+void
+mat_init_row (struct matrix *mat, int r, double value)
+{
+  double *p;
+  int i;
+
+  p = &mat_elem (mat, r, 0);
+  for (i = 0; i < mat->nc; i++)
+    *p++ = value;
+}
+
+/* Set all MAT entries in column C to VALUE. */
+void
+mat_init_col (struct matrix *mat, int c, double value)
+{
+  double *p;
+  int i;
+
+  p = &mat_elem (mat, 0, c);
+  for (i = 0; i < mat->nr; i++)
+    {
+      *p = value;
+      p += mat->nc;
+    }
+}
+
+/* Print out MAT entries to stdout, optionally with row and column
+   labels ROW_LABELS and COL_LABELS. */
+#if GLOBAL_DEBUGGING
+void
+mat_print (const struct matrix *mat,
+          const struct vector *row_labels,
+          const struct vector *col_labels)
+{
+  int r, c;
+  
+  assert (!row_labels || row_labels->n == mat->nr);
+  if (col_labels)
+    {
+      int c;
+      
+      assert (col_labels->n == mat->nc);
+      if (row_labels)
+       printf ("        ");
+      for (c = 0; c < mat->nc; c++)
+       printf ("%8g", vec_elem (col_labels, c));
+    }
+
+  for (r = 0; r < mat->nr; r++)
+    {
+      if (row_labels)
+       printf ("%8g:", vec_elem (row_labels, r));
+      for (c = 0; c < mat->nc; c++)
+       printf ("%8g", mat_elem (mat, r, c));
+      putchar ('\n');
+    }
+}
+#endif /* GLOBAL_DEBUGGING */
+
+/* Calculate row totals for matrix MAT into vector ROW_TOTS. */
+void
+mat_row_totals (const struct matrix *mat, struct vector *row_tots)
+{
+  int r;
+  
+  vec_realloc (row_tots, mat->nr);
+  for (r = 0; r < mat->nr; r++)
+    {
+      double sum;
+
+#define SUMMATION_COUNT (mat->nc)
+#define SUMMATION_ELEMENT(INDEX) (mat_elem (mat, r, INDEX))
+      KAHAN_SUMMATION_FORMULA (sum);
+#undef SUMMATION_COUNT
+#undef SUMMATION_ELEMENT
+
+      vec_elem (row_tots, r) = sum;
+    }
+}
+
+/* Calculate column totals for matrix MAT into vector COL_TOTS. */
+void
+mat_col_totals (const struct matrix *mat, struct vector *col_tots)
+{
+  int c;
+  
+  vec_realloc (col_tots, mat->nc);
+  for (c = 0; c < mat->nc; c++)
+    {
+      double sum;
+
+#define SUMMATION_COUNT (mat->nr)
+#define SUMMATION_ELEMENT(INDEX) (mat_elem (mat, INDEX, c))
+      KAHAN_SUMMATION_FORMULA (sum);
+#undef SUMMATION_COUNT
+#undef SUMMATION_ELEMENT
+
+      vec_elem (col_tots, c) = sum;
+    }
+}
+
+/* Return the grand total for matrix MAT.  Of course, if you're also
+   calculating column or row totals, it would be faster to use
+   vec_total on one of those sets of totals. */
+double
+mat_grand_total (const struct matrix *mat)
+{
+  double sum;
+
+#define SUMMATION_COUNT (mat->nr * mat->nc)
+#define SUMMATION_ELEMENT(INDEX) (mat->data[INDEX])
+  KAHAN_SUMMATION_FORMULA (sum);
+#undef SUMMATION_COUNT
+#undef SUMMATION_ELEMENT
+
+  return sum;
+}
diff --git a/src/matrix.h b/src/matrix.h
new file mode 100644 (file)
index 0000000..c1e5c61
--- /dev/null
@@ -0,0 +1,96 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !matrix_h
+#define matrix_h 1
+\f
+/* Vector representation. */
+struct vector
+  {
+    int n;
+    int m;
+    double *data;
+  };
+
+/* Allocate vectors. */
+struct vector *vec_alloc (int n);
+void vec_realloc (struct vector *, int n);
+void vec_free (struct vector *);
+
+/* Vector elements. */
+#define vec_elem(VEC, INDEX) ((VEC)->data[INDEX])
+
+/* Set the vector to a constant value. */
+void vec_init (struct vector *, double);
+
+/* Print out the vector to stdout. */
+#if GLOBAL_DEBUGGING
+void vec_print (const struct vector *);
+#endif
+
+/* Sum the vector values. */
+double vec_total (const struct vector *);
+\f
+/* Matrix representation. */
+struct matrix
+  {
+    int nr, nc;
+    int m;
+    double *data;
+  };
+
+/* Allocate matrices. */
+struct matrix *mat_alloc (int nr, int nc);
+void mat_realloc (struct matrix *, int nr, int nc);
+void mat_free (struct matrix *);
+
+/* Matrix elements. */
+#define mat_elem(MAT, R, C) ((MAT)->data[(C) + (R) * (MAT)->nc])
+
+/* Set matrix values to a constant. */
+void mat_init (struct matrix *, double);
+void mat_init_row (struct matrix *, int r, double);
+void mat_init_col (struct matrix *, int c, double);
+
+/* Print out the matrix values to stdout, optionally with row and
+   column labels (for debugging purposes). */
+#if GLOBAL_DEBUGGING
+void mat_print (const struct matrix *,
+               const struct vector *row_labels, const struct vector *col_labels);
+#endif
+
+/* Sum matrix values. */
+void mat_row_totals (const struct matrix *, struct vector *row_tots);
+void mat_col_totals (const struct matrix *, struct vector *col_tots);
+double mat_grand_total (const struct matrix *);
+
+/* Chi-square statistics. */
+enum
+  {
+    CHISQ_PEARSON,
+    CHISQ_LIKELIHOOD_RATIO,
+    CHISQ_FISHER,
+    CHISQ_CC,
+    CHISQ_LINEAR,
+    N_CHISQ
+  };
+
+void mat_chisq (const struct matrix *, double chisq[N_CHISQ], int df[N_CHISQ]);
+
+#endif /* matrix_h */
diff --git a/src/means.q b/src/means.q
new file mode 100644 (file)
index 0000000..99432a3
--- /dev/null
@@ -0,0 +1,409 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include "alloc.h"
+#include "avl.h"
+#include "command.h"
+#include "lexer.h"
+#include "error.h"
+#include "magic.h"
+#include "var.h"
+/* (headers) */
+
+#undef DEBUGGING
+#define DEBUGGING 1
+#include "debug-print.h"
+
+/* (specification)
+   means (mns_):
+     *tables=custom;
+     +variables=custom;
+     +crossbreak=custom;
+     +format=lab:!labels/nolabels/nocatlabs,
+            name:!names/nonames,
+            val:!values/novalues,
+            fmt:!table/tree;
+     +missing=miss:!table/include/dependent;
+     +cells[cl_]=default,count,sum,mean,stddev,variance,all;
+     +statistics[st_]=anova,linearity,all,none.
+*/
+/* (declarations) */
+/* (functions) */
+
+#if DEBUGGING
+static void debug_print (struct cmd_means *cmd);
+#endif
+
+/* TABLES: Variable lists for each dimension. */
+int n_dim;             /* Number of dimensions. */
+int *nv_dim;           /* Number of variables in each dimension. */
+struct variable ***v_dim;      /* Variables in each dimension.  */
+
+/* VARIABLES: List of variables. */
+int n_var;
+struct variable **v_var;
+
+/* Parses and executes the T-TEST procedure. */
+int
+cmd_means (void)
+{
+  struct cmd_means cmd;
+  int success = CMD_FAILURE;
+  
+  n_dim = 0;
+  nv_dim = NULL;
+  v_dim = NULL;
+  v_var = NULL;
+
+  lex_match_id ("MEANS");
+  if (!parse_means (&cmd))
+    goto free;
+
+  if (cmd.sbc_cells)
+    {
+      int i;
+      for (i = 0; i < MNS_CL_count; i++)
+       if (cmd.a_cells[i])
+         break;
+      if (i >= MNS_CL_count)
+       cmd.a_cells[MNS_CL_ALL] = 1;
+    }
+  else
+    cmd.a_cells[MNS_CL_DEFAULT] = 1;
+  if (cmd.a_cells[MNS_CL_DEFAULT] || cmd.a_cells[MNS_CL_ALL])
+    cmd.a_cells[MNS_CL_MEAN] = cmd.a_cells[MNS_CL_STDDEV] = cmd.a_cells[MNS_CL_COUNT] = 1;
+  if (cmd.a_cells[MNS_CL_ALL])
+    cmd.a_cells[MNS_CL_SUM] = cmd.a_cells[MNS_CL_VARIANCE] = 1;
+
+  if (cmd.sbc_statistics)
+    {
+      if (!cmd.a_statistics[MNS_ST_ANOVA] && !cmd.a_statistics[MNS_ST_LINEARITY])
+       cmd.a_statistics[MNS_ST_ANOVA] = 1;
+      if (cmd.a_statistics[MNS_ST_ALL])
+       cmd.a_statistics[MNS_ST_ANOVA] = cmd.a_statistics[MNS_ST_LINEARITY] = 1;
+    }
+
+  if (!cmd.sbc_tables)
+    {
+      msg (SE, _("Missing required subcommand TABLES."));
+      goto free;
+    }
+
+#if DEBUGGING
+  debug_print (&cmd);
+#endif
+  
+  success = CMD_SUCCESS;
+
+free:
+  {
+    int i;
+    
+    for (i = 0; i < n_dim; i++)
+      free (v_dim[i]);
+    free (nv_dim);
+    free (v_dim);
+    free (v_var);
+  }
+  
+  return success;
+}
+
+/* Returns nonzero only if value V is valid as an endpoint for a
+   dependent variable in integer mode. */
+int
+validate_dependent_endpoint (double V)
+{
+  return V == (int) V && V != LOWEST && V != HIGHEST;
+}
+
+/* Parses the TABLES subcommand. */
+static int
+mns_custom_tables (struct cmd_means *cmd)
+{
+  struct dictionary *dict;
+  struct dictionary temp_dict;
+  
+  if (!lex_match_id ("TABLES")
+      && (token != T_ID || !is_varname (tokid))
+      && token != T_ALL)
+    return 2;
+  lex_match ('=');
+
+  if (cmd->sbc_tables || cmd->sbc_crossbreak)
+    {
+      msg (SE, _("TABLES or CROSSBREAK subcommand may not appear more "
+                "than once."));
+      return 0;
+    }
+
+  if (cmd->sbc_variables)
+    {
+      dict = &temp_dict;
+      temp_dict.var = v_var;
+      temp_dict.nvar = n_var;
+      
+      {
+       int i;
+      
+       temp_dict.var_by_name = avl_create (NULL, cmp_variable, NULL);
+       for (i = 0; i < temp_dict.nvar; i++)
+         avl_force_insert (temp_dict.var_by_name, temp_dict.var[i]);
+      }
+    }
+  else
+    dict = &default_dict;
+
+  do
+    {
+      int nvl;
+      struct variable **vl;
+       
+      if (!parse_variables (dict, &vl, &nvl, PV_NO_DUPLICATE | PV_NO_SCRATCH))
+       return 0;
+      
+      n_dim++;
+      nv_dim = xrealloc (nv_dim, n_dim * sizeof (int));
+      v_dim = xrealloc (v_dim, n_dim * sizeof (struct variable **));
+
+      nv_dim[n_dim - 1] = nvl;
+      v_dim[n_dim - 1] = vl;
+
+      if (cmd->sbc_variables)
+       {
+         int i;
+
+         for (i = 0; i < nv_dim[0]; i++)
+           {
+             struct means_proc *v_inf = &v_dim[0][i]->p.mns;
+
+             if (v_inf->min == SYSMIS)
+               {
+                 msg (SE, _("Variable %s specified on TABLES or "
+                            "CROSSBREAK, but not specified on "
+                            "VARIABLES."),
+                      v_dim[0][i]->name);
+                 return 0;
+               }
+             
+             if (n_dim == 1)
+               {
+                 v_inf->min = (int) v_inf->min;
+                 v_inf->max = (int) v_inf->max;
+               } else {
+                 if (v_inf->min == LOWEST || v_inf->max == HIGHEST)
+                   {
+                     msg (SE, _("LOWEST and HIGHEST may not be used "
+                                "for independent variables (%s)."),
+                          v_dim[0][i]->name);
+                     return 0;
+                   }
+                 if (v_inf->min != (int) v_inf->min
+                     || v_inf->max != (int) v_inf->max)
+                   {
+                     msg (SE, _("Independent variables (%s) may not "
+                                "have noninteger endpoints in their "
+                                "ranges."),
+                          v_dim[0][i]->name);
+                     return 0;
+                   }
+               }
+           }
+       }
+    }
+  while (lex_match (T_BY));
+
+  /* Check for duplicates. */
+  {
+    int i;
+    
+    for (i = 0; i < default_dict.nvar; i++)
+      default_dict.var[i]->foo = 0;
+    for (i = 0; i < dict->nvar; i++)
+      if (dict->var[i]->foo++)
+       {
+         msg (SE, _("Variable %s is multiply specified on TABLES "
+                    "or CROSSBREAK."),
+              dict->var[i]->name);
+         return 0;
+       }
+  }
+  
+  if (cmd->sbc_variables)
+    avl_destroy (temp_dict.var_by_name, NULL);
+
+  return 1;
+}
+
+/* Parse CROSSBREAK subcommand. */
+static int
+mns_custom_crossbreak (struct cmd_means *cmd)
+{
+  return mns_custom_tables (cmd);
+}
+
+/* Parses the VARIABLES subcommand. */
+static int
+mns_custom_variables (struct cmd_means *cmd)
+{
+  if (cmd->sbc_tables)
+    {
+      msg (SE, _("VARIABLES must precede TABLES."));
+      return 0;
+    }
+
+  if (cmd->sbc_variables == 1)
+    {
+      int i;
+      
+      for (i = 0; i < default_dict.nvar; i++)
+       default_dict.var[i]->p.mns.min = SYSMIS;
+    }
+  
+  do
+    {
+      int orig_n = n_var;
+      
+      double min, max;
+      
+      if (!parse_variables (&default_dict, &v_var, &n_var,
+                           PV_APPEND | PV_NO_DUPLICATE | PV_NO_SCRATCH))
+       return 0;
+
+      if (!lex_force_match ('('))
+       return 0;
+
+      /* Lower value. */
+      if (token == T_ID
+         && (!strcmp (tokid, "LO") || lex_id_match ("LOWEST", tokid)))
+       min = LOWEST;
+      else
+       {
+         if (!lex_force_num ())
+           return 0;
+         min = tokval;
+       }
+      lex_get ();
+
+      lex_match (',');
+
+      /* Higher value. */
+      if (token == T_ID
+         && (!strcmp (tokid, "HI") || lex_id_match ("HIGHEST", tokid)))
+       max = HIGHEST;
+      else
+       {
+         if (!lex_force_num ())
+           return 0;
+         max = tokval;
+       }
+      lex_get ();
+
+      if (!lex_force_match (')'))
+       return 0;
+
+      /* Range check. */
+      if (max < min)
+       {
+         msg (SE, _("Upper value (%g) is less than lower value "
+                    "(%g) on VARIABLES subcommand."), max, min);
+         return 0;
+       }
+      
+      {
+       int i;
+
+       for (i = orig_n; i < n_var; i++)
+         {
+           struct means_proc *v_inf = &v_var[i]->p.mns;
+
+           v_inf->min = min;
+           v_inf->max = max;
+         }
+      }
+    }
+  while (token != '/' && token != '.');
+  
+  return 1;
+}
+
+#if DEBUGGING
+static void
+debug_print (struct cmd_means *cmd)
+{
+  int i;
+  
+  printf ("MEANS");
+
+  if (cmd->sbc_variables)
+    {
+      int j = 0;
+      
+      printf (" VARIABLES=");
+      for (i = 0; i < default_dict.nvar; i++)
+       {
+         struct variable *v = default_dict.var[i];
+         
+         if (v->p.mns.min == SYSMIS)
+           continue;
+         if (j++)
+           printf (" ");
+         printf ("%s(", v->name);
+         if (v->p.mns.min == LOWEST)
+           printf ("LO");
+         else
+           printf ("%g", v->p.mns.min);
+         printf (",");
+         if (v->p.mns.max == HIGHEST)
+           printf ("HI");
+         else
+           printf ("%g", v->p.mns.max);
+         printf (")");
+       }
+      printf ("\n");
+    }
+
+  printf (" TABLES=");
+  for (i = 0; i < n_dim; i++)
+    {
+      int j;
+
+      if (i)
+       printf (" BY");
+
+      for (j = 0; j < nv_dim[i]; j++)
+       {
+         if (i || j)
+           printf (" ");
+         printf (v_dim[i][j]->name);
+       }
+    }
+  printf ("\n");
+}
+#endif /* DEBUGGING */
+
+/* 
+   Local Variables:
+   mode: c
+   End:
+*/
diff --git a/src/mis-val.c b/src/mis-val.c
new file mode 100644 (file)
index 0000000..a048e40
--- /dev/null
@@ -0,0 +1,409 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "magic.h"
+#include "str.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+#if DEBUGGING
+static void debug_print ();
+#endif
+
+/* Variables on MIS VAL. */
+static struct variable **v;
+static int nv;
+
+/* Type of the variables on MIS VAL. */
+static int type;
+
+/* Width of string variables on MIS VAL. */
+static size_t width;
+
+/* Items to fill-in var structs with. */
+static int miss_type;
+static union value missing[3];
+
+static int parse_varnames (void);
+static int parse_numeric (void);
+static int parse_alpha (void);
+
+int
+cmd_missing_values (void)
+{
+  int i;
+
+  lex_match_id ("MISSING");
+  lex_match_id ("VALUES");
+  while (token != '.')
+    {
+#if __CHECKER__
+      memset (missing, 0, sizeof missing);
+#endif
+
+      if (!parse_varnames ())
+       goto fail;
+
+      if (token != ')')
+       {
+         if ((type == NUMERIC && !parse_numeric ())
+             || (type == ALPHA && !parse_alpha ()))
+           goto fail;
+       }
+      else
+       miss_type = MISSING_NONE;
+
+      if (!lex_match (')'))
+       {
+         msg (SE, _("`)' expected after value specification."));
+         goto fail;
+       }
+
+      for (i = 0; i < nv; i++)
+       {
+         v[i]->miss_type = miss_type;
+         memcpy (v[i]->missing, missing, sizeof v[i]->missing);
+       }
+
+      lex_match ('/');
+      free (v);
+    }
+
+#if DEBUGGING
+  debug_print ();
+#endif
+
+  return lex_end_of_command ();
+
+fail:
+  free (v);
+  return CMD_PART_SUCCESS_MAYBE;
+}
+
+static int
+parse_varnames (void)
+{
+  int i;
+
+  if (!parse_variables (NULL, &v, &nv, PV_SAME_TYPE))
+    return 0;
+  if (!lex_match ('('))
+    {
+      msg (SE, _("`(' expected after variable name%s."), nv > 1 ? "s" : "");
+      return 0;
+    }
+
+  type = v[0]->type;
+  if (type == NUMERIC)
+    return 1;
+
+  width = v[0]->width;
+  for (i = 1; i < nv; i++)
+    if (v[i]->type == ALPHA && v[i]->nv != 1)
+      {
+       msg (SE, _("Long string value specified."));
+       return 0;
+      }
+    else if (v[i]->type == ALPHA && (int) width != v[i]->width)
+      {
+       msg (SE, _("Short strings must be of equal width."));
+       return 0;
+      }
+
+  return 1;
+}
+
+/* Number or range? */
+enum
+  {
+    MV_NOR_NOTHING,            /* Empty. */
+    MV_NOR_NUMBER,             /* Single number. */
+    MV_NOR_RANGE               /* Range. */
+  };
+
+/* A single value or a range. */
+struct num_or_range
+  {
+    int type;                  /* One of NOR_*. */
+    double d[2];               /* d[0]=lower bound or value, d[1]=upper bound. */
+  };
+
+/* Parses something of the form <num>, or LO[WEST] THRU <num>, or
+   <num> THRU HI[GHEST], or <num> THRU <num>, and sets the appropriate
+   members of NOR.  Returns success. */
+static int
+parse_num_or_range (struct num_or_range * nor)
+{
+  if (lex_match_id ("LO") || lex_match_id ("LOWEST"))
+    {
+      nor->type = MV_NOR_RANGE;
+      if (!lex_force_match_id ("THRU"))
+       return 0;
+      if (!lex_force_num ())
+       return 0;
+      nor->d[0] = LOWEST;
+      nor->d[1] = tokval;
+    }
+  else if (token == T_NUM)
+    {
+      nor->d[0] = tokval;
+      lex_get ();
+
+      if (lex_match_id ("THRU"))
+       {
+         nor->type = MV_NOR_RANGE;
+         if (lex_match_id ("HI") || lex_match_id ("HIGHEST"))
+           nor->d[1] = HIGHEST;
+         else
+           {
+             if (!lex_force_num ())
+               return 0;
+             nor->d[1] = tokval;
+             lex_get ();
+
+             if (nor->d[0] > nor->d[1])
+               {
+                 msg (SE, _("Range %g THRU %g is not valid because %g is "
+                            "greater than %g."),
+                      nor->d[0], nor->d[1], nor->d[0], nor->d[1]);
+                 return 0;
+               }
+           }
+       }
+      else
+       nor->type = MV_NOR_NUMBER;
+    }
+  else
+    return -1;
+
+  return 1;
+}
+
+/* Parses a set of numeric missing values and stores them into
+   `missing[]' and `miss_type' global variables. */
+static int
+parse_numeric (void)
+{
+  struct num_or_range set[3];
+  int r;
+
+  set[1].type = set[2].type = MV_NOR_NOTHING;
+
+  /* Get first number or range. */
+  r = parse_num_or_range (&set[0]);
+  if (r < 1)
+    {
+      if (r == -1)
+       msg (SE, _("Number or range expected."));
+      return 0;
+    }
+
+  /* Get second and third optional number or range. */
+  lex_match (',');
+  r = parse_num_or_range (&set[1]);
+  if (r == 1)
+    {
+      lex_match (',');
+      r = parse_num_or_range (&set[2]);
+    }
+  if (r == 0)
+    return 0;
+
+  /* Force range, if present, into set[0]. */
+  if (set[1].type == MV_NOR_RANGE)
+    {
+      struct num_or_range t = set[1];
+      set[1] = set[0];
+      set[0] = t;
+    }
+  if (set[2].type == MV_NOR_RANGE)
+    {
+      struct num_or_range t = set[2];
+      set[2] = set[0];
+      set[0] = t;
+    }
+  
+  /* Ensure there's not more than one range, or one range
+     plus one value. */
+  if (set[1].type == MV_NOR_RANGE || set[2].type == MV_NOR_RANGE)
+    {
+      msg (SE, _("At most one range can exist in the missing values "
+                "for any one variable."));
+      return 0;
+    }
+  if (set[0].type == MV_NOR_RANGE && set[2].type != MV_NOR_NOTHING)
+    {
+      msg (SE, _("At most one individual value can be missing along "
+                "with one range."));
+      return 0;
+    }
+
+  /* Set missing[] from set[]. */
+  if (set[0].type == MV_NOR_RANGE)
+    {
+      int x = 0;
+
+      if (set[0].d[0] == LOWEST)
+       {
+         miss_type = MISSING_LOW;
+         missing[x++].f = set[0].d[1];
+       }
+      else if (set[0].d[1] == HIGHEST)
+       {
+         miss_type = MISSING_HIGH;
+         missing[x++].f = set[0].d[0];
+       }
+      else
+       {
+         miss_type = MISSING_RANGE;
+         missing[x++].f = set[0].d[0];
+         missing[x++].f = set[0].d[1];
+       }
+
+      if (set[1].type == MV_NOR_NUMBER)
+       {
+         miss_type += 3;
+         missing[x].f = set[1].d[0];
+       }
+    }
+  else
+    {
+      if (set[0].type == MV_NOR_NUMBER)
+       {
+         miss_type = MISSING_1;
+         missing[0].f = set[0].d[0];
+       }
+      if (set[1].type == MV_NOR_NUMBER)
+       {
+         miss_type = MISSING_2;
+         missing[1].f = set[1].d[0];
+       }
+      if (set[2].type == MV_NOR_NUMBER)
+       {
+         miss_type = MISSING_3;
+         missing[2].f = set[2].d[0];
+       }
+    }
+
+  return 1;
+}
+
+static int
+parse_alpha (void)
+{
+  for (miss_type = 0; token == T_STRING && miss_type < 3; miss_type++)
+    {
+      if (ds_length (&tokstr) != width)
+       {
+         msg (SE, _("String is not of proper length."));
+         return 0;
+       }
+      strncpy (missing[miss_type].s, ds_value (&tokstr), MAX_SHORT_STRING);
+      lex_get ();
+      lex_match (',');
+    }
+  if (miss_type < 1)
+    {
+      msg (SE, _("String expected."));
+      return 0;
+    }
+
+  return 1;
+}
+
+/* Copy the missing values from variable SRC to variable DEST. */
+void
+copy_missing_values (struct variable *dest, const struct variable *src)
+{
+  static const int n_values[MISSING_COUNT] = 
+    {
+      0, 1, 2, 3, 2, 1, 1, 3, 2, 2,
+    };
+    
+  assert (dest->width == src->width);
+  assert (src->miss_type >= 0 && src->miss_type < MISSING_COUNT);
+  
+  {
+    int i;
+
+    dest->miss_type = src->miss_type;
+    for (i = 0; i < n_values[src->miss_type]; i++)
+      if (src->type == NUMERIC)
+       dest->missing[i].f = src->missing[i].f;
+      else
+       memcpy (dest->missing[i].s, src->missing[i].s, src->width);
+  }
+}
+
+\f
+/* Debug output. */
+
+#if DEBUGGING
+static void
+debug_print (void)
+{
+  int i, j;
+
+  puts (_("Missing value:"));
+  for (i = 0; i < nvar; i++)
+    {
+      printf ("         %8s: ", var[i]->name);
+      if (var[i]->type == ALPHA && var[i]->nv > 1)
+       puts (_("(long string variable)"));
+      else
+       switch (var[i]->miss_type)
+         {
+         case MISSING_NONE:
+           printf (_("(no missing values)\n"));
+           break;
+         case MISSING_1:
+         case MISSING_2:
+         case MISSING_3:
+           printf ("(MISSING_%d)", var[i]->miss_type);
+           for (j = 0; j < var[i]->miss_type; j++)
+             if (var[i]->type == ALPHA)
+               printf ("  \"%.*s\"", var[i]->width, var[i]->missing[j].s);
+             else
+               printf ("  %.2g", var[i]->missing[j].f);
+           printf ("\n");
+           break;
+         case MISSING_RANGE:
+           printf ("(MISSING_RANGE)  %.2g THRU %.2g\n",
+                   var[i]->missing[0].f, var[i]->missing[1].f);
+           break;
+         case MISSING_RANGE_1:
+           printf ("(MISSING_RANGE_1)  %.2g THRU %.2g, %.2g\n",
+                   var[i]->missing[0].f, var[i]->missing[1].f,
+                   var[i]->missing[2].f);
+           break;
+         default:
+           printf (_("(!!!INTERNAL ERROR--%d!!!)\n"), var[i]->miss_type);
+         }
+    }
+}
+#endif /* DEBUGGING */
diff --git a/src/misc.c b/src/misc.c
new file mode 100644 (file)
index 0000000..7c517ad
--- /dev/null
@@ -0,0 +1,38 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include "misc.h"
+
+/* Returns the number of digits in X. */
+int
+intlog10 (unsigned x)
+{
+  int digits = 0;
+
+  do
+    {
+      digits++;
+      x /= 10;
+    }
+  while (x > 0);
+
+  return digits;
+}
+
diff --git a/src/misc.h b/src/misc.h
new file mode 100644 (file)
index 0000000..d8d1970
--- /dev/null
@@ -0,0 +1,108 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !math_misc_h
+#define math_misc_h 1
+
+#include <math.h>
+
+/* HUGE_VAL is traditionally defined as positive infinity, or
+   alternatively, DBL_MAX. */
+#if !HAVE_ISINF
+#define isinf(X)                               \
+       (fabs (X) == HUGE_VAL)
+#endif
+
+/* A Not a Number is not equal to itself. */
+#if !HAVE_ISNAN
+#define isnan(X)                               \
+       ((X) != (X))
+#endif
+
+/* Finite numbers are not infinities or NaNs. */
+#if !HAVE_FINITE
+#define finite(X)                              \
+       (!isinf (X) && !isnan (X))
+#elif HAVE_IEEEFP_H
+#include <ieeefp.h>            /* Declares finite() under Solaris. */
+#endif
+
+#if __TURBOC__
+#include <stdlib.h>            /* screwed-up Borland headers define min(), max(),
+                                  so we might as well let 'em */
+#endif
+
+#ifndef min
+#if __GNUC__ && !__STRICT_ANSI__
+#define min(A, B)                              \
+       ({                                      \
+         int _a = (A), _b = (B);               \
+         _a < _b ? _a : _b;                    \
+       })
+#else /* !__GNUC__ */
+#define min(A, B)                              \
+       ((A) < (B) ? (A) : (B))
+#endif /* !__GNUC__ */
+#endif /* !min */
+
+#ifndef max
+#if __GNUC__ && !__STRICT_ANSI__
+#define max(A, B)                              \
+       ({                                      \
+         int _a = (A), _b = (B);               \
+         _a > _b ? _a : _b;                    \
+       })
+#else /* !__GNUC__ */
+#define max(A, B)                              \
+       ((A) > (B) ? (A) : (B))
+#endif /* !__GNUC__ */
+#endif /* !max */
+
+/* Clamps A to be between B and C. */
+#define range(A, B, C)                         \
+       ((A) < (B) ? (B) : ((A) > (C) ? (C) : (A)))
+
+/* Divides nonnegative X by positive Y, rounding up. */
+#define DIV_RND_UP(X, Y)                       \
+       (((X) + ((Y) - 1)) / (Y))
+
+/* Returns nonnegative difference between {nonnegative X} and {the
+   least multiple of positive Y greater than or equal to X}. */
+#if __GNUC__ && !__STRICT_ANSI__
+#define REM_RND_UP(X, Y)                       \
+       ({                                      \
+         int rem = (X) % (Y);                  \
+         rem ? (Y) - rem : 0;                  \
+       })
+#else
+#define REM_RND_UP(X, Y)                       \
+       ((X) % (Y) ? (Y) - (X) % (Y) : 0)
+#endif
+
+/* Rounds X up to the next multiple of Y. */
+#define ROUND_UP(X, Y)                                 \
+       (((X) + ((Y) - 1)) / (Y) * (Y))
+
+/* Rounds X down to the previous multiple of Y. */
+#define ROUND_DOWN(X, Y)                       \
+       ((X) / (Y) * (Y))
+
+int intlog10 (unsigned);
+
+#endif /* math/misc.h */
diff --git a/src/modify-vars.c b/src/modify-vars.c
new file mode 100644 (file)
index 0000000..4fa71fd
--- /dev/null
@@ -0,0 +1,522 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <stdlib.h>
+#include <assert.h>
+#include "alloc.h"
+#include "avl.h"
+#include "bitvector.h"
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+#include "var.h"
+#include "vfm.h"
+
+/* FIXME: should change weighting variable, etc. */
+/* These control the way that compare_variables() does its work. */
+static int forward;            /* 1=FORWARD, 0=BACKWARD. */
+static int positional;         /* 1=POSITIONAL, 0=ALPHA. */
+
+static int compare_variables (const void *pa, const void *pb);
+
+/* Explains how to modify the variables in a dictionary in conjunction
+   with the p.mfv field of `variable'. */
+struct var_modification
+  {
+    /* REORDER information. */
+    struct variable **reorder_list;
+
+    /* RENAME information. */
+    struct variable **old_names;
+    char **new_names;
+    int n_rename;
+
+    /* DROP/KEEP information. */
+    int n_drop;                        /* Number of variables being dropped. */
+  };
+
+static struct dictionary *rearrange_dict (struct dictionary *d,
+                                         struct var_modification *vm,
+                                         int permanent);
+
+/* Performs MODIFY VARS command. */
+int
+cmd_modify_vars (void)
+{
+  /* Bits indicated whether we've already encountered a subcommand of
+     this type. */
+  unsigned already_encountered = 0;
+
+  /* What we're gonna do to the active file. */
+  struct var_modification vm;
+
+  lex_match_id ("MODIFY");
+  lex_match_id ("VARS");
+
+  vm.reorder_list = NULL;
+  vm.old_names = NULL;
+  vm.new_names = NULL;
+  vm.n_rename = 0;
+  vm.n_drop = 0;
+
+  /* Parse each subcommand. */
+  lex_match ('/');
+  for (;;)
+    {
+      if (lex_match_id ("REORDER"))
+       {
+         struct variable **v = NULL;
+         int nv = 0;
+
+         if (already_encountered & 1)
+           {
+             msg (SE, _("REORDER subcommand may be given at most once."));
+             goto lossage;
+           }
+         already_encountered |= 1;
+
+         lex_match ('=');
+         do
+           {
+             int prev_nv = nv;
+
+             forward = positional = 1;
+             if (lex_match_id ("FORWARD"));
+             else if (lex_match_id ("BACKWARD"))
+               forward = 0;
+             if (lex_match_id ("POSITIONAL"));
+             else if (lex_match_id ("ALPHA"))
+               positional = 0;
+
+             if (lex_match (T_ALL) || token == '/' || token == '.')
+               {
+                 if (prev_nv != 0)
+                   {
+                     msg (SE, _("Cannot specify ALL after specifying a set "
+                          "of variables."));
+                     goto lossage;
+                   }
+                 fill_all_vars (&v, &nv, FV_NO_SYSTEM);
+               }
+             else
+               {
+                 if (!lex_match ('('))
+                   {
+                     msg (SE, _("`(' expected on REORDER subcommand."));
+                     free (v);
+                     goto lossage;
+                   }
+                 if (!parse_variables (&default_dict, &v, &nv,
+                                       PV_APPEND | PV_NO_DUPLICATE))
+                   {
+                     free (v);
+                     goto lossage;
+                   }
+                 if (!lex_match (')'))
+                   {
+                     msg (SE, _("`)' expected following variable names on "
+                          "REORDER subcommand."));
+                     free (v);
+                     goto lossage;
+                   }
+               }
+             qsort (&v[prev_nv], nv - prev_nv, sizeof *v, compare_variables);
+           }
+         while (token != '/' && token != '.');
+
+         if (nv != default_dict.nvar)
+           {
+             size_t nbytes = DIV_RND_UP (default_dict.nvar, 8);
+             unsigned char *bits = local_alloc (nbytes);
+             int i;
+
+             memset (bits, 0, nbytes);
+             for (i = 0; i < nv; i++)
+               SET_BIT (bits, v[i]->index);
+             v = xrealloc (v, sizeof *v * default_dict.nvar);
+             for (i = 0; i < default_dict.nvar; i++)
+               if (!TEST_BIT (bits, i))
+                 v[nv++] = default_dict.var[i];
+             local_free (bits);
+           }
+
+         vm.reorder_list = v;
+       }
+      else if (lex_match_id ("RENAME"))
+       {
+         if (already_encountered & 2)
+           {
+             msg (SE, _("RENAME subcommand may be given at most once."));
+             goto lossage;
+           }
+         already_encountered |= 2;
+
+         lex_match ('=');
+         do
+           {
+             int prev_nv_1 = vm.n_rename;
+             int prev_nv_2 = vm.n_rename;
+
+             if (!lex_match ('('))
+               {
+                 msg (SE, _("`(' expected on RENAME subcommand."));
+                 goto lossage;
+               }
+             if (!parse_variables (&default_dict, &vm.old_names, &vm.n_rename,
+                                   PV_APPEND | PV_NO_DUPLICATE))
+               goto lossage;
+             if (!lex_match ('='))
+               {
+                 msg (SE, _("`=' expected between lists of new and old variable "
+                      "names on RENAME subcommand."));
+                 goto lossage;
+               }
+             if (!parse_DATA_LIST_vars (&vm.new_names, &prev_nv_1, PV_APPEND))
+               goto lossage;
+             if (prev_nv_1 != vm.n_rename)
+               {
+                 int i;
+
+                 msg (SE, _("Differing number of variables in old name list "
+                      "(%d) and in new name list (%d)."),
+                      vm.n_rename - prev_nv_2, prev_nv_1 - prev_nv_2);
+                 for (i = 0; i < prev_nv_1; i++)
+                   free (&vm.new_names[i]);
+                 free (&vm.new_names);
+                 vm.new_names = NULL;
+                 goto lossage;
+               }
+             if (!lex_match (')'))
+               {
+                 msg (SE, _("`)' expected after variable lists on RENAME "
+                      "subcommand."));
+                 goto lossage;
+               }
+           }
+         while (token != '.' && token != '/');
+       }
+      else if (lex_match_id ("KEEP"))
+       {
+         struct variable **keep_vars;
+         int nv;
+         int counter;
+         int i;
+
+         if (already_encountered & 4)
+           {
+             msg (SE, _("KEEP subcommand may be given at most once.  It may not"
+                  "be given in conjunction with the DROP subcommand."));
+             goto lossage;
+           }
+         already_encountered |= 4;
+
+         lex_match ('=');
+         if (!parse_variables (&default_dict, &keep_vars, &nv, PV_NONE))
+           goto lossage;
+
+         /* Transform the list of variables to keep into a list of
+            variables to drop.  First sort the keep list, then figure
+            out which variables are missing. */
+         forward = positional = 1;
+         qsort (keep_vars, nv, sizeof *keep_vars, compare_variables);
+
+         vm.n_drop = default_dict.nvar - nv;
+
+         counter = 0;
+         for (i = 0; i < nv; i++)
+           {
+             while (counter < keep_vars[i]->index)
+               default_dict.var[counter++]->p.mfv.drop_this_var = 1;
+             default_dict.var[counter++]->p.mfv.drop_this_var = 0;
+           }
+         while (counter < nv)
+           default_dict.var[counter++]->p.mfv.drop_this_var = 1;
+
+         free (keep_vars);
+       }
+      else if (lex_match_id ("DROP"))
+       {
+         struct variable **drop_vars;
+         int nv;
+         int i;
+
+         if (already_encountered & 4)
+           {
+             msg (SE, _("DROP subcommand may be given at most once.  It may not"
+                  "be given in conjunction with the KEEP subcommand."));
+             goto lossage;
+           }
+         already_encountered |= 4;
+
+         lex_match ('=');
+         if (!parse_variables (&default_dict, &drop_vars, &nv, PV_NONE))
+           goto lossage;
+         for (i = 0; i < default_dict.nvar; i++)
+           default_dict.var[i]->p.mfv.drop_this_var = 0;
+         for (i = 0; i < nv; i++)
+           drop_vars[i]->p.mfv.drop_this_var = 1;
+         vm.n_drop = nv;
+         free (drop_vars);
+       }
+      else if (lex_match_id ("MAP"))
+       {
+         struct dictionary *new_dict = rearrange_dict (&default_dict, &vm, 0);
+         if (!new_dict)
+           goto lossage;
+         /* FIXME: display new dictionary. */
+       }
+      else
+       {
+         if (token == T_ID)
+           msg (SE, _("Unrecognized subcommand name `%s'."), tokid);
+         else
+           msg (SE, _("Subcommand name expected."));
+         goto lossage;
+       }
+
+      if (token == '.')
+       break;
+      if (token != '/')
+       {
+         msg (SE, _("`/' or `.' expected."));
+         goto lossage;
+       }
+      lex_get ();
+    }
+
+  {
+    int i;
+
+    if (already_encountered & (1 | 4))
+      {
+       /* Read the data. */
+       procedure (NULL, NULL, NULL);
+      }
+
+    if (NULL == rearrange_dict (&default_dict, &vm, 1))
+      goto lossage;
+
+    free (vm.reorder_list);
+    free (vm.old_names);
+    for (i = 0; i < vm.n_rename; i++)
+      free (vm.new_names[i]);
+    free (vm.new_names);
+
+    return CMD_SUCCESS;
+  }
+
+lossage:
+  {
+    int i;
+
+    free (vm.reorder_list);
+    free (vm.old_names);
+    for (i = 0; i < vm.n_rename; i++)
+      free (vm.new_names[i]);
+    free (vm.new_names);
+    return CMD_FAILURE;
+  }
+}
+
+/* Compares a pair of variables according to the settings in `forward'
+   and `positional', returning a strcmp()-type result. */
+static int
+compare_variables (const void *pa, const void *pb)
+{
+  const struct variable *a = *(const struct variable **) pa;
+  const struct variable *b = *(const struct variable **) pb;
+
+  int result = positional ? a->index - b->index : strcmp (a->name, b->name);
+  return forward ? result : -result;
+}
+
+/* (Possibly) rearranges variables and (possibly) removes some
+   variables and (possibly) renames some more variables in dictionary
+   D.  There are two modes of operation, distinguished by the value of
+   PERMANENT:
+
+   If PERMANENT is nonzero, then the dictionary is modified in place.
+   Returns the new dictionary on success or NULL if there would have
+   been duplicate variable names in the resultant dictionary (in this
+   case the dictionary has not been modified).
+
+   If PERMANENT is zero, then the dictionary is copied to a new
+   dictionary structure that retains most of the same deep structure
+   as D.  The p.mfv.new_name field of each variable is set to what
+   would become the variable's new name if PERMANENT were nonzero.
+   Returns the new dictionary. */
+static struct dictionary *
+rearrange_dict (struct dictionary * d, struct var_modification * vm, int permanent)
+{
+  struct dictionary *n;
+
+  struct variable **save_var;
+
+  /* Linked list of variables for deletion. */
+  struct variable *head, *tail;
+
+  int i;
+
+  /* First decide what dictionary to modify. */
+  if (permanent == 0)
+    {
+      n = xmalloc (sizeof *n);
+      *n = *d;
+    }
+  else
+    n = d;
+  save_var = n->var;
+
+  /* Perform first half of renaming. */
+  if (permanent)
+    {
+      for (i = 0; i < d->nvar; i++)
+       d->var[i]->p.mfv.new_name[0] = 0;
+      d->var = xmalloc (sizeof *d->var * d->nvar);
+    }
+  else
+    for (i = 0; i < d->nvar; i++)
+      strcpy (d->var[i]->p.mfv.new_name, d->var[i]->name);
+  for (i = 0; i < vm->n_rename; i++)
+    strcpy (vm->old_names[i]->p.mfv.new_name, vm->new_names[i]);
+
+  /* Copy the variable list, reordering if appropriate. */
+  if (vm->reorder_list)
+    memcpy (n->var, vm->reorder_list, sizeof *n->var * d->nvar);
+  else if (!permanent)
+    for (i = 0; i < d->nvar; i++)
+      n->var[i] = d->var[i];
+
+  /* Drop all the unwanted variables. */
+  head = NULL;
+  if (vm->n_drop)
+    {
+      int j;
+
+      n->nvar = d->nvar - vm->n_drop;
+      for (i = j = 0; i < n->nvar; i++)
+       {
+         while (n->var[j]->p.mfv.drop_this_var != 0)
+           {
+             if (permanent)
+               {
+                 /* If this is permanent, then we have to keep a list
+                    of all the dropped variables because they must be
+                    free()'d, but can't be until we know that there
+                    aren't any duplicate variable names. */
+                 if (head)
+                   tail = tail->p.mfv.next = n->var[j];
+                 else
+                   head = tail = n->var[j];
+               }
+             j++;
+           }
+         n->var[i] = n->var[j++];
+       }
+      if (permanent)
+       tail->p.mfv.next = NULL;
+    }
+
+  /* Check for duplicate variable names if appropriate. */
+  if (permanent && vm->n_rename)
+    {
+      struct variable **v;
+
+      if (vm->reorder_list)
+       v = vm->reorder_list;   /* Reuse old buffer if possible. */
+      else
+       v = xmalloc (sizeof *v * n->nvar);
+      memcpy (v, n->var, sizeof *v * n->nvar);
+      forward = 1, positional = 0;
+      qsort (v, n->nvar, sizeof *v, compare_variables);
+      for (i = 1; i < n->nvar; i++)
+       if (!strcmp (n->var[i]->name, n->var[i - 1]->name))
+         {
+           msg (SE, _("Duplicate variable name `%s' after renaming."),
+                n->var[i]->name);
+           if (vm->reorder_list == NULL)
+             free (v);
+           n->var = save_var;
+           return NULL;
+         }
+      if (vm->reorder_list == NULL)
+       free (v);
+    }
+
+  /* Delete unwanted variables and finalize renaming if
+     appropriate. */
+  if (permanent)
+    {
+      /* Delete dropped variables for good. */
+      for (; head; head = tail)
+       {
+         tail = head->p.mfv.next;
+         clear_variable (n, head);
+         free (head);
+       }
+
+      /* Remove names from all renamed variables. */
+      head = NULL;
+      for (i = 0; i < n->nvar; i++)
+       if (n->var[i]->p.mfv.new_name[0])
+         {
+           avl_force_delete (n->var_by_name, n->var[i]);
+           if (head)
+             tail = tail->p.mfv.next = n->var[i];
+           else
+             head = tail = n->var[i];
+         }
+      if (head)
+       tail->p.mfv.next = NULL;
+
+      /* Put names onto renamed variables. */
+      for (; head; head = head->p.mfv.next)
+       {
+         strcpy (head->name, head->p.mfv.new_name);
+         avl_force_insert (n->var_by_name, head);
+       }
+      free (save_var);
+
+      /* As a final step the index fields must be redone. */
+      for (i = 0; i < n->nvar; i++)
+       n->var[i]->index = i;
+    }
+
+  return n;
+}
diff --git a/src/numeric.c b/src/numeric.c
new file mode 100644 (file)
index 0000000..f80865d
--- /dev/null
@@ -0,0 +1,213 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "cases.h"
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "str.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* Parses the NUMERIC command. */
+int
+cmd_numeric (void)
+{
+  int i;
+
+  /* Names of variables to create. */
+  char **v;
+  int nv;
+
+  /* Format spec for variables to create.  f.type==-1 if default is to
+     be used. */
+  struct fmt_spec f;
+
+  lex_match_id ("NUMERIC");
+  do
+    {
+      if (!parse_DATA_LIST_vars (&v, &nv, PV_NONE))
+       return CMD_PART_SUCCESS_MAYBE;
+
+      /* Get the optional format specification. */
+      if (lex_match ('('))
+       {
+         if (!parse_format_specifier (&f, 0))
+           goto fail;
+         if (formats[f.type].cat & FCAT_STRING)
+           {
+             msg (SE, _("Format type %s may not be used with a numeric "
+                  "variable."), fmt_to_string (&f));
+             goto fail;
+           }
+
+         if (!lex_match (')'))
+           {
+             msg (SE, _("`)' expected after output format."));
+             goto fail;
+           }
+       }
+      else
+       f.type = -1;
+
+      /* Create each variable. */
+      for (i = 0; i < nv; i++)
+       {
+         struct variable *new_var = create_variable (&default_dict, v[i],
+                                              NUMERIC, 0);
+         if (!new_var)
+           msg (SE, _("There is already a variable named %s."), v[i]);
+         else
+           {
+             if (f.type != -1)
+               new_var->print = new_var->write = f;
+             envector (new_var);
+           }
+       }
+
+      /* Clean up. */
+      for (i = 0; i < nv; i++)
+       free (v[i]);
+      free (v);
+    }
+  while (lex_match ('/'));
+
+  return lex_end_of_command ();
+
+  /* If we have an error at a point where cleanup is required,
+     flow-of-control comes here. */
+fail:
+  for (i = 0; i < nv; i++)
+    free (v[i]);
+  free (v);
+  return CMD_PART_SUCCESS_MAYBE;
+}
+
+/* Parses the STRING command. */
+int
+cmd_string (void)
+{
+  int i;
+
+  /* Names of variables to create. */
+  char **v;
+  int nv;
+
+  /* Format spec for variables to create. */
+  struct fmt_spec f;
+
+  /* Width of variables to create. */
+  int width;
+
+  lex_match_id ("STRING");
+  do
+    {
+      if (!parse_DATA_LIST_vars (&v, &nv, PV_NONE))
+       return CMD_PART_SUCCESS_MAYBE;
+
+      if (!lex_force_match ('(')
+         || !parse_format_specifier (&f, 0))
+       goto fail;
+      if (!(formats[f.type].cat & FCAT_STRING))
+       {
+         msg (SE, _("Format type %s may not be used with a string "
+              "variable."), fmt_to_string (&f));
+         goto fail;
+       }
+
+      if (!lex_match (')'))
+       {
+         msg (SE, _("`)' expected after output format."));
+         goto fail;
+       }
+
+      switch (f.type)
+       {
+       case FMT_A:
+         width = f.w;
+         break;
+       case FMT_AHEX:
+         width = f.w / 2;
+         break;
+       default:
+         assert (0);
+       }
+
+      /* Create each variable. */
+      for (i = 0; i < nv; i++)
+       {
+         struct variable *new_var = create_variable (&default_dict, v[i],
+                                              ALPHA, width);
+         if (!new_var)
+           msg (SE, _("There is already a variable named %s."), v[i]);
+         else
+           {
+             new_var->print = new_var->write = f;
+             envector (new_var);
+           }
+       }
+
+      /* Clean up. */
+      for (i = 0; i < nv; i++)
+       free (v[i]);
+      free (v);
+    }
+  while (lex_match ('/'));
+
+  return lex_end_of_command ();
+
+  /* If we have an error at a point where cleanup is required,
+     flow-of-control comes here. */
+fail:
+  for (i = 0; i < nv; i++)
+    free (v[i]);
+  free (v);
+  return CMD_PART_SUCCESS_MAYBE;
+}
+
+/* Parses the LEAVE command. */
+int
+cmd_leave (void)
+{
+  struct variable **v;
+  int nv;
+
+  int i;
+
+  lex_match_id ("LEAVE");
+  if (!parse_variables (NULL, &v, &nv, PV_NONE))
+    return CMD_FAILURE;
+  for (i = 0; i < nv; i++)
+    {
+      if (v[i]->left)
+       continue;
+      devector (v[i]);
+      v[i]->left = 1;
+      envector (v[i]);
+    }
+  free (v);
+
+  return lex_end_of_command ();
+}
diff --git a/src/output.c b/src/output.c
new file mode 100644 (file)
index 0000000..8cf5baf
--- /dev/null
@@ -0,0 +1,1324 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include "alloc.h"
+#include "approx.h"
+#include "error.h"
+#include "filename.h"
+#include "lexer.h"
+#include "misc.h"
+#include "output.h"
+#include "settings.h"
+#include "str.h"
+
+/* FIXME? Should the output configuration format be changed to
+   drivername:classname:devicetype:options, where devicetype is zero
+   or more of screen, printer, listing? */
+
+/* FIXME: Have the reentrancy problems been solved? */
+
+/* Where the output driver name came from. */
+enum
+  {
+    OUTP_S_COMMAND_LINE,       /* Specified by the user. */
+    OUTP_S_INIT_FILE           /* `default' or the init file. */
+  };
+
+/* Names the output drivers to be used. */
+struct outp_names
+  {
+    char *name;                        /* Name of the output driver. */
+    int source;                        /* OUTP_S_* */
+    struct outp_names *next, *prev;
+  };
+
+/* Defines an init file macro. */
+struct outp_defn
+  {
+    char *key;
+    char *value;
+    struct outp_defn *next, *prev;
+  };
+
+static struct outp_defn *outp_macros;
+static struct outp_names *outp_configure_vec;
+
+struct outp_driver_class_list *outp_class_list;
+struct outp_driver *outp_driver_list;
+
+char *outp_title;
+char *outp_subtitle;
+
+/* A set of OUTP_DEV_* bits indicating the devices that are
+   disabled. */
+int disabled_devices;
+
+static void destroy_driver (struct outp_driver *);
+static void configure_driver (char *);
+
+#if GLOBAL_DEBUGGING
+/* This mechanism attempts to catch reentrant use of outp_driver_list. */
+static int iterating_driver_list;
+
+#define reentrancy() msg (FE, _("Attempt to iterate driver list reentrantly."))
+#endif
+
+/* Add a class to the class list. */
+static void
+add_class (struct outp_class *class)
+{
+  struct outp_driver_class_list *new_list = xmalloc (sizeof *new_list);
+
+  new_list->class = class;
+  new_list->ref_count = 0;
+
+  if (!outp_class_list)
+    {
+      outp_class_list = new_list;
+      new_list->next = NULL;
+    }
+  else
+    {
+      new_list->next = outp_class_list;
+      outp_class_list = new_list;
+    }
+}
+
+/* Finds the outp_names in outp_configure_vec with name between BP and
+   EP exclusive. */
+static struct outp_names *
+search_names (char *bp, char *ep)
+{
+  struct outp_names *n;
+
+  for (n = outp_configure_vec; n; n = n->next)
+    if ((int) strlen (n->name) == ep - bp && !memcmp (n->name, bp, ep - bp))
+      return n;
+  return NULL;
+}
+
+/* Deletes outp_names NAME from outp_configure_vec. */
+static void
+delete_name (struct outp_names * n)
+{
+  free (n->name);
+  if (n->prev)
+    n->prev->next = n->next;
+  if (n->next)
+    n->next->prev = n->prev;
+  if (n == outp_configure_vec)
+    outp_configure_vec = n->next;
+  free (n);
+}
+
+/* Adds the name between BP and EP exclusive to list
+   outp_configure_vec with source SOURCE. */
+static void
+add_name (char *bp, char *ep, int source)
+{
+  struct outp_names *n = xmalloc (sizeof *n);
+  n->name = xmalloc (ep - bp + 1);
+  memcpy (n->name, bp, ep - bp);
+  n->name[ep - bp] = 0;
+  n->source = source;
+  n->next = outp_configure_vec;
+  n->prev = NULL;
+  if (outp_configure_vec)
+    outp_configure_vec->prev = n;
+  outp_configure_vec = n;
+}
+
+/* Checks that outp_configure_vec is empty, bitches & clears it if it
+   isn't. */
+static void
+check_configure_vec (void)
+{
+  struct outp_names *n;
+
+  for (n = outp_configure_vec; n; n = n->next)
+    if (n->source == OUTP_S_COMMAND_LINE)
+      msg (ME, _("Unknown output driver `%s'."), n->name);
+    else
+      msg (IE, _("Output driver `%s' referenced but never defined."), n->name);
+  outp_configure_clear ();
+}
+
+/* Searches outp_configure_vec for the name between BP and EP
+   exclusive.  If found, it is deleted, then replaced by the names
+   given in EP+1, if any. */
+static void
+expand_name (char *bp, char *ep)
+{
+  struct outp_names *n = search_names (bp, ep);
+  if (!n)
+    return;
+  delete_name (n);
+
+  bp = ep + 1;
+  for (;;)
+    {
+      while (isspace ((unsigned char) *bp))
+       bp++;
+      ep = bp;
+      while (*ep && !isspace ((unsigned char) *ep))
+       ep++;
+      if (bp == ep)
+       return;
+      if (!search_names (bp, ep))
+       add_name (bp, ep, OUTP_S_INIT_FILE);
+      bp = ep;
+    }
+}
+
+/* Looks for a macro with key KEY, and returns the corresponding value
+   if found, or NULL if not. */
+const char *
+find_defn_value (const char *key)
+{
+  static char buf[INT_DIGITS + 1];
+  struct outp_defn *d;
+
+  for (d = outp_macros; d; d = d->next)
+    if (!strcmp (key, d->key))
+      return d->value;
+  if (!strcmp (key, "viewwidth"))
+    {
+      sprintf (buf, "%d", set_viewwidth);
+      return buf;
+    }
+  else if (!strcmp (key, "viewlength"))
+    {
+      sprintf (buf, "%d", set_viewlength);
+      return buf;
+    }
+  else
+    return getenv (key);
+}
+
+/* Initializes global variables. */
+int
+outp_init (void)
+{
+  extern struct outp_class ascii_class;
+#if !NO_POSTSCRIPT
+  extern struct outp_class postscript_class;
+  extern struct outp_class epsf_class;
+#endif
+#if !NO_HTML
+  extern struct outp_class html_class;
+#endif
+
+  char def[] = "default";
+
+#if !NO_HTML
+  add_class (&html_class);
+#endif
+#if !NO_POSTSCRIPT
+  add_class (&epsf_class);
+  add_class (&postscript_class);
+#endif
+  add_class (&ascii_class);
+
+  add_name (def, &def[strlen (def)], OUTP_S_INIT_FILE);
+
+  return 1;
+}
+
+/* Deletes all the output macros. */
+static void
+delete_macros (void)
+{
+  struct outp_defn *d, *next;
+
+  for (d = outp_macros; d; d = next)
+    {
+      next = d->next;
+      free (d->key);
+      free (d->value);
+      free (d);
+    }
+}
+
+/* Reads the initialization file; initializes outp_driver_list. */
+int
+outp_read_devices (void)
+{
+  int result = 0;
+
+  char *init_fn;
+
+  FILE *f = NULL;
+  struct string line;
+  struct file_locator where;
+
+#if GLOBAL_DEBUGGING
+  if (iterating_driver_list)
+    reentrancy ();
+#endif
+
+  init_fn = fn_search_path (fn_getenv_default ("STAT_OUTPUT_INIT_FILE",
+                                              "devices"),
+                           fn_getenv_default ("STAT_OUTPUT_INIT_PATH",
+                                              config_path),
+                           NULL);
+  where.filename = init_fn;
+  where.line_number = 0;
+  err_push_file_locator (&where);
+
+  if (init_fn == NULL)
+    {
+      msg (IE, _("Cannot find output initialization file.  Use `-vv' to view "
+          "search path."));
+      goto exit;
+    }
+
+  msg (VM (1), _("%s: Opening device description file..."), init_fn);
+  f = fopen (init_fn, "r");
+  if (f == NULL)
+    {
+      msg (IE, _("Opening %s: %s."), init_fn, strerror (errno));
+      goto exit;
+    }
+
+  ds_init (NULL, &line, 128);
+  for (;;)
+    {
+      char *cp;
+
+      if (!ds_get_config_line (f, &line, &where))
+       {
+         if (ferror (f))
+           msg (ME, _("Reading %s: %s."), init_fn, strerror (errno));
+         break;
+       }
+      for (cp = ds_value (&line); isspace ((unsigned char) *cp); cp++);
+      if (!strncmp ("define", cp, 6) && isspace ((unsigned char) cp[6]))
+       outp_configure_macro (&cp[7]);
+      else if (*cp)
+       {
+         char *ep;
+         for (ep = cp; *ep && *ep != ':' && *ep != '='; ep++);
+         if (*ep == '=')
+           expand_name (cp, ep);
+         else if (*ep == ':')
+           {
+             struct outp_names *n = search_names (cp, ep);
+             if (n)
+               {
+                 configure_driver (cp);
+                 delete_name (n);
+               }
+           }
+         else
+           msg (IS, _("Syntax error."));
+       }
+    }
+  result = 1;
+
+  check_configure_vec ();
+
+exit:
+  err_pop_file_locator (&where);
+  if (f && -1 == fclose (f))
+    msg (MW, _("Closing %s: %s."), init_fn, strerror (errno));
+  free (init_fn);
+  ds_destroy (&line);
+  delete_macros ();
+  if (outp_driver_list == NULL)
+    msg (MW, _("No output drivers are active."));
+
+  if (result)
+    msg (VM (2), _("Device definition file read successfully."));
+  else
+    msg (VM (1), _("Error reading device definition file."));
+  return result;
+}
+
+/* Clear the list of drivers to configure. */
+void
+outp_configure_clear (void)
+{
+  struct outp_names *n, *next;
+
+  for (n = outp_configure_vec; n; n = next)
+    {
+      next = n->next;
+      free (n->name);
+      free (n);
+    }
+  outp_configure_vec = NULL;
+}
+
+/* Adds the name BP to the list of drivers to configure into
+   outp_driver_list. */
+void
+outp_configure_add (char *bp)
+{
+  char *ep = &bp[strlen (bp)];
+  if (!search_names (bp, ep))
+    add_name (bp, ep, OUTP_S_COMMAND_LINE);
+}
+
+/* Defines one configuration macro based on the text in BP, which
+   should be of the form `KEY=VALUE'. */
+void
+outp_configure_macro (char *bp)
+{
+  struct outp_defn *d;
+  char *ep;
+
+  while (isspace ((unsigned char) *bp))
+    bp++;
+  ep = bp;
+  while (*ep && !isspace ((unsigned char) *ep) && *ep != '=')
+    ep++;
+
+  d = xmalloc (sizeof *d);
+  d->key = xmalloc (ep - bp + 1);
+  memcpy (d->key, bp, ep - bp);
+  d->key[ep - bp] = 0;
+
+  /* Earlier definitions for a particular KEY override later ones. */
+  if (find_defn_value (d->key))
+    {
+      free (d->key);
+      free (d);
+      return;
+    }
+  
+  if (*ep == '=')
+    ep++;
+  while (isspace ((unsigned char) *ep))
+    ep++;
+  d->value = fn_interp_vars (ep, find_defn_value);
+  d->next = outp_macros;
+  d->prev = NULL;
+  if (outp_macros)
+    outp_macros->prev = d;
+  outp_macros = d;
+}
+
+/* Destroys all the drivers in driver list *DL and sets *DL to
+   NULL. */
+void
+destroy_list (struct outp_driver ** dl)
+{
+  struct outp_driver *d, *next;
+
+  for (d = *dl; d; d = next)
+    {
+      destroy_driver (d);
+      next = d->next;
+      free (d);
+    }
+  *dl = NULL;
+}
+
+/* Closes all the output drivers. */
+int
+outp_done (void)
+{
+#if GLOBAL_DEBUGGING
+  if (iterating_driver_list)
+    reentrancy ();
+#endif
+  destroy_list (&outp_driver_list);
+
+  return 1;
+}
+
+/* Display on stdout a list of all registered driver classes. */
+void
+outp_list_classes (void)
+{
+  int width = set_viewwidth;
+  struct outp_driver_class_list *c;
+
+  printf (_("Driver classes:\n\t"));
+  width -= 8;
+  for (c = outp_class_list; c; c = c->next)
+    {
+      if ((int) strlen (c->class->name) + 1 > width)
+       {
+         printf ("\n\t");
+         width = set_viewwidth - 8;
+       }
+      else
+       putc (' ', stdout);
+      fputs (c->class->name, stdout);
+    }
+  putc('\n', stdout);
+}
+
+static int op_token;           /* `=', 'a', 0. */
+static struct string op_tokstr;
+static char *prog;
+
+/* Parses a token from prog into op_token, op_tokstr.  Sets op_token
+   to '=' on an equals sign, to 'a' on a string or identifier token,
+   or to 0 at end of line.  Returns the new op_token. */
+static int
+tokener (void)
+{
+  if (op_token == 0)
+    {
+      msg (IS, _("Syntax error."));
+      return 0;
+    }
+
+  while (isspace ((unsigned char) *prog))
+    prog++;
+  if (!*prog)
+    {
+      op_token = 0;
+      return 0;
+    }
+
+  if (*prog == '=')
+    op_token = *prog++;
+  else
+    {
+      ds_clear (&op_tokstr);
+
+      if (*prog == '\'' || *prog == '"')
+       {
+         int quote = *prog++;
+
+         while (*prog && *prog != quote)
+           {
+             if (*prog != '\\')
+               ds_putchar (&op_tokstr, *prog++);
+             else
+               {
+                 int c;
+                 
+                 prog++;
+                 assert ((int) *prog); /* How could a line end in `\'? */
+                 switch (*prog++)
+                   {
+                   case '\'':
+                     c = '\'';
+                     break;
+                   case '"':
+                     c = '"';
+                     break;
+                   case '?':
+                     c = '?';
+                     break;
+                   case '\\':
+                     c = '\\';
+                     break;
+                   case '}':
+                     c = '}';
+                     break;
+                   case 'a':
+                     c = '\a';
+                     break;
+                   case 'b':
+                     c = '\b';
+                     break;
+                   case 'f':
+                     c = '\f';
+                     break;
+                   case 'n':
+                     c = '\n';
+                     break;
+                   case 'r':
+                     c = '\r';
+                     break;
+                   case 't':
+                     c = '\t';
+                     break;
+                   case 'v':
+                     c = '\v';
+                     break;
+                   case '0':
+                   case '1':
+                   case '2':
+                   case '3':
+                   case '4':
+                   case '5':
+                   case '6':
+                   case '7':
+                     {
+                       c = prog[-1] - '0';
+                       while (*prog >= '0' && *prog <= '7')
+                         c = c * 8 + *prog++ - '0';
+                     }
+                     break;
+                   case 'x':
+                   case 'X':
+                     {
+                       c = 0;
+                       while (isxdigit ((unsigned char) *prog))
+                         {
+                           c *= 16;
+                           if (isdigit ((unsigned char) *prog))
+                             c += *prog - '0';
+                           else
+                             c += (tolower ((unsigned char) (*prog))
+                                   - 'a' + 10);
+                           prog++;
+                         }
+                     }
+                     break;
+                   default:
+                     msg (IS, _("Syntax error in string constant."));
+                   }
+                 ds_putchar (&op_tokstr, (unsigned char) c);
+               }
+           }
+         prog++;
+       }
+      else
+       while (*prog && !isspace ((unsigned char) *prog) && *prog != '=')
+         ds_putchar (&op_tokstr, *prog++);
+      op_token = 'a';
+    }
+
+  return 1;
+}
+
+/* Applies the user-specified options in string S to output driver D
+   (at configuration time). */
+static void
+parse_options (char *s, struct outp_driver * d)
+{
+  prog = s;
+  op_token = -1;
+
+  ds_init (NULL, &op_tokstr, 64);
+  while (tokener ())
+    {
+      char key[65];
+
+      if (op_token != 'a')
+       {
+         msg (IS, _("Syntax error in options."));
+         break;
+       }
+
+      ds_truncate (&op_tokstr, 64);
+      strcpy (key, ds_value (&op_tokstr));
+
+      tokener ();
+      if (op_token != '=')
+       {
+         msg (IS, _("Syntax error in options (`=' expected)."));
+         break;
+       }
+
+      tokener ();
+      if (op_token != 'a')
+       {
+         msg (IS, _("Syntax error in options (value expected after `=')."));
+         break;
+       }
+      d->class->option (d, key, &op_tokstr);
+    }
+  ds_destroy (&op_tokstr);
+}
+
+/* Find the driver in outp_driver_list with name NAME. */
+static struct outp_driver *
+find_driver (char *name)
+{
+  struct outp_driver *d;
+
+#if GLOBAL_DEBUGGING
+  if (iterating_driver_list)
+    reentrancy ();
+#endif
+  for (d = outp_driver_list; d; d = d->next)
+    if (!strcmp (d->name, name))
+      return d;
+  return NULL;
+}
+
+/* Tokenize string S into colon-separated fields, removing leading and
+   trailing whitespace on tokens.  Returns a pointer to the
+   null-terminated token, which is formed by setting a NUL character
+   into the string.  After the first call, subsequent calls should set
+   S to NULL.  CP should be consistent across calls.  Returns NULL
+   after all fields have been used up.
+
+   FIXME: Should ignore colons inside double quotes. */
+static char *
+colon_tokenize (char *s, char **cp)
+{
+  char *token;
+  
+  if (!s)
+    {
+      s = *cp;
+      if (*s == 0)
+       return NULL;
+    }
+  token = s += strspn (s, " \t\v\r");
+  *cp = strchr (s, ':');
+  if (*cp == NULL)
+    s = *cp = strchr (s, 0);
+  else
+    s = (*cp)++;
+  while (s > token && strchr (" \t\v\r", s[-1]))
+    s--;
+  *s = 0;
+  return token;
+}
+
+/* String S is in format:
+   DRIVERNAME:CLASSNAME:DEVICETYPE:OPTIONS
+   Adds a driver to outp_driver_list pursuant to the specification
+   provided.  */
+static void
+configure_driver (char *s)
+{
+  char *token, *cp;
+  struct outp_driver *d = NULL, *iter;
+  struct outp_driver_class_list *c = NULL;
+
+  s = fn_interp_vars (s, find_defn_value);
+
+  /* Driver name. */
+  token = colon_tokenize (s, &cp);
+  if (!token)
+    {
+      msg (IS, _("Driver name expected."));
+      goto error;
+    }
+
+  d = xmalloc (sizeof *d);
+
+  d->class = NULL;
+  d->name = xstrdup (token);
+  d->driver_open = 0;
+  d->page_open = 0;
+
+  d->next = d->prev = NULL;
+
+  d->device = OUTP_DEV_NONE;
+  
+  d->ext = NULL;
+
+  /* Class name. */
+  token = colon_tokenize (NULL, &cp);
+  if (!token)
+    {
+      msg (IS, _("Class name expected."));
+      goto error;
+    }
+
+  for (c = outp_class_list; c; c = c->next)
+    if (!strcmp (c->class->name, token))
+      break;
+  if (!c)
+    {
+      msg (IS, _("Unknown output driver class `%s'."), token);
+      goto error;
+    }
+  
+  d->class = c->class;
+  if (!c->ref_count && !d->class->open_global (d->class))
+    {
+      msg (IS, _("Can't initialize output driver class `%s'."),
+          d->class->name);
+      goto error;
+    }
+  c->ref_count++;
+  if (!d->class->preopen_driver (d))
+    {
+      msg (IS, _("Can't initialize output driver `%s' of class `%s'."),
+          d->name, d->class->name);
+      goto error;
+    }
+
+  /* Device types. */
+  token = colon_tokenize (NULL, &cp);
+  if (token)
+    {
+      char *sp, *type;
+
+      for (type = strtok_r (token, " \t\r\v", &sp); type;
+          type = strtok_r (NULL, " \t\r\v", &sp))
+       {
+         if (!strcmp (type, "listing"))
+           d->device |= OUTP_DEV_LISTING;
+         else if (!strcmp (type, "screen"))
+           d->device |= OUTP_DEV_SCREEN;
+         else if (!strcmp (type, "printer"))
+           d->device |= OUTP_DEV_PRINTER;
+         else
+           {
+             msg (IS, _("Unknown device type `%s'."), type);
+             goto error;
+           }
+       }
+    }
+  
+  /* Options. */
+  token = colon_tokenize (NULL, &cp);
+  if (token)
+    parse_options (token, d);
+  if (!d->class->postopen_driver (d))
+    {
+      msg (IS, _("Can't complete initialization of output driver `%s' of "
+          "class `%s'."), d->name, d->class->name);
+      goto error;
+    }
+
+  /* Find like-named driver and delete. */
+  iter = find_driver (d->name);
+  if (iter)
+    destroy_driver (iter);
+
+  /* Add to list. */
+  d->next = outp_driver_list;
+  d->prev = NULL;
+  if (outp_driver_list)
+    outp_driver_list->prev = d;
+  outp_driver_list = d;
+  goto exit;
+
+error:
+  if (d)
+    destroy_driver (d);
+exit:
+  free (s);
+}
+
+/* Destroys output driver D. */
+static void
+destroy_driver (struct outp_driver *d)
+{
+  if (d->page_open)
+    d->class->close_page (d);
+  if (d->class)
+    {
+      struct outp_driver_class_list *c;
+
+      if (d->driver_open)
+       d->class->close_driver (d);
+
+      for (c = outp_class_list; c; c = c->next)
+       if (c->class == d->class)
+         break;
+      assert (c != NULL);
+      
+      c->ref_count--;
+      if (c->ref_count == 0)
+       {
+         if (!d->class->close_global (d->class))
+           msg (IS, _("Can't deinitialize output driver class `%s'."),
+                d->class->name);
+       }
+    }
+  free (d->name);
+
+  /* Remove this driver from the global driver list. */
+  if (d->prev)
+    d->prev->next = d->next;
+  if (d->next)
+    d->next->prev = d->prev;
+  if (d == outp_driver_list)
+    outp_driver_list = d->next;
+}
+
+static int
+option_cmp (const void *a, const void *b)
+{
+  const struct outp_option *o1 = a;
+  const struct outp_option *o2 = b;
+  return strcmp (o1->keyword, o2->keyword);
+}
+
+/* Tries to match S as one of the keywords in TAB, with corresponding
+   information structure INFO.  Returns category code or 0 on failure;
+   if category code is negative then stores subcategory in *SUBCAT. */
+int
+outp_match_keyword (const char *s, struct outp_option *tab,
+                   struct outp_option_info *info, int *subcat)
+{
+  char *cp;
+  struct outp_option *oip;
+
+  /* Form hash table. */
+  if (NULL == info->initial)
+    {
+      /* Count items. */
+      int count, i;
+      char s[256], *cp;
+      struct outp_option *ptr[255], **oip;
+
+      for (count = 0; tab[count].keyword[0]; count++)
+       ;
+
+      /* Sort items. */
+      qsort (tab, count, sizeof *tab, option_cmp);
+
+      cp = s;
+      oip = ptr;
+      *cp = tab[0].keyword[0];
+      *oip++ = &tab[0];
+      for (i = 0; i < count; i++)
+       if (tab[i].keyword[0] != *cp)
+         {
+           *++cp = tab[i].keyword[0];
+           *oip++ = &tab[i];
+         }
+      *++cp = 0;
+
+      info->initial = xstrdup (s);
+      info->options = xmalloc (sizeof *info->options * (cp - s));
+      memcpy (info->options, ptr, sizeof *info->options * (cp - s));
+    }
+
+  cp = info->initial;
+  oip = *info->options;
+
+  if (s[0] == 0)
+    return 0;
+  cp = strchr (info->initial, s[0]);
+  if (!cp)
+    return 0;
+#if 0
+  printf (_("Trying to find keyword `%s'...\n"), s);
+#endif
+  oip = info->options[cp - info->initial];
+  while (oip->keyword[0] == s[0])
+    {
+#if 0
+      printf ("- %s\n", oip->keyword);
+#endif
+      if (!strcmp (s, oip->keyword))
+       {
+         if (oip->cat < 0)
+           *subcat = oip->subcat;
+         return oip->cat;
+       }
+      oip++;
+    }
+
+  return 0;
+}
+
+/* Encapsulate two characters in a single int. */
+#define TWO_CHARS(A, B)                                \
+       ((A) + ((B)<<8))
+
+/* Determines the size of a dimensional measurement and returns the
+   size in units of 1/72000".  Units if not specified explicitly are
+   inches for values under 50, millimeters otherwise.  Returns 0,
+   stores NULL to *TAIL on error; otherwise returns dimension, stores
+   address of next */
+int
+outp_evaluate_dimension (char *dimen, char **tail)
+{
+  char *s = dimen;
+  char *ptail;
+  double value;
+
+  value = strtod (s, &ptail);
+  if (ptail == s)
+    goto lossage;
+  if (*ptail == '-')
+    {
+      double b, c;
+      s = &ptail[1];
+      b = strtod (s, &ptail);
+      if (b <= 0.0 || ptail == s)
+       goto lossage;
+      if (*ptail != '/')
+       goto lossage;
+      s = &ptail[1];
+      c = strtod (s, &ptail);
+      if (c <= 0.0 || ptail == s)
+       goto lossage;
+      s = ptail;
+      if (approx_eq (c, 0.0))
+       goto lossage;
+      if (value > 0)
+       value += b / c;
+      else
+       value -= b / c;
+    }
+  else if (*ptail == '/')
+    {
+      double b;
+      s = &ptail[1];
+      b = strtod (s, &ptail);
+      if (approx_le (b, 0.0) || ptail == s)
+       goto lossage;
+      s = ptail;
+      value /= b;
+    }
+  else
+    s = ptail;
+  if (*s == 0 || isspace ((unsigned char) *s))
+    {
+      if (value < 50.0)
+       value *= 72000;
+      else
+       value *= 72000 / 25.4;
+    }
+  else
+    {
+      double factor;
+
+      /* Standard TeX units are supported. */
+      if (*s == '"')
+       factor = 72000, s++;
+      else
+       switch (TWO_CHARS (s[0], s[1]))
+         {
+         case TWO_CHARS ('p', 't'):
+           factor = 72000 / 72.27;
+           break;
+         case TWO_CHARS ('p', 'c'):
+           factor = 72000 / 72.27 * 12.0;
+           break;
+         case TWO_CHARS ('i', 'n'):
+           factor = 72000;
+           break;
+         case TWO_CHARS ('b', 'p'):
+           factor = 72000 / 72.0;
+           break;
+         case TWO_CHARS ('c', 'm'):
+           factor = 72000 / 2.54;
+           break;
+         case TWO_CHARS ('m', 'm'):
+           factor = 72000 / 25.4;
+           break;
+         case TWO_CHARS ('d', 'd'):
+           factor = 72000 / 72.27 * 1.0700086;
+           break;
+         case TWO_CHARS ('c', 'c'):
+           factor = 72000 / 72.27 * 12.840104;
+           break;
+         case TWO_CHARS ('s', 'p'):
+           factor = 72000 / 72.27 / 65536.0;
+           break;
+         default:
+           msg (SE, _("Unit \"%s\" is unknown in dimension \"%s\"."), s, dimen);
+           *tail = NULL;
+           return 0;
+         }
+      ptail += 2;
+      value *= factor;
+    }
+  if (approx_lt (value, 0.0))
+    goto lossage;
+  if (tail)
+    *tail = ptail;
+  return value + 0.5;
+
+lossage:
+  *tail = NULL;
+  msg (SE, _("Bad dimension \"%s\"."), dimen);
+  return 0;
+}
+
+/* Stores the dimensions in 1/72000" units of paper identified by
+   SIZE, which is of form `HORZ x VERT' or `HORZ by VERT' where each
+   of HORZ and VERT are dimensions, into *H and *V.  Return nonzero on
+   success. */
+static int
+internal_get_paper_size (char *size, int *h, int *v)
+{
+  char *tail;
+
+  while (isspace ((unsigned char) *size))
+    size++;
+  *h = outp_evaluate_dimension (size, &tail);
+  if (tail == NULL)
+    return 0;
+  while (isspace ((unsigned char) *tail))
+    tail++;
+  if (*tail == 'x')
+    tail++;
+  else if (*tail == 'b' && tail[1] == 'y')
+    tail += 2;
+  else
+    {
+      msg (SE, _("`x' expected in paper size `%s'."), size);
+      return 0;
+    }
+  *v = outp_evaluate_dimension (tail, &tail);
+  if (tail == NULL)
+    return 0;
+  while (isspace ((unsigned char) *tail))
+    tail++;
+  if (*tail)
+    {
+      msg (SE, _("Trailing garbage `%s' on paper size `%s'."), tail, size);
+      return 0;
+    }
+  
+  return 1;
+}
+
+/* Stores the dimensions, in 1/72000" units, of paper identified by
+   SIZE into *H and *V.  SIZE may be a pair of dimensions of form `H x
+   V', or it may be a case-insensitive paper identifier, which is
+   looked up in the `papersize' configuration file.  Returns nonzero
+   on success.  May modify SIZE. */
+/* Don't read further unless you've got a strong stomach. */
+int
+outp_get_paper_size (char *size, int *h, int *v)
+{
+  struct paper_size
+    {
+      char *name;
+      int use;
+      int h, v;
+    };
+
+  static struct paper_size cache[4];
+  static int use;
+
+  FILE *f;
+  char *pprsz_fn;
+
+  struct string line;
+  struct file_locator where;
+
+  int free_it = 0;
+  int result = 0;
+  int min_value, min_index;
+  char *ep;
+  int i;
+
+  while (isspace ((unsigned char) *size))
+    size++;
+  if (isdigit ((unsigned char) *size))
+    return internal_get_paper_size (size, h, v);
+  ep = size;
+  while (*ep)
+    ep++;
+  while (isspace ((unsigned char) *ep) && ep >= size)
+    ep--;
+  if (ep == size)
+    {
+      msg (SE, _("Paper size name must not be empty."));
+      return 0;
+    }
+  
+  ep++;
+  if (*ep)
+    *ep = 0;
+
+  use++;
+  for (i = 0; i < 4; i++)
+    if (cache[i].name != NULL && !strcasecmp (cache[i].name, size))
+      {
+       *h = cache[i].h;
+       *v = cache[i].v;
+       cache[i].use = use;
+       return 1;
+      }
+
+  pprsz_fn = fn_search_path (fn_getenv_default ("STAT_OUTPUT_PAPERSIZE_FILE",
+                                               "papersize"),
+                            fn_getenv_default ("STAT_OUTPUT_INIT_PATH",
+                                               config_path),
+                            NULL);
+
+  where.filename = pprsz_fn;
+  where.line_number = 0;
+  err_push_file_locator (&where);
+
+  if (pprsz_fn == NULL)
+    {
+      msg (IE, _("Cannot find `papersize' configuration file."));
+      goto exit;
+    }
+
+  msg (VM (1), _("%s: Opening paper size definition file..."), pprsz_fn);
+  f = fopen (pprsz_fn, "r");
+  if (!f)
+    {
+      msg (IE, _("Opening %s: %s."), pprsz_fn, strerror (errno));
+      goto exit;
+    }
+
+  ds_init (NULL, &line, 128);
+  for (;;)
+    {
+      char *cp, *bp, *ep;
+
+      if (!ds_get_config_line (f, &line, &where))
+       {
+         if (ferror (f))
+           msg (ME, _("Reading %s: %s."), pprsz_fn, strerror (errno));
+         break;
+       }
+      for (cp = ds_value (&line); isspace ((unsigned char) *cp); cp++);
+      if (*cp == 0)
+       continue;
+      if (*cp != '"')
+       goto lex_error;
+      for (bp = ep = cp + 1; *ep && *ep != '"'; ep++);
+      if (!*ep)
+       goto lex_error;
+      *ep = 0;
+      if (0 != strcasecmp (bp, size))
+       continue;
+
+      for (cp = ep + 1; isspace ((unsigned char) *cp); cp++);
+      if (*cp == '=')
+       {
+         size = xmalloc (ep - bp + 1);
+         strcpy (size, bp);
+         free_it = 1;
+         continue;
+       }
+      size = &ep[1];
+      break;
+
+    lex_error:
+      msg (IE, _("Syntax error in paper size definition."));
+    }
+
+  /* We found the one we want! */
+  result = internal_get_paper_size (size, h, v);
+  if (result)
+    {
+      min_value = cache[0].use;
+      min_index = 0;
+      for (i = 1; i < 4; i++)
+       if (cache[0].use < min_value)
+         {
+           min_value = cache[i].use;
+           min_index = i;
+         }
+      free (cache[min_index].name);
+      cache[min_index].name = xstrdup (size);
+      cache[min_index].use = use;
+      cache[min_index].h = *h;
+      cache[min_index].v = *v;
+    }
+
+exit:
+  err_pop_file_locator (&where);
+  ds_destroy (&line);
+  if (free_it)
+    free (size);
+
+  if (result)
+    msg (VM (2), _("Paper size definition file read successfully."));
+  else
+    msg (VM (1), _("Error reading paper size definition file."));
+  
+  return result;
+}
+
+/* If D is NULL, returns the first enabled driver if any, NULL if
+   none.  Otherwise D must be the last driver returned by this
+   function, in which case the next enabled driver is returned or NULL
+   if that was the last. */
+struct outp_driver *
+outp_drivers (struct outp_driver *d)
+{
+#if GLOBAL_DEBUGGING
+  struct outp_driver *orig_d = d;
+#endif
+
+  for (;;)
+    {
+      if (d == NULL)
+       d = outp_driver_list;
+      else
+       d = d->next;
+
+      if (d == NULL
+         || (d->driver_open
+             && (d->device == 0
+                 || (d->device & disabled_devices) != d->device)))
+       break;
+    }
+
+#if GLOBAL_DEBUGGING
+  if (d && !orig_d)
+    {
+      if (iterating_driver_list++)
+       reentrancy ();
+    }
+  else if (orig_d && !d)
+    {
+      assert (iterating_driver_list == 1);
+      iterating_driver_list = 0;
+    }
+#endif
+
+  return d;
+}
+
+/* Enables (if ENABLE is nonzero) or disables (if ENABLE is zero) the
+   device(s) given in mask DEVICE. */
+void
+outp_enable_device (int enable, int device)
+{
+  if (enable)
+    disabled_devices &= ~device;
+  else
+    disabled_devices |= device;
+}
+
+/* Ejects the paper on device D, if the page is not blank. */
+int
+outp_eject_page (struct outp_driver *d)
+{
+  if (d->page_open == 0)
+    return 1;
+  
+  if (d->cp_y != 0)
+    {
+      d->cp_x = d->cp_y = 0;
+
+      if (d->class->close_page (d) == 0)
+       msg (ME, _("Error closing page on %s device of %s class."),
+            d->name, d->class->name);
+      if (d->class->open_page (d) == 0)
+       {
+         msg (ME, _("Error opening page on %s device of %s class."),
+              d->name, d->class->name);
+         return 0;
+       }
+    }
+  return 1;
+}
+
+/* Returns the width of string S, in device units, when output on
+   device D. */
+int
+outp_string_width (struct outp_driver *d, const char *s)
+{
+  struct outp_text text;
+
+  text.options = OUTP_T_JUST_LEFT;
+  ls_init (&text.s, (char *) s, strlen (s));
+  d->class->text_metrics (d, &text);
+
+  return text.h;
+}
diff --git a/src/output.h b/src/output.h
new file mode 100644 (file)
index 0000000..00e5b23
--- /dev/null
@@ -0,0 +1,289 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !output_h
+#define output_h 1
+
+#include "str.h"
+
+/* A rectangle. */
+struct rect
+  {
+    int x1, y1;                        /* Upper left. */
+    int x2, y2;                        /* Lower right, not part of the rectangle. */
+  };
+
+#if __GNUC__ > 1 && defined(__OPTIMIZE__)
+extern inline int width (rect r) __attribute__ ((const));
+extern inline int height (rect r) __attribute__ ((const));
+
+extern inline int
+width (rect r)
+{
+  return r.x2 - r.x1 + 1;
+}
+
+extern inline int 
+height (rect r)
+{
+  return r.y2 - r.y1 + 1;
+}
+#else /* !__GNUC__ */
+#define width(R)                               \
+       ((R).x2 - (R).x1 + 1)
+#define height(R)                              \
+       ((R).y2 - (R).y1 + 1)
+#endif /* !__GNUC__ */
+
+/* Color descriptor. */
+struct color
+  {
+    int flags;                 /* 0=normal, 1=transparent (ignore r,g,b). */
+    int r;                     /* Red component, 0-65535. */
+    int g;                     /* Green component, 0-65535. */
+    int b;                     /* Blue component, 0-65535. */
+  };
+
+/* Mount positions for the four basic fonts.  Do not change the values. */
+enum
+  {
+    OUTP_F_R,                  /* Roman font. */
+    OUTP_F_I,                  /* Italic font. */
+    OUTP_F_B,                  /* Bold font. */
+    OUTP_F_BI                  /* Bold-italic font. */
+  };
+
+/* Line styles.  These must match:
+   som.h:SLIN_*
+   ascii.c:ascii_line_*() 
+   postscript.c:ps_line_*() */
+enum
+  {
+    OUTP_L_NONE = 0,           /* No line. */
+    OUTP_L_SINGLE = 1,         /* Single line. */
+    OUTP_L_DOUBLE = 2,         /* Double line. */
+    OUTP_L_SPECIAL = 3,                /* Special line of driver-defined style. */
+
+    OUTP_L_COUNT               /* Number of line styles. */
+  };
+
+/* Contains a line style for each part of an intersection. */
+struct outp_styles
+  {
+    int l;                     /* left */
+    int t;                     /* top */
+    int r;                     /* right */
+    int b;                     /* bottom */
+  };
+
+/* Text display options. */
+enum
+  {
+    OUTP_T_NONE = 0,
+
+    /* Must match tab.h:TAB_*. */
+    OUTP_T_JUST_MASK = 00003,  /* Justification mask. */
+    OUTP_T_JUST_RIGHT = 00000, /* Right justification. */
+    OUTP_T_JUST_LEFT = 00001,  /* Left justification. */
+    OUTP_T_JUST_CENTER = 00002,        /* Center justification. */
+
+    OUTP_T_HORZ = 00010,       /* Horizontal size is specified. */
+    OUTP_T_VERT = 00020,       /* (Max) vertical size is specified. */
+
+    OUTP_T_0 = 00140,          /* Normal orientation. */
+    OUTP_T_CC90 = 00040,       /* 90 degrees counterclockwise. */
+    OUTP_T_CC180 = 00100,      /* 180 degrees counterclockwise. */
+    OUTP_T_CC270 = 00140,      /* 270 degrees counterclockwise. */
+    OUTP_T_C90 = 00140,                /* 90 degrees clockwise. */
+    OUTP_T_C180 = 00100,       /* 180 degrees clockwise. */
+    OUTP_T_C270 = 00040,       /* 270 degrees clockwise. */
+
+    /* Internal use by drivers only. */
+    OUTP_T_INTERNAL_DRAW = 01000       /* 1=Draw the text, 0=Metrics only. */
+  };
+
+/* Describes text output. */
+struct outp_text
+  {
+    /* Public. */
+    int options;               /* What is specified. */
+    struct len_string s;       /* String. */
+    int h, v;                  /* Horizontal, vertical size. */
+    int x, y;                  /* Position. */
+
+    /* Internal use only. */
+    int w, l;                  /* Width, length. */
+  };
+
+struct som_table;
+struct outp_driver;
+
+/* Defines a class of output driver. */
+struct outp_class
+  {
+    /* Basic class information. */
+    const char *name;          /* Name of this driver class. */
+    int magic;                 /* Driver-specific constant. */
+    int special;               /* Boolean value. */
+
+    /* Static member functions. */
+    int (*open_global) (struct outp_class *);
+    int (*close_global) (struct outp_class *);
+    int *(*font_sizes) (struct outp_class *, int *n_valid_sizes);
+
+    /* Virtual member functions. */
+    int (*preopen_driver) (struct outp_driver *);
+    void (*option) (struct outp_driver *, const char *key,
+                   const struct string *value);
+    int (*postopen_driver) (struct outp_driver *);
+    int (*close_driver) (struct outp_driver *);
+
+    int (*open_page) (struct outp_driver *);
+    int (*close_page) (struct outp_driver *);
+
+    /* special != 0: Used to submit tables for output. */
+    void (*submit) (struct outp_driver *, struct som_table *);
+    
+    /* special != 0: Methods below need not be defined. */
+    
+    /* Line methods. */
+    void (*line_horz) (struct outp_driver *, const struct rect *,
+                      const struct color *, int style);
+    void (*line_vert) (struct outp_driver *, const struct rect *,
+                      const struct color *, int style);
+    void (*line_intersection) (struct outp_driver *, const struct rect *,
+                              const struct color *,
+                              const struct outp_styles *style);
+
+    /* Drawing methods. */
+    void (*box) (struct outp_driver *, const struct rect *,
+                const struct color *bord, const struct color *fill);
+    void (*polyline_begin) (struct outp_driver *, const struct color *);
+    void (*polyline_point) (struct outp_driver *, int, int);
+    void (*polyline_end) (struct outp_driver *);
+
+    /* Text methods. */
+    void (*text_set_font_by_name) (struct outp_driver *, const char *s);
+    void (*text_set_font_by_position) (struct outp_driver *, int);
+    void (*text_set_font_family) (struct outp_driver *, const char *s);
+    const char *(*text_get_font_name) (struct outp_driver *);
+    const char *(*text_get_font_family) (struct outp_driver *);
+    int (*text_set_size) (struct outp_driver *, int);
+    int (*text_get_size) (struct outp_driver *, int *em_width);
+    void (*text_metrics) (struct outp_driver *, struct outp_text *);
+    void (*text_draw) (struct outp_driver *, struct outp_text *);
+  };
+
+/* Device types. */
+enum
+  {
+    OUTP_DEV_NONE = 0,         /* None of the below. */
+    OUTP_DEV_LISTING = 001,    /* Listing device. */
+    OUTP_DEV_SCREEN = 002,     /* Screen device. */
+    OUTP_DEV_PRINTER = 004,    /* Printer device. */
+    OUTP_DEV_DISABLED = 010    /* Broken device. */
+  };
+
+/* Defines the configuration of an output driver. */
+struct outp_driver
+  {
+    struct outp_class *class;          /* Driver class. */
+    char *name;                        /* Name of this driver. */
+    int driver_open;           /* 1=driver is open, 0=driver is closed. */
+    int page_open;             /* 1=page is open, 0=page is closed. */
+
+    struct outp_driver *next, *prev;   /* Next, previous output driver in list. */
+
+    int device;                        /* Zero or more of OUTP_DEV_*. */
+    int res, horiz, vert;      /* Device resolution. */
+    int width, length;         /* Page size. */
+
+    int cp_x, cp_y;            /* Current position. */
+    int font_height;           /* Default font character height. */
+    int prop_em_width;         /* Proportional font em width. */
+    int fixed_width;           /* Fixed-pitch font character width. */
+    int horiz_line_width[OUTP_L_COUNT];        /* Width of horizontal lines. */
+    int vert_line_width[OUTP_L_COUNT]; /* Width of vertical lines. */
+    int horiz_line_spacing[1 << OUTP_L_COUNT];
+    int vert_line_spacing[1 << OUTP_L_COUNT];
+
+    void *ext;                 /* Private extension record. */
+    void *prc;                 /* Per-procedure extension record. */
+  };
+
+/* Option structure for the keyword recognizer. */
+struct outp_option
+  {
+    const char *keyword;       /* Keyword name. */
+    int cat;                   /* Category. */
+    int subcat;                        /* Subcategory. */
+  };
+
+/* Information structure for the keyword recognizer. */
+struct outp_option_info
+  {
+    char *initial;                     /* Initial characters. */
+    struct outp_option **options;      /* Search starting points. */
+  };
+
+/* A list of driver classes. */
+struct outp_driver_class_list
+  {
+    int ref_count;
+    struct outp_class *class;
+    struct outp_driver_class_list *next;
+  };
+
+/* List of known output driver classes. */
+extern struct outp_driver_class_list *outp_class_list;
+
+/* List of configured output drivers. */
+extern struct outp_driver *outp_driver_list;
+
+/* Title, subtitle. */
+extern char *outp_title;
+extern char *outp_subtitle;
+
+int outp_init (void);
+int outp_read_devices (void);
+int outp_done (void);
+
+void outp_configure_clear (void);
+void outp_configure_add (char *);
+void outp_configure_macro (char *);
+
+void outp_list_classes (void);
+
+void outp_enable_device (int enable, int device);
+struct outp_driver *outp_drivers (struct outp_driver *);
+
+int outp_match_keyword (const char *, struct outp_option *,
+                       struct outp_option_info *, int *);
+
+int outp_evaluate_dimension (char *, char **);
+int outp_get_paper_size (char *, int *h, int *v);
+
+int outp_eject_page (struct outp_driver *);
+
+int outp_string_width (struct outp_driver *, const char *);
+
+/* Imported from som-frnt.c. */
+void som_destroy_driver (struct outp_driver *);
+
+#endif /* output.h */
diff --git a/src/pfm-read.c b/src/pfm-read.c
new file mode 100644 (file)
index 0000000..d5ddd21
--- /dev/null
@@ -0,0 +1,1065 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <math.h>
+#include "alloc.h"
+#include "avl.h"
+#include "file-handle.h"
+#include "format.h"
+#include "getline.h"
+#include "magic.h"
+#include "misc.h"
+#include "pfm.h"
+#include "str.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* pfm's file_handle extension. */
+struct pfm_fhuser_ext
+  {
+    FILE *file;                        /* Actual file. */
+
+    struct dictionary *dict;   /* File's dictionary. */
+    int weight_index;          /* 0-based index of weight variable, or -1. */
+
+    unsigned char *trans;      /* 256-byte character set translation table. */
+
+    int nvars;                 /* Number of variables. */
+    int *vars;                 /* Variable widths, 0 for numeric. */
+    int case_size;             /* Number of `value's per case. */
+
+    unsigned char buf[83];     /* Input buffer. */
+    unsigned char *bp;         /* Buffer pointer. */
+    int cc;                    /* Current character. */
+  };
+
+static struct fh_ext_class pfm_r_class;
+
+static int 
+corrupt_msg (struct file_handle *h, const char *format,...)
+  __attribute__ ((format (printf, 2, 3)));
+
+/* Displays a corruption error. */
+static int
+corrupt_msg (struct file_handle *h, const char *format, ...)
+{
+  struct pfm_fhuser_ext *ext = h->ext;
+  char buf[1024];
+  
+  {
+    va_list args;
+
+    va_start (args, format);
+    vsnprintf (buf, 1024, format, args);
+    va_end (args);
+  }
+  
+  {
+    char *title;
+    struct error e;
+
+    e.class = ME;
+    getl_location (&e.where.filename, &e.where.line_number);
+    e.title = title = local_alloc (strlen (h->fn) + 80);
+    sprintf (title, _("portable file %s corrupt at offset %ld: "),
+            h->fn, ftell (ext->file) - (82 - (long) (ext->bp - ext->buf)));
+    e.text = buf;
+
+    err_vmsg (&e);
+
+    local_free (title);
+  }
+  
+  return 0;
+}
+
+/* Closes a portable file after we're done with it. */
+static void
+pfm_close (struct file_handle * h)
+{
+  struct pfm_fhuser_ext *ext = h->ext;
+
+  if (EOF == fclose (ext->file))
+    msg (ME, _("%s: Closing portable file: %s."), h->fn, strerror (errno));
+  free (ext->vars);
+  free (ext->trans);
+  free (h->ext);
+}
+
+/* Displays the message X with corrupt_msg, then jumps to the lossage
+   label. */
+#define lose(X)                                        \
+       do                                      \
+         {                                     \
+           corrupt_msg X;                      \
+           goto lossage;                       \
+         }                                     \
+       while (0)
+
+/* Read an 80-character line into handle H's buffer.  Return
+   success. */
+static int
+fill_buf (struct file_handle *h)
+{
+  struct pfm_fhuser_ext *ext = h->ext;
+
+  if (80 != fread (ext->buf, 1, 80, ext->file))
+    lose ((h, _("Unexpected end of file.")));
+
+  /* PORTME: line ends. */
+  {
+    int c;
+    
+    c = getc (ext->file);
+    if (c != '\n' && c != '\r')
+      lose ((h, _("Bad line end.")));
+
+    c = getc (ext->file);
+    if (c != '\n' && c != '\r')
+      ungetc (c, ext->file);
+  }
+  
+  if (ext->trans)
+    {
+      int i;
+      
+      for (i = 0; i < 80; i++)
+       ext->buf[i] = ext->trans[ext->buf[i]];
+    }
+
+  ext->bp = ext->buf;
+
+  return 1;
+
+ lossage:
+  return 0;
+}
+
+/* Read a single character into cur_char.  Return success; */
+static int
+read_char (struct file_handle *h)
+{
+  struct pfm_fhuser_ext *ext = h->ext;
+
+  if (ext->bp >= &ext->buf[80] && !fill_buf (h))
+    return 0;
+  ext->cc = *ext->bp++;
+  return 1;
+}
+
+/* Advance a single character. */
+#define advance() if (!read_char (h)) goto lossage
+
+/* Skip a single character if present, and return whether it was
+   skipped. */
+static inline int
+skip_char (struct file_handle *h, int c)
+{
+  struct pfm_fhuser_ext *ext = h->ext;
+  
+  if (ext->cc == c)
+    {
+      advance ();
+      return 1;
+    }
+ lossage:
+  return 0;
+}
+
+/* Skip a single character if present, and return whether it was
+   skipped. */
+#define match(C) skip_char (h, C)
+
+static int read_header (struct file_handle *h);
+static int read_version_data (struct file_handle *h, struct pfm_read_info *inf);
+static int read_variables (struct file_handle *h);
+static int read_value_label (struct file_handle *h);
+void dump_dictionary (struct dictionary *dict);
+
+/* Reads the dictionary from file with handle H, and returns it in a
+   dictionary structure.  This dictionary may be modified in order to
+   rename, reorder, and delete variables, etc. */
+struct dictionary *
+pfm_read_dictionary (struct file_handle *h, struct pfm_read_info *inf)
+{
+  /* The file handle extension record. */
+  struct pfm_fhuser_ext *ext;
+
+  /* Check whether the file is already open. */
+  if (h->class == &pfm_r_class)
+    {
+      ext = h->ext;
+      return ext->dict;
+    }
+  else if (h->class != NULL)
+    {
+      msg (ME, _("Cannot read file %s as portable file: already opened "
+                "for %s."),
+          fh_handle_name (h), h->class->name);
+      return NULL;
+    }
+
+  msg (VM (1), _("%s: Opening portable-file handle %s for reading."),
+       fh_handle_filename (h), fh_handle_name (h));
+
+  /* Open the physical disk file. */
+  ext = xmalloc (sizeof (struct pfm_fhuser_ext));
+  ext->file = fopen (h->norm_fn, "rb");
+  if (ext->file == NULL)
+    {
+      msg (ME, _("An error occurred while opening \"%s\" for reading "
+          "as a portable file: %s."), h->fn, strerror (errno));
+      err_cond_fail ();
+      free (ext);
+      return NULL;
+    }
+
+  /* Initialize the sfm_fhuser_ext structure. */
+  h->class = &pfm_r_class;
+  h->ext = ext;
+  ext->dict = NULL;
+  ext->trans = NULL;
+  if (!fill_buf (h))
+    goto lossage;
+  advance ();
+
+  /* Read the header. */
+  if (!read_header (h))
+    goto lossage;
+  
+  /* Read version, date info, product identification. */
+  if (!read_version_data (h, inf))
+    goto lossage;
+
+  /* Read variables. */
+  if (!read_variables (h))
+    goto lossage;
+
+  /* Value labels. */
+  while (match (77 /* D */))
+    if (!read_value_label (h))
+      goto lossage;
+
+  if (!match (79 /* F */))
+    lose ((h, _("Data record expected.")));
+
+  msg (VM (2), _("Read portable-file dictionary successfully."));
+
+#if DEBUGGING
+  dump_dictionary (ext->dict);
+#endif
+  return ext->dict;
+
+ lossage:
+  /* Come here on unsuccessful completion. */
+  msg (VM (1), _("Error reading portable-file dictionary."));
+  
+  fclose (ext->file);
+  if (ext && ext->dict)
+    free_dictionary (ext->dict);
+  free (ext);
+  h->class = NULL;
+  h->ext = NULL;
+  return NULL;
+}
+\f
+/* Read a floating point value and return its value, or
+   second_lowest_value on error. */
+static double
+read_float (struct file_handle *h)
+{
+  struct pfm_fhuser_ext *ext = h->ext;                       
+  double num = 0.;
+  int got_dot = 0;
+  int got_digit = 0;
+  int exponent = 0;
+  int neg = 0;
+
+  /* Skip leading spaces. */
+  while (match (126 /* space */))
+    ;
+
+  if (match (137 /* * */))
+    {
+      advance ();      /* Probably a dot (.) but doesn't appear to matter. */
+      return SYSMIS;
+    }
+  else if (match (141 /* - */))
+    neg = 1;
+
+  for (;;)
+    {
+      if (ext->cc >= 64 /* 0 */ && ext->cc <= 93 /* T */)
+       {
+         got_digit++;
+
+         /* Make sure that multiplication by 30 will not overflow.  */
+         if (num > DBL_MAX * (1. / 30.))
+           /* The value of the digit doesn't matter, since we have already
+              gotten as many digits as can be represented in a `double'.
+              This doesn't necessarily mean the result will overflow.
+              The exponent may reduce it to within range.
+
+              We just need to record that there was another
+              digit so that we can multiply by 10 later.  */
+           ++exponent;
+         else
+           num = (num * 30.0) + (ext->cc - 64);
+
+         /* Keep track of the number of digits after the decimal point.
+            If we just divided by 30 here, we would lose precision.  */
+         if (got_dot)
+           --exponent;
+       }
+      else if (!got_dot && ext->cc == 127 /* . */)
+       /* Record that we have found the decimal point.  */
+       got_dot = 1;
+      else
+       /* Any other character terminates the number.  */
+       break;
+
+      advance ();
+    }
+
+  if (!got_digit)
+    lose ((h, "Number expected."));
+      
+  if (ext->cc == 130 /* + */ || ext->cc == 141 /* - */)
+    {
+      /* Get the exponent.  */
+      long int exp = 0;
+      int neg_exp = ext->cc == 141 /* - */;
+
+      for (;;)
+       {
+         advance ();
+
+         if (ext->cc < 64 /* 0 */ || ext->cc > 93 /* T */)
+           break;
+
+         if (exp > LONG_MAX / 30)
+           goto overflow;
+         exp = exp * 30 + (ext->cc - 64);
+       }
+
+      /* We don't check whether there were actually any digits, but we
+         probably should. */
+      if (neg_exp)
+       exp = -exp;
+      exponent += exp;
+    }
+  
+  if (!match (142 /* / */))
+    lose ((h, _("Missing numeric terminator.")));
+
+  /* Multiply NUM by 30 to the EXPONENT power, checking for overflow.  */
+
+  if (exponent < 0)
+    num *= pow (30.0, (double) exponent);
+  else if (exponent > 0)
+    {
+      if (num > DBL_MAX * pow (30.0, (double) -exponent))
+       goto overflow;
+      num *= pow (30.0, (double) exponent);
+    }
+
+  if (neg)
+    return -num;
+  else
+    return num;
+
+ overflow:
+  if (neg)
+    return -DBL_MAX / 10.;
+  else
+    return DBL_MAX / 10;
+
+ lossage:
+  return second_lowest_value;
+}
+  
+/* Read an integer and return its value, or NOT_INT on failure. */
+int
+read_int (struct file_handle *h)
+{
+  double f = read_float (h);
+
+  if (f == second_lowest_value)
+    goto lossage;
+  if (floor (f) != f || f >= INT_MAX || f <= INT_MIN)
+    lose ((h, _("Bad integer format.")));
+  return f;
+
+ lossage:
+  return NOT_INT;
+}
+
+/* Reads a string and returns its value in a static buffer, or NULL on
+   failure.  The buffer can be deallocated by calling with a NULL
+   argument. */
+static unsigned char *
+read_string (struct file_handle *h)
+{
+  struct pfm_fhuser_ext *ext = h->ext;
+  static char *buf;
+  int n;
+  
+  if (h == NULL)
+    {
+      free (buf);
+      buf = NULL;
+      return NULL;
+    }
+  else if (buf == NULL)
+    buf = xmalloc (256);
+
+  n = read_int (h);
+  if (n == NOT_INT)
+    return NULL;
+  if (n < 0 || n > 255)
+    lose ((h, _("Bad string length %d."), n));
+  
+  {
+    int i;
+
+    for (i = 0; i < n; i++)
+      {
+       buf[i] = ext->cc;
+       advance ();
+      }
+  }
+  
+  buf[n] = 0;
+  return buf;
+
+ lossage:
+  return NULL;
+}
+\f
+/* Reads the 464-byte file header. */
+int
+read_header (struct file_handle *h)
+{
+  struct pfm_fhuser_ext *ext = h->ext;
+
+  /* For now at least, just ignore the vanity splash strings. */
+  {
+    int i;
+
+    for (i = 0; i < 200; i++)
+      advance ();
+  }
+  
+  {
+    unsigned char src[256];
+    int trans_temp[256];
+    int i;
+
+    for (i = 0; i < 256; i++)
+      {
+       src[i] = (unsigned char) ext->cc;
+       advance ();
+      }
+
+    for (i = 0; i < 256; i++)
+      trans_temp[i] = -1;
+
+    /* 0 is used to mark untranslatable characters, so we have to mark
+       it specially. */
+    trans_temp[src[64]] = 64;
+    for (i = 0; i < 256; i++)
+      if (trans_temp[src[i]] == -1)
+       trans_temp[src[i]] = i;
+    
+    ext->trans = xmalloc (256);
+    for (i = 0; i < 256; i++)
+      ext->trans[i] = trans_temp[i] == -1 ? 0 : trans_temp[i];
+
+    /* Translate the input buffer. */
+    for (i = 0; i < 80; i++)
+      ext->buf[i] = ext->trans[ext->buf[i]];
+    ext->cc = ext->trans[ext->cc];
+  }
+  
+  {
+    unsigned char sig[8] = {92, 89, 92, 92, 89, 88, 91, 93};
+    int i;
+
+    for (i = 0; i < 8; i++)
+      if (!match (sig[i]))
+       lose ((h, "Missing SPSSPORT signature."));
+  }
+
+  return 1;
+
+ lossage:
+  return 0;
+}
+
+/* Reads the version and date info record, as well as product and
+   subproduct identification records if present. */
+int
+read_version_data (struct file_handle *h, struct pfm_read_info *inf)
+{
+  struct pfm_fhuser_ext *ext = h->ext;
+
+  /* Version. */
+  if (!match (74 /* A */))
+    lose ((h, "Unrecognized version code %d.", ext->cc));
+
+  /* Date. */
+  {
+    static const int map[] = {6, 7, 8, 9, 3, 4, 0, 1};
+    char *date = read_string (h);
+    int i;
+    
+    if (!date)
+      return 0;
+    if (strlen (date) != 8)
+      lose ((h, _("Bad date string length %d."), strlen (date)));
+    for (i = 0; i < 8; i++)
+      {
+       if (date[i] < 64 /* 0 */ || date[i] > 73 /* 9 */)
+         lose ((h, _("Bad character in date.")));
+       if (inf)
+         inf->creation_date[map[i]] = date[i] - 64 /* 0 */ + '0';
+      }
+    if (inf)
+      {
+       inf->creation_date[2] = inf->creation_date[5] = ' ';
+       inf->creation_date[10] = 0;
+      }
+  }
+  
+  /* Time. */
+  {
+    static const int map[] = {0, 1, 3, 4, 6, 7};
+    char *time = read_string (h);
+    int i;
+
+    if (!time)
+      return 0;
+    if (strlen (time) != 6)
+      lose ((h, _("Bad time string length %d."), strlen (time)));
+    for (i = 0; i < 6; i++)
+      {
+       if (time[i] < 64 /* 0 */ || time[i] > 73 /* 9 */)
+         lose ((h, _("Bad character in time.")));
+       if (inf)
+         inf->creation_time[map[i]] = time[i] - 64 /* 0 */ + '0';
+      }
+    if (inf)
+      {
+       inf->creation_time[2] = inf->creation_time[5] = ' ';
+       inf->creation_time[8] = 0;
+      }
+  }
+
+  /* Product. */
+  if (match (65 /* 1 */))
+    {
+      char *product;
+      
+      product = read_string (h);
+      if (product == NULL)
+       return 0;
+      if (inf)
+       strncpy (inf->product, product, 61);
+    }
+  else if (inf)
+    inf->product[0] = 0;
+
+  /* Subproduct. */
+  if (match (67 /* 3 */))
+    {
+      char *subproduct;
+
+      subproduct = read_string (h);
+      if (subproduct == NULL)
+       return 0;
+      if (inf)
+       strncpy (inf->subproduct, subproduct, 61);
+    }
+  else if (inf)
+    inf->subproduct[0] = 0;
+  return 1;
+  
+ lossage:
+  return 0;
+}
+
+static int
+convert_format (struct file_handle *h, int fmt[3], struct fmt_spec *v,
+               struct variable *vv)
+{
+  if (fmt[0] < 0
+      || (size_t) fmt[0] >= sizeof translate_fmt / sizeof *translate_fmt)
+    lose ((h, _("%s: Bad format specifier byte %d."), vv->name, fmt[0]));
+
+  v->type = translate_fmt[fmt[0]];
+  v->w = fmt[1];
+  v->d = fmt[2];
+
+  /* FIXME?  Should verify the resulting specifier more thoroughly. */
+
+  if (v->type == -1)
+    lose ((h, _("%s: Bad format specifier byte (%d)."), vv->name, fmt[0]));
+  if ((vv->type == ALPHA) ^ ((formats[v->type].cat & FCAT_STRING) != 0))
+    lose ((h, _("%s variable %s has %s format specifier %s."),
+          vv->type == ALPHA ? _("String") : _("Numeric"),
+          vv->name,
+          formats[v->type].cat & FCAT_STRING ? _("string") : _("numeric"),
+          formats[v->type].name));
+  return 1;
+
+ lossage:
+  return 0;
+}
+
+/* Translation table from SPSS character code to this computer's
+   native character code (which is probably ASCII). */
+static const unsigned char spss2ascii[256] =
+  {
+    "                                                                "
+    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ."
+    "<(+|&[]!$*);^-/|,%_>?`:$@'=\"      ~-   0123456789   -() {}\\     "
+    "                                                                "
+  };
+
+/* Translate string S into ASCII. */
+static void
+asciify (char *s)
+{
+  for (; *s; s++)
+    *s = spss2ascii[(unsigned char) *s];
+}
+
+static int parse_value (struct file_handle *, union value *, struct variable *);
+
+/* Read information on all the variables.  */
+static int
+read_variables (struct file_handle *h)
+{
+  struct pfm_fhuser_ext *ext = h->ext;
+  int i;
+  
+  if (!match (68 /* 4 */))
+    lose ((h, _("Expected variable count record.")));
+  
+  ext->nvars = read_int (h);
+  if (ext->nvars <= 0 || ext->nvars == NOT_INT)
+    lose ((h, _("Invalid number of variables %d."), ext->nvars));
+  ext->vars = xmalloc (sizeof *ext->vars * ext->nvars);
+
+  /* Purpose of this value is unknown.  It is typically 161. */
+  {
+    int x = read_int (h);
+
+    if (x == NOT_INT)
+      goto lossage;
+    if (x != 161)
+      corrupt_msg (h, _("Unexpected flag value %d."), x);
+  }
+
+  ext->dict = new_dictionary (0);
+
+  if (match (70 /* 6 */))
+    {
+      char *name = read_string (h);
+      if (!name)
+       goto lossage;
+
+      strcpy (ext->dict->weight_var, name);
+      asciify (ext->dict->weight_var);
+    }
+  
+  for (i = 0; i < ext->nvars; i++)
+    {
+      int width;
+      unsigned char *name;
+      int fmt[6];
+      struct variable *v;
+      int j;
+
+      if (!match (71 /* 7 */))
+       lose ((h, _("Expected variable record.")));
+
+      width = read_int (h);
+      if (width == NOT_INT)
+       goto lossage;
+      if (width < 0)
+       lose ((h, _("Invalid variable width %d."), width));
+      ext->vars[i] = width;
+      
+      name = read_string (h);
+      if (name == NULL)
+       goto lossage;
+      for (j = 0; j < 6; j++)
+       {
+         fmt[j] = read_int (h);
+         if (fmt[j] == NOT_INT)
+           goto lossage;
+       }
+
+      /* Verify first character of variable name.
+
+        Weirdly enough, there is no # character in the SPSS portable
+        character set, so we can't check for it. */
+      if (strlen (name) > 8)
+       lose ((h, _("position %d: Variable name has %u characters."),
+              i, strlen (name)));
+      if ((name[0] < 74 /* A */ || name[0] > 125 /* Z */)
+         && name[0] != 152 /* @ */)
+       lose ((h, _("position %d: Variable name begins with invalid "
+              "character."), i));
+      if (name[0] >= 100 /* a */ && name[0] <= 125 /* z */)
+       {
+         corrupt_msg (h, _("position %d: Variable name begins with "
+                           "lowercase letter %c."),
+                      i, name[0] - 100 + 'a');
+         name[0] -= 26 /* a - A */;
+       }
+
+      /* Verify remaining characters of variable name. */
+      for (j = 1; j < (int) strlen (name); j++)
+       {
+         int c = name[j];
+
+         if (c >= 100 /* a */ && c <= 125 /* z */)
+           {
+             corrupt_msg (h, _("position %d: Variable name character %d "
+                               "is lowercase letter %c."),
+                          i, j + 1, c - 100 + 'a');
+             name[j] -= 26 /* z - Z */;
+           }
+         else if ((c >= 64 /* 0 */ && c <= 99 /* Z */)
+                  || c == 127 /* . */ || c == 152 /* @ */
+                  || c == 136 /* $ */ || c == 146 /* _ */)
+           name[j] = c;
+         else
+           lose ((h, _("position %d: character `\\%03o' is not "
+                       "valid in a variable name."), i, c));
+       }
+
+      asciify (name);
+      if (width < 0 || width > 255)
+       lose ((h, "Bad width %d for variable %s.", width, name));
+
+      v = create_variable (ext->dict, name, width ? ALPHA : NUMERIC, width);
+      v->get.fv = v->fv;
+      if (v == NULL)
+       lose ((h, _("Duplicate variable name %s."), name));
+      if (!convert_format (h, &fmt[0], &v->print, v))
+       goto lossage;
+      if (!convert_format (h, &fmt[3], &v->write, v))
+       goto lossage;
+
+      /* Range missing values. */
+      if (match (75 /* B */))
+       {
+         v->miss_type = MISSING_RANGE;
+         if (!parse_value (h, &v->missing[0], v)
+             || !parse_value (h, &v->missing[1], v))
+           goto lossage;
+       }
+      else if (match (74 /* A */))
+       {
+         v->miss_type = MISSING_HIGH;
+         if (!parse_value (h, &v->missing[0], v))
+           goto lossage;
+       }
+      else if (match (73 /* 9 */))
+       {
+         v->miss_type = MISSING_LOW;
+         if (!parse_value (h, &v->missing[0], v))
+           goto lossage;
+       }
+
+      /* Single missing values. */
+      while (match (72 /* 8 */))
+       {
+         static const int map_next[MISSING_COUNT] =
+           {
+             MISSING_1, MISSING_2, MISSING_3, -1,
+             MISSING_RANGE_1, MISSING_LOW_1, MISSING_HIGH_1,
+             -1, -1, -1,
+           };
+
+         static const int map_ofs[MISSING_COUNT] = 
+           {
+             -1, 0, 1, 2, -1, -1, -1, 2, 1, 1,
+           };
+
+         v->miss_type = map_next[v->miss_type];
+         if (v->miss_type == -1)
+           lose ((h, _("Bad missing values for %s."), v->name));
+         
+         assert (map_ofs[v->miss_type] != -1);
+         if (!parse_value (h, &v->missing[map_ofs[v->miss_type]], v))
+           goto lossage;
+       }
+
+      if (match (76 /* C */))
+       {
+         char *label = read_string (h);
+         
+         if (label == NULL)
+           goto lossage;
+
+         v->label = xstrdup (label);
+         asciify (v->label);
+       }
+    }
+  ext->case_size = ext->dict->nval;
+
+  if (ext->dict->weight_var[0] != 0
+      && !find_dict_variable (ext->dict, ext->dict->weight_var))
+    lose ((h, _("Weighting variable %s not present in dictionary."),
+          ext->dict->weight_var));
+
+  return 1;
+
+ lossage:
+  return 0;
+}
+
+/* Parse a value for variable VV into value V.  Returns success. */
+static int
+parse_value (struct file_handle *h, union value *v, struct variable *vv)
+{
+  if (vv->type == ALPHA)
+    {
+      char *mv = read_string (h);
+      int j;
+      
+      if (mv == NULL)
+       return 0;
+
+      strncpy (v->s, mv, 8);
+      for (j = 0; j < 8; j++)
+       if (v->s[j])
+         v->s[j] = spss2ascii[v->s[j]];
+       else
+         /* Value labels are always padded with spaces. */
+         v->s[j] = ' ';
+    }
+  else
+    {
+      v->f = read_float (h);
+      if (v->f == second_lowest_value)
+       return 0;
+    }
+
+  return 1;
+}
+
+/* Parse a value label record and return success. */
+static int
+read_value_label (struct file_handle *h)
+{
+  struct pfm_fhuser_ext *ext = h->ext;
+
+  /* Variables. */
+  int nv;
+  struct variable **v;
+
+  /* Labels. */
+  int n_labels;
+
+  int i;
+
+  nv = read_int (h);
+  if (nv == NOT_INT)
+    return 0;
+
+  v = xmalloc (sizeof *v * nv);
+  for (i = 0; i < nv; i++)
+    {
+      char *name = read_string (h);
+      if (name == NULL)
+       goto lossage;
+      asciify (name);
+
+      v[i] = find_dict_variable (ext->dict, name);
+      if (v[i] == NULL)
+       lose ((h, _("Unknown variable %s while parsing value labels."), name));
+
+      if (v[0]->width != v[i]->width)
+       lose ((h, _("Cannot assign value labels to %s and %s, which "
+                   "have different variable types or widths."),
+              v[0]->name, v[i]->name));
+    }
+
+  n_labels = read_int (h);
+  if (n_labels == NOT_INT)
+    goto lossage;
+
+  for (i = 0; i < n_labels; i++)
+    {
+      union value val;
+      char *label;
+      struct value_label *vl;
+
+      int j;
+      
+      if (!parse_value (h, &val, v[0]))
+       goto lossage;
+      
+      label = read_string (h);
+      if (label == NULL)
+       goto lossage;
+      asciify (label);
+
+      /* Create a label. */
+      vl = xmalloc (sizeof *vl);
+      vl->v = val;
+      vl->s = xstrdup (label);
+      vl->ref_count = nv;
+
+      /* Assign the value_label's to each variable. */
+      for (j = 0; j < nv; j++)
+       {
+         struct variable *var = v[j];
+         struct value_label *old;
+
+         /* Create AVL tree if necessary. */
+         if (!var->val_lab)
+           var->val_lab = avl_create (NULL, val_lab_cmp,
+                                      (void *) (var->width));
+
+         old = avl_replace (var->val_lab, vl);
+         if (old == NULL)
+           continue;
+
+         if (var->type == NUMERIC)
+           lose ((h, _("Duplicate label for value %g for variable %s."),
+                  vl->v.f, var->name));
+         else
+           lose ((h, _("Duplicate label for value `%.*s' for variable %s."),
+                  var->width, vl->v.s, var->name));
+
+         free_value_label (old);
+       }
+    }
+  free (v);
+  return 1;
+
+ lossage:
+  free (v);
+  return 0;
+}
+
+/* Reads one case from portable file H into the value array PERM
+   according to the instuctions given in associated dictionary DICT,
+   which must have the get.fv elements appropriately set.  Returns
+   nonzero only if successful. */
+int
+pfm_read_case (struct file_handle *h, union value *perm, struct dictionary *dict)
+{
+  struct pfm_fhuser_ext *ext = h->ext;
+
+  union value *temp, *tp;
+  int i;
+
+  /* Check for end of file. */
+  if (ext->cc == 99 /* Z */)
+    return 0;
+  
+  /* The first concern is to obtain a full case relative to the data
+     file.  (Cases in the data file have no particular relationship to
+     cases in the active file.) */
+  tp = temp = local_alloc (sizeof *tp * ext->case_size);
+  for (tp = temp, i = 0; i < ext->nvars; i++)
+    if (ext->vars[i] == 0)
+      {
+       tp->f = read_float (h);
+       if (tp->f == second_lowest_value)
+         goto unexpected_eof;
+       tp++;
+      }
+    else
+      {
+       char *s = read_string (h);
+       if (s == NULL)
+         goto unexpected_eof;
+       asciify (s);
+         
+       st_bare_pad_copy (tp->s, s, ext->vars[i]);
+       tp += DIV_RND_UP (ext->vars[i], MAX_SHORT_STRING);
+      }
+
+  /* Translate a case in data file format to a case in active file
+     format. */
+  for (i = 0; i < dict->nvar; i++)
+    {
+      struct variable *v = dict->var[i];
+
+      if (v->get.fv == -1)
+       continue;
+      
+      if (v->type == NUMERIC)
+       perm[v->fv].f = temp[v->get.fv].f;
+      else
+       memcpy (&perm[v->fv].s, &temp[v->get.fv], v->width);
+    }
+
+  local_free (temp);
+  return 1;
+
+ unexpected_eof:
+  lose ((h, _("End of file midway through case.")));
+
+ lossage:
+  local_free (temp);
+  return 0;
+}
+
+static struct fh_ext_class pfm_r_class =
+{
+  5,
+  N_("reading as a portable file"),
+  pfm_close,
+};
diff --git a/src/pfm-write.c b/src/pfm-write.c
new file mode 100644 (file)
index 0000000..0683f39
--- /dev/null
@@ -0,0 +1,510 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include "alloc.h"
+#include "avl.h"
+#include "error.h"
+#include "file-handle.h"
+#include "gmp/gmp.h"
+#include "magic.h"
+#include "pfm.h"
+#include "str.h"
+#include "var.h"
+#include "version.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1 */
+#include "debug-print.h"
+
+/* pfm writer file_handle extension. */
+struct pfm_fhuser_ext
+  {
+    FILE *file;                        /* Actual file. */
+
+    int lc;                    /* Number of characters on this line so far. */
+
+    int nvars;                 /* Number of variables. */
+    int *vars;                 /* Variable widths. */
+  };
+
+static struct fh_ext_class pfm_w_class;
+
+static int bufwrite (struct file_handle *h, const void *buf, size_t nbytes);
+static int write_header (struct file_handle *h);
+static int write_version_data (struct file_handle *h);
+static int write_variables (struct file_handle *h, struct dictionary *d);
+static int write_value_labels (struct file_handle *h, struct dictionary *d);
+
+/* Writes the dictionary DICT to portable file HANDLE.  Returns
+   nonzero only if successful. */
+int
+pfm_write_dictionary (struct file_handle *handle, struct dictionary *dict)
+{
+  struct pfm_fhuser_ext *ext;
+  
+  if (handle->class != NULL)
+    {
+      msg (ME, _("Cannot write file %s as portable file: already opened "
+                "for %s."),
+          fh_handle_name (handle), handle->class->name);
+      return 0;
+    }
+
+  msg (VM (1), _("%s: Opening portable-file handle %s for writing."),
+       fh_handle_filename (handle), fh_handle_name (handle));
+  
+  /* Open the physical disk file. */
+  handle->class = &pfm_w_class;
+  handle->ext = ext = xmalloc (sizeof (struct pfm_fhuser_ext));
+  ext->file = fopen (handle->norm_fn, "wb");
+  ext->lc = 0;
+  if (ext->file == NULL)
+    {
+      msg (ME, _("An error occurred while opening \"%s\" for writing "
+          "as a portable file: %s."), handle->fn, strerror (errno));
+      err_cond_fail ();
+      free (ext);
+      return 0;
+    }
+  
+  {
+    int i;
+
+    ext->nvars = dict->nvar;
+    ext->vars = xmalloc (sizeof *ext->vars * dict->nvar);
+    for (i = 0; i < dict->nvar; i++)
+      ext->vars[i] = dict->var[i]->width;
+  }
+
+  /* Write the file header. */
+  if (!write_header (handle))
+    goto lossage;
+
+  /* Write version data. */
+  if (!write_version_data (handle))
+    goto lossage;
+
+  /* Write variables. */
+  if (!write_variables (handle, dict))
+    goto lossage;
+
+  /* Write value labels. */
+  if (!write_value_labels (handle, dict))
+    goto lossage;
+
+  /* Write beginning of data marker. */
+  if (!bufwrite (handle, "F", 1))
+    goto lossage;
+
+  msg (VM (2), _("Wrote portable-file header successfully."));
+
+  return 1;
+
+lossage:
+  msg (VM (1), _("Error writing portable-file header."));
+  fclose (ext->file);
+  free (ext->vars);
+  handle->class = NULL;
+  handle->ext = NULL;
+  return 0;
+}
+\f  
+/* Write NBYTES starting at BUF to the portable file represented by
+   H.  Break lines properly every 80 characters.  */
+static int
+bufwrite (struct file_handle *h, const void *buf, size_t nbytes)
+{
+  struct pfm_fhuser_ext *ext = h->ext;
+
+  assert (buf != NULL);
+  while (nbytes + ext->lc >= 80)
+    {
+      size_t n = 80 - ext->lc;
+      
+      if (n && 1 != fwrite (buf, n, 1, ext->file))
+       goto lossage;
+      
+      /* PORTME: line ends. */
+      if (1 != fwrite ("\r\n", 2, 1, ext->file))
+       goto lossage;
+
+      nbytes -= n;
+      *((char **) &buf) += n;
+      ext->lc = 0;
+    }
+
+  if (nbytes && 1 != fwrite (buf, nbytes, 1, ext->file))
+    goto lossage;
+  ext->lc += nbytes;
+  
+  return 1;
+
+ lossage:
+  abort ();
+  msg (ME, _("%s: Writing portable file: %s."), h->fn, strerror (errno));
+  return 0;
+}
+
+/* Write D to the portable file as a floating-point field, and return
+   success. */
+static int
+write_float (struct file_handle *h, double d)
+{
+  int neg = 0;
+  char *mantissa;
+  int mantissa_len;
+  mp_exp_t exponent;
+  char *buf, *cp;
+  int success;
+
+  if (d < 0.)
+    {
+      d = -d;
+      neg = 1;
+    }
+  
+  if (d == fabs (SYSMIS) || d == HUGE_VAL)
+    return bufwrite (h, "*.", 2);
+  
+  /* Use GNU libgmp2 to convert D into base-30. */
+  {
+    mpf_t f;
+    
+    mpf_init_set_d (f, d);
+    mantissa = mpf_get_str (NULL, &exponent, 30, 0, f);
+    mpf_clear (f);
+
+    for (cp = mantissa; *cp; cp++)
+      *cp = toupper (*cp);
+  }
+  
+  /* Choose standard or scientific notation. */
+  mantissa_len = (int) strlen (mantissa);
+  cp = buf = local_alloc (mantissa_len + 32);
+  if (neg)
+    *cp++ = '-';
+  if (mantissa_len == 0)
+    *cp++ = '0';
+  else if (exponent < -4 || exponent > (mp_exp_t) mantissa_len)
+    {
+      /* Scientific notation. */
+      *cp++ = mantissa[0];
+      *cp++ = '.';
+      cp = stpcpy (cp, &mantissa[1]);
+      cp = spprintf (cp, "%+ld", (long) (exponent - 1));
+    }
+  else if (exponent <= 0)
+    {
+      /* Standard notation, D <= 1. */
+      *cp++ = '.';
+      memset (cp, '0', -exponent);
+      cp += -exponent;
+      cp = stpcpy (cp, mantissa);
+    }
+  else 
+    {
+      /* Standard notation, D > 1. */
+      memcpy (cp, mantissa, exponent);
+      cp += exponent;
+      *cp++ = '.';
+      cp = stpcpy (cp, &mantissa[exponent]);
+    }
+  *cp++ = '/';
+  
+  success = bufwrite (h, buf, cp - buf);
+  local_free (buf);
+  free (mantissa);
+  return success;
+}
+
+/* Write N to the portable file as an integer field, and return success. */
+static int
+write_int (struct file_handle *h, int n)
+{
+  char buf[64];
+  char *bp = &buf[64];
+  int neg = 0;
+
+  *--bp = '/';
+  
+  if (n < 0)
+    {
+      n = -n;
+      neg = 1;
+    }
+  
+  do
+    {
+      int r = n % 30;
+
+      /* PORTME: character codes. */
+      if (r < 10)
+       *--bp = r + '0';
+      else
+       *--bp = r - 10 + 'A';
+
+      n /= 30;
+    }
+  while (n > 0);
+
+  if (neg)
+    *--bp = '-';
+
+  return bufwrite (h, bp, &buf[64] - bp);
+}
+
+/* Write S to the portable file as a string field. */
+static int
+write_string (struct file_handle *h, const char *s)
+{
+  size_t n = strlen (s);
+  return write_int (h, (int) n) && bufwrite (h, s, n);
+}
+\f
+/* Write file header. */
+static int
+write_header (struct file_handle *h)
+{
+  /* PORTME. */
+  {
+    int i;
+
+    for (i = 0; i < 5; i++)
+      if (!bufwrite (h, "ASCII SPSS PORT FILE                    ", 40))
+       return 0;
+  }
+  
+  {
+    /* PORTME: Translation table from SPSS character code to this
+       computer's native character code (which is probably ASCII). */
+    static const unsigned char spss2ascii[256] =
+      {
+       "0000000000000000000000000000000000000000000000000000000000000000"
+       "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ."
+       "<(+|&[]!$*);^-/|,%_>?`:$@'=\"000000~-0000123456789000-()0{}\\00000"
+       "0000000000000000000000000000000000000000000000000000000000000000"
+      };
+
+    if (!bufwrite (h, spss2ascii, 256))
+      return 0;
+  }
+
+  if (!bufwrite (h, "SPSSPORT", 8))
+    return 0;
+
+  return 1;
+}
+
+/* Writes version, date, and identification records. */
+static int
+write_version_data (struct file_handle *h)
+{
+  if (!bufwrite (h, "A", 1))
+    return 0;
+  
+  {
+    char date_str[9];
+    char time_str[7];
+    time_t t;
+    struct tm tm;
+    struct tm *tmp;
+
+    if ((time_t) -1 == time (&t))
+      {
+       tm.tm_sec = tm.tm_min = tm.tm_hour = tm.tm_mon = tm.tm_year = 0;
+       tm.tm_mday = 1;
+       tmp = &tm;
+      }
+    else 
+      tmp = localtime (&t);
+    
+    sprintf (date_str, "%04d%02d%02d",
+            tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday);
+    sprintf (time_str, "%02d%02d%02d", tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
+    if (!write_string (h, date_str) || !write_string (h, time_str))
+      return 0;
+  }
+
+  /* Product identification. */
+  if (!bufwrite (h, "1", 1) || !write_string (h, version))
+    return 0;
+
+  /* Subproduct identification. */
+  if (!bufwrite (h, "3", 1) || !write_string (h, host_system))
+    return 0;
+
+  return 1;
+}
+
+/* Write format F to file H, and return success. */
+static int
+write_format (struct file_handle *h, struct fmt_spec *f)
+{
+  return (write_int (h, formats[f->type].spss)
+         && write_int (h, f->w)
+         && write_int (h, f->d));
+}
+
+/* Write value V for variable VV to file H, and return success. */
+static int
+write_value (struct file_handle *h, union value *v, struct variable *vv)
+{
+  if (vv->type == NUMERIC)
+    return write_float (h, v->f);
+  else
+    return write_int (h, vv->width) && bufwrite (h, v->s, vv->width);
+}
+
+/* Write variable records, and return success. */
+static int
+write_variables (struct file_handle *h, struct dictionary *dict)
+{
+  int i;
+  
+  if (!bufwrite (h, "4", 1) || !write_int (h, dict->nvar)
+      || !write_int (h, 161))
+    return 0;
+
+  for (i = 0; i < dict->nvar; i++)
+    {
+      static const char *miss_types[MISSING_COUNT] =
+       {
+         "", "8", "88", "888", "B ", "9", "A", "B 8", "98", "A8",
+       };
+
+      const char *m;
+      int j;
+
+      struct variable *v = dict->var[i];
+      
+      if (!bufwrite (h, "7", 1) || !write_int (h, v->width)
+         || !write_string (h, v->name)
+         || !write_format (h, &v->print) || !write_format (h, &v->write))
+       return 0;
+
+      for (m = miss_types[v->miss_type], j = 0; j < (int) strlen (m); j++)
+       if ((m[j] != ' ' && !bufwrite (h, &m[j], 1))
+           || !write_value (h, &v->missing[j], v))
+         return 0;
+
+      if (v->label && (!bufwrite (h, "C", 1) || !write_string (h, v->label)))
+       return 0;
+    }
+
+  return 1;
+}
+
+/* Write value labels to disk.  FIXME: Inefficient. */
+static int
+write_value_labels (struct file_handle *h, struct dictionary *dict)
+{
+  int i;
+
+  for (i = 0; i < dict->nvar; i++)
+    {
+      avl_traverser iter;
+      struct variable *v = dict->var[i];
+      struct value_label *vl;
+
+      if (v->val_lab == NULL)
+       continue;
+
+      if (!bufwrite (h, "D", 1)
+         || !write_int (h, 1)
+         || !write_string (h, v->name)
+         || !write_int (h, avl_count (v->val_lab)))
+       return 0;
+
+      avl_traverser_init (iter);
+      while (NULL != (vl = avl_traverse (v->val_lab, &iter)))
+       if (!write_value (h, &vl->v, v)
+           || !write_string (h, vl->s))
+         return 0;
+    }
+
+  return 1;
+}
+
+/* Writes case ELEM to the portable file represented by H.  Returns
+   success. */
+int 
+pfm_write_case (struct file_handle *h, const union value *elem)
+{
+  struct pfm_fhuser_ext *ext = h->ext;
+  
+  int i;
+  
+  for (i = 0; i < ext->nvars; i++)
+    {
+      const int width = ext->vars[i];
+      
+      if (width == 0)
+       {
+         if (!write_float (h, elem[i].f))
+           return 0;
+       }
+      else
+       {
+         if (!write_int (h, width) || !bufwrite (h, elem->c, width))
+           return 0;
+       }
+    }
+
+  return 1;
+}
+
+/* Closes a portable file after we're done with it. */
+static void
+pfm_close (struct file_handle *h)
+{
+  struct pfm_fhuser_ext *ext = h->ext;
+  
+  {
+    char buf[80];
+    
+    int n = 80 - ext->lc;
+    if (n == 0)
+      n = 80;
+
+    memset (buf, 'Z', n);
+    bufwrite (h, buf, n);
+  }
+
+  if (EOF == fclose (ext->file))
+    msg (ME, _("%s: Closing portable file: %s."), h->fn, strerror (errno));
+
+  free (ext->vars);
+  free (ext);
+}
+
+static struct fh_ext_class pfm_w_class =
+{
+  6,
+  N_("writing as a portable file"),
+  pfm_close,
+};
diff --git a/src/pfm.h b/src/pfm.h
new file mode 100644 (file)
index 0000000..ebf4401
--- /dev/null
+++ b/src/pfm.h
@@ -0,0 +1,56 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !pfm_h
+#define pfm_h 1
+
+/* Portable file manager (pfm).
+
+   This module is in charge of reading and writing portable files.
+   pfm is an fhuser, so see file-handle.h for the fhuser interface.  */
+
+/* Portable file types. */
+enum
+  {
+    PFM_COMM,
+    PFM_TAPE
+  };
+
+/* Information produced by pfm_read_dictionary() that doesn't fit into
+   a dictionary struct. */
+struct pfm_read_info
+  {
+    char creation_date[11];    /* `dd mm yyyy' plus a null. */
+    char creation_time[9];     /* `hh:mm:ss' plus a null. */
+    char product[61];          /* Product name plus a null. */
+    char subproduct[61];       /* Subproduct name plus a null. */
+  };
+
+struct dictionary;
+struct file_handle;
+union value;
+
+struct dictionary *pfm_read_dictionary (struct file_handle *,
+                                       struct pfm_read_info *);
+int pfm_read_case (struct file_handle *, union value *, struct dictionary *);
+
+int pfm_write_dictionary (struct file_handle *, struct dictionary *);
+int pfm_write_case (struct file_handle *, const union value *elem);
+
+#endif /* !pfm_h */
diff --git a/src/pool.c b/src/pool.c
new file mode 100644 (file)
index 0000000..cce54af
--- /dev/null
@@ -0,0 +1,734 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <assert.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "pool.h"
+
+/* Fast, low-overhead memory block suballocator. */
+struct pool
+  {
+    struct pool *parent;       /* Pool of which this pool is a subpool. */
+    struct pool_block *blocks; /* Blocks owned by the pool. */
+    struct pool_gizmo *gizmos; /* Other stuff owned by the pool. */
+  };
+
+/* Pool block. */
+struct pool_block 
+  {
+    struct pool_block *prev;
+    struct pool_block *next;
+    size_t ofs;
+  };
+
+/* Gizmo types. */
+enum
+  {
+    POOL_GIZMO_MALLOC,
+    POOL_GIZMO_FILE,
+    POOL_GIZMO_SUBPOOL,
+    POOL_GIZMO_REGISTERED,
+  };
+
+/* Pool routines can maintain objects (`gizmos') as well as doing
+   suballocation.  
+   This structure is used to keep track of them. */
+struct pool_gizmo
+  {
+    struct pool_gizmo *prev;
+    struct pool_gizmo *next;
+
+    long serial;               /* Serial number. */
+    int type;                  /* Type of this gizmo. */
+
+    /* Type-dependent info. */
+    union
+      {
+       FILE *file;             /* POOL_GIZMO_FILE. */
+       struct pool *subpool;   /* POOL_GIZMO_SUBPOOL. */
+
+       /* POOL_GIZMO_REGISTERED. */
+       struct
+         {
+           void (*free) (void *p);
+           void *p;
+         }
+       registered;
+      }
+    p;
+  };
+
+/* Rounds X up to the next multiple of Y. */
+#ifndef ROUND_UP
+#define ROUND_UP(X, Y)                                 \
+       (((X) + ((Y) - 1)) / (Y) * (Y))
+#endif
+
+/* Types that provide typically useful alignment sizes. */
+union align
+  {
+    void *op;
+    void (*fp) (void);
+    long l;
+    double d;
+  };
+
+/* This should be the alignment size used by malloc().  The size of
+   the union above is correct, if not optimal, in all known cases. */
+#if defined (i386) || defined (__i386__)
+#define ALIGN_SIZE 4           /* Save some extra memory. */
+#else
+#define ALIGN_SIZE sizeof (union align)
+#endif
+
+/* DISCRETE_BLOCKS may be declared as nonzero to prevent suballocation
+   of blocks.  This is useful under memory debuggers like Checker
+   because it allows the source location of bugs to be more accurately
+   pinpointed.
+
+   On the other hand, if we're testing the library, then we want to
+   test the library's real functionality, not its crippled, slow,
+   simplified functionality. */
+#if __CHECKER__  && !SELF_TEST
+#define DISCRETE_BLOCKS 1
+#endif
+
+/* Enable debug code if appropriate. */
+#undef DEBUGGING
+#if SELF_TEST
+#define DEBUGGING 1
+#endif
+
+/* Size of each block allocated in the pool, in bytes.
+   Should be at least 1k. */
+#ifndef BLOCK_SIZE
+#define BLOCK_SIZE 1024
+#endif
+
+/* Maximum size of a suballocated block.  Larger blocks are allocated
+   directly with malloc() to avoid memory wastage at the end of a
+   suballocation block. */
+#ifndef MAX_SUBALLOC
+#define MAX_SUBALLOC 64
+#endif
+
+/* Sizes of some structures with alignment padding included. */
+#define POOL_BLOCK_SIZE ROUND_UP (sizeof (struct pool_block), ALIGN_SIZE)
+#define POOL_GIZMO_SIZE ROUND_UP (sizeof (struct pool_gizmo), ALIGN_SIZE)
+#define POOL_SIZE ROUND_UP (sizeof (struct pool), ALIGN_SIZE)
+
+/* Serial number used to keep track of gizmos for mark/release. */
+static long serial = 0;
+
+/* Prototypes. */
+static void add_gizmo (struct pool *, struct pool_gizmo *);
+static void free_gizmo (struct pool_gizmo *);
+static void delete_gizmo (struct pool *, struct pool_gizmo *);
+
+#if !PSPP
+static void *xmalloc (size_t);
+static void *xrealloc (void *, size_t);
+#endif
+\f
+/* General routines. */
+
+/* Creates and returns a new memory pool, which allows malloc()'d
+   blocks to be suballocated in a time- and space-efficient manner.
+   The entire contents of the memory pool are freed at once.
+
+   In addition, other objects can be associated with a memory pool.
+   These are released when the pool is destroyed. */
+struct pool *
+pool_create (void)
+{
+  struct pool_block *block;
+  struct pool *pool;
+
+  block = xmalloc (BLOCK_SIZE);
+  block->prev = block->next = block;
+  block->ofs = POOL_BLOCK_SIZE + POOL_SIZE;
+  
+  pool = (struct pool *) (((char *) block) + POOL_BLOCK_SIZE);
+  pool->parent = NULL;
+  pool->blocks = block;
+  pool->gizmos = NULL;
+
+  return pool;
+}
+
+/* Destroy the specified pool, including all subpools. */
+void
+pool_destroy (struct pool *pool)
+{
+  if (pool == NULL)
+    return;
+
+  if (pool->parent) 
+    delete_gizmo (pool,
+                 (void *) (((char *) pool) + POOL_SIZE + POOL_BLOCK_SIZE));
+
+  {
+    struct pool_gizmo *cur, *next;
+
+    for (cur = pool->gizmos; cur; cur = next)
+      {
+       next = cur->next;
+       free_gizmo (cur);
+      }
+  }
+  
+  {
+    struct pool_block *cur, *next;
+
+    pool->blocks->prev->next = NULL;
+    for (cur = pool->blocks; cur; cur = next)
+      {
+       next = cur->next;
+       free (cur);
+      }
+  }
+}
+\f
+/* Suballocation routines. */
+
+/* Allocates a memory region AMT bytes in size from POOL and returns a
+   pointer to the region's start. */
+void *
+pool_alloc (struct pool *pool, size_t amt)
+{
+  assert (pool != NULL);
+  
+#if !DISCRETE_BLOCKS /* Help identify source of bugs for Checker users. */
+  if (amt <= MAX_SUBALLOC)
+    {
+      struct pool_block *b = pool->blocks;
+      b->ofs = ROUND_UP (b->ofs, ALIGN_SIZE);
+      if (b->ofs + amt <= BLOCK_SIZE)
+       {
+         void *const p = ((char *) b) + b->ofs;
+         b->ofs += amt;
+         return p;
+       }
+
+      b = xmalloc (BLOCK_SIZE);
+      b->next = pool->blocks;
+      b->prev = pool->blocks->prev;
+      b->ofs = POOL_BLOCK_SIZE + amt;
+
+      pool->blocks->prev->next = b;
+      pool->blocks = pool->blocks->prev = b;
+
+      return ((char *) b) + POOL_BLOCK_SIZE;
+    }
+  else
+#endif /* !DISCRETE_BLOCKS */
+    return pool_malloc (pool, amt);
+}
+
+/* Duplicates STRING within POOL and returns a pointer to the
+   duplicate. */
+char *
+pool_strdup (struct pool *pool, const char *string)
+{
+  size_t amt;
+  void *p;
+
+  assert (pool && string);
+  amt = strlen (string) + 1;
+
+  /* Note that strings need not be aligned on any boundary. */
+  {
+#if !DISCRETE_BLOCKS
+    struct pool_block *const b = pool->blocks;
+
+    if (b->ofs + amt <= BLOCK_SIZE)
+      {
+       p = ((char *) b) + b->ofs;
+       b->ofs += amt;
+      }
+    else
+#endif
+      p = pool_alloc (pool, amt);
+  }
+
+  memcpy (p, string, amt);
+  return p;
+}
+\f
+/* Standard allocation routines. */
+
+/* Allocates AMT bytes using malloc(), to be managed by POOL, and
+   returns a pointer to the beginning of the block.
+   If POOL is a null pointer, then allocates a normal memory block
+   with malloc().  */
+void *
+pool_malloc (struct pool *pool, size_t amt)
+{
+  if (pool != NULL)
+    {
+      if (amt != 0)
+       {
+         struct pool_gizmo *g = xmalloc (amt + POOL_GIZMO_SIZE);
+         g->type = POOL_GIZMO_MALLOC;
+         add_gizmo (pool, g);
+
+         return ((char *) g) + POOL_GIZMO_SIZE;
+       }
+      else
+       return NULL;
+    }
+  else
+    return xmalloc (amt);
+}
+
+/* Changes the allocation size of the specified memory block P managed
+   by POOL to AMT bytes and returns a pointer to the beginning of the
+   block.
+   If POOL is a null pointer, then the block is reallocated in the
+   usual way with realloc(). */
+void *
+pool_realloc (struct pool *pool, void *p, size_t amt)
+{
+  if (pool != NULL)
+    {
+      if (p != NULL)
+       {
+         if (amt != 0)
+           {
+             struct pool_gizmo *g;
+
+             g = xrealloc (((char *) p) - POOL_GIZMO_SIZE,
+                           amt + POOL_GIZMO_SIZE);
+             if (g->next)
+               g->next->prev = g;
+             if (g->prev)
+               g->prev->next = g;
+             else
+               pool->gizmos = g;
+
+             return ((char *) g) + POOL_GIZMO_SIZE;
+           }
+         else
+           {
+             pool_free (pool, p);
+             return NULL;
+           }
+       }
+      else
+       return pool_malloc (pool, amt);
+    }
+  else
+    return xrealloc (p, amt);
+}
+
+/* Frees block P managed by POOL.
+   If POOL is a null pointer, then the block is freed as usual with
+   free(). */
+void
+pool_free (struct pool *pool, void *p)
+{
+  if (pool != NULL && p != NULL)
+    {
+      struct pool_gizmo *g = (void *) (((char *) p) - POOL_GIZMO_SIZE);
+      delete_gizmo (pool, g);
+      free (g);
+    }
+  else
+    free (p);
+}
+\f
+/* Gizmo allocations. */
+
+/* Creates and returns a pool as a subpool of POOL.
+   The subpool will be destroyed automatically when POOL is destroyed.
+   It may also be destroyed explicitly in advance. */
+struct pool *
+pool_create_subpool (struct pool *pool)
+{
+  struct pool *subpool;
+  struct pool_gizmo *g;
+
+  assert (pool != NULL);
+  subpool = pool_create ();
+  subpool->parent = pool;
+
+  g = (void *) (((char *) subpool) + subpool->blocks->ofs);
+  subpool->blocks->ofs += POOL_GIZMO_SIZE;
+  
+  g->type = POOL_GIZMO_SUBPOOL;
+  g->p.subpool = subpool;
+
+  add_gizmo (pool, g);
+
+  return subpool;
+}
+
+/* Opens file FILENAME with mode MODE and returns a handle to it
+   if successful or a null pointer if not.
+   The file will be closed automatically when POOL is destroyed, or it
+   may be closed explicitly in advance using pool_fclose. */
+FILE *
+pool_fopen (struct pool *pool, const char *filename, const char *mode)
+{
+  FILE *f;
+
+  assert (pool && filename && mode);
+  f = fopen (filename, mode);
+  if (f == NULL)
+    return NULL;
+
+  {
+    struct pool_gizmo *g = pool_alloc (pool, sizeof *g);
+    g->type = POOL_GIZMO_FILE;
+    g->p.file = f;
+    add_gizmo (pool, g);
+  }
+
+  return f;
+}
+
+/* Closes file FILE managed by POOL. */
+int
+pool_fclose (struct pool *pool, FILE *file)
+{
+  assert (pool && file);
+  if (fclose (file) == EOF)
+    return EOF;
+  
+  {
+    struct pool_gizmo *g;
+
+    for (g = pool->gizmos; g; g = g->next)
+      if (g->type == POOL_GIZMO_FILE && g->p.file == file)
+       {
+         delete_gizmo (pool, g);
+         break;
+       }
+  }
+  
+  return 0;
+}
+\f
+/* Registers FREE to be called with argument P.
+   P should be unique among those registered in POOL so that it can be
+   uniquely identified by pool_unregister().
+   If not unregistered, FREE will be called with argument P when POOL
+   is destroyed. */
+void
+pool_register (struct pool *pool, void (*free) (void *), void *p)
+{
+  assert (pool && free && p);
+
+  {
+    struct pool_gizmo *g = pool_alloc (pool, sizeof *g);
+    g->type = POOL_GIZMO_REGISTERED;
+    g->p.registered.free = free;
+    g->p.registered.p = p;
+    add_gizmo (pool, g);
+  }
+}
+
+/* Unregisters previously registered P from POOL.
+   Returns nonzero only if P was found to be registered in POOL. */
+int
+pool_unregister (struct pool *pool, void *p)
+{
+  assert (pool && p);
+  
+  {
+    struct pool_gizmo *g;
+
+    for (g = pool->gizmos; g; g = g->next)
+      if (g->type == POOL_GIZMO_REGISTERED && g->p.registered.p == p)
+       {
+         delete_gizmo (pool, g);
+         return 1;
+       }
+  }
+  
+  return 0;
+}
+\f
+/* Partial freeing. */
+
+/* Notes the state of POOL into MARK so that it may be restored
+   by a call to pool_release(). */
+void
+pool_mark (struct pool *pool, struct pool_mark *mark)
+{
+  assert (pool && mark);
+
+  mark->block = pool->blocks;
+  mark->ofs = pool->blocks->ofs;
+
+  mark->serial = serial;
+}
+
+/* Restores to POOL the state recorded in MARK. */
+void
+pool_release (struct pool *pool, const struct pool_mark *mark)
+{
+  assert (pool && mark);
+  
+  {
+    struct pool_gizmo *cur, *next;
+
+    for (cur = pool->gizmos; cur && cur->serial >= mark->serial; cur = next)
+      {
+       next = cur->next;
+       free_gizmo (cur);
+      }
+
+    if (cur != NULL)
+      {
+       cur->prev = NULL;
+       pool->gizmos = cur;
+      }
+    else
+      pool->gizmos = NULL;
+  }
+  
+  {
+    struct pool_block *cur, *next, *last;
+
+    last = pool->blocks->prev;
+    for (cur = pool->blocks; cur != mark->block; cur = next)
+      {
+       next = cur->next;
+       assert (next != cur);
+
+       free (cur);
+      }
+
+    cur->prev = last;
+    last->next = pool->blocks = cur;
+  
+    cur->ofs = mark->ofs;
+  }
+}
+\f
+/* Private functions. */
+
+/* Adds GIZMO at the beginning of POOL's gizmo list. */
+static void
+add_gizmo (struct pool *pool, struct pool_gizmo *gizmo)
+{
+  assert (pool && gizmo);
+  
+  gizmo->next = pool->gizmos;
+  gizmo->prev = NULL;
+  if (pool->gizmos)
+    pool->gizmos->prev = gizmo;
+  pool->gizmos = gizmo;
+
+  gizmo->serial = serial++;
+}
+/* Removes GIZMO from POOL's gizmo list. */
+static void
+delete_gizmo (struct pool *pool, struct pool_gizmo *gizmo)
+{
+  assert (pool && gizmo);
+  
+  if (gizmo->prev)
+    gizmo->prev->next = gizmo->next;
+  else
+    pool->gizmos = gizmo->next;
+  if (gizmo->next)
+    gizmo->next->prev = gizmo->prev;
+}
+
+/* Frees any of GIZMO's internal state.
+   GIZMO's data must not be referenced after calling this function. */
+static void
+free_gizmo (struct pool_gizmo *gizmo)
+{
+  assert (gizmo != NULL);
+  
+  switch (gizmo->type)
+    {
+    case POOL_GIZMO_MALLOC:
+      free (gizmo);
+      break;
+    case POOL_GIZMO_FILE:
+      fclose (gizmo->p.file);  /* Ignore errors. */
+      break;
+    case POOL_GIZMO_SUBPOOL:
+      gizmo->p.subpool->parent = NULL;
+      pool_destroy (gizmo->p.subpool);
+      break;
+    case POOL_GIZMO_REGISTERED:
+      gizmo->p.registered.free (gizmo->p.registered.p);
+      break;
+    default:
+      assert (0);
+    }
+}
+\f
+/* Memory allocation. */
+
+#if !PSPP
+/* Allocates SIZE bytes of space using malloc().  Aborts if out of
+   memory. */
+static void *
+xmalloc (size_t size)
+{
+  void *vp;
+  if (size == 0)
+    return NULL;
+  vp = malloc (size);
+  assert (vp != NULL);
+  if (vp == NULL)
+    abort ();
+  return vp;
+}
+
+/* Reallocates P to be SIZE bytes long using realloc().  Aborts if out
+   of memory. */
+static void *
+xrealloc (void *p, size_t size)
+{
+  if (p == NULL)
+    return xmalloc (size);
+  if (size == 0)
+    {
+      free (p);
+      return NULL;
+    }
+  p = realloc (p, size);
+  if (p == NULL)
+    abort ();
+  return p;
+}
+#endif /* !PSPP */
+\f
+/* Self-test routine. */
+
+#if SELF_TEST
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#define N_ITERATIONS 8192
+#define N_FILES 16
+
+/* Self-test routine.
+   This is not exhaustive, but it can be useful. */
+int
+main (int argc, char **argv)
+{
+  int seed;
+  
+  if (argc == 2)
+    seed = atoi (argv[1]);
+  else
+    seed = time (0) * 257 % 32768;
+
+  for (;;)
+    {
+      struct pool *pool;
+      struct pool_mark m1, m2;
+      FILE *files[N_FILES];
+      int cur_file;
+      long i;
+
+      printf ("Random number seed: %d\n", seed);
+      srand (seed++);
+
+      printf ("Creating pool...\n");
+      pool = pool_create ();
+
+      printf ("Marking pool state...\n");
+      pool_mark (pool, &m1);
+
+      printf ("    Populating pool with random-sized small objects...\n");
+      for (i = 0; i < N_ITERATIONS; i++)
+       {
+         size_t size = rand () % MAX_SUBALLOC;
+         void *p = pool_alloc (pool, size);
+         memset (p, 0, size);
+       }
+
+      printf ("    Marking pool state...\n");
+      pool_mark (pool, &m2);
+      
+      printf ("       Populating pool with random-sized small "
+             "and large objects...\n");
+      for (i = 0; i < N_ITERATIONS; i++)
+       {
+         size_t size = rand () % (2 * MAX_SUBALLOC);
+         void *p = pool_alloc (pool, size);
+         memset (p, 0, size);
+       }
+
+      printf ("    Releasing pool state...\n");
+      pool_release (pool, &m2);
+
+      printf ("    Populating pool with random objects and gizmos...\n");
+      for (i = 0; i < N_FILES; i++)
+       files[i] = NULL;
+      cur_file = 0;
+      for (i = 0; i < N_ITERATIONS; i++)
+       {
+         int type = rand () % 32;
+
+         if (type == 0)
+           {
+             if (files[cur_file] != NULL
+                 && EOF == pool_fclose (pool, files[cur_file]))
+               printf ("error on fclose: %s\n", strerror (errno));
+
+             files[cur_file] = pool_fopen (pool, "/dev/null", "r");
+
+             if (++cur_file >= N_FILES)
+               cur_file = 0;
+           }
+         else if (type == 1)
+           pool_create_subpool (pool);
+         else 
+           {
+             size_t size = rand () % (2 * MAX_SUBALLOC);
+             void *p = pool_alloc (pool, size);
+             memset (p, 0, size);
+           }
+       }
+      
+      printf ("Releasing pool state...\n");
+      pool_release (pool, &m1);
+
+      printf ("Destroying pool...\n");
+      pool_destroy (pool);
+
+      putchar ('\n');
+    }
+}
+
+#endif /* SELF_TEST */
+
+/* 
+   Local variables:
+   compile-command: "gcc -DSELF_TEST=1 -W -Wall -I. -o pool_test pool.c"
+   End:
+*/
diff --git a/src/pool.h b/src/pool.h
new file mode 100644 (file)
index 0000000..117e0c5
--- /dev/null
@@ -0,0 +1,67 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !pool_h
+#define pool_h 1
+
+#include <stdio.h>
+
+/* Records the state of a pool for later restoration. */
+struct pool_mark 
+  {
+    /* Current block and offset into it. */
+    struct pool_block *block;
+    size_t ofs;
+
+    /* Current serial number to allow freeing of gizmos. */
+    long serial;
+  };
+
+/* General routines. */
+struct pool *pool_create (void);
+void pool_destroy (struct pool *);
+
+/* Suballocation routines. */
+void *pool_alloc (struct pool *, size_t);
+char *pool_strdup (struct pool *, const char *);
+char *pool_strcat (struct pool *, const char *, ...);
+
+/* Standard allocation routines. */
+void *pool_malloc (struct pool *, size_t);
+void *pool_realloc (struct pool *, void *, size_t);
+void pool_free (struct pool *, void *);
+
+/* Gizmo allocations. */
+struct pool *pool_create_subpool (struct pool *);
+FILE *pool_fopen (struct pool *, const char *, const char *);
+int pool_fclose (struct pool *, FILE *);
+
+/* Custom allocations. */
+void pool_register (struct pool *, void (*free) (void *), void *p);
+int pool_unregister (struct pool *, void *);
+
+/* Partial freeing. */
+void pool_mark (struct pool *, struct pool_mark *);
+void pool_release (struct pool *, const struct pool_mark *);
+
+#if GLOBAL_DEBUGGING
+void pool_dump (const struct pool *, const char *title);
+#endif
+
+#endif /* pool.h */
diff --git a/src/postscript.c b/src/postscript.c
new file mode 100644 (file)
index 0000000..8e6fa48
--- /dev/null
@@ -0,0 +1,2966 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+/*this #if encloses the remainder of the file. */
+#if !NO_POSTSCRIPT
+
+#include <ctype.h>
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if TIME_WITH_SYS_TIME
+#include <sys/time.h>
+#include <time.h>
+#else
+#if HAVE_SYS_TIME_H
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#endif
+
+#include "alloc.h"
+#include "bitvector.h"
+#include "error.h"
+#include "filename.h"
+#include "font.h"
+#include "getline.h"
+#include "hash.h"
+#include "main.h"
+#include "misc.h"
+#include "misc.h"
+#include "output.h"
+#include "version.h"
+
+/* FIXMEs:
+
+   optimize-text-size not implemented.
+   
+   Line buffering is the only possibility; page buffering should also
+   be possible.
+
+   max-fonts-simult
+   
+   Should add a field to give a file that has a list of fonts
+   typically used.
+   
+   Should add an option that tells the driver it can emit %%Include:'s.
+   
+   Should have auto-encode=true stream-edit or whatever to allow
+   addition to list of encodings.
+   
+   Should align fonts of different sizes along their baselines (see
+   text()).  */
+
+/* PostScript driver options: (defaults listed first)
+
+   output-file="pspp.ps"
+   color=yes|no
+   data=clean7bit|clean8bit|binary
+   line-ends=lf|crlf
+
+   paper-size=letter (see "papersize" file)
+   orientation=portrait|landscape
+   headers=on|off
+   
+   left-margin=0.5in
+   right-margin=0.5in
+   top-margin=0.5in
+   bottom-margin=0.5in
+
+   font-dir=devps
+   prologue-file=ps-prologue
+   device-file=DESC
+   encoding-file=ps-encodings
+   auto-encode=true|false
+
+   prop-font-family=T
+   fixed-font-family=C
+   font-size=10000
+
+   line-style=thick|double
+   line-gutter=0.5pt
+   line-spacing=0.5pt
+   line-width=0.5pt
+   line-width-thick=1pt
+
+   optimize-text-size=1|0|2
+   optimize-line-size=1|0
+   max-fonts-simult=0     Max # of fonts in printer memory at once (0=infinite)
+ */
+
+/* The number of `psus' (PostScript driver UnitS) per inch.  Although
+   this is a #define, the value is expected never to change.  If it
+   does, review all uses.  */
+#define PSUS 72000
+
+/* Magic numbers for PostScript and EPSF drivers. */
+enum
+  {
+    MAGIC_PS,
+    MAGIC_EPSF
+  };
+
+/* Orientations. */
+enum
+  {
+    OTN_PORTRAIT,              /* Portrait. */
+    OTN_LANDSCAPE              /* Landscape. */
+  };
+
+/* Output options. */
+enum
+  {
+    OPO_MIRROR_HORZ = 001,     /* 1=Mirror across a horizontal axis. */
+    OPO_MIRROR_VERT = 002,     /* 1=Mirror across a vertical axis. */
+    OPO_ROTATE_180 = 004,      /* 1=Rotate the page 180 degrees. */
+    OPO_COLOR = 010,           /* 1=Enable color. */
+    OPO_HEADERS = 020,         /* 1=Draw headers at top of page. */
+    OPO_AUTO_ENCODE = 040,     /* 1=Add encodings semi-intelligently. */
+    OPO_DOUBLE_LINE = 0100     /* 1=Double lines instead of thick lines. */
+  };
+
+/* Data allowed in output. */
+enum
+  {
+    ODA_CLEAN7BIT,             /* 0x09, 0x0a, 0x0d, 0x1b...0x7e */
+    ODA_CLEAN8BIT,             /* 0x09, 0x0a, 0x0d, 0x1b...0xff */
+    ODA_BINARY,                        /* 0x00...0xff */
+    ODA_COUNT
+  };
+
+/* Types of lines for purpose of caching. */
+enum
+  {
+    horz,                      /* Single horizontal. */
+    dbl_horz,                  /* Double horizontal. */
+    spl_horz,                  /* Special horizontal. */
+    vert,                      /* Single vertical. */
+    dbl_vert,                  /* Double vertical. */
+    spl_vert,                  /* Special vertical. */
+    n_line_types
+  };
+
+/* Cached line. */
+struct line_form
+  {
+    int ind;                   /* Independent var.  Don't reorder. */
+    int mdep;                  /* Maximum number of dependent var pairs. */
+    int ndep;                  /* Current number of dependent var pairs. */
+    int dep[1][2];             /* Dependent var pairs. */
+  };
+
+/* Contents of ps_driver_ext.loaded. */
+struct font_entry
+  {
+    char *dit;                 /* Font Groff name. */
+    struct font_desc *font;    /* Font descriptor. */
+  };
+
+/* Combines a font with a font size for benefit of generated code. */
+struct ps_font_combo
+  {
+    struct font_entry *font;   /* Font. */
+    int size;                  /* Font size. */
+    int index;                 /* PostScript index. */
+  };
+
+/* A font encoding. */
+struct ps_encoding
+  {
+    char *filename;            /* Normalized filename of this encoding. */
+    int index;                 /* Index value. */
+  };
+
+/* PostScript output driver extension record. */
+struct ps_driver_ext
+  {
+    /* User parameters. */
+    int orientation;           /* OTN_PORTRAIT or OTN_LANDSCAPE. */
+    int output_options;                /* OPO_*. */
+    int data;                  /* ODA_*. */
+
+    int left_margin;           /* Left margin in psus. */
+    int right_margin;          /* Right margin in psus. */
+    int top_margin;            /* Top margin in psus. */
+    int bottom_margin;         /* Bottom margin in psus. */
+
+    char eol[3];               /* End of line--CR, LF, or CRLF. */
+    
+    char *font_dir;            /* Font directory relative to font path. */
+    char *prologue_fn;         /* Prologue's filename relative to font dir. */
+    char *desc_fn;             /* DESC filename relative to font dir. */
+    char *encoding_fn;         /* Encoding's filename relative to font dir. */
+
+    char *prop_family;         /* Default proportional font family. */
+    char *fixed_family;                /* Default fixed-pitch font family. */
+    int font_size;             /* Default font size (psus). */
+
+    int line_gutter;           /* Space around lines. */
+    int line_space;            /* Space between lines. */
+    int line_width;            /* Width of lines. */
+    int line_width_thick;      /* Width of thick lines. */
+
+    int text_opt;              /* Text optimization level. */
+    int line_opt;              /* Line optimization level. */
+    int max_fonts;             /* Max # of simultaneous fonts (0=infinite). */
+
+    /* Internal state. */
+    struct file_ext file;      /* Output file. */
+    int page_number;           /* Current page number. */
+    int file_page_number;      /* Page number in this file. */
+    int w, l;                  /* Paper size. */
+    struct hsh_table *lines[n_line_types];     /* Line buffers. */
+    
+    struct font_entry *prop;   /* Default Roman proportional font. */
+    struct font_entry *fixed;  /* Default Roman fixed-pitch font. */
+    struct hsh_table *loaded;  /* Fonts in memory. */
+
+    struct hsh_table *combos;  /* Combinations of fonts with font sizes. */
+    struct ps_font_combo *last_font;   /* PostScript selected font. */
+    int next_combo;            /* Next font combo position index. */
+
+    struct hsh_table *encodings;/* Set of encodings. */
+    int next_encoding;         /* Next font encoding index. */
+
+    /* Currently selected font. */
+    struct font_entry *current;        /* Current font. */
+    char *family;              /* Font family. */
+    int size;                  /* Size in psus. */
+  }
+ps_driver_ext;
+
+/* Transform logical y-ordinate Y into a page ordinate. */
+#define YT(Y) (this->length - (Y))
+
+/* Prototypes. */
+static int postopen (struct file_ext *);
+static int preclose (struct file_ext *);
+static void draw_headers (struct outp_driver *this);
+
+static int compare_font_entry (const void *, const void *, void *param);
+static unsigned hash_font_entry (const void *, void *param);
+static void free_font_entry (void *, void *foo);
+static struct font_entry *load_font (struct outp_driver *, const char *dit);
+static void init_fonts (void);
+
+static void dump_lines (struct outp_driver *this);
+
+static void read_ps_encodings (struct outp_driver *this);
+static int compare_ps_encoding (const void *pa, const void *pb, void *foo);
+static unsigned hash_ps_encoding (const void *pa, void *foo);
+static void free_ps_encoding (void *a, void *foo);
+static void add_encoding (struct outp_driver *this, char *filename);
+static struct ps_encoding *default_encoding (struct outp_driver *this);
+
+static int compare_ps_combo (const void *pa, const void *pb, void *foo);
+static unsigned hash_ps_combo (const void *pa, void *foo);
+static void free_ps_combo (void *a, void *foo);
+
+static char *quote_ps_name (char *dest, const char *string);
+static char *quote_ps_string (char *dest, const char *string);
+\f
+/* Driver initialization. */
+
+int
+ps_open_global (struct outp_class *this unused)
+{
+  init_fonts ();
+  groff_init ();
+  return 1;
+}
+
+int
+ps_close_global (struct outp_class *this unused)
+{
+  return 1;
+}
+
+int *
+ps_font_sizes (struct outp_class *this unused, int *n_valid_sizes)
+{
+  /* Allow fonts up to 1" in height. */
+  static int valid_sizes[] =
+  {1, PSUS, 0, 0};
+
+  assert (n_valid_sizes != NULL);
+  *n_valid_sizes = 1;
+  return valid_sizes;
+}
+
+int
+ps_preopen_driver (struct outp_driver *this)
+{
+  struct ps_driver_ext *x;
+  
+  int i;
+
+  assert (this->driver_open == 0);
+  msg (VM (1), _("PostScript driver initializing as `%s'..."), this->name);
+       
+  this->ext = x = xmalloc (sizeof (struct ps_driver_ext));
+  this->res = PSUS;
+  this->horiz = this->vert = 1;
+  this->width = this->length = 0;
+
+  x->orientation = OTN_PORTRAIT;
+  x->output_options = OPO_COLOR | OPO_HEADERS | OPO_AUTO_ENCODE;
+  x->data = ODA_CLEAN7BIT;
+       
+  x->left_margin = x->right_margin =
+    x->top_margin = x->bottom_margin = PSUS / 2;
+       
+  strcpy (x->eol, "\n");
+
+  x->font_dir = NULL;
+  x->prologue_fn = NULL;
+  x->desc_fn = NULL;
+  x->encoding_fn = NULL;
+
+  x->prop_family = NULL;
+  x->fixed_family = NULL;
+  x->font_size = PSUS * 10 / 72;
+
+  x->line_gutter = PSUS / 144;
+  x->line_space = PSUS / 144;
+  x->line_width = PSUS / 144;
+  x->line_width_thick = PSUS / 48;
+
+  x->text_opt = -1;
+  x->line_opt = -1;
+  x->max_fonts = 0;
+
+  x->file.filename = NULL;
+  x->file.mode = "wb";
+  x->file.file = NULL;
+  x->file.sequence_no = &x->page_number;
+  x->file.param = this;
+  x->file.postopen = postopen;
+  x->file.preclose = preclose;
+  x->page_number = 0;
+  x->w = x->l = 0;
+
+  x->file_page_number = 0;
+  for (i = 0; i < n_line_types; i++)
+    x->lines[i] = NULL;
+  x->last_font = NULL;
+
+  x->prop = NULL;
+  x->fixed = NULL;
+  x->loaded = NULL;
+
+  x->next_combo = 0;
+  x->combos = NULL;
+
+  x->encodings = hsh_create (31, compare_ps_encoding, hash_ps_encoding,
+                            free_ps_encoding, NULL);
+  x->next_encoding = 0;
+
+  x->current = NULL;
+  x->family = NULL;
+  x->size = 0;
+
+  return 1;
+}
+
+int
+ps_postopen_driver (struct outp_driver *this)
+{
+  struct ps_driver_ext *x = this->ext;
+  
+  assert (this->driver_open == 0);
+
+  if (this->width == 0)
+    {
+      this->width = PSUS * 17 / 2;     /* Defaults to 8.5"x11". */
+      this->length = PSUS * 11;
+    }
+
+  if (x->text_opt == -1)
+    x->text_opt = (this->device & OUTP_DEV_SCREEN) ? 0 : 1;
+  if (x->line_opt == -1)
+    x->line_opt = (this->device & OUTP_DEV_SCREEN) ? 0 : 1;
+
+  x->w = this->width;
+  x->l = this->length;
+  if (x->orientation == OTN_LANDSCAPE)
+    {
+      int temp = this->width;
+      this->width = this->length;
+      this->length = temp;
+    }
+  this->width -= x->left_margin + x->right_margin;
+  this->length -= x->top_margin + x->bottom_margin;
+  if (x->output_options & OPO_HEADERS)
+    {
+      this->length -= 3 * x->font_size;
+      x->top_margin += 3 * x->font_size;
+    }
+  if (NULL == x->file.filename)
+    x->file.filename = xstrdup ("pspp.ps");
+
+  if (x->font_dir == NULL)
+    x->font_dir = xstrdup ("devps");
+  if (x->prologue_fn == NULL)
+    x->prologue_fn = xstrdup ("ps-prologue");
+  if (x->desc_fn == NULL)
+    x->desc_fn = xstrdup ("DESC");
+  if (x->encoding_fn == NULL)
+    x->encoding_fn = xstrdup ("ps-encodings");
+
+  if (x->prop_family == NULL)
+    x->prop_family = xstrdup ("H");
+  if (x->fixed_family == NULL)
+    x->fixed_family = xstrdup ("C");
+
+  read_ps_encodings (this);
+
+  x->family = NULL;
+  x->size = PSUS / 6;
+
+  if (this->length / x->font_size < 15)
+    {
+      msg (SE, _("PostScript driver: The defined page is not long "
+                "enough to hold margins and headers, plus least 15 "
+                "lines of the default fonts.  In fact, there's only "
+                "room for %d lines of each font at the default size "
+                "of %d.%03d points."),
+          this->length / x->font_size,
+          x->font_size / 1000, x->font_size % 1000);
+      return 0;
+    }
+
+  this->driver_open = 1;
+  msg (VM (2), _("%s: Initialization complete."), this->name);
+
+  return 1;
+}
+
+int
+ps_close_driver (struct outp_driver *this)
+{
+  struct ps_driver_ext *x = this->ext;
+  
+  int i;
+
+  assert (this->driver_open == 1);
+  msg (VM (2), _("%s: Beginning closing..."), this->name);
+  
+  fn_close_ext (&x->file);
+  free (x->file.filename);
+  free (x->font_dir);
+  free (x->prologue_fn);
+  free (x->desc_fn);
+  free (x->encoding_fn);
+  free (x->prop_family);
+  free (x->fixed_family);
+  free (x->family);
+  for (i = 0; i < n_line_types; i++)
+    hsh_destroy (x->lines[i]);
+  hsh_destroy (x->encodings);
+  hsh_destroy (x->combos);
+  hsh_destroy (x->loaded);
+  free (x);
+  
+  this->driver_open = 0;
+  msg (VM (3), _("%s: Finished closing."), this->name);
+
+  return 1;
+}
+
+/* font_entry comparison function for hash tables. */
+static int
+compare_font_entry (const void *a, const void *b, void *foobar unused)
+{
+  return strcmp (((struct font_entry *) a)->dit, ((struct font_entry *) b)->dit);
+}
+
+/* font_entry hash function for hash tables. */
+static unsigned
+hash_font_entry (const void *a, void *foobar unused)
+{
+  return hashpjw (((struct font_entry *) a)->dit);
+}
+
+/* font_entry destructor function for hash tables. */
+static void
+free_font_entry (void *pa, void *foo unused)
+{
+  struct font_entry *a = pa;
+  free (a->dit);
+  free (a);
+}
+
+/* Generic option types. */
+enum
+{
+  boolean_arg = -10,
+  pos_int_arg,
+  dimension_arg,
+  string_arg,
+  nonneg_int_arg
+};
+
+/* All the options that the PostScript driver supports. */
+static struct outp_option option_tab[] =
+{
+  /* *INDENT-OFF* */
+  {"output-file",              1,              0},
+  {"paper-size",               2,              0},
+  {"orientation",              3,              0},
+  {"color",                    boolean_arg,    0},
+  {"data",                     4,              0},
+  {"auto-encode",              boolean_arg,    5},
+  {"headers",                  boolean_arg,    1},
+  {"left-margin",              pos_int_arg,    0},
+  {"right-margin",             pos_int_arg,    1},
+  {"top-margin",               pos_int_arg,    2},
+  {"bottom-margin",            pos_int_arg,    3},
+  {"font-dir",                 string_arg,     0},
+  {"prologue-file",            string_arg,     1},
+  {"device-file",              string_arg,     2},
+  {"encoding-file",            string_arg,     3},
+  {"prop-font-family",         string_arg,     5},
+  {"fixed-font-family",                string_arg,     6},
+  {"font-size",                        pos_int_arg,    4},
+  {"optimize-text-size",       nonneg_int_arg, 0},
+  {"optimize-line-size",       nonneg_int_arg, 1},
+  {"max-fonts-simult",         nonneg_int_arg, 2},
+  {"line-ends",                        6,              0},
+  {"line-style",               7,              0},
+  {"line-width",               dimension_arg,  2},
+  {"line-gutter",              dimension_arg,  3},
+  {"line-width",               dimension_arg,  4},
+  {"line-width-thick",         dimension_arg,  5},
+  {"", 0, 0},
+  /* *INDENT-ON* */
+};
+static struct outp_option_info option_info;
+
+void
+ps_option (struct outp_driver *this, const char *key, const struct string *val)
+{
+  struct ps_driver_ext *x = this->ext;
+  int cat, subcat;
+  char *value = ds_value (val);
+
+  cat = outp_match_keyword (key, option_tab, &option_info, &subcat);
+
+  switch (cat)
+    {
+    case 0:
+      msg (SE, _("Unknown configuration parameter `%s' for PostScript device "
+          "driver."), key);
+      break;
+    case 1:
+      free (x->file.filename);
+      x->file.filename = xstrdup (value);
+      break;
+    case 2:
+      outp_get_paper_size (value, &this->width, &this->length);
+      break;
+    case 3:
+      if (!strcmp (value, "portrait"))
+       x->orientation = OTN_PORTRAIT;
+      else if (!strcmp (value, "landscape"))
+       x->orientation = OTN_LANDSCAPE;
+      else
+       msg (SE, _("Unknown orientation `%s'.  Valid orientations are "
+            "`portrait' and `landscape'."), value);
+      break;
+    case 4:
+      if (!strcmp (value, "clean7bit") || !strcmp (value, "Clean7Bit"))
+       x->data = ODA_CLEAN7BIT;
+      else if (!strcmp (value, "clean8bit")
+              || !strcmp (value, "Clean8Bit"))
+       x->data = ODA_CLEAN8BIT;
+      else if (!strcmp (value, "binary") || !strcmp (value, "Binary"))
+       x->data = ODA_BINARY;
+      else
+       msg (SE, _("Unknown value for `data'.  Valid values are `clean7bit', "
+            "`clean8bit', and `binary'."));
+      break;
+    case 6:
+      if (!strcmp (value, "lf"))
+       strcpy (x->eol, "\n");
+      else if (!strcmp (value, "crlf"))
+       strcpy (x->eol, "\r\n");
+      else
+       msg (SE, _("Unknown value for `line-ends'.  Valid values are `lf' and "
+                  "`crlf'."));
+      break;
+    case 7:
+      if (!strcmp (value, "thick"))
+       x->output_options &= ~OPO_DOUBLE_LINE;
+      else if (!strcmp (value, "double"))
+       x->output_options |= OPO_DOUBLE_LINE;
+      else
+       msg (SE, _("Unknown value for `line-style'.  Valid values are `thick' "
+                  "and `double'."));
+      break;
+    case boolean_arg:
+      {
+       int setting;
+       int mask;
+
+       if (!strcmp (value, "on") || !strcmp (value, "true")
+           || !strcmp (value, "yes") || atoi (value))
+         setting = 1;
+       else if (!strcmp (value, "off") || !strcmp (value, "false")
+                || !strcmp (value, "no") || !strcmp (value, "0"))
+         setting = 0;
+       else
+         {
+           msg (SE, _("Boolean value expected for %s."), key);
+           return;
+         }
+       switch (subcat)
+         {
+         case 0:
+           mask = OPO_COLOR;
+           break;
+         case 1:
+           mask = OPO_HEADERS;
+           break;
+         case 2:
+           mask = OPO_MIRROR_HORZ;
+           break;
+         case 3:
+           mask = OPO_MIRROR_VERT;
+           break;
+         case 4:
+           mask = OPO_ROTATE_180;
+           break;
+         case 5:
+           mask = OPO_AUTO_ENCODE;
+           break;
+         default:
+           assert (0);
+         }
+       if (setting)
+         x->output_options |= mask;
+       else
+         x->output_options &= ~mask;
+      }
+      break;
+    case pos_int_arg:
+      {
+       char *tail;
+       int arg;
+
+       errno = 0;
+       arg = strtol (value, &tail, 0);
+       if (arg < 1 || errno == ERANGE || *tail)
+         {
+           msg (SE, _("Positive integer required as value for `%s'."), key);
+           break;
+         }
+       if ((subcat == 4 || subcat == 5) && arg < 1000)
+         {
+           msg (SE, _("Default font size must be at least 1 point (value "
+                "of 1000 for key `%s')."), key);
+           break;
+         }
+       switch (subcat)
+         {
+         case 0:
+           x->left_margin = arg;
+           break;
+         case 1:
+           x->right_margin = arg;
+           break;
+         case 2:
+           x->top_margin = arg;
+           break;
+         case 3:
+           x->bottom_margin = arg;
+           break;
+         case 4:
+           x->font_size = arg;
+           break;
+         default:
+           assert (0);
+         }
+      }
+      break;
+    case dimension_arg:
+      {
+       int dimension = outp_evaluate_dimension (value, NULL);
+
+       if (dimension <= 0)
+         {
+           msg (SE, _("Value for `%s' must be a dimension of positive "
+                "length (i.e., `1in')."), key);
+           break;
+         }
+       switch (subcat)
+         {
+         case 2:
+           x->line_width = dimension;
+           break;
+         case 3:
+           x->line_gutter = dimension;
+           break;
+         case 4:
+           x->line_width = dimension;
+           break;
+         case 5:
+           x->line_width_thick = dimension;
+           break;
+         default:
+           assert (0);
+         }
+      }
+      break;
+    case string_arg:
+      {
+       char **dest;
+       switch (subcat)
+         {
+         case 0:
+           dest = &x->font_dir;
+           break;
+         case 1:
+           dest = &x->prologue_fn;
+           break;
+         case 2:
+           dest = &x->desc_fn;
+           break;
+         case 3:
+           dest = &x->encoding_fn;
+           break;
+         case 5:
+           dest = &x->prop_family;
+           break;
+         case 6:
+           dest = &x->fixed_family;
+           break;
+         default:
+           assert (0);
+         }
+       if (*dest)
+         free (*dest);
+       *dest = xstrdup (value);
+      }
+      break;
+    case nonneg_int_arg:
+      {
+       char *tail;
+       int arg;
+
+       errno = 0;
+       arg = strtol (value, &tail, 0);
+       if (arg < 0 || errno == ERANGE || *tail)
+         {
+           msg (SE, _("Nonnegative integer required as value for `%s'."), key);
+           break;
+         }
+       switch (subcat)
+         {
+         case 0:
+           x->text_opt = arg;
+           break;
+         case 1:
+           x->line_opt = arg;
+           break;
+         case 2:
+           x->max_fonts = arg;
+           break;
+         default:
+           assert (0);
+         }
+      }
+      break;
+#if __CHECKER__
+    case 42000:
+      assert (0);
+#endif
+    default:
+      assert (0);
+    }
+}
+
+/* Looks for a PostScript font file or config file in all the
+   appropriate places.  Returns the filename on success, NULL on
+   failure. */
+/* PORTME: Filename operations. */
+static char *
+find_ps_file (struct outp_driver *this, const char *name)
+{
+  struct ps_driver_ext *x = this->ext;
+  char *cp;
+
+  /* x->font_dir + name: "devps/ps-encodings". */
+  char *basename;
+
+  /* Usually equal to groff_font_path. */
+  char *pathname;
+
+  /* Final filename. */
+  char *fn;
+
+  /* Make basename. */
+  basename = local_alloc (strlen (x->font_dir) + 1 + strlen (name) + 1);
+  cp = stpcpy (basename, x->font_dir);
+  *cp++ = DIR_SEPARATOR;
+  strcpy (cp, name);
+
+  /* Decide on search path. */
+  {
+    const char *pre_pathname;
+    
+    pre_pathname = getenv ("STAT_GROFF_FONT_PATH");
+    if (pre_pathname == NULL)
+      pre_pathname = getenv ("GROFF_FONT_PATH");
+    if (pre_pathname == NULL)
+      pre_pathname = groff_font_path;
+    pathname = fn_tilde_expand (pre_pathname);
+  }
+
+  /* Search all possible places for the file. */
+  fn = fn_search_path (basename, pathname, NULL);
+  if (fn == NULL)
+    fn = fn_search_path (basename, config_path, NULL);
+  if (fn == NULL)
+    fn = fn_search_path (name, pathname, NULL);
+  if (fn == NULL)
+    fn = fn_search_path (name, config_path, NULL);
+  free (pathname);
+  local_free (basename);
+
+  return fn;
+}
+\f
+/* Encodings. */
+
+/* Hash table comparison function for ps_encoding's. */
+static int
+compare_ps_encoding (const void *pa, const void *pb, void *foo unused)
+{
+  const struct ps_encoding *a = pa;
+  const struct ps_encoding *b = pb;
+
+  return strcmp (a->filename, b->filename);
+}
+
+/* Hash table hash function for ps_encoding's. */
+static unsigned
+hash_ps_encoding (const void *pa, void *foo unused)
+{
+  const struct ps_encoding *a = pa;
+
+  return hashpjw (a->filename);
+}
+
+/* Hash table free function for ps_encoding's. */
+static void
+free_ps_encoding (void *pa, void *foo unused)
+{
+  struct ps_encoding *a = pa;
+
+  free (a->filename);
+  free (a);
+}
+
+/* Iterates through the list of encodings used for this driver
+   instance, reads each of them from disk, and writes them as
+   PostScript code to the output file. */
+static void
+output_encodings (struct outp_driver *this)
+{
+  struct ps_driver_ext *x = this->ext;
+
+  struct hsh_iterator iter;
+  struct ps_encoding *pe;
+
+  struct string line, buf;
+
+  ds_init (NULL, &line, 128);
+  ds_init (NULL, &buf, 128);
+  hsh_iterator_init (iter);
+  while ((pe = hsh_foreach (x->encodings, &iter)) != NULL)
+    {
+      FILE *f;
+
+      msg (VM (1), _("%s: %s: Opening PostScript font encoding..."),
+          this->name, pe->filename);
+      
+      f = fopen (pe->filename, "r");
+      if (!f)
+       {
+         msg (IE, _("PostScript driver: Cannot open encoding file `%s': %s.  "
+              "Substituting ISOLatin1Encoding for missing encoding."),
+              pe->filename, strerror (errno));
+         fprintf (x->file.file, "/E%x ISOLatin1Encoding def%s",
+                  pe->index, x->eol);
+       }
+      else
+       {
+         struct file_locator where;
+         
+         const char *tab[256];
+
+         char *pschar;
+         char *code;
+         int code_val;
+         char *fubar;
+
+         const char *notdef = ".notdef";
+
+         int i;
+
+         for (i = 0; i < 256; i++)
+           tab[i] = notdef;
+
+         where.filename = pe->filename;
+         where.line_number = 0;
+         err_push_file_locator (&where);
+
+         while (ds_get_config_line (f, &buf, &where))
+           {
+             char *sp;
+             
+             pschar = strtok_r (ds_value (&buf), " \t\r\n", &sp);
+             code = strtok_r (NULL, " \t\r\n", &sp);
+             if (*pschar == 0 || *code == 0)
+               continue;
+             code_val = strtol (code, &fubar, 0);
+             if (*fubar)
+               {
+                 msg (IS, _("PostScript driver: Invalid numeric format."));
+                 continue;
+               }
+             if (code_val < 0 || code_val > 255)
+               {
+                 msg (IS, _("PostScript driver: Codes must be between 0 "
+                            "and 255.  (%d is not allowed.)"), code_val);
+                 break;
+               }
+             tab[code_val] = local_alloc (strlen (pschar) + 1);
+             strcpy ((char *) (tab[code_val]), pschar);
+           }
+         err_pop_file_locator (&where);
+
+         ds_clear (&line);
+         ds_printf (&line, "/E%x[", pe->index);
+         for (i = 0; i < 257; i++)
+           {
+             char temp[288];
+
+             if (i < 256)
+               {
+                 quote_ps_name (temp, tab[i]);
+                 if (tab[i] != notdef)
+                   local_free (tab[i]);
+               }
+             else
+               strcpy (temp, "]def");
+             
+             if (ds_length (&line) + strlen (temp) > 70)
+               {
+                 ds_concat (&line, x->eol);
+                 fputs (ds_value (&line), x->file.file);
+                 ds_clear (&line);
+               }
+             ds_concat (&line, temp);
+           }
+         ds_concat (&line, x->eol);
+         fputs (ds_value (&line), x->file.file);
+
+         if (fclose (f) == EOF)
+           msg (MW, _("PostScript driver: Error closing encoding file `%s'."),
+                pe->filename);
+
+         msg (VM (2), _("%s: PostScript font encoding read successfully."),
+              this->name);
+       }
+    }
+  ds_destroy (&line);
+  ds_destroy (&buf);
+}
+
+/* Finds the ps_encoding in THIS that corresponds to the file with
+   name NORM_FILENAME, which must have previously been normalized with
+   normalize_filename(). */
+static struct ps_encoding *
+get_encoding (struct outp_driver *this, const char *norm_filename)
+{
+  struct ps_driver_ext *x = this->ext;
+  struct ps_encoding *pe;
+
+  pe = (struct ps_encoding *) hsh_find (x->encodings, (void *) &norm_filename);
+  return pe;
+}
+
+/* Searches the filesystem for an encoding file with name FILENAME;
+   returns its malloc'd, normalized name if found, otherwise NULL. */
+static char *
+find_encoding_file (struct outp_driver *this, char *filename)
+{
+  char *cp, *temp;
+
+  if (filename == NULL)
+    return NULL;
+  while (isspace ((unsigned char) *filename))
+    filename++;
+  for (cp = filename; *cp && !isspace ((unsigned char) *cp); cp++)
+    ;
+  if (cp == filename)
+    return NULL;
+  *cp = 0;
+
+  temp = find_ps_file (this, filename);
+  if (temp == NULL)
+    return NULL;
+
+  filename = fn_normalize (temp);
+  assert (filename != NULL);
+  free (temp);
+
+  return filename;
+}
+
+/* Adds the encoding represented by the not-necessarily-normalized
+   file FILENAME to the list of encodings, if it exists and is not
+   already in the list. */
+static void
+add_encoding (struct outp_driver *this, char *filename)
+{
+  struct ps_driver_ext *x = this->ext;
+
+  struct ps_encoding **pe;
+
+  filename = find_encoding_file (this, filename);
+  if (!filename)
+    return;
+
+  pe = (struct ps_encoding **) hsh_probe (x->encodings, (void *) &filename);
+  if (*pe)
+    {
+      free (filename);
+      return;
+    }
+  *pe = xmalloc (sizeof **pe);
+  (*pe)->filename = filename;
+  (*pe)->index = x->next_encoding++;
+}
+
+/* Finds the file on disk that contains the list of encodings to
+   include in the output file, then adds those encodings to the list
+   of encodings. */
+static void
+read_ps_encodings (struct outp_driver *this)
+{
+  struct ps_driver_ext *x = this->ext;
+
+  /* Encodings file. */
+  char *encoding_fn;           /* `ps-encodings' filename. */
+  FILE *f;
+
+  struct string line;
+  struct file_locator where;
+
+  /* It's okay if there's no list of encodings; not everyone cares. */
+  encoding_fn = find_ps_file (this, x->encoding_fn);
+  if (encoding_fn == NULL)
+    return;
+  free (encoding_fn);
+
+  msg (VM (1), _("%s: %s: Opening PostScript encoding list file."),
+       this->name, encoding_fn);
+  f = fopen (encoding_fn, "r");
+  if (!f)
+    {
+      msg (IE, _("Opening %s: %s."), encoding_fn, strerror (errno));
+      return;
+    }
+
+  where.filename = encoding_fn;
+  where.line_number = 0;
+  err_push_file_locator (&where);
+
+  ds_init (NULL, &line, 128);
+    
+  for (;;)
+    {
+      char *bp;
+
+      if (!ds_get_config_line (f, &line, &where))
+       {
+         if (ferror (f))
+           msg (ME, _("Reading %s: %s."), encoding_fn, strerror (errno));
+         break;
+       }
+
+      add_encoding (this, bp);
+    }
+
+  ds_destroy (&line);
+  err_pop_file_locator (&where);
+  
+  if (-1 == fclose (f))
+    msg (MW, _("Closing %s: %s."), encoding_fn, strerror (errno));
+
+  msg (VM (2), _("%s: PostScript encoding list file read successfully."), this->name);
+}
+
+/* Creates a default encoding for driver D that can be substituted for
+   an unavailable encoding. */
+struct ps_encoding *
+default_encoding (struct outp_driver *d)
+{
+  struct ps_driver_ext *x = d->ext;
+  static struct ps_encoding *enc;
+
+  if (!enc)
+    {
+      enc = xmalloc (sizeof *enc);
+      enc->filename = xstrdup (_("<<default encoding>>"));
+      enc->index = x->next_encoding++;
+    }
+  return enc;
+}
+\f
+/* Basic file operations. */
+
+/* Variables for the prologue. */
+struct ps_variable
+  {
+    const char *key;
+    const char *value;
+  };
+
+static struct ps_variable *ps_var_tab;
+
+/* Searches ps_var_tab for a ps_variable with key KEY, and returns the
+   associated value. */
+static const char *
+ps_get_var (const char *key)
+{
+  struct ps_variable *v;
+
+  for (v = ps_var_tab; v->key; v++)
+    if (!strcmp (key, v->key))
+      return v->value;
+  return NULL;
+}
+
+/* Writes the PostScript prologue to file F. */
+static int
+postopen (struct file_ext *f)
+{
+  static struct ps_variable dict[] =
+  {
+    {"bounding-box", 0},
+    {"creator", 0},
+    {"date", 0},
+    {"data", 0},
+    {"orientation", 0},
+    {"user", 0},
+    {"host", 0},
+    {"prop-font", 0},
+    {"fixed-font", 0},
+    {"scale-factor", 0},
+    {"paper-width", 0},
+    {"paper-length", 0},
+    {"left-margin", 0},
+    {"top-margin", 0},
+    {"line-width", 0},
+    {"line-width-thick", 0},
+    {"title", 0},
+    {"source-file", 0},
+    {0, 0},
+  };
+  char boundbox[INT_DIGITS * 4 + 4];
+#if HAVE_UNISTD_H
+  char host[128];
+#endif
+  char scaling[INT_DIGITS + 5];
+  time_t curtime;
+  struct tm *loctime;
+  char *p, *cp;
+  char paper_width[INT_DIGITS + 1];
+  char paper_length[INT_DIGITS + 1];
+  char left_margin[INT_DIGITS + 1];
+  char top_margin[INT_DIGITS + 1];
+  char line_width[INT_DIGITS + 1];
+  char line_width_thick[INT_DIGITS + 1];
+
+  struct outp_driver *this = f->param;
+  struct ps_driver_ext *x = this->ext;
+
+  char *prologue_fn = find_ps_file (this, x->prologue_fn);
+  FILE *prologue_file;
+
+  char *buf = NULL;
+  size_t buf_size = 0;
+
+  x->loaded = hsh_create (31, compare_font_entry, hash_font_entry,
+                         free_font_entry, NULL);
+  
+  {
+    char *font_name = local_alloc (2 + max (strlen (x->prop_family),
+                                           strlen (x->fixed_family)));
+    
+    strcpy (stpcpy (font_name, x->prop_family), "R");
+    x->prop = load_font (this, font_name);
+
+    strcpy (stpcpy (font_name, x->fixed_family), "R");
+    x->fixed = load_font (this, font_name);
+
+    local_free(font_name);
+  }
+
+  x->current = x->prop;
+  x->family = xstrdup (x->prop_family);
+  x->size = x->font_size;
+  
+  {
+    int *h = this->horiz_line_width, *v = this->vert_line_width;
+    
+    this->cp_x = this->cp_y = 0;
+    this->font_height = x->font_size;
+    {
+      struct char_metrics *metric;
+
+      metric = font_get_char_metrics (x->prop->font, '0');
+      this->prop_em_width = ((metric
+                             ? metric->width : x->prop->font->space_width)
+                            * x->font_size / 1000);
+
+      metric = font_get_char_metrics (x->fixed->font, '0');
+      this->fixed_width = ((metric
+                           ? metric->width : x->fixed->font->space_width)
+                          * x->font_size / 1000);
+    }
+        
+    h[0] = v[0] = 0;
+    h[1] = v[1] = 2 * x->line_gutter + x->line_width;
+    if (x->output_options & OPO_DOUBLE_LINE)
+      h[2] = v[2] = 2 * x->line_gutter + 2 * x->line_width + x->line_space;
+    else
+      h[2] = v[2] = 2 * x->line_gutter + x->line_width_thick;
+    h[3] = v[3] = 2 * x->line_gutter + x->line_width;
+    
+    {
+      int i;
+      
+      for (i = 0; i < (1 << OUTP_L_COUNT); i++)
+       {
+         int bit;
+
+         /* Maximum width of any line type so far. */
+         int max = 0;
+
+         for (bit = 0; bit < OUTP_L_COUNT; bit++)
+           if ((i & (1 << bit)) && h[bit] > max)
+             max = h[bit];
+         this->horiz_line_spacing[i] = this->vert_line_spacing[i] = max;
+       }
+    }
+  }
+
+  if (x->output_options & OPO_AUTO_ENCODE)
+    {
+      /* It's okay if this is done more than once since add_encoding()
+         is idempotent over identical encodings. */
+      add_encoding (this, x->prop->font->encoding);
+      add_encoding (this, x->fixed->font->encoding);
+    }
+
+  x->file_page_number = 0;
+
+  errno = 0;
+  if (prologue_fn == NULL)
+    {
+      msg (IE, _("Cannot find PostScript prologue.  The use of `-vv' "
+                "on the command line is suggested as a debugging aid."));
+      return 0;
+    }
+
+  msg (VM (1), _("%s: %s: Opening PostScript prologue..."),
+       this->name, prologue_fn);
+  prologue_file = fopen (prologue_fn, "rb");
+  if (prologue_file == NULL)
+    {
+      fclose (prologue_file);
+      free (prologue_fn);
+      msg (IE, "%s: %s", prologue_fn, strerror (errno));
+      goto error;
+    }
+
+  sprintf (boundbox, "0 0 %d %d",
+          x->w / (PSUS / 72) + (x->w % (PSUS / 72) > 0),
+          x->l / (PSUS / 72) + (x->l % (PSUS / 72) > 0));
+  dict[0].value = boundbox;
+
+  dict[1].value = (char *) version;
+
+  curtime = time (NULL);
+  loctime = localtime (&curtime);
+  dict[2].value = asctime (loctime);
+  cp = strchr (dict[2].value, '\n');
+  if (cp)
+    *cp = 0;
+
+  switch (x->data)
+    {
+    case ODA_CLEAN7BIT:
+      dict[3].value = "Clean7Bit";
+      break;
+    case ODA_CLEAN8BIT:
+      dict[3].value = "Clean8Bit";
+      break;
+    case ODA_BINARY:
+      dict[3].value = "Binary";
+      break;
+    default:
+      assert (0);
+    }
+
+  if (x->orientation == OTN_PORTRAIT)
+    dict[4].value = "Portrait";
+  else
+    dict[4].value = "Landscape";
+
+  /* PORTME: Determine username, net address. */
+#if HAVE_UNISTD_H
+  dict[5].value = getenv ("LOGNAME");
+  if (!dict[5].value)
+    dict[5].value = getlogin ();
+  if (!dict[5].value)
+    dict[5].value = _("nobody");
+
+  if (gethostname (host, 128) == -1)
+    {
+      if (errno == ENAMETOOLONG)
+       host[127] = 0;
+      else
+       strcpy (host, _("nowhere"));
+    }
+  dict[6].value = host;
+#else /* !HAVE_UNISTD_H */
+  dict[5].value = _("nobody");
+  dict[6].value = _("nowhere");
+#endif /* !HAVE_UNISTD_H */
+
+  cp = stpcpy (p = local_alloc (288), "font ");
+  quote_ps_string (cp, x->prop->font->internal_name);
+  dict[7].value = p;
+
+  cp = stpcpy (p = local_alloc (288), "font ");
+  quote_ps_string (cp, x->fixed->font->internal_name);
+  dict[8].value = p;
+
+  sprintf (scaling, "%.3f", PSUS / 72.0);
+  dict[9].value = scaling;
+
+  sprintf (paper_width, "%g", x->w / (PSUS / 72.0));
+  dict[10].value = paper_width;
+
+  sprintf (paper_length, "%g", x->l / (PSUS / 72.0));
+  dict[11].value = paper_length;
+
+  sprintf (left_margin, "%d", x->left_margin);
+  dict[12].value = left_margin;
+
+  sprintf (top_margin, "%d", x->top_margin);
+  dict[13].value = top_margin;
+
+  sprintf (line_width, "%d", x->line_width);
+  dict[14].value = line_width;
+
+  sprintf (line_width, "%d", x->line_width_thick);
+  dict[15].value = line_width_thick;
+  
+  getl_location (&dict[17].value, NULL);
+  if (dict[17].value == NULL)
+    dict[17].value = "<stdin>";
+
+  if (!outp_title)
+    {
+      dict[16].value = cp = local_alloc (strlen (dict[17].value) + 30);
+      sprintf (cp, "PSPP (%s)", dict[17].value);
+    }
+  else
+    {
+      dict[16].value = local_alloc (strlen (outp_title) + 1);
+      strcpy ((char *) (dict[16].value), outp_title);
+    }
+  
+  ps_var_tab = dict;
+  while (-1 != getline (&buf, &buf_size, prologue_file))
+    {
+      char *cp;
+      char *buf2;
+      int len;
+
+      cp = strstr (buf, "!eps");
+      if (cp)
+       {
+         if (this->class->magic == MAGIC_PS)
+           continue;
+         else
+           *cp = '\0';
+       }
+      else
+       {
+         cp = strstr (buf, "!ps");
+         if (cp)
+           {
+             if (this->class->magic == MAGIC_EPSF)
+               continue;
+             else
+               *cp = '\0';
+           } else {
+             if (strstr (buf, "!!!"))
+               continue;
+           }
+       }
+
+      if (!strncmp (buf, "!encodings", 10))
+       output_encodings (this);
+      else
+       {
+         char *beg;
+         beg = buf2 = fn_interp_vars (buf, ps_get_var);
+         len = strlen (buf2);
+         while (isspace (*beg))
+           beg++, len--;
+         if (beg[len - 1] == '\n')
+           len--;
+         if (beg[len - 1] == '\r')
+           len--;
+         fwrite (beg, len, 1, f->file);
+         fputs (x->eol, f->file);
+         free (buf2);
+       }
+    }
+  if (ferror (f->file))
+    msg (IE, _("Reading `%s': %s."), prologue_fn, strerror (errno));
+  fclose (prologue_file);
+
+  free (prologue_fn);
+  free (buf);
+
+  local_free (dict[7].value);
+  local_free (dict[8].value);
+  local_free (dict[16].value);
+
+  if (ferror (f->file))
+    goto error;
+
+  msg (VM (2), _("%s: PostScript prologue read successfully."), this->name);
+  return 1;
+
+error:
+  msg (VM (1), _("%s: Error reading PostScript prologue."), this->name);
+  return 0;
+}
+
+/* Writes the string STRING to buffer DEST (of at least 288
+   characters) as a PostScript name object.  Returns a pointer
+   to the null terminator of the resultant string. */
+static char *
+quote_ps_name (char *dest, const char *string)
+{
+  const char *sp;
+
+  for (sp = string; *sp; sp++)
+    switch (*(unsigned char *) sp)
+      {
+      case 'a':
+      case 'f':
+      case 'k':
+      case 'p':
+      case 'u':
+      case 'b':
+      case 'g':
+      case 'l':
+      case 'q':
+      case 'v':
+      case 'c':
+      case 'h':
+      case 'm':
+      case 'r':
+      case 'w':
+      case 'd':
+      case 'i':
+      case 'n':
+      case 's':
+      case 'x':
+      case 'e':
+      case 'j':
+      case 'o':
+      case 't':
+      case 'y':
+      case 'z':
+      case 'A':
+      case 'F':
+      case 'K':
+      case 'P':
+      case 'U':
+      case 'B':
+      case 'G':
+      case 'L':
+      case 'Q':
+      case 'V':
+      case 'C':
+      case 'H':
+      case 'M':
+      case 'R':
+      case 'W':
+      case 'D':
+      case 'I':
+      case 'N':
+      case 'S':
+      case 'X':
+      case 'E':
+      case 'J':
+      case 'O':
+      case 'T':
+      case 'Y':
+      case 'Z':
+      case '@':
+      case '^':
+      case '_':
+      case '|':
+      case '!':
+      case '$':
+      case '&':
+      case ':':
+      case ';':
+      case '.':
+      case ',':
+      case '-':
+      case '+':
+       break;
+      default:
+       {
+         char *dp = dest;
+
+         *dp++ = '<';
+         for (sp = string; *sp && dp < &dest[256]; sp++)
+           {
+             sprintf (dp, "%02x", *(unsigned char *) sp);
+             dp += 2;
+           }
+         return stpcpy (dp, ">cvn");
+       }
+      }
+  dest[0] = '/';
+  return stpcpy (&dest[1], string);
+}
+
+/* Adds the string STRING to buffer DEST as a PostScript quoted
+   string; returns a pointer to the null terminator added.  Will not
+   add more than 235 characters. */
+static char *
+quote_ps_string (char *dest, const char *string)
+{
+  const char *sp = string;
+  char *dp = dest;
+
+  *dp++ = '(';
+  for (; *sp && dp < &dest[235]; sp++)
+    if (*sp == '(')
+      dp = stpcpy (dp, "\\(");
+    else if (*sp == ')')
+      dp = stpcpy (dp, "\\)");
+    else if (*sp < 32 || *((unsigned char *) sp) > 127)
+      dp = spprintf (dp, "\\%3o", *sp);
+    else
+      *dp++ = *sp;
+  return stpcpy (dp, ")");
+}
+
+/* Writes the PostScript epilogue to file F. */
+static int
+preclose (struct file_ext *f)
+{
+  struct outp_driver *this = f->param;
+  struct ps_driver_ext *x = this->ext;
+  struct hsh_iterator iter;
+  struct font_entry *fe;
+
+  fprintf (f->file,
+          ("%%%%Trailer%s"
+           "%%%%Pages: %d%s"
+           "%%%%DocumentNeededResources:%s"),
+          x->eol, x->file_page_number, x->eol, x->eol);
+
+  hsh_iterator_init (iter);
+  while ((fe = hsh_foreach (x->loaded, &iter)) != NULL)
+    {
+      char buf[256], *cp;
+
+      cp = stpcpy (buf, "%%+ font ");
+      cp = quote_ps_string (cp, fe->font->internal_name);
+      strcpy (cp, x->eol);
+      fputs (buf, f->file);
+    }
+
+  hsh_destroy (x->loaded);
+  x->loaded = NULL;
+  hsh_destroy (x->combos);
+  x->combos = NULL;
+  x->last_font = NULL;
+  x->next_combo = 0;
+
+  fprintf (f->file, "%%EOF%s", x->eol);
+  if (ferror (f->file))
+    return 0;
+  return 1;
+}
+
+int
+ps_open_page (struct outp_driver *this)
+{
+  struct ps_driver_ext *x = this->ext;
+
+  assert (this->driver_open && !this->page_open);
+      
+  x->page_number++;
+  if (!fn_open_ext (&x->file))
+    {
+      if (errno)
+       msg (ME, _("PostScript output driver: %s: %s"), x->file.filename,
+            strerror (errno));
+      return 0;
+    }
+  x->file_page_number++;
+
+  hsh_destroy (x->combos);
+  x->combos = hsh_create (31, compare_ps_combo, hash_ps_combo,
+                         free_ps_combo, NULL);
+  x->last_font = NULL;
+  x->next_combo = 0;
+
+  fprintf (x->file.file,
+          "%%%%Page: %d %d%s"
+          "%%%%BeginPageSetup%s"
+          "/pg save def 0.001 dup scale%s",
+          x->page_number, x->file_page_number, x->eol,
+          x->eol,
+          x->eol);
+
+  if (x->orientation == OTN_LANDSCAPE)
+    fprintf (x->file.file,
+            "%d 0 translate 90 rotate%s",
+            x->w, x->eol);
+
+  if (x->bottom_margin != 0 || x->left_margin != 0)
+    fprintf (x->file.file,
+            "%d %d translate%s",
+            x->left_margin, x->bottom_margin, x->eol);
+
+  fprintf (x->file.file,
+          "/LW %d def/TW %d def %d setlinewidth%s"
+          "%%%%EndPageSetup%s",
+          x->line_width, x->line_width_thick, x->line_width, x->eol,
+          x->eol);
+
+  if (!ferror (x->file.file))
+    {
+      this->page_open = 1;
+      if (x->output_options & OPO_HEADERS)
+       draw_headers (this);
+    }
+
+  return !ferror (x->file.file);
+}
+
+int
+ps_close_page (struct outp_driver *this)
+{
+  struct ps_driver_ext *x = this->ext;
+
+  assert (this->driver_open && this->page_open);
+  
+  if (x->line_opt)
+    dump_lines (this);
+
+  fprintf (x->file.file,
+          "%%PageTrailer%s"
+          "EP%s",
+          x->eol, x->eol);
+
+  this->page_open = 0;
+  return !ferror (x->file.file);
+}
+\f
+/* Lines. */
+
+/* qsort() comparison function for int tuples. */
+static int
+int_2_compare (const void *a, const void *b)
+{
+  return *((const int *) a) - *((const int *) b);
+}
+
+/* Hash table comparison function for cached lines. */
+static int
+compare_line (const void *a, const void *b, void *foo unused)
+{
+  return ((struct line_form *) a)->ind - ((struct line_form *) b)->ind;
+}
+
+/* Hash table hash function for cached lines. */
+static unsigned
+hash_line (const void *pa, void *foo unused)
+{
+  const struct line_form *a = pa;
+
+  return a->ind;
+}
+
+/* Hash table free function for cached lines. */
+static void
+free_line (void *pa, void *foo unused)
+{
+  free (pa);
+}
+
+/* Writes PostScript code to draw a line from (x1,y1) to (x2,y2) to
+   the output file. */
+#define dump_line(x1, y1, x2, y2)                      \
+       fprintf (ext->file.file, "%d %d %d %d L%s",     \
+                x1, YT (y1), x2, YT (y2), ext->eol)
+
+/* Write PostScript code to draw a thick line from (x1,y1) to (x2,y2)
+   to the output file. */
+#define dump_thick_line(x1, y1, x2, y2)                        \
+       fprintf (ext->file.file, "%d %d %d %d TL%s",    \
+                x1, YT (y1), x2, YT (y2), ext->eol)
+
+/* Writes a line of type TYPE to THIS driver's output file.  The line
+   (or its center, in the case of double lines) has its independent
+   axis coordinate at IND; it extends from DEP1 to DEP2 on the
+   dependent axis. */
+static void
+dump_fancy_line (struct outp_driver *this, int type, int ind, int dep1, int dep2)
+{
+  struct ps_driver_ext *ext = this->ext;
+  int ofs = ext->line_space / 2 + ext->line_width / 2;
+
+  switch (type)
+    {
+    case horz:
+      dump_line (dep1, ind, dep2, ind);
+      break;
+    case dbl_horz:
+      if (ext->output_options & OPO_DOUBLE_LINE)
+       {
+         dump_line (dep1, ind - ofs, dep2, ind - ofs);
+         dump_line (dep1, ind + ofs, dep2, ind + ofs);
+       }
+      else
+       dump_thick_line (dep1, ind, dep2, ind);
+      break;
+    case spl_horz:
+      assert (0);
+    case vert:
+      dump_line (ind, dep1, ind, dep2);
+      break;
+    case dbl_vert:
+      if (ext->output_options & OPO_DOUBLE_LINE)
+       {
+         dump_line (ind - ofs, dep1, ind - ofs, dep2);
+         dump_line (ind + ofs, dep1, ind + ofs, dep2);
+       }
+      else
+       dump_thick_line (ind, dep1, ind, dep2);
+      break;
+    case spl_vert:
+      assert (0);
+    default:
+      assert (0);
+    }
+}
+
+#undef dump_line
+
+/* Writes all the cached lines to the output file, then clears the
+   cache. */
+static void
+dump_lines (struct outp_driver *this)
+{
+  struct ps_driver_ext *x = this->ext;
+
+  struct hsh_iterator iter;
+  struct line_form *line;
+  int type;
+
+  hsh_iterator_init (iter);
+  for (type = 0; type < n_line_types; type++)
+    {
+      while (NULL != (line = hsh_foreach (x->lines[type], &iter)))
+       {
+         int i;
+         int lo = INT_MIN, hi;
+
+         qsort (line->dep, line->ndep, sizeof *line->dep, int_2_compare);
+         lo = line->dep[0][0];
+         hi = line->dep[0][1];
+         for (i = 1; i < line->ndep; i++)
+           if (line->dep[i][0] <= hi + 1)
+             {
+               int min_hi = line->dep[i][1];
+               if (min_hi > hi)
+                 hi = min_hi;
+             }
+           else
+             {
+               dump_fancy_line (this, type, line->ind, lo, hi);
+               lo = line->dep[i][0];
+               hi = line->dep[i][1];
+             }
+         dump_fancy_line (this, type, line->ind, lo, hi);
+       }
+
+      hsh_destroy (x->lines[type]);
+      x->lines[type] = NULL;
+    }
+}
+
+/* (Same args as dump_fancy_line()).  Either dumps the line directly
+   to the output file, or adds it to the cache, depending on the
+   user-selected line optimization mode. */
+static void
+line (struct outp_driver *this, int type, int ind, int dep1, int dep2)
+{
+  struct ps_driver_ext *ext = this->ext;
+  struct line_form **f;
+
+  assert (dep2 >= dep1);
+  if (ext->line_opt == 0)
+    {
+      dump_fancy_line (this, type, ind, dep1, dep2);
+      return;
+    }
+
+  if (ext->lines[type] == NULL)
+    ext->lines[type] = hsh_create (31, compare_line, hash_line,
+                                  free_line, NULL);
+  f = (struct line_form **) hsh_probe (ext->lines[type],
+                                      (struct line_form *) & ind);
+  if (*f == NULL)
+    {
+      *f = xmalloc (sizeof **f + sizeof (int[15][2]));
+      (*f)->ind = ind;
+      (*f)->mdep = 16;
+      (*f)->ndep = 1;
+      (*f)->dep[0][0] = dep1;
+      (*f)->dep[0][1] = dep2;
+      return;
+    }
+  if ((*f)->ndep >= (*f)->mdep)
+    {
+      (*f)->mdep += 16;
+      *f = xrealloc (*f, (sizeof **f + sizeof (int[2]) * ((*f)->mdep - 1)));
+    }
+  (*f)->dep[(*f)->ndep][0] = dep1;
+  (*f)->dep[(*f)->ndep][1] = dep2;
+  (*f)->ndep++;
+}
+
+void
+ps_line_horz (struct outp_driver *this, const struct rect *r,
+             const struct color *c unused, int style)
+{
+  /* Must match output.h:OUTP_L_*. */
+  static const int types[OUTP_L_COUNT] =
+  {-1, horz, dbl_horz, spl_horz};
+
+  int y = (r->y1 + r->y2) / 2;
+
+  assert (this->driver_open && this->page_open);
+  assert (style >= 0 && style < OUTP_L_COUNT);
+  style = types[style];
+  if (style != -1)
+    line (this, style, y, r->x1, r->x2);
+}
+
+void
+ps_line_vert (struct outp_driver *this, const struct rect *r,
+             const struct color *c unused, int style)
+{
+  /* Must match output.h:OUTP_L_*. */
+  static const int types[OUTP_L_COUNT] =
+  {-1, vert, dbl_vert, spl_vert};
+
+  int x = (r->x1 + r->x2) / 2;
+
+  assert (this->driver_open && this->page_open);
+  assert (style >= 0 && style < OUTP_L_COUNT);
+  style = types[style];
+  if (style != -1)
+    line (this, style, x, r->y1, r->y2);
+}
+
+#define L (style->l != OUTP_L_NONE)
+#define R (style->r != OUTP_L_NONE)
+#define T (style->t != OUTP_L_NONE)
+#define B (style->b != OUTP_L_NONE)
+
+void
+ps_line_intersection (struct outp_driver *this, const struct rect *r,
+                     const struct color *c unused,
+                     const struct outp_styles *style)
+{
+  struct ps_driver_ext *ext = this->ext;
+
+  int x = (r->x1 + r->x2) / 2;
+  int y = (r->y1 + r->y2) / 2;
+  int ofs = (ext->line_space + ext->line_width) / 2;
+  int x1 = x - ofs, x2 = x + ofs;
+  int y1 = y - ofs, y2 = y + ofs;
+
+  assert (this->driver_open && this->page_open);
+  assert (!((style->l != style->r && style->l != OUTP_L_NONE
+            && style->r != OUTP_L_NONE)
+           || (style->t != style->b && style->t != OUTP_L_NONE
+               && style->b != OUTP_L_NONE)));
+
+  switch ((style->l | style->r) | ((style->t | style->b) << 8))
+    {
+    case (OUTP_L_SINGLE) | (OUTP_L_SINGLE << 8):
+    case (OUTP_L_SINGLE) | (OUTP_L_NONE << 8):
+    case (OUTP_L_NONE) | (OUTP_L_SINGLE << 8):
+      if (L)
+       line (this, horz, y, r->x1, x);
+      if (R)
+       line (this, horz, y, x, r->x2);
+      if (T)
+       line (this, vert, x, r->y1, y);
+      if (B)
+       line (this, vert, x, y, r->y2);
+      break;
+    case (OUTP_L_SINGLE) | (OUTP_L_DOUBLE << 8):
+    case (OUTP_L_NONE) | (OUTP_L_DOUBLE << 8):
+      if (L)
+       line (this, horz, y, r->x1, x1);
+      if (R)
+       line (this, horz, y, x2, r->x2);
+      if (T)
+       line (this, dbl_vert, x, r->y1, y);
+      if (B)
+       line (this, dbl_vert, x, y, r->y2);
+      if ((L && R) && !(T && B))
+       line (this, horz, y, x1, x2);
+      break;
+    case (OUTP_L_DOUBLE) | (OUTP_L_SINGLE << 8):
+    case (OUTP_L_DOUBLE) | (OUTP_L_NONE << 8):
+      if (L)
+       line (this, dbl_horz, y, r->x1, x);
+      if (R)
+       line (this, dbl_horz, y, x, r->x2);
+      if (T)
+       line (this, vert, x, r->y1, y);
+      if (B)
+       line (this, vert, x, y, r->y2);
+      if ((T && B) && !(L && R))
+       line (this, vert, x, y1, y2);
+      break;
+    case (OUTP_L_DOUBLE) | (OUTP_L_DOUBLE << 8):
+      if (L)
+       line (this, dbl_horz, y, r->x1, x);
+      if (R)
+       line (this, dbl_horz, y, x, r->x2);
+      if (T)
+       line (this, dbl_vert, x, r->y1, y);
+      if (B)
+       line (this, dbl_vert, x, y, r->y2);
+      if (T && B && !L)
+       line (this, vert, x1, y1, y2);
+      if (T && B && !R)
+       line (this, vert, x2, y1, y2);
+      if (L && R && !T)
+       line (this, horz, y1, x1, x2);
+      if (L && R && !B)
+       line (this, horz, y2, x1, x2);
+      break;
+    default:
+      assert (0);
+    }
+}
+
+void
+ps_line_width (struct outp_driver *this, int *width, int *height)
+{
+  struct ps_driver_ext *x = this->ext;
+
+  assert (this->driver_open && this->page_open);
+  width[0] = height[0] = 0;
+  width[1] = height[1] = 2 * x->line_gutter + x->line_width;
+  width[2] = height[2] = (2 * x->line_gutter + 2 * x->line_width
+                         + x->line_space);
+  width[3] = height[3] = 2 * x->line_gutter + x->line_width;
+}
+
+void
+ps_box (struct outp_driver *this unused, const struct rect *r unused,
+       const struct color *bord unused, const struct color *fill unused)
+{
+  assert (this->driver_open && this->page_open);
+}
+
+void 
+ps_polyline_begin (struct outp_driver *this unused,
+                  const struct color *c unused)
+{
+  assert (this->driver_open && this->page_open);
+}
+void 
+ps_polyline_point (struct outp_driver *this unused, int x unused, int y unused)
+{
+  assert (this->driver_open && this->page_open);
+}
+void 
+ps_polyline_end (struct outp_driver *this unused)
+{
+  assert (this->driver_open && this->page_open);
+}
+
+/* Returns the width of string S for THIS driver. */
+static int
+text_width (struct outp_driver *this, char *s)
+{
+  struct outp_text text;
+
+  text.options = OUTP_T_JUST_LEFT;
+  ls_init (&text.s, s, strlen (s));
+  this->class->text_metrics (this, &text);
+  return text.h;
+}
+
+/* Write string S at location (X,Y) with width W for THIS driver. */
+static void
+out_text_plain (struct outp_driver *this, char *s, int x, int y, int w)
+{
+  struct outp_text text;
+
+  text.options = OUTP_T_JUST_LEFT | OUTP_T_HORZ | OUTP_T_VERT;
+  ls_init (&text.s, s, strlen (s));
+  text.h = w;
+  text.v = this->font_height;
+  text.x = x;
+  text.y = y;
+  this->class->text_draw (this, &text);
+}
+
+/* Draw top of page headers for THIS driver. */
+static void
+draw_headers (struct outp_driver *this)
+{
+  struct ps_driver_ext *ext = this->ext;
+  
+  struct font_entry *old_current = ext->current;
+  char *old_family = xstrdup (ext->family); /* FIXME */
+  int old_size = ext->size;
+
+  int fh = this->font_height;
+  int y = -3 * fh;
+
+  fprintf (ext->file.file, "%d %d %d %d GB%s",
+          0, YT (y), this->width, YT (y + 2 * fh + ext->line_gutter),
+          ext->eol);
+  this->class->text_set_font_family (this, "T");
+
+  y += ext->line_width + ext->line_gutter;
+  
+  {
+    int rh_width;
+    char buf[128];
+
+    sprintf (buf, _("%s - Page %d"), curdate, ext->page_number);
+    rh_width = text_width (this, buf);
+
+    out_text_plain (this, buf, this->width - this->prop_em_width - rh_width,
+                   y, rh_width);
+
+    if (outp_title && outp_subtitle)
+      out_text_plain (this, outp_title, this->prop_em_width, y,
+                     this->width - 3 * this->prop_em_width - rh_width);
+
+    y += fh;
+  }
+  
+  {
+    int rh_width;
+    char buf[128];
+    char *string = outp_subtitle ? outp_subtitle : outp_title;
+
+    sprintf (buf, "%s - %s", version, host_system);
+    rh_width = text_width (this, buf);
+    
+    out_text_plain (this, buf, this->width - this->prop_em_width - rh_width,
+                   y, rh_width);
+
+    if (string)
+      out_text_plain (this, string, this->prop_em_width, y,
+                     this->width - 3 * this->prop_em_width - rh_width);
+
+    y += fh;
+  }
+
+  ext->current = old_current;
+  free (ext->family);
+  ext->family = old_family;
+  ext->size = old_size;
+}
+
+\f
+/* Text. */
+
+void
+ps_text_set_font_by_name (struct outp_driver *this, const char *dit)
+{
+  struct ps_driver_ext *x = this->ext;
+  struct font_entry *fe;
+
+  assert (this->driver_open && this->page_open);
+  
+  /* Short-circuit common fonts. */
+  if (!strcmp (dit, "PROP"))
+    {
+      x->current = x->prop;
+      x->size = x->font_size;
+      return;
+    }
+  else if (!strcmp (dit, "FIXED"))
+    {
+      x->current = x->fixed;
+      x->size = x->font_size;
+      return;
+    }
+
+  /* Find font_desc corresponding to Groff name dit. */
+  fe = hsh_find (x->loaded, &dit);
+  if (fe == NULL)
+    fe = load_font (this, dit);
+  x->current = fe;
+}
+
+void
+ps_text_set_font_by_position (struct outp_driver *this, int pos)
+{
+  struct ps_driver_ext *x = this->ext;
+  char *dit;
+
+  assert (this->driver_open && this->page_open);
+
+  /* Determine font name by suffixing position string to font family
+     name. */
+  {
+    char *cp;
+
+    dit = local_alloc (strlen (x->family) + 3);
+    cp = stpcpy (dit, x->family);
+    switch (pos)
+      {
+      case OUTP_F_R:
+       *cp++ = 'R';
+       break;
+      case OUTP_F_I:
+       *cp++ = 'I';
+       break;
+      case OUTP_F_B:
+       *cp++ = 'B';
+       break;
+      case OUTP_F_BI:
+       *cp++ = 'B';
+       *cp++ = 'I';
+       break;
+      default:
+       assert(0);
+      }
+    *cp++ = 0;
+  }
+  
+  /* Find font_desc corresponding to Groff name dit. */
+  {
+    struct font_entry *fe = hsh_find (x->loaded, &dit);
+    if (fe == NULL)
+      fe = load_font (this, dit);
+    x->current = fe;
+  }
+
+  local_free (dit);
+}
+
+void
+ps_text_set_font_family (struct outp_driver *this, const char *s)
+{
+  struct ps_driver_ext *x = this->ext;
+
+  assert (this->driver_open && this->page_open);
+  
+  free(x->family);
+  x->family = xstrdup (s);
+}
+
+const char *
+ps_text_get_font_name (struct outp_driver *this)
+{
+  struct ps_driver_ext *x = this->ext;
+
+  assert (this->driver_open && this->page_open);
+  return x->current->font->name;
+}
+
+const char *
+ps_text_get_font_family (struct outp_driver *this)
+{
+  struct ps_driver_ext *x = this->ext;
+  
+  assert (this->driver_open && this->page_open);
+  return x->family;
+}
+
+int
+ps_text_set_size (struct outp_driver *this, int size)
+{
+  struct ps_driver_ext *x = this->ext;
+
+  assert (this->driver_open && this->page_open);
+  x->size = PSUS / 72000 * size;
+  return 1;
+}
+
+int
+ps_text_get_size (struct outp_driver *this, int *em_width)
+{
+  struct ps_driver_ext *x = this->ext;
+
+  assert (this->driver_open && this->page_open);
+  if (em_width)
+    *em_width = (x->current->font->space_width * x->size) / 1000;
+  return x->size / (PSUS / 72000);
+}
+
+/* An output character. */
+struct output_char
+  {
+    struct font_entry *font;   /* Font of character. */
+    int size;                  /* Size of character. */
+    int x, y;                  /* Location of character. */
+    unsigned char ch;          /* Character. */
+    char separate;             /* Must be separate from previous char. */
+  };
+
+/* Hash table comparison function for ps_combo structs. */
+static int
+compare_ps_combo (const void *pa, const void *pb, void *foo unused)
+{
+  const struct ps_font_combo *a = pa;
+  const struct ps_font_combo *b = pb;
+
+  return !((a->font == b->font) && (a->size == b->size));
+}
+
+/* Hash table hash function for ps_combo structs. */
+static unsigned
+hash_ps_combo (const void *pa, void *foo unused)
+{
+  const struct ps_font_combo *a = pa;
+
+  return hashpjw (a->font->font->internal_name) ^ a->size;
+}
+
+/* Hash table free function for ps_combo structs. */
+static void
+free_ps_combo (void *a, void *foo unused)
+{
+  free (a);
+}
+
+/* Causes PostScript code to be output that switches to the font
+   CP->FONT and font size CP->SIZE.  The first time a particular
+   font/size combination is used on a particular page, this involves
+   outputting PostScript code to load the font. */
+static void
+switch_font (struct outp_driver *this, const struct output_char *cp)
+{
+  struct ps_driver_ext *ext = this->ext;
+  struct ps_font_combo srch, **fc;
+
+  srch.font = cp->font;
+  srch.size = cp->size;
+
+  fc = (struct ps_font_combo **) hsh_probe (ext->combos, &srch);
+  if (*fc)
+    {
+      fprintf (ext->file.file, "F%x%s", (*fc)->index, ext->eol);
+    }
+  else
+    {
+      char *filename;
+      struct ps_encoding *encoding;
+      char buf[512], *bp;
+
+      *fc = xmalloc (sizeof **fc);
+      (*fc)->font = cp->font;
+      (*fc)->size = cp->size;
+      (*fc)->index = ext->next_combo++;
+
+      filename = find_encoding_file (this, cp->font->font->encoding);
+      if (filename)
+       {
+         encoding = get_encoding (this, filename);
+         free (filename);
+       }
+      else
+       {
+         msg (IE, _("PostScript driver: Cannot find encoding `%s' for "
+              "PostScript font `%s'."), cp->font->font->encoding,
+              cp->font->font->internal_name);
+         encoding = default_encoding (this);
+       }
+
+      if (cp->font != ext->fixed && cp->font != ext->prop)
+       {
+         bp = stpcpy (buf, "%%IncludeResource: font ");
+         bp = quote_ps_string (bp, cp->font->font->internal_name);
+         bp = stpcpy (bp, ext->eol);
+       }
+      else
+       bp = buf;
+
+      bp = spprintf (bp, "/F%x E%x %d", (*fc)->index, encoding->index,
+                    cp->size);
+      bp = quote_ps_name (bp, cp->font->font->internal_name);
+      sprintf (bp, " SF%s", ext->eol);
+      fputs (buf, ext->file.file);
+    }
+  ext->last_font = *fc;
+}
+
+/* (write_text) Writes the accumulated line buffer to the output
+   file. */
+#define output_line()                          \
+       do                                      \
+         {                                     \
+            lp = stpcpy (lp, ext->eol);                \
+           *lp = 0;                            \
+           fputs (line, ext->file.file);       \
+           lp = line;                          \
+         }                                     \
+        while (0)
+
+/* (write_text) Adds the string representing number X to the line
+   buffer, flushing the buffer to disk beforehand if necessary. */
+#define put_number(X)                          \
+       do                                      \
+         {                                     \
+           int n = nsprintf (number, "%d", X); \
+           if (n + lp > &line[75])             \
+             output_line ();                   \
+           lp = stpcpy (lp, number);           \
+         }                                     \
+       while (0)
+
+/* Outputs PostScript code to THIS driver's output file to display the
+   characters represented by the output_char's between CP and END,
+   using the associated outp_text T to determine formatting.  WIDTH is
+   the width of the output region; WIDTH_LEFT is the amount of the
+   WIDTH that is not taken up by text (so it can be used to determine
+   justification). */
+static void
+write_text (struct outp_driver *this,
+           const struct output_char *cp, const struct output_char *end,
+           struct outp_text *t, int width unused, int width_left)
+{
+  struct ps_driver_ext *ext = this->ext;
+  int ofs;
+
+  int last_y;
+
+  char number[INT_DIGITS + 1];
+  char line[80];
+  char *lp;
+
+  switch (t->options & OUTP_T_JUST_MASK)
+    {
+    case OUTP_T_JUST_LEFT:
+      ofs = 0;
+      break;
+    case OUTP_T_JUST_RIGHT:
+      ofs = width_left;
+      break;
+    case OUTP_T_JUST_CENTER:
+      ofs = width_left / 2;
+      break;
+    }
+
+  lp = line;
+  last_y = INT_MIN;
+  while (cp < end)
+    {
+      int x = cp->x + ofs;
+      int y = cp->y + (cp->font->font->ascent * cp->size / 1000);
+
+      if (ext->last_font == NULL
+         || cp->font != ext->last_font->font
+         || cp->size != ext->last_font->size)
+       switch_font (this, cp);
+
+      *lp++ = '(';
+      do
+       {
+         /* PORTME! */
+         static unsigned char literal_chars[ODA_COUNT][32] =
+         {
+           {0x00, 0x00, 0x00, 0xf8, 0xff, 0xfc, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+           },
+           {0x00, 0x00, 0x00, 0xf8, 0xff, 0xfc, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+           },
+           {0x7e, 0xd6, 0xff, 0xfb, 0xff, 0xfc, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+           }
+         };
+
+         if (TEST_BIT (literal_chars[ext->data], cp->ch))
+           *lp++ = cp->ch;
+         else
+           switch (cp->ch)
+             {
+             case '(':
+               lp = stpcpy (lp, "\\(");
+               break;
+             case ')':
+               lp = stpcpy (lp, "\\)");
+               break;
+             default:
+               lp = spprintf (lp, "\\%03o", cp->ch);
+               break;
+             }
+         cp++;
+       }
+      while (cp < end && lp < &line[70] && cp->separate == 0);
+      *lp++ = ')';
+
+      put_number (x);
+
+      if (y != last_y)
+       {
+         *lp++ = ' ';
+         put_number (YT (y));
+         *lp++ = ' ';
+         *lp++ = 'S';
+         last_y = y;
+       }
+      else
+       {
+         *lp++ = ' ';
+         *lp++ = 'T';
+       }
+
+      if (lp >= &line[70])
+       output_line ();
+    }
+  if (lp != line)
+    output_line ();
+}
+
+#undef output_line
+#undef put_number
+
+/* Displays the text in outp_text T, if DRAW is nonzero; or, merely
+   determine the text metrics, if DRAW is zero. */
+static void
+text (struct outp_driver *this, struct outp_text *t, int draw)
+{
+  struct ps_driver_ext *ext = this->ext;
+
+  /* Output. */
+  struct output_char *buf;     /* Output buffer. */
+  struct output_char *buf_end; /* End of output buffer. */
+  struct output_char *buf_loc; /* Current location in output buffer. */
+
+  /* Saved state. */
+  struct font_entry *old_current = ext->current;
+  char *old_family = xstrdup (ext->family); /* FIXME */
+  int old_size = ext->size;
+
+  /* Input string. */
+  char *cp, *end;
+
+  /* Current location. */
+  int x, y;
+
+  /* Keeping track of what's left over. */
+  int width;                   /* Width available for characters. */
+  int width_left, height_left; /* Width, height left over. */
+  int max_height;              /* Tallest character on this line so far. */
+
+  /* Previous character. */
+  int prev_char;
+
+  /* Information about location of previous space. */
+  char *space_char;            /* Character after space. */
+  struct output_char *space_buf_loc; /* Buffer location after space. */
+  int space_width_left;                /* Width of characters before space. */
+
+  /* Name of the current character. */
+  const char *char_name;
+  char local_char_name[2] = {0, 0};
+
+  local_char_name[0] = local_char_name[1] = 0;
+
+  buf = local_alloc (sizeof *buf * 128);
+  buf_end = &buf[128];
+  buf_loc = buf;
+
+  assert (!ls_null_p (&t->s));
+  cp = ls_value (&t->s);
+  end = ls_end (&t->s);
+  if (draw)
+    {
+      x = t->x;
+      y = t->y;
+    }
+  width = width_left = (t->options & OUTP_T_HORZ) ? t->h : INT_MAX;
+  height_left = (t->options & OUTP_T_VERT) ? t->v : INT_MAX;
+  max_height = 0;
+  prev_char = -1;
+  space_char = NULL;
+
+  if (!width || !height_left)
+    goto exit;
+
+  while (cp < end)
+    {
+      struct char_metrics *metric;
+      int cur_char;
+      int kern_amt;
+      int char_width;
+      int separate = 0;
+
+      /* Set char_name to the name of the character or ligature at
+         *cp. */
+      if (ext->current->font->ligatures && *cp == 'f')
+       {
+         int lig = 0;
+
+         if (cp < end - 1)
+           switch (cp[1])
+             {
+             case 'i':
+               lig = LIG_fi, char_name = "fi";
+               break;
+             case 'l':
+               lig = LIG_fl, char_name = "fl";
+               break;
+             case 'f':
+               if (cp < end - 2)
+                 switch (cp[2])
+                   {
+                   case 'i':
+                     lig = LIG_ffi, char_name = "ffi";
+                     goto got_ligature;
+                   case 'l':
+                     lig = LIG_ffl, char_name = "ffl";
+                     goto got_ligature;
+                   }
+               lig = LIG_ff, char_name = "ff";
+             got_ligature:
+               break;
+             }
+         if ((lig & ext->current->font->ligatures) == 0)
+           {
+             local_char_name[0] = *cp++;       /* 'f' */
+             char_name = local_char_name;
+           }
+         else
+           cp += strlen (char_name);
+       }
+      else if (*cp == '\n')
+       {
+         if (draw)
+           {
+             write_text (this, buf, buf_loc, t, width, width_left);
+             buf_loc = buf;
+             x = t->x;
+             y += max_height;
+           }
+
+         width_left = width;
+         height_left -= max_height;
+         max_height = 0;
+         kern_amt = 0;
+         separate = 1;
+         cp++;
+
+         /* FIXME: when we're page buffering it will be necessary to
+            set separate to 1. */
+         continue;
+       }
+      else
+       {
+         local_char_name[0] = *cp++;
+         char_name = local_char_name;
+       }
+
+      /* Figure out what size this character is, and what kern
+         adjustment we need. */
+      cur_char = font_char_name_to_index (char_name);
+      metric = font_get_char_metrics (ext->current->font, cur_char);
+      if (!metric)
+       {
+         static struct char_metrics m;
+         metric = &m;
+         m.width = ext->current->font->space_width;
+         m.code = *char_name;
+       }
+      kern_amt = font_get_kern_adjust (ext->current->font, prev_char,
+                                      cur_char);
+      if (kern_amt)
+       {
+         kern_amt = (kern_amt * ext->size / 1000);
+         separate = 1;
+       }
+      char_width = metric->width * ext->size / 1000;
+
+      /* Record the current status if this is a space character. */
+      if (cur_char == space_index && buf_loc > buf)
+       {
+         space_char = cp;
+         space_buf_loc = buf_loc;
+         space_width_left = width_left;
+       }
+
+      /* Drop down to a new line if there's no room left on this
+         line. */
+      if (char_width + kern_amt > width_left)
+       {
+         /* Regress to previous space, if any. */
+         if (space_char)
+           {
+             cp = space_char;
+             width_left = space_width_left;
+             buf_loc = space_buf_loc;
+           }
+
+         if (draw)
+           {
+             write_text (this, buf, buf_loc, t, width, width_left);
+             buf_loc = buf;
+             x = t->x;
+             y += max_height;
+           }
+
+         width_left = width;
+         height_left -= max_height;
+         max_height = 0;
+         kern_amt = 0;
+
+         if (space_char)
+           {
+             space_char = NULL;
+             prev_char = -1;
+             /* FIXME: when we're page buffering it will be
+                necessary to set separate to 1. */
+             continue;
+           }
+         separate = 1;
+       }
+      if (ext->size > max_height)
+       max_height = ext->size;
+      if (max_height > height_left)
+       goto exit;
+
+      /* Actually draw the character. */
+      if (draw)
+       {
+         if (buf_loc >= buf_end)
+           {
+             int buf_len = buf_end - buf;
+
+             if (buf_len == 128)
+               {
+                 struct output_char *new_buf;
+
+                 new_buf = xmalloc (sizeof *new_buf * 256);
+                 memcpy (new_buf, buf, sizeof *new_buf * 128);
+                 buf_loc = new_buf + 128;
+                 buf_end = new_buf + 256;
+                 local_free (buf);
+                 buf = new_buf;
+               }
+             else
+               {
+                 buf = xrealloc (buf, sizeof *buf * buf_len * 2);
+                 buf_loc = buf + buf_len;
+                 buf_end = buf + buf_len * 2;
+               }
+           }
+
+         x += kern_amt;
+#if __CHECKER__
+         memset (buf_loc, 0, sizeof *buf_loc);
+#endif
+         buf_loc->font = ext->current;
+         buf_loc->size = ext->size;
+         buf_loc->x = x;
+         buf_loc->y = y;
+         buf_loc->ch = metric->code;
+         buf_loc->separate = separate;
+         buf_loc++;
+         x += char_width;
+       }
+
+      /* Prepare for next iteration. */
+      width_left -= char_width + kern_amt;
+      prev_char = cur_char;
+    }
+  height_left -= max_height;
+  if (buf_loc > buf && draw)
+    write_text (this, buf, buf_loc, t, width, width_left);
+
+exit:
+  if (!(t->options & OUTP_T_HORZ))
+    t->h = INT_MAX - width_left;
+  if (!(t->options & OUTP_T_VERT))
+    t->v = INT_MAX - height_left;
+  else
+    t->v -= height_left;
+  if (buf_end - buf == 128)
+    local_free (buf);
+  else
+    free (buf);
+  ext->current = old_current;
+  free (ext->family);
+  ext->family = old_family;
+  ext->size = old_size;
+}
+
+void
+ps_text_metrics (struct outp_driver *this, struct outp_text *t)
+{
+  assert (this->driver_open && this->page_open);
+  text (this, t, 0);
+}
+
+void
+ps_text_draw (struct outp_driver *this, struct outp_text *t)
+{
+  assert (this->driver_open && this->page_open);
+  text (this, t, 1);
+}
+\f
+/* Font loader. */
+
+/* Translate a filename to a font. */
+struct filename2font
+  {
+    char *filename;            /* Normalized filename. */
+    struct font_desc *font;
+  };
+
+/* Table of `filename2font's. */
+static struct hsh_table *ps_fonts;
+
+/* Hash table comparison function for filename2font structs. */
+static int
+compare_filename2font (const void *a, const void *b, void *param unused)
+{
+  return strcmp (((struct filename2font *) a)->filename,
+                ((struct filename2font *) b)->filename);
+}
+
+/* Hash table hash function for filename2font structs. */
+static unsigned
+hash_filename2font (const void *a, void *param unused)
+{
+  /* I sure hope this works with long filenames. */
+  return hashpjw (((struct filename2font *) a)->filename);
+}
+
+/* Initializes the global font list by creating the hash table for
+   translation of filenames to font_desc structs. */
+static void
+init_fonts (void)
+{
+  ps_fonts = hsh_create (31, compare_filename2font, hash_filename2font,
+                        NULL, NULL);
+}
+
+/* Loads the font having Groff name DIT into THIS driver instance.
+   Specifically, adds it into the THIS driver's `loaded' hash
+   table. */
+static struct font_entry *
+load_font (struct outp_driver *this, const char *dit)
+{
+  struct ps_driver_ext *x = this->ext;
+  char *filename1, *filename2;
+  void **entry;
+  struct font_entry *fe;
+
+  filename1 = find_ps_file (this, dit);
+  if (!filename1)
+    filename1 = xstrdup (dit);
+  filename2 = fn_normalize (filename1);
+  free (filename1);
+
+  entry = hsh_probe (ps_fonts, &filename2);
+  if (*entry == NULL)
+    {
+      struct filename2font *f2f;
+      struct font_desc *f = groff_read_font (filename2);
+
+      if (f == NULL)
+       {
+         if (x->fixed)
+           f = x->fixed->font;
+         else
+           f = default_font ();
+       }
+      
+      f2f = xmalloc (sizeof *f2f);
+      f2f->filename = filename2;
+      f2f->font = f;
+      *entry = f2f;
+    }
+  else
+    free (filename2);
+
+  fe = xmalloc (sizeof *fe);
+  fe->dit = xstrdup (dit);
+  fe->font = ((struct filename2font *) * entry)->font;
+  *hsh_probe (x->loaded, &dit) = fe;
+
+  return fe;
+}
+
+/* PostScript driver class. */
+struct outp_class postscript_class =
+{
+  "postscript",
+  MAGIC_PS,
+  0,
+
+  ps_open_global,
+  ps_close_global,
+  ps_font_sizes,
+
+  ps_preopen_driver,
+  ps_option,
+  ps_postopen_driver,
+  ps_close_driver,
+
+  ps_open_page,
+  ps_close_page,
+
+  NULL,
+
+  ps_line_horz,
+  ps_line_vert,
+  ps_line_intersection,
+
+  ps_box,
+  ps_polyline_begin,
+  ps_polyline_point,
+  ps_polyline_end,
+
+  ps_text_set_font_by_name,
+  ps_text_set_font_by_position,
+  ps_text_set_font_family,
+  ps_text_get_font_name,
+  ps_text_get_font_family,
+  ps_text_set_size,
+  ps_text_get_size,
+  ps_text_metrics,
+  ps_text_draw,
+};
+
+/* EPSF driver class.  FIXME: Probably doesn't work right. */
+struct outp_class epsf_class =
+{
+  "epsf",
+  MAGIC_EPSF,
+  0,
+
+  ps_open_global,
+  ps_close_global,
+  ps_font_sizes,
+
+  ps_preopen_driver,
+  ps_option,
+  ps_postopen_driver,
+  ps_close_driver,
+
+  ps_open_page,
+  ps_close_page,
+
+  NULL,
+
+  ps_line_horz,
+  ps_line_vert,
+  ps_line_intersection,
+
+  ps_box,
+  ps_polyline_begin,
+  ps_polyline_point,
+  ps_polyline_end,
+
+  ps_text_set_font_by_name,
+  ps_text_set_font_by_position,
+  ps_text_set_font_family,
+  ps_text_get_font_name,
+  ps_text_get_font_family,
+  ps_text_set_size,
+  ps_text_get_size,
+  ps_text_metrics,
+  ps_text_draw,
+};
+
+#endif /* NO_POSTSCRIPT */
diff --git a/src/print.c b/src/print.c
new file mode 100644 (file)
index 0000000..70a4176
--- /dev/null
@@ -0,0 +1,1211 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "dfm.h"
+#include "error.h"
+#include "expr.h"
+#include "file-handle.h"
+#include "lexer.h"
+#include "misc.h"
+#include "som.h"
+#include "tab.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* Describes what to do when an output field is encountered. */
+enum
+  {
+    PRT_ERROR,                 /* Invalid value. */
+    PRT_NEWLINE,               /* Newline. */
+    PRT_CONST,                 /* Constant string. */
+    PRT_VAR,                   /* Variable. */
+    PRT_SPACE                  /* A single space. */
+  };
+
+/* Describes how to output one field. */
+struct prt_out_spec
+  {
+    struct prt_out_spec *next;
+    int type;                  /* PRT_* constant. */
+    int fc;                    /* 0-based first column. */
+    union
+      {
+       char *c;                /* PRT_CONST: Associated string. */
+       struct
+         {
+           struct variable *v; /* PRT_VAR: Associated variable. */
+           struct fmt_spec f;  /* PRT_VAR: Output spec. */
+         }
+       v;
+      }
+    u;
+  };
+
+/* Enums for use with print_trns's `options' field. */
+enum
+  {
+    PRT_CMD_MASK = 1,          /* Command type mask. */
+    PRT_PRINT = 0,             /* PRINT transformation identifier. */
+    PRT_WRITE = 1,             /* WRITE transformation identifier. */
+    PRT_EJECT = 002            /* Can be combined with CMD_PRINT only. */
+  };
+
+/* PRINT, PRINT EJECT, WRITE private data structure. */
+struct print_trns
+  {
+    struct trns_header h;
+    struct file_handle *handle;        /* Output file, NULL=listing file. */
+    int options;               /* PRT_* bitmapped field. */
+    struct prt_out_spec *spec; /* Output specifications. */
+    int max_width;             /* Maximum line width including null. */
+#if !PAGED_STACK
+    char *line;                        /* Buffer for sticking lines in. */
+#endif
+  };
+
+/* PRT_PRINT or PRT_WRITE. */
+int which_cmd;
+
+/* Holds information on parsing the data file. */
+static struct print_trns prt;
+
+/* Last prt_out_spec in the chain.  Used for building the linked-list. */
+static struct prt_out_spec *next;
+
+/* Number of records. */
+static int nrec;
+
+static int internal_cmd_print (int flags);
+static int print_trns_proc (struct trns_header *, struct ccase *);
+static void print_trns_free (struct trns_header *);
+static int parse_specs (void);
+static void dump_table (void);
+static void append_var_spec (struct prt_out_spec *spec);
+static void alloc_line (void);
+
+#if DEBUGGING
+void debug_print (void);
+#endif
+\f
+/* Basic parsing. */
+
+/* Parses PRINT command. */
+int
+cmd_print (void)
+{
+  lex_match_id ("PRINT");
+  return internal_cmd_print (PRT_PRINT);
+}
+
+/* Parses PRINT EJECT command. */
+int
+cmd_print_eject (void)
+{
+  lex_match_id ("EJECT");
+  return internal_cmd_print (PRT_PRINT | PRT_EJECT);
+}
+
+/* Parses WRITE command. */
+int
+cmd_write (void)
+{
+  lex_match_id ("WRITE");
+  return internal_cmd_print (PRT_WRITE);
+}
+
+/* Parses the output commands.  F is PRT_PRINT, PRT_WRITE, or
+   PRT_PRINT|PRT_EJECT. */
+static int
+internal_cmd_print (int f)
+{
+  /* 0=print no table, 1=print table.  (TABLE subcommand.)  */
+  int table = 0;
+
+  /* malloc()'d transformation. */
+  struct print_trns *trns;
+
+  /* Fill in prt to facilitate error-handling. */
+  prt.h.proc = print_trns_proc;
+  prt.h.free = print_trns_free;
+  prt.handle = NULL;
+  prt.options = f;
+  prt.spec = NULL;
+#if !PAGED_STACK
+  prt.line = NULL;
+#endif
+  next = NULL;
+  nrec = 0;
+
+  which_cmd = f & PRT_CMD_MASK;
+
+  /* Parse the command options. */
+  while (!lex_match ('/'))
+    {
+      if (lex_match_id ("OUTFILE"))
+       {
+         lex_match ('=');
+
+         prt.handle = fh_parse_file_handle ();
+         if (!prt.handle)
+           goto lossage;
+       }
+      else if (lex_match_id ("RECORDS"))
+       {
+         lex_match ('=');
+         lex_match ('(');
+         if (!lex_force_int ())
+           goto lossage;
+         nrec = lex_integer ();
+         lex_get ();
+         lex_match (')');
+       }
+      else if (lex_match_id ("TABLE"))
+       table = 1;
+      else if (lex_match_id ("NOTABLE"))
+       table = 0;
+      else
+       {
+         lex_error (_("expecting a valid subcommand"));
+         goto lossage;
+       }
+    }
+
+  /* Parse variables and strings. */
+  if (!parse_specs ())
+    goto lossage;
+  
+  /* Output the variable table if requested. */
+  if (table)
+    dump_table ();
+
+  /* Count the maximum line width.  Allocate linebuffer if
+     applicable. */
+  alloc_line ();
+
+  /* Put the transformation in the queue. */
+  trns = xmalloc (sizeof *trns);
+  memcpy (trns, &prt, sizeof *trns);
+  add_transformation ((struct trns_header *) trns);
+
+#if DEBUGGING
+  debug_print ();
+#endif
+
+  return CMD_SUCCESS;
+
+ lossage:
+  print_trns_free ((struct trns_header *) & prt);
+  return CMD_FAILURE;
+}
+
+/* Appends the field output specification SPEC to the list maintained
+   in prt. */
+static void
+append_var_spec (struct prt_out_spec *spec)
+{
+  if (next == 0)
+    prt.spec = next = xmalloc (sizeof *spec);
+  else
+    next = next->next = xmalloc (sizeof *spec);
+
+  memcpy (next, spec, sizeof *spec);
+  next->next = NULL;
+}
+\f
+/* Field parsing.  Mostly stolen from data-list.c. */
+
+/* Used for chaining together fortran-like format specifiers. */
+struct fmt_list
+{
+  struct fmt_list *next;
+  int count;
+  struct fmt_spec f;
+  struct fmt_list *down;
+};
+
+/* Used as "local" variables among the fixed-format parsing funcs.  If
+   it were guaranteed that PSPP were going to be compiled by gcc,
+   I'd make all these functions a single set of nested functions. */
+static struct
+  {
+    struct variable **v;               /* variable list */
+    int nv;                    /* number of variables in list */
+    int cv;                    /* number of variables from list used up so far
+                                  by the FORTRAN-like format specifiers */
+
+    int recno;                 /* current 1-based record number */
+    int sc;                    /* 1-based starting column for next variable */
+
+    struct prt_out_spec spec;          /* next format spec to append to list */
+    int fc, lc;                        /* first, last 1-based column number of current
+                                  var */
+
+    int level;                 /* recursion level for FORTRAN-like format
+                                  specifiers */
+  }
+fx;
+
+static int fixed_parse_compatible (void);
+static struct fmt_list *fixed_parse_fortran (void);
+
+static int parse_string_argument (void);
+static int parse_variable_argument (void);
+
+/* Parses all the variable and string specifications on a single
+   PRINT, PRINT EJECT, or WRITE command into the prt structure.
+   Returns success. */
+static int
+parse_specs (void)
+{
+  /* Return code from called function. */
+  int code;
+
+  fx.recno = 1;
+  fx.sc = 1;
+
+  while (token != '.')
+    {
+      while (lex_match ('/'))
+       {
+         int prev_recno = fx.recno;
+
+         fx.recno++;
+         if (token == T_NUM)
+           {
+             if (!lex_force_int ())
+               return 0;
+             if (lex_integer () < fx.recno)
+               {
+                 msg (SE, _("The record number specified, %ld, is "
+                            "before the previous record, %d.  Data "
+                            "fields must be listed in order of "
+                            "increasing record number."),
+                      lex_integer (), fx.recno - 1);
+                 return 0;
+               }
+             fx.recno = lex_integer ();
+             lex_get ();
+           }
+
+         fx.spec.type = PRT_NEWLINE;
+         while (prev_recno++ < fx.recno)
+           append_var_spec (&fx.spec);
+
+         fx.sc = 1;
+       }
+
+      if (token == T_STRING)
+       code = parse_string_argument ();
+      else
+       code = parse_variable_argument ();
+      if (!code)
+       return 0;
+    }
+  fx.spec.type = PRT_NEWLINE;
+  append_var_spec (&fx.spec);
+
+  if (!nrec)
+    nrec = fx.recno;
+  else if (fx.recno > nrec)
+    {
+      msg (SE, _("Variables are specified on records that "
+                "should not exist according to RECORDS subcommand."));
+      return 0;
+    }
+      
+  if (token != '.')
+    {
+      lex_error (_("expecting end of command"));
+      return 0;
+    }
+  
+  return 1;
+}
+
+/* Parses a string argument to the PRINT commands.  Returns success. */
+static int
+parse_string_argument (void)
+{
+  fx.spec.type = PRT_CONST;
+  fx.spec.fc = fx.sc - 1;
+  fx.spec.u.c = xstrdup (ds_value (&tokstr));
+  lex_get ();
+
+  /* Parse the included column range. */
+  if (token == T_NUM)
+    {
+      /* Width of column range in characters. */
+      int c_len;
+
+      /* Width of constant string in characters. */
+      int s_len;
+
+      /* 1-based index of last column in range. */
+      int lc;
+
+      if (!lex_integer_p () || lex_integer () <= 0)
+       {
+         msg (SE, _("%g is not a valid column location."), tokval);
+         goto fail;
+       }
+      fx.spec.fc = lex_integer () - 1;
+
+      lex_get ();
+      lex_negative_to_dash ();
+      if (lex_match ('-'))
+       {
+         if (!lex_integer_p ())
+           {
+             msg (SE, _("Column location expected following `%d-'."),
+                  fx.spec.fc + 1);
+             goto fail;
+           }
+         if (lex_integer () <= 0)
+           {
+             msg (SE, _("%g is not a valid column location."), tokval);
+             goto fail;
+           }
+         if (lex_integer () < fx.spec.fc + 1)
+           {
+             msg (SE, _("%d-%ld is not a valid column range.  The second "
+                  "column must be greater than or equal to the first."),
+                  fx.spec.fc + 1, lex_integer ());
+             goto fail;
+           }
+         lc = lex_integer () - 1;
+
+         lex_get ();
+       }
+      else
+       /* If only a starting location is specified then the field is
+          the width of the provided string. */
+       lc = fx.spec.fc + strlen (fx.spec.u.c) - 1;
+
+      /* Apply the range. */
+      c_len = lc - fx.spec.fc + 1;
+      s_len = strlen (fx.spec.u.c);
+      if (s_len > c_len)
+       fx.spec.u.c[c_len] = 0;
+      else if (s_len < c_len)
+       {
+         fx.spec.u.c = xrealloc (fx.spec.u.c, c_len + 1);
+         memset (&fx.spec.u.c[s_len], ' ', c_len - s_len);
+         fx.spec.u.c[c_len] = 0;
+       }
+
+      fx.sc = lc + 1;
+    }
+  else
+    /* If nothing is provided then the field is the width of the
+       provided string. */
+    fx.sc += strlen (fx.spec.u.c);
+
+  append_var_spec (&fx.spec);
+  return 1;
+
+fail:
+  free (fx.spec.u.c);
+  return 0;
+}
+
+/* Parses a variable argument to the PRINT commands by passing it off
+   to fixed_parse_compatible() or fixed_parse_fortran() as appropriate.
+   Returns success. */
+static int
+parse_variable_argument (void)
+{
+  if (!parse_variables (NULL, &fx.v, &fx.nv, PV_DUPLICATE))
+    return 0;
+
+  if (token == T_NUM)
+    {
+      if (!fixed_parse_compatible ())
+       goto fail;
+    }
+  else if (token == '(')
+    {
+      fx.level = 0;
+      fx.cv = 0;
+      if (!fixed_parse_fortran ())
+       goto fail;
+    }
+  else
+    {
+      /* User wants dictionary format specifiers. */
+      int i;
+
+      lex_match ('*');
+      for (i = 0; i < fx.nv; i++)
+       {
+         /* Variable. */
+         fx.spec.type = PRT_VAR;
+         fx.spec.fc = fx.sc - 1;
+         fx.spec.u.v.v = fx.v[i];
+         fx.spec.u.v.f = fx.v[i]->print;
+         append_var_spec (&fx.spec);
+         fx.sc += fx.v[i]->print.w;
+
+         /* Space. */
+         fx.spec.type = PRT_SPACE;
+         fx.spec.fc = fx.sc - 1;
+         append_var_spec (&fx.spec);
+         fx.sc++;
+       }
+    }
+
+  free (fx.v);
+  return 1;
+
+fail:
+  free (fx.v);
+  return 0;
+}
+
+/* Parses a column specification for parse_specs(). */
+static int
+fixed_parse_compatible (void)
+{
+  int dividend;
+  int type;
+  int i;
+
+  type = fx.v[0]->type;
+  for (i = 1; i < fx.nv; i++)
+    if (type != fx.v[i]->type)
+      {
+       msg (SE, _("%s is not of the same type as %s.  To specify "
+                  "variables of different types in the same variable "
+                  "list, use a FORTRAN-like format specifier."),
+            fx.v[i]->name, fx.v[0]->name);
+       return 0;
+      }
+
+  if (!lex_force_int ())
+    return 0;
+  fx.fc = lex_integer () - 1;
+  if (fx.fc < 0)
+    {
+      msg (SE, _("Column positions for fields must be positive."));
+      return 0;
+    }
+  lex_get ();
+
+  lex_negative_to_dash ();
+  if (lex_match ('-'))
+    {
+      if (!lex_force_int ())
+       return 0;
+      fx.lc = lex_integer () - 1;
+      if (fx.lc < 0)
+       {
+         msg (SE, _("Column positions for fields must be positive."));
+         return 0;
+       }
+      else if (fx.lc < fx.fc)
+       {
+         msg (SE, _("The ending column for a field must not "
+                    "be less than the starting column."));
+         return 0;
+       }
+      lex_get ();
+    }
+  else
+    fx.lc = fx.fc;
+
+  fx.spec.u.v.f.w = fx.lc - fx.fc + 1;
+  if (lex_match ('('))
+    {
+      struct fmt_desc *fdp;
+
+      if (token == T_ID)
+       {
+         const char *cp;
+
+         fx.spec.u.v.f.type = parse_format_specifier_name (&cp, 0);
+         if (fx.spec.u.v.f.type == -1)
+           return 0;
+         if (*cp)
+           {
+             msg (SE, _("A format specifier on this line "
+                        "has extra characters on the end."));
+             return 0;
+           }
+         lex_get ();
+         lex_match (',');
+       }
+      else
+       fx.spec.u.v.f.type = FMT_F;
+
+      if (token == T_NUM)
+       {
+         if (!lex_force_int ())
+           return 0;
+         if (lex_integer () < 1)
+           {
+             msg (SE, _("The value for number of decimal places "
+                        "must be at least 1."));
+             return 0;
+           }
+         fx.spec.u.v.f.d = lex_integer ();
+         lex_get ();
+       }
+      else
+       fx.spec.u.v.f.d = 0;
+
+      fdp = &formats[fx.spec.u.v.f.type];
+      if (fdp->n_args < 2 && fx.spec.u.v.f.d)
+       {
+         msg (SE, _("Input format %s doesn't accept decimal places."),
+              fdp->name);
+         return 0;
+       }
+      if (fx.spec.u.v.f.d > 16)
+       fx.spec.u.v.f.d = 16;
+
+      if (!lex_force_match (')'))
+       return 0;
+    }
+  else
+    {
+      fx.spec.u.v.f.type = FMT_F;
+      fx.spec.u.v.f.d = 0;
+    }
+
+  fx.sc = fx.lc + 1;
+
+  if ((fx.lc - fx.fc + 1) % fx.nv)
+    {
+      msg (SE, _("The %d columns %d-%d can't be evenly divided into %d "
+                "fields."), fx.lc - fx.fc + 1, fx.fc + 1, fx.lc + 1, fx.nv);
+      return 0;
+    }
+
+  dividend = (fx.lc - fx.fc + 1) / fx.nv;
+  fx.spec.u.v.f.w = dividend;
+  if (!check_output_specifier (&fx.spec.u.v.f))
+    return 0;
+  if ((type == ALPHA) ^ (formats[fx.spec.u.v.f.type].cat & FCAT_STRING))
+    {
+      msg (SE, _("%s variables cannot be displayed with format %s."),
+          type == ALPHA ? _("String") : _("Numeric"),
+          fmt_to_string (&fx.spec.u.v.f));
+      return 0;
+    }
+
+  /* Check that, for string variables, the user didn't specify a width
+     longer than an actual string width. */
+  if (type == ALPHA)
+    {
+      /* Minimum width of all the string variables specified. */
+      int min_len = fx.v[0]->width;
+
+      for (i = 1; i < fx.nv; i++)
+       min_len = min (min_len, fx.v[i]->width);
+      if (!check_string_specifier (&fx.spec.u.v.f, min_len))
+       return 0;
+    }
+
+  fx.spec.type = PRT_VAR;
+  for (i = 0; i < fx.nv; i++)
+    {
+      fx.spec.fc = fx.fc + dividend * i;
+      fx.spec.u.v.v = fx.v[i];
+      append_var_spec (&fx.spec);
+    }
+  return 1;
+}
+
+/* Destroy a format list and, optionally, all its sublists. */
+static void
+destroy_fmt_list (struct fmt_list * f, int recurse)
+{
+  struct fmt_list *next;
+
+  for (; f; f = next)
+    {
+      next = f->next;
+      if (recurse && f->f.type == FMT_DESCEND)
+       destroy_fmt_list (f->down, 1);
+      free (f);
+    }
+}
+
+/* Recursively puts the format list F (which represents a set of
+   FORTRAN-like format specifications, like 4(F10,2X)) into the
+   structure prt. */
+static int
+dump_fmt_list (struct fmt_list * f)
+{
+  int i;
+
+  for (; f; f = f->next)
+    if (f->f.type == FMT_X)
+      fx.sc += f->count;
+    else if (f->f.type == FMT_T)
+      fx.sc = f->f.w;
+    else if (f->f.type == FMT_NEWREC)
+      {
+       fx.recno += f->count;
+       fx.sc = 1;
+       fx.spec.type = PRT_NEWLINE;
+       for (i = 0; i < f->count; i++)
+         append_var_spec (&fx.spec);
+      }
+    else
+      for (i = 0; i < f->count; i++)
+       if (f->f.type == FMT_DESCEND)
+         {
+           if (!dump_fmt_list (f->down))
+             return 0;
+         }
+       else
+         {
+           struct variable *v;
+
+           if (fx.cv >= fx.nv)
+             {
+               msg (SE, _("The number of format "
+                          "specifications exceeds the number of variable "
+                          "names given."));
+               return 0;
+             }
+
+           v = fx.v[fx.cv++];
+           if ((v->type == ALPHA) ^ (formats[f->f.type].cat & FCAT_STRING))
+             {
+               msg (SE, _("Display format %s may not be used with a "
+                          "%s variable."), fmt_to_string (&f->f),
+                    v->type == ALPHA ? _("string") : _("numeric"));
+               return 0;
+             }
+           if (!check_string_specifier (&f->f, v->width))
+             return 0;
+
+           fx.spec.type = PRT_VAR;
+           fx.spec.u.v.v = v;
+           fx.spec.u.v.f = f->f;
+           fx.spec.fc = fx.sc - 1;
+           append_var_spec (&fx.spec);
+
+           fx.sc += f->f.w;
+         }
+  return 1;
+}
+
+/* Recursively parses a list of FORTRAN-like format specifiers.  Calls
+   itself to parse nested levels of parentheses.  Returns to its
+   original caller NULL, to indicate error, non-NULL, but nothing
+   useful, to indicate success (it returns a free()'d block). */
+static struct fmt_list *
+fixed_parse_fortran (void)
+{
+  struct fmt_list *head;
+  struct fmt_list *fl = NULL;
+
+  lex_get ();                  /* skip opening parenthesis */
+  while (token != ')')
+    {
+      if (fl)
+       fl = fl->next = xmalloc (sizeof *fl);
+      else
+       head = fl = xmalloc (sizeof *fl);
+
+      if (token == T_NUM)
+       {
+         if (!lex_integer_p ())
+           goto fail;
+         fl->count = lex_integer ();
+         lex_get ();
+       }
+      else
+       fl->count = 1;
+
+      if (token == '(')
+       {
+         fl->f.type = FMT_DESCEND;
+         fx.level++;
+         fl->down = fixed_parse_fortran ();
+         fx.level--;
+         if (!fl->down)
+           goto fail;
+       }
+      else if (lex_match ('/'))
+       fl->f.type = FMT_NEWREC;
+      else if (!parse_format_specifier (&fl->f, 1)
+              || !check_output_specifier (&fl->f))
+       goto fail;
+
+      lex_match (',');
+    }
+  fl->next = NULL;
+  lex_get ();
+
+  if (fx.level)
+    return head;
+
+  fl->next = NULL;
+  dump_fmt_list (head);
+  destroy_fmt_list (head, 1);
+  if (fx.cv < fx.nv)
+    {
+      msg (SE, _("There aren't enough format specifications "
+          "to match the number of variable names given."));
+      goto fail;
+    }
+  return head;
+
+fail:
+  fl->next = NULL;
+  destroy_fmt_list (head, 0);
+
+  return NULL;
+}
+
+/* Prints the table produced by the TABLE subcommand to the listing
+   file. */
+static void
+dump_table (void)
+{
+  struct prt_out_spec *spec;
+  const char *filename;
+  struct tab_table *t;
+  int recno;
+  int nspec;
+
+  for (nspec = 0, spec = prt.spec; spec; spec = spec->next)
+    if (spec->type == PRT_CONST || spec->type == PRT_VAR)
+      nspec++;
+  t = tab_create (4, nspec + 1, 0);
+  tab_columns (t, TAB_COL_DOWN, 1);
+  tab_box (t, TAL_1, TAL_1, TAL_0, TAL_1, 0, 0, 3, nspec);
+  tab_hline (t, TAL_2, 0, 3, 1);
+  tab_headers (t, 0, 0, 1, 0);
+  tab_text (t, 0, 0, TAB_CENTER | TAT_TITLE, _("Variable"));
+  tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("Record"));
+  tab_text (t, 2, 0, TAB_CENTER | TAT_TITLE, _("Columns"));
+  tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("Format"));
+  tab_dim (t, tab_natural_dimensions);
+  for (nspec = recno = 0, spec = prt.spec; spec; spec = spec->next)
+    switch (spec->type)
+      {
+      case PRT_NEWLINE:
+       recno++;
+       break;
+      case PRT_CONST:
+       {
+         int len = strlen (spec->u.c);
+         nspec++;
+         tab_text (t, 0, nspec, TAB_LEFT | TAT_FIX | TAT_PRINTF,
+                       "\"%s\"", spec->u.c);
+         tab_text (t, 1, nspec, TAT_PRINTF, "%d", recno + 1);
+         tab_text (t, 2, nspec, TAT_PRINTF, "%3d-%3d",
+                       spec->fc + 1, spec->fc + len);
+         tab_text (t, 3, nspec, TAB_LEFT | TAT_FIX | TAT_PRINTF,
+                       "A%d", len);
+         break;
+       }
+      case PRT_VAR:
+       {
+         nspec++;
+         tab_text (t, 0, nspec, TAB_LEFT, spec->u.v.v->name);
+         tab_text (t, 1, nspec, TAT_PRINTF, "%d", recno + 1);
+         tab_text (t, 2, nspec, TAT_PRINTF, "%3d-%3d",
+                       spec->fc + 1, spec->fc + spec->u.v.f.w);
+         tab_text (t, 3, nspec, TAB_LEFT | TAT_FIX,
+                       fmt_to_string (&spec->u.v.f));
+         break;
+       }
+      case PRT_SPACE:
+       break;
+      case PRT_ERROR:
+       assert (0);
+      }
+
+  filename = fh_handle_name (prt.handle);
+  tab_title (t, 1, (prt.handle != NULL
+                   ? _("Writing %3d records to file %s.")
+                   : _("Writing %3d records to the listing file.")),
+            recno, filename);
+  tab_submit (t);
+  fh_handle_name (NULL);
+}
+
+/* PORTME: The number of characters in a line terminator. */
+#if __MSDOS__ 
+#define LINE_END_WIDTH 2       /* \r\n */
+#else
+#define LINE_END_WIDTH 1       /* \n */
+#endif
+
+/* Calculates the maximum possible line width and allocates a buffer
+   big enough to contain it, if necessary (otherwise sets max_width).
+   (The action taken depends on compiler & OS as detected by pref.h.) */
+static void
+alloc_line (void)
+{
+  /* Cumulative maximum line width (excluding null terminator) so far. */
+  int w = 0;
+
+  /* Width required by current this prt_out_spec. */
+  int pot_w;                   /* Potential w. */
+
+  /* Iterator. */
+  struct prt_out_spec *i;
+
+  for (i = prt.spec; i; i = i->next)
+    {
+      switch (i->type)
+       {
+       case PRT_NEWLINE:
+         pot_w = 0;
+         break;
+       case PRT_CONST:
+         pot_w = i->fc + strlen (i->u.c);
+         break;
+       case PRT_VAR:
+         pot_w = i->fc + i->u.v.f.w;
+         break;
+       case PRT_SPACE:
+         pot_w = i->fc + 1;
+         break;
+       case PRT_ERROR:
+         assert (0);
+         break;
+       }
+      if (pot_w > w)
+       w = pot_w;
+    }
+  prt.max_width = w + LINE_END_WIDTH + 1;
+#if !PAGED_STACK
+  prt.line = xmalloc (prt.max_width);
+#endif
+}
+\f
+/* Transformation. */
+
+/* Performs the transformation inside print_trns T on case C. */
+static int
+print_trns_proc (struct trns_header * trns, struct ccase * c)
+{
+  /* Transformation. */
+  struct print_trns *t = (struct print_trns *) trns;
+
+  /* Iterator. */
+  struct prt_out_spec *i;
+
+  /* Line buffer. */
+#if PAGED_STACK
+#if __GNUC__ && !__STRICT_ANSI__
+  char buf[t->max_width];
+#else /* !__GNUC__ */
+  char *buf = alloca (t->max_width);
+#endif /* !__GNUC__ */
+#else /* !PAGED_STACK */
+  char *buf = t->line;
+#endif /* !PAGED_STACK */
+
+  /* Length of the line in buf. */
+  int len = 0;
+  memset (buf, ' ', t->max_width);
+
+  if (t->options & PRT_EJECT)
+    som_eject_page ();
+
+  /* Note that a field written to a place where a field has already
+     been written truncates the record.  `PRINT /A B (T10,F8,T1,F8).'
+     only outputs B.  This is an example of bug-for-bug compatibility,
+     in the author's opinion. */
+  for (i = t->spec; i; i = i->next)
+    switch (i->type)
+      {
+      case PRT_NEWLINE:
+       if (t->handle == NULL)
+         {
+           buf[len] = 0;
+           tab_output_text (TAT_FIX | TAT_NOWRAP, buf);
+         }
+       else
+         {
+           if ((t->options & PRT_CMD_MASK) == PRT_PRINT
+               || t->handle->mode != FH_MD_BINARY)
+             {
+               /* PORTME: Line ends. */
+#if __MSDOS__
+               buf[len++] = '\r';
+#endif
+               buf[len++] = '\n';
+             }
+
+           dfm_put_record (t->handle, buf, len);
+         }
+
+       memset (buf, ' ', t->max_width);
+       len = 0;
+       break;
+
+      case PRT_CONST:
+       /* FIXME: Should be revised to keep track of the string's
+          length outside the loop, probably in i->u.c[0]. */
+       memcpy (&buf[i->fc], i->u.c, strlen (i->u.c));
+       len = i->fc + strlen (i->u.c);
+       break;
+
+      case PRT_VAR:
+       if (i->u.v.v->type == NUMERIC)
+         data_out (&buf[i->fc], &i->u.v.f, &c->data[i->u.v.v->fv]);
+       else
+         {
+           union value t;
+           t.c = c->data[i->u.v.v->fv].s;
+           data_out (&buf[i->fc], &i->u.v.f, &t);
+         }
+       len = i->fc + i->u.v.f.w;
+       break;
+
+      case PRT_SPACE:
+       /* PRT_SPACE always immediately follows PRT_VAR. */
+       buf[len++] = ' ';
+       break;
+
+      case PRT_ERROR:
+       assert (0);
+       break;
+      }
+
+  return -1;
+}
+
+/* Frees all the data inside print_trns T.  Does not free T. */
+static void
+print_trns_free (struct trns_header * t)
+{
+  struct prt_out_spec *i, *n;
+
+  for (i = ((struct print_trns *) t)->spec; i; i = n)
+    {
+      switch (i->type)
+       {
+       case PRT_CONST:
+         free (i->u.c);
+         /* fall through */
+       case PRT_NEWLINE:
+       case PRT_VAR:
+       case PRT_SPACE:
+         /* nothing to do */
+         break;
+       case PRT_ERROR:
+         assert (0);
+         break;
+       }
+      n = i->next;
+      free (i);
+    }
+#if !PAGED_STACK
+  free (((struct print_trns *) t)->line);
+#endif
+}
+\f
+/* PRINT SPACE. */
+
+/* PRINT SPACE transformation. */
+struct print_space_trns
+{
+  struct trns_header h;
+
+  struct file_handle *handle;  /* Output file, NULL=listing file. */
+  struct expression *e;                /* Number of lines; NULL=1. */
+}
+print_space_trns;
+
+static int print_space_trns_proc (struct trns_header *, struct ccase *);
+static void print_space_trns_free (struct trns_header *);
+
+int
+cmd_print_space (void)
+{
+  struct print_space_trns *t;
+  struct file_handle *handle;
+  struct expression *e;
+
+  lex_match_id ("SPACE");
+  if (lex_match_id ("OUTFILE"))
+    {
+      lex_match ('=');
+
+      if (token == T_ID)
+       handle = fh_get_handle_by_name (tokid);
+      else if (token == T_STRING)
+       handle = fh_get_handle_by_filename (tokid);
+      else
+       {
+         msg (SE, _("A file name or handle was expected in the "
+                    "OUTFILE subcommand."));
+         return CMD_FAILURE;
+       }
+      
+      if (!handle)
+       return CMD_FAILURE;
+      lex_get ();
+    }
+  else
+    handle = NULL;
+
+  if (token != '.')
+    {
+      e = expr_parse (PXP_NUMERIC);
+      if (token != '.')
+       {
+         expr_free (e);
+         lex_error (_("expecting end of command"));
+         return CMD_FAILURE;
+       }
+    }
+  else
+    e = NULL;
+
+  t = xmalloc (sizeof *t);
+  t->h.proc = print_space_trns_proc;
+  if (e)
+    t->h.free = print_space_trns_free;
+  else
+    t->h.free = NULL;
+  t->handle = handle;
+  t->e = e;
+
+  add_transformation ((struct trns_header *) t);
+  return CMD_SUCCESS;
+}
+
+static int
+print_space_trns_proc (struct trns_header * trns, struct ccase * c)
+{
+  struct print_space_trns *t = (struct print_space_trns *) trns;
+  int n;
+
+  if (t->e)
+    {
+      union value v;
+
+      expr_evaluate (t->e, c, &v);
+      n = v.f;
+      if (n < 0)
+       {
+         msg (SW, _("The expression on PRINT SPACE evaluated to %d.  It's "
+                    "not possible to PRINT SPACE a negative number of "
+                    "lines."),
+              n);
+         n = 1;
+       }
+    }
+  else
+    n = 1;
+
+  if (t->handle == NULL)
+    while (n--)
+      som_blank_line ();
+  else
+    {
+      char buf[LINE_END_WIDTH];
+
+      /* PORTME: Line ends. */
+#if __MSDOS__
+      buf[0] = '\r';
+      buf[1] = '\n';
+#else
+      buf[0] = '\n';
+#endif
+      while (n--)
+       dfm_put_record (t->handle, buf, LINE_END_WIDTH);
+    }
+
+  return -1;
+}
+
+static void
+print_space_trns_free (struct trns_header * trns)
+{
+  expr_free (((struct print_space_trns *) trns)->e);
+}
+\f
+/* Debugging code. */
+
+#if DEBUGGING
+void
+debug_print (void)
+{
+  struct prt_out_spec *p;
+
+  if (prt.handle == NULL)
+    {
+      printf ("PRINT");
+      if (prt.eject)
+       printf (" EJECT");
+    }
+  else
+    printf ("WRITE OUTFILE=%s", handle_name (prt.handle));
+  printf (" MAX_WIDTH=%d", prt.max_width);
+  printf (" /");
+  for (p = prt.spec; p; p = p->next)
+    switch (p->type)
+      {
+      case PRT_ERROR:
+       printf (_("<ERROR>"));
+       break;
+      case PRT_NEWLINE:
+       printf ("\n /");
+       break;
+      case PRT_CONST:
+       printf (" \"%s\" %d-%d", p->u.c, p->fc + 1, p->fc + strlen (p->u.c));
+       break;
+      case PRT_VAR:
+       printf (" %s %d %d-%d (%s)", p->u.v.v->name, p->u.v.v->fv, p->fc + 1,
+               p->fc + p->u.v.v->print.w, fmt_to_string (&p->u.v.v->print));
+       break;
+      case PRT_SPACE:
+       printf (" \" \" %d", p->fc + 1);
+       break;
+      }
+  printf (".\n");
+}
+#endif /* DEBUGGING */
diff --git a/src/q2c.c b/src/q2c.c
new file mode 100644 (file)
index 0000000..839d4a7
--- /dev/null
+++ b/src/q2c.c
@@ -0,0 +1,1871 @@
+/* q2c - parser generator for PSPP procedures.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <time.h>
+#include <errno.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include "str.h"
+
+/* Brokenness. */
+#ifndef EXIT_SUCCESS
+#define EXIT_SUCCESS 0
+#endif
+
+#ifndef EXIT_FAILURE
+#define EXIT_FAILURE 1
+#endif
+
+#if !HAVE_STRERROR
+#include "misc/strerror.c"
+#endif
+     
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* Max length of an input line. */
+#define MAX_LINE_LEN 1024
+
+/* Max token length. */
+#define MAX_TOK_LEN 1024
+
+/* argv[0]. */
+char *pgmname;
+
+/* Have the input and output files been opened yet? */
+int is_open;
+
+/* Input, output files. */
+FILE *in, *out;
+
+/* Input, output file names. */
+char *ifn, *ofn;
+
+/* Input, output file line number. */
+int ln, oln = 1;
+
+/* Input line buffer, current position. */
+char *buf, *cp;
+
+/* Token types. */
+enum
+  {
+    T_STRING = 256,    /* String literal. */
+    T_ID = 257         /* Identifier.  */
+  };
+
+/* Current token: either one of the above, or a single character. */
+int token;
+
+/* Token string value. */
+char *tokstr;
+\f
+/* Utility functions. */
+
+#if !(__GNUC__ >= 2)
+#define nullstr ""
+#else
+const char nullstr[] = "";
+#endif
+
+/* Close all open files and delete the output file, on failure. */
+void
+finish_up (void)
+{
+  if (!is_open)
+    return;
+  is_open = 0;
+  fclose (in);
+  fclose (out);
+  if (remove (ofn) == -1)
+    fprintf (stderr, "%s: %s: remove: %s\n", pgmname, ofn, strerror (errno));
+}
+
+#if __GNUC__ >= 2
+void hcf (void) __attribute__ ((noreturn));
+#endif
+
+/* Terminate unsuccessfully. */
+void
+hcf (void)
+{
+  finish_up ();
+  exit (EXIT_FAILURE);
+}
+
+#if __GNUC__ >= 2
+int fail (const char *, ...) __attribute__ ((format (printf, 1, 2)));
+int error (const char *, ...) __attribute__ ((format (printf, 1, 2)));
+#endif
+
+/* Output an error message and terminate unsuccessfully. */
+int
+fail (const char *format, ...)
+{
+  va_list args;
+
+  va_start (args, format);
+  fprintf (stderr, "%s: ", pgmname);
+  vfprintf (stderr, format, args);
+  fprintf (stderr, "\n");
+  va_end (args);
+
+  hcf ();
+}
+
+/* Output a context-dependent error message and terminate
+   unsuccessfully. */
+int
+error (const char *format,...)
+{
+  va_list args;
+
+  va_start (args, format);
+  fprintf (stderr, "%s:%d: (column %d) ", ifn, ln, (int) (cp - buf));
+  vfprintf (stderr, format, args);
+  fprintf (stderr, "\n");
+  va_end (args);
+
+  hcf ();
+}
+
+#define VME "virtual memory exhausted"
+
+/* Allocate a block of SIZE bytes and return a pointer to its
+   beginning. */
+void *
+xmalloc (size_t size)
+{
+  void *vp;
+  
+  if (size == 0)
+    return NULL;
+  
+  vp = malloc (size);
+  if (!vp)
+    {
+#if DEBUGGING && __CHECKER__
+      error ("xmalloc(%lu): Inducing segfault.", (unsigned long) size);
+      *((int *) 0) = 0;
+#endif
+      fail ("xmalloc(%lu): %s", (unsigned long) size, VME);
+    }
+  
+  return vp;
+}
+
+/* Resize the block at PTR to size SIZE and return a pointer to the
+   beginning of the new block. */
+void *
+xrealloc (void *ptr, size_t size)
+{
+  void *vp;
+  
+  if (!size)
+    {
+      if (ptr)
+       free (ptr);
+      return NULL;
+    }
+  
+  if (ptr)
+    vp = realloc (ptr, size);
+  else
+    vp = malloc (size);
+  
+  if (!vp)
+    fail ("xrealloc(%lu): %s", (unsigned long) size, VME);
+  
+  return vp;
+}
+
+/* Make a dynamically allocated copy of string S and return a pointer
+   to the first character. */
+char *
+xstrdup (const char *s)
+{
+  size_t size;
+  char *t;
+
+  assert (s != NULL);
+  size = strlen (s) + 1;
+  
+  t = malloc (size);
+  if (!t)
+    fail ("xstrdup(%lu): %s", (unsigned long) strlen (s), VME);
+    
+  memcpy (t, s, size);
+  return t;
+}
+
+/* Returns a pointer to one of 8 static buffers.  The buffers are used
+   in rotation. */
+char *
+get_buffer (void)
+{
+  static char b[8][256];
+  static int cb;
+
+  if (++cb >= 8)
+    cb = 0;
+  
+  return b[cb];
+}
+   
+/* Copies a string to a static buffer, converting it to lowercase in
+   the process, and returns a pointer to the static buffer. */
+char *
+st_lower (const char *s)
+{
+  char *p, *cp;
+  
+  p = cp = get_buffer ();
+  while (*s)
+    *cp++ = tolower ((unsigned char) (*s++));
+  *cp++ = '\0';
+  
+  return p;
+}
+
+/* Copies a string to a static buffer, converting it to uppercase in
+   the process, and returns a pointer to the static buffer. */
+char *
+st_upper (const char *s)
+{
+  char *p, *cp;
+
+  p = cp = get_buffer ();
+  while (*s)
+    *cp++ = toupper ((unsigned char) (*s++));
+  *cp++ = '\0';
+  
+  return p;
+}
+
+/* Returns the address of the first non-whitespace character in S, or
+   the address of the null terminator if none. */
+char *
+skip_ws (const char *s)
+{
+  while (isspace ((unsigned char) *s))
+    s++;
+  return (char *) s;
+}
+
+/* Read one line from the input file into buf.  Lines having special
+   formats are handled specially. */
+int
+get_line (void)
+{
+  ln++;
+  if (0 == fgets (buf, MAX_LINE_LEN, in))
+    {
+      if (ferror (in))
+       fail ("%s: fgets: %s", ifn, strerror (errno));
+      return 0;
+    }
+
+  cp = strchr (buf, '\n');
+  if (cp != NULL)
+    *cp = '\0';
+
+  cp = buf;
+  return 1;
+}
+\f
+/* Symbol table manager. */
+
+/* Symbol table entry. */
+typedef struct symbol symbol;
+struct symbol
+  {
+    symbol *next;              /* Next symbol in symbol table. */
+    char *name;                        /* Symbol name. */
+    int unique;                        /* 1=Name must be unique in this file. */
+    int ln;                    /* Line number of definition. */
+    int value;                 /* Symbol value. */
+  };
+
+/* Symbol table. */
+symbol *symtab;
+
+/* Add a symbol to the symbol table having name NAME, uniqueness
+   UNIQUE, and value VALUE.  If a symbol having the same name is found
+   in the symbol table, its sequence number is returned and the symbol
+   table is not modified.  Otherwise, the symbol is added and the next
+   available sequence number is returned. */
+int
+add_symbol (const char *name, int unique, int value)
+{
+  symbol *iter, *sym;
+  int x;
+
+  sym = xmalloc (sizeof (symbol));
+  sym->name = xstrdup (name);
+  sym->unique = unique;
+  sym->value = value;
+  sym->next = NULL;
+  sym->ln = ln;
+  if (!symtab)
+    {
+      symtab = sym;
+      return 1;
+    }
+  iter = symtab;
+  x = 1;
+  for (;;)
+    {
+      if (!strcmp (iter->name, name))
+       {
+         if (iter->unique)
+           {
+             fprintf (stderr, "%s:%d: `%s' is already defined above\n", ifn,
+                      ln, name);
+             fprintf (stderr, "%s:%d: location of previous definition\n", ifn,
+                      iter->ln);
+             hcf ();
+           }
+         free (sym->name);
+         free (sym);
+         return x;
+       }
+      if (!iter->next)
+       break;
+      iter = iter->next;
+      x++;
+    }
+  iter->next = sym;
+  return ++x;
+}
+
+/* Finds the symbol having given sequence number X within the symbol
+   table, and returns the associated symbol structure. */
+symbol *
+find_symbol (int x)
+{
+  symbol *iter;
+
+  iter = symtab;
+  while (x > 1 && iter)
+    {
+      iter = iter->next;
+      x--;
+    }
+  assert (iter);
+  return iter;
+}
+
+#if DEBUGGING
+/* Writes a printable representation of the current token to
+   stdout. */
+void
+dump_token (void)
+{
+  switch (token)
+    {
+    case T_STRING:
+      printf ("STRING\t\"%s\"\n", tokstr);
+      break;
+    case T_ID:
+      printf ("ID\t%s\n", tokstr);
+      break;
+    default:
+      printf ("PUNCT\t%c\n", token);
+    }
+}
+#endif /* DEBUGGING */
+
+/* Reads a token from the input file. */
+int
+lex_get (void)
+{
+  /* Skip whitespace and check for end of file. */
+  for (;;)
+    {
+      cp = skip_ws (cp);
+      if (*cp != '\0')
+       break;
+      
+      if (!get_line ())
+       fail ("%s: Unexpected end of file.", ifn);
+    }
+  
+  if (*cp == '_' || isalnum ((unsigned char) *cp))
+    {
+      char *dest = tokstr;
+      token = T_ID;
+      while (*cp == '_' || isalnum ((unsigned char) *cp))
+       *dest++ = toupper ((unsigned char) (*cp++));
+      *dest++ = '\0';
+    }
+  else if (*cp == '"')
+    {
+      char *dest = tokstr;
+      token = T_STRING;
+      cp++;
+      while (*cp != '"' && *cp)
+       {
+         if (*cp == '\\')
+           {
+             cp++;
+             if (!*cp)
+               error ("Unterminated string literal.");
+             *dest++ = *cp++;
+           }
+         else
+           *dest++ = *cp++;
+       }
+      *dest++ = 0;
+      if (!*cp)
+       error ("Unterminated string literal.");
+      cp++;
+    }
+  else
+    token = *cp++;
+  
+#if DEBUGGING
+  dump_token ();
+#endif
+  
+  return token;
+}
+
+/* Force the current token to be an identifier token. */
+void
+force_id (void)
+{
+  if (token != T_ID)
+    error ("Identifier expected.");
+}
+
+/* Force the current token to be a string token. */
+void
+force_string (void)
+{
+  if (token != T_STRING)
+    error ("String expected.");
+}
+
+/* Checks whether the current token is the identifier S; if so, skips
+   the token and returns 1; otherwise, returns 0. */
+int
+match_id (const char *s)
+{
+  if (token == T_ID && !strcmp (tokstr, s))
+    {
+      lex_get ();
+      return 1;
+    }
+  return 0;
+}
+
+/* Checks whether the current token is T.  If so, skips the token and
+   returns 1; otherwise, returns 0. */
+int
+match_token (int t)
+{
+  if (token == t)
+    {
+      lex_get ();
+      return 1;
+    }
+  return 0;
+}
+
+/* Force the current token to be T, and skip it. */
+void
+skip_token (int t)
+{
+  if (token != t)
+    error ("`%c' expected.", t);
+  lex_get ();
+}
+\f
+/* Structures. */
+
+/* Some specifiers have associated values. */
+enum
+  {
+    VAL_NONE,  /* No value. */
+    VAL_INT,   /* Integer value. */
+    VAL_DBL    /* Floating point value. */
+  };
+
+/* For those specifiers with values, the syntax of those values. */
+enum
+  {
+    VT_PLAIN,  /* Unadorned value. */
+    VT_PAREN   /* Value must be enclosed in parentheses. */
+  };
+
+/* Forward definition. */
+typedef struct specifier specifier;
+
+/* A single setting. */
+typedef struct setting setting;
+struct setting
+  {
+    specifier *parent; /* Owning specifier. */
+    setting *next;     /* Next in the chain. */
+    char *specname;    /* Name of the setting. */
+    int con;           /* Sequence number. */
+
+    /* Values. */
+    int valtype;       /* One of VT_*. */
+    int value;         /* One of VAL_*. */
+    int optvalue;      /* 1=value is optional, 0=value is required. */
+    char *valname;     /* Variable name for the value. */
+    char *restriction; /* !=NULL: expression specifying valid values. */
+  };
+
+/* A single specifier. */
+struct specifier
+  {
+    specifier *next;   /* Next in the chain. */
+    char *varname;     /* Variable name. */
+    setting *s;                /* Associated settings. */
+
+    setting *def;      /* Default setting. */
+    setting *omit_kw;  /* Setting for which the keyword can be omitted. */
+    
+    int index;         /* Next array index. */
+  };
+
+/* Subcommand types. */
+typedef enum
+  {
+    SBC_PLAIN,         /* The usual case. */
+    SBC_VARLIST,       /* Variable list. */
+    SBC_INT,           /* Integer value. */
+    SBC_PINT,          /* Integer inside parentheses. */
+    SBC_DBL,           /* Floating point value. */
+    SBC_INT_LIST,      /* List of integers (?). */
+    SBC_DBL_LIST,      /* List of floating points (?). */
+    SBC_CUSTOM,                /* Custom. */
+    SBC_ARRAY,         /* Array of boolean values. */
+    SBC_STRING,                /* String value. */
+    SBC_VAR            /* Single variable name. */
+  }
+subcommand_type;
+
+/* A single subcommand. */
+typedef struct subcommand subcommand;
+struct subcommand
+  {
+    subcommand *next;          /* Next in the chain. */
+    char *name;                        /* Subcommand name. */
+    subcommand_type type;      /* One of SBC_*. */
+    int once;                  /* 1=Subcommand may appear only once. */
+    int narray;                        /* Index of next array element. */
+    const char *prefix;                /* Prefix for variable and constant names. */
+    specifier *spec;           /* Array of specifiers. */
+    
+    /* SBC_STRING only. */
+    char *restriction;         /* Expression restricting string length. */
+    char *message;             /* Error message. */
+  };
+
+/* Name of the command; i.e., DESCRIPTIVES. */
+char *cmdname;
+
+/* Short prefix for the command; i.e., `dsc_'. */
+char *prefix;
+
+/* List of subcommands. */
+subcommand *subcommands;
+
+/* Default subcommand if any, or NULL. */
+subcommand *def;
+\f
+/* Parsing. */
+
+void parse_subcommands (void);
+
+/* Parse an entire specification. */
+void
+parse (void)
+{
+  /* Get the command name and prefix. */
+  if (token != T_STRING && token != T_ID)
+    error ("Command name expected.");
+  cmdname = xstrdup (tokstr);
+  lex_get ();
+  skip_token ('(');
+  force_id ();
+  prefix = xstrdup (tokstr);
+  lex_get ();
+  skip_token (')');
+  skip_token (':');
+
+  /* Read all the subcommands. */
+  subcommands = NULL;
+  def = NULL;
+  parse_subcommands ();
+}
+
+/* Parses a single setting into S, given subcommand information SBC
+   and specifier information SPEC. */
+void
+parse_setting (setting *s, specifier *spec)
+{
+  s->parent = spec;
+  
+  if (match_token ('*'))
+    {
+      if (spec->omit_kw)
+       error ("Cannot have two settings with omittable keywords.");
+      else
+       spec->omit_kw = s;
+    }
+  
+  if (match_token ('!'))
+    {
+      if (spec->def)
+       error ("Cannot have two default settings.");
+      else
+       spec->def = s;
+    }
+  
+  force_id ();
+  s->specname = xstrdup (tokstr);
+  s->con = add_symbol (s->specname, 0, 0);
+  s->value = VAL_NONE;
+
+  lex_get ();
+
+  /* Parse setting value info if necessary. */
+  if (token != '/' && token != ';' && token != '.' && token != ',')
+    {
+      if (token == '(')
+       {
+         s->valtype = VT_PAREN;
+         lex_get ();
+       }
+      else
+       s->valtype = VT_PLAIN;
+
+      s->optvalue = match_token ('*');
+      
+      if (match_id ("N"))
+       s->value = VAL_INT;
+      else if (match_id ("D"))
+       s->value = VAL_DBL;
+      else
+       error ("`n' or `d' expected.");
+      
+      skip_token (':');
+      
+      force_id ();
+      s->valname = xstrdup (tokstr);
+      lex_get ();
+      
+      if (token == ',')
+       {
+         lex_get ();
+         force_string ();
+         s->restriction = xstrdup (tokstr);
+         lex_get ();
+       }
+      else
+       s->restriction = NULL;
+      
+      if (s->valtype == VT_PAREN)
+       skip_token (')');
+    }
+}
+
+/* Parse a single specifier into SPEC, given subcommand information
+   SBC. */
+void
+parse_specifier (specifier *spec, subcommand *sbc)
+{
+  spec->index = 0;
+  spec->s = NULL;
+  spec->def = NULL;
+  spec->omit_kw = NULL;
+  spec->varname = NULL;
+
+  if (token == T_ID)
+    {
+      spec->varname = xstrdup (st_lower (tokstr));
+      lex_get ();
+    }
+  
+  /* Handle array elements. */
+  if (token != ':')
+    {
+      spec->index = sbc->narray;
+      if (sbc->type == SBC_ARRAY)
+       {
+         if (token == '|')
+           token = ',';
+         else
+           sbc->narray++;
+       }
+      spec->s = NULL;
+      return;
+    }
+  skip_token (':');
+  
+  /* Parse all the settings. */
+  {
+    setting **s = &spec->s;
+    
+    for (;;)
+      {
+       *s = xmalloc (sizeof (setting));
+       parse_setting (*s, spec);
+       if (token == ',' || token == ';' || token == '.')
+         break;
+       skip_token ('/');
+       s = &(*s)->next;
+      }
+    (*s)->next = NULL;
+  }
+}
+
+/* Parse a list of specifiers for subcommand SBC. */
+void
+parse_specifiers (subcommand *sbc)
+{
+  specifier **spec = &sbc->spec;
+
+  if (token == ';' || token == '.')
+    {
+      *spec = NULL;
+      return;
+    }
+  
+  for (;;)
+    {
+      *spec = xmalloc (sizeof (specifier));
+      parse_specifier (*spec, sbc);
+      if (token == ';' || token == '.')
+       break;
+      skip_token (',');
+      spec = &(*spec)->next;
+    }
+  (*spec)->next = NULL;
+}
+
+/* Parse a subcommand into SBC. */
+void
+parse_subcommand (subcommand *sbc)
+{
+  if (match_token ('*'))
+    {
+      if (def)
+       error ("Multiple default subcommands.");
+      def = sbc;
+    }
+
+  sbc->once = match_token ('+');
+
+  force_id ();
+  sbc->name = xstrdup (tokstr);
+  lex_get ();
+  
+  sbc->narray = 0;
+  sbc->type = SBC_PLAIN;
+  sbc->spec = NULL;
+
+  if (match_token ('['))
+    {
+      force_id ();
+      sbc->prefix = xstrdup (st_lower (tokstr));
+      lex_get ();
+      
+      skip_token (']');
+      skip_token ('=');
+      
+      sbc->type = SBC_ARRAY;
+      parse_specifiers (sbc);
+    }
+  else
+    {
+      if (match_token ('('))
+       {
+         force_id ();
+         sbc->prefix = xstrdup (st_lower (tokstr));
+         lex_get ();
+         
+         skip_token (')');
+       }
+      else
+       sbc->prefix = "";
+      
+      skip_token ('=');
+
+      if (match_id ("VAR"))
+       sbc->type = SBC_VAR;
+      if (match_id ("VARLIST"))
+       {
+         if (match_token ('('))
+           {
+             force_string ();
+             sbc->message = xstrdup (tokstr);
+             lex_get();
+             
+             skip_token (')');
+           }
+         else sbc->message = NULL;
+
+         sbc->type = SBC_VARLIST;
+       }
+      else if (match_id ("INTEGER"))
+       sbc->type = match_id ("LIST") ? SBC_INT_LIST : SBC_INT;
+      else if (match_id ("PINT"))
+       sbc->type = SBC_PINT;
+      else if (match_id ("DOUBLE"))
+       sbc->type = match_id ("LIST") ? SBC_DBL_LIST : SBC_DBL;
+      else if (match_id ("STRING"))
+       {
+         sbc->type = SBC_STRING;
+         if (token == T_STRING)
+           {
+             sbc->restriction = xstrdup (tokstr);
+             lex_get ();
+             force_string ();
+             sbc->message = xstrdup (tokstr);
+             lex_get ();
+           }
+         else
+           sbc->restriction = NULL;
+       }
+      else if (match_id ("CUSTOM"))
+       sbc->type = SBC_CUSTOM;
+      else
+       parse_specifiers (sbc);
+    }
+}
+
+/* Parse all the subcommands. */
+void
+parse_subcommands (void)
+{
+  subcommand **sbc = &subcommands;
+  
+  for (;;)
+    {
+      *sbc = xmalloc (sizeof (subcommand));
+      (*sbc)->next = NULL;
+
+      parse_subcommand (*sbc);
+
+      if (token == '.')
+       return;
+
+      skip_token (';');
+      sbc = &(*sbc)->next;
+    }
+}
+\f
+/* Output. */
+
+#define BASE_INDENT 2          /* Starting indent. */
+#define INC_INDENT 2           /* Indent increment. */
+
+/* Increment the indent. */
+#define indent() indent += INC_INDENT
+#define outdent() indent -= INC_INDENT
+
+/* Size of the indent from the left margin. */
+int indent;
+
+#if __GNUC__ >= 2
+void dump (int, const char *, ...) __attribute__ ((format (printf, 2, 3)));
+#endif
+
+/* Write line FORMAT to the output file, formatted as with printf,
+   indented `indent' characters from the left margin.  If INDENTION is
+   greater than 0, indents BASE_INDENT * INDENTION characters after
+   writing the line; if INDENTION is less than 0, dedents BASE_INDENT
+   * INDENTION characters _before_ writing the line. */
+void
+dump (int indention, const char *format, ...)
+{
+  va_list args;
+  int i;
+
+  if (indention < 0)
+    indent += BASE_INDENT * indention;
+  
+  oln++;
+  va_start (args, format);
+  for (i = 0; i < indent; i++)
+    putc (' ', out);
+  vfprintf (out, format, args);
+  putc ('\n', out);
+  va_end (args);
+
+  if (indention > 0)
+    indent += BASE_INDENT * indention;
+}
+
+/* Write the structure members for specifier SPEC to the output file.
+   SBC is the including subcommand. */
+void
+dump_specifier_vars (const specifier *spec, const subcommand *sbc)
+{
+  if (spec->varname)
+    dump (0, "long %s%s;", sbc->prefix, spec->varname);
+  
+  {
+    setting *s;
+
+    for (s = spec->s; s; s = s->next)
+      {
+       if (s->value != VAL_NONE)
+         {
+           const char *typename;
+
+           assert (s->value == VAL_INT || s->value == VAL_DBL);
+           typename = s->value == VAL_INT ? "long" : "double";
+
+           dump (0, "%s %s%s;", typename, sbc->prefix, st_lower (s->valname));
+         }
+      }
+  }
+}
+
+/* Returns 1 if string T is a PSPP keyword, 0 otherwise. */
+int
+is_keyword (const char *t)
+{
+  static const char *kw[] =
+    {
+      "AND", "OR", "NOT", "EQ", "GE", "GT", "LE", "LT",
+      "NE", "ALL", "BY", "TO", "WITH", 0,
+    };
+  const char **cp;
+
+  for (cp = kw; *cp; cp++)
+    if (!strcmp (t, *cp))
+      return 1;
+  return 0;
+}
+
+/* Transforms a string NAME into a valid C identifier: makes
+   everything lowercase and maps nonalphabetic characters to
+   underscores.  Returns a pointer to a static buffer. */
+char *
+make_identifier (const char *name)
+{
+  char *p = get_buffer ();
+  char *cp;
+
+  for (cp = p; *name; name++)
+    if (isalpha ((unsigned char) *name))
+      *cp++ = tolower ((unsigned char) (*name));
+    else
+      *cp++ = '_';
+  *cp = '\0';
+  
+  return p;
+}
+
+/* Writes the struct and enum declarations for the parser. */
+void
+dump_declarations (void)
+{
+  indent = 0;
+
+  /* Write out enums for all the identifiers in the symbol table. */
+  {
+    int f, k;
+    symbol *sym;
+    char *buf = NULL;
+
+    /* Note the squirmings necessary to make sure that the last enum
+       is not followed by a comma, as mandated by ANSI C89. */
+    for (sym = symtab, f = k = 0; sym; sym = sym->next)
+      if (!sym->unique && !is_keyword (sym->name))
+       {
+         if (!f)
+           {
+             dump (0, "/* Settings for subcommand specifiers. */");
+             dump (1, "enum");
+             dump (1, "{");
+             f = 1;
+           }
+
+         if (buf == NULL)
+           buf = xmalloc (1024);
+         else
+           dump (0, buf);
+         
+         if (k)
+           sprintf (buf, "%s%s,", st_upper (prefix), sym->name);
+         else
+           {
+             k = 1;
+             sprintf (buf, "%s%s = 1000,", st_upper (prefix), sym->name);
+           }
+       }
+    if (buf)
+      {
+       buf[strlen (buf) - 1] = 0;
+       dump (0, buf);
+       free (buf);
+      }
+    if (f)
+      {
+       dump (-1, "};");
+       dump (-1, nullstr);
+      }
+  }
+
+  /* For every array subcommand, write out the associated enumerated
+     values. */
+  {
+    subcommand *sbc;
+
+    for (sbc = subcommands; sbc; sbc = sbc->next)
+      if (sbc->type == SBC_ARRAY && sbc->narray)
+       {
+         dump (0, "/* Array indices for %s subcommand. */", sbc->name);
+         
+         dump (1, "enum");
+         dump (1, "{");
+
+         {
+           specifier *spec;
+
+           for (spec = sbc->spec; spec; spec = spec->next)
+             if (!spec->s)
+               dump (0, "%s%s%s = %d,",
+                     st_upper (prefix), st_upper (sbc->prefix),
+                     st_upper (spec->varname), spec->index);
+
+           dump (0, "%s%scount", st_upper (prefix), st_upper (sbc->prefix));
+
+           dump (-1, "};");
+           dump (-1, nullstr);
+         }
+       }
+  }
+
+  /* Write out structure declaration. */
+  {
+    subcommand *sbc;
+
+    dump (0, "/* %s structure. */", cmdname);
+    dump (1, "struct cmd_%s", make_identifier (cmdname));
+    dump (1, "{");
+    for (sbc = subcommands; sbc; sbc = sbc->next)
+      {
+       int f = 0;
+
+       if (sbc != subcommands)
+         dump (0, nullstr);
+       
+       dump (0, "/* %s subcommand. */", sbc->name);
+       dump (0, "int sbc_%s;", st_lower (sbc->name));
+
+       switch (sbc->type)
+         {
+         case SBC_ARRAY:
+         case SBC_PLAIN:
+           {
+             specifier *spec;
+           
+             for (spec = sbc->spec; spec; spec = spec->next)
+               {
+                 if (spec->s == 0)
+                   {
+                     if (sbc->type == SBC_PLAIN)
+                       dump (0, "long int %s%s;", st_lower (sbc->prefix),
+                             spec->varname);
+                     else if (f == 0)
+                       {
+                         dump (0, "int a_%s[%d];", 
+                               st_lower (sbc->name), sbc->narray);
+                         f = 1;
+                       }
+                   }
+                 else
+                   dump_specifier_vars (spec, sbc);
+               }
+           }
+           break;
+
+         case SBC_VARLIST:
+           dump (0, "int %sn_%s;", st_lower (sbc->prefix),
+                 st_lower (sbc->name));
+           dump (0, "struct variable **%sv_%s;", st_lower (sbc->prefix),
+                 st_lower (sbc->name));
+           break;
+
+         case SBC_VAR:
+           dump (0, "struct variable *%sv_%s;", st_lower (sbc->prefix),
+                 st_lower (sbc->name));
+           break;
+
+         case SBC_STRING:
+           dump (0, "char *s_%s;", st_lower (sbc->name));
+           break;
+
+         case SBC_INT:
+         case SBC_PINT:
+           dump (0, "long n_%s;", st_lower (sbc->name));
+           break;
+
+         default:
+           /* nothing */
+         }
+      }
+
+    dump (-1, "};");
+    dump (-1, nullstr);
+  }
+
+  /* Write out prototypes for custom_*() functions as necessary. */
+  {
+    int seen = 0;
+    subcommand *sbc;
+
+    for (sbc = subcommands; sbc; sbc = sbc->next)
+      if (sbc->type == SBC_CUSTOM)
+       {
+         if (!seen)
+           {
+             seen = 1;
+             dump (0, "/* Prototype for custom subcommands of %s. */",
+                   cmdname);
+           }
+         dump (0, "static int %scustom_%s (struct cmd_%s *);",
+               st_lower (prefix), st_lower (sbc->name),
+               make_identifier (cmdname));
+       }
+
+    if (seen)
+      dump (0, nullstr);
+  }
+
+  /* Prototypes for parsing and freeing functions. */
+  {
+    dump (0, "/* Command parsing functions. */");
+    dump (0, "static int parse_%s (struct cmd_%s *);",
+         make_identifier (cmdname), make_identifier (cmdname));
+    dump (0, "static void free_%s (struct cmd_%s *);",
+         make_identifier (cmdname), make_identifier (cmdname));
+    dump (0, nullstr);
+  }
+}
+
+/* Writes out code to initialize all the variables that need
+   initialization for particular specifier SPEC inside subcommand SBC. */
+void
+dump_specifier_init (const specifier *spec, const subcommand *sbc)
+{
+  if (spec->varname)
+    {
+      char s[256];
+
+      if (spec->def)
+       sprintf (s, "%s%s",
+                st_upper (prefix), find_symbol (spec->def->con)->name);
+      else
+       strcpy (s, "-1");
+      dump (0, "p->%s%s = %s;", sbc->prefix, spec->varname, s);
+    }
+  
+  {
+    setting *s;
+
+    for (s = spec->s; s; s = s->next)
+      {
+       if (s->value != VAL_NONE)
+         {
+           const char *init;
+
+           assert (s->value == VAL_INT || s->value == VAL_DBL);
+           init = s->value == VAL_INT ? "NOT_LONG" : "SYSMIS";
+
+           dump (0, "p->%s%s = %s;", sbc->prefix, st_lower (s->valname), init);
+         }
+      }
+  }
+}
+
+/* Write code to initialize all variables. */
+void
+dump_vars_init (void)
+{
+  /* Loop through all the subcommands. */
+  {
+    subcommand *sbc;
+
+    for (sbc = subcommands; sbc; sbc = sbc->next)
+      {
+       int f = 0;
+       
+       dump (0, "p->sbc_%s = 0;", st_lower (sbc->name));
+       switch (sbc->type)
+         {
+         case SBC_DBL:
+         case SBC_INT_LIST:
+         case SBC_DBL_LIST:
+         case SBC_CUSTOM:
+           /* nothing */
+           break;
+           
+         case SBC_PLAIN:
+         case SBC_ARRAY:
+           {
+             specifier *spec;
+           
+             for (spec = sbc->spec; spec; spec = spec->next)
+               if (spec->s == NULL)
+                 {
+                   if (sbc->type == SBC_PLAIN)
+                     dump (0, "p->%s%s = 0;", sbc->prefix, spec->varname);
+                   else if (f == 0)
+                     {
+                       dump (0, "memset (p->a_%s, 0, sizeof p->a_%s);",
+                             st_lower (sbc->name), st_lower (sbc->name));
+                       f = 1;
+                     }
+                 }
+               else
+                 dump_specifier_init (spec, sbc);
+           }
+           break;
+
+         case SBC_VARLIST:
+           dump (0, "p->%sn_%s = 0;",
+                 st_lower (sbc->prefix), st_lower (sbc->name));
+           dump (0, "p->%sv_%s = NULL;",
+                 st_lower (sbc->prefix), st_lower (sbc->name));
+           break;
+           
+         case SBC_VAR:
+           dump (0, "p->%sv_%s = NULL;",
+                 st_lower (sbc->prefix), st_lower (sbc->name));
+           break;
+
+         case SBC_STRING:
+           dump (0, "p->s_%s = NULL;", st_lower (sbc->name));
+           break;
+
+         case SBC_INT:
+         case SBC_PINT:
+           dump (0, "p->n_%s = NOT_LONG;", st_lower (sbc->name));
+           break;
+
+         default:
+           assert (0);
+         }
+      }
+  }
+}
+
+/* Return a pointer to a static buffer containing an expression that
+   will match token T. */
+char *
+make_match (const char *t)
+{
+  char *s;
+
+  s = get_buffer ();
+
+  while (*t == '_')
+    t++;
+      
+  if (is_keyword (t))
+    sprintf (s, "lex_match (T_%s)", t);
+  else if (!strcmp (t, "ON") || !strcmp (t, "YES"))
+    strcpy (s, "(lex_match_id (\"ON\") || lex_match_id (\"YES\") "
+           "|| lex_match_id (\"TRUE\"))");
+  else if (!strcmp (t, "OFF") || !strcmp (t, "NO"))
+    strcpy (s, "(lex_match_id (\"OFF\") || lex_match_id (\"NO\") "
+           "|| lex_match_id (\"FALSE\"))");
+  else if (isdigit ((unsigned char) t[0]))
+    sprintf (s, "lex_match_int (%s)", t);
+  else
+    sprintf (s, "lex_match_id (\"%s\")", t);
+  
+  return s;
+}
+
+/* Write out the parsing code for specifier SPEC within subcommand
+   SBC. */
+void
+dump_specifier_parse (const specifier *spec, const subcommand *sbc)
+{
+  setting *s;
+
+  if (spec->omit_kw && spec->omit_kw->next)
+    error ("Omittable setting is not last setting in `%s' specifier.",
+          spec->varname);
+  if (spec->omit_kw && spec->omit_kw->parent->next)
+    error ("Default specifier is not in last specifier in `%s' "
+          "subcommand.", sbc->name);
+  
+  for (s = spec->s; s; s = s->next)
+    {
+      int first = spec == sbc->spec && s == spec->s;
+
+      /* Match the setting's keyword. */
+      if (spec->omit_kw == s)
+       {
+         if (!first)
+           {
+             dump (1, "else");
+             dump (1, "{");
+           }
+         dump (1, "%s;", make_match (s->specname));
+       }
+      else
+       dump (1, "%sif (%s)", first ? "" : "else ",
+             make_match (s->specname));
+
+      /* Handle values. */
+      if (s->value == VAL_NONE)
+       dump (0, "p->%s%s = %s%s;", sbc->prefix, spec->varname,
+             st_upper (prefix), find_symbol (s->con)->name);
+      else
+       {
+         if (spec->omit_kw != s)
+           dump (1, "{");
+         
+         if (spec->varname)
+           dump (0, "p->%s%s = %s%s;", sbc->prefix, spec->varname,
+                 st_upper (prefix), find_symbol (s->con)->name);
+         
+         if (s->valtype == VT_PAREN)
+           {
+             if (s->optvalue)
+               {
+                 dump (1, "if (lex_match ('('))");
+                 dump (1, "{");
+               }
+             else
+               {
+                 dump (1, "if (!lex_match ('('))");
+                 dump (1, "{");
+                 dump (0, "msg (SE, _(\"`(' expected after %s "
+                       "specifier of %s subcommand.\"));",
+                       s->specname, sbc->name);
+                 dump (0, "goto lossage;");
+                 dump (-1, "}");
+                 outdent ();
+               }
+           }
+
+         if (s->value == VAL_INT)
+           {
+             dump (1, "if (!lex_integer_p ())");
+             dump (1, "{");
+             dump (0, "msg (SE, _(\"%s specifier of %s subcommand "
+                   "requires an integer argument.\"));",
+                   s->specname, sbc->name);
+             dump (0, "goto lossage;");
+             dump (-1, "}");
+             dump (-1, "p->%s%s = lex_integer ();",
+                   sbc->prefix, st_lower (s->valname));
+           }
+         else
+           {
+             dump (1, "if (token != T_NUM)");
+             dump (1, "{");
+             dump (0, "msg (SE, _(\"Number expected after %s "
+                   "specifier of %s subcommand.\"));",
+                   s->specname, sbc->name);
+             dump (0, "goto lossage;");
+             dump (-1, "}");
+             dump (-1, "p->%s%s = tokval;", sbc->prefix,
+                   st_lower (s->valname));
+           }
+         
+         if (s->restriction)
+           {
+             {
+               char *str, *str2;
+               str = xmalloc (MAX_TOK_LEN);
+               str2 = xmalloc (MAX_TOK_LEN);
+               sprintf (str2, "p->%s%s", sbc->prefix, st_lower (s->valname));
+               sprintf (str, s->restriction, str2, str2, str2, str2,
+                        str2, str2, str2, str2);
+               dump (1, "if (!(%s))", str);
+               free (str);
+               free (str2);
+             }
+             
+             dump (1, "{");
+             dump (0, "msg (SE, _(\"Bad argument for %s "
+                   "specifier of %s subcommand.\"));",
+                   s->specname, sbc->name);
+             dump (0, "goto lossage;");
+             dump (-1, "}");
+             outdent ();
+           }
+         
+         dump (0, "lex_get ();");
+         
+         if (s->valtype == VT_PAREN)
+           {
+             dump (1, "if (!lex_match (')'))");
+             dump (1, "{");
+             dump (0, "msg (SE, _(\"`)' expected after argument for "
+                   "%s specifier of %s.\"));",
+                   s->specname, sbc->name);
+             dump (0, "goto lossage;");
+             dump (-1, "}");
+             outdent ();
+             if (s->optvalue)
+               {
+                 dump (-1, "}");
+                 outdent ();
+               }
+           }
+         
+         if (s != spec->omit_kw)
+           dump (-1, "}");
+       }
+      
+      if (s == spec->omit_kw)
+       {
+         dump (-1, "}");
+         outdent ();
+       }
+      outdent ();
+    }
+}
+
+/* Write out the code to parse subcommand SBC. */
+void
+dump_subcommand (const subcommand *sbc)
+{
+  if (sbc->type == SBC_PLAIN || sbc->type == SBC_ARRAY)
+    {
+      int count;
+
+      dump (1, "while (token != '/' && token != '.')");
+      dump (1, "{");
+      
+      {
+       specifier *spec;
+
+       for (count = 0, spec = sbc->spec; spec; spec = spec->next)
+         {
+           if (spec->s)
+             dump_specifier_parse (spec, sbc);
+           else
+             {
+               count++;
+               dump (1, "%sif (%s)", spec != sbc->spec ? "else " : "",
+                     make_match (st_upper (spec->varname)));
+               if (sbc->type == SBC_PLAIN)
+                 dump (0, "p->%s%s = 1;", st_lower (sbc->prefix),
+                       spec->varname);
+               else
+                 dump (0, "p->a_%s[%s%s%s] = 1;",
+                       st_lower (sbc->name),
+                       st_upper (prefix), st_upper (sbc->prefix),
+                       st_upper (spec->varname));
+               outdent ();
+             }
+         }
+      }
+      
+      {
+       specifier *spec;
+       setting *s;
+
+       /* This code first finds the last specifier in sbc.  Then it
+          finds the last setting within that last specifier.  Either
+          or both might be NULL. */
+       spec = sbc->spec;
+       s = NULL;
+       if (spec)
+         {
+           while (spec->next)
+             spec = spec->next;
+           s = spec->s;
+           if (s)
+             while (s->next)
+               s = s->next;
+         }
+
+       if (spec && (!spec->s || !spec->omit_kw))
+         {
+           dump (1, "else");
+           dump (1, "{");
+           dump (0, "lex_error (NULL);");
+           dump (0, "goto lossage;");
+           dump (-1, "}");
+           outdent ();
+         }
+      }
+
+      dump (0, "lex_match (',');");
+      dump (-1, "}");
+      outdent ();
+    }
+  else if (sbc->type == SBC_VARLIST)
+    {
+      dump (1, "if (!parse_variables (NULL, &p->%sv_%s, &p->%sn_%s, "
+           "PV_APPEND%s%s))",
+           st_lower (sbc->prefix), st_lower (sbc->name),
+           st_lower (sbc->prefix), st_lower (sbc->name),
+           sbc->message ? " |" : "",
+           sbc->message ? sbc->message : "");
+      dump (0, "goto lossage;");
+      outdent ();
+    }
+  else if (sbc->type == SBC_VAR)
+    {
+      dump (0, "p->%sv_%s = parse_variable ();",
+           st_lower (sbc->prefix), st_lower (sbc->name));
+      dump (1, "if (p->%sv_%s)",
+           st_lower (sbc->prefix), st_lower (sbc->name));
+      dump (0, "goto lossage;");
+      outdent ();
+    }
+  else if (sbc->type == SBC_STRING)
+    {
+      if (sbc->restriction)
+       {
+         dump (1, "{");
+         dump (0, "int x;");
+       }
+      dump (1, "if (!lex_force_string ())");
+      dump (0, "return 0;");
+      outdent ();
+      if (sbc->restriction)
+       {
+         dump (0, "x = ds_length (&tokstr);");
+         dump (1, "if (!(%s))", sbc->restriction);
+         dump (1, "{");
+         dump (0, "msg (SE, _(\"String for %s must be %s.\"));",
+               sbc->name, sbc->message);
+         dump (0, "goto lossage;");
+         dump (-1, "}");
+         outdent ();
+       }
+      dump (0, "p->s_%s = xstrdup (ds_value (&tokstr));",
+           st_lower (sbc->name));
+      dump (0, "lex_get ();");
+      if (sbc->restriction)
+       dump (-1, "}");
+    }
+  else if (sbc->type == SBC_INT)
+    {
+      dump (1, "if (!lex_force_int ())");
+      dump (0, "goto lossage;");
+      dump (-1, "p->n_%s = lex_integer ();", st_lower (sbc->name));
+    }
+  else if (sbc->type == SBC_PINT)
+    {
+      dump (0, "lex_match ('(');");
+      dump (1, "if (!lex_force_int ())");
+      dump (0, "goto lossage;");
+      dump (-1, "p->n_%s = lex_integer ();", st_lower (sbc->name));
+      dump (0, "lex_match (')');");
+    }
+  else if (sbc->type == SBC_CUSTOM)
+    {
+      dump (1, "switch (%scustom_%s (p))",
+           st_lower (prefix), st_lower (sbc->name));
+      dump (0, "{");
+      dump (1, "case 0:");
+      dump (0, "goto lossage;");
+      dump (-1, "case 1:");
+      indent ();
+      dump (0, "break;");
+      dump (-1, "case 2:");
+      indent ();
+      dump (0, "lex_error (NULL);");
+      dump (0, "goto lossage;");
+      dump (-1, "default:");
+      indent ();
+      dump (0, "assert (0);");
+      dump (-1, "}");
+      outdent ();
+    }
+}
+
+/* Write out entire parser. */
+void
+dump_parser (void)
+{
+  int f;
+
+  indent = 0;
+
+  dump (0, "static int");
+  dump (0, "parse_%s (struct cmd_%s *p)", make_identifier (cmdname),
+       make_identifier (cmdname));
+  dump (1, "{");
+
+  dump_vars_init ();
+
+  dump (1, "for (;;)");
+  dump (1, "{");
+
+  f = 0;
+  if (def && (def->type == SBC_VARLIST))
+    {
+      if (def->type == SBC_VARLIST)
+       dump (1, "if (token == T_ID && is_varname (tokid) && "
+             "lex_look_ahead () != '=')");
+      else
+       {
+         dump (0, "if ((token == T_ID && is_varname (tokid) && "
+               "lex_look_ahead () != '=')");
+         dump (1, "     || token == T_ALL)");
+       }
+      dump (1, "{");
+      dump (0, "p->sbc_%s++;", st_lower (def->name));
+      dump (1, "if (!parse_variables (NULL, &p->%sv_%s, &p->%sn_%s, "
+           "PV_APPEND))",
+           st_lower (def->prefix), st_lower (def->name),
+           st_lower (def->prefix), st_lower (def->name));
+      dump (0, "goto lossage;");
+      dump (-2, "}");
+      outdent ();
+      f = 1;
+    }
+  else if (def && def->type == SBC_CUSTOM)
+    {
+      dump (1, "switch (%scustom_%s (p))",
+           st_lower (prefix), st_lower (def->name));
+      dump (0, "{");
+      dump (1, "case 0:");
+      dump (0, "goto lossage;");
+      dump (-1, "case 1:");
+      indent ();
+      dump (0, "p->sbc_%s++;", st_lower (def->name));
+      dump (0, "continue;");
+      dump (-1, "case 2:");
+      indent ();
+      dump (0, "break;");
+      dump (-1, "default:");
+      indent ();
+      dump (0, "assert (0);");
+      dump (-1, "}");
+      outdent ();
+    }
+  
+  {
+    subcommand *sbc;
+
+    for (sbc = subcommands; sbc; sbc = sbc->next)
+      {
+       dump (1, "%sif (%s)", f ? "else " : "", make_match (sbc->name));
+       f = 1;
+       dump (1, "{");
+
+       dump (0, "lex_match ('=');");
+       dump (0, "p->sbc_%s++;", st_lower (sbc->name));
+       if (sbc->once)
+         {
+           dump (1, "if (p->sbc_%s > 1)", st_lower (sbc->name));
+           dump (1, "{");
+           dump (0, "msg (SE, _(\"%s subcommand may be given only once.\"));",
+                 sbc->name);
+           dump (0, "goto lossage;");
+           dump (-1, "}");
+           outdent ();
+         }
+       dump_subcommand (sbc);
+       dump (-1, "}");
+       outdent ();
+      }
+  }
+  
+  dump (1, "if (!lex_match ('/'))");
+  dump (0, "break;");
+  dump (-2, "}");
+  outdent ();
+  dump (0, nullstr);
+  dump (1, "if (token != '.')");
+  dump (1, "{");
+  dump (0, "lex_error (_(\"expecting end of command\"));");
+  dump (0, "goto lossage;");
+  dump (-1, "}");
+  dump (0, nullstr);
+  dump (-1, "return 1;");
+  dump (0, nullstr);
+  dump (-1, "lossage:");
+  indent ();
+  dump (0, "free_%s (p);", make_identifier (cmdname));
+  dump (0, "return 0;");
+  dump (-1, "}");
+  dump (0, nullstr);
+}
+
+/* Write the output file header. */
+void
+dump_header (void)
+{
+  time_t curtime;
+  struct tm *loctime;
+  char *timep;
+
+  indent = 0;
+  curtime = time (NULL);
+  loctime = localtime (&curtime);
+  timep = asctime (loctime);
+  timep[strlen (timep) - 1] = 0;
+  dump (0,   "/* %s", ofn);
+  dump (0, nullstr);
+  dump (0, "   Generated by q2c from %s on %s.", ifn, timep);
+  dump (0, "   Do not modify!");
+  dump (0, " */");
+  dump (0, nullstr);
+}
+
+/* Write out commands to free variable state. */
+void
+dump_free (void)
+{
+  subcommand *sbc;
+  int used;
+
+  indent = 0;
+
+  used = 0;
+  for (sbc = subcommands; sbc; sbc = sbc->next)
+    if (sbc->type == SBC_STRING)
+      used = 1;
+
+  dump (0, "static void");
+  dump (0, "free_%s (struct cmd_%s *p%s)", make_identifier (cmdname),
+       make_identifier (cmdname), used ? "" : " unused");
+  dump (1, "{");
+
+  for (sbc = subcommands; sbc; sbc = sbc->next)
+    if (sbc->type == SBC_STRING)
+      dump (0, "free (p->s_%s);", st_lower (sbc->name));
+
+  dump (-1, "}");
+}
+
+/* Returns the name of a directive found on the current input line, if
+   any, or a null pointer if none found. */
+const char *
+recognize_directive (void)
+{
+  static char directive[16];
+  char *sp, *ep;
+  
+  sp = skip_ws (buf);
+  if (strncmp (sp, "/*", 2))
+    return NULL;
+  sp = skip_ws (sp + 2);
+  if (*sp != '(')
+    return NULL;
+  sp++;
+
+  ep = strchr (sp, ')');
+  if (ep == NULL)
+    return NULL;
+
+  if (ep - sp > 15)
+    ep = sp + 15;
+  memcpy (directive, sp, ep - sp);
+  directive[ep - sp] = '\0';
+  return directive;
+}
+  
+int
+main (int argc, char *argv[])
+{
+  pgmname = argv[0];
+  if (argc != 3)
+    fail ("Syntax: q2c input.q output.c");
+
+  ifn = argv[1];
+  in = fopen (ifn, "r");
+  if (!in)
+    fail ("%s: open: %s.", ifn, strerror (errno));
+
+  ofn = argv[2];
+  out = fopen (ofn, "w");
+  if (!out)
+    fail ("%s: open: %s.", ofn, strerror (errno));
+
+  is_open = 1;
+  buf = xmalloc (MAX_LINE_LEN);
+  tokstr = xmalloc (MAX_TOK_LEN);
+
+  dump_header ();
+
+  indent = 0;
+  dump (0, "#line %d \"%s\"", ln + 1, ifn);
+  while (get_line ())
+    {
+      const char *directive = recognize_directive ();
+      if (directive == NULL)
+       {
+         dump (0, "%s", buf);
+         continue;
+       }
+      
+      dump (0, "#line %d \"%s\"", oln - 1, ofn);
+      if (!strcmp (directive, "specification"))
+       {
+         /* Skip leading slash-star line. */
+         get_line ();
+         lex_get ();
+
+         parse ();
+
+         /* Skip trailing star-slash line. */
+         get_line ();
+       }
+      else if (!strcmp (directive, "headers"))
+       {
+         indent = 0;
+
+         dump (0, "#include <assert.h>");
+         dump (0, "#include <stdlib.h>");
+         dump (0, "#include \"alloc.h\"");
+         dump (0, "#include \"error.h\"");
+         dump (0, "#include \"lexer.h\"");
+         dump (0, "#include \"str.h\"");
+         dump (0, "#include \"var.h\"");
+         dump (0, nullstr);
+       }
+      else if (!strcmp (directive, "declarations"))
+       dump_declarations ();
+      else if (!strcmp (directive, "functions"))
+       {
+         dump_parser ();
+         dump_free ();
+       }
+      else
+       error ("unknown directive `%s'", directive);
+      indent = 0;
+      dump (0, "#line %d \"%s\"", ln + 1, ifn);
+    }
+
+  return EXIT_SUCCESS;
+}
+
diff --git a/src/random.c b/src/random.c
new file mode 100644 (file)
index 0000000..7f63f47
--- /dev/null
@@ -0,0 +1,149 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <math.h>
+#include <stdlib.h>
+#include <time.h>
+#include "magic.h"
+#include "random.h"
+#include "settings.h"
+
+/* Deal with broken system random number generator. */
+#if HAVE_GOOD_RANDOM
+#define real_rand rand
+#define real_srand srand
+#define REAL_RAND_MAX RAND_MAX
+#else /* !HAVE_GOOD_RANDOM */
+#define REAL_RAND_MAX 32767
+
+/* Some systems are so broken that they do not supply a value for
+   RAND_MAX.  There is absolutely no reliable way to determine this
+   value, either.  So we must supply our own.  This one is the one
+   presented in the ANSI C standard as strictly compliant. */
+static unsigned long int next = 1;
+
+int
+real_rand (void)
+{
+  next = next * 1103515245 + 12345;
+  return (unsigned int)(next / 65536) % 32768;
+}
+
+void
+real_srand (unsigned int seed)
+{
+  next = seed;
+}
+#endif /* !HAVE_GOOD_RANDOM */
+
+/* The random number generator here is an implementation in C of
+   Knuth's Algorithm 3.2.2B (Randomizing by Shuffling) in _The Art of
+   Computer Programming_, Vol. 2. */
+
+#define k 13
+static int V[k];
+static int Y;
+
+static double X2;
+
+/* Initializes the random number generator.  Should be called once by
+   every cmd_*() that uses random numbers.  Note that this includes
+   all procedures that use expressions since they may generate random
+   numbers. */
+void
+setup_randomize (void)
+{
+  static time_t curtime;
+  int i;
+
+  if (set_seed == NOT_LONG)
+    {
+      if (!curtime)
+       time (&curtime);
+      real_srand (curtime++);
+    }
+  else
+    real_srand (set_seed);
+
+  set_seed_used = 1;
+
+  for (i = 0; i < k; i++)
+    V[i] = real_rand ();
+  Y = real_rand ();
+  X2 = NOT_DOUBLE;
+}
+
+/* Standard shuffling procedure for increasing randomness of the ANSI
+   C random number generator. Returns a random number R where 0 <= R
+   <= RAND_MAX. */
+inline int
+shuffle (void)
+{
+  int j = k * Y / RAND_MAX;
+  Y = V[j];
+  V[j] = real_rand ();
+  return Y;
+}
+
+/* Returns a random number R where 0 <= R <= X. */
+double 
+rand_uniform (double x)
+{
+  return ((double) shuffle ()) / (((double) RAND_MAX) / x);
+}
+
+/* Returns a random number from the distribution with mean 0 and
+   standard deviation X.  This uses algorithm P in section 3.4.1C of
+   Knuth's _Art of Computer Programming_, Vol 2. */
+double 
+rand_normal (double x)
+{
+  double U1, U2;
+  double V1, V2;
+  double S;
+  double X1;
+
+  if (X2 != NOT_DOUBLE)
+    {
+      double t = X2;
+      X2 = NOT_DOUBLE;
+      return t * x;
+    }
+  do
+    {
+      U1 = ((double) shuffle ()) / RAND_MAX;
+      U2 = ((double) shuffle ()) / RAND_MAX;
+      V1 = 2 * U1 - 1;
+      V2 = 2 * U2 - 1;
+      S = V1 * V1 + V2 * V2;
+    }
+  while (S >= 1);
+  X1 = V1 * sqrt (-2. * log (S) / S);
+  X2 = V2 * sqrt (-2. * log (S) / S);
+  return X1 * x;
+}
+
+/* Returns a random integer R, where 0 <= R < X. */
+int
+rand_simple (int x)
+{
+  return shuffle () % x;
+}
+
diff --git a/src/random.h b/src/random.h
new file mode 100644 (file)
index 0000000..b76f2e4
--- /dev/null
@@ -0,0 +1,28 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !random_h
+#define random_h 1
+
+void setup_randomize (void);
+double rand_uniform (double x);
+double rand_normal (double x);
+int rand_simple (int x);
+
+#endif /* random.h */
diff --git a/src/recode.c b/src/recode.c
new file mode 100644 (file)
index 0000000..d799a38
--- /dev/null
@@ -0,0 +1,1121 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "approx.h"
+#include "cases.h"
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "magic.h"
+#include "str.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1 */
+#include "debug-print.h"
+\f
+/* Definitions. */
+
+enum
+  {
+    RCD_END,                   /* sentinel value */
+    RCD_USER,                  /* user-missing => one */
+    RCD_SINGLE,                        /* one => one */
+    RCD_HIGH,                  /* x > a => one */
+    RCD_LOW,                   /* x < b => one */
+    RCD_RANGE,                 /* b < x < a => one */
+    RCD_ELSE,                  /* any but SYSMIS => one */
+    RCD_CONVERT                        /* "123" => 123 */
+  };
+
+/* Describes how to recode a single value or range of values into a
+   single value.  */
+struct coding
+  {
+    int type;                  /* RCD_* */
+    union value f1, f2;                /* Describe value or range as src.  Long
+                                  strings are stored in `c'. */
+    union value t;             /* Describes value as dest. Long strings in `c'. */
+  };
+
+/* Describes how to recode a single variable. */
+struct rcd_var
+  {
+    struct rcd_var *next;
+
+    unsigned flags;            /* RCD_SRC_* | RCD_DEST_* | RCD_MISC_* */
+
+    struct variable *src;      /* Source variable. */
+    struct variable *dest;     /* Destination variable. */
+    char dest_name[9];         /* Name of dest variable if we're creating it. */
+
+    int has_sysmis;            /* Do we recode for SYSMIS? */
+    union value sysmis;                /* Coding for SYSMIS (if src is numeric). */
+
+    struct coding *map;                /* Coding for other values. */
+    int nmap, mmap;            /* Length of map, max capacity of map. */
+  };
+
+/* RECODE transformation. */
+struct recode_trns
+  {
+    struct trns_header h;
+    struct rcd_var *codings;
+  };
+
+/* What we're recoding from (`src'==`source'). */
+#define RCD_SRC_ERROR          0000u   /* Bad value for src. */
+#define RCD_SRC_NUMERIC                0001u   /* Src is numeric. */
+#define RCD_SRC_STRING         0002u   /* Src is short string. */
+#define RCD_SRC_MASK           0003u   /* AND mask to isolate src bits. */
+
+/* What we're recoding to (`dest'==`destination'). */
+#define RCD_DEST_ERROR         0000u   /* Bad value for dest. */
+#define RCD_DEST_NUMERIC       0004u   /* Dest is numeric. */
+#define RCD_DEST_STRING                0010u   /* Dest is short string. */
+#define RCD_DEST_MASK          0014u   /* AND mask to isolate dest bits. */
+
+/* Miscellaneous bits. */
+#define RCD_MISC_CREATE                0020u   /* We create dest var (numeric only) */
+#define RCD_MISC_DUPLICATE     0040u   /* This var_info has the same MAP
+                                          value as the previous var_info.
+                                          Prevents redundant free()ing. */
+#define RCD_MISC_MISSING       0100u   /* Encountered MISSING or SYSMIS in
+                                          this input spec. */
+
+static int parse_dest_spec (struct rcd_var * rcd, union value *v,
+                           size_t *max_dst_width);
+static int parse_src_spec (struct rcd_var * rcd, int type, size_t max_src_width);
+static int recode_trns_proc (struct trns_header *, struct ccase *);
+static void recode_trns_free (struct trns_header *);
+static double convert_to_double (char *, int);
+
+#if DEBUGGING
+static void debug_print (rcd_var * head);
+#endif
+\f
+/* Parser. */
+
+/* First transformation in the list.  rcd is in this list. */
+static struct rcd_var *head;
+
+/* Variables in the current part of the recoding. */
+struct variable **v;
+int nv;
+
+/* Parses the RECODE transformation. */
+int
+cmd_recode (void)
+{
+  int i;
+
+  /* Transformation that we're constructing. */
+  struct rcd_var *rcd;
+
+  /* Type of the src variables. */
+  int type;
+
+  /* Length of longest src string. */
+  size_t max_src_width;
+
+  /* Length of longest dest string. */
+  size_t max_dst_width;
+
+  /* For stepping through, constructing the linked list of
+     recodings. */
+  struct rcd_var *iter;
+
+  /* The real transformation, just a wrapper for a list of
+     rcd_var's. */
+  struct recode_trns *trns;
+
+  lex_match_id ("RECODE");
+
+  /* Parses each specification between slashes. */
+  head = rcd = xmalloc (sizeof *rcd);
+  for (;;)
+    {
+      /* Whether we've already encountered a specification for SYSMIS. */
+      int had_sysmis = 0;
+
+      /* Initialize this rcd_var to ensure proper cleanup. */
+      rcd->next = NULL;
+      rcd->map = NULL;
+      rcd->nmap = rcd->mmap = 0;
+      rcd->has_sysmis = 0;
+      rcd->sysmis.f = 0;
+
+      /* Parse variable names. */
+      if (!parse_variables (NULL, &v, &nv, PV_SAME_TYPE))
+       goto lossage;
+
+      /* Ensure all variables are same type; find length of longest
+         source variable. */
+      type = v[0]->type;
+      max_src_width = v[0]->width;
+
+      if (type == ALPHA)
+       for (i = 0; i < nv; i++)
+         if (v[i]->width > (int) max_src_width)
+           max_src_width = v[i]->width;
+
+      /* Set up flags. */
+      rcd->flags = 0;
+      if (type == NUMERIC)
+       rcd->flags |= RCD_SRC_NUMERIC;
+      else
+       rcd->flags |= RCD_SRC_STRING;
+
+      /* Parse each coding in parentheses. */
+      max_dst_width = 0;
+      if (!lex_force_match ('('))
+       goto lossage;
+      for (;;) 
+       {
+         /* Get the input value (before the `='). */
+         int mark = rcd->nmap;
+         int code = parse_src_spec (rcd, type, max_src_width);
+         if (!code)
+           goto lossage;
+
+         /* ELSE is the same as any other input spec except that it
+            precludes later sysmis specifications. */
+         if (code == 3)
+           {
+             had_sysmis = 1;
+             code = 1;
+           }
+
+         /* If keyword CONVERT was specified, there is no output
+            specification.  */
+         if (code == 1)
+           {
+             union value output;
+
+             /* Get the output value (after the `='). */
+             lex_get ();       /* Skip `='. */
+             if (!parse_dest_spec (rcd, &output, &max_dst_width))
+               goto lossage;
+
+             /* Set the value for SYSMIS if requested and if we don't
+                already have one. */
+             if ((rcd->flags & RCD_MISC_MISSING) && !had_sysmis)
+               {
+                 rcd->has_sysmis = 1;
+                 if ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_NUMERIC)
+                   rcd->sysmis.f = output.f;
+                 else
+                   rcd->sysmis.c = xstrdup (output.c);
+                 had_sysmis = 1;
+
+                 rcd->flags &= ~RCD_MISC_MISSING;
+               }
+
+             /* Since there may be multiple input values for a single
+                output, the output value need to propagated among all
+                of them. */
+             if ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_NUMERIC)
+               for (i = mark; i < rcd->nmap; i++)
+                 rcd->map[i].t.f = output.f;
+             else
+               {
+                 for (i = mark; i < rcd->nmap; i++)
+                   rcd->map[i].t.c = xstrdup (output.c);
+                 free (output.c);
+               }
+           }
+         lex_get ();           /* Skip `)'. */
+         if (!lex_match ('('))
+           break;
+       }
+
+      /* Append sentinel value. */
+      rcd->map[rcd->nmap++].type = RCD_END;
+
+      /* Since multiple variables may use the same recodings, it is
+         necessary to propogate the codings to all of them. */
+      rcd->src = v[0];
+      rcd->dest = v[0];
+      rcd->dest_name[0] = 0;
+      iter = rcd;
+      for (i = 1; i < nv; i++)
+       {
+         iter = iter->next = xmalloc (sizeof *iter);
+         iter->next = NULL;
+         iter->flags = rcd->flags | RCD_MISC_DUPLICATE;
+         iter->src = v[i];
+         iter->dest = v[i];
+         iter->dest_name[0] = 0;
+         iter->has_sysmis = rcd->has_sysmis;
+         iter->sysmis = rcd->sysmis;
+         iter->map = rcd->map;
+       }
+
+      if (lex_match_id ("INTO"))
+       {
+         char **names;
+         int nnames;
+
+         int success = 0;
+
+         if (!parse_mixed_vars (&names, &nnames, PV_NONE))
+           goto lossage;
+
+         if (nnames != nv)
+           {
+             for (i = 0; i < nnames; i++)
+               free (names[i]);
+             free (names);
+             msg (SE, _("%d variable(s) cannot be recoded into "
+                        "%d variable(s).  Specify the same number "
+                        "of variables as input and output variables."),
+                  nv, nnames);
+             goto lossage;
+           }
+
+         if ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_STRING)
+           for (i = 0, iter = rcd; i < nv; i++, iter = iter->next)
+             {
+               struct variable *v = find_variable (names[i]);
+
+               if (!v)
+                 {
+                   msg (SE, _("There is no string variable named "
+                        "%s.  (All string variables specified "
+                        "on INTO must already exist.  Use the "
+                        "STRING command to create a string "
+                        "variable.)"), names[i]);
+                   goto INTO_fail;
+                 }
+               if (v->type != ALPHA)
+                 {
+                   msg (SE, _("Type mismatch between input and output "
+                        "variables.  Output variable %s is not "
+                        "a string variable, but all the input "
+                        "variables are string variables."), v->name);
+                   goto INTO_fail;
+                 }
+               if (v->width > (int) max_dst_width)
+                 max_dst_width = v->width;
+               iter->dest = v;
+             }
+         else
+           for (i = 0, iter = rcd; i < nv; i++, iter = iter->next)
+             {
+               struct variable *v = find_variable (names[i]);
+
+               if (v)
+                 {
+                   if (v->type != NUMERIC)
+                     {
+                       msg (SE, _("Type mismatch after INTO: %s "
+                                  "is not a numeric variable."), v->name);
+                       goto INTO_fail;
+                     }
+                   else
+                     iter->dest = v;
+                 }
+               else
+                 strcpy (iter->dest_name, names[i]);
+             }
+         success = 1;
+
+         /* Note that regardless of whether we succeed or fail,
+            flow-of-control comes here.  `success' is the important
+            factor.  Ah, if C had garbage collection...  */
+       INTO_fail:
+         for (i = 0; i < nnames; i++)
+           free (names[i]);
+         free (names);
+         if (!success)
+           goto lossage;
+       }
+      else
+       {
+         if (max_src_width > max_dst_width)
+           max_dst_width = max_src_width;
+
+         if ((rcd->flags & RCD_SRC_MASK) == RCD_SRC_NUMERIC
+             && (rcd->flags & RCD_DEST_MASK) != RCD_DEST_NUMERIC)
+           {
+             msg (SE, _("INTO must be used when the input values are "
+                        "numeric and output values are string."));
+             goto lossage;
+           }
+         
+         if ((rcd->flags & RCD_SRC_MASK) != RCD_SRC_NUMERIC
+             && (rcd->flags & RCD_DEST_MASK) == RCD_DEST_NUMERIC)
+           {
+             msg (SE, _("INTO must be used when the input values are "
+                        "string and output values are numeric."));
+             goto lossage;
+           }
+       }
+
+      if ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_STRING)
+       {
+         struct coding *cp;
+
+         for (cp = rcd->map; cp->type != RCD_END; cp++)
+           if (cp->t.c)
+             {
+               if (strlen (cp->t.c) < max_dst_width)
+                 {
+                   /* The NULL is only really necessary for the
+                      debugging code. */
+                   char *repl = xmalloc (max_dst_width + 1);
+                   st_pad_copy (repl, cp->t.c, max_dst_width + 1);
+                   free (cp->t.c);
+                   cp->t.c = repl;
+                 }
+               else
+                 /* The strings are guaranteed to be in order of
+                    nondecreasing length. */
+                 break;
+             }
+         
+       }
+
+      if (!lex_match ('/'))
+       break;
+      while (rcd->next)
+       rcd = rcd->next;
+      rcd = rcd->next = xmalloc (sizeof *rcd);
+
+      free (v);
+    }
+
+  if (token != '.')
+    {
+      lex_error (_("expecting end of command"));
+      goto lossage;
+    }
+
+  for (rcd = head; rcd; rcd = rcd->next)
+    if (rcd->dest_name[0])
+      {
+       rcd->dest = create_variable (&default_dict, rcd->dest_name,
+                                    NUMERIC, 0);
+       if (!rcd->dest)
+         {
+           /* This can occur if a destname is duplicated.  We could
+              give an error at parse time but I don't care enough. */
+           rcd->dest = find_variable (rcd->dest_name);
+           assert (rcd->dest != NULL);
+         }
+       else
+         envector (rcd->dest);
+      }
+
+  trns = xmalloc (sizeof *trns);
+  trns->h.proc = recode_trns_proc;
+  trns->h.free = recode_trns_free;
+  trns->codings = head;
+  add_transformation ((struct trns_header *) trns);
+
+#if DEBUGGING
+  debug_print (head);
+#endif
+
+  return CMD_SUCCESS;
+
+ lossage:
+  {
+    struct recode_trns t;
+
+    t.codings = head;
+    recode_trns_free ((struct trns_header *) &t);
+    return CMD_FAILURE;
+  }
+}
+
+static int
+parse_dest_spec (struct rcd_var * rcd, union value * v, size_t *max_dst_width)
+{
+  int flags;
+
+  v->c = NULL;
+
+  if (token == T_NUM)
+    {
+      v->f = tokval;
+      lex_get ();
+      flags = RCD_DEST_NUMERIC;
+    }
+  else if (lex_match_id ("SYSMIS"))
+    {
+      v->f = SYSMIS;
+      flags = RCD_DEST_NUMERIC;
+    }
+  else if (token == T_STRING)
+    {
+      size_t max = *max_dst_width;
+      size_t toklen = ds_length (&tokstr);
+      if (toklen > max)
+       max = toklen;
+      v->c = xmalloc (max + 1);
+      st_pad_copy (v->c, ds_value (&tokstr), max + 1);
+      flags = RCD_DEST_STRING;
+      *max_dst_width = max;
+      lex_get ();
+    }
+  else if (lex_match_id ("COPY"))
+    {
+      if ((rcd->flags & RCD_SRC_MASK) == RCD_SRC_NUMERIC)
+       {
+         flags = RCD_DEST_NUMERIC;
+         v->f = -SYSMIS;
+       }
+      else
+       {
+         flags = RCD_DEST_STRING;
+         v->c = NULL;
+       }
+    }
+
+  if ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_ERROR)
+    rcd->flags |= flags;
+#if 0
+  else if (((rcd->flags & RCD_DEST_MASK) == RCD_DEST_NUMERIC
+           && flags != RCD_DEST_NUMERIC)
+          || ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_STRING
+              && flags != RCD_DEST_STRING))
+#endif
+    else if ((rcd->flags & RCD_DEST_MASK) ^ flags)
+      {
+       msg (SE, _("Inconsistent output types.  The output values "
+                  "must be all numeric or all string."));
+       return 0;
+      }
+
+  return 1;
+}
+
+/* Reads a set of source specifications and returns one of the
+   following values: 0 on failure; 1 for normal success; 2 for success
+   but with CONVERT as the keyword; 3 for success but with ELSE as the
+   keyword. */
+static int
+parse_src_spec (struct rcd_var * rcd, int type, size_t max_src_width)
+{
+  struct coding *c;
+
+  for (;;)
+    {
+      if (rcd->nmap >= rcd->mmap - 1)
+       {
+         rcd->mmap += 16;
+         rcd->map = xrealloc (rcd->map, rcd->mmap * sizeof *rcd->map);
+       }
+
+      c = &rcd->map[rcd->nmap];
+      c->f1.c = c->f2.c = NULL;
+      if (lex_match_id ("ELSE"))
+       {
+         c->type = RCD_ELSE;
+         rcd->nmap++;
+         return 3;
+       }
+      else if (type == NUMERIC)
+       {
+         if (token == T_ID)
+           {
+             if (lex_match_id ("LO") || lex_match_id ("LOWEST"))
+               {
+                 if (!lex_force_match_id ("THRU"))
+                   return 0;
+                 if (lex_match_id ("HI") || lex_match_id ("HIGHEST"))
+                   c->type = RCD_ELSE;
+                 else if (token == T_NUM)
+                   {
+                     c->type = RCD_LOW;
+                     c->f1.f = tokval;
+                     lex_get ();
+                   }
+                 else
+                   {
+                     lex_error (_("following LO THRU"));
+                     return 0;
+                   }
+               }
+             else if (lex_match_id ("MISSING"))
+               {
+                 c->type = RCD_USER;
+                 rcd->flags |= RCD_MISC_MISSING;
+               }
+             else if (lex_match_id ("SYSMIS"))
+               {
+                 c->type = RCD_END;
+                 rcd->flags |= RCD_MISC_MISSING;
+               }
+             else
+               {
+                 lex_error (_("in source value"));
+                 return 0;
+               }
+           }
+         else if (token == T_NUM)
+           {
+             c->f1.f = tokval;
+             lex_get ();
+             if (lex_match_id ("THRU"))
+               {
+                 if (lex_match_id ("HI") || lex_match_id ("HIGHEST"))
+                   c->type = RCD_HIGH;
+                 else if (token == T_NUM)
+                   {
+                     c->type = RCD_RANGE;
+                     c->f2.f = tokval;
+                     lex_get ();
+                   }
+                 else
+                   {
+                     lex_error (NULL);
+                     return 0;
+                   }
+               }
+             else
+               c->type = RCD_SINGLE;
+           }
+         else
+           {
+             lex_error (_("in source value"));
+             return 0;
+           }
+       }
+      else
+       {
+         assert (type == ALPHA);
+         if (lex_match_id ("CONVERT"))
+           {
+             if ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_ERROR)
+               rcd->flags |= RCD_DEST_NUMERIC;
+             else if ((rcd->flags & RCD_DEST_MASK) != RCD_DEST_NUMERIC)
+               {
+                 msg (SE, _("Keyword CONVERT may only be used with "
+                            "string input values and numeric output "
+                            "values."));
+                 return 0;
+               }
+
+             c->type = RCD_CONVERT;
+             rcd->nmap++;
+             return 2;
+           }
+         else
+           {
+             /* Only the debugging code needs the NULLs at the ends
+                of the strings.  However, changing code behavior more
+                than necessary based on the DEBUGGING `#define' is just
+                *inviting* bugs. */
+             c->type = RCD_SINGLE;
+             if (!lex_force_string ())
+               return 0;
+             c->f1.c = xmalloc (max_src_width + 1);
+             st_pad_copy (c->f1.c, ds_value (&tokstr), max_src_width + 1);
+             lex_get ();
+           }
+       }
+
+      if (c->type != RCD_END)
+       rcd->nmap++;
+
+      lex_match (',');
+      if (token == '=')
+       break;
+    }
+  return 1;
+}
+\f
+/* Data transformation. */
+
+static void
+recode_trns_free (struct trns_header * t)
+{
+  int i;
+  struct rcd_var *head, *next;
+
+  head = ((struct recode_trns *) t)->codings;
+  while (head)
+    {
+      if (head->map && !(head->flags & RCD_MISC_DUPLICATE))
+       {
+         if (head->flags & RCD_SRC_STRING)
+           for (i = 0; i < head->nmap; i++)
+             switch (head->map[i].type)
+               {
+               case RCD_RANGE:
+                 free (head->map[i].f2.c);
+                 /* fall through */
+               case RCD_USER:
+               case RCD_SINGLE:
+               case RCD_HIGH:
+               case RCD_LOW:
+                 free (head->map[i].f1.c);
+                 break;
+               case RCD_END:
+               case RCD_ELSE:
+               case RCD_CONVERT:
+                 break;
+               default:
+                 assert (0);
+               }
+         if (head->flags & RCD_DEST_STRING)
+           for (i = 0; i < head->nmap; i++)
+             if (head->map[i].type != RCD_CONVERT && head->map[i].type != RCD_END)
+               free (head->map[i].t.c);
+         free (head->map);
+       }
+      next = head->next;
+      free (head);
+      head = next;
+    }
+}
+
+static inline struct coding *
+find_src_numeric (struct rcd_var * v, struct ccase * c)
+{
+  double cmp = c->data[v->src->fv].f;
+  struct coding *cp;
+
+  if (cmp == SYSMIS)
+    {
+      if (v->sysmis.f != -SYSMIS)
+       {
+         if ((v->flags & RCD_DEST_MASK) == RCD_DEST_NUMERIC)
+           c->data[v->dest->fv].f = v->sysmis.f;
+         else
+           memcpy (c->data[v->dest->fv].s, v->sysmis.c,
+                   v->dest->width);
+       }
+      return NULL;
+    }
+
+  for (cp = v->map;; cp++)
+    switch (cp->type)
+      {
+      case RCD_END:
+       return NULL;
+      case RCD_USER:
+       if (is_num_user_missing (cmp, v->src))
+         return cp;
+       break;
+      case RCD_SINGLE:
+       if (approx_eq (cmp, cp->f1.f))
+         return cp;
+       break;
+      case RCD_HIGH:
+       if (approx_ge (cmp, cp->f1.f))
+         return cp;
+       break;
+      case RCD_LOW:
+       if (approx_le (cmp, cp->f1.f))
+         return cp;
+       break;
+      case RCD_RANGE:
+       if (approx_in_range (cmp, cp->f1.f, cp->f2.f))
+         return cp;
+       break;
+      case RCD_ELSE:
+       return cp;
+      default:
+       assert (0);
+      }
+}
+
+static inline struct coding *
+find_src_string (struct rcd_var * v, struct ccase * c)
+{
+  char *cmp = c->data[v->src->fv].s;
+  int w = v->src->width;
+  struct coding *cp;
+
+  for (cp = v->map;; cp++)
+    switch (cp->type)
+      {
+      case RCD_END:
+       return NULL;
+      case RCD_SINGLE:
+       if (!memcmp (cp->f1.c, cmp, w))
+         return cp;
+       break;
+      case RCD_ELSE:
+       return cp;
+      case RCD_CONVERT:
+       {
+         double f = convert_to_double (cmp, w);
+         if (f != -SYSMIS)
+           {
+             c->data[v->dest->fv].f = f;
+             return NULL;
+           }
+         break;
+       }
+      default:
+       assert (0);
+      }
+}
+
+static int
+recode_trns_proc (struct trns_header * t, struct ccase * c)
+{
+  struct rcd_var *v;
+  struct coding *cp;
+
+  for (v = ((struct recode_trns *) t)->codings; v; v = v->next)
+    {
+      switch (v->flags & RCD_SRC_MASK)
+       {
+       case RCD_SRC_NUMERIC:
+         cp = find_src_numeric (v, c);
+         break;
+       case RCD_SRC_STRING:
+         cp = find_src_string (v, c);
+         break;
+       }
+      if (!cp)
+       continue;
+
+      /* A matching input value was found. */
+      if ((v->flags & RCD_DEST_MASK) == RCD_DEST_NUMERIC)
+       {
+         double val = cp->t.f;
+         if (val == -SYSMIS)
+           c->data[v->dest->fv].f = c->data[v->src->fv].f;
+         else
+           c->data[v->dest->fv].f = val;
+       }
+      else
+       {
+         char *val = cp->t.c;
+         if (val == NULL)
+           st_bare_pad_len_copy (c->data[v->dest->fv].s,
+                                 c->data[v->src->fv].c,
+                                 v->dest->width, v->src->width);
+         else
+           memcpy (c->data[v->dest->fv].s, cp->t.c, v->dest->width);
+       }
+    }
+
+  return -1;
+}
+\f
+/* Debug output. */
+
+#if DEBUGGING
+static void
+dump_dest (struct rcd_var * v, union value * c)
+{
+  if ((v->flags & RCD_DEST_MASK) == RCD_DEST_NUMERIC)
+    if (c->f == SYSMIS)
+      printf ("=SYSMIS");
+    else if (c->f == -SYSMIS)
+      printf ("=COPY");
+    else
+      printf ("=%g", c->f);
+  else if (c->c)
+    printf ("=\"%s\"", c->c);
+  else
+    printf ("=COPY");
+}
+
+static void
+debug_print (struct rcd_var * head)
+{
+  struct rcd_var *iter, *start;
+  struct coding *c;
+
+  printf ("RECODE\n");
+  for (iter = head; iter; iter = iter->next)
+    {
+      start = iter;
+      printf ("  %s%s", iter == head ? "" : "/", iter->src->name);
+      while (iter->next && (iter->next->flags & RCD_MISC_DUPLICATE))
+       {
+         iter = iter->next;
+         printf (" %s", iter->src->name);
+       }
+      if (iter->has_sysmis)
+       {
+         printf ("(SYSMIS");
+         dump_dest (iter, &iter->sysmis);
+         printf (")");
+       }
+      for (c = iter->map; c->type != RCD_END; c++)
+       {
+         printf ("(");
+         if ((iter->flags & RCD_SRC_MASK) == RCD_SRC_NUMERIC)
+           switch (c->type)
+             {
+             case RCD_END:
+               printf (_("!!END!!"));
+               break;
+             case RCD_USER:
+               printf ("MISSING");
+               break;
+             case RCD_SINGLE:
+               printf ("%g", c->f1.f);
+               break;
+             case RCD_HIGH:
+               printf ("%g THRU HIGH", c->f1.f);
+               break;
+             case RCD_LOW:
+               printf ("LOW THRU %g", c->f1.f);
+               break;
+             case RCD_RANGE:
+               printf ("%g THRU %g", c->f1.f, c->f2.f);
+               break;
+             case RCD_ELSE:
+               printf ("ELSE");
+               break;
+             default:
+               printf (_("!!ERROR!!"));
+               break;
+             }
+         else
+           switch (c->type)
+             {
+             case RCD_SINGLE:
+               printf ("\"%s\"", c->f1.c);
+               break;
+             case RCD_ELSE:
+               printf ("ELSE");
+               break;
+             case RCD_CONVERT:
+               printf ("CONVERT");
+               break;
+             default:
+               printf (_("!!ERROR!!"));
+               break;
+             }
+         if (c->type != RCD_CONVERT)
+           dump_dest (iter, &c->t);
+         printf (")");
+       }
+      printf ("\n    INTO");
+      for (;;)
+       {
+         printf (" %s",
+               start->dest_name[0] ? start->dest_name : start->dest->name);
+         if (start == iter)
+           break;
+         start = start->next;
+       }
+      printf ("\n");
+    }
+}
+#endif
+
+/* Convert NPTR to a `long int' in base 10.  Returns the long int on
+   success, NOT_LONG on failure.  On success stores a pointer to the
+   first character after the number into *ENDPTR.  From the GNU C
+   library. */
+long int
+string_to_long (char *nptr, int width, char **endptr)
+{
+  int negative;
+  register unsigned long int cutoff;
+  register unsigned int cutlim;
+  register unsigned long int i;
+  register char *s;
+  register unsigned char c;
+  const char *save;
+
+  s = nptr;
+
+  /* Check for a sign.  */
+  if (*s == '-')
+    {
+      negative = 1;
+      ++s;
+    }
+  else if (*s == '+')
+    {
+      negative = 0;
+      ++s;
+    }
+  else
+    negative = 0;
+  if (s >= nptr + width)
+    return NOT_LONG;
+
+  /* Save the pointer so we can check later if anything happened.  */
+  save = s;
+
+  cutoff = ULONG_MAX / 10ul;
+  cutlim = ULONG_MAX % 10ul;
+
+  i = 0;
+  for (c = *s;;)
+    {
+      if (isdigit ((unsigned char) c))
+       c -= '0';
+      else
+       break;
+      /* Check for overflow.  */
+      if (i > cutoff || (i == cutoff && c > cutlim))
+       return NOT_LONG;
+      else
+       i = i * 10ul + c;
+
+      s++;
+      if (s >= nptr + width)
+       break;
+      c = *s;
+    }
+
+  /* Check if anything actually happened.  */
+  if (s == save)
+    return NOT_LONG;
+
+  /* Check for a value that is within the range of `unsigned long
+     int', but outside the range of `long int'.  We limit LONG_MIN and
+     LONG_MAX by one point because we know that NOT_LONG is out there
+     somewhere. */
+  if (i > (negative
+          ? -((unsigned long int) LONG_MIN) - 1
+          : ((unsigned long int) LONG_MAX) - 1))
+    return NOT_LONG;
+
+  *endptr = s;
+
+  /* Return the result of the appropriate sign.  */
+  return (negative ? -i : i);
+}
+
+/* Converts S to a double according to format Fx.0.  Returns the value
+   found, or -SYSMIS if there was no valid number in s.  WIDTH is the
+   length of string S.  From the GNU C library. */
+static double
+convert_to_double (char *s, int width)
+{
+  register const char *end = &s[width];
+
+  short int sign;
+
+  /* The number so far.  */
+  double num;
+
+  int got_dot;                 /* Found a decimal point.  */
+  int got_digit;               /* Count of digits.  */
+
+  /* The exponent of the number.  */
+  long int exponent;
+
+  /* Eat whitespace.  */
+  while (s < end && isspace ((unsigned char) *s))
+    ++s;
+  if (s >= end)
+    return SYSMIS;
+
+  /* Get the sign.  */
+  sign = *s == '-' ? -1 : 1;
+  if (*s == '-' || *s == '+')
+    {
+      ++s;
+      if (s >= end)
+       return -SYSMIS;
+    }
+
+  num = 0.0;
+  got_dot = 0;
+  got_digit = 0;
+  exponent = 0;
+  for (; s < end; ++s)
+    {
+      if (isdigit ((unsigned char) *s))
+       {
+         got_digit++;
+
+         /* Make sure that multiplication by 10 will not overflow.  */
+         if (num > DBL_MAX * 0.1)
+           /* The value of the digit doesn't matter, since we have already
+              gotten as many digits as can be represented in a `double'.
+              This doesn't necessarily mean the result will overflow.
+              The exponent may reduce it to within range.
+
+              We just need to record that there was another
+              digit so that we can multiply by 10 later.  */
+           ++exponent;
+         else
+           num = (num * 10.0) + (*s - '0');
+
+         /* Keep track of the number of digits after the decimal point.
+            If we just divided by 10 here, we would lose precision.  */
+         if (got_dot)
+           --exponent;
+       }
+      else if (!got_dot && *s == '.')
+       /* Record that we have found the decimal point.  */
+       got_dot = 1;
+      else
+       break;
+    }
+
+  if (!got_digit)
+    return -SYSMIS;
+
+  if (s < end && (tolower ((unsigned char) (*s)) == 'e'
+                 || tolower ((unsigned char) (*s)) == 'd'))
+    {
+      /* Get the exponent specified after the `e' or `E'.  */
+      long int exp;
+
+      s++;
+      if (s >= end)
+       return -SYSMIS;
+
+      exp = string_to_long (s, end - s, &s);
+      if (exp == NOT_LONG || end == s)
+       return -SYSMIS;
+      exponent += exp;
+    }
+
+  while (s < end && isspace ((unsigned char) *s))
+    s++;
+  if (s < end)
+    return -SYSMIS;
+
+  if (num == 0.0)
+    return 0.0;
+
+  /* Multiply NUM by 10 to the EXPONENT power,
+     checking for overflow and underflow.  */
+
+  if (exponent < 0)
+    {
+      if (-exponent + got_digit > -(DBL_MIN_10_EXP) + 5
+         || num < DBL_MIN * pow (10.0, (double) -exponent))
+       return -SYSMIS;
+      num *= pow (10.0, (double) exponent);
+    }
+  else if (exponent > 0)
+    {
+      if (num > DBL_MAX * pow (10.0, (double) -exponent))
+       return -SYSMIS;
+      num *= pow (10.0, (double) exponent);
+    }
+
+  return sign > 0 ? num : -num;
+}
diff --git a/src/rename-vars.c b/src/rename-vars.c
new file mode 100644 (file)
index 0000000..fb21478
--- /dev/null
@@ -0,0 +1,154 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <assert.h>
+#include "alloc.h"
+#include "avl.h"
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "str.h"
+#include "var.h"
+
+/* FIXME: should change weighting variable, etc. */
+static int compare_name (const void *, const void *);
+
+/* The code for this function is very similar to the code for the
+   RENAME subcommand of MODIFY VARS. */
+int
+cmd_rename_variables (void)
+{
+  char (*names)[8] = NULL;
+
+  struct variable **old_names = NULL;
+  char **new_names = NULL;
+  int n_rename = 0;
+
+  struct variable *head, *tail, *iter;
+
+  int i;
+
+  lex_match_id ("RENAME");
+  lex_match_id ("VARIABLES");
+
+  do
+    {
+      int prev_nv_1 = n_rename;
+      int prev_nv_2 = n_rename;
+
+      if (!lex_match ('('))
+       {
+         msg (SE, _("`(' expected."));
+         goto lossage;
+       }
+      if (!parse_variables (&default_dict, &old_names, &n_rename,
+                           PV_APPEND | PV_NO_DUPLICATE))
+       goto lossage;
+      if (!lex_match ('='))
+       {
+         msg (SE, _("`=' expected between lists of new and old variable names."));
+         goto lossage;
+       }
+      if (!parse_DATA_LIST_vars (&new_names, &prev_nv_1, PV_APPEND))
+       goto lossage;
+      if (prev_nv_1 != n_rename)
+       {
+         msg (SE, _("Differing number of variables in old name list "
+              "(%d) and in new name list (%d)."),
+              n_rename - prev_nv_2, prev_nv_1 - prev_nv_2);
+         for (i = 0; i < prev_nv_1; i++)
+           free (new_names[i]);
+         free (new_names);
+         new_names = NULL;
+         goto lossage;
+       }
+      if (!lex_match (')'))
+       {
+         msg (SE, _("`)' expected after variable names."));
+         goto lossage;
+       }
+    }
+  while (token != '.');
+
+  /* Form a linked list of the variables to be renamed; also, set
+     their p.mfv.new_name members. */
+  head = NULL;
+  for (i = 0; i < n_rename; i++)
+    {
+      strcpy (old_names[i]->p.mfv.new_name, new_names[i]);
+      free (new_names[i]);
+      if (head != NULL)
+       tail = tail->p.mfv.next = old_names[i];
+      else
+       head = tail = old_names[i];
+    }
+  tail->p.mfv.next = NULL;
+  free (new_names);
+  free (old_names);
+  new_names = NULL;
+  old_names = NULL;
+
+  /* Construct a vector of all variables' new names. */
+  names = xmalloc (8 * default_dict.nvar);
+  for (i = 0; i < default_dict.nvar; i++)
+    strncpy (names[i], default_dict.var[i]->name, 8);
+  for (iter = head; iter; iter = iter->p.mfv.next)
+    strncpy (names[iter->index], iter->p.mfv.new_name, 8);
+
+  /* Sort the vector, then check for duplicates. */
+  qsort (names, default_dict.nvar, 8, compare_name);
+  for (i = 1; i < default_dict.nvar; i++)
+    if (memcmp (names[i], names[i - 1], 8) == 0)
+      {
+       char name[9];
+       strncpy (name, names[i], 8);
+       name[8] = 0;
+       msg (SE, _("Duplicate variable name `%s' after renaming."), name);
+       goto lossage;
+      }
+  free (names);
+
+  /* Finally, do the renaming. */
+  for (iter = head; iter; iter = iter->p.mfv.next)
+    avl_force_delete (default_dict.var_by_name, iter);
+  for (iter = head; iter; iter = iter->p.mfv.next)
+    {
+      strcpy (iter->name, iter->p.mfv.new_name);
+      avl_force_insert (default_dict.var_by_name, iter);
+    }
+
+  return CMD_SUCCESS;
+
+lossage:
+  if (new_names)
+    for (i = 0; i < n_rename; i++)
+      free (new_names[i]);
+  free (new_names);
+  free (old_names);
+  free (names);
+  return CMD_FAILURE;
+}
+
+static int
+compare_name (const void *a, const void *b)
+{
+  return memcmp (a, b, 8);
+}
diff --git a/src/repeat.c b/src/repeat.c
new file mode 100644 (file)
index 0000000..8b66a71
--- /dev/null
@@ -0,0 +1,650 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "cases.h"
+#include "command.h"
+#include "error.h"
+#include "getline.h"
+#include "lexer.h"
+#include "misc.h"
+#include "settings.h"
+#include "str.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* Describes one DO REPEAT macro. */
+struct repeat_entry
+  {
+    int type;                  /* 1=variable names, 0=any other. */
+    char id[9];                        /* Macro identifier. */
+    char **replacement;                /* Macro replacement. */
+    struct repeat_entry *next;
+  };
+
+/* List of macro identifiers. */
+static struct repeat_entry *repeat_tab;
+
+/* Number of substitutions for each macro. */
+static int count;
+
+/* List of lines before it's actually assigned to a file. */
+static struct getl_line_list *line_buf_head;
+static struct getl_line_list *line_buf_tail;
+
+static int parse_ids (struct repeat_entry *);
+static int parse_numbers (struct repeat_entry *);
+static int parse_strings (struct repeat_entry *);
+static void clean_up (void);
+static int internal_cmd_do_repeat (void);
+
+#if DEBUGGING
+static void debug_print (void);
+static void debug_print_lines (void);
+#endif
+
+int
+cmd_do_repeat (void)
+{
+  if (internal_cmd_do_repeat ())
+    return CMD_SUCCESS;
+
+  clean_up ();
+  return CMD_FAILURE;
+}
+
+/* Garbage collects all the allocated memory that's no longer
+   needed. */
+static void
+clean_up (void)
+{
+  struct repeat_entry *iter, *next;
+  int i;
+
+  iter = repeat_tab;
+  repeat_tab = NULL;
+
+  while (iter)
+    {
+      if (iter->replacement)
+       {
+         for (i = 0; i < count; i++)
+           free (iter->replacement[i]);
+         free (iter->replacement);
+       }
+      next = iter->next;
+      free (iter);
+      iter = next;
+    }
+}
+
+/* Allocates & appends another record at the end of the line_buf_tail
+   chain. */
+static inline void
+append_record (void)
+{
+  struct getl_line_list *new = xmalloc (sizeof *new);
+  
+  if (line_buf_head == NULL)
+    line_buf_head = line_buf_tail = new;
+  else
+    line_buf_tail = line_buf_tail->next = new;
+}
+
+/* Returns nonzero if KEYWORD appears beginning at CONTEXT. */
+static int
+recognize_keyword (const char *context, const char *keyword)
+{
+  const char *end = context;
+  while (isalpha ((unsigned char) *end))
+    end++;
+  return lex_id_match_len (keyword, strlen (keyword), context, end - context);
+}
+
+/* Does the real work of parsing the DO REPEAT command and its nested
+   commands. */
+static int
+internal_cmd_do_repeat (void)
+{
+  /* Name of first DO REPEAT macro. */
+  char first_name[9];
+
+  /* Current filename. */
+  const char *current_filename = NULL;
+
+  /* 1=Print lines after preprocessing. */
+  int print;
+
+  /* The first step is parsing the DO REPEAT command itself. */
+  lex_match_id ("DO");
+  lex_match_id ("REPEAT");
+
+  count = 0;
+  line_buf_head = NULL;
+  do
+    {
+      struct repeat_entry *e;
+      struct repeat_entry *iter;
+      int result;
+
+      /* Get a stand-in variable name and make sure it's unique. */
+      if (!lex_force_id ())
+       return 0;
+      for (iter = repeat_tab; iter; iter = iter->next)
+       if (!strcmp (iter->id, tokid))
+         {
+           msg (SE, _("Identifier %s is given twice."), tokid);
+           return 0;
+         }
+
+      /* Make a new stand-in variable entry and link it into the
+         list. */
+      e = xmalloc (sizeof *e);
+      e->type = 0;
+      e->next = repeat_tab;
+      strcpy (e->id, tokid);
+      repeat_tab = e;
+
+      /* Skip equals sign. */
+      lex_get ();
+      if (!lex_force_match ('='))
+       return 0;
+
+      /* Get the details of the variable's possible values. */
+      
+      if (token == T_ID)
+       result = parse_ids (e);
+      else if (token == T_NUM)
+       result = parse_numbers (e);
+      else if (token == T_STRING)
+       result = parse_strings (e);
+      else
+       {
+         lex_error (NULL);
+         return 0;
+       }
+      if (!result)
+       return 0;
+
+      /* If this is the first variable then it defines how many
+        replacements there must be; otherwise enforce this number of
+        replacements. */
+      if (!count)
+       {
+         count = result;
+         strcpy (first_name, e->id);
+       }
+      else if (count != result)
+       {
+         msg (SE, _("There must be the same number of substitutions "
+                    "for each dummy variable specified.  Since there "
+                    "were %d substitutions for %s, there must be %d "
+                    "for %s as well, but %d were specified."),
+              count, first_name, count, e->id, result);
+         return 0;
+       }
+
+      /* Next! */
+      lex_match ('/');
+    }
+  while (token != '.');
+
+#if DEBUGGING
+  debug_print ();
+#endif
+
+  /* Read all the lines inside the DO REPEAT ... END REPEAT. */
+  {
+    int nest = 1;
+
+    for (;;)
+      {
+       if (!getl_read_line ())
+         msg (FE, _("Unexpected end of file."));
+
+       /* If the current file has changed then record the fact. */
+       {
+         const char *curfn;
+         int curln;
+
+         getl_location (&curfn, &curln);
+         if (current_filename != curfn)
+           {
+             assert (curln > 0 && curfn != NULL);
+           
+             append_record ();
+             line_buf_tail->len = -curln;
+             line_buf_tail->line = xstrdup (curfn);
+             current_filename = curfn;
+           }
+       }
+       
+       /* FIXME?  This code is not strictly correct, however if you
+          have begun a line with DO REPEAT or END REPEAT and it's
+          *not* a command name, then you are obviously *trying* to
+          break this mechanism.  And you will.  Also, the entire
+          command names must appear on a single line--they can't be
+          spread out. */
+       {
+         char *cp = ds_value (&getl_buf);
+
+         /* Skip leading indentors and any whitespace. */
+         if (*cp == '+' || *cp == '-' || *cp == '.')
+           cp++;
+         while (isspace ((unsigned char) *cp))
+           cp++;
+
+         /* Find END REPEAT. */
+         if (recognize_keyword (cp, "end"))
+           {
+             while (isalpha ((unsigned char) *cp))
+               cp++;
+             while (isspace ((unsigned char) *cp))
+               cp++;
+             if (recognize_keyword (cp, "repeat"))
+               {
+                 nest--;
+
+                 if (!nest)
+                 {
+                   while (isalpha ((unsigned char) *cp))
+                     cp++;
+                   while (isspace ((unsigned char) *cp))
+                     cp++;
+
+                   print = recognize_keyword (cp, "print");
+                   break;
+                 }
+               }
+           }
+         else /* Find DO REPEAT. */
+           if (!strncasecmp (cp, "do", 2))
+             {
+               cp += 2;
+               while (isspace ((unsigned char) *cp))
+                 cp++;
+               if (!strncasecmp (cp, "rep", 3))
+                 nest++;
+             }
+       }
+
+       append_record ();
+       line_buf_tail->len = ds_length (&getl_buf);
+       line_buf_tail->line = xmalloc (ds_length (&getl_buf) + 1);
+       memcpy (line_buf_tail->line,
+               ds_value (&getl_buf), ds_length (&getl_buf) + 1);
+      }
+  }
+
+  /* FIXME: For the moment we simply discard the contents of the END
+     REPEAT line.  We should actually check for the PRINT specifier.
+     This can be done easier when we buffer entire commands instead of
+     doing it token by token; see TODO. */
+  lex_entire_line ();  
+  
+  /* Tie up the loose end of the chain. */
+  if (line_buf_head == NULL)
+    {
+      msg (SW, _("No commands in scope."));
+      return 1;
+    }
+  line_buf_tail->next = NULL;
+
+  /* Show the line list. */
+#if DEBUGGING
+  debug_print_lines ();
+#endif
+  
+  /* Make new variables. */
+  {
+    struct repeat_entry *iter;
+    for (iter = repeat_tab; iter; iter = iter->next)
+      if (iter->type == 1)
+       {
+         int i;
+         for (i = 0; i < count; i++)
+           {
+             /* Note that if the variable already exists there is no
+                harm done. */
+             struct variable *v = create_variable (&default_dict,
+                                                   iter->replacement[i],
+                                                   NUMERIC, 0);
+
+             /* If we created the variable then we need to initialize
+                its observations to SYSMIS. */
+             if (v)
+               envector (v);
+           }
+       }
+  }
+
+  /* Create the DO REPEAT virtual input file. */
+  {
+    struct getl_script *script = xmalloc (sizeof *script);
+
+    script->first_line = line_buf_head;
+    script->cur_line = NULL;
+    script->remaining_loops = count;
+    script->loop_index = -1;
+    script->macros = repeat_tab;
+    script->print = print;
+
+    getl_add_DO_REPEAT_file (script);
+  }
+
+  return 1;
+}
+
+/* Parses a set of ids for DO REPEAT. */
+static int
+parse_ids (struct repeat_entry * e)
+{
+  int i;
+  int n = 0;
+
+  e->type = 1;
+  e->replacement = NULL;
+
+  do
+    {
+      char **names;
+      int nnames;
+
+      if (!parse_mixed_vars (&names, &nnames, PV_NONE))
+       return 0;
+
+      e->replacement = xrealloc (e->replacement,
+                                (nnames + n) * sizeof *e->replacement);
+      for (i = 0; i < nnames; i++)
+       {
+         e->replacement[n + i] = xstrdup (names[i]);
+         free (names[i]);
+       }
+      free (names);
+      n += nnames;
+    }
+  while (token != '/' && token != '.');
+
+  return n;
+}
+
+/* Stores VALUE into *REPL. */
+static inline void
+store_numeric (char **repl, long value)
+{
+  *repl = xmalloc (INT_DIGITS + 1);
+  sprintf (*repl, "%ld", value);
+}
+
+/* Parses a list of numbers for DO REPEAT. */
+static int
+parse_numbers (struct repeat_entry *e)
+{
+  /* First and last numbers for TO, plus the step factor. */
+  long a, b;
+
+  /* Alias to e->replacement. */
+  char **array;
+
+  /* Number of entries in array; maximum number for this allocation
+     size. */
+  int n, m;
+
+  n = m = 0;
+  e->type = 0;
+  e->replacement = array = NULL;
+
+  do
+    {
+      /* Parse A TO B into a, b. */
+      if (!lex_force_int ())
+       return 0;
+      a = lex_integer ();
+
+      lex_get ();
+      if (token == T_TO)
+       {
+         lex_get ();
+         if (!lex_force_int ())
+           return 0;
+         b = lex_integer ();
+
+         lex_get ();
+       }
+      else b = a;
+
+      if (n + (abs (b - a) + 1) > m)
+       {
+         m = n + (abs (b - a) + 1) + 16;
+         e->replacement = array = xrealloc (array,
+                                            m * sizeof *e->replacement);
+       }
+
+      if (a == b)
+       store_numeric (&array[n++], a);
+      else
+       {
+         long iter;
+
+         if (a < b)
+           for (iter = a; iter <= b; iter++)
+             store_numeric (&array[n++], iter);
+         else
+           for (iter = a; iter >= b; iter--)
+             store_numeric (&array[n++], iter);
+       }
+
+      lex_match (',');
+    }
+  while (token != '/' && token != '.');
+  e->replacement = xrealloc (array, n * sizeof *e->replacement);
+
+  return n;
+}
+
+/* Parses a list of strings for DO REPEAT. */
+int
+parse_strings (struct repeat_entry * e)
+{
+  char **string;
+  int n, m;
+
+  e->type = 0;
+  string = e->replacement = NULL;
+  n = m = 0;
+
+  do
+    {
+      if (token != T_STRING)
+       {
+         int i;
+         msg (SE, _("String expected."));
+         for (i = 0; i < n; i++)
+           free (string[i]);
+         free (string);
+         return 0;
+       }
+
+      if (n + 1 > m)
+       {
+         m += 16;
+         e->replacement = string = xrealloc (string,
+                                             m * sizeof *e->replacement);
+       }
+      string[n++] = lex_token_representation ();
+      lex_get ();
+
+      lex_match (',');
+    }
+  while (token != '/' && token != '.');
+  e->replacement = xrealloc (string, n * sizeof *e->replacement);
+
+  return n;
+}
+\f
+int
+cmd_end_repeat (void)
+{
+  msg (SE, _("No matching DO REPEAT."));
+  return CMD_FAILURE;
+}
+\f
+/* Finds a DO REPEAT macro with name MACRO_NAME and returns the
+   appropriate subsitution if found, or NULL if not. */
+char *
+find_DO_REPEAT_substitution (char *macro_name)
+{
+  struct getl_script *s;
+           
+  for (s = getl_head; s; s = s->included_from)
+    {
+      struct repeat_entry *e;
+      
+      if (s->first_line == NULL)
+       continue;
+
+      for (e = s->macros; e; e = e->next)
+       if (!strcasecmp (e->id, macro_name))
+         return e->replacement[s->loop_index];
+    }
+  
+  return NULL;
+}
+
+/* Makes appropriate DO REPEAT macro substitutions within getl_buf. */
+void
+perform_DO_REPEAT_substitutions (void)
+{
+  /* Are we in an apostrophized string or a quoted string? */
+  int in_apos = 0, in_quote = 0;
+
+  /* Source pointer. */
+  char *cp;
+
+  /* Output buffer, size, pointer. */
+  struct string output;
+
+  /* Terminal dot. */
+  int dot = 0;
+
+  ds_init (NULL, &output, ds_size (&getl_buf));
+
+  /* Strip trailing whitespace, check for & remove terminal dot. */
+  while (ds_length (&getl_buf) > 0
+        && isspace ((unsigned char) ds_end (&getl_buf)[-1]))
+    ds_truncate (&getl_buf, ds_length (&getl_buf) - 1);
+  if (ds_length (&getl_buf) > 0 && ds_end (&getl_buf)[-1] == set_endcmd)
+    {
+      dot = 1;
+      ds_truncate (&getl_buf, ds_length (&getl_buf) - 1);
+    }
+  
+  for (cp = ds_value (&getl_buf); cp < ds_end (&getl_buf); )
+    {
+      if (*cp == '\'' && !in_quote)
+       in_apos ^= 1;
+      else if (*cp == '"' && !in_apos)
+       in_quote ^= 1;
+      
+      if (in_quote || in_apos || !CHAR_IS_ID1 (*cp))
+       {
+         ds_putchar (&output, *cp++);
+         continue;
+       }
+
+      /* Collect an identifier. */
+      {
+       char name[9];
+       char *start = cp;
+       char *np = name;
+       char *substitution;
+
+       while (CHAR_IS_IDN (*cp) && np < &name[8])
+         *np++ = *cp++;
+       while (CHAR_IS_IDN (*cp))
+         cp++;
+       *np = 0;
+
+       substitution = find_DO_REPEAT_substitution (name);
+       if (!substitution)
+         {
+           ds_concat_buffer (&output, start, cp - start);
+           continue;
+         }
+
+       /* Force output buffer size, copy substitution. */
+       ds_concat (&output, substitution);
+      }
+    }
+  if (dot)
+    ds_putchar (&output, (unsigned char) set_endcmd);
+
+  ds_destroy (&getl_buf);
+  getl_buf = output;
+}
+\f
+/* Debugging code. */
+
+#if DEBUGGING
+static void
+debug_print (void)
+{
+  struct repeat_entry *iter;
+  int j;
+
+  printf ("DO REPEAT\n");
+  for (iter = repeat_tab; iter; iter = iter->next)
+    {
+      printf ("   %s%s=", iter->id, iter->type ? "(ids)" : "");
+      for (j = 0; j < count; j++)
+       printf ("%s ", iter->replacement[j]);
+      putc (iter->next ? '/' : '.', stdout);
+      printf ("\n");
+    }
+}
+
+static void
+debug_print_lines (void)
+{
+  struct getl_line_list *iter;
+  const char *fn = "(none)";
+  int ln = 65536;
+
+  printf ("---begin DO REPEAT lines---\n");
+  for (iter = line_buf_head; iter; iter = iter->next)
+    {
+      if (iter->len < 0)
+       {
+         ln = -iter->len;
+         fn = iter->line;
+       } else {
+         printf ("%s:%d: %s", fn, ln++, iter->line);
+       }
+    }
+  printf ("---end DO REPEAT lines---\n");
+}
+#endif /* DEBUGGING */
diff --git a/src/sample.c b/src/sample.c
new file mode 100644 (file)
index 0000000..84f2e89
--- /dev/null
@@ -0,0 +1,146 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <stdio.h>
+#include <math.h>
+#include "alloc.h"
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "random.h"
+#include "str.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1 */
+#include "debug-print.h"
+
+/* The two different types of samples. */
+enum
+  {
+    TYPE_A_FROM_B,             /* 5 FROM 10 */
+    TYPE_FRACTION              /* 0.5 */
+  };
+
+/* SAMPLE transformation. */
+struct sample_trns
+  {
+    struct trns_header h;
+    int type;                  /* One of TYPE_*. */
+    int n, N;                  /* TYPE_A_FROM_B: n from N. */
+    int m, t;                  /* TYPE_A_FROM_B: # selected so far; # so far. */
+    int frac;                  /* TYPE_FRACTION: a fraction out of 65536. */
+  };
+
+int sample_trns_proc (struct trns_header *, struct ccase *);
+
+int
+cmd_sample (void)
+{
+  struct sample_trns *trns;
+
+  int type;
+  int a, b;
+  int frac;
+
+  lex_match_id ("SAMPLE");
+
+  if (!lex_force_num ())
+    return CMD_FAILURE;
+  if (!lex_integer_p ())
+    {
+      type = TYPE_FRACTION;
+      if (tokval <= 0 || tokval >= 1)
+       {
+         msg (SE, _("The sampling factor must be between 0 and 1 "
+                    "exclusive."));
+         return CMD_FAILURE;
+       }
+         
+      frac = tokval * 65536;
+      a = b = 0;
+    }
+  else
+    {
+      type = TYPE_A_FROM_B;
+      a = lex_integer ();
+      lex_get ();
+      if (!lex_force_match_id ("FROM"))
+       return CMD_FAILURE;
+      if (!lex_force_int ())
+       return CMD_FAILURE;
+      b = lex_integer ();
+      if (a >= b)
+       {
+         msg (SE, _("Cannot sample %d observations from a population of "
+                    "%d."),
+              a, b);
+         return CMD_FAILURE;
+       }
+      
+      frac = 0;
+    }
+  lex_get ();
+
+#if DEBUGGING
+  if (type == TYPE_FRACTION)
+    printf ("SAMPLE %g.\n", frac / 65536.);
+  else
+    printf ("SAMPLE %d FROM %d.\n", a, b);
+#endif
+
+  trns = xmalloc (sizeof *trns);
+  trns->h.proc = sample_trns_proc;
+  trns->h.free = NULL;
+  trns->type = type;
+  trns->n = a;
+  trns->N = b;
+  trns->m = trns->t = 0;
+  trns->frac = frac;
+  add_transformation ((struct trns_header *) trns);
+
+  return lex_end_of_command ();
+}
+
+int
+sample_trns_proc (struct trns_header * trns, struct ccase *c unused)
+{
+  struct sample_trns *t = (struct sample_trns *) trns;
+  double U;
+
+  if (t->type == TYPE_FRACTION)
+    return (rand_simple (0x10000) <= t->frac) - 2;
+
+  if (t->m >= t->n)
+    return -2;
+
+  U = rand_uniform (1);
+  if ((t->N - t->t) * U >= t->n - t->m)
+    {
+      t->t++;
+      return -2;
+    }
+  else
+    {
+      t->m++;
+      t->t++;
+      return -1;
+    }
+}
diff --git a/src/sel-if.c b/src/sel-if.c
new file mode 100644 (file)
index 0000000..f4afd9c
--- /dev/null
@@ -0,0 +1,148 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include "alloc.h"
+#include "command.h"
+#include "error.h"
+#include "expr.h"
+#include "lexer.h"
+#include "str.h"
+#include "var.h"
+
+/* SELECT IF transformation. */
+struct select_if_trns
+  {
+    struct trns_header h;
+    struct expression *e;      /* Test expression. */
+  };
+
+static int select_if_proc (struct trns_header *, struct ccase *);
+static void select_if_free (struct trns_header *);
+
+/* Parses the SELECT IF transformation. */
+int
+cmd_select_if (void)
+{
+  struct expression *e;
+  struct select_if_trns *t;
+
+  lex_match_id ("SELECT");
+  lex_match_id ("IF");
+
+  e = expr_parse (PXP_BOOLEAN);
+  if (!e)
+    return CMD_FAILURE;
+
+  if (token != '.')
+    {
+      expr_free (e);
+      lex_error (_("expecting end of command"));
+      return CMD_FAILURE;
+    }
+
+  t = xmalloc (sizeof *t);
+  t->h.proc = select_if_proc;
+  t->h.free = select_if_free;
+  t->e = e;
+  add_transformation ((struct trns_header *) t);
+
+  return CMD_SUCCESS;
+}
+
+/* Performs the SELECT IF transformation T on case C. */
+static int
+select_if_proc (struct trns_header * t, struct ccase * c)
+{
+  return (expr_evaluate (((struct select_if_trns *) t)->e, c, NULL) == 1.0) - 2;
+}
+
+/* Frees SELECT IF transformation T. */
+static void
+select_if_free (struct trns_header * t)
+{
+  expr_free (((struct select_if_trns *) t)->e);
+}
+
+/* Parses the FILTER command. */
+int
+cmd_filter (void)
+{
+  lex_match_id ("FILTER");
+
+  if (lex_match_id ("OFF"))
+    default_dict.filter_var[0] = 0;
+  else
+    {
+      struct variable *v;
+
+      lex_match (T_BY);
+      v = parse_variable ();
+      if (!v)
+       return CMD_FAILURE;
+
+      if (v->type == ALPHA)
+       {
+         msg (SE, _("The filter variable must be numeric."));
+         return CMD_FAILURE;
+       }
+
+      if (v->name[0] == '#')
+       {
+         msg (SE, _("The filter variable may not be scratch."));
+         return CMD_FAILURE;
+       }
+      
+      strcpy (default_dict.filter_var, v->name);
+
+      FILTER_before_TEMPORARY = !temporary;
+    }
+
+  return CMD_SUCCESS;
+}
+
+/* Parses the PROCESS IF command. */
+int
+cmd_process_if (void)
+{
+  struct expression *e;
+
+  lex_match_id ("PROCESS");
+  lex_match_id ("IF");
+
+  e = expr_parse (PXP_BOOLEAN);
+  if (!e)
+    return CMD_FAILURE;
+
+  if (token != '.')
+    {
+      expr_free (e);
+      lex_error (_("expecting end of command"));
+      return CMD_FAILURE;
+    }
+
+  if (process_if_expr)
+    {
+      msg (MW, _("Only last instance of this command is in effect."));
+      expr_free (process_if_expr);
+    }
+  process_if_expr = e;
+
+  return CMD_SUCCESS;
+}
diff --git a/src/set.q b/src/set.q
new file mode 100644 (file)
index 0000000..8683bff
--- /dev/null
+++ b/src/set.q
@@ -0,0 +1,882 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/*
+   Categories of SET subcommands:
+
+   data input: BLANKS, DECIMAL, FORMAT.
+   
+   program input: ENDCMD, NULLINE.
+   
+   interaction: CPROMPT, DPROMPT, ERRORBREAK, MXERRS, MXWARNS, PROMPT.
+   
+   program execution: MEXPAND, MITERATE, MNEST, MPRINT,
+   MXLOOPS, SEED, UNDEFINED.
+
+   data output: CCA...CCE, DECIMAL, FORMAT, RESULTS-p.
+
+   output routing: ECHO, ERRORS, INCLUDE, MESSAGES, PRINTBACK, ERRORS,
+   RESULTS-rw.
+
+   output activation: LISTING (on/off), SCREEN, PRINTER.
+
+   output driver options: HEADERS, MORE, PAGER, VIEWLENGTH, VIEWWIDTH,
+   LISTING (filename).
+
+   logging: LOG, JOURNAL.
+
+   system files: COMP/COMPRESSION, SCOMP/SCOMPRESSION.
+
+   security: SAFER.
+*/
+
+/*
+   FIXME
+
+   These subcommands remain to be implemented:
+     ECHO, PRINTBACK, INCLUDE
+     MORE, PAGER, VIEWLENGTH, VIEWWIDTH, HEADERS
+
+   These subcommands are not complete:
+     MESSAGES, ERRORS, RESULTS
+     LISTING/DISK, LOG/JOURNAL
+*/     
+   
+#include <config.h>
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "lexer.h"
+#include "error.h"
+#include "magic.h"
+#include "log.h"
+#include "output.h"
+#include "var.h"
+#include "format.h"
+#include "settings.h"
+
+double set_blanks;
+int set_compression;
+struct set_cust_currency set_cc[5];
+int set_cpi;
+char *set_cprompt;
+int set_decimal;
+int set_grouping;
+char *set_dprompt;
+int set_echo;
+int set_endcmd;
+int set_errorbreak;
+int set_errors, set_messages, set_results;
+struct fmt_spec set_format;
+int set_headers;
+int set_include;
+char *set_journal;
+int set_journaling;
+int set_lpi;
+int set_messages;
+int set_mexpand;
+int set_miterate;
+int set_mnest;
+int set_more;
+int set_mprint;
+int set_mxerrs;
+int set_mxloops;
+int set_mxwarns;
+int set_nullline;
+int set_printback;
+int set_output = 1;
+#if !USE_INTERNAL_PAGER
+char *set_pager;
+#endif /* !USE_INTERNAL_PAGER */
+int set_printer;
+char *set_prompt;
+char *set_results_file;
+int set_safer;
+int set_scompression;
+int set_screen;
+long set_seed;
+int set_seed_used;
+int set_testing_mode;
+int set_undefined;
+int set_viewlength;
+int set_viewwidth;
+
+static void set_routing (int q, int *setting);
+static int set_ccx (const char *cc_string, struct set_cust_currency * cc,
+                   int cc_name);
+
+/* (specification)
+   "SET" (stc_):
+     automenu=automenu:on/off;
+     beep=beep:on/off;
+     blanks=custom;
+     block=string "x==1" "one character long";
+     boxstring=string "x==3 || x==11" "3 or 11 characters long";
+     case=size:upper/uplow;
+     cca=string;
+     ccb=string;
+     ccc=string;
+     ccd=string;
+     cce=string;
+     color=custom;
+     compression=compress:on/off;
+     cpi=integer;
+     cprompt=string;
+     decimal=dec:dot/_comma;
+     disk=custom;
+     dprompt=string;
+     echo=echo:on/off;
+     eject=eject:on/off;
+     endcmd=string "x==1" "one character long";
+     errorbreak=errbrk:on/off;
+     errors=errors:on/off/terminal/listing/both/none;
+     format=custom;
+     headers=headers:no/yes/blank;
+     helpwindows=helpwin:on/off;
+     highres=hires:on/off;
+     histogram=string "x==1" "one character long";
+     include=inc:on/off;
+     journal=custom;
+     length=custom;
+     listing=custom;
+     log=custom;
+     lowres=lores:auto/on/off;
+     lpi=integer;
+     menus=menus:standard/extended;
+     messages=messages:on/off/terminal/listing/both/none;
+     mexpand=mexp:on/off;
+     miterate=integer;
+     mnest=integer;
+     more=more:on/off;
+     mprint=mprint:on/off;
+     mxerrs=integer;
+     mxloops=integer;
+     mxmemory=integer;
+     mxwarns=integer;
+     nulline=null:on/off;
+     pager=custom;
+     printback=prtbck:on/off;
+     printer=prtr:on/off;
+     prompt=string;
+     ptranslate=ptrans:on/off;
+     rcolor=custom;
+     results=custom;
+     runreview=runrev:auto/manual;
+     safer=safe:on;
+     scompression=scompress:on/off;
+     screen=scrn:on/off;
+     scripttab=string "x==1" "one character long";
+     seed=custom;
+     tb1=string "x==3 || x==11" "3 or 11 characters long";
+     tbfonts=string;
+     undefined=undef:warn/nowarn;
+     viewlength=custom;
+     viewwidth=integer;
+     width=custom;
+     workdev=custom;
+     workspace=integer;
+     xsort=xsort:yes/no.
+*/
+
+/* (declarations) */
+/* (functions) */
+
+int internal_cmd_set (void);
+
+int
+cmd_set (void)
+{
+  struct cmd_set cmd;
+
+  lex_match_id ("SET");
+
+  if (!parse_set (&cmd))
+    return CMD_FAILURE;
+
+  if (cmd.sbc_block)
+    msg (SW, _("BLOCK is obsolete."));
+
+  if (cmd.sbc_boxstring)
+    msg (SW, _("BOXSTRING is obsolete."));
+
+  if (cmd.compress != -1)
+    {
+      msg (MW, _("Active file compression is not yet implemented "
+                "(and probably won't be)."));
+      set_compression = cmd.compress == STC_OFF ? 0 : 1;
+    }
+  if (cmd.scompress != -1)
+    set_scompression = cmd.scompress == STC_OFF ? 0 : 1;
+  if (cmd.n_cpi != NOT_LONG)
+    {
+      if (cmd.n_cpi <= 0)
+       msg (SE, _("CPI must be greater than 0."));
+      else
+       set_cpi = cmd.n_cpi;
+    }
+  if (cmd.sbc_histogram)
+    msg (MW, _("HISTOGRAM is obsolete."));
+  if (cmd.n_lpi != NOT_LONG)
+    {
+      if (cmd.n_lpi <= 0)
+       msg (SE, _("LPI must be greater than 0."));
+      else
+       set_lpi = cmd.n_lpi;
+    }
+  
+  /* Windows compatible syntax. */
+  if (cmd.sbc_case)
+    msg (SW, _("CASE is not implemented and probably won't be.  If you care, "
+              "complain about it."));
+  if (cmd.sbc_cca)
+    set_ccx (cmd.s_cca, &set_cc[0], 'A');
+  if (cmd.sbc_ccb)
+    set_ccx (cmd.s_ccb, &set_cc[1], 'B');
+  if (cmd.sbc_ccc)
+    set_ccx (cmd.s_ccc, &set_cc[2], 'C');
+  if (cmd.sbc_ccd)
+    set_ccx (cmd.s_ccd, &set_cc[3], 'D');
+  if (cmd.sbc_cce)
+    set_ccx (cmd.s_cce, &set_cc[4], 'E');
+  if (cmd.dec != -1)
+    {
+      set_decimal = cmd.dec == STC_DOT ? '.' : ',';
+      set_grouping = cmd.dec == STC_DOT ? ',' : '.';
+    }
+  if (cmd.errors != -1)
+    set_routing (cmd.errors, &set_errors);
+  if (cmd.headers != -1)
+    set_headers = cmd.headers == STC_NO ? 0 : (cmd.headers == STC_YES ? 1 : 2);
+  if (cmd.messages != -1)
+    set_routing (cmd.messages, &set_messages);
+  if (cmd.mexp != -1)
+    set_mexpand = cmd.mexp == STC_OFF ? 0 : 1;
+  if (cmd.n_miterate != NOT_LONG)
+    {
+      if (cmd.n_miterate > 0)
+       set_miterate = cmd.n_miterate;
+      else
+       msg (SE, _("Value for MITERATE (%ld) must be greater than 0."),
+            cmd.n_miterate);
+    }
+  if (cmd.n_mnest != NOT_LONG)
+    {
+      if (cmd.n_mnest > 0)
+       set_mnest = cmd.n_mnest;
+      else
+       msg (SE, _("Value for MNEST (%ld) must be greater than 0."),
+            cmd.n_mnest);
+    }
+  if (cmd.mprint != -1)
+    set_mprint = cmd.mprint == STC_OFF ? 0 : 1;
+  if (cmd.n_mxerrs != NOT_LONG)
+    {
+      if (set_mxerrs < 1)
+       msg (SE, _("MXERRS must be at least 1."));
+      else
+       set_mxerrs = cmd.n_mxerrs;
+    }
+  if (cmd.n_mxloops != NOT_LONG)
+    {
+      if (set_mxloops < 1)
+       msg (SE, _("MXLOOPS must be at least 1."));
+      else
+       set_mxloops = cmd.n_mxloops;
+    }
+  if (cmd.n_mxmemory != NOT_LONG)
+    msg (SE, _("MXMEMORY is obsolete."));
+  if (cmd.n_mxwarns != NOT_LONG)
+    set_mxwarns = cmd.n_mxwarns;
+  if (cmd.prtbck != -1)
+    set_printback = cmd.prtbck == STC_OFF ? 0 : 1;
+  if (cmd.s_scripttab)
+    msg (SE, _("SCRIPTTAB is obsolete."));
+  if (cmd.s_tbfonts)
+    msg (SW, _("TBFONTS not implemented."));
+  if (cmd.s_tb1)
+    msg (SW, _("TB1 not implemented."));
+  if (cmd.undef != -1)
+    set_undefined = cmd.undef == STC_NOWARN ? 0 : 1;
+  if (cmd.n_workspace != NOT_LONG)
+    msg (SE, _("WORKSPACE is obsolete."));
+
+  /* PC+ compatible syntax. */
+  if (cmd.scrn != -1)
+    outp_enable_device (cmd.scrn == STC_OFF ? 0 : 1, OUTP_DEV_SCREEN);
+
+  if (cmd.automenu != -1)
+    msg (SW, _("AUTOMENU is obsolete."));
+  if (cmd.beep != -1)
+    msg (SW, _("BEEP is obsolete."));
+
+  if (cmd.s_cprompt)
+    {
+      free (set_cprompt);
+      set_cprompt = cmd.s_cprompt;
+      cmd.s_cprompt = NULL;
+    }
+  if (cmd.s_dprompt)
+    {
+      free (set_dprompt);
+      set_dprompt = cmd.s_dprompt;
+      cmd.s_dprompt = NULL;
+    }
+  if (cmd.echo != -1)
+    set_echo = cmd.echo == STC_OFF ? 0 : 1;
+  if (cmd.s_endcmd)
+    set_endcmd = cmd.s_endcmd[0];
+  if (cmd.eject != -1)
+    msg (SW, _("EJECT is obsolete."));
+  if (cmd.errbrk != -1)
+    set_errorbreak = cmd.errbrk == STC_OFF ? 0 : 1;
+  if (cmd.helpwin != -1)
+    msg (SW, _("HELPWINDOWS is obsolete."));
+  if (cmd.inc != -1)
+    set_include = cmd.inc == STC_OFF ? 0 : 1;
+  if (cmd.menus != -1)
+    msg (MW, _("MENUS is obsolete."));
+  if (cmd.null != -1)
+    set_nullline = cmd.null == STC_OFF ? 0 : 1;
+  if (cmd.more != -1)
+    set_more = cmd.more == STC_OFF ? 0 : 1;
+  if (cmd.prtr != -1)
+    outp_enable_device (cmd.prtr == STC_OFF ? 0 : 1, OUTP_DEV_PRINTER);
+  if (cmd.s_prompt)
+    {
+      free (set_prompt);
+      set_prompt = cmd.s_prompt;
+      cmd.s_prompt = NULL;
+    }
+  if (cmd.ptrans != -1)
+    msg (SW, _("PTRANSLATE is obsolete."));
+  if (cmd.runrev != -1)
+    msg (SW, "RUNREVIEW is obsolete.");
+  if (cmd.safe == STC_ON)
+    set_safer = 1;
+  if (cmd.xsort != -1)
+    msg (SW, _("XSORT is obsolete."));
+
+  free_set (&cmd);
+
+  return CMD_SUCCESS;
+}
+
+/* Sets custom currency specifier CC having name CC_NAME ('A' through
+   'E') to correspond to the settings in CC_STRING. */
+static int
+set_ccx (const char *cc_string, struct set_cust_currency * cc, int cc_name)
+{
+  if (strlen (cc_string) > 16)
+    {
+      msg (SE, _("CC%c: Length of custom currency string `%s' (%d) "
+                "exceeds maximum length of 16."),
+          cc_name, cc_string, strlen (cc_string));
+      return 0;
+    }
+
+  /* Determine separators. */
+  {
+    const char *sp;
+    int n_commas, n_periods;
+  
+    /* Count the number of commas and periods.  There must be exactly
+       three of one or the other. */
+    n_commas = n_periods = 0;
+    for (sp = cc_string; *sp; sp++)
+      if (*sp == ',')
+       n_commas++;
+      else if (*sp == '.')
+       n_periods++;
+  
+    if (!((n_commas == 3) ^ (n_periods == 3)))
+      {
+       msg (SE, _("CC%c: Custom currency string `%s' does not contain "
+                  "exactly three periods or commas (not both)."),
+            cc_name, cc_string);
+       return 0;
+      }
+    else if (n_commas == 3)
+      {
+       cc->decimal = '.';
+       cc->grouping = ',';
+      }
+    else
+      {
+       cc->decimal = ',';
+       cc->grouping = '.';
+      }
+  }
+  
+  /* Copy cc_string to cc, changing separators to nulls. */
+  {
+    char *cp;
+    
+    strcpy (cc->buf, cc_string);
+    cp = cc->neg_prefix = cc->buf;
+
+    while (*cp++ != cc->grouping)
+      ;
+    cp[-1] = '\0';
+    cc->prefix = cp;
+
+    while (*cp++ != cc->grouping)
+      ;
+    cp[-1] = '\0';
+    cc->suffix = cp;
+
+    while (*cp++ != cc->grouping)
+      ;
+    cp[-1] = '\0';
+    cc->neg_suffix = cp;
+  }
+  
+  return 1;
+}
+
+/* Sets *SETTING, which is a combination of SET_ROUTE_* bits that
+   indicates what to do with some sort of output, to the value
+   indicated by Q, which is a value provided by the input parser. */
+static void
+set_routing (int q, int *setting)
+{
+  switch (q)
+    {
+    case STC_ON:
+      *setting |= SET_ROUTE_DISABLE;
+      break;
+    case STC_OFF:
+      *setting &= ~SET_ROUTE_DISABLE;
+      break;
+    case STC_TERMINAL:
+      *setting &= ~(SET_ROUTE_LISTING | SET_ROUTE_OTHER);
+      *setting |= SET_ROUTE_SCREEN;
+      break;
+    case STC_LISTING:
+      *setting &= ~SET_ROUTE_SCREEN;
+      *setting |= SET_ROUTE_LISTING | SET_ROUTE_OTHER;
+      break;
+    case STC_BOTH:
+      *setting |= SET_ROUTE_SCREEN | SET_ROUTE_LISTING | SET_ROUTE_OTHER;
+      break;
+    case STC_NONE:
+      *setting &= ~(SET_ROUTE_SCREEN | SET_ROUTE_LISTING | SET_ROUTE_OTHER);
+      break;
+    default:
+      assert (0);
+    }
+}
+
+static int
+stc_custom_pager (struct cmd_set *cmd unused)
+{
+  lex_match ('=');
+#if !USE_INTERNAL_PAGER
+  if (lex_match_id ("OFF"))
+    {
+      if (set_pager)
+       free (set_pager);
+      set_pager = NULL;
+    }
+  else
+    {
+      if (!lex_force_string ())
+       return 0;
+      if (set_pager)
+       free (set_pager);
+      set_pager = xstrdup (ds_value (&tokstr));
+      lex_get ();
+    }
+  return 1;
+#else /* USE_INTERNAL_PAGER */
+  if (match_id (OFF))
+    return 1;
+  msg (SW, "External pagers not supported.");
+  return 0;
+#endif /* USE_INTERNAL_PAGER */
+}
+
+/* Parses the BLANKS subcommand, which controls the value that
+   completely blank fields in numeric data imply.  X, Wnd: Syntax is
+   SYSMIS or a numeric value; PC+: Syntax is '.', which is equivalent
+   to SYSMIS, or a numeric value. */
+static int
+stc_custom_blanks (struct cmd_set *cmd unused)
+{
+  lex_match ('=');
+  if ((token == T_ID && lex_id_match ("SYSMIS", tokid))
+      || (token == T_STRING && !strcmp (tokid, ".")))
+    {
+      lex_get ();
+      set_blanks = SYSMIS;
+    }
+  else
+    {
+      if (!lex_force_num ())
+       return 0;
+      set_blanks = tokval;
+      lex_get ();
+    }
+  return 1;
+}
+
+static int
+stc_custom_length (struct cmd_set *cmd unused)
+{
+  int page_length;
+
+  lex_match ('=');
+  if (lex_match_id ("NONE"))
+    page_length = -1;
+  else
+    {
+      if (!lex_force_int ())
+       return 0;
+      if (lex_integer () < 1)
+       {
+         msg (SE, _("LENGTH must be at least 1."));
+         return 0;
+       }
+      page_length = lex_integer ();
+      lex_get ();
+    }
+
+  /* FIXME: Set page length. */
+  return 1;
+}
+
+static int
+stc_custom_results (struct cmd_set *cmd unused)
+{
+  struct tuple
+    {  
+      const char *s;   
+      int v;
+    };
+
+  static struct tuple tab[] =
+    {
+      {"ON", STC_ON},
+      {"OFF", STC_OFF},
+      {"TERMINAL", STC_TERMINAL},
+      {"LISTING", STC_LISTING},
+      {"BOTH", STC_BOTH},
+      {"NONE", STC_NONE},
+      {NULL, 0},
+    };
+
+  struct tuple *t;
+
+  lex_match ('=');
+
+  if (token != T_ID)
+    {
+      msg (SE, _("Missing identifier in RESULTS subcommand."));
+      return 0;
+    }
+  
+  for (t = tab; t->s; t++)
+    if (lex_id_match (t->s, tokid))
+      {
+       lex_get ();
+       set_routing (t->v, &set_results);
+       return 1;
+      }
+  msg (SE, _("Unrecognized identifier in RESULTS subcommand."));
+  return 0;
+}
+
+static int
+stc_custom_seed (struct cmd_set *cmd unused)
+{
+  lex_match ('=');
+  if (lex_match_id ("RANDOM"))
+    set_seed = NOT_LONG;
+  else
+    {
+      if (!lex_force_num ())
+       return 0;
+      set_seed = tokval;
+      lex_get ();
+    }
+  return 1;
+}
+
+static int
+stc_custom_width (struct cmd_set *cmd unused)
+{
+  int page_width;
+
+  lex_match ('=');
+  if (lex_match_id ("NARROW"))
+    page_width = 79;
+  else if (lex_match_id ("WIDE"))
+    page_width = 131;
+  else
+    {
+      if (!lex_force_int ())
+       return 0;
+      if (lex_integer () < 1)
+       {
+         msg (SE, _("WIDTH must be at least 1."));
+         return 0;
+       }
+      page_width = lex_integer ();
+      lex_get ();
+    }
+
+  /* FIXME: Set page width. */
+  return 1;
+}
+
+/* Parses FORMAT subcommand, which consists of a numeric format
+   specifier. */
+static int
+stc_custom_format (struct cmd_set *cmd unused)
+{
+  struct fmt_spec fmt;
+
+  lex_match ('=');
+  if (!parse_format_specifier (&fmt, 0))
+    return 0;
+  if ((formats[fmt.type].cat & FCAT_STRING) != 0)
+    {
+      msg (SE, _("FORMAT requires numeric output format as an argument.  "
+                "Specified format %s is of type string."),
+          fmt_to_string (&fmt));
+      return 0;
+    }
+
+  set_format = fmt;
+  return 1;
+}
+
+static int
+stc_custom_journal (struct cmd_set *cmd unused)
+{
+  lex_match ('=');
+  if (lex_match_id ("ON"))
+    set_journaling = 1;
+  else if (lex_match_id ("OFF"))
+    set_journaling = 0;
+  if (token == T_STRING)
+    {
+      set_journal = xstrdup (ds_value (&tokstr));
+      lex_get ();
+    }
+  return 1;
+}
+
+/* Parses COLOR subcommand.  PC+: either ON or OFF or two or three
+   comma-delimited numbers inside parentheses. */
+static int
+stc_custom_color (struct cmd_set *cmd unused)
+{
+  msg (MW, "COLOR is obsolete.");
+
+  lex_match ('=');
+  if (!lex_match_id ("ON") && !lex_match_id ("YES") && !lex_match_id ("OFF") && !lex_match_id ("NO"))
+    {
+      if (!lex_force_match ('('))
+       return 0;
+      if (!lex_match ('*'))
+       {
+         if (!lex_force_int ())
+           return 0;
+         if (lex_integer () < 0 || lex_integer () > 15)
+           {
+             msg (SE, _("Text color must be in range 0-15."));
+             return 0;
+           }
+         lex_get ();
+       }
+      if (!lex_force_match (','))
+       return 0;
+      if (!lex_match ('*'))
+       {
+         if (!lex_force_int ())
+           return 0;
+         if (lex_integer () < 0 || lex_integer () > 7)
+           {
+             msg (SE, _("Background color must be in range 0-7."));
+             return 0;
+           }
+         lex_get ();
+       }
+      if (lex_match (',') && !lex_match ('*'))
+       {
+         if (!lex_force_int ())
+           return 0;
+         if (lex_integer () < 0 || lex_integer () > 7)
+           {
+             msg (SE, _("Border color must be in range 0-7."));
+             return 0;
+           }
+         lex_get ();
+       }
+      if (!lex_force_match (')'))
+       return 0;
+    }
+  return 1;
+}
+
+static int
+stc_custom_listing (struct cmd_set *cmd unused)
+{
+  lex_match ('=');
+  if (lex_match_id ("ON") || lex_match_id ("YES"))
+    outp_enable_device (1, OUTP_DEV_LISTING);
+  else if (lex_match_id ("OFF") || lex_match_id ("NO"))
+    outp_enable_device (0, OUTP_DEV_LISTING);
+  else
+    {
+      /* FIXME */
+    }
+
+  return 0;
+}
+
+static int
+stc_custom_disk (struct cmd_set *cmd unused)
+{
+  stc_custom_listing (cmd);
+  return 0;
+}
+
+static int
+stc_custom_log (struct cmd_set *cmd unused)
+{ 
+  stc_custom_journal (cmd);
+  return 0;
+}
+
+static int
+stc_custom_rcolor (struct cmd_set *cmd unused)
+{
+  msg (SW, _("RCOLOR is obsolete."));
+
+  lex_match ('=');
+  if (!lex_force_match ('('))
+    return 0;
+
+  if (!lex_match ('*'))
+    {
+      if (!lex_force_int ())
+       return 0;
+      if (lex_integer () < 0 || lex_integer () > 6)
+       {
+         msg (SE, _("Lower window color must be between 0 and 6."));
+         return 0;
+       }
+      lex_get ();
+    }
+  if (!lex_force_match (','))
+    return 0;
+
+  if (!lex_match ('*'))
+    {
+      if (!lex_force_int ())
+       return 0;
+      if (lex_integer () < 0 || lex_integer () > 6)
+       {
+         msg (SE, _("Upper window color must be between 0 and 6."));
+         return 0;
+       }
+      lex_get ();
+    }
+
+  if (lex_match (',') && !lex_match ('*'))
+    {
+      if (!lex_force_int ())
+       return 0;
+      if (lex_integer () < 0 || lex_integer () > 6)
+       {
+         msg (SE, _("Frame color must be between 0 and 6."));
+         return 0;
+       }
+      lex_get ();
+    }
+  return 1;
+}
+
+static int
+stc_custom_viewlength (struct cmd_set *cmd unused)
+{
+  if (lex_match_id ("MINIMUM"))
+    set_viewlength = 25;
+  else if (lex_match_id ("MEDIAN"))
+    set_viewlength = 43;       /* This is not correct for VGA displays. */
+  else if (lex_match_id ("MAXIMUM"))
+    set_viewlength = 43;
+  else
+    {
+      if (!lex_force_int ())
+       return 0;
+#if __MSDOS__
+      if (lex_integer () >= (43 + 25) / 2)
+       set_viewlength = 43;
+      else
+       set_viewlength = 25;
+#else /* not dos */
+      set_viewlength = lex_integer ();
+#endif /* not dos */
+      lex_get ();
+    }
+
+#if __MSDOS__
+  msg (SW, _("VIEWLENGTH not implemented."));
+#endif /* dos */
+  return 1;
+}
+
+static int
+stc_custom_workdev (struct cmd_set *cmd unused)
+{
+  char c[2];
+
+  msg (SW, _("WORKDEV is obsolete."));
+
+  c[1] = 0;
+  for (*c = 'A'; *c <= 'Z'; (*c)++)
+    if (token == T_ID && lex_id_match (c, tokid))
+      {
+       lex_get ();
+       return 1;
+      }
+  msg (SE, _("Drive letter expected in WORKDEV subcommand."));
+  return 0;
+}
+
+\f
+/* GSET. */
+
+int
+cmd_gset (void)
+{
+  /* FIXME */
+  return CMD_FAILURE;
+}
+
+/*
+   Local Variables:
+   mode: c
+   End:
+*/
diff --git a/src/settings.h b/src/settings.h
new file mode 100644 (file)
index 0000000..3a8eaed
--- /dev/null
@@ -0,0 +1,253 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !settings_h
+#define settings_h 1
+
+/* Table of mode settings (x=X, w=Windows, p=PC+, f=has relevance for
+   PSPP):
+
+   AUTOMENU: p
+   BEEP: p
+   BLANKS: xwpf
+   BLKSIZE: x (only on SHOW, not on SET)
+   BLOCK: xwp
+   BOX/BOXSTRING: xwp
+   BUFNO: x (only on SHOW, not on SET)
+   CASE: xw
+   CCA...CCE: xwf
+   COLOR: p
+   COMP/COMPRESSION: xwpf (meaning varies between p and xw)
+   CPI: xwp
+   CPROMPT: pf
+   DECIMAL: wf
+   DPROMPT: f
+   ECHO: pf
+   EJECT: p
+   EMULATION: f
+   ENDCMD: xpf
+   ERRORBREAK: pf
+   ERRORS: wf
+   FORMAT: xwf
+   HEADERS: xwf
+   HELPWINDOWS: p
+   HIGHRES: w
+   HISTOGRAM: xp
+   INCLUDE: pf
+   JOURNAL: wf (equivalent to LOG)
+   LENGTH: xwp
+   LISTING: xpf
+   LOG: pf (equivalent to JOURNAL)
+   LOWRES: w
+   LPI: xwp
+   MENUS: p
+   MESSAGES: wf
+   MEXPAND: xwf
+   MITERATE: xwf
+   MNEST: xwf
+   MORE: pf
+   MPRINT: xwf
+   MXERRS: xf
+   MXLOOPS: xwf
+   MXMEMORY: w
+   MXWARNS: xwf
+   N: xw (only on SHOW, not on SET)
+   NULLINE: xpf
+   NUMBERED: x (only on SHOW, not on SET)
+   PAGER: f
+   PRINTBACK: xwf
+   PRINTER: pf
+   PROMPT: pf
+   PTRANSLATE: p
+   RCOLOR: p
+   RESULTS: wpf (semantics differ)
+   RUNREVIEW: p
+   SCOMP/SCOMPRESSION: xwf
+   SCREEN: pf
+   SCRIPTTAB: xw
+   SEED: xwpf (semantics differ)
+   SYSMIS: xwf (only on SHOW, not on SET)
+   TBFONTS: xw
+   TB1: xw
+   TB2: x
+   UNDEFINED: xwf
+   VIEWLENGTH: pf
+   VIEWWIDTH: f
+   WEIGHT: xwf (only on SHOW, not on SET)
+   WIDTH: xwp
+   WORKDEV: p
+   WORKSPACE: w
+   XSORT: x
+   $VARS: wf (only on SHOW, not on SET)
+
+ */
+
+#include <float.h>
+
+/* The value that blank numeric fields are set to when read in;
+   normally SYSMIS. */
+extern double set_blanks;
+
+/* Describes one custom currency specification. */
+struct set_cust_currency
+  {
+    char buf[32];              /* Buffer for strings. */
+    char *neg_prefix;          /* Negative prefix. */
+    char *prefix;              /* Prefix. */
+    char *suffix;              /* Suffix. */
+    char *neg_suffix;          /* Negative suffix. */
+    int decimal;               /* Decimal point. */
+    int grouping;              /* Grouping character. */
+  };
+
+/* CCA through CCE. */
+extern struct set_cust_currency set_cc[5];
+
+/* Whether the active file should be compressed. */
+extern int set_compression;
+
+/* Characters per inch (horizontal). */
+extern int set_cpi;
+
+/* Continuation prompt. */
+extern char *set_cprompt;
+
+/* The character used for a decimal point: ',' or '.'.  Only respected
+   for data input and output. */
+extern int set_decimal;
+
+/* The character used for grouping in numbers: '.' or ','; the
+   opposite of set_decimal.  Only used in COMMA data input and
+   output. */
+extern int set_grouping;
+
+/* Prompt used for lines between BEGIN DATA and END DATA. */
+extern char *set_dprompt;
+
+/* Whether we echo commands to the listing file/printer; 0=no, 1=yes. */
+extern int set_echo;
+
+/* The character used to terminate commands. */
+extern int set_endcmd;
+
+/* Types of routing. */
+enum
+  {
+    SET_ROUTE_SCREEN = 001,    /* Output to screen devices? */
+    SET_ROUTE_LISTING = 002,   /* Output to listing devices? */
+    SET_ROUTE_OTHER = 004,     /* Output to other devices? */
+    SET_ROUTE_DISABLE = 010    /* Disable output--overrides all other bits. */
+  };
+
+/* Routing for errors, messages, and procedure results. */
+extern int set_errors, set_messages, set_results;
+
+/* Whether an error stops execution; 0=no, 1=yes. */
+extern int set_errorbreak;
+
+/* Default format for variables created by transformations and by DATA
+   LIST {FREE,LIST}. */
+extern struct fmt_spec set_format;
+
+/* I don't know what this setting means; 0=no, 1=yes, 2=blank. */
+extern int set_headers;
+
+/* If set_echo is on, whether commands from include files are echoed;
+ * 0=no, 1=yes. */
+extern int set_include;
+
+/* Journal file's name. */
+extern char *set_journal;
+
+/* Whether we're journaling. */
+extern int set_journaling;
+
+/* Lines per inch (vertical). */
+extern int set_lpi;
+
+/* 0=macro expansion is disabled, 1=macro expansion is enabled. */
+extern int set_mexpand;
+
+/* Maximum number of iterations in a macro loop. */
+extern int set_miterate;
+
+/* Maximum nesting level for macros. */
+extern int set_mnest;
+
+/* Whether we pause after each screen of output; 0=no, 1=yes. */
+extern int set_more;
+
+/* Independent of set_printback, controls whether the commands
+   generated by macro invocations are displayed. */
+extern int set_mprint;
+
+/* Maximum number of errors. */
+extern int set_mxerrs;
+
+/* Implied limit of unbounded loop. */
+extern int set_mxloops;
+
+/* Maximum number of warnings + errors. */
+extern int set_mxwarns;
+
+/* Whether a blank line is a command terminator; 0=no, 1=yes. */
+extern int set_nullline;
+
+/* Whether commands are written to the display; 0=off, 1=on. */
+extern int set_printback;
+
+#if !USE_INTERNAL_PAGER
+/* Name of the pager program. */
+extern char *set_pager;
+#endif /* !USE_INTERNAL_PAGER */
+
+/* The command prompt. */
+extern char *set_prompt;
+
+/* Name of the results file. */
+extern char *set_results_file;
+
+/* Whether to allow certain unsafe operations.  Cannot be unset after
+   it is set. */
+extern int set_safer;
+
+/* Whether save files should be compressed by default. */
+extern int set_scompression;
+
+/* The random number seed; NOT_LONG if we want a "random" random
+   number seed.  */
+extern long set_seed;
+
+/* 1=The user has modified or made use of the random number seed. */
+extern int set_seed_used;
+
+/* 1=Turn on some heuristics that make testing PSPP for correct
+   workings a little easier. */
+extern int set_testing_mode;
+
+/* Whether to warn on undefined values in numeric data. */
+extern int set_undefined;
+
+/* Requested "view length" in lines. */
+extern int set_viewlength;
+
+/* Screen width. */
+extern int set_viewwidth;
+
+#endif /* !settings_h */
diff --git a/src/sfm-read.c b/src/sfm-read.c
new file mode 100644 (file)
index 0000000..f6353d8
--- /dev/null
@@ -0,0 +1,1540 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include "alloc.h"
+#include "avl.h"
+#include "error.h"
+#include "file-handle.h"
+#include "format.h"
+#include "getline.h"
+#include "magic.h"
+#include "misc.h"
+#include "sfm.h"
+#include "sfmP.h"
+#include "str.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* PORTME: This file may require substantial revision for those
+   systems that don't meet the typical 32-bit integer/64-bit double
+   model.  It's kinda hard to tell without having one of them on my
+   desk.  */
+
+/* sfm's file_handle extension. */
+struct sfm_fhuser_ext
+  {
+    FILE *file;                        /* Actual file. */
+    int opened;                        /* Reference count. */
+
+    struct dictionary *dict;   /* File's dictionary. */
+
+    int reverse_endian;                /* 1=file has endianness opposite us. */
+    int case_size;             /* Number of `values's per case. */
+    long ncases;               /* Number of cases, -1 if unknown. */
+    int compressed;            /* 1=compressed, 0=not compressed. */
+    double bias;               /* Compression bias, usually 100.0. */
+    int weight_index;          /* 0-based index of weighting variable, or -1. */
+
+    /* File's special constants. */
+    flt64 sysmis;
+    flt64 highest;
+    flt64 lowest;
+
+    /* Uncompression buffer. */
+    flt64 *buf;                        /* Buffer data. */
+    flt64 *ptr;                        /* Current location in buffer. */
+    flt64 *end;                        /* End of buffer data. */
+
+    /* Compression instruction octet. */
+    unsigned char x[sizeof (flt64)];
+    /* Current instruction octet. */
+    unsigned char *y;          /* Location in current instruction octet. */
+  };
+
+static struct fh_ext_class sfm_r_class;
+
+#if GLOBAL_DEBUGGING
+void dump_dictionary (struct dictionary * dict);
+#endif
+\f
+/* Utilities. */
+
+/* bswap_int32(): Reverse the byte order of 32-bit integer *X. */
+#if __linux__
+#include <asm/byteorder.h>
+static inline void
+bswap_int32 (int32 * x)
+{
+  *x = ntohl (*x);
+}
+#else /* not Linux */
+static inline void
+bswap_int32 (int32 * x)
+{
+  unsigned char *y = (char *) x;
+  unsigned char t;
+  t = y[0];
+  y[0] = y[3];
+  y[3] = t;
+  t = y[1];
+  y[1] = y[2];
+  y[2] = t;
+}
+#endif /* not Linux */
+
+/* Reverse the byte order of 64-bit floating point *X. */
+static inline void
+bswap_flt64 (flt64 * x)
+{
+  /* Note that under compilers of any quality, half of this function
+     should optimize out as dead code. */
+  unsigned char *y = (char *) x;
+
+  if (sizeof (flt64) == 8)
+    {
+      unsigned char t;
+      t = y[0];
+      y[0] = y[7];
+      y[7] = t;
+      t = y[1];
+      y[1] = y[6];
+      y[6] = t;
+      t = y[2];
+      y[2] = y[5];
+      y[5] = t;
+      t = y[3];
+      y[3] = y[4];
+      y[4] = t;
+    }
+  else
+    {
+      unsigned char t;
+      size_t x;
+
+      for (x = 0; x < sizeof (flt64) / 2; x++)
+       {
+         t = y[x];
+         y[x] = y[sizeof (flt64) - x];
+         y[sizeof (flt64) - x] = t;
+       }
+    }
+}
+
+static void
+corrupt_msg (int class, const char *format,...)
+  __attribute__ ((format (printf, 2, 3)));
+
+/* Displays a corrupt sysfile error. */
+static void
+corrupt_msg (int class, const char *format,...)
+{
+  char buf[1024];
+  
+  {
+    va_list args;
+
+    va_start (args, format);
+    vsnprintf (buf, 1024, format, args);
+    va_end (args);
+  }
+  
+  {
+    struct error e;
+
+    e.class = class;
+    getl_location (&e.where.filename, &e.where.line_number);
+    e.title = _("corrupt system file: ");
+    e.text = buf;
+
+    err_vmsg (&e);
+  }
+}
+
+/* Closes a system file after we're done with it. */
+static void
+sfm_close (struct file_handle * h)
+{
+  struct sfm_fhuser_ext *ext = h->ext;
+
+  ext->opened--;
+  assert (ext->opened == 0);
+  if (EOF == fclose (ext->file))
+    msg (ME, _("%s: Closing system file: %s."), h->fn, strerror (errno));
+  free (ext->buf);
+  free (h->ext);
+}
+
+/* Closes a system file if we're done with it. */
+void
+sfm_maybe_close (struct file_handle *h)
+{
+  struct sfm_fhuser_ext *ext = h->ext;
+
+  if (ext->opened == 1)
+    fh_close_handle (h);
+  else
+    ext->opened--;
+}
+\f
+/* Dictionary reader. */
+
+static void *bufread (struct file_handle * handle, void *buf, size_t nbytes,
+                     size_t minalloc);
+
+static int read_header (struct file_handle * h, struct sfm_read_info * inf);
+static int parse_format_spec (struct file_handle * h, int32 s,
+                             struct fmt_spec * v, struct variable *vv);
+static int read_value_labels (struct file_handle * h, struct variable ** var_by_index);
+static int read_variables (struct file_handle * h, struct variable *** var_by_index);
+static int read_machine_int32_info (struct file_handle * h, int size, int count);
+static int read_machine_flt64_info (struct file_handle * h, int size, int count);
+static int read_documents (struct file_handle * h);
+
+/* Displays the message X with corrupt_msg, then jumps to the lossage
+   label. */
+#define lose(X)                                        \
+       do                                      \
+         {                                     \
+           corrupt_msg X;                      \
+           goto lossage;                       \
+         }                                     \
+       while (0)
+
+/* Calls bufread with the specified arguments, and jumps to lossage if
+   the read fails. */
+#define assertive_bufread(a,b,c,d)             \
+       do                                      \
+         {                                     \
+           if (!bufread (a,b,c,d))             \
+             goto lossage;                     \
+         }                                     \
+       while (0)
+
+/* Reads the dictionary from file with handle H, and returns it in a
+   dictionary structure.  This dictionary may be modified in order to
+   rename, reorder, and delete variables, etc. */
+struct dictionary *
+sfm_read_dictionary (struct file_handle * h, struct sfm_read_info * inf)
+{
+  /* The file handle extension record. */
+  struct sfm_fhuser_ext *ext;
+
+  /* Allows for quick reference to variables according to indexes
+     relative to position within a case. */
+  struct variable **var_by_index = NULL;
+
+  /* Check whether the file is already open. */
+  if (h->class == &sfm_r_class)
+    {
+      ext = h->ext;
+      ext->opened++;
+      return ext->dict;
+    }
+  else if (h->class != NULL)
+    {
+      msg (ME, _("Cannot read file %s as system file: already opened for %s."),
+          fh_handle_name (h), h->class->name);
+      return NULL;
+    }
+
+  msg (VM (1), _("%s: Opening system-file handle %s for reading."),
+       fh_handle_filename (h), fh_handle_name (h));
+  
+  /* Open the physical disk file. */
+  ext = xmalloc (sizeof (struct sfm_fhuser_ext));
+  ext->file = fopen (h->norm_fn, "rb");
+  if (ext->file == NULL)
+    {
+      msg (ME, _("An error occurred while opening \"%s\" for reading "
+          "as a system file: %s."), h->fn, strerror (errno));
+      err_cond_fail ();
+      free (ext);
+      return NULL;
+    }
+
+  /* Initialize the sfm_fhuser_ext structure. */
+  h->class = &sfm_r_class;
+  h->ext = ext;
+  ext->dict = NULL;
+  ext->buf = ext->ptr = ext->end = NULL;
+  ext->y = ext->x + sizeof ext->x;
+  ext->opened = 1;
+
+  /* Default special constants. */
+  ext->sysmis = -FLT64_MAX;
+  ext->highest = FLT64_MAX;
+  ext->lowest = second_lowest_flt64;
+
+  /* Read the header. */
+  if (!read_header (h, inf))
+    goto lossage;
+
+  /* Read about the variables. */
+  if (!read_variables (h, &var_by_index))
+    goto lossage;
+
+  /* Handle weighting. */
+  if (ext->weight_index != -1)
+    {
+      struct variable *wv = var_by_index[ext->weight_index];
+
+      if (wv == NULL)
+       lose ((ME, _("%s: Weighting variable may not be a continuation of "
+              "a long string variable."), h->fn));
+      else if (wv->type == ALPHA)
+       lose ((ME, _("%s: Weighting variable may not be a string variable."),
+              h->fn));
+
+      strcpy (ext->dict->weight_var, wv->name);
+    }
+  else
+    ext->dict->weight_var[0] = 0;
+
+  /* Read records of types 3, 4, 6, and 7. */
+  for (;;)
+    {
+      int32 rec_type;
+
+      assertive_bufread (h, &rec_type, sizeof rec_type, 0);
+      if (ext->reverse_endian)
+       bswap_int32 (&rec_type);
+
+      switch (rec_type)
+       {
+       case 3:
+         if (!read_value_labels (h, var_by_index))
+           goto lossage;
+         break;
+
+       case 4:
+         lose ((ME, _("%s: Orphaned variable index record (type 4).  Type 4 "
+                "records must always immediately follow type 3 records."),
+                h->fn));
+
+       case 6:
+         if (!read_documents (h))
+           goto lossage;
+         break;
+
+       case 7:
+         {
+           struct
+             {
+               int32 subtype P;
+               int32 size P;
+               int32 count P;
+             }
+           data;
+
+           int skip = 0;
+
+           assertive_bufread (h, &data, sizeof data, 0);
+           if (ext->reverse_endian)
+             {
+               bswap_int32 (&data.subtype);
+               bswap_int32 (&data.size);
+               bswap_int32 (&data.count);
+             }
+
+           /*if(data.size != sizeof(int32) && data.size != sizeof(flt64))
+              lose((ME, "%s: Element size in record type 7, subtype %d, is "
+              "not either the size of IN (%d) or OBS (%d); actual value "
+              "is %d.",
+              h->fn, data.subtype, sizeof(int32), sizeof(flt64),
+              data.size)); */
+
+           switch (data.subtype)
+             {
+             case 3:
+               if (!read_machine_int32_info (h, data.size, data.count))
+                 goto lossage;
+               break;
+
+             case 4:
+               if (!read_machine_flt64_info (h, data.size, data.count))
+                 goto lossage;
+               break;
+
+             case 5:
+             case 6:
+             case 11: /* ?? Used by SPSS 8.0. */
+               skip = 1;
+               break;
+
+             default:
+               msg (MW, _("%s: Unrecognized record type 7, subtype %d "
+                    "encountered in system file."), h->fn, data.subtype);
+               skip = 1;
+             }
+
+           if (skip)
+             {
+               void *x = bufread (h, NULL, data.size * data.count, 0);
+               if (x == NULL)
+                 goto lossage;
+               free (x);
+             }
+         }
+         break;
+
+       case 999:
+         {
+           int32 filler;
+
+           assertive_bufread (h, &filler, sizeof filler, 0);
+           goto break_out_of_loop;
+         }
+
+       default:
+         lose ((ME, _("%s: Unrecognized record type %d."), h->fn, rec_type));
+       }
+    }
+
+break_out_of_loop:
+  /* Come here on successful completion. */
+  msg (VM (2), _("Read system-file dictionary successfully."));
+    
+#if DEBUGGING
+  dump_dictionary (ext->dict);
+#endif
+  free (var_by_index);
+  return ext->dict;
+
+lossage:
+  /* Come here on unsuccessful completion. */
+  msg (VM (1), _("Error reading system-file header."));
+  
+  free (var_by_index);
+  fclose (ext->file);
+  if (ext && ext->dict)
+    free_dictionary (ext->dict);
+  free (ext);
+  h->class = NULL;
+  h->ext = NULL;
+  return NULL;
+}
+
+/* Read record type 7, subtype 3. */
+static int
+read_machine_int32_info (struct file_handle * h, int size, int count)
+{
+  struct sfm_fhuser_ext *ext = h->ext;
+
+  int32 data[8];
+  int file_endian;
+
+  int i;
+
+  if (size != sizeof (int32) || count != 8)
+    lose ((ME, _("%s: Bad size (%d) or count (%d) field on record type 7, "
+          "subtype 3.  Expected size %d, count 8."),
+          h->fn, size, count, sizeof (int32)));
+
+  assertive_bufread (h, data, sizeof data, 0);
+  if (ext->reverse_endian)
+    for (i = 0; i < 8; i++)
+      bswap_int32 (&data[i]);
+
+  /* PORTME: Check floating-point representation. */
+  switch (FPREP)
+    {
+    case FPREP_IEEE754:
+      if (data[4] != 1)
+       lose ((ME, _("%s: Floating-point representation in system file is not "
+              "IEEE-754.  PSPP cannot convert between floating-point "
+              "formats."), h->fn));
+      break;
+    default:
+      assert (0);
+    }
+
+  /* PORTME: Check recorded file endianness against intuited file
+     endianness. */
+  file_endian = endian;
+  if (ext->reverse_endian)
+    {
+      if (file_endian == BIG)
+       file_endian = LITTLE;
+      else if (file_endian == LITTLE)
+       file_endian = BIG;
+      else
+       assert (0);
+    }
+  if ((file_endian == BIG) ^ (data[6] == 1))
+    lose ((ME, _("%s: File-indicated endianness (%s) does not match endianness "
+          "intuited from file header (%s)."),
+          h->fn, file_endian == BIG ? _("big-endian") : _("little-endian"),
+          data[6] == 1 ? _("big-endian") : (data[6] == 2 ? _("little-endian")
+                                         : _("unknown"))));
+
+  /* PORTME: Character representation code. */
+  if (data[7] != 2 && data[7] != 3)
+    lose ((ME, _("%s: File-indicated character representation code (%s) is not "
+          "ASCII."), h->fn,
+       data[7] == 1 ? "EBCDIC" : (data[7] == 4 ? _("DEC Kanji") : _("Unknown"))));
+
+  return 1;
+
+lossage:
+  return 0;
+}
+
+/* Read record type 7, subtype 4. */
+static int
+read_machine_flt64_info (struct file_handle * h, int size, int count)
+{
+  struct sfm_fhuser_ext *ext = h->ext;
+
+  flt64 data[3];
+
+  int i;
+
+  if (size != sizeof (flt64) || count != 3)
+    lose ((ME, _("%s: Bad size (%d) or count (%d) field on record type 7, "
+          "subtype 4.  Expected size %d, count 8."),
+          h->fn, size, count, sizeof (flt64)));
+
+  assertive_bufread (h, data, sizeof data, 0);
+  if (ext->reverse_endian)
+    for (i = 0; i < 3; i++)
+      bswap_flt64 (&data[i]);
+
+  if (data[0] != SYSMIS || data[1] != FLT64_MAX
+      || data[2] != second_lowest_flt64)
+    {
+      ext->sysmis = data[0];
+      ext->highest = data[1];
+      ext->lowest = data[2];
+      msg (MW, _("%s: File-indicated value is different from internal value "
+                "for at least one of the three system values.  SYSMIS: "
+                "indicated %g, expected %g; HIGHEST: %g, %g; LOWEST: "
+                "%g, %g."),
+          h->fn, (double) data[0], (double) SYSMIS,
+          (double) data[1], (double) FLT64_MAX,
+          (double) data[2], (double) second_lowest_flt64);
+    }
+  
+  return 1;
+
+lossage:
+  return 0;
+}
+
+static int
+read_header (struct file_handle * h, struct sfm_read_info * inf)
+{
+  struct sfm_fhuser_ext *ext = h->ext; /* File extension strcut. */
+  struct sysfile_header hdr;           /* Disk buffer. */
+  struct dictionary *dict;             /* File dictionary. */
+  char prod_name[sizeof hdr.prod_name + 1];    /* Buffer for product name. */
+  int skip_amt;                        /* Amount of product name to omit. */
+  int i;
+
+  /* Create the dictionary. */
+  dict = ext->dict = xmalloc (sizeof *dict);
+  dict->var = NULL;
+  dict->var_by_name = NULL;
+  dict->nvar = 0;
+  dict->N = 0;
+  dict->nval = -1;             /* Unknown. */
+  dict->n_splits = 0;
+  dict->splits = NULL;
+  dict->weight_var[0] = 0;
+  dict->weight_index = -1;
+  dict->filter_var[0] = 0;
+  dict->label = NULL;
+  dict->n_documents = 0;
+  dict->documents = NULL;
+
+  /* Read header, check magic. */
+  assertive_bufread (h, &hdr, sizeof hdr, 0);
+  if (0 != strncmp ("$FL2", hdr.rec_type, 4))
+    lose ((ME, _("%s: Bad magic.  Proper system files begin with "
+                "the four characters `$FL2'. This file will not be read."),
+          h->fn));
+
+  /* Check eye-catcher string. */
+  memcpy (prod_name, hdr.prod_name, sizeof hdr.prod_name);
+  for (i = 0; i < 60; i++)
+    if (!isprint ((unsigned char) prod_name[i]))
+      prod_name[i] = ' ';
+  for (i = 59; i >= 0; i--)
+    if (!isgraph ((unsigned char) prod_name[i]))
+      {
+       prod_name[i] = '\0';
+       break;
+      }
+  prod_name[60] = '\0';
+  
+  {
+#define N_PREFIXES 2
+    static const char *prefix[N_PREFIXES] =
+      {
+       "@(#) SPSS DATA FILE",
+       "SPSS SYSTEM FILE.",
+      };
+
+    int i;
+
+    for (i = 0; i < N_PREFIXES; i++)
+      if (!strncmp (prefix[i], hdr.prod_name, strlen (prefix[i])))
+       {
+         skip_amt = strlen (prefix[i]);
+         break;
+       }
+  }
+  
+  /* Check endianness. */
+  /* PORTME: endianness. */
+  if (hdr.layout_code == 2)
+    ext->reverse_endian = 0;
+  else
+    {
+      bswap_int32 (&hdr.layout_code);
+      if (hdr.layout_code != 2)
+       lose ((ME, _("%s: File layout code has unexpected value %d.  Value "
+              "should be 2, in big-endian or little-endian format."),
+              h->fn, hdr.layout_code));
+
+      ext->reverse_endian = 1;
+      bswap_int32 (&hdr.case_size);
+      bswap_int32 (&hdr.compressed);
+      bswap_int32 (&hdr.weight_index);
+      bswap_int32 (&hdr.ncases);
+      bswap_flt64 (&hdr.bias);
+    }
+
+  /* Copy basic info and verify correctness. */
+  ext->case_size = hdr.case_size;
+  if (hdr.case_size <= 0 || ext->case_size > (INT_MAX
+                                             / (int) sizeof (union value) / 2))
+    lose ((ME, _("%s: Number of elements per case (%d) is not between 1 "
+          "and %d."), h->fn, hdr.case_size, INT_MAX / sizeof (union value) / 2));
+
+  ext->compressed = hdr.compressed;
+
+  ext->weight_index = hdr.weight_index - 1;
+  if (hdr.weight_index < 0 || hdr.weight_index > hdr.case_size)
+    lose ((ME, _("%s: Index of weighting variable (%d) is not between 0 "
+          "and number of elements per case (%d)."),
+          h->fn, hdr.weight_index, ext->case_size));
+
+  ext->ncases = hdr.ncases;
+  if (ext->ncases < -1 || ext->ncases > INT_MAX / 2)
+    lose ((ME, _("%s: Number of cases in file (%ld) is not between -1 and "
+          "%d."), h->fn, (long) ext->ncases, INT_MAX / 2));
+
+  ext->bias = hdr.bias;
+  if (ext->bias != 100.0)
+    corrupt_msg (MW, _("%s: Compression bias (%g) is not the usual "
+                "value of 100."), h->fn, ext->bias);
+
+  /* Make a file label only on the condition that the given label is
+     not all spaces or nulls. */
+  {
+    int i;
+
+    dict->label = NULL;
+    for (i = sizeof hdr.file_label - 1; i >= 0; i--)
+      if (!isspace ((unsigned char) hdr.file_label[i])
+         && hdr.file_label[i] != 0)
+       {
+         dict->label = xmalloc (i + 2);
+         memcpy (dict->label, hdr.file_label, i + 1);
+         dict->label[i + 1] = 0;
+         break;
+       }
+  }
+
+  if (inf)
+    {
+      char *cp;
+
+      memcpy (inf->creation_date, hdr.creation_date, 9);
+      inf->creation_date[9] = 0;
+
+      memcpy (inf->creation_time, hdr.creation_time, 8);
+      inf->creation_time[8] = 0;
+
+      if (!ext->reverse_endian)
+       inf->endianness = endian;
+      else
+       inf->endianness = endian == BIG ? LITTLE : BIG;
+
+      inf->compressed = hdr.compressed;
+
+      inf->ncases = hdr.ncases;
+
+      for (cp = &prod_name[skip_amt]; cp < &prod_name[60]; cp++)
+       if (isgraph ((unsigned char) *cp))
+         break;
+      strcpy (inf->product, cp);
+    }
+
+  return 1;
+
+lossage:
+  return 0;
+}
+
+/* Reads most of the dictionary from file H; also fills in the
+   associated VAR_BY_INDEX array. 
+
+   Note: the dictionary returned by this function has an invalid NVAL
+   element, also the VAR[] array does not have the FV and LV elements
+   set, however the NV elements *are* set.  This is because the caller
+   will probably modify the dictionary before reading it in from the
+   file.  Also, the get.* elements are set to appropriate values to
+   allow the file to be read.  */
+static int
+read_variables (struct file_handle * h, struct variable *** var_by_index)
+{
+  int i;
+
+  struct sfm_fhuser_ext *ext = h->ext; /* File extension record. */
+  struct dictionary *dict = ext->dict; /* Dictionary being constructed. */
+  struct sysfile_variable sv;          /* Disk buffer. */
+  int long_string_count = 0;   /* # of long string continuation
+                                  records still expected. */
+  int next_value = 0;          /* Index to next `value' structure. */
+
+  /* Allocate variables. */
+  dict->var = xmalloc (sizeof *dict->var * ext->case_size);
+  *var_by_index = xmalloc (sizeof **var_by_index * ext->case_size);
+
+  /* Read in the entry for each variable and use the info to
+     initialize the dictionary. */
+  for (i = 0; i < ext->case_size; i++)
+    {
+      struct variable *vv;
+      int j;
+
+      assertive_bufread (h, &sv, sizeof sv, 0);
+
+      if (ext->reverse_endian)
+       {
+         bswap_int32 (&sv.rec_type);
+         bswap_int32 (&sv.type);
+         bswap_int32 (&sv.has_var_label);
+         bswap_int32 (&sv.n_missing_values);
+         bswap_int32 (&sv.print);
+         bswap_int32 (&sv.write);
+       }
+
+      if (sv.rec_type != 2)
+       lose ((ME, _("%s: position %d: Bad record type (%d); "
+              "the expected value was 2."), h->fn, i, sv.rec_type));
+
+      /* If there was a long string previously, make sure that the
+        continuations are present; otherwise make sure there aren't
+        any. */
+      if (long_string_count)
+       {
+         if (sv.type != -1)
+           lose ((ME, _("%s: position %d: String variable does not have "
+                        "proper number of continuation records."), h->fn, i));
+
+         (*var_by_index)[i] = NULL;
+         long_string_count--;
+         continue;
+       }
+      else if (sv.type == -1)
+       lose ((ME, _("%s: position %d: Superfluous long string continuation "
+              "record."), h->fn, i));
+
+      /* Check fields for validity. */
+      if (sv.type < 0 || sv.type > 255)
+       lose ((ME, _("%s: position %d: Bad variable type code %d."),
+              h->fn, i, sv.type));
+      if (sv.has_var_label != 0 && sv.has_var_label != 1)
+       lose ((ME, _("%s: position %d: Variable label indicator field is not "
+              "0 or 1."), h->fn, i));
+      if (sv.n_missing_values < -3 || sv.n_missing_values > 3
+         || sv.n_missing_values == -1)
+       lose ((ME, _("%s: position %d: Missing value indicator field is not "
+                    "-3, -2, 0, 1, 2, or 3."), h->fn, i));
+
+      /* Construct internal variable structure, initialize critical bits. */
+      vv = (*var_by_index)[i] = dict->var[dict->nvar++] = xmalloc (sizeof *vv);
+      vv->index = dict->nvar - 1;
+      vv->foo = -1;
+      vv->label = NULL;
+      vv->val_lab = NULL;
+
+      /* Copy first character of variable name. */
+      if (!isalpha ((unsigned char) sv.name[0])
+         && sv.name[0] != '@' && sv.name[0] != '#')
+       lose ((ME, _("%s: position %d: Variable name begins with invalid "
+              "character."), h->fn, i));
+      if (islower ((unsigned char) sv.name[0]))
+       msg (MW, _("%s: position %d: Variable name begins with lowercase letter "
+            "%c."), h->fn, i, sv.name[0]);
+      if (sv.name[0] == '#')
+       msg (MW, _("%s: position %d: Variable name begins with octothorpe "
+                  "(`#').  Scratch variables should not appear in system "
+                  "files."), h->fn, i);
+      vv->name[0] = toupper ((unsigned char) (sv.name[0]));
+
+      /* Copy remaining characters of variable name. */
+      for (j = 1; j < 8; j++)
+       {
+         int c = (unsigned char) sv.name[j];
+
+         if (isspace (c))
+           break;
+         else if (islower (c))
+           {
+             msg (MW, _("%s: position %d: Variable name character %d is "
+                  "lowercase letter %c."), h->fn, i, j + 1, sv.name[j]);
+             vv->name[j] = toupper ((unsigned char) (c));
+           }
+         else if (isalnum (c) || c == '.' || c == '@'
+                  || c == '#' || c == '$' || c == '_')
+           vv->name[j] = c;
+         else
+           lose ((ME, _("%s: position %d: character `\\%03o' (%c) is not valid in a "
+                  "variable name."), h->fn, i, c, c));
+       }
+      vv->name[j] = 0;
+
+      /* Set type, width, and `left' fields and allocate `value'
+        indices. */
+      if (sv.type == 0)
+       {
+         vv->type = NUMERIC;
+         vv->width = 0;
+         vv->get.nv = 1;
+         vv->get.fv = next_value++;
+         vv->nv = 1;
+       }
+      else
+       {
+         vv->type = ALPHA;
+         vv->width = sv.type;
+         vv->nv = DIV_RND_UP (vv->width, MAX_SHORT_STRING);
+         vv->get.nv = DIV_RND_UP (vv->width, sizeof (flt64));
+         vv->get.fv = next_value;
+         next_value += vv->get.nv;
+         long_string_count = vv->get.nv - 1;
+       }
+      vv->left = (vv->name[0] == '#');
+
+      /* Get variable label, if any. */
+      if (sv.has_var_label == 1)
+       {
+         /* Disk buffer. */
+         int32 len;
+
+         /* Read length of label. */
+         assertive_bufread (h, &len, sizeof len, 0);
+         if (ext->reverse_endian)
+           bswap_int32 (&len);
+
+         /* Check len. */
+         if (len < 0 || len > 255)
+           lose ((ME, _("%s: Variable %s indicates variable label of invalid "
+                  "length %d."), h->fn, vv->name, len));
+
+         /* Read label into variable structure. */
+         vv->label = bufread (h, NULL, ROUND_UP (len, sizeof (int32)), len + 1);
+         if (vv->label == NULL)
+           goto lossage;
+         vv->label[len] = '\0';
+       }
+
+      /* Set missing values. */
+      if (sv.n_missing_values != 0)
+       {
+         flt64 mv[3];
+
+         if (vv->width > MAX_SHORT_STRING)
+           lose ((ME, _("%s: Long string variable %s may not have missing "
+                  "values."), h->fn, vv->name));
+
+         assertive_bufread (h, mv, sizeof *mv * abs (sv.n_missing_values), 0);
+
+         if (ext->reverse_endian && vv->type == NUMERIC)
+           for (j = 0; j < abs (sv.n_missing_values); j++)
+             bswap_flt64 (&mv[j]);
+
+         if (sv.n_missing_values > 0)
+           {
+             vv->miss_type = sv.n_missing_values;
+             if (vv->type == NUMERIC)
+               for (j = 0; j < sv.n_missing_values; j++)
+                 vv->missing[j].f = mv[j];
+             else
+               for (j = 0; j < sv.n_missing_values; j++)
+                 memcpy (vv->missing[j].s, &mv[j], vv->width);
+           }
+         else
+           {
+             int x = 0;
+
+             if (vv->type == ALPHA)
+               lose ((ME, _("%s: String variable %s may not have missing "
+                      "values specified as a range."), h->fn, vv->name));
+
+             if (mv[0] == ext->lowest)
+               {
+                 vv->miss_type = MISSING_LOW;
+                 vv->missing[x++].f = mv[1];
+               }
+             else if (mv[1] == ext->highest)
+               {
+                 vv->miss_type = MISSING_HIGH;
+                 vv->missing[x++].f = mv[0];
+               }
+             else
+               {
+                 vv->miss_type = MISSING_RANGE;
+                 vv->missing[x++].f = mv[0];
+                 vv->missing[x++].f = mv[1];
+               }
+
+             if (sv.n_missing_values == -3)
+               {
+                 vv->miss_type += 3;
+                 vv->missing[x++].f = mv[2];
+               }
+           }
+       }
+      else
+       vv->miss_type = MISSING_NONE;
+
+      if (!parse_format_spec (h, sv.print, &vv->print, vv)
+         || !parse_format_spec (h, sv.write, &vv->write, vv))
+       goto lossage;
+    }
+
+  /* Some consistency checks. */
+  if (long_string_count != 0)
+    lose ((ME, _("%s: Long string continuation records omitted at end of "
+          "dictionary."), h->fn));
+  if (next_value != ext->case_size)
+    lose ((ME, _("%s: System file header indicates %d variable positions but "
+          "%d were read from file."), h->fn, ext->case_size, next_value));
+  dict->var = xrealloc (dict->var, sizeof *dict->var * dict->nvar);
+
+  /* Construct AVL tree of dictionary in order to speed up later
+     processing and to check for duplicate varnames. */
+  dict->var_by_name = avl_create (NULL, cmp_variable, NULL);
+  for (i = 0; i < dict->nvar; i++)
+    if (NULL != avl_insert (dict->var_by_name, dict->var[i]))
+      lose ((ME, _("%s: Duplicate variable name `%s' within system file."),
+            h->fn, dict->var[i]->name));
+
+  return 1;
+
+lossage:
+  for (i = 0; i < dict->nvar; i++)
+    {
+      free (dict->var[i]->label);
+      free (dict->var[i]);
+    }
+  free (dict->var);
+  if (dict->var_by_name)
+    avl_destroy (dict->var_by_name, NULL);
+  free (dict);
+  ext->dict = NULL;
+
+  return 0;
+}
+
+/* Translates the format spec from sysfile format to internal
+   format. */
+static int
+parse_format_spec (struct file_handle *h, int32 s, struct fmt_spec *v, struct variable *vv)
+{
+  if ((size_t) ((s >> 16) & 0xff)
+      >= sizeof translate_fmt / sizeof *translate_fmt)
+    lose ((ME, _("%s: Bad format specifier byte (%d)."),
+          h->fn, (s >> 16) & 0xff));
+  
+  v->type = translate_fmt[(s >> 16) & 0xff];
+  v->w = (s >> 8) & 0xff;
+  v->d = s & 0xff;
+
+  /* FIXME?  Should verify the resulting specifier more thoroughly. */
+
+  if (v->type == -1)
+    lose ((ME, _("%s: Bad format specifier byte (%d)."),
+          h->fn, (s >> 16) & 0xff));
+  if ((vv->type == ALPHA) ^ ((formats[v->type].cat & FCAT_STRING) != 0))
+    lose ((ME, _("%s: %s variable %s has %s format specifier %s."),
+          h->fn, vv->type == ALPHA ? _("String") : _("Numeric"),
+          vv->name,
+          formats[v->type].cat & FCAT_STRING ? _("string") : _("numeric"),
+          formats[v->type].name));
+  return 1;
+
+lossage:
+  return 0;
+}
+
+/* Reads value labels from sysfile H and inserts them into the
+   associated dictionary. */
+int
+read_value_labels (struct file_handle * h, struct variable ** var_by_index)
+{
+  struct sfm_fhuser_ext *ext = h->ext; /* File extension record. */
+
+  flt64 *raw_label = NULL;     /* Array of raw label values. */
+  struct value_label **cooked_label = NULL;    /* Array of cooked labels. */
+  int32 n_labels;              /* Number of labels. */
+
+  struct variable **var = NULL;        /* Associated variables. */
+  int32 n_vars;                        /* Number of associated variables. */
+
+  int i;
+
+  /* First step: read the contents of the type 3 record and record its
+     contents. Note that we can't do much with the data since we
+     don't know yet whether it is of numeric or string type. */
+
+  /* Read number of labels. */
+  assertive_bufread (h, &n_labels, sizeof n_labels, 0);
+  if (ext->reverse_endian)
+    bswap_int32 (&n_labels);
+
+  /* Allocate memory. */
+  raw_label = xmalloc (sizeof *raw_label * n_labels);
+  cooked_label = xmalloc (sizeof *cooked_label * n_labels);
+  for (i = 0; i < n_labels; i++)
+    cooked_label[i] = NULL;
+
+  /* Read each value/label tuple. */
+  for (i = 0; i < n_labels; i++)
+    {
+      flt64 value;
+      unsigned char label_len;
+
+      int rem;
+
+      /* Read value, label length. */
+      assertive_bufread (h, &value, sizeof value, 0);
+      assertive_bufread (h, &label_len, 1, 0);
+      memcpy (&raw_label[i], &value, sizeof value);
+
+      /* Read label. */
+      cooked_label[i] = xmalloc (sizeof **cooked_label);
+      cooked_label[i]->s = xmalloc (label_len + 1);
+      assertive_bufread (h, cooked_label[i]->s, label_len, 0);
+      cooked_label[i]->s[label_len] = 0;
+
+      /* Skip padding. */
+      rem = REM_RND_UP (label_len + 1, sizeof (flt64));
+      if (rem)
+       assertive_bufread (h, &value, rem, 0);
+    }
+
+  /* Second step: Read the type 4 record that has the list of
+     variables to which the value labels are to be applied. */
+
+  /* Read record type of type 4 record. */
+  {
+    int32 rec_type;
+    
+    assertive_bufread (h, &rec_type, sizeof rec_type, 0);
+    if (ext->reverse_endian)
+      bswap_int32 (&rec_type);
+    
+    if (rec_type != 4)
+      lose ((ME, _("%s: Variable index record (type 4) does not immediately "
+            "follow value label record (type 3) as it ought."), h->fn));
+  }
+
+  /* Read number of variables associated with value label from type 4
+     record. */
+  assertive_bufread (h, &n_vars, sizeof n_vars, 0);
+  if (ext->reverse_endian)
+    bswap_int32 (&n_vars);
+  if (n_vars < 1 || n_vars > ext->dict->nvar)
+    lose ((ME, _("%s: Number of variables associated with a value label (%d) "
+          "is not between 1 and the number of variables (%d)."),
+          h->fn, n_vars, ext->dict->nvar));
+
+  /* Allocate storage. */
+  var = xmalloc (sizeof *var * n_vars);
+
+  /* Read the list of variables. */
+  for (i = 0; i < n_vars; i++)
+    {
+      int32 var_index;
+      struct variable *v;
+
+      /* Read variable index, check range. */
+      assertive_bufread (h, &var_index, sizeof var_index, 0);
+      if (ext->reverse_endian)
+       bswap_int32 (&var_index);
+      if (var_index < 1 || var_index > ext->case_size)
+       lose ((ME, _("%s: Variable index associated with value label (%d) is "
+              "not between 1 and the number of values (%d)."),
+              h->fn, var_index, ext->case_size));
+
+      /* Make sure it's a real variable. */
+      v = var_by_index[var_index - 1];
+      if (v == NULL)
+       lose ((ME, _("%s: Variable index associated with value label (%d) refers "
+              "to a continuation of a string variable, not to an actual "
+              "variable."), h->fn, var_index));
+      if (v->type == ALPHA && v->width > MAX_SHORT_STRING)
+       lose ((ME, _("%s: Value labels are not allowed on long string variables "
+              "(%s)."), h->fn, v->name));
+
+      /* Add it to the list of variables. */
+      var[i] = v;
+    }
+
+  /* Type check the variables. */
+  for (i = 1; i < n_vars; i++)
+    if (var[i]->type != var[0]->type)
+      lose ((ME, _("%s: Variables associated with value label are not all of "
+            "identical type.  Variable %s has %s type, but variable %s has "
+            "%s type."), h->fn,
+            var[0]->name, var[0]->type == ALPHA ? _("string") : _("numeric"),
+            var[i]->name, var[i]->type == ALPHA ? _("string") : _("numeric")));
+
+  /* Create a value_label for each value/label tuple, now that we know
+     the desired type. */
+  for (i = 0; i < n_labels; i++)
+    {
+      if (var[0]->type == ALPHA)
+       {
+         const int copy_len = min (sizeof (flt64), MAX_SHORT_STRING);
+         memcpy (cooked_label[i]->v.s, (char *) &raw_label[i], copy_len);
+         if (MAX_SHORT_STRING > copy_len)
+           memset (&cooked_label[i]->v.s[copy_len], ' ',
+                   MAX_SHORT_STRING - copy_len);
+       } else {
+         cooked_label[i]->v.f = raw_label[i];
+         if (ext->reverse_endian)
+           bswap_flt64 (&cooked_label[i]->v.f);
+       }
+      cooked_label[i]->ref_count = n_vars;
+    }
+
+  /* Assign the value_label's to each variable. */
+  for (i = 0; i < n_vars; i++)
+    {
+      struct variable *v = var[i];
+      int j;
+
+      /* Create AVL tree if necessary. */
+      if (!v->val_lab)
+       v->val_lab = avl_create (NULL, val_lab_cmp, (void *) (v->width));
+
+      /* Add each label to the variable. */
+      for (j = 0; j < n_labels; j++)
+       {
+         struct value_label *old = avl_replace (v->val_lab, cooked_label[j]);
+         if (old == NULL)
+           continue;
+
+         if (var[0]->type == NUMERIC)
+           msg (MW, _("%s: File contains duplicate label for value %g for "
+                "variable %s."), h->fn, cooked_label[j]->v.f, v->name);
+         else
+           msg (MW, _("%s: File contains duplicate label for value `%.*s' "
+                "for variable %s."), h->fn, v->width,
+                cooked_label[j]->v.s, v->name);
+
+         free_value_label (old);
+       }
+    }
+
+  free (cooked_label);
+  free (raw_label);
+  free (var);
+  return 1;
+
+lossage:
+  if (cooked_label)
+    for (i = 0; i < n_labels; i++)
+      if (cooked_label[i])
+       {
+         free (cooked_label[i]->s);
+         free (cooked_label[i]);
+       }
+  free (raw_label);
+  free (var);
+  return 0;
+}
+
+/* Reads NBYTES bytes from the file represented by H.  If BUF is
+   non-NULL, uses that as the buffer; otherwise allocates at least
+   MINALLOC bytes.  Returns a pointer to the buffer on success, NULL
+   on failure. */
+static void *
+bufread (struct file_handle * h, void *buf, size_t nbytes, size_t minalloc)
+{
+  struct sfm_fhuser_ext *ext = h->ext;
+
+  if (buf == NULL)
+    buf = xmalloc (max (nbytes, minalloc));
+  if (1 != fread (buf, nbytes, 1, ext->file))
+    {
+      if (ferror (ext->file))
+       msg (ME, _("%s: Reading system file: %s."), h->fn, strerror (errno));
+      else
+       corrupt_msg (ME, _("%s: Unexpected end of file."), h->fn);
+      return NULL;
+    }
+  return buf;
+}
+
+/* Reads a document record, type 6, from system file H, and sets up
+   the documents and n_documents fields in the associated
+   dictionary. */
+static int
+read_documents (struct file_handle * h)
+{
+  struct sfm_fhuser_ext *ext = h->ext;
+  struct dictionary *dict = ext->dict;
+  int32 n_lines;
+
+  if (dict->documents != NULL)
+    lose ((ME, _("%s: System file contains multiple type 6 (document) records."),
+          h->fn));
+
+  assertive_bufread (h, &n_lines, sizeof n_lines, 0);
+  dict->n_documents = n_lines;
+  if (dict->n_documents <= 0)
+    lose ((ME, _("%s: Number of document lines (%ld) must be greater than 0."),
+          h->fn, (long) dict->n_documents));
+
+  dict->documents = bufread (h, NULL, 80 * n_lines, 0);
+  if (dict->documents == NULL)
+    return 0;
+  return 1;
+
+lossage:
+  return 0;
+}
+
+#if GLOBAL_DEBUGGING
+#define DEBUGGING 1
+#include "debug-print.h"
+/* Displays dictionary DICT on stdout. */
+void
+dump_dictionary (struct dictionary * dict)
+{
+  int i;
+
+  debug_printf ((_("dictionary:\n")));
+  for (i = 0; i < dict->nvar; i++)
+    {
+      char print[32];
+      struct variable *v = dict->var[i];
+      int n, j;
+
+      debug_printf (("  var %s", v->name));
+      /*debug_printf (("(indices:%d,%d)", v->index, v->foo));*/
+      debug_printf (("(type:%s,%d)", (v->type == NUMERIC ? _("num")
+                                : (v->type == ALPHA ? _("str") : "!!!")),
+                    v->width));
+      debug_printf (("(fv:%d,%d)", v->fv, v->nv));
+      /*debug_printf (("(get.fv:%d,%d)", v->get.fv, v->get.nv));*/
+      debug_printf (("(left:%s)(miss:", v->left ? _("left") : _("right")));
+             
+      switch (v->miss_type)
+       {
+       case MISSING_NONE:
+         n = 0;
+         debug_printf ((_("none")));
+         break;
+       case MISSING_1:
+         n = 1;
+         debug_printf ((_("one")));
+         break;
+       case MISSING_2:
+         n = 2;
+         debug_printf ((_("two")));
+         break;
+       case MISSING_3:
+         n = 3;
+         debug_printf ((_("three")));
+         break;
+       case MISSING_RANGE:
+         n = 2;
+         debug_printf ((_("range")));
+         break;
+       case MISSING_LOW:
+         n = 1;
+         debug_printf ((_("low")));
+         break;
+       case MISSING_HIGH:
+         n = 1;
+         debug_printf ((_("high")));
+         break;
+       case MISSING_RANGE_1:
+         n = 3;
+         debug_printf ((_("range+1")));
+         break;
+       case MISSING_LOW_1:
+         n = 2;
+         debug_printf ((_("low+1")));
+         break;
+       case MISSING_HIGH_1:
+         n = 2;
+         debug_printf ((_("high+1")));
+         break;
+       default:
+         assert (0);
+       }
+      for (j = 0; j < n; j++)
+       if (v->type == NUMERIC)
+         debug_printf ((",%g", v->missing[j].f));
+       else
+         debug_printf ((",\"%.*s\"", v->width, v->missing[j].s));
+      strcpy (print, fmt_to_string (&v->print));
+      debug_printf ((")(fmt:%s,%s)(lbl:%s)\n",
+                    print, fmt_to_string (&v->write),
+                    v->label ? v->label : "nolabel"));
+    }
+}
+#endif
+\f
+/* Data reader. */
+
+/* Reads compressed data into H->BUF and sets other pointers
+   appropriately.  Returns nonzero only if both no errors occur and
+   data was read. */
+static int
+buffer_input (struct file_handle * h)
+{
+  struct sfm_fhuser_ext *ext = h->ext;
+  size_t amt;
+
+  if (ext->buf == NULL)
+    ext->buf = xmalloc (sizeof *ext->buf * 128);
+  amt = fread (ext->buf, sizeof *ext->buf, 128, ext->file);
+  if (ferror (ext->file))
+    {
+      msg (ME, _("%s: Error reading file: %s."), h->fn, strerror (errno));
+      return 0;
+    }
+  ext->ptr = ext->buf;
+  ext->end = &ext->buf[amt];
+  return amt;
+}
+
+/* Reads a single case consisting of compressed data from system file
+   H into the array TEMP[] according to dictionary DICT, and returns
+   nonzero only if successful. */
+/* Data in system files is compressed in the following manner:
+   data values are grouped into sets of eight; each of the eight has
+   one instruction byte, which are output together in an octet; each
+   byte gives a value for that byte or indicates that the value can be
+   found following the instructions. */
+static int
+read_compressed_data (struct file_handle * h, flt64 * temp)
+{
+  struct sfm_fhuser_ext *ext = h->ext;
+
+  const unsigned char *p_end = ext->x + sizeof (flt64);
+  unsigned char *p = ext->y;
+
+  const flt64 *temp_beg = temp;
+  const flt64 *temp_end = &temp[ext->case_size];
+
+  for (;;)
+    {
+      for (; p < p_end; p++)
+       switch (*p)
+         {
+         case 0:
+           /* Code 0 is ignored. */
+           continue;
+         case 252:
+           /* Code 252 is end of file. */
+           if (temp_beg != temp)
+             lose ((ME, _("%s: Compressed data is corrupted.  Data ends "
+                    "partway through a case."), h->fn));
+           goto lossage;
+         case 253:
+           /* Code 253 indicates that the value is stored explicitly
+              following the instruction bytes. */
+           if (ext->ptr == NULL || ext->ptr >= ext->end)
+             if (!buffer_input (h))
+               {
+                 lose ((ME, _("%s: Unexpected end of file."), h->fn));
+                 goto lossage;
+               }
+           memcpy (temp++, ext->ptr++, sizeof *temp);
+           if (temp >= temp_end)
+             goto winnage;
+           break;
+         case 254:
+           /* Code 254 indicates a string that is all blanks. */
+           memset (temp++, ' ', sizeof *temp);
+           if (temp >= temp_end)
+             goto winnage;
+           break;
+         case 255:
+           /* Code 255 indicates the system-missing value. */
+           *temp = ext->sysmis;
+           if (ext->reverse_endian)
+             bswap_flt64 (temp);
+           temp++;
+           if (temp >= temp_end)
+             goto winnage;
+           break;
+         default:
+           /* Codes 1 through 251 inclusive are taken to indicate a
+              value of (BYTE - BIAS), where BYTE is the byte's value
+              and BIAS is the compression bias (generally 100.0). */
+           *temp = *p - ext->bias;
+           if (ext->reverse_endian)
+             bswap_flt64 (temp);
+           temp++;
+           if (temp >= temp_end)
+             goto winnage;
+           break;
+         }
+
+      /* We have reached the end of this instruction octet.  Read
+        another. */
+      if (ext->ptr == NULL || ext->ptr >= ext->end)
+       if (!buffer_input (h))
+         {
+           if (temp_beg != temp)
+             lose ((ME, _("%s: Unexpected end of file."), h->fn));
+           goto lossage;
+         }
+      memcpy (ext->x, ext->ptr++, sizeof *temp);
+      p = ext->x;
+    }
+
+  /* Not reached. */
+  assert (0);
+
+winnage:
+  /* We have filled up an entire record.  Update state and return
+     successfully. */
+  ext->y = ++p;
+  return 1;
+
+lossage:
+  /* We have been unsuccessful at filling a record, either through i/o
+     error or through an end-of-file indication.  Update state and
+     return unsuccessfully. */
+  return 0;
+}
+
+/* Reads one case from system file H into the value array PERM
+   according to the instructions given in associated dictionary DICT,
+   which must have the get.* elements appropriately set.  Returns
+   nonzero only if successful.  */
+int
+sfm_read_case (struct file_handle * h, union value * perm, struct dictionary * dict)
+{
+  struct sfm_fhuser_ext *ext = h->ext;
+
+  size_t nbytes;
+  flt64 *temp;
+
+  int i;
+
+  /* Make sure the caller remembered to finish polishing the
+     dictionary returned by sfm_read_dictionary(). */
+  assert (dict->nval > 0);
+
+  /* The first concern is to obtain a full case relative to the data
+     file.  (Cases in the data file have no particular relationship to
+     cases in the active file.) */
+  nbytes = sizeof *temp * ext->case_size;
+  temp = local_alloc (nbytes);
+
+  if (ext->compressed == 0)
+    {
+      size_t amt = fread (temp, 1, nbytes, ext->file);
+
+      if (amt != nbytes)
+       {
+         if (ferror (ext->file))
+           msg (ME, _("%s: Reading system file: %s."), h->fn, strerror (errno));
+         else if (amt != 0)
+           msg (ME, _("%s: Partial record at end of system file."), h->fn);
+         goto lossage;
+       }
+    }
+  else if (!read_compressed_data (h, temp))
+    goto lossage;
+
+  /* Translate a case in data file format to a case in active file
+     format. */
+  for (i = 0; i < dict->nvar; i++)
+    {
+      struct variable *v = dict->var[i];
+
+      if (v->get.fv == -1)
+       continue;
+      
+      if (v->type == NUMERIC)
+       {
+         flt64 src = temp[v->get.fv];
+         if (ext->reverse_endian)
+           bswap_flt64 (&src);
+         perm[v->fv].f = src == ext->sysmis ? SYSMIS : src;
+       }
+      else
+       memcpy (&perm[v->fv].s, &temp[v->get.fv], v->width);
+    }
+
+  local_free (temp);
+  return 1;
+
+lossage:
+  local_free (temp);
+  return 0;
+}
+
+static struct fh_ext_class sfm_r_class =
+{
+  3,
+  N_("reading as a system file"),
+  sfm_close,
+};
diff --git a/src/sfm-write.c b/src/sfm-write.c
new file mode 100644 (file)
index 0000000..0e99013
--- /dev/null
@@ -0,0 +1,756 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <time.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>    /* Required by SunOS4. */
+#endif
+#include "alloc.h"
+#include "approx.h"
+#include "avl.h"
+#include "error.h"
+#include "file-handle.h"
+#include "getline.h"
+#include "magic.h"
+#include "misc.h"
+#include "sfm.h"
+#include "sfmP.h"
+#include "str.h"
+#include "var.h"
+#include "version.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1 */
+#include "debug-print.h"
+
+/* PORTME: This file may require substantial revision for those
+   systems that don't meet the typical 32-bit integer/64-bit double
+   model.  It's kinda hard to tell without having one of them on my
+   desk.  */
+
+/* Compression bias used by PSPP.  Values between (1 -
+   COMPRESSION_BIAS) and (251 - COMPRESSION_BIAS) inclusive can be
+   compressed. */
+#define COMPRESSION_BIAS 100
+
+/* sfm writer file_handle extension. */
+struct sfm_fhuser_ext
+  {
+    FILE *file;                        /* Actual file. */
+
+    int compressed;            /* 1=compressed, 0=not compressed. */
+    flt64 *buf;                        /* Buffered data. */
+    flt64 *end;                        /* Buffer end. */
+    flt64 *ptr;                        /* Current location in buffer. */
+    unsigned char *x;          /* Location in current instruction octet. */
+    unsigned char *y;          /* End of instruction octet. */
+    int n_cases;               /* Number of cases written so far. */
+
+    char *elem_type;           /* ALPHA or NUMERIC for each flt64 element. */
+  };
+
+static struct fh_ext_class sfm_w_class;
+
+static char *append_string_max (char *, const char *, const char *);
+static int write_header (struct sfm_write_info *inf);
+static int bufwrite (struct file_handle *h, const void *buf, size_t nbytes);
+static int write_variable (struct sfm_write_info *inf, struct variable *v);
+static int write_value_labels (struct sfm_write_info *inf, struct variable * s, int index);
+static int write_rec_7_34 (struct sfm_write_info *inf);
+static int write_documents (struct sfm_write_info *inf);
+
+/* Writes the dictionary INF->dict to system file INF->h.  The system
+   file is compressed if INF->compress is nonzero.  INF->case_size is
+   set to the number of flt64 elements in a single case.  Returns
+   nonzero only if successful. */
+int
+sfm_write_dictionary (struct sfm_write_info *inf)
+{
+  struct dictionary *d = inf->dict;
+  struct sfm_fhuser_ext *ext;
+  int i;
+  int index;
+
+  if (inf->h->class != NULL)
+    {
+      msg (ME, _("Cannot write file %s as system file: already opened for %s."),
+          fh_handle_name (inf->h), inf->h->class->name);
+      return 0;
+    }
+
+  msg (VM (1), _("%s: Opening system-file handle %s for writing."),
+       fh_handle_filename (inf->h), fh_handle_name (inf->h));
+  
+  /* Open the physical disk file. */
+  inf->h->class = &sfm_w_class;
+  inf->h->ext = ext = xmalloc (sizeof (struct sfm_fhuser_ext));
+  ext->file = fopen (inf->h->norm_fn, "wb");
+  ext->elem_type = NULL;
+  if (ext->file == NULL)
+    {
+      msg (ME, _("An error occurred while opening \"%s\" for writing "
+          "as a system file: %s."), inf->h->fn, strerror (errno));
+      err_cond_fail ();
+      free (ext);
+      return 0;
+    }
+
+  /* Initialize the sfm_fhuser_ext structure. */
+  ext->compressed = inf->compress;
+  ext->buf = ext->ptr = NULL;
+  ext->x = ext->y = NULL;
+  ext->n_cases = 0;
+
+  /* Write the file header. */
+  if (!write_header (inf))
+    goto lossage;
+
+  /* Write basic variable info. */
+  for (i = 0; i < d->nvar; i++)
+    write_variable (inf, d->var[i]);
+
+  /* Write out value labels. */
+  for (index = i = 0; i < d->nvar; i++)
+    {
+      struct variable *v = d->var[i];
+
+      if (!write_value_labels (inf, v, index))
+       goto lossage;
+      index += (v->type == NUMERIC ? 1
+               : DIV_RND_UP (v->width, sizeof (flt64)));
+    }
+
+  if (d->documents != NULL && !write_documents (inf))
+    goto lossage;
+  if (!write_rec_7_34 (inf))
+    goto lossage;
+
+  /* Write record 999. */
+  {
+    struct
+      {
+       int32 rec_type P;
+       int32 filler P;
+      }
+    rec_999;
+
+    rec_999.rec_type = 999;
+    rec_999.filler = 0;
+
+    if (!bufwrite (inf->h, &rec_999, sizeof rec_999))
+      goto lossage;
+  }
+
+  msg (VM (2), _("Wrote system-file header successfully."));
+  
+  return 1;
+
+lossage:
+  msg (VM (1), _("Error writing system-file header."));
+  fclose (ext->file);
+  inf->h->class = NULL;
+  inf->h->ext = NULL;
+  free (ext->elem_type);
+  ext->elem_type = NULL;
+  return 0;
+}
+
+/* Returns value of X truncated to two least-significant digits. */
+static int
+rerange (int x)
+{
+  if (x < 0)
+    x = -x;
+  if (x >= 100)
+    x %= 100;
+  return x;
+}
+
+/* Write the sysfile_header header to the system file represented by
+   INF. */
+static int
+write_header (struct sfm_write_info *inf)
+{
+  struct dictionary *d = inf->dict;
+  struct sfm_fhuser_ext *ext = inf->h->ext;
+  struct sysfile_header hdr;
+  char *p;
+  int i;
+
+  time_t t;
+
+  memcpy (hdr.rec_type, "$FL2", 4);
+
+  p = stpcpy (hdr.prod_name, "@(#) SPSS DATA FILE ");
+  p = append_string_max (p, version, &hdr.prod_name[60]);
+  p = append_string_max (p, " - ", &hdr.prod_name[60]);
+  p = append_string_max (p, host_system, &hdr.prod_name[60]);
+  memset (p, ' ', &hdr.prod_name[60] - p);
+
+  hdr.layout_code = 2;
+
+  hdr.case_size = 0;
+  for (i = 0; i < d->nvar; i++)
+    {
+      struct variable *v = d->var[i];
+      hdr.case_size += (v->type == NUMERIC ? 1
+                       : DIV_RND_UP (v->width, sizeof (flt64)));
+    }
+  inf->case_size = hdr.case_size;
+
+  p = ext->elem_type = xmalloc (inf->case_size);
+  for (i = 0; i < d->nvar; i++)
+    {
+      struct variable *v = d->var[i];
+      int count = (v->type == NUMERIC ? 1
+                   : DIV_RND_UP (v->width, sizeof (flt64)));
+      while (count--)
+        *p++ = v->type;
+    }
+
+  hdr.compressed = inf->compress;
+
+  update_weighting (d);
+  if (d->weight_index != -1)
+    {
+      int recalc_weight_index = 1;
+
+      for (i = 0; i < d->weight_index; i++)
+       {
+         struct variable *v = d->var[i];
+         recalc_weight_index += (v->type == NUMERIC ? 1
+                                 : DIV_RND_UP (v->width, sizeof (flt64)));
+       }
+      hdr.weight_index = recalc_weight_index;
+    }
+  else
+    hdr.weight_index = 0;
+
+  hdr.ncases = -1;
+  hdr.bias = COMPRESSION_BIAS;
+
+  if ((time_t) - 1 == time (&t))
+    {
+      memcpy (hdr.creation_date, "01 Jan 70", 9);
+      memcpy (hdr.creation_time, "00:00:00", 8);
+    }
+  else
+    {
+      static const char *month_name[12] =
+      {
+       "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+       "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+      };
+      struct tm *tmp = localtime (&t);
+      int day = rerange (tmp->tm_mday);
+      int mon = rerange (tmp->tm_mon + 1);
+      int year = rerange (tmp->tm_year);
+      int hour = rerange (tmp->tm_hour + 1);
+      int min = rerange (tmp->tm_min + 1);
+      int sec = rerange (tmp->tm_sec + 1);
+      char buf[10];
+
+      sprintf (buf, "%02d %s %02d", day, month_name[mon - 1], year);
+      memcpy (hdr.creation_date, buf, sizeof hdr.creation_date);
+      sprintf (buf, "%02d:%02d:%02d", hour - 1, min - 1, sec - 1);
+      memcpy (hdr.creation_time, buf, sizeof hdr.creation_time);
+    }
+
+  st_bare_pad_copy (hdr.file_label, d->label ? d->label : "",
+                   sizeof hdr.file_label);
+  memset (hdr.padding, 0, sizeof hdr.padding);
+
+  if (!bufwrite (inf->h, &hdr, sizeof hdr))
+    return 0;
+  return 1;
+}
+
+/* Translates format spec from internal form in SRC to system file
+   format in DEST. */
+static inline void
+write_format_spec (struct fmt_spec *src, int32 *dest)
+{
+  *dest = (formats[src->type].spss << 16) | (src->w << 8) | src->d;
+}
+
+/* Write the variable record(s) for primary variable P and secondary
+   variable S to the system file represented by INF. */
+static int
+write_variable (struct sfm_write_info *inf, struct variable *v)
+{
+  struct sysfile_variable sv;
+
+  /* Missing values. */
+  flt64 m[3];                  /* Missing value values. */
+  int nm;                      /* Number of missing values, possibly negative. */
+
+  sv.rec_type = 2;
+  sv.type = (v->type == NUMERIC ? 0 : v->width);
+  sv.has_var_label = (v->label != NULL);
+
+  switch (v->miss_type)
+    {
+    case MISSING_NONE:
+      nm = 0;
+      break;
+    case MISSING_1:
+    case MISSING_2:
+    case MISSING_3:
+      for (nm = 0; nm < v->miss_type; nm++)
+       m[nm] = v->missing[nm].f;
+      break;
+    case MISSING_RANGE:
+      m[0] = v->missing[0].f;
+      m[1] = v->missing[1].f;
+      nm = -2;
+      break;
+    case MISSING_LOW:
+      m[0] = second_lowest_flt64;
+      m[1] = v->missing[0].f;
+      nm = -2;
+      break;
+    case MISSING_HIGH:
+      m[0] = v->missing[0].f;
+      m[1] = FLT64_MAX;
+      nm = -2;
+      break;
+    case MISSING_RANGE_1:
+      m[0] = v->missing[0].f;
+      m[1] = v->missing[1].f;
+      m[2] = v->missing[2].f;
+      nm = -3;
+      break;
+    case MISSING_LOW_1:
+      m[0] = second_lowest_flt64;
+      m[1] = v->missing[0].f;
+      m[2] = v->missing[1].f;
+      nm = -3;
+      break;
+    case MISSING_HIGH_1:
+      m[0] = v->missing[0].f;
+      m[1] = second_lowest_flt64;
+      m[2] = v->missing[1].f;
+      nm = -3;
+      break;
+    default:
+      assert (0);
+    }
+
+  sv.n_missing_values = nm;
+  write_format_spec (&v->print, &sv.print);
+  write_format_spec (&v->write, &sv.write);
+  memcpy (sv.name, v->name, strlen (v->name));
+  memset (&sv.name[strlen (v->name)], ' ', 8 - strlen (v->name));
+  if (!bufwrite (inf->h, &sv, sizeof sv))
+    return 0;
+
+  if (v->label)
+    {
+      struct label
+       {
+         int32 label_len P;
+         char label[120] P;
+       }
+      l;
+
+      int ext_len;
+
+      l.label_len = min (strlen (v->label), 120);
+      ext_len = ROUND_UP (l.label_len, sizeof l.label_len);
+      memcpy (l.label, v->label, l.label_len);
+      memset (&l.label[l.label_len], ' ', ext_len - l.label_len);
+
+      if (!bufwrite (inf->h, &l, offsetof (struct label, label) + ext_len))
+         return 0;
+    }
+
+  if (nm && !bufwrite (inf->h, m, sizeof *m * nm))
+    return 0;
+
+  if (v->type == ALPHA && v->width > (int) sizeof (flt64))
+    {
+      int i;
+      int pad_count;
+
+      sv.type = -1;
+      sv.has_var_label = 0;
+      sv.n_missing_values = 0;
+      memset (&sv.print, 0, sizeof sv.print);
+      memset (&sv.write, 0, sizeof sv.write);
+      memset (&sv.name, 0, sizeof sv.name);
+
+      pad_count = DIV_RND_UP (v->width, (int) sizeof (flt64)) - 1;
+      for (i = 0; i < pad_count; i++)
+       if (!bufwrite (inf->h, &sv, sizeof sv))
+         return 0;
+    }
+
+  return 1;
+}
+
+/* Writes the value labels for variable V having system file variable
+   index INDEX to the system file associated with INF.  Returns
+   nonzero only if successful. */
+static int
+write_value_labels (struct sfm_write_info * inf, struct variable *v, int index)
+{
+  struct value_label_rec
+    {
+      int32 rec_type P;
+      int32 n_labels P;
+      flt64 labels[1] P;
+    };
+
+  struct variable_index_rec
+    {
+      int32 rec_type P;
+      int32 n_vars P;
+      int32 vars[1] P;
+    };
+
+  avl_traverser i;
+  struct value_label_rec *vlr;
+  struct variable_index_rec vir;
+  struct value_label *vl;
+  size_t vlr_size;
+  flt64 *loc;
+  avl_traverser_init (i);
+
+  if (v->val_lab == NULL || avl_count (v->val_lab) == 0)
+    return 1;
+
+  /* Pass 1: Count bytes. */
+  vlr_size = (sizeof (struct value_label_rec)
+             + sizeof (flt64) * (avl_count (v->val_lab) - 1));
+  while (NULL != (vl = avl_traverse (v->val_lab, &i)))
+    vlr_size += ROUND_UP (strlen (vl->s) + 1, sizeof (flt64));
+
+  /* Pass 2: Copy bytes. */
+  vlr = local_alloc (vlr_size);
+  vlr->rec_type = 3;
+  vlr->n_labels = avl_count (v->val_lab);
+  loc = vlr->labels;
+  while (NULL != (vl = avl_traverse (v->val_lab, &i)))
+    {
+      int len = strlen (vl->s);
+
+      *loc++ = vl->v.f;
+      *(unsigned char *) loc = len;
+      memcpy (&((unsigned char *) loc)[1], vl->s, len);
+      memset (&((unsigned char *) loc)[1 + len], ' ',
+             REM_RND_UP (len + 1, sizeof (flt64)));
+      loc += DIV_RND_UP (len + 1, sizeof (flt64));
+    }
+  
+  if (!bufwrite (inf->h, vlr, vlr_size))
+    {
+      local_free (vlr);
+      return 0;
+    }
+  local_free (vlr);
+
+  vir.rec_type = 4;
+  vir.n_vars = 1;
+  vir.vars[0] = index + 1;
+  if (!bufwrite (inf->h, &vir, sizeof vir))
+    return 0;
+
+  return 1;
+}
+
+/* Writes record type 6, document record. */
+static int
+write_documents (struct sfm_write_info * inf)
+{
+  struct dictionary *d = inf->dict;
+  struct
+  {
+    int32 rec_type P;          /* Always 6. */
+    int32 n_lines P;           /* Number of lines of documents. */
+  }
+  rec_6;
+
+  rec_6.rec_type = 6;
+  rec_6.n_lines = d->n_documents;
+  if (!bufwrite (inf->h, &rec_6, sizeof rec_6))
+    return 0;
+  if (!bufwrite (inf->h, d->documents, 80 * d->n_documents))
+    return 0;
+
+  return 1;
+}
+
+/* Writes record type 7, subtypes 3 and 4. */
+static int
+write_rec_7_34 (struct sfm_write_info * inf)
+{
+  struct
+    {
+      int32 rec_type_3 P;
+      int32 subtype_3 P;
+      int32 data_type_3 P;
+      int32 n_elem_3 P;
+      int32 elem_3[8] P;
+      int32 rec_type_4 P;
+      int32 subtype_4 P;
+      int32 data_type_4 P;
+      int32 n_elem_4 P;
+      flt64 elem_4[3] P;
+    }
+  rec_7;
+
+  /* Components of the version number, from major to minor. */
+  int version_component[3];
+  
+  /* Used to step through the version string. */
+  char *p;
+
+  /* Parses the version string, which is assumed to be of the form
+     #.#x, where each # is a string of digits, and x is a single
+     letter. */
+  version_component[0] = strtol (bare_version, &p, 10);
+  if (*p == '.')
+    p++;
+  version_component[1] = strtol (bare_version, &p, 10);
+  version_component[2] = (isalpha ((unsigned char) *p)
+                         ? tolower ((unsigned char) *p) - 'a' : 0);
+    
+  rec_7.rec_type_3 = 7;
+  rec_7.subtype_3 = 3;
+  rec_7.data_type_3 = sizeof (int32);
+  rec_7.n_elem_3 = 8;
+  rec_7.elem_3[0] = version_component[0];
+  rec_7.elem_3[1] = version_component[1];
+  rec_7.elem_3[2] = version_component[2];
+  rec_7.elem_3[3] = -1;
+
+  /* PORTME: 1=IEEE754, 2=IBM 370, 3=DEC VAX E. */
+#if FPREP==FPREP_IEEE754
+  rec_7.elem_3[4] = 1;
+#else
+#error Floating-point representation unknown here.
+#endif
+
+  rec_7.elem_3[5] = 1;
+
+  /* PORTME: 1=big-endian, 2=little-endian. */
+  if (endian == BIG)
+    rec_7.elem_3[6] = 1;
+  else if (endian == LITTLE)
+    rec_7.elem_3[6] = 2;
+  else
+    rec_7.elem_3[6] = 0;
+
+  /* PORTME: 1=EBCDIC, 2=7-bit ASCII, 3=8-bit ASCII, 4=DEC Kanji. */
+  rec_7.elem_3[7] = 2;
+
+  rec_7.rec_type_4 = 7;
+  rec_7.subtype_4 = 4;
+  rec_7.data_type_4 = sizeof (flt64);
+  rec_7.n_elem_4 = 3;
+  rec_7.elem_4[0] = -FLT64_MAX;
+  rec_7.elem_4[1] = FLT64_MAX;
+  rec_7.elem_4[2] = second_lowest_flt64;
+
+  if (!bufwrite (inf->h, &rec_7, sizeof rec_7))
+    return 0;
+  return 1;
+}
+
+/* Write NBYTES starting at BUF to the system file represented by
+   H. */
+static int
+bufwrite (struct file_handle * h, const void *buf, size_t nbytes)
+{
+  struct sfm_fhuser_ext *ext = h->ext;
+
+  assert (buf);
+  if (1 != fwrite (buf, nbytes, 1, ext->file))
+    {
+      msg (ME, _("%s: Writing system file: %s."), h->fn, strerror (errno));
+      return 0;
+    }
+  return 1;
+}
+
+/* Copies string DEST to SRC with the proviso that DEST does not reach
+   byte END; no null terminator is copied.  Returns a pointer to the
+   byte after the last byte copied. */
+static char *
+append_string_max (char *dest, const char *src, const char *end)
+{
+  int nbytes = min (end - dest, (int) strlen (src));
+  memcpy (dest, src, nbytes);
+  return dest + nbytes;
+}
+
+/* Makes certain that the compression buffer of H has room for another
+   element.  If there's not room, pads out the current instruction
+   octet with zero and dumps out the buffer. */
+static inline int
+ensure_buf_space (struct file_handle *h)
+{
+  struct sfm_fhuser_ext *ext = h->ext;
+
+  if (ext->ptr >= ext->end)
+    {
+      memset (ext->x, 0, ext->y - ext->x);
+      ext->x = ext->y;
+      ext->ptr = ext->buf;
+      if (!bufwrite (h, ext->buf, sizeof *ext->buf * 128))
+       return 0;
+    }
+  return 1;
+}
+
+/* Writes case ELEM consisting of N_ELEM flt64 elements to the system
+   file represented by H.  Return success. */
+int
+sfm_write_case (struct file_handle * h, const flt64 *elem, int n_elem)
+{
+  struct sfm_fhuser_ext *ext = h->ext;
+  const flt64 *end_elem = &elem[n_elem];
+  char *elem_type = ext->elem_type;
+
+  ext->n_cases++;
+
+  if (ext->compressed == 0)
+    return bufwrite (h, elem, sizeof *elem * n_elem);
+
+  if (ext->buf == NULL)
+    {
+      ext->buf = xmalloc (sizeof *ext->buf * 128);
+      ext->ptr = ext->buf;
+      ext->end = &ext->buf[128];
+      ext->x = (unsigned char *) (ext->ptr++);
+      ext->y = (unsigned char *) (ext->ptr);
+    }
+  for (; elem < end_elem; elem++, elem_type++)
+    {
+      if (ext->x >= ext->y)
+       {
+         if (!ensure_buf_space (h))
+           return 0;
+         ext->x = (unsigned char *) (ext->ptr++);
+         ext->y = (unsigned char *) (ext->ptr);
+       }
+
+      if (*elem_type == NUMERIC)
+        {
+         if (*elem == -FLT64_MAX)
+            {
+             *ext->x++ = 255;
+              continue;
+            }
+         else
+           {
+             int value = *elem < 0 ? *elem - EPSILON : *elem + EPSILON;
+
+             if (value >= 1 - COMPRESSION_BIAS
+                 && value <= 251 - COMPRESSION_BIAS
+                 && approx_eq (value, *elem))
+                {
+                 *ext->x++ = value + COMPRESSION_BIAS;
+                  continue;
+                }
+            }
+        }
+      else
+       {
+          if (0 == memcmp ((char *) elem,
+                          "                                           ",
+                          sizeof (flt64)))
+            {
+             *ext->x++ = 254;
+              continue;
+            }
+        }
+      
+      *ext->x++ = 253;
+      if (!ensure_buf_space (h))
+       return 0;
+      *ext->ptr++ = *elem;
+    }
+
+  return 1;
+}
+
+/* Closes a system file after we're done with it. */
+static void
+sfm_close (struct file_handle * h)
+{
+  struct sfm_fhuser_ext *ext = h->ext;
+
+  if (ext->buf != NULL && ext->ptr > ext->buf)
+    {
+      memset (ext->x, 0, ext->y - ext->x);
+      bufwrite (h, ext->buf, (ext->ptr - ext->buf) * sizeof *ext->buf);
+    }
+
+  /* Attempt to seek back to the beginning in order to write the
+     number of cases.  If that's not possible (i.e., we're writing to
+     a tty or a pipe), then it's not a big deal because we wrote the
+     code that indicates an unknown number of cases. */
+  if (0 == fseek (ext->file, offsetof (struct sysfile_header, ncases),
+                 SEEK_SET))
+    {
+      int32 n_cases = ext->n_cases;
+
+      /* I don't really care about the return value: it doesn't matter
+         whether this data is written.  This is the only situation in
+         which you will see me fail to check a return value. */
+      fwrite (&n_cases, sizeof n_cases, 1, ext->file);
+    }
+
+  if (EOF == fclose (ext->file))
+    msg (ME, _("%s: Closing system file: %s."), h->fn, strerror (errno));
+  free (ext->buf);
+
+  free (ext->elem_type);
+  free (ext);
+}
+
+static struct fh_ext_class sfm_w_class =
+{
+  4,
+  N_("writing as a system file"),
+  sfm_close,
+};
diff --git a/src/sfm.h b/src/sfm.h
new file mode 100644 (file)
index 0000000..6b3f6a1
--- /dev/null
+++ b/src/sfm.h
@@ -0,0 +1,66 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !sfm_h
+#define sfm_h 1
+
+/* System file manager (sfm).
+
+   This module is in charge of reading and writing system files.  For
+   now, only ordinary system files are supported; in the future, PC+
+   compatible system files should be supported, too.  sfm is an
+   fhuser, so see file-handle.h for the fhuser interface.  */
+
+/* Information produced by sfm_read_dictionary() that doesn't fit into
+   a dictionary struct. */
+struct sfm_read_info
+  {
+    char creation_date[10];    /* `dd mmm yy' plus a null. */
+    char creation_time[9];     /* `hh:mm:ss' plus a null. */
+    int endianness;            /* BIG or LITTLE. */
+    int compressed;            /* 0=no, 1=yes. */
+    int ncases;                        /* -1 if unknown. */
+    char product[61];          /* Product name plus a null. */
+  };
+
+struct dictionary;
+struct file_handle;
+union value;
+
+struct dictionary *sfm_read_dictionary (struct file_handle *,
+                                       struct sfm_read_info *);
+int sfm_read_case (struct file_handle *, union value *, struct dictionary *);
+void sfm_maybe_close (struct file_handle *);
+
+/* Information needed by sfm_write_dictionary(). */
+struct sfm_write_info
+  {
+    /* Read by sfm_write_dictionary(). */
+    struct file_handle *h;     /* File handle. */
+    struct dictionary *dict;   /* Primary dictionary. */
+    int compress;              /* 1=compress, 0=do not compress. */
+
+    /* Written by sfm_write_dictionary(). */
+    int case_size;             /* Number of flt64 elements per case. */
+  };
+
+int sfm_write_dictionary (struct sfm_write_info *);
+int sfm_write_case (struct file_handle *, const flt64* elem, int n_elem);
+
+#endif /* !sfm_h */
diff --git a/src/sfmP.h b/src/sfmP.h
new file mode 100644 (file)
index 0000000..d03ba04
--- /dev/null
@@ -0,0 +1,63 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* PORTME: There might easily be alignment problems with some of these
+   structures. */
+
+/* This attribute might avoid some problems.  On the other hand... */
+#define P __attribute__((packed))
+
+#if __BORLANDC__
+#pragma option -a-             /* Turn off alignment. */
+#endif
+
+/* Record Type 1: General Information. */
+struct sysfile_header
+  {
+    char rec_type[4] P;                /* Record-type code, "$FL2". */
+    char prod_name[60] P;      /* Product identification. */
+    int32 layout_code P;       /* 2. */
+    int32 case_size P;         /* Number of `value's per case. */
+    int32 compressed P;                /* 1=compressed, 0=not compressed. */
+    int32 weight_index P;      /* 1-based index of weighting var, or zero. */
+    int32 ncases P;            /* Number of cases, -1 if unknown. */
+    flt64 bias P;              /* Compression bias (100.0). */
+    char creation_date[9] P;   /* `dd mmm yy' creation date of file. */
+    char creation_time[8] P;   /* `hh:mm:ss' 24-hour creation time. */
+    char file_label[64] P;     /* File label. */
+    char padding[3] P;         /* Ignored padding. */
+  };
+
+/* Record Type 2: Variable. */
+struct sysfile_variable
+  {
+    int32 rec_type P;          /* 2. */
+    int32 type P;              /* 0=numeric, 1-255=string width,
+                                  -1=continued string. */
+    int32 has_var_label P;     /* 1=has a variable label, 0=doesn't. */
+    int32 n_missing_values P;  /* Missing value code of -3,-2,0,1,2, or 3. */
+    int32 print P;     /* Print format. */
+    int32 write P;     /* Write format. */
+    char name[8] P;            /* Variable name. */
+    /* The rest of the structure varies. */
+  };
+
+#if __BORLANDC__
+#pragma -a4
+#endif
diff --git a/src/som.c b/src/som.c
new file mode 100644 (file)
index 0000000..dde12b5
--- /dev/null
+++ b/src/som.c
@@ -0,0 +1,275 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdio.h>
+#include "output.h"
+#include "som.h"
+/*#undef DEBUGGING*/
+/*#define DEBUGGING 1 */
+#include "debug-print.h"
+
+/* Table. */
+int table_num = 1;
+int subtable_num;
+\f
+/* Increments table_num so different procedures' output can be
+   distinguished. */
+void
+som_new_series (void)
+{
+  if (subtable_num != 0)
+    {
+      table_num++;
+      subtable_num = 0;
+    }
+}
+
+/* Ejects the paper for all active devices. */
+void
+som_eject_page (void)
+{
+  struct outp_driver *d;
+
+  for (d = outp_drivers (NULL); d; d = outp_drivers (d))
+    outp_eject_page (d);
+}
+
+/* Skip down a single line on all active devices. */
+void
+som_blank_line (void)
+{
+  struct outp_driver *d;
+  
+  for (d = outp_drivers (NULL); d; d = outp_drivers (d))
+    if (d->page_open && d->cp_y != 0)
+      d->cp_y += d->font_height;
+}
+\f
+/* Driver. */
+struct outp_driver *d;
+
+/* Table. */
+struct som_table *t;
+
+/* Flags. */
+static unsigned flags;
+
+/* Number of columns, rows. */
+static int nc, nr;
+
+/* Number of columns or rows in left, right, top, bottom headers. */
+static int hl, hr, ht, hb;
+
+/* Column style. */
+static int cs;
+
+/* Table height, width. */
+static int th, tw;
+
+static void render_columns (void);
+static void render_simple (void);
+static void render_segments (void);
+
+static void output_table (struct outp_driver *, struct som_table *);
+
+/* Output table T to appropriate output devices. */
+void
+som_submit (struct som_table *t)
+{
+#if GLOBAL_DEBUGGING
+  static int entry;
+  
+  assert (entry++ == 0);
+#endif
+
+  t->class->table (t);
+  t->class->flags (&flags);
+  t->class->count (&nc, &nr);
+  t->class->headers (&hl, &hr, &ht, &hb);
+
+#if GLOBAL_DEBUGGING
+  if (hl + hr > nc || ht + hb > nr)
+    {
+      printf ("headers: (l,r)=(%d,%d), (t,b)=(%d,%d) in table size (%d,%d)\n",
+             hl, hr, ht, hb, nc, nr);
+      abort ();
+    }
+  else if (hl + hr == nc)
+    printf ("warning: headers (l,r)=(%d,%d) in table width %d\n", hl, hr, nc);
+  else if (ht + hb == nr)
+    printf ("warning: headers (t,b)=(%d,%d) in table height %d\n", ht, hb, nr);
+#endif
+
+  t->class->columns (&cs);
+
+  if (!(flags & SOMF_NO_TITLE))
+    subtable_num++;
+    
+  {
+    struct outp_driver *d;
+
+    for (d = outp_drivers (NULL); d; d = outp_drivers (d))
+      output_table (d, t);
+  }
+  
+#if GLOBAL_DEBUGGING
+  assert (--entry == 0);
+#endif
+}
+
+/* Output table TABLE to driver DRIVER. */
+static void
+output_table (struct outp_driver *driver, struct som_table *table)
+{
+  d = driver;
+  t = table;
+
+  assert (d->driver_open);
+  if (!d->page_open && !d->class->open_page (d))
+    {
+      d->device = OUTP_DEV_DISABLED;
+      return;
+    }
+  
+  if (d->class->special)
+    {
+      driver->class->submit (d, t);
+      return;
+    }
+  
+  t->class->driver (d);
+  t->class->area (&tw, &th);
+  
+  if (!(flags & SOMF_NO_SPACING) && d->cp_y != 0)
+    d->cp_y += d->font_height;
+       
+  if (cs != SOM_COL_NONE
+      && 2 * (tw + d->prop_em_width) <= d->width
+      && nr - (ht + hb) > 5)
+    render_columns ();
+  else if (tw < d->width && th + d->cp_y < d->length)
+    render_simple ();
+  else 
+    render_segments ();
+}
+
+/* Render the table into multiple columns. */
+static void
+render_columns (void)
+{
+  int y0, y1;
+  int max_len = 0;
+  int index = 0;
+  
+  assert (cs == SOM_COL_DOWN);
+  assert (d->cp_x == 0);
+
+  for (y0 = ht; y0 < nr - hb; y0 = y1)
+    {
+      int len;
+      
+      t->class->cumulate (SOM_ROWS, y0, &y1, d->length - d->cp_y, &len);
+
+      if (y0 == y1)
+       {
+         assert (d->cp_y);
+         outp_eject_page (d);
+       } else {
+         if (len > max_len)
+           max_len = len;
+
+         t->class->title (index++, 0);
+         t->class->render (0, y0, nc, y1);
+         
+         d->cp_x += tw + 2 * d->prop_em_width;
+         if (d->cp_x + tw > d->width)
+           {
+             d->cp_x = 0;
+             d->cp_y += max_len;
+             max_len = 0;
+           }
+       }
+    }
+  
+  if (d->cp_x > 0)
+    {
+      d->cp_x = 0;
+      d->cp_y += max_len;
+    }
+}
+
+/* Render the table by itself on the current page. */
+static void
+render_simple (void)
+{
+  assert (d->cp_x == 0);
+  assert (tw < d->width && th + d->cp_y < d->length);
+
+  t->class->title (0, 0);
+  t->class->render (hl, ht, nc - hr, nr - hb);
+  d->cp_y += th;
+}
+
+/* General table breaking routine. */
+static void
+render_segments (void)
+{
+  int count = 0;
+  
+  int x_index;
+  int x0, x1;
+  
+  assert (d->cp_x == 0);
+
+  for (x_index = 0, x0 = hl; x0 < nc - hr; x0 = x1, x_index++)
+    {
+      int y_index;
+      int y0, y1;
+      
+      t->class->cumulate (SOM_COLUMNS, x0, &x1, d->width, NULL);
+      if (x_index == 0 && x1 != nc - hr)
+       x_index++;
+
+      for (y_index = 0, y0 = ht; y0 < nr - hb; y0 = y1, y_index++)
+       {
+         int len;
+      
+         if (count++ != 0 && d->cp_y != 0)
+           d->cp_y += d->font_height;
+             
+         t->class->cumulate (SOM_ROWS, y0, &y1, d->length - d->cp_y, &len);
+         if (y_index == 0 && y1 != nr - hb)
+           y_index++;
+
+         if (y0 == y1)
+           {
+             assert (d->cp_y);
+             outp_eject_page (d);
+           } else {
+             t->class->title (x_index ? x_index : y_index,
+                              x_index ? y_index : 0);
+             t->class->render (x0, y0, x1, y1);
+         
+             d->cp_y += len;
+           }
+       }
+    }
+}
diff --git a/src/som.h b/src/som.h
new file mode 100644 (file)
index 0000000..9ac69c8
--- /dev/null
+++ b/src/som.h
@@ -0,0 +1,109 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !som_h
+#define som_h 1
+
+/* Structured Output Manager.
+
+   som considers the output stream to be a series of tables.  Each
+   table is made up of a rectangular grid of cells.  Cells can be
+   joined to form larger cells.  Rows and columns can be separated by
+   rules of various types.  Tables too large to fit on a single page
+   will be divided into sections.  Rows and columns can be designated
+   as headers, which causes them to be repeated in each section.
+
+   Every table is an instance of a particular table class.  A table
+   class is responsible for keeping track of cell data, for handling
+   requests from the som, and finally for rendering cell data to the
+   output drivers.  Tables may implement these operations in any way
+   desired, and in fact almost every operation performed by som may be
+   overridden in a table class.  */
+
+/* Table. */
+struct som_table
+  {
+    struct som_table_class *class;     /* Table class. */
+    void *ext;                         /* Owned by table class. */
+  };
+
+/* Group styles. */
+enum
+  {
+    SOM_COL_NONE,                      /* No columns. */
+    SOM_COL_DOWN                       /* Columns down first. */
+  };
+
+/* Cumulation types. */
+enum
+  {
+    SOM_ROWS, SOM_ROW = SOM_ROWS,      /* Rows. */
+    SOM_COLUMNS, SOM_COLUMN = SOM_COLUMNS      /* Columns. */
+  };
+
+/* Flags. */
+enum
+  {
+    SOMF_NONE = 0,
+    SOMF_NO_SPACING = 01,      /* No spacing before the table. */
+    SOMF_NO_TITLE = 02         /* No title. */
+  };
+
+/* Table class. */
+struct outp_driver;
+struct som_table_class
+  {
+    /* Set table, driver. */
+    void (*table) (struct som_table *);
+    void (*driver) (struct outp_driver *);
+
+    /* Query columns and rows. */
+    void (*count) (int *n_columns, int *n_rows);
+    void (*area) (int *horiz, int *vert);
+    void (*width) (int *columns);
+    void (*height) (int *rows);
+    void (*columns) (int *style);
+    int (*breakable) (int row);                                /* ? */
+    void (*headers) (int *l, int *r, int *t, int *b);
+    void (*join) (int *(column[2]), int *(row[2]));    /* ? */
+    void (*cumulate) (int cumtype, int start, int *end, int max, int *actual);
+    void (*flags) (unsigned *);
+
+    /* Set columns and rows. */
+    void (*set_width) (int column, int width);         /* ? */
+    void (*set_height) (int row, int height);          /* ? */
+
+    /* Rendering. */
+    void (*title) (int x, int y);
+    void (*render) (int x1, int y1, int x2, int y2);
+  };
+
+/* Table indexes. */
+extern int table_num;
+extern int subtable_num;
+
+/* Submission. */
+void som_new_series (void);
+void som_submit (struct som_table *t);
+
+/* Miscellaneous. */
+void som_eject_page (void);
+void som_blank_line (void);
+
+#endif /* som_h */
diff --git a/src/sort.c b/src/sort.c
new file mode 100644 (file)
index 0000000..e5feef9
--- /dev/null
@@ -0,0 +1,1385 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include "alloc.h"
+#include "approx.h"
+#include "command.h"
+#include "error.h"
+#include "expr.h"
+#include "heap.h"
+#include "lexer.h"
+#include "misc.h"
+#include "sort.h"
+#include "str.h"
+#include "var.h"
+#include "vfm.h"
+#include "vfmP.h"
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#if HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+/* Variables to sort. */
+struct variable **v_sort;
+int nv_sort;
+
+/* Used when internal-sorting to a separate file. */
+static struct case_list **separate_case_tab;
+
+/* Exported by qsort.c. */
+void blp_quicksort (void *pbase, size_t total_elems, size_t size,
+                   int (*cmp) (const void *, const void *),
+                   void *temp_buf);
+
+/* Other prototypes. */
+static int compare_case_lists (const void *, const void *);
+static int do_internal_sort (int separate);
+static int do_external_sort (int separate);
+int parse_sort_variables (void);
+void read_sort_output (int (*write_case) (void));
+
+/* Performs the SORT CASES procedures. */
+int
+cmd_sort_cases (void)
+{
+  /* First, just parse the command. */
+  lex_match_id ("SORT");
+  lex_match_id ("CASES");
+  lex_match (T_BY);
+
+  if (!parse_sort_variables ())
+    return CMD_FAILURE;
+      
+  cancel_temporary ();
+
+  /* Then it's time to do the actual work.  There are two cases:
+
+     (internal sort) All the data is in memory.  In this case, we
+     perform an EXECUTE to get the data into the desired form, then
+     sort the cases in place, if it is still all in memory.
+
+     (external sort) The data is not in memory.  It may be coming from
+     a system file or other data file, etc.  In any case, it is now
+     time to perform an external sort.  This is better explained in
+     do_external_sort(). */
+
+  /* Do all this dirty work. */
+  {
+    int success = sort_cases (0);
+    free (v_sort);
+    if (success)
+      return lex_end_of_command ();
+    else
+      return CMD_FAILURE;
+  }
+}
+
+/* Parses a list of sort variables into v_sort and nv_sort.  */
+int
+parse_sort_variables (void)
+{
+  v_sort = NULL;
+  nv_sort = 0;
+  do
+    {
+      int prev_nv_sort = nv_sort;
+      int order = SRT_ASCEND;
+
+      if (!parse_variables (&default_dict, &v_sort, &nv_sort,
+                           PV_NO_DUPLICATE | PV_APPEND | PV_NO_SCRATCH))
+       return 0;
+      if (lex_match ('('))
+       {
+         if (lex_match_id ("D") || lex_match_id ("DOWN"))
+           order = SRT_DESCEND;
+         else if (!lex_match_id ("A") && !lex_match_id ("UP"))
+           {
+             free (v_sort);
+             msg (SE, _("`A' or `D' expected inside parentheses."));
+             return 0;
+           }
+         if (!lex_match (')'))
+           {
+             free (v_sort);
+             msg (SE, _("`)' expected."));
+             return 0;
+           }
+       }
+      for (; prev_nv_sort < nv_sort; prev_nv_sort++)
+       v_sort[prev_nv_sort]->p.srt.order = order;
+    }
+  while (token != '.' && token != '/');
+  
+  return 1;
+}
+
+/* Sorts the active file based on the key variables specified in
+   global variables v_sort and nv_sort.  The output is either to the
+   active file, if SEPARATE is zero, or to a separate file, if
+   SEPARATE is nonzero.  In the latter case the output cases can be
+   read with a call to read_sort_output().  (In the former case the
+   output cases should be dealt with through the usual vfm interface.)
+
+   The caller is responsible for freeing v_sort[]. */
+int
+sort_cases (int separate)
+{
+  assert (separate_case_tab == NULL);
+
+  /* Not sure this is necessary but it's good to be safe. */
+  if (separate && vfm_source == &sort_stream)
+    procedure (NULL, NULL, NULL);
+  
+  /* SORT CASES cancels PROCESS IF. */
+  expr_free (process_if_expr);
+  process_if_expr = NULL;
+
+  if (do_internal_sort (separate))
+    return 1;
+
+  page_to_disk ();
+  return do_external_sort (separate);
+}
+
+/* If a reasonable situation is set up, do an internal sort of the
+   data.  Return success. */
+static int
+do_internal_sort (int separate)
+{
+  if (vfm_source != &vfm_disk_stream)
+    {
+      if (vfm_source != &vfm_memory_stream)
+       procedure (NULL, NULL, NULL);
+      if (vfm_source == &vfm_memory_stream)
+       {
+         struct case_list **case_tab = malloc (sizeof *case_tab
+                                        * (vfm_source_info.ncases + 1));
+         if (vfm_source_info.ncases == 0)
+           {
+             free (case_tab);
+             return 1;
+           }
+         if (case_tab != NULL)
+           {
+             struct case_list *clp = memory_source_cases;
+             struct case_list **ctp = case_tab;
+             int i;
+
+             for (; clp; clp = clp->next)
+               *ctp++ = clp;
+             qsort (case_tab, vfm_source_info.ncases, sizeof *case_tab,
+                    compare_case_lists);
+
+             if (!separate)
+               {
+                 memory_source_cases = case_tab[0];
+                 for (i = 1; i < vfm_source_info.ncases; i++)
+                   case_tab[i - 1]->next = case_tab[i];
+                 case_tab[vfm_source_info.ncases - 1]->next = NULL;
+                 free (case_tab);
+               } else {
+                 case_tab[vfm_source_info.ncases] = NULL;
+                 separate_case_tab = case_tab;
+               }
+             
+             return 1;
+           }
+       }
+    }
+  return 0;
+}
+
+/* Compares the NV_SORT variables in V_SORT[] between the `case_list's
+   at _A and _B, and returns a strcmp()-type result. */
+static int
+compare_case_lists (const void *pa, const void *pb)
+{
+  struct case_list *a = *(struct case_list **) pa;
+  struct case_list *b = *(struct case_list **) pb;
+  struct variable *v;
+  int result = 0;
+  int i;
+
+  for (i = 0; i < nv_sort; i++)
+    {
+      v = v_sort[i];
+      
+      if (v->type == NUMERIC)
+       {
+         if (approx_ne (a->c.data[v->fv].f, b->c.data[v->fv].f))
+           {
+             result = (a->c.data[v->fv].f > b->c.data[v->fv].f) ? 1 : -1;
+             break;
+           }
+       }
+      else
+       {
+         result = memcmp (a->c.data[v->fv].s, b->c.data[v->fv].s, v->width);
+         if (result != 0)
+           break;
+       }
+    }
+
+  if (v->p.srt.order == SRT_ASCEND)
+    return result;
+  else
+    {
+      assert (v->p.srt.order == SRT_DESCEND);
+      return -result;
+    }
+}
+\f
+/* External sort. */
+
+/* Maximum number of input + output file handles. */
+#if defined FOPEN_MAX && (FOPEN_MAX - 5 < 18)
+#define MAX_FILE_HANDLES       (FOPEN_MAX - 5)
+#else
+#define MAX_FILE_HANDLES       18
+#endif
+
+#if MAX_FILE_HANDLES < 3
+#error At least 3 file handles must be available for sorting.
+#endif
+
+/* Number of input buffers. */
+#define N_INPUT_BUFFERS                (MAX_FILE_HANDLES - 1)
+
+/* Maximum order of merge.  This is the value suggested by Knuth;
+   specifically, he said to use tree selection, which we don't
+   implement, for larger orders of merge. */
+#define MAX_MERGE_ORDER                7
+
+/* Minimum total number of records for buffers. */
+#define MIN_BUFFER_TOTAL_SIZE_RECS     64
+
+/* Minimum single input or output buffer size, in bytes and records. */
+#define MIN_BUFFER_SIZE_BYTES  4096
+#define MIN_BUFFER_SIZE_RECS   16
+
+/* Structure for replacement selection tree. */
+struct repl_sel_tree
+  {
+    struct repl_sel_tree *loser;/* Loser associated w/this internal node. */
+    int rn;                    /* Run number of `loser'. */
+    struct repl_sel_tree *fe;  /* Internal node above this external node. */
+    struct repl_sel_tree *fi;  /* Internal node above this internal node. */
+    union value record[1];     /* The case proper. */
+  };
+
+/* Static variables used for sorting. */
+static struct repl_sel_tree **x; /* Buffers. */
+static int x_max;              /* Size of buffers, in records. */
+static int records_per_buffer; /* Number of records in each buffer. */
+
+/* In the merge phase, the first N_INPUT_BUFFERS handle[] elements are
+   input files and the last element is the output file.  Before that,
+   they're all used as output files, although the last one is
+   segregated. */
+static FILE *handle[MAX_FILE_HANDLES]; /* File handles. */
+
+/* Now, MAX_FILE_HANDLES is the maximum number of files we will *try*
+   to open.  But if we can't open that many, max_handles will be set
+   to the number we apparently can open. */
+static int max_handles;                /* Maximum number of handles. */
+
+/* When we create temporary files, they are all put in the same
+   directory and numbered sequentially from zero.  tmp_basename is the
+   drive/directory, etc., and tmp_extname can be sprintf() with "%08x"
+   to the file number, then tmp_basename used to open the file. */
+static char *tmp_basename;     /* Temporary file basename. */
+static char *tmp_extname;      /* Temporary file extension name. */
+
+/* We use Huffman's method to determine the merge pattern.  This means
+   that we need to know which runs are the shortest at any given time.
+   Priority queues as implemented by heap.c are a natural for this
+   task (probably because I wrote the code specifically for it). */
+static struct heap *huffman_queue;     /* Huffman priority queue. */
+
+/* Prototypes for helper functions. */
+static void sort_stream_write (void);
+static int write_initial_runs (int separate);
+static int allocate_cases (void);
+static int allocate_file_handles (void);
+static int merge (void);
+static void rmdir_temp_dir (void);
+
+/* Performs an external sort of the active file.  A description of the
+   procedure follows.  All page references refer to Knuth's _Art of
+   Computer Programming, Vol. 3: Sorting and Searching_, which is the
+   canonical resource for sorting.
+
+   1. The data is read and S initial runs are formed through the
+   action of algorithm 5.4.1R (replacement selection).
+
+   2. Huffman's method (p. 365-366) is used to determine the optimum
+   merge pattern.
+
+   3. If an OS that supports overlapped reading, writing, and
+   computing is being run, we should use 5.4.6F for forecasting.
+   Otherwise, buffers are filled only when they run out of data.
+   FIXME: Since the author of PSPP uses GNU/Linux, which does not
+   yet implement overlapped r/w/c, 5.4.6F is not used.
+
+   4. We perform P-way merges:
+
+   (a) The desired P is the smallest P such that ceil(ln(S)/ln(P))
+   is minimized.  (FIXME: Since I don't have an algorithm for
+   minimizing this, it's just set to MAX_MERGE_ORDER.)
+
+   (b) P is reduced if the selected value would make input buffers
+   less than 4096 bytes each, or 16 records, whichever is larger.
+
+   (c) P is reduced if we run out of available file handles or space
+   for file handles.
+
+   (d) P is reduced if we don't have space for one or two output
+   buffers, which have the same minimum size as input buffers.  (We
+   need two output buffers if 5.4.6F is in use for forecasting.)  */
+static int
+do_external_sort (int separate)
+{
+  int success = 0;
+
+  assert (MAX_FILE_HANDLES >= 3);
+
+  x = NULL;
+  tmp_basename = NULL;
+
+  huffman_queue = heap_create (512);
+  if (huffman_queue == NULL)
+    return 0;
+
+  if (!allocate_cases ())
+    goto lossage;
+
+  if (!allocate_file_handles ())
+    goto lossage;
+
+  if (!write_initial_runs (separate))
+    goto lossage;
+
+  merge ();
+
+  success = 1;
+
+  /* Despite the name, flow of control comes here regardless of
+     whether or not the sort is successful. */
+lossage:
+  heap_destroy (huffman_queue);
+
+  if (x)
+    {
+      int i;
+
+      for (i = 0; i <= x_max; i++)
+       free (x[i]);
+      free (x);
+    }
+
+  if (!success)
+    rmdir_temp_dir ();
+
+  return success;
+}
+
+#if !HAVE_GETPID
+#define getpid() (0)
+#endif
+
+/* Sets up to open temporary files. */
+/* PORTME: This creates a directory for temporary files.  Some OSes
+   might not have that concept... */
+static int
+allocate_file_handles (void)
+{
+  const char *dir;             /* Directory prefix. */
+  char *buf;                   /* String buffer. */
+  char *cp;                    /* Pointer into buf. */
+
+  dir = getenv ("SPSSTMPDIR");
+  if (dir == NULL)
+    dir = getenv ("SPSSXTMPDIR");
+  if (dir == NULL)
+    dir = getenv ("TMPDIR");
+#ifdef P_tmpdir
+  if (dir == NULL)
+    dir = P_tmpdir;
+#endif
+#if __unix__
+  if (dir == NULL)
+    dir = "/tmp";
+#elif __MSDOS__
+  if (dir == NULL)
+    dir = getenv ("TEMP");
+  if (dir == NULL)
+    dir = getenv ("TMP");
+  if (dir == NULL)
+    dir = "\\";
+#else
+  dir = "";
+#endif
+
+  buf = xmalloc (strlen (dir) + 1 + 4 + 8 + 4 + 1 + INT_DIGITS + 1);
+  cp = spprintf (buf, "%s%c%04lX%04lXpspp", dir, DIR_SEPARATOR,
+                ((long) time (0)) & 0xffff, ((long) getpid ()) & 0xffff);
+  if (-1 == mkdir (buf, S_IRWXU))
+    {
+      free (buf);
+      msg (SE, _("%s: Cannot create temporary directory: %s."),
+          buf, strerror (errno));
+      return 0;
+    }
+  *cp++ = DIR_SEPARATOR;
+
+  tmp_basename = buf;
+  tmp_extname = cp;
+
+  max_handles = MAX_FILE_HANDLES;
+
+  return 1;
+}
+
+/* Removes the directory created for temporary files, if one exists.
+   Also frees tmp_basename. */
+static void
+rmdir_temp_dir (void)
+{
+  if (NULL == tmp_basename)
+    return;
+
+  tmp_extname[-1] = '\0';
+  if (rmdir (tmp_basename) == -1)
+    msg (SE, _("%s: Error removing directory for temporary files: %s."),
+        tmp_basename, strerror (errno));
+
+  free (tmp_basename);
+}
+
+/* Allocates room for lots of cases as a buffer. */
+static int
+allocate_cases (void)
+{
+  /* This is the size of one case. */
+  const int case_size = (sizeof (struct repl_sel_tree)
+                        + sizeof (union value) * (default_dict.nval - 1)
+                        + sizeof (struct repl_sel_tree *));
+
+  x = NULL;
+
+  /* Allocate as many cases as we can, assuming a space of four
+     void pointers for malloc()'s internal bookkeeping. */
+  x_max = MAX_WORKSPACE / (case_size + 4 * sizeof (void *));
+  x = malloc (sizeof (struct repl_sel_tree *) * x_max);
+  if (x != NULL)
+    {
+      int i;
+
+      for (i = 0; i < x_max; i++)
+       {
+         x[i] = malloc (sizeof (struct repl_sel_tree)
+                        + sizeof (union value) * (default_dict.nval - 1));
+         if (x[i] == NULL)
+           break;
+       }
+      x_max = i;
+    }
+  if (x == NULL || x_max < MIN_BUFFER_TOTAL_SIZE_RECS)
+    {
+      if (x != NULL)
+       {
+         int i;
+         
+         for (i = 0; i < x_max; i++)
+           free (x[i]);
+       }
+      free (x);
+      msg (SE, _("Out of memory.  Could not allocate room for minimum of %d "
+                "cases of %d bytes each.  (PSPP workspace is currently "
+                "restricted to a maximum of %d KB.)"),
+          MIN_BUFFER_TOTAL_SIZE_RECS, case_size, MAX_WORKSPACE / 1024);
+      x_max = 0;
+      x = NULL;
+      return 0;
+    }
+
+  /* The last element of the array is used to store lastkey. */
+  x_max--;
+
+  debug_printf ((_("allocated %d cases == %d bytes\n"),
+                x_max, x_max * case_size));
+  return 1;
+}
+\f
+/* Replacement selection. */
+
+static int rmax, rc, rq;
+static struct repl_sel_tree *q;
+static union value *lastkey;
+static int run_no, file_index;
+static int deferred_abort;
+static int run_length;
+
+static int compare_record (union value *, union value *);
+
+static inline void
+output_record (union value *v)
+{
+  union value *src_case;
+  
+  if (deferred_abort)
+    return;
+
+  if (compaction_necessary)
+    {
+      compact_case (compaction_case, (struct ccase *) v);
+      src_case = (union value *) compaction_case;
+    }
+  else
+    src_case = (union value *) v;
+
+  if ((int) fwrite (src_case, sizeof *src_case, compaction_nval,
+                   handle[file_index])
+      != compaction_nval)
+    {
+      deferred_abort = 1;
+      sprintf (tmp_extname, "%08x", run_no);
+      msg (SE, _("%s: Error writing temporary file: %s."),
+          tmp_basename, strerror (errno));
+      return;
+    }
+
+  run_length++;
+}
+
+static int
+close_handle (int i)
+{
+  int result = fclose (handle[i]);
+  msg (VM (2), _("SORT: Closing handle %d."), i);
+  
+  handle[i] = NULL;
+  if (EOF == result)
+    {
+      sprintf (tmp_extname, "%08x", i);
+      msg (SE, _("%s: Error closing temporary file: %s."),
+          tmp_basename, strerror (errno));
+      return 0;
+    }
+  return 1;
+}
+
+static int
+close_handles (int beg, int end)
+{
+  int success = 1;
+  int i;
+
+  for (i = beg; i < end; i++)
+    success &= close_handle (i);
+  return success;
+}
+
+static int
+open_handle_w (int handle_no, int run_no)
+{
+  sprintf (tmp_extname, "%08x", run_no);
+  msg (VM (1), _("SORT: %s: Opening for writing as run %d."),
+       tmp_basename, run_no);
+
+  /* The `x' modifier causes the GNU C library to insist on creating a
+     new file--if the file already exists, an error is signaled.  The
+     ANSI C standard says that other libraries should ignore anything
+     after the `w+b', so it shouldn't be a problem. */
+  return NULL != (handle[handle_no] = fopen (tmp_basename, "w+bx"));
+}
+
+static int
+open_handle_r (int handle_no, int run_no)
+{
+  FILE *f;
+
+  sprintf (tmp_extname, "%08x", run_no);
+  msg (VM (1), _("SORT: %s: Opening for writing as run %d."),
+       tmp_basename, run_no);
+  f = handle[handle_no] = fopen (tmp_basename, "rb");
+
+  if (f == NULL)
+    {
+      msg (SE, _("%s: Error opening temporary file for reading: %s."),
+          tmp_basename, strerror (errno));
+      return 0;
+    }
+  
+  return 1;
+}
+
+/* Begins a new initial run, specifically its output file. */
+static void
+begin_run (void)
+{
+  /* Decide which handle[] to use.  If run_no is max_handles or
+     greater, then we've run out of handles so it's time to just do
+     one file at a time, which by default is handle 0. */
+  file_index = (run_no < max_handles ? run_no : 0);
+  run_length = 0;
+
+  /* Alright, now create the temporary file. */
+  if (open_handle_w (file_index, run_no) == 0)
+    {
+      /* Failure to create the temporary file.  Check if there are
+         unacceptably few files already open. */
+      if (file_index < 3)
+       {
+         deferred_abort = 1;
+         msg (SE, _("%s: Error creating temporary file: %s."),
+              tmp_basename, strerror (errno));
+         return;
+       }
+
+      /* Close all the open temporary files. */
+      if (!close_handles (0, file_index))
+       return;
+
+      /* Now try again to create the temporary file. */
+      max_handles = file_index;
+      file_index = 0;
+      if (open_handle_w (0, run_no) == 0)
+       {
+         /* It still failed, report it this time. */
+         deferred_abort = 1;
+         msg (SE, _("%s: Error creating temporary file: %s."),
+              tmp_basename, strerror (errno));
+         return;
+       }
+    }
+}
+
+/* Ends the current initial run.  Just increments run_no if no initial
+   run has been started yet. */
+static void
+end_run (void)
+{
+  /* Close file handles if necessary. */
+  {
+    int result;
+
+    if (run_no == max_handles - 1)
+      result = close_handles (0, max_handles);
+    else if (run_no >= max_handles)
+      result = close_handle (0);
+    else
+      result = 1;
+    if (!result)
+      deferred_abort = 1;
+  }
+
+  /* Advance to next run. */
+  run_no++;
+  if (run_no)
+    heap_insert (huffman_queue, run_no - 1, run_length);
+}
+
+/* Performs 5.4.1R. */
+static int
+write_initial_runs (int separate)
+{
+  run_no = -1;
+  deferred_abort = 0;
+
+  /* Steps R1, R2, R3. */
+  rmax = 0;
+  rc = 0;
+  lastkey = NULL;
+  q = x[0];
+  rq = 0;
+  {
+    int j;
+
+    for (j = 0; j < x_max; j++)
+      {
+       struct repl_sel_tree *J = x[j];
+
+       J->loser = J;
+       J->rn = 0;
+       J->fe = x[(x_max + j) / 2];
+       J->fi = x[j / 2];
+       memset (J->record, 0, default_dict.nval * sizeof (union value));
+      }
+  }
+
+  /* Most of the iterations of steps R4, R5, R6, R7, R2, R3, ... */
+  if (!separate)
+    {
+      if (vfm_sink)
+       vfm_sink->destroy_sink ();
+      vfm_sink = &sort_stream;
+    }
+  procedure (NULL, NULL, NULL);
+
+  /* Final iterations of steps R4, R5, R6, R7, R2, R3, ... */
+  for (;;)
+    {
+      struct repl_sel_tree *t;
+
+      /* R4. */
+      rq = rmax + 1;
+
+      /* R5. */
+      t = q->fe;
+
+      /* R6 and R7. */
+      for (;;)
+       {
+         /* R6. */
+         if (t->rn < rq
+             || (t->rn == rq
+                 && compare_record (t->loser->record, q->record) < 0))
+           {
+             struct repl_sel_tree *temp_tree;
+             int temp_int;
+
+             temp_tree = t->loser;
+             t->loser = q;
+             q = temp_tree;
+
+             temp_int = t->rn;
+             t->rn = rq;
+             rq = temp_int;
+           }
+
+         /* R7. */
+         if (t == x[1])
+           break;
+         t = t->fi;
+       }
+
+      /* R2. */
+      if (rq != rc)
+       {
+         end_run ();
+         if (rq > rmax)
+           break;
+         begin_run ();
+         rc = rq;
+       }
+
+      /* R3. */
+      if (rq != 0)
+       {
+         output_record (q->record);
+         lastkey = x[x_max]->record;
+         memcpy (lastkey, q->record, sizeof (union value) * vfm_sink_info.nval);
+       }
+    }
+  assert (run_no == rmax);
+
+  /* If an unrecoverable error occurred somewhere in the above code,
+     then the `deferred_abort' flag would have been set.  */
+  if (deferred_abort)
+    {
+      int i;
+
+      for (i = 0; i < max_handles; i++)
+       if (handle[i] != NULL)
+         {
+           sprintf (tmp_extname, "%08x", i);
+
+           if (fclose (handle[i]) == EOF)
+             msg (SE, _("%s: Error closing temporary file: %s."),
+                  tmp_basename, strerror (errno));
+
+           if (remove (tmp_basename) != 0)
+             msg (SE, _("%s: Error removing temporary file: %s."),
+                  tmp_basename, strerror (errno));
+
+           handle[i] = NULL;
+         }
+      return 0;
+    }
+
+  return 1;
+}
+
+/* Compares the NV_SORT variables in V_SORT[] between the `value's at
+   A and B, and returns a strcmp()-type result. */
+static int
+compare_record (union value * a, union value * b)
+{
+  int i;
+  int result = 0;
+  struct variable *v;
+
+  assert (a != NULL);
+  if (b == NULL)               /* Sort NULLs after everything else. */
+    return -1;
+
+  for (i = 0; i < nv_sort; i++)
+    {
+      v = v_sort[i];
+
+      if (v->type == NUMERIC)
+       {
+         if (approx_ne (a[v->fv].f, b[v->fv].f))
+           {
+             result = (a[v->fv].f > b[v->fv].f) ? 1 : -1;
+             break;
+           }
+       }
+      else
+       {
+         result = memcmp (a[v->fv].s, b[v->fv].s, v->width);
+         if (result != 0)
+           break;
+       }
+    }
+
+  if (v->p.srt.order == SRT_ASCEND)
+    return result;
+  else
+    {
+      assert (v->p.srt.order == SRT_DESCEND);
+      return -result;
+    }
+}
+\f
+/* Merging. */
+
+static int merge_once (int run_index[], int run_length[], int n_runs);
+
+/* Modula function as defined by Knuth. */
+static int
+mod (int x, int y)
+{
+  int result;
+
+  if (y == 0)
+    return x;
+  result = abs (x) % abs (y);
+  if (y < 0)
+    result = -result;
+  return result;
+}
+
+/* Performs a series of P-way merges of initial runs using Huffman's
+   method. */
+static int
+merge (void)
+{
+  /* Order of merge. */
+  int order;
+
+  /* Idiot check. */
+  assert (MIN_BUFFER_SIZE_RECS * 2 <= MIN_BUFFER_TOTAL_SIZE_RECS - 1);
+
+  /* Close all the input files.  I hope that the boundary conditions
+     are correct on this but I'm not sure. */
+  if (run_no < max_handles)
+    {
+      int i;
+
+      for (i = 0; i < run_no; )
+       if (!close_handle (i++))
+         {
+           for (; i < run_no; i++)
+             close_handle (i);
+           return 0;
+         }
+    }
+
+  /* Determine order of merge. */
+  order = MAX_MERGE_ORDER;
+  if (x_max / order < MIN_BUFFER_SIZE_RECS)
+    order = x_max / MIN_BUFFER_SIZE_RECS;
+  else if (x_max / order * sizeof (union value) * default_dict.nval
+          < MIN_BUFFER_SIZE_BYTES)
+    order = x_max / (MIN_BUFFER_SIZE_BYTES
+                    / (sizeof (union value) * (default_dict.nval - 1)));
+
+  /* Make sure the order of merge is bounded. */
+  if (order < 2)
+    order = 2;
+  if (order > rmax)
+    order = rmax;
+  assert (x_max / order > 0);
+
+  /* Calculate number of records per buffer. */
+  records_per_buffer = x_max / order;
+
+  /* Add (1 - S) mod (P - 1) dummy runs of length 0. */
+  {
+    int n_dummy_runs = mod (1 - rmax, order - 1);
+    debug_printf (("rmax=%d, order=%d, n_dummy_runs=%d\n",
+                  rmax, order, n_dummy_runs));
+    assert (n_dummy_runs >= 0);
+    while (n_dummy_runs--)
+      {
+       heap_insert (huffman_queue, -2, 0);
+       rmax++;
+      }
+  }
+
+  /* Repeatedly merge the P shortest existing runs until only one run
+     is left. */
+  while (rmax > 1)
+    {
+      int run_index[MAX_MERGE_ORDER];
+      int run_length[MAX_MERGE_ORDER];
+      int total_run_length = 0;
+      int i;
+
+      assert (rmax >= order);
+
+      /* Find the shortest runs; put them in runs[] in reverse order
+         of length, to force dummy runs of length 0 to the end of the
+         list. */
+      debug_printf ((_("merging runs")));
+      for (i = order - 1; i >= 0; i--)
+       {
+         run_index[i] = heap_delete (huffman_queue, &run_length[i]);
+         assert (run_index[i] != -1);
+         total_run_length += run_length[i];
+         debug_printf ((" %d(%d)", run_index[i], run_length[i]));
+       }
+      debug_printf ((_(" into run %d(%d)\n"), run_no, total_run_length));
+
+      if (!merge_once (run_index, run_length, order))
+       {
+         int index;
+
+         while (-1 != (index = heap_delete (huffman_queue, NULL)))
+           {
+             sprintf (tmp_extname, "%08x", index);
+             if (remove (tmp_basename) != 0)
+               msg (SE, _("%s: Error removing temporary file: %s."),
+                    tmp_basename, strerror (errno));
+           }
+
+         return 0;
+       }
+
+      if (!heap_insert (huffman_queue, run_no++, total_run_length))
+       {
+         msg (SE, _("Out of memory expanding Huffman priority queue."));
+         return 0;
+       }
+
+      rmax -= order - 1;
+    }
+
+  /* There should be exactly one element in the priority queue after
+     all that merging.  This represents the entire sorted active file.
+     So we could find a total case count by deleting this element from
+     the queue. */
+  assert (heap_size (huffman_queue) == 1);
+
+  return 1;
+}
+
+/* Merges N_RUNS initial runs into a new run.  The jth run for 0 <= j
+   < N_RUNS is taken from temporary file RUN_INDEX[j]; it is composed
+   of RUN_LENGTH[j] cases. */
+static int
+merge_once (int run_index[], int run_length[], int n_runs)
+{
+  /* For each run, the number of records remaining in its buffer. */
+  int buffered[MAX_MERGE_ORDER];
+
+  /* For each run, the index of the next record in the buffer. */
+  int buffer_ptr[MAX_MERGE_ORDER];
+
+  /* Open input files. */
+  {
+    int i;
+
+    for (i = 0; i < n_runs; i++)
+      if (run_index[i] != -2 && !open_handle_r (i, run_index[i]))
+       {
+         /* Close and remove temporary files. */
+         while (i--)
+           {
+             close_handle (i);
+             sprintf (tmp_extname, "%08x", i);
+             if (remove (tmp_basename) != 0)
+               msg (SE, _("%s: Error removing temporary file: %s."),
+                    tmp_basename, strerror (errno));
+           }
+
+         return 0;
+       }
+  }
+
+  /* Create output file. */
+  if (!open_handle_w (N_INPUT_BUFFERS, run_no))
+    {
+      msg (SE, _("%s: Error creating temporary file for merge: %s."),
+          tmp_basename, strerror (errno));
+      goto lossage;
+    }
+
+  /* Prime each buffer. */
+  {
+    int i;
+
+    for (i = 0; i < n_runs; i++)
+      if (run_index[i] == -2)
+       {
+         n_runs = i;
+         break;
+       }
+      else
+       {
+         int j;
+         int ofs = records_per_buffer * i;
+
+         buffered[i] = min (records_per_buffer, run_length[i]);
+         for (j = 0; j < buffered[i]; j++)
+           if ((int) fread (x[j + ofs]->record, sizeof (union value),
+                            default_dict.nval, handle[i])
+               != default_dict.nval)
+             {
+               sprintf (tmp_extname, "%08x", run_index[i]);
+               if (ferror (handle[i]))
+                 msg (SE, _("%s: Error reading temporary file in merge: %s."),
+                      tmp_basename, strerror (errno));
+               else
+                 msg (SE, _("%s: Unexpected end of temporary file in merge."),
+                      tmp_basename);
+               goto lossage;
+             }
+         buffer_ptr[i] = ofs;
+         run_length[i] -= buffered[i];
+       }
+  }
+
+  /* Perform the merge proper. */
+  while (n_runs)               /* Loop while some data is left. */
+    {
+      int i;
+      int min = 0;
+
+      for (i = 1; i < n_runs; i++)
+       if (compare_record (x[buffer_ptr[min]]->record,
+                           x[buffer_ptr[i]]->record) > 0)
+         min = i;
+
+      if ((int) fwrite (x[buffer_ptr[min]]->record, sizeof (union value),
+                       default_dict.nval, handle[N_INPUT_BUFFERS])
+         != default_dict.nval)
+       {
+         sprintf (tmp_extname, "%08x", run_index[i]);
+         msg (SE, _("%s: Error writing temporary file in "
+              "merge: %s."), tmp_basename, strerror (errno));
+         goto lossage;
+       }
+
+      /* Remove one case from the buffer for this input file. */
+      if (--buffered[min] == 0)
+       {
+         /* The input buffer is empty.  Do any cases remain in the
+            initial run on disk? */
+         if (run_length[min])
+           {
+             /* Yes.  Read them in. */
+
+             int j;
+             int ofs;
+
+             /* Reset the buffer pointer.  Note that we can't simply
+                set it to (i * records_per_buffer) since the run
+                order might have changed. */
+             ofs = buffer_ptr[min] -= buffer_ptr[min] % records_per_buffer;
+
+             buffered[min] = min (records_per_buffer, run_length[min]);
+             for (j = 0; j < buffered[min]; j++)
+               if ((int) fread (x[j + ofs]->record, sizeof (union value),
+                                default_dict.nval, handle[min])
+                   != default_dict.nval)
+                 {
+                   sprintf (tmp_extname, "%08x", run_index[min]);
+                   if (ferror (handle[min]))
+                     msg (SE, _("%s: Error reading temporary file in "
+                                "merge: %s."),
+                          tmp_basename, strerror (errno));
+                   else
+                     msg (SE, _("%s: Unexpected end of temporary file "
+                                "in merge."),
+                          tmp_basename);
+                   goto lossage;
+                 }
+             run_length[min] -= buffered[min];
+           }
+         else
+           {
+             /* No.  Delete this run. */
+
+             /* Close the file. */
+             FILE *f = handle[min];
+             handle[min] = NULL;
+             sprintf (tmp_extname, "%08x", run_index[min]);
+             if (fclose (f) == EOF)
+               msg (SE, _("%s: Error closing temporary file in merge: "
+                    "%s."), tmp_basename, strerror (errno));
+
+             /* Delete the file. */
+             if (remove (tmp_basename) != 0)
+               msg (SE, _("%s: Error removing temporary file in merge: "
+                    "%s."), tmp_basename, strerror (errno));
+
+             n_runs--;
+             if (min != n_runs)
+               {
+                 /* Since this isn't the last run, we move the last
+                    run into its spot to force all the runs to be
+                    contiguous. */
+                 run_index[min] = run_index[n_runs];
+                 run_length[min] = run_length[n_runs];
+                 buffer_ptr[min] = buffer_ptr[n_runs];
+                 buffered[min] = buffered[n_runs];
+                 handle[min] = handle[n_runs];
+               }
+           }
+       }
+      else
+       buffer_ptr[min]++;
+    }
+
+  /* Close output file. */
+  {
+    FILE *f = handle[N_INPUT_BUFFERS];
+    handle[N_INPUT_BUFFERS] = NULL;
+    if (fclose (f) == EOF)
+      {
+       sprintf (tmp_extname, "%08x", run_no);
+       msg (SE, _("%s: Error closing temporary file in merge: "
+                  "%s."),
+            tmp_basename, strerror (errno));
+       return 0;
+      }
+  }
+
+  return 1;
+
+lossage:
+  /* Close all the input and output files. */
+  {
+    int i;
+
+    for (i = 0; i < n_runs; i++)
+      if (run_length[i] != 0)
+       {
+         close_handle (i);
+         sprintf (tmp_basename, "%08x", run_index[i]);
+         if (remove (tmp_basename) != 0)
+           msg (SE, _("%s: Error removing temporary file: %s."),
+                tmp_basename, strerror (errno));
+       }
+  }
+  close_handle (N_INPUT_BUFFERS);
+  sprintf (tmp_basename, "%08x", run_no);
+  if (remove (tmp_basename) != 0)
+    msg (SE, _("%s: Error removing temporary file: %s."),
+        tmp_basename, strerror (errno));
+  return 0;
+}
+\f
+/* External sort input program. */
+
+/* Reads all the records from the source stream and passes them
+   to write_case(). */
+void
+sort_stream_read (void)
+{
+  read_sort_output (write_case);
+}
+
+/* Reads all the records from the output stream and passes them to the
+   function provided, which must have an interface identical to
+   write_case(). */
+void
+read_sort_output (int (*write_case) (void))
+{
+  int i;
+  FILE *f;
+
+  if (separate_case_tab)
+    {
+      struct ccase *save_temp_case = temp_case;
+      struct case_list **p;
+
+      for (p = separate_case_tab; *p; p++)
+       {
+         temp_case = &(*p)->c;
+         write_case ();
+       }
+      
+      free (separate_case_tab);
+      separate_case_tab = NULL;
+           
+      temp_case = save_temp_case;
+    } else {
+      sprintf (tmp_extname, "%08x", run_no - 1);
+      f = fopen (tmp_basename, "rb");
+      if (!f)
+       {
+         msg (ME, _("%s: Cannot open sort result file: %s."), tmp_basename,
+              strerror (errno));
+         err_failure ();
+         return;
+       }
+
+      for (i = 0; i < vfm_source_info.ncases; i++)
+       {
+         if (!fread (temp_case, vfm_source_info.case_size, 1, f))
+           {
+             if (ferror (f))
+               msg (ME, _("%s: Error reading sort result file: %s."),
+                    tmp_basename, strerror (errno));
+             else
+               msg (ME, _("%s: Unexpected end of sort result file: %s."),
+                    tmp_basename, strerror (errno));
+             err_failure ();
+             break;
+           }
+
+         if (!write_case ())
+           break;
+       }
+
+      if (fclose (f) == EOF)
+       msg (ME, _("%s: Error closing sort result file: %s."), tmp_basename,
+            strerror (errno));
+
+      if (remove (tmp_basename) != 0)
+       msg (ME, _("%s: Error removing sort result file: %s."), tmp_basename,
+            strerror (errno));
+      else
+       rmdir_temp_dir ();
+    }
+}
+
+#if 0 /* dead code */
+/* Alternate interface to sort_stream_write used for external sorting
+   when SEPARATE is true. */
+static int
+write_separate (struct ccase *c)
+{
+  assert (c == temp_case);
+
+  sort_stream_write ();
+  return 1;
+}
+#endif
+
+/* Performs one iteration of 5.4.1R steps R4, R5, R6, R7, R2, and
+   R3. */
+static void
+sort_stream_write (void)
+{
+  struct repl_sel_tree *t;
+
+  /* R4. */
+  memcpy (q->record, temp_case->data, vfm_sink_info.case_size);
+  if (compare_record (q->record, lastkey) < 0)
+    if (++rq > rmax)
+      rmax = rq;
+
+  /* R5. */
+  t = q->fe;
+
+  /* R6 and R7. */
+  for (;;)
+    {
+      /* R6. */
+      if (t->rn < rq
+         || (t->rn == rq && compare_record (t->loser->record, q->record) < 0))
+       {
+         struct repl_sel_tree *temp_tree;
+         int temp_int;
+
+         temp_tree = t->loser;
+         t->loser = q;
+         q = temp_tree;
+
+         temp_int = t->rn;
+         t->rn = rq;
+         rq = temp_int;
+       }
+
+      /* R7. */
+      if (t == x[1])
+       break;
+      t = t->fi;
+    }
+
+  /* R2. */
+  if (rq != rc)
+    {
+      end_run ();
+      begin_run ();
+      assert (rq <= rmax);
+      rc = rq;
+    }
+
+  /* R3. */
+  if (rq != 0)
+    {
+      output_record (q->record);
+      lastkey = x[x_max]->record;
+      memcpy (lastkey, q->record, vfm_sink_info.case_size);
+    }
+}
+
+/* Switches mode from sink to source. */
+void
+sort_stream_mode (void)
+{
+  /* If this is not done, then we get the following source/sink pairs:
+     source=memory/disk/DATA LIST/etc., sink=SORT; source=SORT,
+     sink=SORT; which is not good. */
+  vfm_sink = NULL;
+}
+
+struct case_stream sort_stream =
+  {
+    NULL,
+    sort_stream_read,
+    sort_stream_write,
+    sort_stream_mode,
+    NULL,
+    NULL,
+    "SORT",
+  };
diff --git a/src/sort.h b/src/sort.h
new file mode 100644 (file)
index 0000000..e6c9fed
--- /dev/null
@@ -0,0 +1,31 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !sort_h
+#define sort_h 1
+
+/* SORT CASES programmatic interface. */
+int sort_cases (int separate);
+void read_sort_output (int (*write_case)(void));
+
+/* Variables to sort. */
+extern struct variable **v_sort;
+extern int nv_sort;
+
+#endif /* !sort_h */
diff --git a/src/split-file.c b/src/split-file.c
new file mode 100644 (file)
index 0000000..29fc432
--- /dev/null
@@ -0,0 +1,56 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "str.h"
+#include "var.h"
+
+int
+cmd_split_file (void)
+{
+  lex_match_id ("SPLIT");
+  lex_match_id ("FILE");
+
+  if (lex_match_id ("OFF"))
+    {
+      default_dict.n_splits = 0;
+      free (default_dict.splits);
+      default_dict.splits = NULL;
+    }
+  else
+    {
+      struct variable **v;
+      int n;
+
+      lex_match (T_BY);
+      if (!parse_variables (NULL, &v, &n, PV_NO_DUPLICATE))
+       return CMD_FAILURE;
+
+      default_dict.n_splits = n;
+      default_dict.splits = v = xrealloc (v, sizeof *v * (n + 1));
+      v[n] = NULL;
+    }
+
+  return lex_end_of_command ();
+}
diff --git a/src/stat.h b/src/stat.h
new file mode 100644 (file)
index 0000000..6958085
--- /dev/null
@@ -0,0 +1,65 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <sys/stat.h>
+
+#ifdef STAT_MACROS_BROKEN
+#undef S_ISBLK
+#undef S_ISCHR
+#undef S_ISDIR
+#undef S_ISFIFO
+#undef S_ISLNK
+#undef S_ISMPB
+#undef S_ISMPC
+#undef S_ISNWK
+#undef S_ISREG
+#undef S_ISSOCK
+#endif /* STAT_MACROS_BROKEN.  */
+
+#if !defined(S_ISBLK) && defined(S_IFBLK)
+#define        S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
+#endif
+#if !defined(S_ISCHR) && defined(S_IFCHR)
+#define        S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
+#endif
+#if !defined(S_ISDIR) && defined(S_IFDIR)
+#define        S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+#endif
+#if !defined(S_ISREG) && defined(S_IFREG)
+#define        S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
+#endif
+#if !defined(S_ISFIFO) && defined(S_IFIFO)
+#define        S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
+#endif
+#if !defined(S_ISLNK) && defined(S_IFLNK)
+#define        S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
+#endif
+#if !defined(S_ISSOCK) && defined(S_IFSOCK)
+#define        S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
+#endif
+#if !defined(S_ISMPB) && defined(S_IFMPB) /* V7 */
+#define S_ISMPB(m) (((m) & S_IFMT) == S_IFMPB)
+#define S_ISMPC(m) (((m) & S_IFMT) == S_IFMPC)
+#endif
+#if !defined(S_ISNWK) && defined(S_IFNWK) /* HP/UX */
+#define S_ISNWK(m) (((m) & S_IFMT) == S_IFNWK)
+#endif
+#if !defined(HAVE_MKFIFO)
+#define mkfifo(path, mode) (mknod ((path), (mode) | S_IFIFO, 0))
+#endif
diff --git a/src/stats.c b/src/stats.c
new file mode 100644 (file)
index 0000000..a52ab40
--- /dev/null
@@ -0,0 +1,203 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <math.h>
+#include "stats.h"
+
+/* Returns the fourth power of its argument. */
+double
+hypercube (double x)
+{
+  x *= x;
+  return x * x;
+}
+
+/* Returns the cube of its argument. */
+double
+cube (double x)
+{
+  return x * x * x;
+}
+
+/* Returns the square of its argument. */
+double
+sqr (double x)
+{
+  return x * x;
+}
+
+/*
+ * kurtosis = [(n+1){n*sum(X**4) - 4*sum(X)*sum(X**3)
+ *                   + 6*sum(X)**2*sum(X**2)/n - 3*sum(X)**4/n**2}]
+ *           /[(n-1)(n-2)(n-3)*(variance)**2]
+ *             -[3*{(n-1)**2}]
+ *             /[(n-2)(n-3)]
+ *
+ * This and other formulas from _Biometry_, Sokal and Rohlf,
+ * W. H. Freeman and Company, 1969.  See pages 117 and 136 especially.
+ */
+double
+calc_kurt (const double d[4], double n, double variance)
+{
+  return
+    (((n + 1) * (n * d[3]
+                - 4.0 * d[0] * d[2]
+                + 6.0 * sqr (d[0]) * d[1] / n
+                - 3.0 * hypercube (d[0]) / sqr (n)))
+     / ((n - 1.0) * (n - 2.0) * (n - 3.0) * sqr (variance))
+     - (3.0 * sqr (n - 1.0))
+     / ((n - 2.0) * (n - 3.)));
+}
+
+/*
+ * standard error of kurtosis = sqrt([24n((n-1)**2)]/[(n-3)(n-2)(n+3)(n+5)])
+ */
+double
+calc_sekurt (double n)
+{
+  return sqrt ((24.0 * n * sqr (n - 1.0))
+              / ((n - 3.0) * (n - 2.0) * (n + 3.0) * (n + 5.0)));
+}
+
+/*
+ * skewness = [n*sum(X**3) - 3*sum(X)*sum(X**2) + 2*sum(X)**3/n]/
+ *           /[(n-1)(n-2)*(variance)**3]
+ */
+double
+calc_skew (const double d[3], double n, double stddev)
+{
+  return
+    ((n * d[2] - 3.0 * d[0] * d[1] + 2.0 * cube (d[0]) / n)
+     / ((n - 1.0) * (n - 2.0) * cube (stddev)));
+}
+
+/*
+ * standard error of skewness = sqrt([6n(n-1)] / [(n-2)(n+1)(n+3)])
+ */
+double
+calc_seskew (double n)
+{
+  return
+    sqrt ((6.0 * n * (n - 1.0))
+         / ((n - 2.0) * (n + 1.0) * (n + 3.0)));
+}
+
+/* Returns one-sided significance level corresponding to standard
+   normal deviate X.  Algorithm from _SPSS Statistical Algorithms_,
+   Appendix 1. */
+#if 0
+double
+normal_sig (double x)
+{
+  const double a1 = .070523078;
+  const double a2 = .0422820123;
+  const double a3 = .0092705272;
+  const double a4 = .0001520143;
+  const double a5 = .0002765672;
+  const double a6 = .0000430638;
+
+  const double z = fabs (x) <= 14.14 ? 0.7071067812 * fabs (x) : 10.;
+  double r;
+
+  r = 1. + z * (a1 + z * (a2 + z * (a3 + z * (a4 + z * (a5 + z * a6)))));
+  r *= r;      /* r ** 2 */
+  r *= r;      /* r ** 4 */
+  r *= r;      /* r ** 16 */
+
+  return .5 / r;
+}
+#else /* 1 */
+/* Taken from _BASIC Statistics: An Introduction to Problem Solving
+   with Your Personal Computer_, Jerry W. O'Dell, TAB 1984, page 314-5. */
+double
+normal_sig (double z)
+{
+  double h;
+
+  h = 1 + 0.0498673470 * z;
+  z *= z;
+  h += 0.0211410061 * z;
+  z *= z;
+  h += 0.0032776263 * z;
+  z *= z;
+  h += 0.0000380036 * z;
+  z *= z;
+  h += 0.0000488906 * z;
+  z *= z;
+  h += 0.0000053830 * z;
+  return pow (h, -16.) / 2.;
+}
+#endif /* 1 */
+
+/* Algorithm from _Turbo Pascal Programmer's Toolkit_, Rugg and
+   Feldman, Que 1989.  Returns the significance level of chi-square
+   value CHISQ with DF degrees of freedom, correct to at least 7
+   decimal places.  */
+double
+chisq_sig (double x, int k)
+{
+  if (x <= 0. || k < 1)
+    return 1.0;
+  else if (k == 1)
+    return 2. * normal_sig (sqrt (x));
+  else if (k <= 30)
+    {
+      double z, z_partial, term, denom, numerator, value;
+
+      z = 1.;
+      z_partial = 1.;
+      term = k;
+      do
+       {
+         term += 2;
+         z_partial *= x / term;
+         if (z_partial >= 10000000.)
+           return 0.;
+         z += z_partial;
+       }
+      while (z_partial >= 1.e-7);
+      denom = term = 2 - k % 2;
+      while (term < k)
+       {
+         term += 2;
+         denom *= term;
+       }
+      if (k % 2)
+       {
+         value = ((k + 1) / 2) * log (x) - x / 2.;
+         numerator = exp (value) * sqrt (2. / x / PI);
+       }
+      else
+       {
+         value = k / 2. * log (x) - x / 2.;
+         numerator = exp (value);
+       }
+      return 1. - numerator * z / denom;
+    }
+  else
+    {
+      double term, numer, norm_x;
+
+      term = 2. / 9. / k;
+      numer = pow (x / k, 1. / 3.);
+      norm_x = numer / sqrt (term);
+      return 1.0 - normal_sig (norm_x);
+    }
+}
diff --git a/src/stats.h b/src/stats.h
new file mode 100644 (file)
index 0000000..81a3f11
--- /dev/null
@@ -0,0 +1,84 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !statistics_h
+#define statistics_h 1
+
+/* These are all sample statistics except for mean since uses
+   population statistics for whatever reason. */
+
+/* Define pi to the maximum precision available. */
+#include <math.h>              /* defines M_PI on many systems */
+#ifndef PI
+#ifdef M_PI
+#define PI M_PI
+#else /* !PI && !M_PI */
+#define PI 3.14159265358979323846264338327
+#endif /* !PI && !M_PI */
+#endif /* !PI */
+
+/* Returns the fourth power of its argument. */
+extern inline double
+hypercube (double x)
+{
+  x *= x;
+  return x * x;
+}
+
+/* Returns the cube of its argument. */
+extern inline double
+cube (double x)
+{
+  return x * x * x;
+}
+
+/* Returns the square of its argument. */
+extern inline double
+sqr (double x)
+{
+  return x * x;
+}
+
+/* Mean, standard error of mean. */
+#define calc_mean(D, N)                                        \
+       ((D)[0] / (N))
+#define calc_semean(STDDEV, N)                         \
+       ((STDDEV) / sqrt (N))
+
+/* Variance, standard deviation, coefficient of variance. */
+#define calc_variance(D, N)                            \
+       ( ((D)[1] - sqr ((D)[0])/(N)) / ((N)-1) )
+#define calc_stddev(VARIANCE)                  \
+       (sqrt (VARIANCE))
+#define calc_cfvar(D, N)                                       \
+       ( calc_stddev (calc_variance (D, N)) / calc_mean (D, N) )
+
+/* Kurtosis, standard error of kurtosis. */
+double calc_kurt (const double d[4], double n, double variance);
+double calc_sekurt (double n);
+
+/* Skewness, standard error of skewness. */
+double calc_skew (const double d[3], double n, double stddev);
+double calc_seskew (double n);
+
+/* Significance. */
+double normal_sig (double x);
+double chisq_sig (double chisq, int df);
+
+#endif /* !statistics_h */
diff --git a/src/str.c b/src/str.c
new file mode 100644 (file)
index 0000000..4bf2d60
--- /dev/null
+++ b/src/str.c
@@ -0,0 +1,584 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "error.h"
+#include "pool.h"
+#include "str.h"
+\f
+/* sprintf() wrapper functions for convenience. */
+
+#if !__GNUC__
+char *
+spprintf (char *buf, const char *format,...)
+{
+#if HAVE_GOOD_SPRINTF
+  int count;
+#endif
+  va_list args;
+
+  va_start (args, format);
+#if HAVE_GOOD_SPRINTF
+  count =
+#endif
+    vsprintf (buf, format, args);
+  va_end (args);
+
+#if HAVE_GOOD_SPRINTF
+  return &buf[count];
+#else
+  return strchr (buf, 0);
+#endif
+}
+#endif /* !__GNUC__ */
+
+#if !__GNUC__ && !HAVE_GOOD_SPRINTF
+int
+nsprintf (char *buf, const char *format,...)
+{
+  va_list args;
+
+  va_start (args, format);
+  vsprintf (buf, format, args);
+  va_end (args);
+
+  return strlen (buf);
+}
+
+int
+nvsprintf (char *buf, const char *format, va_list args)
+{
+  vsprintf (buf, format, args);
+  return strlen (buf);
+}
+#endif /* Not GNU C and not good sprintf(). */
+\f
+/* Reverses the order of NBYTES bytes at address P, thus converting
+   between little- and big-endian byte orders.  */
+void
+mm_reverse (void *p, size_t nbytes)
+{
+  unsigned char *h = p, *t = &h[nbytes - 1];
+  unsigned char temp;
+
+  nbytes /= 2;
+  while (nbytes--)
+    {
+      temp = *h;
+      *h++ = *t;
+      *t-- = temp;
+    }
+}
+
+/* Finds the last NEEDLE of length NEEDLE_LEN in a HAYSTACK of length
+   HAYSTACK_LEN.  Returns a pointer to the needle found. */
+char *
+mm_find_reverse (const char *haystack, size_t haystack_len,
+        const char *needle, size_t needle_len)
+{
+  int i;
+  for (i = haystack_len - needle_len; i >= 0; i--)
+    if (!memcmp (needle, &haystack[i], needle_len))
+      return (char *) &haystack[i];
+  return 0;
+}
+
+/* Compares S0 of length S0L to S1 of length S1L.  The shorter string
+   is considered to be padded with spaces to the length of the
+   longer. */
+int
+st_compare_pad (const char *s0, int s0l, const char *s1, int s1l)
+{
+  /* 254 spaces. */
+  static char blanks[254] =
+  "                                                               "
+  "                                                               "
+  "                                                               "
+  "                                                               "
+  "  ";
+
+  int diff = s0l - s1l;
+  int r;
+
+  if (diff == 0)
+    {
+      if (s0l == 0)
+       return 0;
+      return memcmp (s0, s1, s0l);
+    }
+  else if (diff > 0)
+    {
+      /* s0l > s1l */
+      if (s1l)
+       {
+         r = memcmp (s0, s1, s1l);
+         if (r)
+           return r;
+       }
+      return memcmp (&s0[s1l], blanks, diff);
+    }
+  else
+    /* diff<0 */
+    {
+      /* s0l < s1l */
+      if (s0l)
+       {
+         r = memcmp (s0, s1, s0l);
+         if (r)
+           return r;
+       }
+      return memcmp (blanks, &s1[s0l], -diff);
+    }
+}
+
+/* Copies SRC to DEST, truncating to N characters or right-padding
+   with spaces to N characters as necessary.  Does not append a null
+   character.  SRC must be null-terminated. */
+void
+st_bare_pad_copy (char *dest, const char *src, size_t n)
+{
+  size_t len;
+
+  len = strlen (src);
+  if (len >= n)
+    memcpy (dest, src, n);
+  else
+    {
+      memcpy (dest, src, len);
+      memset (&dest[len], ' ', n - len);
+    }
+}
+
+/* Copies SRC to DEST, truncating SRC to N characters or right-padding
+   with spaces to N characters if necessary.  Does not append a null
+   character.  SRC must be LEN characters long but does not need to be
+   null-terminated. */
+void
+st_bare_pad_len_copy (char *dest, const char *src, size_t n, size_t len)
+{
+  if (len >= n)
+    memcpy (dest, src, n);
+  else
+    {
+      memcpy (dest, src, len);
+      memset (&dest[len], ' ', n - len);
+    }
+}
+
+/* Copies SRC to DEST, truncating SRC to N-1 characters or
+   right-padding with spaces to N-1 characters if necessary.  Always
+   appends a null character. */
+void
+st_pad_copy (char *dest, const char *src, size_t n)
+{
+  size_t len;
+
+  len = strlen (src);
+  if (len == n - 1)
+    strcpy (dest, src);
+  else if (len < n - 1)
+    {
+      memcpy (dest, src, len);
+      memset (&dest[len], ' ', n - 1 - len);
+      dest[n - 1] = 0;
+    }
+  else
+    {
+      memcpy (dest, src, n - 1);
+      dest[n - 1] = 0;
+    }
+}
+\f
+/* Initializes ST inside pool POOL (which may be a null pointer) with
+   initial contents S. */
+void
+ds_create (struct pool *pool, struct string *st, const char *s)
+{
+  st->pool = pool;
+  st->length = strlen (s);
+  st->size = 8 + st->length * 2;
+  st->string = pool_malloc (pool, st->size + 1);
+  strcpy (st->string, s);
+}
+
+/* Initializes ST inside POOL (which may be null), making room for at
+   least SIZE characters. */
+void
+ds_init (struct pool *pool, struct string *st, size_t size)
+{
+  st->pool = pool;
+  st->length = 0;
+  if (size > 8)
+    st->size = size;
+  else
+    st->size = 8;
+  st->string = pool_malloc (pool, st->size + 1);
+}
+
+/* Replaces the contents of ST with STRING.  STRING may overlap with
+   ST. */
+void
+ds_replace (struct string *st, const char *string)
+{
+  char *s = st->string;
+  st->string = NULL;
+  ds_create (st->pool, st, string);
+  pool_free (st->pool, s);
+}
+
+/* Frees ST. */
+void
+ds_destroy (struct string *st)
+{
+  pool_free (st->pool, st->string);
+}
+
+/* Truncates ST to zero length. */
+void
+ds_clear (struct string *st)
+{
+  st->length = 0;
+}
+
+/* Ensures that ST can hold at least MIN_SIZE characters plus a null
+   terminator. */
+void
+ds_extend (struct string *st, size_t min_size)
+{
+  if (min_size > st->size)
+    {
+      st->size *= 2;
+      if (st->size < min_size)
+       st->size = min_size * 2;
+      
+      st->string = pool_realloc (st->pool, st->string, st->size + 1);
+    }
+}
+
+/* Shrink ST to the minimum size need to contain its content. */
+void
+ds_shrink (struct string *st)
+{
+  if (st->size != st->length)
+    {
+      st->size = st->length;
+      st->string = pool_realloc (st->pool, st->string, st->size + 1);
+    }
+}
+
+/* Truncates ST to at most LENGTH characters long. */
+void
+ds_truncate (struct string *st, size_t length)
+{
+  if (length >= st->length)
+    return;
+  st->length = length;
+}
+
+/* Returns the length of ST. */
+size_t
+ds_length (const struct string *st)
+{
+  return st->length;
+}
+
+/* Returns the allocation size of ST. */
+size_t
+ds_size (const struct string *st)
+{
+  return st->size;
+}
+
+/* Returns the value of ST as a null-terminated string. */
+char *
+ds_value (const struct string *st)
+{
+  ((char *) st->string)[st->length] = '\0';
+  return st->string;
+}
+
+/* Returns a pointer to the null terminator ST.
+   This might not be an actual null character unless ds_value() has
+   been called since the last modification to ST. */
+char *
+ds_end (const struct string *st)
+{
+  return st->string + st->length;
+}
+
+/* Concatenates S onto ST. */
+void
+ds_concat (struct string *st, const char *s)
+{
+  size_t s_len = strlen (s);
+  ds_extend (st, st->length + s_len);
+  strcpy (st->string + st->length, s);
+  st->length += s_len;
+}
+
+/* Concatenates LEN characters from BUF onto ST. */
+void
+ds_concat_buffer (struct string *st, const char *buf, size_t len)
+{
+  ds_extend (st, st->length + len);
+  memcpy (st->string + st->length, buf, len);
+  st->length += len;
+}
+
+/* Formats FORMAT as a printf string and appends the result to ST. */
+void
+ds_printf (struct string *st, const char *format, ...)
+{
+  /* Fscking glibc silently changed behavior between 2.0 and 2.1.
+     Fsck fsck fsck.  Before, it returned -1 on buffer overflow.  Now,
+     it returns the number of characters (not bytes) that would have
+     been written. */
+  va_list args;
+
+  int avail, needed;
+
+  va_start (args, format);
+  avail = st->size - st->length + 1;
+  needed = vsnprintf (st->string + st->length, avail, format, args);
+  va_end (args);
+
+  if (needed >= avail)
+    {
+      ds_extend (st, st->length + needed);
+      
+      va_start (args, format);
+      vsprintf (st->string + st->length, format, args);
+      va_end (args);
+    }
+  else
+    while (needed == -1)
+      {
+       ds_extend (st, (st->size + 1) * 2);
+       avail = st->size - st->length + 1;
+       
+       va_start (args, format);
+       needed = vsnprintf (st->string + st->length, avail, format, args);
+       va_end (args);
+      }
+
+  st->length += needed;
+}
+
+/* Appends character CH to ST. */
+void
+ds_putchar (struct string *st, int ch)
+{
+  if (st->length == st->size)
+    ds_extend (st, st->length + 1);
+  st->string[st->length++] = ch;
+}
+
+/* Reads a newline-terminated line from STREAM into ST.
+   Newline is the last character of ST on return, unless an I/O error
+   or end of file is encountered after reading some characters.
+   Returns 1 if a line is successfully read, or 0 if no characters at
+   all were read before an I/O error or end of file was
+   encountered. */
+int
+ds_getline (struct string *st, FILE *stream)
+{
+  int c;
+
+  c = getc (stream);
+  if (c == EOF)
+    return 0;
+
+  for (;;)
+    {
+      ds_putchar (st, c);
+      if (c == '\n')
+       return 1;
+
+      c = getc (stream);
+      if (c == EOF)
+       return 1;
+    }
+}
+
+/* Reads a line from STREAM into ST, then preprocesses as follows:
+
+   - Splices lines terminated with `\'.
+
+   - Deletes comments introduced by `#' outside of single or double
+     quotes.
+
+   - Trailing whitespace will be deleted.  
+
+   Increments cust_ln as appropriate.
+
+   Returns nonzero only if a line was successfully read. */
+int
+ds_get_config_line (FILE *stream, struct string *st, struct file_locator *where)
+{
+  /* Read the first line. */
+  ds_clear (st);
+  where->line_number++;
+  if (!ds_getline (st, stream))
+    return 0;
+
+  /* Read additional lines, if any. */
+  for (;;)
+    {
+      /* Remove trailing whitespace. */
+      {
+       char *s = ds_value (st);
+       size_t len = ds_length (st);
+      
+       while (len > 0 && isspace ((unsigned char) s[len - 1]))
+         len--;
+       ds_truncate (st, len);
+      }
+
+      /* Check for trailing \.  Remove if found, bail otherwise. */
+      if (ds_length (st) == 0 || ds_value (st)[ds_length (st) - 1] != '\\')
+       break;
+      ds_truncate (st, ds_length (st) - 1);
+
+      /* Append another line and go around again. */
+      {
+       int success = ds_getline (st, stream);
+       where->line_number++;
+       if (!success)
+         return 1;
+      }
+    }
+
+  /* Find a comment and remove. */
+  {
+    char *cp;
+    int quote = 0;
+      
+    for (cp = ds_value (st); *cp; cp++)
+      if (quote)
+       {
+         if (*cp == quote)
+           quote = 0;
+         else if (*cp == '\\')
+           cp++;
+       }
+      else if (*cp == '\'' || *cp == '"')
+       quote = *cp;
+      else if (*cp == '#')
+       {
+         ds_truncate (st, cp - ds_value (st));
+         break;
+       }
+  }
+
+  return 1;
+}
+\f
+/* Lengthed strings. */
+
+/* Creates a new lengthed string LS in POOL with contents as a copy of
+   S. */
+void
+ls_create (struct pool *pool, struct len_string *ls, const char *s)
+{
+  ls->length = strlen (s);
+  ls->string = pool_alloc (pool, ls->length + 1);
+  memcpy (ls->string, s, ls->length + 1);
+}
+
+/* Creates a new lengthed string LS in POOL with contents as a copy of
+   BUFFER with length LEN. */
+void
+ls_create_buffer (struct pool *pool, struct len_string *ls,
+                 const char *buffer, size_t len)
+{
+  ls->length = len;
+  ls->string = pool_malloc (pool, len + 1);
+  memcpy (ls->string, buffer, len);
+  ls->string[len] = '\0';
+}
+
+/* Sets the fields of LS to the specified values. */
+void
+ls_init (struct len_string *ls, const char *string, size_t length)
+{
+  ls->string = (char *) string;
+  ls->length = length;
+}
+
+/* Copies the fields of SRC to DST. */
+void
+ls_shallow_copy (struct len_string *dst, const struct len_string *src)
+{
+  *dst = *src;
+}
+
+/* Frees the memory in POOL backing LS. */
+void
+ls_destroy (struct pool *pool, struct len_string *ls)
+{
+  pool_free (pool, ls->string);
+}
+
+/* Sets LS to a null pointer value. */
+void
+ls_null (struct len_string *ls)
+{
+  ls->string = NULL;
+}
+
+/* Returns nonzero only if LS has a null pointer value. */
+int
+ls_null_p (const struct len_string *ls)
+{
+  return ls->string == NULL;
+}
+
+/* Returns nonzero only if LS is a null pointer or has length 0. */
+int
+ls_empty_p (const struct len_string *ls)
+{
+  return ls->string == NULL || ls->length == 0;
+}
+
+/* Returns the length of LS, which must not be null. */
+size_t
+ls_length (const struct len_string *ls)
+{
+  return ls->length;
+}
+
+/* Returns a pointer to the character string in LS. */
+char *
+ls_value (const struct len_string *ls)
+{
+  return (char *) ls->string;
+}
+
+/* Returns a pointer to the null terminator of the character string in
+   LS. */
+char *
+ls_end (const struct len_string *ls)
+{
+  return (char *) (ls->string + ls->length);
+}
diff --git a/src/str.h b/src/str.h
new file mode 100644 (file)
index 0000000..dedcc10
--- /dev/null
+++ b/src/str.h
@@ -0,0 +1,215 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !str_h
+#define str_h 1
+
+/* Headers and miscellaneous. */
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#if STDC_HEADERS
+  #include <string.h>
+#else
+  #ifndef HAVE_STRCHR 
+    #define strchr index
+    #define strrchr rindex
+  #endif
+
+  char *strchr (), *strrchr ();
+#endif
+
+#if !HAVE_STRTOK_R
+  char *strtok_r (char *, const char *, char **);
+#endif
+
+#if !HAVE_STPCPY && !__linux__
+  char *stpcpy (char *dest, const char *src);
+#endif
+
+#if !HAVE_STRCASECMP
+  int strcasecmp (const char *s1, const char *s2);
+#endif
+
+#if !HAVE_STRNCASECMP
+  int strncasecmp (const char *s1, const char *s2, size_t n);
+#endif
+
+#if !HAVE_MEMMEM
+  void *memmem (const void *haystack, size_t haystack_len,
+               const void *needle, size_t needle_len);
+#endif
+\f
+/* sprintf() wrapper functions for convenience. */
+
+/* spprintf() calls sprintf() and returns the address of the null
+   terminator in the resulting string.  It should be portable the way
+   it's been implemented. */
+#if __GNUC__
+  #if HAVE_GOOD_SPRINTF
+    #define spprintf(BUF, FORMAT, ARGS...)                     \
+           ((BUF) + sprintf ((BUF), (FORMAT) , ## ARGS))
+  #else
+    #define spprintf(BUF, FORMAT, ARGS...)             \
+           ({ sprintf ((BUF), (FORMAT) , ## ARGS);     \
+               strchr ((BUF), 0); })
+  #endif
+#else /* Not GNU C. */
+  char *spprintf (char *buf, const char *format, ...);
+#endif /* Not GNU C. */
+
+/* nsprintf() calls sprintf() and returns the strlen() of the
+   resulting string.  It should be portable the way it's been
+   implemented. */
+#if __GNUC__
+  #if HAVE_GOOD_SPRINTF
+    #define nsprintf(BUF, FORMAT, ARGS...)             \
+           (sprintf ((BUF), (FORMAT) , ## ARGS))
+    #define nvsprintf(BUF, FORMAT, ARGS)               \
+           (vsprintf ((BUF), (FORMAT), (ARGS)))
+  #else /* Not good sprintf(). */
+    #define nsprintf(BUF, FORMAT, ARGS...)             \
+           ({                                          \
+             char *pbuf = BUF;                         \
+             sprintf ((pbuf), (FORMAT) , ## ARGS);     \
+             strlen (pbuf);                            \
+           })
+    #define nvsprintf(BUF, FORMAT, ARGS)               \
+           ({                                          \
+             char *pbuf = BUF;                         \
+             vsprintf ((pbuf), (FORMAT), (ARGS));      \
+             strlen (pbuf);                            \
+           })
+  #endif /* Not good sprintf(). */
+#else /* Not GNU C. */
+  #if HAVE_GOOD_SPRINTF
+    #define nsprintf sprintf
+    #define nvsprintf vsprintf
+  #else /* Not good sprintf(). */
+    int nsprintf (char *buf, const char *format, ...);
+    int nvsprintf (char *buf, const char *format, va_list args);
+  #endif /* Not good sprintf(). */
+#endif /* Not GNU C. */
+
+#if !HAVE_GETLINE
+long getline (char **lineptr, size_t *n, FILE *stream);
+#endif
+
+#if !HAVE_GETDELIM
+long getdelim (char **lineptr, size_t * n, int delimiter, FILE * stream);
+#endif
+\f
+/* Miscellaneous. */
+
+void mm_reverse (void *, size_t);
+char *mm_find_reverse (const char *, size_t, const char *, size_t);
+
+int st_compare_pad (const char *, int, const char *, int);
+char *st_spaces (int);
+void st_bare_pad_copy (char *dest, const char *src, size_t n);
+void st_bare_pad_len_copy (char *dest, const char *src, size_t n, size_t len);
+void st_pad_copy (char *dest, const char *src, size_t n);
+\f
+/* Lengthed strings. */
+struct len_string 
+  {
+    char *string;
+    size_t length;
+  };
+
+struct pool;
+void ls_create (struct pool *, struct len_string *, const char *);
+void ls_create_buffer (struct pool *, struct len_string *,
+                      const char *, size_t len);
+void ls_init (struct len_string *, const char *, size_t);
+void ls_shallow_copy (struct len_string *, const struct len_string *);
+void ls_destroy (struct pool *, struct len_string *);
+
+void ls_null (struct len_string *);
+int ls_null_p (const struct len_string *);
+int ls_empty_p (const struct len_string *);
+
+size_t ls_length (const struct len_string *);
+char *ls_value (const struct len_string *);
+char *ls_end (const struct len_string *);
+\f
+/* Dynamic strings. */
+
+struct string
+  {
+    struct pool *pool;
+    size_t length;
+    size_t size;
+    char *string;
+  };
+
+void ds_create (struct pool *, struct string *, const char *);
+void ds_init (struct pool *, struct string *, size_t size);
+void ds_replace (struct string *, const char *);
+void ds_destroy (struct string *);
+void ds_clear (struct string *);
+void ds_extend (struct string *, size_t min_size);
+void ds_shrink (struct string *);
+void ds_truncate (struct string *, size_t length);
+
+size_t ds_length (const struct string *);
+char *ds_value (const struct string *);
+char *ds_end (const struct string *);
+size_t ds_size (const struct string *);
+
+struct file_locator;
+int ds_getline (struct string *st, FILE *stream);
+int ds_get_config_line (FILE *, struct string *, struct file_locator *);
+void ds_putchar (struct string *, int ch);
+void ds_concat (struct string *, const char *);
+void ds_concat_buffer (struct string *, const char *buf, size_t len);
+void ds_printf (struct string *, const char *, ...)
+     __attribute__ ((format (printf, 2, 3)));
+
+#if __GNUC__ > 1
+extern inline void
+ds_putchar (struct string *st, int ch)
+{
+  if (st->length == st->size)
+    ds_extend (st, st->length + 1);
+  st->string[st->length++] = ch;
+}
+
+extern inline size_t
+ds_length (const struct string *st)
+{
+  return st->length;
+}
+
+extern inline char *
+ds_value (const struct string *st)
+{
+  ((char *) st->string)[st->length] = '\0';
+  return st->string;
+}
+
+extern inline char *
+ds_end (const struct string *st)
+{
+  return st->string + st->length;
+}
+#endif
+
+#endif /* str_h */
diff --git a/src/sysfile-info.c b/src/sysfile-info.c
new file mode 100644 (file)
index 0000000..dda4f54
--- /dev/null
@@ -0,0 +1,620 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "avl.h"
+#include "command.h"
+#include "error.h"
+#include "file-handle.h"
+#include "lexer.h"
+#include "misc.h"
+#include "output.h"
+#include "sfm.h"
+#include "som.h"
+#include "tab.h"
+#include "var.h"
+#include "vector.h"
+
+/* Constants for DISPLAY utility. */
+enum
+  {
+    AS_NAMES = 0,
+    AS_INDEX,
+    AS_VARIABLES,
+    AS_LABELS,
+    AS_DICTIONARY,
+    AS_SCRATCH,
+    AS_VECTOR
+  };
+
+int describe_variable (struct variable *v, struct tab_table *t, int r, int as);
+     
+/* Sets the widths of all the columns and heights of all the rows in
+   table T for driver D. */
+static void
+sysfile_info_dim (struct tab_table *t, struct outp_driver *d)
+{
+  static const int max[] = {20, 5, 35, 3, 0};
+  const int *p;
+  int i;
+
+  for (p = max; *p; p++)
+    t->w[p - max] = min (tab_natural_width (t, d, p - max),
+                        *p * d->prop_em_width);
+  for (i = 0; i < t->nr; i++)
+    t->h[i] = tab_natural_height (t, d, i);
+}
+
+/* SYSFILE INFO utility. */
+int
+cmd_sysfile_info (void)
+{
+  struct file_handle *h;
+  struct dictionary *d;
+  struct tab_table *t;
+  struct sfm_read_info inf;
+  int r, nr;
+  int i;
+
+  lex_match_id ("SYSFILE");
+  lex_match_id ("INFO");
+
+  lex_match_id ("FILE");
+  lex_match ('=');
+
+  h = fh_parse_file_handle ();
+  if (!h)
+    return CMD_FAILURE;
+
+  d = sfm_read_dictionary (h, &inf);
+  fh_close_handle (h);
+  if (!d)
+    return CMD_FAILURE;
+
+  t = tab_create (2, 9, 0);
+  tab_vline (t, TAL_1 | TAL_SPACING, 1, 0, 8);
+  tab_text (t, 0, 0, TAB_LEFT, _("File:"));
+  tab_text (t, 1, 0, TAB_LEFT, fh_handle_filename (h));
+  tab_text (t, 0, 1, TAB_LEFT, _("Label:"));
+  tab_text (t, 1, 1, TAB_LEFT,
+               d->label ? d->label : _("No label."));
+  tab_text (t, 0, 2, TAB_LEFT, _("Created:"));
+  tab_text (t, 1, 2, TAB_LEFT | TAT_PRINTF, "%s %s by %s",
+               inf.creation_date, inf.creation_time, inf.product);
+  tab_text (t, 0, 3, TAB_LEFT, _("Endian:"));
+  tab_text (t, 1, 3, TAB_LEFT,
+               (inf.endianness == BIG ? _("Big.")
+                : (inf.endianness == LITTLE ? _("Little.")
+                   : (assert (0), _("<internal error>")))));
+  tab_text (t, 0, 4, TAB_LEFT, _("Variables:"));
+  tab_text (t, 1, 4, TAB_LEFT | TAT_PRINTF, "%d",
+               d->nvar);
+  tab_text (t, 0, 5, TAB_LEFT, _("Cases:"));
+  tab_text (t, 1, 5, TAB_LEFT | TAT_PRINTF,
+               inf.ncases == -1 ? _("Unknown") : "%d", inf.ncases);
+  tab_text (t, 0, 6, TAB_LEFT, _("Type:"));
+  tab_text (t, 1, 6, TAB_LEFT, _("System File."));
+  tab_text (t, 0, 7, TAB_LEFT, _("Weight:"));
+  tab_text (t, 1, 7, TAB_LEFT,
+               d->weight_var[0] ? d->weight_var : _("Not weighted."));
+  tab_text (t, 0, 8, TAB_LEFT, _("Mode:"));
+  tab_text (t, 1, 8, TAB_LEFT | TAT_PRINTF,
+               _("Compression %s."), inf.compressed ? _("on") : _("off"));
+  tab_dim (t, tab_natural_dimensions);
+  tab_submit (t);
+
+  nr = 1 + 2 * d->nvar;
+  t = tab_create (4, nr, 1);
+  tab_dim (t, sysfile_info_dim);
+  tab_headers (t, 0, 0, 1, 0);
+  tab_text (t, 0, 0, TAB_LEFT | TAT_TITLE, _("Variable"));
+  tab_joint_text (t, 1, 0, 2, 0, TAB_LEFT | TAT_TITLE, _("Description"));
+  tab_text (t, 3, 0, TAB_LEFT | TAT_TITLE, _("Position"));
+  tab_hline (t, TAL_2, 0, 3, 1);
+  for (r = 1, i = 0; i < d->nvar; i++)
+    {
+      int nvl = d->var[i]->val_lab ? avl_count (d->var[i]->val_lab) : 0;
+      
+      if (r + 10 + nvl > nr)
+       {
+         nr = max (nr * d->nvar / (i + 1), nr);
+         nr += 10 + nvl;
+         tab_realloc (t, 4, nr);
+       }
+
+      r = describe_variable (d->var[i], t, r, AS_DICTIONARY);
+    }
+  tab_box (t, TAL_1, TAL_1, -1, -1, 0, 0, 3, r);
+  tab_vline (t, TAL_1, 0, 0, r);
+  tab_vline (t, TAL_1, 1, 0, r);
+  tab_vline (t, TAL_1, 3, 0, r);
+  tab_resize (t, -1, r);
+  tab_flags (t, SOMF_NO_TITLE);
+  tab_submit (t);
+
+  free_dictionary (d);
+  
+  return lex_end_of_command ();
+}
+\f
+/* DISPLAY utility. */
+
+static void display_macros (void);
+static void display_documents (void);
+static void display_variables (struct variable **, int, int);
+static void display_vectors (int sorted);
+
+static int cmp_var_by_name (const void *, const void *);
+
+int
+cmd_display (void)
+{
+  /* Whether to sort the list of variables alphabetically. */
+  int sorted;
+
+  /* Variables to display. */
+  int n;
+  struct variable **vl;
+
+  lex_match_id ("DISPLAY");
+
+  if (lex_match_id ("MACROS"))
+    display_macros ();
+  else if (lex_match_id ("DOCUMENTS"))
+    display_documents ();
+  else if (lex_match_id ("FILE"))
+    {
+      som_blank_line ();
+      if (!lex_force_match_id ("LABEL"))
+       return CMD_FAILURE;
+      if (default_dict.label == NULL)
+       tab_output_text (TAB_LEFT,
+                        _("The active file does not have a file label."));
+      else
+       {
+         tab_output_text (TAB_LEFT | TAT_TITLE, _("File label:"));
+         tab_output_text (TAB_LEFT | TAT_FIX, default_dict.label);
+       }
+    }
+  else
+    {
+      static const char *sbc[] =
+       {"NAMES", "INDEX", "VARIABLES", "LABELS",
+        "DICTIONARY", "SCRATCH", "VECTORS", NULL};
+      const char **cp;
+      int as;
+
+      sorted = lex_match_id ("SORTED");
+
+      for (cp = sbc; *cp; cp++)
+       if (token == T_ID && lex_id_match (*cp, tokid))
+         {
+           lex_get ();
+           break;
+         }
+      as = cp - sbc;
+
+      if (*cp == NULL)
+       as = AS_NAMES;
+
+      if (as == AS_VECTOR)
+       {
+         display_vectors (sorted);
+         return CMD_SUCCESS;
+       }
+
+      lex_match ('/');
+      lex_match_id ("VARIABLES");
+      lex_match ('=');
+
+      if (token != '.')
+       {
+         if (!parse_variables (NULL, &vl, &n, PV_NONE))
+           {
+             free (vl);
+             return CMD_FAILURE;
+           }
+         as = AS_DICTIONARY;
+       }
+      else
+       fill_all_vars (&vl, &n, FV_NONE);
+
+      if (as == AS_SCRATCH)
+       {
+         int i, m;
+         for (i = 0, m = n; i < n; i++)
+           if (vl[i]->name[0] != '#')
+             {
+               vl[i] = NULL;
+               m--;
+             }
+         as = AS_NAMES;
+         n = m;
+       }
+
+      if (n == 0)
+       {
+         msg (SW, _("No variables to display."));
+         return CMD_FAILURE;
+       }
+
+      if (sorted)
+       qsort (vl, n, sizeof *vl, cmp_var_by_name);
+
+      display_variables (vl, n, as);
+
+      free (vl);
+    }
+
+  return lex_end_of_command ();
+}
+
+static int
+cmp_var_by_name (const void *a, const void *b)
+{
+  return strcmp ((*((struct variable **) a))->name, (*((struct variable **) b))->name);
+}
+
+static void
+display_macros (void)
+{
+  som_blank_line ();
+  tab_output_text (TAB_LEFT, _("Macros not supported."));
+}
+
+static void
+display_documents (void)
+{
+  som_blank_line ();
+  if (default_dict.n_documents == 0)
+    tab_output_text (TAB_LEFT, _("The active file dictionary does not "
+                    "contain any documents."));
+  else
+    {
+      char buf[81];
+      int i;
+
+      tab_output_text (TAB_LEFT | TAT_TITLE,
+                      _("Documents in the active file:"));
+      som_blank_line ();
+      buf[80] = 0;
+      for (i = 0; i < default_dict.n_documents; i++)
+       {
+         int len = 79;
+
+         memcpy (buf, &default_dict.documents[i * 80], 80);
+         while ((isspace ((unsigned char) buf[len]) || buf[len] == 0)
+                && len > 0)
+           len--;
+         buf[len + 1] = 0;
+         tab_output_text (TAB_LEFT | TAT_FIX | TAT_NOWRAP, buf);
+       }
+    }
+}
+
+static int _as;
+
+/* Sets the widths of all the columns and heights of all the rows in
+   table T for driver D. */
+static void
+variables_dim (struct tab_table *t, struct outp_driver *d)
+{
+  int pc;
+  int i;
+  
+  t->w[0] = tab_natural_width (t, d, 0);
+  if (_as == AS_DICTIONARY || _as == AS_VARIABLES || _as == AS_LABELS)
+    {
+      t->w[1] = max (tab_natural_width (t, d, 1), d->prop_em_width * 5);
+      t->w[2] = max (tab_natural_width (t, d, 2), d->prop_em_width * 35);
+      pc = 3;
+    }
+  else pc = 1;
+  if (_as != AS_NAMES)
+    t->w[pc] = tab_natural_width (t, d, pc);
+
+  for (i = 0; i < t->nr; i++)
+    t->h[i] = tab_natural_height (t, d, i);
+}
+  
+static void
+display_variables (struct variable **vl, int n, int as)
+{
+  struct variable **vp = vl;           /* Variable pointer. */
+  struct tab_table *t;
+  int nc;                      /* Number of columns. */
+  int nr;                      /* Number of rows. */
+  int pc;                      /* `Position column' */
+  int r;                       /* Current row. */
+  int i;
+
+  _as = as;
+  switch (as)
+    {
+    case AS_INDEX:
+      nc = 2;
+      break;
+    case AS_NAMES:
+      nc = 1;
+      break;
+    default:
+      nc = 4;
+      break;
+    }
+
+  t = tab_create (nc, n + 5, 1);
+  tab_headers (t, 0, 0, 1, 0);
+  nr = n + 5;
+  tab_hline (t, TAL_2, 0, nc - 1, 1);
+  tab_text (t, 0, 0, TAB_LEFT | TAT_TITLE, _("Variable"));
+  if (as != AS_NAMES)
+    {
+      pc = (as == AS_INDEX ? 1 : 3);
+      tab_text (t, pc, 0, TAB_LEFT | TAT_TITLE, _("Position"));
+    }
+  if (as == AS_DICTIONARY || as == AS_VARIABLES)
+    tab_joint_text (t, 1, 0, 2, 0, TAB_LEFT | TAT_TITLE, _("Description"));
+  else if (as == AS_LABELS)
+    tab_joint_text (t, 1, 0, 2, 0, TAB_LEFT | TAT_TITLE, _("Label"));
+  tab_dim (t, variables_dim);
+    
+  for (i = r = 1; i <= n; i++)
+    {
+      struct variable *v;
+
+      while (*vp == NULL)
+       vp++;
+      v = *vp++;
+
+      if (as == AS_DICTIONARY || as == AS_VARIABLES)
+       {
+         int nvl = v->val_lab ? avl_count (v->val_lab) : 0;
+      
+         if (r + 10 + nvl > nr)
+           {
+             nr = max (nr * n / (i + 1), nr);
+             nr += 10 + nvl;
+             tab_realloc (t, nc, nr);
+           }
+
+         r = describe_variable (v, t, r, as);
+       } else {
+         tab_text (t, 0, r, TAB_LEFT, v->name);
+         if (as == AS_LABELS)
+           tab_joint_text (t, 1, r, 2, r, TAB_LEFT,
+                           v->label == NULL ? "(no label)" : v->label);
+         if (as != AS_NAMES)
+           {
+             tab_text (t, pc, r, TAT_PRINTF, "%d", v->index + 1);
+             tab_hline (t, TAL_1, 0, nc - 1, r);
+           }
+         r++;
+       }
+    }
+  tab_hline (t, as == AS_NAMES ? TAL_1 : TAL_2, 0, nc - 1, 1);
+  if (as != AS_NAMES)
+    {
+      tab_box (t, TAL_1, TAL_1, -1, -1, 0, 0, nc - 1, r - 1);
+      tab_vline (t, TAL_1, 1, 0, r - 1);
+    }
+  else
+    tab_flags (t, SOMF_NO_TITLE);
+  if (as == AS_DICTIONARY || as == AS_VARIABLES || as == AS_LABELS)
+    tab_vline (t, TAL_1, 3, 0, r - 1);
+  tab_resize (t, -1, r);
+  tab_columns (t, TAB_COL_DOWN, 1);
+  tab_submit (t);
+}
+\f
+/* Puts a description of variable V into table T starting at row R.
+   The variable will be described in the format AS.  Returns the next
+   row available for use in the table. */
+int 
+describe_variable (struct variable *v, struct tab_table *t, int r, int as)
+{
+  /* Put the name, var label, and position into the first row. */
+  tab_text (t, 0, r, TAB_LEFT, v->name);
+  tab_text (t, 3, r, TAT_PRINTF, "%d", v->index + 1);
+
+  if (as == AS_DICTIONARY && v->label)
+    {
+      tab_joint_text (t, 1, r, 2, r, TAB_LEFT, v->label);
+      r++;
+    }
+  
+  /* Print/write format, or print and write formats. */
+  if (v->print.type == v->write.type
+      && v->print.w == v->write.w
+      && v->print.d == v->write.d)
+    {
+      tab_joint_text (t, 1, r, 2, r, TAB_LEFT | TAT_PRINTF, _("Format: %s"),
+                     fmt_to_string (&v->print));
+      r++;
+    }
+  else
+    {
+      tab_joint_text (t, 1, r, 2, r, TAB_LEFT | TAT_PRINTF,
+                     _("Print Format: %s"), fmt_to_string (&v->print));
+      r++;
+      tab_joint_text (t, 1, r, 2, r, TAB_LEFT | TAT_PRINTF,
+                     _("Write Format: %s"), fmt_to_string (&v->write));
+      r++;
+    }
+
+  /* Missing values if any. */
+  if (v->miss_type != MISSING_NONE)
+    {
+      char buf[80];
+      char *cp = stpcpy (buf, _("Missing Values: "));
+
+      if (v->type == NUMERIC)
+       switch (v->miss_type)
+         {
+         case MISSING_1:
+           sprintf (cp, "%g", v->missing[0].f);
+           break;
+         case MISSING_2:
+           sprintf (cp, "%g; %g", v->missing[0].f, v->missing[1].f);
+           break;
+         case MISSING_3:
+           sprintf (cp, "%g; %g; %g", v->missing[0].f,
+                    v->missing[1].f, v->missing[2].f);
+           break;
+         case MISSING_RANGE:
+           sprintf (cp, "%g THRU %g", v->missing[0].f, v->missing[1].f);
+           break;
+         case MISSING_LOW:
+           sprintf (cp, "LOWEST THRU %g", v->missing[0].f);
+           break;
+         case MISSING_HIGH:
+           sprintf (cp, "%g THRU HIGHEST", v->missing[0].f);
+           break;
+         case MISSING_RANGE_1:
+           sprintf (cp, "%g THRU %g; %g",
+                    v->missing[0].f, v->missing[1].f, v->missing[2].f);
+           break;
+         case MISSING_LOW_1:
+           sprintf (cp, "LOWEST THRU %g; %g",
+                    v->missing[0].f, v->missing[1].f);
+           break;
+         case MISSING_HIGH_1:
+           sprintf (cp, "%g THRU HIGHEST; %g",
+                    v->missing[0].f, v->missing[1].f);
+           break;
+         default:
+           assert (0);
+         }
+      else
+       {
+         int i;
+
+         for (i = 0; i < v->miss_type; i++)
+           {
+             if (i != 0)
+               cp = stpcpy (cp, "; ");
+             *cp++ = '"';
+             memcpy (cp, v->missing[i].s, v->width);
+             cp += v->width;
+             *cp++ = '"';
+           }
+         *cp = 0;
+       }
+
+      tab_joint_text (t, 1, r, 2, r, TAB_LEFT, buf);
+      r++;
+    }
+
+  /* Value labels. */
+  if (as == AS_DICTIONARY && v->val_lab)
+    {
+      avl_traverser trav;
+      struct value_label *vl;
+      int nvl = avl_count (v->val_lab);
+      int orig_r = r;
+      int i;
+
+#if 0
+      tab_text (t, 1, r, TAB_LEFT, _("Value"));
+      tab_text (t, 2, r, TAB_LEFT, _("Label"));
+      r++;
+#endif
+
+      tab_hline (t, TAL_1, 1, 2, r);
+      avl_traverser_init (trav);
+      for (i = 1, vl = avl_traverse (v->val_lab, &trav); vl;
+          i++, vl = avl_traverse (v->val_lab, &trav))
+       {
+         char buf[128];
+
+         if (v->type == ALPHA)
+           {
+             memcpy (buf, vl->v.s, v->width);
+             buf[v->width] = 0;
+           }
+         else
+           sprintf (buf, "%g", vl->v.f);
+
+         tab_text (t, 1, r, TAB_NONE, buf);
+         tab_text (t, 2, r, TAB_LEFT, vl->s);
+         r++;
+
+         if (i == nvl) 
+           break;
+       }
+
+      for (;;)
+       {
+         if (vl == NULL)
+           break;
+         vl = avl_traverse (v->val_lab, &trav);
+       }
+
+      tab_vline (t, TAL_1, 2, orig_r, r - 1);
+    }
+
+  /* Draw a line below the last row of information on this variable. */
+  tab_hline (t, TAL_1, 0, 3, r);
+
+  return r;
+}
+
+static int
+compare_vectors_by_name (const void *a, const void *b)
+{
+  return strcmp ((*((struct vector **) a))->name, (*((struct vector **) b))->name);
+}
+
+/* Display a list of vectors.  If SORTED is nonzero then they are
+   sorted alphabetically. */
+static void
+display_vectors (int sorted)
+{
+  struct vector **vl;
+  int i;
+  struct tab_table *t;
+
+  if (nvec == 0)
+    {
+      msg (SW, _("No vectors defined."));
+      return;
+    }
+
+  vl = xmalloc (sizeof *vl * nvec);
+  for (i = 0; i < nvec; i++)
+    vl[i] = &vec[i];
+  if (sorted)
+    qsort (vl, nvec, sizeof *vl, compare_vectors_by_name);
+
+  t = tab_create (1, nvec + 1, 0);
+  tab_headers (t, 0, 0, 1, 0);
+  tab_columns (t, TAB_COL_DOWN, 1);
+  tab_dim (t, tab_natural_dimensions);
+  tab_hline (t, TAL_1, 0, 0, 1);
+  tab_text (t, 0, 0, TAT_TITLE | TAB_LEFT, _("Vector"));
+  tab_flags (t, SOMF_NO_TITLE);
+  for (i = 0; i < nvec; i++)
+    tab_text (t, 0, i + 1, TAB_LEFT, vl[i]->name);
+  tab_submit (t);
+
+  free (vl);
+}
diff --git a/src/t-test.q b/src/t-test.q
new file mode 100644 (file)
index 0000000..6b65d0e
--- /dev/null
@@ -0,0 +1,1087 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "alloc.h"
+#include "str.h"
+#include "dcdflib/cdflib.h"
+#include "command.h"
+#include "lexer.h"
+#include "error.h"
+#include "magic.h"
+#include "var.h"
+#include "vfm.h"
+
+/* (specification)
+   "T-TEST" (tts_):
+     groups=custom;
+     variables=varlist("PV_NO_SCRATCH | PV_NUMERIC");
+     *+pairs=custom;
+     +missing=miss:!analysis/listwise,
+             incl:include/!exclude;
+     +format=fmt:!labels/nolabels;
+     +criteria=:ci(d:criteria,"%s > 0. && %s < 1.").
+*/
+/* (declarations) */
+/* (functions) */
+
+#undef DEBUGGING
+#define DEBUGGING 1
+#include "debug-print.h"
+
+/* Command parsing information. */
+static struct cmd_t_test cmd;
+
+/* Variable for the GROUPS subcommand, if given. */
+static struct variable *groups;
+
+/* GROUPS: Number of values specified by the user; the values
+   specified if any. */
+static int n_groups_values;
+static union value groups_values[2];
+
+/* PAIRED: Number of pairs; each pair. */
+static int n_pairs;
+static struct variable *(*pairs)[2];
+
+/* Routines to scan data and perform t-tests */
+static void precalc (void);
+static void postcalc (void);
+static void g_postcalc (void);
+static void t_pairs (void);
+static void t_groups (void);
+static int groups_calc (struct ccase *);
+static int pairs_calc (struct ccase *);
+static int z_calc (struct ccase *);
+
+struct value_list
+  {
+    double sum;
+    double ss;
+    double n;
+    struct value_list *next;
+  };
+
+/* general workhorses - should  move these to a separate library... */
+double variance (double n, double ss, double sum);
+
+double covariance (double x_sum, double x_n,
+                  double y_sum, double y_n, double ss);
+
+double pooled_variance (double n_1, double var_1,
+                       double n_2, double var_2);
+
+double oneway (double *f, double *p, struct value_list *list);
+
+double pearson_r (double c_xy, double c_xx, double c_yy);
+
+double f_sig (double f, double dfn, double dfd);
+double t_crt (double df, double q);
+double t_sig (double t, double df);
+
+/* massive function simply to remove any responsibility for output
+   from the function which does the actual t-test calculations */
+void print_t_groups (struct variable * grps, union value * g1, union value * g2,
+                    double n1, double n2, double mean1, double mean2,
+                    double sd1, double sd2, double se1, double se2,
+                    double diff, double l_f, double l_p,
+                    double p_t, double p_sig, double p_df, double p_sed,
+                    double p_l, double p_h,
+                    double s_t, double s_sig, double s_df, double s_sed,
+                    double s_l, double s_h);
+
+/* Global variables to communicate between calc() and postcalc()
+   should move to a structure in the p union of variable... */
+static double v1_n, v1_ss, v1_sum, v1_se, v1_var, v1_mean;
+static double v2_n, v2_ss, v2_sum, v2_se, v2_var, v2_mean;
+static double v1_z_sum, v1_z_ss;
+static double v2_z_sum, v2_z_ss;
+static double diff, se_diff, sp, xy_sum, xy_diff, xy_ss;
+static int cur_var;
+
+/* some defines for CDFlib */
+#define FIND_P 1
+#define FIND_CRITICAL_VALUE 2
+#define ERROR_SIG -1
+
+#ifdef DEBUGGING
+static void debug_print (void);
+#endif
+
+/* Parses and executes the T-TEST procedure. */
+int
+cmd_t_test (void)
+{
+  struct cmd_t_test cmd;
+  
+  if (!lex_force_match_id ("T"))
+    return CMD_FAILURE;
+  lex_match ('-');
+  lex_match_id ("TEST");
+
+  if (!parse_t_test (&cmd))
+    return CMD_FAILURE;
+
+#if DEBUGGING
+  debug_print ();
+#endif
+
+  if (n_pairs > 0)
+    procedure (precalc, pairs_calc, postcalc);
+  else
+    /* probably groups then... */
+    {
+      printf ("\n\n  t-tests for independent samples of %s %s\n",
+             groups->name, groups->label);
+
+      for (cur_var = 0; cur_var < cmd.n_variables; cur_var++)
+       {
+         v1_n = v1_ss = v1_sum = v1_se = v1_var = v1_mean = 0.0;
+         v2_n = v2_ss = v2_sum = v2_se = v2_var = v2_mean = 0.0;
+         v1_z_sum = v1_z_ss = v2_z_sum = v2_z_ss = 0.0;
+         diff = se_diff = sp = xy_diff = xy_ss = xy_sum = 0.0;
+
+         procedure (precalc, groups_calc, g_postcalc);
+         procedure (precalc, z_calc, postcalc);
+       }
+    }
+
+  return CMD_SUCCESS;
+}
+
+void
+precalc (void)
+{
+  return;                      /* rilly void... */
+}
+
+int
+groups_calc (struct ccase * c)
+{
+  int bad_weight;
+  double group, w;
+  struct variable *v = cmd.v_variables[cur_var];
+  double X = c->data[v->fv].f;
+
+  /* Get the weight for this case. */
+  if (default_dict.weight_index == -1)
+    w = 1.0;
+  else
+    {
+      w = c->data[default_dict.weight_index].f;
+      if (w <= 0.0 || w == SYSMIS)
+       {
+         w = 0.0;
+         bad_weight = 1;
+         printf ("Bad weight\n");
+       }
+    }
+
+  if (X == SYSMIS || X == 0.0) /* FIXME: should be USER_MISSING? */
+    {
+      /* printf("Missing value\n"); */
+      return 1;
+    }
+  else
+    {
+      X = X * w;
+      group = c->data[groups->fv].f;
+
+      if (group == groups_values[0].f)
+       {
+         v1_sum += X;
+         v1_ss += X * X;
+         v1_n += w;
+       }
+      else if (group == groups_values[1].f)
+       {
+         v2_sum += X;
+         v2_ss += X * X;
+         v2_n += w;
+       }
+    }
+
+  return 1;
+}
+
+void
+g_postcalc (void)
+{
+  v1_mean = v1_sum / v1_n;
+  v2_mean = v2_sum / v2_n;
+  return;
+}
+
+int                            /* this pass generates the z-zcores */
+z_calc (struct ccase * c)
+{
+  int bad_weight;
+  double group, z, w;
+  struct variable *v = cmd.v_variables[cur_var];
+  double X = c->data[v->fv].f;
+
+  z = 0.0;
+
+  /* Get the weight for this case. */
+  if (default_dict.weight_index == -1)
+    w = 1.0;
+  else
+    {
+      w = c->data[default_dict.weight_index].f;
+      if (w <= 0.0 || w == SYSMIS)
+       {
+         w = 0.0;
+         bad_weight = 1;
+       }
+    }
+
+  if (X == SYSMIS || X == 0.0) /* FIXME: how to specify user missing? */
+    {
+      return 1;
+    }
+  else
+    {
+      group = c->data[groups->fv].f;
+      X = w * X;
+
+      if (group == groups_values[0].f)
+       {
+         z = fabs (X - v1_mean);
+         v1_z_sum += z;
+         v1_z_ss += pow (z, 2);
+       }
+      else if (group == groups_values[1].f)
+       {
+         z = fabs (X - v2_mean);
+         v2_z_ss += pow (z, 2);
+         v2_z_sum += z;
+       }
+    }
+
+  return 1;
+}
+
+
+int
+pairs_calc (struct ccase * c)
+{
+  int i;
+  struct variable *v1, *v2;
+  double X, Y;
+
+  for (i = 0; i < n_pairs; i++)
+    {
+
+      v1 = pairs[i][0];
+      v2 = pairs[i][1];
+      X = c->data[v1->fv].f;
+      Y = c->data[v2->fv].f;
+
+      if (X == SYSMIS || Y == SYSMIS)
+       {
+         printf ("Missing value\n");
+       }
+      else
+       {
+         xy_sum += X * Y;
+         xy_diff += (X - Y);
+         xy_ss += pow ((X - Y), 2);
+         v1_sum += X;
+         v2_sum += Y;
+         v1_n++;
+         v2_n++;
+         v1_ss += (X * X);
+         v2_ss += (Y * Y);
+       }
+    }
+
+  return 1;
+}
+
+void
+postcalc (void)
+{
+  /* Calculate basic statistics */
+  v1_var = variance (v1_n, v1_ss, v1_sum);     /* variances */
+  v2_var = variance (v2_n, v2_ss, v2_sum);
+  v1_se = sqrt (v1_var / v1_n);        /* standard errors */
+  v2_se = sqrt (v2_var / v2_n);
+  diff = v1_mean - v2_mean;
+
+  if (n_pairs > 0)
+    {
+      t_pairs ();
+    }
+  else
+    {
+      t_groups ();
+    }
+
+  return;
+}
+
+void
+t_groups (void)
+{
+  double df_pooled, t_pooled, t_sep, p_pooled, p_sep;
+  double crt_t_p, crt_t_s, tmp, v1_z, v2_z, f_levene, p_levene;
+  double df_sep, se_diff_s, se_diff_p;
+  struct value_list *val_1, *val_2;
+
+  /* Levene's test */
+  val_1 = malloc (sizeof (struct value_list));
+  val_1->sum = v1_z_sum;
+  val_1->ss = v1_z_ss;
+  val_1->n = v1_n;
+  val_2 = malloc (sizeof (struct value_list));
+  val_2->sum = v2_z_sum;
+  val_2->ss = v2_z_ss;
+  val_2->n = v2_n;
+
+  val_1->next = val_2;
+  val_2->next = NULL;
+
+  f_levene = oneway (&f_levene, &p_levene, val_1);
+
+  /* T test results for pooled variances */
+  se_diff_p = sqrt (pooled_variance (v1_n, v1_var, v2_n, v2_var));
+  df_pooled = v1_n + v2_n - 2.0;
+  t_pooled = diff / se_diff_p;
+  p_pooled = t_sig (t_pooled, df_pooled);
+  crt_t_p = t_crt (df_pooled, 0.025);
+
+  if ((2.0 * p_pooled) >= 1.0)
+    p_pooled = 1.0 - p_pooled;
+
+  /* oh god, the separate variance calculations... */
+  t_sep = diff / sqrt ((v1_var / v1_n) + (v2_var / v2_n));
+
+  tmp = (v1_var / v1_n) + (v2_var / v2_n);
+  tmp = (v1_var / v1_n) / tmp;
+  tmp = pow (tmp, 2);
+  tmp = tmp / (v1_n - 1.0);
+  v1_z = tmp;
+
+  tmp = (v1_var / v1_n) + (v2_var / v2_n);
+  tmp = (v2_var / v2_n) / tmp;
+  tmp = pow (tmp, 2);
+  tmp = tmp / (v2_n - 1.0);
+  v2_z = tmp;
+
+  tmp = 1.0 / (v1_z + v2_z);
+
+  df_sep = tmp;
+  p_sep = t_sig (t_sep, df_sep);
+  if ((2.0 * p_sep) >= 1.0)
+    p_sep = 1.0 - p_sep;
+  crt_t_s = t_crt (df_sep, 0.025);
+  se_diff_s = sqrt ((v1_var / v1_n) + (v2_var / v2_n));
+
+  /* FIXME: convert to a proper PSPP output call */
+  print_t_groups (groups, &groups_values[0], &groups_values[1],
+                 v1_n, v2_n, v1_mean, v2_mean,
+                 sqrt (v1_var), sqrt (v2_var), v1_se, v2_se,
+                 diff, f_levene, p_levene,
+                 t_pooled, 2.0 * p_pooled, df_pooled, se_diff_p,
+                 diff - (crt_t_p * se_diff_p), diff + (crt_t_p * se_diff_p),
+                 t_sep, 2.0 * p_sep, df_sep, se_diff_s,
+               diff - (crt_t_s * se_diff_s), diff + (crt_t_s * se_diff_s));
+  return;
+}
+
+void
+t_pairs (void)
+{
+  double cov12, cov11, cov22, r, t, p, crt_t, sp, r_t, r_p;
+  struct variable *v1, *v2;
+
+  v1 = pairs[0][0];
+  v2 = pairs[0][1];
+  cov12 = covariance (v1_sum, v1_n, v2_sum, v2_n, xy_sum);
+  cov11 = covariance (v1_sum, v1_n, v1_sum, v1_n, v1_ss);
+  cov22 = covariance (v2_sum, v2_n, v2_sum, v2_n, v2_ss);
+  r = pearson_r (cov12, cov11, cov22);
+  /* this t and it's associated p is a significance test for the pearson's r */
+  r_t = r * sqrt ((v1_n - 2.0) / (1.0 - (r * r)));
+  r_p = t_sig (r_t, v1_n - 2.0);
+
+  /* now we move to the t test for the difference in means */
+  diff = xy_diff / v1_n;
+  sp = sqrt (variance (v1_n, xy_ss, xy_diff));
+  se_diff = sp / sqrt (v1_n);
+  t = diff / se_diff;
+  crt_t = t_crt (v1_n - 1.0, 0.025);
+  p = t_sig (t, v1_n - 1.0);
+
+
+  printf ("             Number of        2-tail\n");
+  printf (" Variable      pairs    Corr   Sig      Mean    SD   SE of Mean\n");
+  printf ("---------------------------------------------------------------\n");
+  printf ("%s                                  %8.4f %8.4f %8.4f\n",
+         v1->name, v1_mean, sqrt (v1_var), v1_se);
+  printf ("           %8.4f  %0.4f  %0.4f\n", v1_n, r, r_p);
+  printf ("%s                                  %8.4f %8.4f %8.4f\n",
+         v2->name, v2_mean, sqrt (v2_var), v2_se);
+  printf ("---------------------------------------------------------------\n");
+
+  printf ("\n\n\n");
+  printf ("      Paired Differences              |\n");
+  printf (" Mean          SD         SE of Mean  |  t-value   df   2-tail Sig\n");
+  printf ("--------------------------------------|---------------------------\n");
+
+  printf ("%8.4f    %8.4f    %8.4f      | %8.4f %8.4f %8.4f\n",
+         diff, sp, se_diff, t, v1_n - 1.0, 2.0 * (1.0 - p));
+
+  printf ("95pc CI (%8.4f, %8.4f)          |\n\n",
+         diff - (se_diff * crt_t), diff + (se_diff * crt_t));
+
+  return;
+}
+
+static int parse_value (union value *);
+
+/* Parses the GROUPS subcommand. */
+int
+tts_custom_groups (struct cmd_t_test *cmd unused)
+{
+  groups = parse_variable ();
+  if (!groups)
+    {
+      lex_error (_("expecting variable name in GROUPS subcommand"));
+      return 0;
+    }
+  if (groups->type == T_STRING && groups->width > MAX_SHORT_STRING)
+    {
+      msg (SE, _("Long string variable %s is not valid here."),
+          groups->name);
+      return 0;
+    }
+
+  if (!lex_match ('('))
+    {
+      if (groups->type == NUMERIC)
+       {
+         n_groups_values = 2;
+         groups_values[0].f = 1;
+         groups_values[1].f = 2;
+         return 1;
+       }
+      else
+       {
+         msg (SE, _("When applying GROUPS to a string variable, at "
+                    "least one value must be specified."));
+         return 0;
+       }
+    }
+  
+  if (!parse_value (&groups_values[0]))
+    return 0;
+  n_groups_values = 1;
+
+  lex_match (',');
+
+  if (lex_match (')'))
+    return 1;
+
+  if (!parse_value (&groups_values[1]))
+    return 0;
+  n_groups_values = 2;
+
+  if (!lex_force_match (')'))
+    return 0;
+
+  return 1;
+}
+
+/* Parses the current token (numeric or string, depending on the
+   variable in `groups') into value V and returns success. */
+static int
+parse_value (union value * v)
+{
+  if (groups->type == NUMERIC)
+    {
+      if (!lex_force_num ())
+       return 0;
+      v->f = tokval;
+    }
+  else
+    {
+      if (!lex_force_string ())
+       return 0;
+      strncpy (v->s, ds_value (&tokstr), ds_length (&tokstr));
+    }
+
+  lex_get ();
+
+  return 1;
+}
+
+/* Parses the PAIRS subcommand. */
+static int
+tts_custom_pairs (struct cmd_t_test *cmd unused)
+{
+  struct variable **vars;
+  int n_before_WITH;
+  int n_vars;
+  int paired;
+  int extra;
+#if DEBUGGING
+  int n_predicted;
+#endif
+
+  if ((token != T_ID || !is_varname (tokid)) && token != T_ALL)
+    return 2;
+  if (!parse_variables (&default_dict, &vars, &n_vars,
+                       PV_DUPLICATE | PV_NUMERIC | PV_NO_SCRATCH))
+    return 0;
+
+  assert (n_vars);
+  if (lex_match (T_WITH))
+    {
+      n_before_WITH = n_vars;
+
+      if (!parse_variables (&default_dict, &vars, &n_vars,
+                           PV_DUPLICATE | PV_APPEND
+                           | PV_NUMERIC | PV_NO_SCRATCH))
+       {
+         free (vars);
+         return 0;
+       }
+    }
+  else
+    n_before_WITH = 0;
+
+  paired = (lex_match ('(') && lex_match_id ("PAIRED") && lex_match (')'));
+
+  if (paired)
+    {
+      if (n_before_WITH * 2 != n_vars)
+       {
+         free (vars);
+         msg (SE, _("PAIRED was specified but the number of variables "
+                    "preceding WITH (%d) did not match the number "
+                    "following (%d)."),
+              n_before_WITH, n_vars - n_before_WITH);
+         return 0;
+       }
+
+      extra = n_before_WITH;
+    }
+  else if (n_before_WITH)
+    extra = n_before_WITH * (n_vars - n_before_WITH);
+  else
+    {
+      if (n_vars < 2)
+       {
+         free (vars);
+         msg (SE, _("At least two variables must be specified "
+                    "on PAIRS."));
+         return 0;
+       }
+
+      extra = n_vars * (n_vars - 1) / 2;
+    }
+
+#if DEBUGGING
+  n_predicted = n_pairs + extra;
+#endif
+
+  pairs = xrealloc (pairs, sizeof (struct variable *[2]) * (n_pairs + extra));
+
+  if (paired)
+    {
+      int i;
+
+      for (i = 0; i < extra; i++)
+       {
+         pairs[n_pairs][0] = vars[i];
+         pairs[n_pairs++][1] = vars[i + extra];
+       }
+    }
+  else if (n_before_WITH)
+    {
+      int i;
+
+      for (i = 0; i < n_before_WITH; i++)
+       {
+         int j;
+
+         for (j = n_before_WITH; j < n_vars; j++)
+           {
+             pairs[n_pairs][0] = vars[i];
+             pairs[n_pairs++][1] = vars[j];
+           }
+       }
+    }
+  else
+    {
+      int i;
+
+      for (i = 0; i < n_vars; i++)
+       {
+         int j;
+
+         for (j = i + 1; j < n_vars; j++)
+           {
+             pairs[n_pairs][0] = vars[i];
+             pairs[n_pairs++][1] = vars[j];
+           }
+       }
+    }
+
+#if DEBUGGING
+  assert (n_pairs == n_predicted);
+#endif
+
+  free (vars);
+  return 1;
+}
+
+#if DEBUGGING
+static void
+debug_print (void)
+{
+  printf ("T-TEST\n");
+  if (groups)
+    {
+      printf ("  GROUPS=%s", groups->name);
+      if (n_groups_values)
+       {
+         int i;
+
+         printf (" (");
+         for (i = 0; i < n_groups_values; i++)
+           if (groups->type == NUMERIC)
+             printf ("%g%s", groups_values[i].f, i ? " " : "");
+           else
+             printf ("%.*s%s", groups->width, groups_values[i].s,
+                     i ? " " : "");
+         printf (")");
+       }
+      printf ("\n");
+    }
+  if (cmd.n_variables)
+    {
+      int i;
+
+      printf ("  VARIABLES=");
+      for (i = 0; i < cmd.n_variables; i++)
+       printf ("%s ", cmd.v_variables[i]->name);
+      printf ("\n");
+    }
+  if (cmd.sbc_pairs)
+    {
+      int i;
+
+      printf ("  PAIRS=");
+      for (i = 0; i < n_pairs; i++)
+       printf ("%s ", pairs[i][0]->name);
+      printf ("WITH");
+      for (i = 0; i < n_pairs; i++)
+       printf (" %s", pairs[i][1]->name);
+      printf (" (PAIRED)\n");
+    }
+  printf ("  MISSING=%s %s\n",
+         cmd.miss == TTS_ANALYSIS ? "ANALYSIS" : "LISTWISE",
+         cmd.miss == TTS_INCLUDE ? "INCLUDE" : "EXCLUDE");
+  printf ("  FORMAT=%s\n",
+         cmd.fmt == TTS_LABELS ? "LABELS" : "NOLABELS");
+  if (cmd.criteria != NOT_LONG)
+    printf ("  CRITERIA=%f\n", cmd.criteria);
+}
+
+#endif /* DEBUGGING */
+
+/* Here are some general routines tha should probably be moved into
+   a separate library and documented as part of the PSPP "API"   */
+double
+variance (double n, double ss, double sum)
+{
+  return ((ss - ((sum * sum) / n)) / (n - 1.0));
+}
+
+double
+pooled_variance (double n_1, double var_1, double n_2, double var_2)
+{
+  double tmp;
+
+  tmp = n_1 + n_2 - 2.0;
+  tmp = (((n_1 - 1.0) * var_1) + ((n_2 - 1.0) * var_2)) / tmp;
+  tmp = tmp * ((n_1 + n_2) / (n_1 * n_2));
+  return tmp;
+}
+
+double
+oneway (double *f, double *p, struct value_list *levels)
+{
+  double k, SSTR, SSE, SSTO, N, MSTR, MSE, sum, dftr, dfe, print;
+  struct value_list *g;
+
+  k = 0.0;
+
+  for (g = levels; g != NULL; g = g->next)
+    {
+      k++;
+      sum += g->sum;
+      N += g->n;
+      SSTR += g->ss - (pow (g->sum, 2) / g->n);
+      SSTO += g->ss;
+    }
+
+  SSTO = SSTO - (pow (sum, 2) / N);
+  SSE = SSTO - SSTR;
+
+  dftr = N - k;
+  dfe = k - 1.0;
+  MSTR = SSTR / dftr;
+  MSE = SSE / dfe;
+
+  *f = (MSE / MSTR);
+  *p = f_sig (*f, dfe, dftr);
+
+  print = 1.0;
+  if (print == 1.0)
+    {
+      printf ("sum1 %f, sum2 %f, ss1 %f, ss2 %f\n",
+             levels->sum, levels->next->sum, levels->ss, levels->next->ss);
+      printf ("                - - - - - - O N E W A Y - - - - - -\n\n");
+      printf ("   Variable %s %s\n",
+             cmd.v_variables[0]->name, cmd.v_variables[0]->label);
+      printf ("By Variable %s %s\n", groups->name, groups->label);
+      printf ("\n             Analysis of Variance\n\n");
+      printf ("                    Sum of    Mean     F       F\n");
+      printf ("Source       D.F.  Squares  Squares  Ratio   Prob\n\n");
+      printf ("Between   %8.0f %8.4f %8.4f %8.4f %8.4f\n",
+             dfe, SSE, MSE, *f, *p);
+      printf ("Within    %8.0f %8.4f %8.4f\n", dftr, SSTR, MSTR);
+      printf ("Total     %8.0f %8.4f\n\n\n", N - 1.0, SSTO);
+    }
+  return (*f);
+}
+
+double
+f_sig (double f, double dfn, double dfd)
+{
+  int which, status;
+  double p, q, bound;
+
+  which = FIND_P;
+  status = 1;
+  p = q = bound = 0.0;
+  cdff (&which, &p, &q, &f, &dfn, &dfd, &status, &bound);
+
+  switch (status)
+    {
+    case -1:
+      {
+       printf ("Parameter 1 is out of range\n");
+       break;
+      }
+    case -2:
+      {
+       printf ("Parameter 2 is out of range\n");
+       break;
+      }
+    case -3:
+      {
+       printf ("Parameter 3 is out of range\n");
+       break;
+      }
+    case -4:
+      {
+       printf ("Parameter 4 is out of range\n");
+       break;
+      }
+    case -5:
+      {
+       printf ("Parameter 5 is out of range\n");
+       break;
+      }
+    case -6:
+      {
+       printf ("Parameter 6 is out of range\n");
+       break;
+      }
+    case -7:
+      {
+       printf ("Parameter 7 is out of range\n");
+       break;
+      }
+    case -8:
+      {
+       printf ("Parameter 8 is out of range\n");
+       break;
+      }
+    case 0:
+      {
+       /* printf( "Command completed successfully\n" ); */
+       break;
+      }
+    case 1:
+      {
+       printf ("Answer appears to be lower than the lowest search bound\n");
+       break;
+      }
+    case 2:
+      {
+       printf ("Answer appears to be higher than the greatest search bound\n");
+       break;
+      }
+    case 3:
+      {
+       printf ("P - Q NE 1\n");
+       break;
+      }
+    }
+
+  if (status)
+    {
+      return (double) ERROR_SIG;
+    }
+  else
+    {
+      return q;
+    }
+}
+
+double
+t_crt (double df, double q)
+{
+  int which, status;
+  double p, bound, t;
+
+  which = FIND_CRITICAL_VALUE;
+  bound = 0.0;
+  p = 1.0 - q;
+  t = 0.0;
+
+  cdft (&which, &p, &q, &t, &df, &status, &bound);
+
+  switch (status)
+    {
+    case -1:
+      {
+       printf ("t_crt: Parameter 1 is out of range\n");
+       break;
+      }
+    case -2:
+      {
+       printf ("t_crt: value of p (%f) is out of range\n", p);
+       break;
+      }
+    case -3:
+      {
+       printf ("t_crt: value of q (%f) is out of range\n", q);
+       break;
+      }
+    case -4:
+      {
+       printf ("t_crt: value of df (%f) is out of range\n", df);
+       break;
+      }
+    case -5:
+      {
+       printf ("t_crt: Parameter 5 is out of range\n");
+       break;
+      }
+    case -6:
+      {
+       printf ("t_crt: Parameter 6 is out of range\n");
+       break;
+      }
+    case -7:
+      {
+       printf ("t_crt: Parameter 7 is out of range\n");
+       break;
+      }
+    case 0:
+      {
+       /* printf( "Command completed successfully\n" ); */
+       break;
+      }
+    case 1:
+      {
+       printf ("t_crt: Answer appears to be lower than the lowest search bound\n");
+       break;
+      }
+    case 2:
+      {
+       printf ("t_crt: Answer appears to be higher than the greatest search bound\n");
+       break;
+      }
+    case 3:
+      {
+       printf ("t_crt: P - Q NE 1\n");
+       break;
+      }
+    }
+
+  if (status)
+    {
+      return (double) ERROR_SIG;
+    }
+  else
+    {
+      return t;
+    }
+}
+
+double
+t_sig (double t, double df)
+{
+  int which, status;
+  double p, q, bound;
+
+  which = FIND_P;
+  q = 0.0;
+  p = 0.0;
+  bound = 0.0;
+
+  cdft (&which, &p, &q, &t, &df, &status, &bound);
+
+  switch (status)
+    {
+    case -1:
+      {
+       printf ("t-sig: Parameter 1 is out of range\n");
+       break;
+      }
+    case -2:
+      {
+       printf ("t-sig: Parameter 2 is out of range\n");
+       break;
+      }
+    case -3:
+      {
+       printf ("t-sig: Parameter 3 is out of range\n");
+       break;
+      }
+    case -4:
+      {
+       printf ("t-sig: Parameter 4 is out of range\n");
+       break;
+      }
+    case -5:
+      {
+       printf ("t-sig: Parameter 5 is out of range\n");
+       break;
+      }
+    case -6:
+      {
+       printf ("t-sig: Parameter 6 is out of range\n");
+       break;
+      }
+    case -7:
+      {
+       printf ("t-sig: Parameter 7 is out of range\n");
+       break;
+      }
+    case 0:
+      {
+       /* printf( "Command completed successfully\n" ); */
+       break;
+      }
+    case 1:
+      {
+       printf ("t-sig: Answer appears to be lower than the lowest search bound\n");
+       break;
+      }
+    case 2:
+      {
+       printf ("t-sig: Answer appears to be higher than the greatest search bound\n");
+       break;
+      }
+    case 3:
+      {
+       printf ("t-sig: P - Q NE 1\n");
+       break;
+      }
+    }
+
+  if (status)
+    {
+      return (double) ERROR_SIG;
+    }
+  else
+    {
+      return q;
+    }
+}
+
+double
+covariance (double x_sum, double x_n, double y_sum, double y_n, double ss)
+{
+  double tmp;
+
+  tmp = x_sum * y_sum;
+  tmp = tmp / x_n;
+  tmp = ss - tmp;
+  tmp = (tmp / (x_n + y_n - 1.0));
+  return tmp;
+}
+
+double
+pearson_r (double c_xy, double c_xx, double c_yy)
+{
+  return (c_xy / (sqrt (c_xx * c_yy)));
+}
+
+void 
+print_t_groups (struct variable * grps, union value * g1, union value * g2,
+               double n1, double n2, double mean1, double mean2,
+               double sd1, double sd2, double se1, double se2,
+               double diff, double l_f, double l_p,
+               double p_t, double p_sig, double p_df, double p_sed,
+               double p_l, double p_h,
+               double s_t, double s_sig, double s_df, double s_sed,
+               double s_l, double s_h)
+{
+
+  /* Display all this shit as SPSS 6.0 does (roughly) */
+  printf ("\n\n                 Number                                 \n");
+  printf ("   Variable     of Cases    Mean      SD      SE of Mean\n");
+  printf ("-----------------------------------------------------------\n");
+  printf ("   %s %s\n\n", cmd.v_variables[cur_var]->name, cmd.v_variables[cur_var]->label);
+  printf ("%s %8.4f %8.0f    %8.4f  %8.3f    %8.3f\n",
+         get_val_lab (grps, *g1, 0), g1->f, n1, mean1, sd1, se1);
+  printf ("%s %8.4f %8.0f    %8.4f  %8.3f    %8.3f\n",
+         get_val_lab (grps, *g2, 0), g2->f, n2, mean2, sd2, se2);
+  printf ("-----------------------------------------------------------\n");
+  printf ("\n   Mean Difference = %8.4f\n", diff);
+  printf ("\n   Levene's Test for Equality of Variances: F= %.3f  P= %.3f\n",
+         l_f, l_p);
+  printf ("\n\n   t-test for Equality of Means                         95pc     \n");
+  printf ("Variances   t-value    df   2-Tail Sig SE of Diff    CI for Diff  \n");
+  printf ("-----------------------------------------------------------------\n");
+  printf ("Equal     %8.2f %8.0f %8.3f %8.3f (%8.3f, %8.3f)\n",
+         p_t, p_df, p_sig, p_sed, p_l, p_h);
+  printf ("Unequal   %8.2f %8.2f %8.3f %8.3f (%8.3f, %8.3f)\n",
+         s_t, s_df, s_sig, s_sed, s_l, s_h);
+  printf ("-----------------------------------------------------------------\n");
+}
+
+/* 
+   Local Variables:
+   mode: c
+   End:
+*/
diff --git a/src/tab.c b/src/tab.c
new file mode 100644 (file)
index 0000000..412bb0a
--- /dev/null
+++ b/src/tab.c
@@ -0,0 +1,1383 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <ctype.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "format.h"
+#include "magic.h"
+#include "misc.h"
+#include "output.h"
+#include "pool.h"
+#include "som.h"
+#include "tab.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1 */
+#include "debug-print.h"
+\f
+extern struct som_table_class tab_table_class;
+
+#if DEBUGGING
+#define DEFFIRST(NAME, LABEL) LABEL,
+#define DEFTAB(NAME, LABEL) LABEL,
+static const char *tab_names[] =
+  {
+#include "tab.def"
+  };
+#undef DEFFIRST
+#undef DEFTAB
+#endif
+
+/* Creates a table with NC columns and NR rows.  If REALLOCABLE is
+   nonzero then the table's size can be increased later; otherwise,
+   its size can only be reduced. */
+struct tab_table *
+tab_create (int nc, int nr, int reallocable)
+{
+  void *(*alloc_func) (struct pool *, size_t);
+
+  struct tab_table *t;
+  
+  {
+    struct pool *container = pool_create ();
+    t = pool_alloc (container, sizeof *t);
+    t->container = container;
+  }
+  
+  t->col_style = TAB_COL_NONE;
+  t->col_group = 0;
+  ls_null (&t->title);
+  t->flags = SOMF_NONE;
+  t->nr = nr;
+  t->nc = t->cf = nc;
+  t->l = t->r = t->t = t->b = 0;
+
+  alloc_func = reallocable ? pool_malloc : pool_alloc;
+#if GLOBAL_DEBUGGING
+  t->reallocable = reallocable;
+#endif
+
+  t->cc = alloc_func (t->container, nr * nc * sizeof *t->cc);
+  t->ct = alloc_func (t->container, nr * nc);
+  memset (t->ct, TAB_EMPTY, nc * nr);
+
+  t->rh = alloc_func (t->container, nc * (nr + 1));
+  memset (t->rh, 0, nc * (nr + 1));
+
+  t->hrh = alloc_func (t->container, sizeof *t->hrh * (nr + 1));
+  memset (t->hrh, 0, sizeof *t->hrh * (nr + 1));
+
+  t->trh = alloc_func (t->container, nr + 1);
+  memset (t->trh, 0, nr + 1);
+
+  t->rv = alloc_func (t->container, (nc + 1) * nr);
+  memset (t->rv, 0, (nc + 1) * nr);
+
+  t->wrv = alloc_func (t->container, sizeof *t->wrv * (nc + 1));
+  memset (t->wrv, 0, sizeof *t->wrv * (nc + 1));
+
+  t->trv = alloc_func (t->container, nc + 1);
+  memset (t->trv, 0, nc + 1);
+
+  t->dim = NULL;
+  t->w = t->h = NULL;
+  t->col_ofs = t->row_ofs = 0;
+  
+  return t;
+}
+
+/* Destroys table T. */
+void
+tab_destroy (struct tab_table *t)
+{
+  assert (t != NULL);
+  pool_destroy (t->container);
+}
+
+/* Sets the width and height of a table, in columns and rows,
+   respectively.  Use only to reduce the size of a table, since it
+   does not change the amount of allocated memory. */
+void
+tab_resize (struct tab_table *t, int nc, int nr)
+{
+  assert (t != NULL);
+  if (nc != -1)
+    {
+      assert (nc + t->col_ofs <= t->cf);
+      t->nc = nc + t->col_ofs;
+    }
+  if (nr != -1)
+    {
+      assert (nr + t->row_ofs <= t->nr);
+      t->nr = nr + t->row_ofs;
+    }
+}
+
+/* Changes either or both dimensions of a table.  Consider using the
+   above routine instead if it won't waste a lot of space.
+
+   Changing the number of columns in a table is particularly expensive
+   in space and time.  Avoid doing such.  FIXME: In fact, transferring
+   of rules isn't even implemented yet. */
+void
+tab_realloc (struct tab_table *t, int nc, int nr)
+{
+  int ro, co;
+  
+  assert (t != NULL);
+#if GLOBAL_DEBUGGING
+  assert (t->reallocable);
+#endif
+  ro = t->row_ofs;
+  co = t->col_ofs;
+  if (ro || co)
+    tab_offset (t, 0, 0);
+
+  if (nc == -1)
+    nc = t->nc;
+  if (nr == -1)
+    nr = t->nr;
+  
+  assert (nc == t->nc);
+  
+  if (nc > t->cf)
+    {
+      int mr1 = min (nr, t->nr);
+      int mc1 = min (nc, t->nc);
+      
+      struct len_string *new_cc;
+      unsigned char *new_ct;
+      int r;
+
+      new_cc = pool_malloc (t->container, nr * nc * sizeof *new_cc);
+      new_ct = pool_malloc (t->container, nr * nc);
+      for (r = 0; r < mr1; r++)
+       {
+         memcpy (&new_cc[r * nc], &t->cc[r * t->nc], mc1 * sizeof *t->cc);
+         memcpy (&new_ct[r * nc], &t->ct[r * t->nc], mc1);
+         memset (&new_ct[r * nc + t->nc], TAB_EMPTY, nc - t->nc);
+       }
+      pool_free (t->container, t->cc);
+      pool_free (t->container, t->ct);
+      t->cc = new_cc;
+      t->ct = new_ct;
+      t->cf = nc;
+    }
+  else if (nr != t->nr)
+    {
+      t->cc = pool_realloc (t->container, t->cc, nr * nc * sizeof *t->cc);
+      t->ct = pool_realloc (t->container, t->ct, nr * nc);
+
+      t->rh = pool_realloc (t->container, t->rh, nc * (nr + 1));
+      t->rv = pool_realloc (t->container, t->rv, (nc + 1) * nr);
+      t->trh = pool_realloc (t->container, t->trh, nr + 1);
+      t->hrh = pool_realloc (t->container, t->hrh,
+                            sizeof *t->hrh * (nr + 1));
+      
+      if (nr > t->nr)
+       {
+         memset (&t->rh[nc * (t->nr + 1)], 0, (nr - t->nr) * nc);
+         memset (&t->rv[(nc + 1) * t->nr], 0, (nr - t->nr) * (nc + 1));
+         memset (&t->trh[t->nr + 1], 0, nr - t->nr);
+       }
+    }
+
+  memset (&t->ct[nc * t->nr], TAB_EMPTY, nc * (nr - t->nr));
+  
+  t->nr = nr;
+  t->nc = nc;
+
+  if (ro || co)
+    tab_offset (t, co, ro);
+}
+
+/* Sets the number of header rows on each side of TABLE to L on the
+   left, R on the right, T on the top, B on the bottom.  Header rows
+   are repeated when a table is broken across multiple columns or
+   multiple pages. */
+void
+tab_headers (struct tab_table *table, int l, int r, int t, int b)
+{
+  assert (table != NULL);
+  table->l = l;
+  table->r = r;
+  table->t = t;
+  table->b = b;
+}
+
+/* Set up table T so that, when it is an appropriate size, it will be
+   displayed across the page in columns.
+
+   STYLE is a TAB_COL_* constant.  GROUP is the number of rows to take
+   as a unit. */
+void
+tab_columns (struct tab_table *t, int style, int group)
+{
+  assert (t != NULL);
+  t->col_style = style;
+  t->col_group = group;
+}
+\f
+/* Rules. */
+
+/* Draws a vertical line to the left of cells at horizontal position X
+   from Y1 to Y2 inclusive in style STYLE, if style is not -1. */
+void
+tab_vline (struct tab_table *t, int style, int x, int y1, int y2)
+{
+  int y;
+
+  assert (t != NULL);
+#if GLOBAL_DEBUGGING
+  if (x + t->col_ofs < 0 || x + t->col_ofs > t->nc
+      || y1 + t->row_ofs < 0 || y1 + t->row_ofs >= t->nr
+      || y2 + t->row_ofs < 0 || y2 + t->row_ofs >= t->nr)
+    {
+      printf (_("bad vline: x=%d+%d=%d y=(%d+%d=%d,%d+%d=%d) in "
+               "table size (%d,%d)\n"),
+             x, t->col_ofs, x + t->col_ofs,
+             y1, t->row_ofs, y1 + t->row_ofs,
+             y2, t->row_ofs, y2 + t->row_ofs,
+             t->nc, t->nr);
+      return;
+    }
+#endif
+
+  x += t->col_ofs;
+  y1 += t->row_ofs;
+  y2 += t->row_ofs;
+
+  if (style != -1)
+    {
+      if ((style & TAL_SPACING) == 0)
+       for (y = y1; y <= y2; y++)
+         t->rv[x + (t->cf + 1) * y] = style;
+      t->trv[x] |= (1 << (style & ~TAL_SPACING));
+    }
+}
+
+/* Draws a horizontal line above cells at vertical position Y from X1
+   to X2 inclusive in style STYLE, if style is not -1. */
+void
+tab_hline (struct tab_table * t, int style, int x1, int x2, int y)
+{
+  int x;
+
+  assert (t != NULL);
+#if GLOBAL_DEBUGGING
+  if (x1 + t->col_ofs < 0 || x1 + t->col_ofs >= t->nc 
+      || x2 + t->col_ofs < 0 || x2 + t->col_ofs >= t->nc
+      || y + t->row_ofs < 0 || y + t->row_ofs > t->nr)
+    {
+      printf (_("bad hline: x=(%d+%d=%d,%d+%d=%d) y=%d+%d=%d "
+               "in table size (%d,%d)\n"),
+             x1, t->col_ofs, x1 + t->col_ofs,
+             x2, t->col_ofs, x2 + t->col_ofs,
+             y, t->row_ofs, y + t->row_ofs,
+             t->nc, t->nr);
+      return;
+    }
+#endif
+
+  x1 += t->col_ofs;
+  x2 += t->col_ofs;
+  y += t->row_ofs;
+
+  if (style != -1)
+    {
+      if ((style & TAL_SPACING) == 0)
+       for (x = x1; x <= x2; x++)
+         t->rh[x + t->cf * y] = style;
+      t->trh[y] |= (1 << (style & ~TAL_SPACING));
+    }
+}
+
+/* Draws a box around cells (X1,Y1)-(X2,Y2) inclusive with horizontal
+   lines of style F_H and vertical lines of style F_V.  Fills the
+   interior of the box with horizontal lines of style I_H and vertical
+   lines of style I_V.  Any of the line styles may be -1 to avoid
+   drawing those lines.  This is distinct from 0, which draws a null
+   line. */
+void
+tab_box (struct tab_table *t, int f_h, int f_v, int i_h, int i_v,
+        int x1, int y1, int x2, int y2)
+{
+  assert (t != NULL);
+#if GLOBAL_DEBUGGING
+  if (x1 + t->col_ofs < 0 || x1 + t->col_ofs >= t->nc 
+      || x2 + t->col_ofs < 0 || x2 + t->col_ofs >= t->nc
+      || y1 + t->row_ofs < 0 || y1 + t->row_ofs >= t->nr 
+      || y2 + t->row_ofs < 0 || y2 + t->row_ofs >= t->nr)
+    {
+      printf (_("bad box: (%d+%d=%d,%d+%d=%d)-(%d+%d=%d,%d+%d=%d) "
+               "in table size (%d,%d)\n"),
+             x1, t->col_ofs, x1 + t->col_ofs,
+             y1, t->row_ofs, y1 + t->row_ofs,
+             x2, t->col_ofs, x2 + t->col_ofs,
+             y2, t->row_ofs, y2 + t->row_ofs,
+             t->nc, t->nr);
+      abort ();
+    }
+#endif
+
+  x1 += t->col_ofs;
+  x2 += t->col_ofs;
+  y1 += t->row_ofs;
+  y2 += t->row_ofs;
+
+  if (f_h != -1)
+    {
+      int x;
+      if ((f_h & TAL_SPACING) == 0)
+       for (x = x1; x <= x2; x++)
+         {
+           t->rh[x + t->cf * y1] = f_h;
+           t->rh[x + t->cf * (y2 + 1)] = f_h;
+         }
+      t->trh[y1] |= (1 << (f_h & ~TAL_SPACING));
+      t->trh[y2 + 1] |= (1 << (f_h & ~TAL_SPACING));
+    }
+  if (f_v != -1)
+    {
+      int y;
+      if ((f_v & TAL_SPACING) == 0)
+       for (y = y1; y <= y2; y++)
+         {
+           t->rv[x1 + (t->cf + 1) * y] = f_v;
+           t->rv[(x2 + 1) + (t->cf + 1) * y] = f_v;
+         }
+      t->trv[x1] |= (1 << (f_v & ~TAL_SPACING));
+      t->trv[x2 + 1] |= (1 << (f_v & ~TAL_SPACING));
+    }
+
+  if (i_h != -1)
+    {
+      int y;
+      
+      for (y = y1 + 1; y <= y2; y++)
+       {
+         int x;
+
+         if ((i_h & TAL_SPACING) == 0)
+           for (x = x1; x <= x2; x++)
+             t->rh[x + t->cf * y] = i_h;
+
+         t->trh[y] |= (1 << (i_h & ~TAL_SPACING));
+       }
+    }
+  if (i_v != -1)
+    {
+      int x;
+      
+      for (x = x1 + 1; x <= x2; x++)
+       {
+         int y;
+         
+         if ((i_v & TAL_SPACING) == 0)
+           for (y = y1; y <= y2; y++)
+             t->rv[x + (t->cf + 1) * y] = i_v;
+
+         t->trv[x] |= (1 << (i_v & ~TAL_SPACING));
+       }
+    }
+}
+
+/* Formats text TEXT and arguments ARGS as indicated in OPT and sets
+   the resultant string into S in TABLE's pool. */
+static void
+text_format (struct tab_table *table, int opt, const char *text, va_list args,
+            struct len_string *s)
+{
+  int len;
+  
+  assert (table != NULL && text != NULL && s != NULL);
+  
+  if (opt & TAT_PRINTF)
+    {
+      char *temp_buf = local_alloc (1024);
+      
+      len = nvsprintf (temp_buf, text, args);
+      text = temp_buf;
+    }
+  else
+    len = strlen (text);
+
+  ls_create_buffer (table->container, s, text, len);
+  
+  if (opt & TAT_PRINTF)
+    local_free (text);
+}
+
+/* Set the title of table T to TITLE, which is formatted with printf
+   if FORMAT is nonzero. */
+void
+tab_title (struct tab_table *t, int format, const char *title, ...)
+{
+  va_list args;
+
+  assert (t != NULL && title != NULL);
+  va_start (args, title);
+  text_format (t, format ? TAT_PRINTF : TAT_NONE, title, args, &t->title);
+  va_end (args);
+}
+
+/* Set DIM_FUNC as the dimension function for table T. */
+void
+tab_dim (struct tab_table *t, tab_dim_func *dim_func)
+{
+  assert (t != NULL && t->dim == NULL);
+  t->dim = dim_func;
+}
+
+/* Returns the natural width of column C in table T for driver D, that
+   is, the smallest width necessary to display all its cells without
+   wrapping.  The width will be no larger than the page width minus
+   left and right rule widths. */
+int
+tab_natural_width (struct tab_table *t, struct outp_driver *d, int c)
+{
+  int width;
+
+  assert (t != NULL && c >= 0 && c < t->nc);
+  {
+    int r;
+
+    for (width = r = 0; r < t->nr; r++)
+      {
+       struct outp_text text;
+       unsigned char opt = t->ct[c + r * t->cf];
+               
+       if (opt & (TAB_JOIN | TAB_EMPTY))
+         continue;
+
+       text.s = t->cc[c + r * t->cf];
+       assert (!ls_null_p (&text.s));
+       text.options = OUTP_T_JUST_LEFT;
+
+       d->class->text_metrics (d, &text);
+       if (text.h > width)
+         width = text.h;
+      }
+  }
+
+  if (width == 0)
+    {
+      width = d->prop_em_width * 8;
+#if GLOBAL_DEBUGGING
+      printf ("warning: table column %d contains no data.\n", c);
+#endif
+    }
+  
+  {
+    const int clamp = d->width - t->wrv[0] - t->wrv[t->nc];
+    
+    if (width > clamp)
+      width = clamp;
+  }
+
+  return width;
+}
+
+/* Returns the natural height of row R in table T for driver D, that
+   is, the minimum height necessary to display the information in the
+   cell at the widths set for each column. */
+int
+tab_natural_height (struct tab_table *t, struct outp_driver *d, int r)
+{
+  int height;
+
+  assert (t != NULL && r >= 0 && r < t->nr);
+  
+  {
+    int c;
+    
+    for (height = d->font_height, c = 0; c < t->nc; c++)
+      {
+       struct outp_text text;
+       unsigned char opt = t->ct[c + r * t->cf];
+
+       assert (t->w[c] != NOT_INT);
+       if (opt & (TAB_JOIN | TAB_EMPTY))
+         continue;
+
+       text.s = t->cc[c + r * t->cf];
+       assert (!ls_null_p (&text.s));
+       text.options = OUTP_T_HORZ | OUTP_T_JUST_LEFT;
+       text.h = t->w[c];
+       d->class->text_metrics (d, &text);
+
+       if (text.v > height)
+         height = text.v;
+      }
+  }
+
+  return height;
+}
+
+/* Callback function to set all columns and rows to their natural
+   dimensions.  Not really meant to be called directly.  */
+void
+tab_natural_dimensions (struct tab_table *t, struct outp_driver *d)
+{
+  int i;
+
+  assert (t != NULL);
+  
+  for (i = 0; i < t->nc; i++)
+    t->w[i] = tab_natural_width (t, d, i);
+  
+  for (i = 0; i < t->nr; i++)
+    t->h[i] = tab_natural_height (t, d, i);
+}
+
+\f
+/* Cells. */
+
+/* Sets cell (C,R) in TABLE, with options OPT, to have a value taken
+   from V, displayed with format spec F. */
+void
+tab_value (struct tab_table *table, int c, int r, unsigned char opt,
+          const union value *v, const struct fmt_spec *f)
+{
+  char *contents;
+  union value temp_val;
+
+  assert (table != NULL && v != NULL && f != NULL);
+#if GLOBAL_DEBUGGING
+  if (c + table->col_ofs < 0 || r + table->row_ofs < 0
+      || c + table->col_ofs >= table->nc
+      || r + table->row_ofs >= table->nr)
+    {
+      printf ("tab_value(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
+             "(%d,%d)\n",
+             c, table->col_ofs, c + table->col_ofs,
+             r, table->row_ofs, r + table->row_ofs,
+             table->nc, table->nr);
+      return;
+    }
+#endif
+
+  contents = pool_alloc (table->container, f->w);
+  ls_init (&table->cc[c + r * table->cf], contents, f->w);
+  table->ct[c + r * table->cf] = opt;
+  
+  if (formats[f->type].cat & FCAT_STRING)
+    {
+      temp_val.c = (char *) v->s;
+      v = &temp_val;
+    }
+  data_out (contents, f, v);
+}
+
+/* Sets cell (C,R) in TABLE, with options OPT, to have value VAL
+   with NDEC decimal places. */
+void
+tab_float (struct tab_table *table, int c, int r, unsigned char opt,
+          double val, int w, int d)
+{
+  char *contents;
+  char buf[40], *cp;
+  
+  struct fmt_spec f;
+
+  assert (table != NULL && w <= 40);
+  
+  f.type = FMT_F;
+  f.w = w;
+  f.d = d;
+  
+#if GLOBAL_DEBUGGING
+  if (c + table->col_ofs < 0 || r + table->row_ofs < 0
+      || c + table->col_ofs >= table->nc
+      || r + table->row_ofs >= table->nr)
+    {
+      printf ("tab_float(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
+             "(%d,%d)\n",
+             c, table->col_ofs, c + table->col_ofs,
+             r, table->row_ofs, r + table->row_ofs,
+             table->nc, table->nr);
+      return;
+    }
+#endif
+
+  data_out (buf, &f, (union value *) &val);
+  cp = buf;
+  while (isspace ((unsigned char) *cp) && cp < &buf[w])
+    cp++;
+  f.w = w - (cp - buf);
+
+  contents = pool_alloc (table->container, f.w);
+  ls_init (&table->cc[c + r * table->cf], contents, f.w);
+  table->ct[c + r * table->cf] = opt;
+  memcpy (contents, cp, f.w);
+}
+
+/* Sets cell (C,R) in TABLE, with options OPT, to have text value
+   TEXT. */
+void
+tab_text (struct tab_table *table, int c, int r, unsigned opt, const char *text, ...)
+{
+  va_list args;
+
+  assert (table != NULL && text != NULL);
+#if GLOBAL_DEBUGGING
+  if (c + table->col_ofs < 0 || r + table->row_ofs < 0
+      || c + table->col_ofs >= table->nc
+      || r + table->row_ofs >= table->nr)
+    {
+      printf ("tab_text(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
+             "(%d,%d)\n",
+             c, table->col_ofs, c + table->col_ofs,
+             r, table->row_ofs, r + table->row_ofs,
+             table->nc, table->nr);
+      return;
+    }
+#endif
+    
+  va_start (args, text);
+  text_format (table, opt, text, args, &table->cc[c + r * table->cf]);
+  table->ct[c + r * table->cf] = opt;
+  va_end (args);
+}
+
+/* Joins cells (X1,X2)-(Y1,Y2) inclusive in TABLE, and sets them with
+   options OPT to have text value TEXT. */
+void
+tab_joint_text (struct tab_table *table, int x1, int y1, int x2, int y2,
+               unsigned opt, const char *text, ...)
+{
+  struct tab_joined_cell *j;
+
+  assert (table != NULL && text != NULL);
+#if GLOBAL_DEBUGGING
+  if (x1 + table->col_ofs < 0 || x1 + table->col_ofs >= table->nc
+      || y1 + table->row_ofs < 0 || y1 + table->row_ofs >= table->nr
+      || x2 < x1 || x2 + table->col_ofs >= table->nc
+      || y2 < y2 || y2 + table->row_ofs >= table->nr)
+    {
+      printf ("tab_joint_text(): bad cell "
+             "(%d+%d=%d,%d+%d=%d)-(%d+%d=%d,%d+%d=%d) in table size (%d,%d)\n",
+             x1, table->col_ofs, x1 + table->col_ofs,
+             y1, table->row_ofs, y1 + table->row_ofs,
+             x2, table->col_ofs, x2 + table->col_ofs,
+             y2, table->row_ofs, y2 + table->row_ofs,
+             table->nc, table->nr);
+      return;
+    }
+#endif
+  
+  j = pool_alloc (table->container, sizeof *j);
+  j->hit = 0;
+  j->x1 = x1 + table->col_ofs;
+  j->y1 = y1 + table->row_ofs;
+  j->x2 = ++x2 + table->col_ofs;
+  j->y2 = ++y2 + table->row_ofs;
+  
+  {
+    va_list args;
+    
+    va_start (args, text);
+    text_format (table, opt, text, args, &j->contents);
+    va_end (args);
+  }
+  
+  opt |= TAB_JOIN;
+  
+  {
+    struct len_string *cc = &table->cc[x1 + y1 * table->cf];
+    unsigned char *ct = &table->ct[x1 + y1 * table->cf];
+    const int ofs = table->cf - (x2 - x1);
+
+    int y;
+    
+    for (y = y1; y < y2; y++)
+      {
+       int x;
+       
+       for (x = x1; x < x2; x++)
+         {
+           ls_init (cc++, (char *) j, 0);
+           *ct++ = opt;
+         }
+       
+       cc += ofs;
+       ct += ofs;
+      }
+  }
+}
+
+/* Sets cell (C,R) in TABLE, with options OPT, to contents STRING. */
+void
+tab_raw (struct tab_table *table, int c, int r, unsigned opt,
+        struct len_string *string)
+{
+  assert (table != NULL && string != NULL);
+  
+#if GLOBAL_DEBUGGING
+  if (c + table->col_ofs < 0 || r + table->row_ofs < 0
+      || c + table->col_ofs >= table->nc
+      || r + table->row_ofs >= table->nr)
+    {
+      printf ("tab_float(): bad cell (%d+%d=%d,%d+%d=%d) in table size "
+             "(%d,%d)\n",
+             c, table->col_ofs, c + table->col_ofs,
+             r, table->row_ofs, r + table->row_ofs,
+             table->nc, table->nr);
+      return;
+    }
+#endif
+
+  table->cc[c + r * table->cf] = *string;
+  table->ct[c + r * table->cf] = opt;
+}
+\f
+/* Miscellaneous. */
+
+/* Sets the widths of all the columns and heights of all the rows in
+   table T for driver D. */
+static void
+nowrap_dim (struct tab_table *t, struct outp_driver *d)
+{
+  t->w[0] = tab_natural_width (t, d, 0);
+  t->h[0] = d->font_height;
+}
+
+/* Sets the widths of all the columns and heights of all the rows in
+   table T for driver D. */
+static void
+wrap_dim (struct tab_table *t, struct outp_driver *d)
+{
+  t->w[0] = tab_natural_width (t, d, 0);
+  t->h[0] = tab_natural_height (t, d, 0);
+}
+
+/* Outputs text BUF as a table with a single cell having cell options
+   OPTIONS, which is a combination of the TAB_* and TAT_*
+   constants. */
+void
+tab_output_text (int options, const char *buf, ...)
+{
+  struct tab_table *t = tab_create (1, 1, 0);
+
+  assert (buf != NULL);
+  if (options & TAT_PRINTF)
+    {
+      va_list args;
+      char *temp_buf = local_alloc (4096);
+      
+      va_start (args, buf);
+      nvsprintf (temp_buf, buf, args);
+      buf = temp_buf;
+      va_end (args);
+    }
+  
+  if (options & TAT_FIX)
+    {
+      struct outp_driver *d;
+
+      for (d = outp_drivers (NULL); d; d = outp_drivers (d))
+       {
+         if (!d->page_open)
+           d->class->open_page (d);
+         
+         d->class->text_set_font_by_name (d, "FIXED");
+       }
+    }
+
+  tab_text (t, 0, 0, options &~ TAT_PRINTF, buf);
+  tab_flags (t, SOMF_NO_TITLE | SOMF_NO_SPACING);
+  if (options & TAT_NOWRAP)
+    tab_dim (t, nowrap_dim);
+  else
+    tab_dim (t, wrap_dim);
+  tab_submit (t);
+
+  if (options & TAT_FIX)
+    {
+      struct outp_driver *d;
+
+      for (d = outp_drivers (NULL); d; d = outp_drivers (d))
+       d->class->text_set_font_by_name (d, "PROP");
+    }
+  
+  if (options & TAT_PRINTF)
+    local_free (buf);
+}
+
+/* Set table flags to FLAGS. */
+void
+tab_flags (struct tab_table *t, unsigned flags)
+{
+  assert (t != NULL);
+  t->flags = flags;
+}
+
+/* Easy, type-safe way to submit a tab table to som. */
+void
+tab_submit (struct tab_table *t)
+{
+  struct som_table s;
+
+  assert (t != NULL);
+  s.class = &tab_table_class;
+  s.ext = t;
+  som_submit (&s);
+  tab_destroy (t);
+}
+\f
+/* Editing. */
+
+/* Set table row and column offsets for all functions that affect
+   cells or rules. */
+void
+tab_offset (struct tab_table *t, int col, int row)
+{
+  int diff = 0;
+
+  assert (t != NULL);
+#if GLOBAL_DEBUGGING
+  if (row < -1 || row >= t->nr)
+    {
+      printf ("tab_offset(): row=%d in %d-row table\n", row, t->nr);
+      abort ();
+    }
+  if (col < -1 || col >= t->nc)
+    {
+      printf ("tab_offset(): col=%d in %d-column table\n", col, t->nc);
+      abort ();
+    }
+#endif
+
+  if (row != -1)
+    diff += (row - t->row_ofs) * t->cf, t->row_ofs = row;
+  if (col != -1)
+    diff += (col - t->col_ofs), t->col_ofs = col;
+
+  t->cc += diff;
+  t->ct += diff;
+}
+
+/* Increment the row offset by one. If the table is too small,
+   increase its size. */
+void
+tab_next_row (struct tab_table *t)
+{
+  assert (t != NULL);
+  t->cc += t->cf;
+  t->ct += t->cf;
+  if (++t->row_ofs >= t->nr)
+    tab_realloc (t, -1, t->nr * 4 / 3);
+}
+\f
+static struct tab_table *t;
+static struct outp_driver *d;
+int tab_hit;
+
+/* Set the current table to TABLE. */
+static void
+tabi_table (struct som_table *table)
+{
+  assert (table != NULL);
+  t = table->ext;
+  tab_offset (t, 0, 0);
+  
+  assert (t->w == NULL && t->h == NULL);
+  t->w = pool_alloc (t->container, sizeof *t->w * t->nc);
+  t->h = pool_alloc (t->container, sizeof *t->h * t->nr);
+}
+
+/* Set the current output device to DRIVER. */
+static void
+tabi_driver (struct outp_driver *driver)
+{
+  int i;
+
+  assert (driver != NULL);
+  d = driver;
+  
+  /* Figure out sizes of rules. */
+  for (t->hr_tot = i = 0; i <= t->nr; i++)
+    t->hr_tot += t->hrh[i] = d->horiz_line_spacing[t->trh[i]];
+  for (t->vr_tot = i = 0; i <= t->nc; i++)
+    t->vr_tot += t->wrv[i] = d->vert_line_spacing[t->trv[i]];
+
+#if GLOBAL_DEBUGGING
+  for (i = 0; i < t->nr; i++)
+    t->h[i] = -1;
+  for (i = 0; i < t->nc; i++)
+    t->w[i] = -1;
+#endif
+
+  assert (t->dim != NULL);
+  t->dim (t, d);
+
+#if GLOBAL_DEBUGGING
+  {
+    int error = 0;
+
+    for (i = 0; i < t->nr; i++)
+      {
+       if (t->h[i] == -1)
+         {
+           printf ("Table row %d height not initialized.\n", i);
+           error = 1;
+         }
+       assert (t->h[i] > 0);
+      }
+    
+    for (i = 0; i < t->nc; i++)
+      {
+       if (t->w[i] == -1)
+         {
+           printf ("Table column %d width not initialized.\n", i);
+           error = 1;
+         }
+       assert (t->w[i] > 0);
+      }
+  }
+#endif
+    
+  /* Add up header sizes. */
+  for (i = 0, t->wl = t->wrv[0]; i < t->l; i++)
+    t->wl += t->w[i] + t->wrv[i + 1];
+  for (i = 0, t->ht = t->hrh[0]; i < t->t; i++)
+    t->ht += t->h[i] + t->hrh[i + 1];
+  for (i = t->nc - t->r, t->wr = t->wrv[i]; i < t->nc; i++)
+    t->wr += t->w[i] + t->wrv[i + 1];
+  for (i = t->nr - t->b, t->hb = t->hrh[i]; i < t->nr; i++)
+    t->hb += t->h[i] + t->hrh[i + 1];
+  
+  /* Title. */
+  if (!(t->flags & SOMF_NO_TITLE))
+    t->ht += d->font_height;
+}
+
+/* Return the number of columns and rows in the table into N_COLUMNS
+   and N_ROWS, respectively. */
+static void
+tabi_count (int *n_columns, int *n_rows)
+{
+  assert (n_columns != NULL && n_rows != NULL);
+  *n_columns = t->nc;
+  *n_rows = t->nr;
+}
+
+static void tabi_cumulate (int cumtype, int start, int *end, int max, int *actual);
+
+/* Return the horizontal and vertical size of the entire table,
+   including headers, for the current output device, into HORIZ and
+   VERT. */
+static void
+tabi_area (int *horiz, int *vert)
+{
+  assert (horiz != NULL && vert != NULL);
+  
+  {
+    int w, c;
+    
+    for (c = t->l + 1, w = t->wl + t->wr + t->w[t->l];
+        c < t->nc - t->r; c++)
+      w += t->w[c] + t->wrv[c];
+    *horiz = w;
+  }
+  
+  {
+    int h, r;
+    for (r = t->t + 1, h = t->ht + t->hb + t->h[t->t];
+        r < t->nr - t->b; r++)
+      h += t->h[r] + t->hrh[r];
+    *vert = h;
+  }
+}
+
+/* Return the column style for this table into STYLE. */
+static void
+tabi_columns (int *style)
+{
+  assert (style != NULL);
+  *style = t->col_style;
+}
+
+/* Return the number of header rows/columns on the left, right, top,
+   and bottom sides into HL, HR, HT, and HB, respectively. */
+static void
+tabi_headers (int *hl, int *hr, int *ht, int *hb)
+{
+  assert (hl != NULL && hr != NULL && ht != NULL && hb != NULL);
+  *hl = t->l;
+  *hr = t->r;
+  *ht = t->t;
+  *hb = t->b;
+}
+
+/* Determines the number of rows or columns (including appropriate
+   headers), depending on CUMTYPE, that will fit into the space
+   specified.  Takes rows/columns starting at index START and attempts
+   to fill up available space MAX.  Returns in END the index of the
+   last row/column plus one; returns in ACTUAL the actual amount of
+   space the selected rows/columns (including appropriate headers)
+   filled. */
+static void
+tabi_cumulate (int cumtype, int start, int *end, int max, int *actual)
+{
+  int n;
+  int *d;
+  int *r;
+  int total;
+  
+  assert (end != NULL && (cumtype == SOM_ROWS || cumtype == SOM_COLUMNS));
+  if (cumtype == SOM_ROWS)
+    {
+      assert (start >= 0 && start < t->nr);
+      n = t->nr - t->b;
+      d = &t->h[start];
+      r = &t->hrh[start + 1];
+      total = t->ht + t->hb;
+    } else {
+      assert (start >= 0 && start < t->nc);
+      n = t->nc - t->r;
+      d = &t->w[start];
+      r = &t->wrv[start + 1];
+      total = t->wl + t->wr;
+    }
+  
+  total += *d++;
+  if (total > max)
+    {
+      if (end)
+       *end = start;
+      if (actual)
+       *actual = 0;
+      return;
+    }
+    
+  {
+    int x;
+      
+    for (x = start + 1; x < n; x++)
+      {
+       int amt = *d++ + *r++;
+       
+       total += amt;
+       if (total > max)
+         {
+           total -= amt;
+           break;
+         }
+      }
+
+    if (end)
+      *end = x;
+    
+    if (actual)
+      *actual = total;
+  }
+}
+
+/* Return flags set for the current table into FLAGS. */
+static void
+tabi_flags (unsigned *flags)
+{
+  assert (flags != NULL);
+  *flags = t->flags;
+}
+
+/* Render title for current table, with major index X and minor index
+   Y.  Y may be zero, or X and Y may be zero, but X should be nonzero
+   if Y is nonzero. */
+static void
+tabi_title (int x, int y)
+{
+  char buf[1024];
+  char *cp;
+
+  if (t->flags & SOMF_NO_TITLE)
+    return;
+  
+  cp = spprintf (buf, "%d.%d", table_num, subtable_num);
+  if (x && y)
+    cp = spprintf (cp, "(%d:%d)", x, y);
+  else if (x)
+    cp = spprintf (cp, "(%d)", x);
+  if (cur_proc)
+    cp = spprintf (cp, " %s", cur_proc);
+  cp = stpcpy (cp, ".  ");
+  if (!ls_empty_p (&t->title))
+    {
+      memcpy (cp, ls_value (&t->title), ls_length (&t->title));
+      cp += ls_length (&t->title);
+    }
+  *cp = 0;
+  
+  {
+    struct outp_text text;
+
+    text.options = OUTP_T_JUST_LEFT | OUTP_T_HORZ | OUTP_T_VERT;
+    ls_init (&text.s, buf, cp - buf);
+    text.h = d->width;
+    text.v = d->font_height;
+    text.x = 0;
+    text.y = d->cp_y;
+    d->class->text_draw (d, &text);
+  }
+}
+
+static int render_strip (int x, int y, int r, int c1, int c2, int r1, int r2);
+
+/* Execute BODY for each value of X from A to B exclusive. */
+#define UNROLL_LOOP(X, A, B, BODY)             \
+       do                                      \
+         {                                     \
+           for (X = A; X < B; X++)             \
+             {                                 \
+               BODY                            \
+             }                                 \
+         }                                     \
+       while (0)
+
+/* Execute PREP, then BODY for each specified value of X: A1...A2, B1...B2,
+   C1...C2, in each case not including the second value. */
+#define UNROLL_3_LOOPS(X, A1, A2, B1, B2, C1, C2, BODY)        \
+       do                                              \
+         {                                             \
+           UNROLL_LOOP (X, A1, A2, BODY);              \
+           UNROLL_LOOP (X, B1, B2, BODY);              \
+           UNROLL_LOOP (X, C1, C2, BODY);              \
+         }                                             \
+       while (0)
+
+/* Draws the table region in rectangle (X1,Y1)-(X2,Y2), where column
+   X2 and row Y2 are not included in the rectangle, at the current
+   position on the current output device.  Draws headers as well. */
+static void
+tabi_render (int x1, int y1, int x2, int y2)
+{
+  int y, r;
+  
+  tab_hit++;
+  y = d->cp_y;
+  if (!(t->flags & SOMF_NO_TITLE))
+    y += d->font_height;
+  UNROLL_3_LOOPS (r, 0, t->t * 2 + 1, y1 * 2 + 1, y2 * 2,
+                 (t->nr - t->b) * 2, t->nr * 2 + 1,
+
+                 int x = d->cp_x;
+                 x += render_strip (x, y, r, 0, t->l * 2 + 1, y1, y2);
+                 x += render_strip (x, y, r, x1 * 2 + 1, x2 * 2, y1, y2);
+                 x += render_strip (x, y, r, (t->nc - t->r) * 2,
+                                    t->nc * 2 + 1, y1, y2);
+                 y += (r & 1) ? t->h[r / 2] : t->hrh[r / 2];
+                 );
+}
+
+struct som_table_class tab_table_class =
+  {
+    tabi_table,
+    tabi_driver,
+    
+    tabi_count,
+    tabi_area,
+    NULL,
+    NULL,
+    tabi_columns,
+    NULL,
+    tabi_headers,
+    NULL,
+    tabi_cumulate,
+    tabi_flags,
+    
+    NULL,
+    NULL,
+
+    tabi_title,
+    tabi_render,
+  };
+\f
+/* Render contiguous strip consisting of columns C1...C2, exclusive,
+   on row R, at location (X,Y).  Return width of the strip thus
+   rendered.
+
+   Renders joined cells, even those outside the strip, within the
+   rendering region (C1,R1)-(C2,R2).
+
+   For the purposes of counting rows and columns in this function
+   only, horizontal rules are considered rows and vertical rules are
+   considered columns.
+
+   FIXME: Doesn't use r1?  Huh?  */
+static int
+render_strip (int x, int y, int r, int c1, int c2, int r1 unused, int r2)
+{
+  int x_origin = x;
+
+  /* Horizontal rules. */
+  if ((r & 1) == 0)
+    {
+      int hrh = t->hrh[r / 2];
+      int c;
+
+      for (c = c1; c < c2; c++)
+       {
+         if (c & 1)
+           {
+             int style = t->rh[(c / 2) + (r / 2 * t->cf)];
+
+             if (style != TAL_0)
+               {
+                 const struct color clr = {0, 0, 0, 0};
+                 struct rect rct;
+
+                 rct.x1 = x;
+                 rct.y1 = y;
+                 rct.x2 = x + t->w[c / 2];
+                 rct.y2 = y + hrh;
+                 d->class->line_horz (d, &rct, &clr, style);
+               }
+             x += t->w[c / 2];
+           } else {
+             const struct color clr = {0, 0, 0, 0};
+             struct rect rct;
+             struct outp_styles s;
+
+             rct.x1 = x;
+             rct.y1 = y;
+             rct.x2 = x + t->wrv[c / 2];
+             rct.y2 = y + hrh;
+
+             s.t = r > 0 ? t->rv[(c / 2) + (t->cf + 1) * (r / 2 - 1)] : 0;
+             s.b = r < 2 * t->nr ? t->rv[(c / 2) + (t->cf + 1) * (r / 2)] : 0;
+             s.l = c > 0 ? t->rh[(c / 2 - 1) + t->cf * (r / 2)] : 0;
+             s.r = c < 2 * t->nc ? t->rh[(c / 2) + t->cf * (r / 2)] : 0;
+
+             if (s.t | s.b | s.l | s.r)
+               d->class->line_intersection (d, &rct, &clr, &s);
+             
+             x += t->wrv[c / 2];
+           }
+       }
+    } else {
+      int c;
+
+      for (c = c1; c < c2; c++)
+       {
+         if (c & 1)
+           {
+             const int index = (c / 2) + (r / 2 * t->cf);
+
+             if (!(t->ct[index] & TAB_JOIN))
+               {
+                 struct outp_text text;
+
+                 text.options = ((t->ct[index] & OUTP_T_JUST_MASK)
+                                 | OUTP_T_HORZ | OUTP_T_VERT);
+                 if ((t->ct[index] & TAB_EMPTY) == 0)
+                   {
+                     text.s = t->cc[index];
+                     assert (!ls_null_p (&text.s));
+                     text.h = t->w[c / 2];
+                     text.v = t->h[r / 2];
+                     text.x = x;
+                     text.y = y;
+                     d->class->text_draw (d, &text);
+                   }
+               } else {
+                 struct tab_joined_cell *j =
+                   (struct tab_joined_cell *) ls_value (&t->cc[index]);
+
+                 if (j->hit != tab_hit)
+                   {
+                     j->hit = tab_hit;
+
+                     if (j->x1 == c / 2 && j->y1 == r / 2
+                         && j->x2 <= c2 && j->y2 <= r2)
+                       {
+                         struct outp_text text;
+
+                         text.options = ((t->ct[index] & OUTP_T_JUST_MASK)
+                                         | OUTP_T_HORZ | OUTP_T_VERT);
+                         text.s = j->contents;
+                         text.x = x;
+                         text.y = y;
+                         
+                         {
+                           int c;
+
+                           for (c = j->x1, text.h = -t->wrv[j->x2];
+                                c < j->x2; c++)
+                             text.h += t->w[c] + t->wrv[c + 1];
+                         }
+                         
+                         {
+                           int r;
+
+                           for (r = j->y1, text.v = -t->hrh[j->y2];
+                                r < j->y2; r++)
+                             text.v += t->h[r] + t->hrh[r + 1];
+                         }
+                         d->class->text_draw (d, &text);
+                       }
+                   }
+               }
+             x += t->w[c / 2];
+           } else {
+             int style = t->rv[(c / 2) + (r / 2 * (t->cf + 1))];
+
+             if (style != TAL_0)
+               {
+                 const struct color clr = {0, 0, 0, 0};
+                 struct rect rct;
+
+                 rct.x1 = x;
+                 rct.y1 = y;
+                 rct.x2 = x + t->wrv[c / 2];
+                 rct.y2 = y + t->h[r / 2];
+                 d->class->line_vert (d, &rct, &clr, style);
+               }
+             x += t->wrv[c / 2];
+           }
+       }
+    }
+
+  return x - x_origin;
+}
+
diff --git a/src/tab.h b/src/tab.h
new file mode 100644 (file)
index 0000000..37e045c
--- /dev/null
+++ b/src/tab.h
@@ -0,0 +1,195 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !tab_h
+#define tab_h 1
+
+#include <limits.h>
+#include "str.h"
+
+/* Cell options. */
+enum
+  {
+    TAB_NONE = 0,
+
+    /* Must match output.h: OUTP_T_JUST_*. */
+    TAB_ALIGN_MASK = 03,       /* Alignment mask. */
+    TAB_RIGHT = 00,            /* Right justify. */
+    TAB_LEFT = 01,             /* Left justify. */
+    TAB_CENTER = 02,           /* Center. */
+
+    /* Oddball cell types. */
+    TAB_JOIN = 010,            /* Joined cell. */
+    TAB_EMPTY = 020            /* Empty cell. */
+  };
+
+/* Line styles.  These must match output.h:OUTP_L_*. */
+enum
+  {
+    TAL_0 = 0,                 /* No line. */
+    TAL_1 = 1,                 /* Single line. */
+    TAL_2 = 2,                 /* Double line. */
+    TAL_3 = 3,                 /* Special line of driver-defined style. */
+    TAL_COUNT,                 /* Number of line styles. */
+
+    TAL_SPACING = 0200         /* Don't draw the line, just reserve space. */
+  };
+
+/* Column styles.  Must correspond to SOM_COL_*. */
+enum
+  {
+    TAB_COL_NONE,                      /* No columns. */
+    TAB_COL_DOWN                       /* Columns down first. */
+  };
+
+/* Joined cell. */
+struct tab_joined_cell
+  {
+    int x1, y1;
+    int x2, y2;
+    int hit;
+    struct len_string contents;
+  };
+
+struct outp_driver;
+struct tab_table;
+typedef void tab_dim_func (struct tab_table *, struct outp_driver *);
+
+/* A table. */
+struct tab_table
+  {
+    struct pool *container;
+    
+    /* Contents. */
+    int col_style;             /* Columns: One of TAB_COL_*. */
+    int col_group;             /* Number of rows per column group. */
+    struct len_string title;   /* Table title. */
+    unsigned flags;            /* SOMF_*. */
+    int nc, nr;                        /* Number of columns, rows. */
+    int cf;                    /* Column factor for indexing purposes. */
+    int l, r, t, b;            /* Number of header rows on each side. */
+    struct len_string *cc;     /* Cell contents; len_string *[nr][nc]. */
+    unsigned char *ct;         /* Cell types; unsigned char[nr][nc]. */
+    unsigned char *rh;         /* Horiz rules; unsigned char[nr+1][nc]. */
+    unsigned char *trh;                /* Types of horiz rules; [nr+1]. */
+    unsigned char *rv;         /* Vert rules; unsigned char[nr][nc+1]. */
+    unsigned char *trv;                /* Types of vert rules; [nc+1]. */
+    tab_dim_func *dim;         /* Calculates cell widths and heights. */
+
+    /* Calculated during output. */
+    int *w;                    /* Column widths; [nc]. */
+    int *h;                    /* Row heights; [nr]. */
+    int *hrh;                  /* Heights of horizontal rules; [nr+1]. */
+    int *wrv;                  /* Widths of vertical rules; [nc+1]. */
+    int wl, wr, ht, hb;                /* Width/height of header rows/columns. */
+    int hr_tot, vr_tot;                /* Hrules total height, vrules total width. */
+
+    /* Editing info. */
+    int col_ofs, row_ofs;      /* X and Y offsets. */
+#if GLOBAL_DEBUGGING
+    int reallocable;           /* Can table be reallocated? */
+#endif
+  };
+
+extern int tab_hit;
+
+/* Number of rows in TABLE. */
+#define tab_nr(TABLE) ((TABLE)->nr)
+
+/* Number of columns in TABLE. */
+#define tab_nc(TABLE) ((TABLE)->nc)
+
+/* Number of left header columns in TABLE. */
+#define tab_l(TABLE) ((TABLE)->l)
+
+/* Number of right header columns in TABLE. */
+#define tab_r(TABLE) ((TABLE)->r)
+
+/* Number of top header rows in TABLE. */
+#define tab_t(TABLE) ((TABLE)->t)
+
+/* Number of bottom header rows in TABLE. */
+#define tab_b(TABLE) ((TABLE)->b)
+
+/* Tables. */
+struct tab_table *tab_create (int nc, int nr, int reallocable);
+void tab_destroy (struct tab_table *);
+void tab_resize (struct tab_table *, int nc, int nr);
+void tab_realloc (struct tab_table *, int nc, int nr);
+void tab_headers (struct tab_table *, int l, int r, int t, int b);
+void tab_columns (struct tab_table *, int style, int group);
+void tab_title (struct tab_table *, int format, const char *, ...);
+void tab_flags (struct tab_table *, unsigned);
+void tab_submit (struct tab_table *);
+
+/* Dimensioning. */
+tab_dim_func tab_natural_dimensions;
+int tab_natural_width (struct tab_table *t, struct outp_driver *d, int c);
+int tab_natural_height (struct tab_table *t, struct outp_driver *d, int r);
+void tab_dim (struct tab_table *, tab_dim_func *);
+
+/* Rules. */
+void tab_hline (struct tab_table *, int style, int x1, int x2, int y);
+void tab_vline (struct tab_table *, int style, int x, int y1, int y2);
+void tab_box (struct tab_table *, int f_h, int f_v, int i_h, int i_v,
+             int x1, int y1, int x2, int y2);
+
+/* Text options, passed in the `opt' argument. */
+enum
+  {
+    TAT_NONE = 0,              /* No options. */
+    TAT_PRINTF = 0x0100,       /* Format the text string with sprintf. */
+    TAT_TITLE = 0x0204,                /* Title attributes. */
+    TAT_FIX = 0x0400,          /* Use fixed-pitch font. */
+    TAT_NOWRAP = 0x0800         /* No text wrap (tab_output_text() only). */
+  };
+
+/* Cells. */
+struct fmt_spec;
+union value;
+void tab_value (struct tab_table *, int c, int r, unsigned char opt,
+               const union value *, const struct fmt_spec *);
+void tab_float (struct tab_table *, int c, int r, unsigned char opt,
+               double v, int w, int d);
+void tab_text (struct tab_table *, int c, int r, unsigned opt,
+              const char *, ...)
+     __attribute__ ((format (printf, 5, 6)));
+void tab_joint_text (struct tab_table *, int x1, int y1, int x2, int y2,
+                    unsigned opt, const char *, ...)
+     __attribute__ ((format (printf, 7, 8)));
+
+/* Cell low-level access. */
+#define tab_alloc(TABLE, AMT) pool_alloc ((TABLE)->container, (AMT))
+void tab_raw (struct tab_table *, int c, int r, unsigned opt,
+             struct len_string *);
+
+/* Editing. */
+void tab_offset (struct tab_table *, int col, int row);
+void tab_next_row (struct tab_table *);
+
+/* Current row/column offset. */
+#define tab_row(TABLE) ((TABLE)->row_ofs)
+#define tab_col(TABLE) ((TABLE)->col_ofs)
+
+/* Simple output. */
+void tab_output_text (int options, const char *string, ...)
+     __attribute__ ((format (printf, 2, 3)));
+
+#endif /* tab_h */
+
diff --git a/src/temporary.c b/src/temporary.c
new file mode 100644 (file)
index 0000000..316fd64
--- /dev/null
@@ -0,0 +1,333 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "avl.h"
+#include "command.h"
+#include "do-ifP.h"
+#include "error.h"
+#include "lexer.h"
+#include "str.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+int temporary;
+struct dictionary *temp_dict;
+int temp_trns;
+
+#if 0
+/* Displays all the value labels in TREE, with label S. */
+void
+display_tree (char *s, avl_tree *tree)
+{
+  value_label *iter;
+  avl_traverser *trav = NULL;
+
+  printf("%s tree:\n", s);
+  fflush(stdout);
+  while ((iter = avl_traverse (tree, &trav)) != NULL)
+    printf (" %g: %s\n", iter->v.f, iter->s);
+}
+#endif
+
+/* Parses the TEMPORARY command. */
+int
+cmd_temporary (void)
+{
+  lex_match_id ("TEMPORARY");
+
+  /* TEMPORARY is not allowed inside DO IF or LOOP. */
+  if (ctl_stack)
+    {
+      msg (SE, _("This command is not valid inside DO IF or LOOP."));
+      return CMD_FAILURE;
+    }
+
+  /* TEMPORARY can only appear once! */
+  if (temporary)
+    {
+      msg (SE, _("This command may only appear once between "
+          "procedures and procedure-like commands."));
+      return CMD_FAILURE;
+    }
+
+  /* Everything is temporary, even if we think it'll last forever.
+     Especially then. */
+  temporary = 1;
+  temp_dict = save_dictionary ();
+  if (f_trns == n_trns)
+    temp_trns = -1;
+  else
+    temp_trns = n_trns;
+  debug_printf (("TEMPORARY: temp_trns=%d\n", temp_trns));
+
+  return lex_end_of_command ();
+}
+
+/* Copies a variable structure. */
+void
+copy_variable (struct variable *dest, const struct variable *src)
+{
+  int i, n;
+
+  assert (dest != src);
+  dest->type = src->type;
+  dest->left = src->left;
+  dest->width = src->width;
+  dest->fv = src->fv;
+  dest->nv = src->nv;
+  dest->miss_type = src->miss_type;
+  
+  switch (src->miss_type)
+    {
+    case MISSING_NONE:
+      n = 0;
+      break;
+    case MISSING_1:
+      n = 1;
+      break;
+    case MISSING_2:
+    case MISSING_RANGE:
+      n = 2;
+      break;
+    case MISSING_3:
+    case MISSING_RANGE_1:
+      n = 3;
+      break;
+    default:
+      assert (0);
+      break;
+    }
+  
+  for (i = 0; i < n; i++)
+    dest->missing[i] = src->missing[i];
+  dest->print = src->print;
+  dest->write = src->write;
+
+  dest->val_lab = copy_value_labels (src->val_lab);
+  dest->label = src->label ? xstrdup (src->label) : NULL;
+}
+
+/* Returns a newly created empty dictionary.  The file label and
+   documents are copied from default_dict if COPY is nonzero. */
+struct dictionary *
+new_dictionary (int copy)
+{
+  struct dictionary *d = xmalloc (sizeof *d);
+  
+  d->var = NULL;
+  d->var_by_name = avl_create (NULL, cmp_variable, NULL);
+  d->nvar = 0;
+
+  d->N = 0;
+
+  d->nval = 0;
+
+  d->n_splits = 0;
+  d->splits = NULL;
+
+  if (default_dict.label && copy)
+    d->label = xstrdup (default_dict.label);
+  else
+    d->label = NULL;
+
+  if (default_dict.n_documents && copy)
+    {
+      d->n_documents = default_dict.n_documents;
+      if (d->n_documents)
+       {
+         d->documents = malloc (default_dict.n_documents * 80);
+         memcpy (d->documents, default_dict.documents,
+                 default_dict.n_documents * 80);
+       }
+    }
+  else
+    {
+      d->n_documents = 0;
+      d->documents = NULL;
+    }
+  
+  d->weight_index = -1;
+  d->weight_var[0] = 0;
+
+  d->filter_var[0] = 0;
+
+  return d;
+}
+    
+/* Copies the current dictionary info into a newly allocated
+   dictionary structure, which is returned. */
+struct dictionary *
+save_dictionary (void)
+{
+  /* Dictionary being created. */
+  struct dictionary *d;
+
+  int i;
+
+  d = xmalloc (sizeof *d);
+
+  /* First the easy stuff. */
+  *d = default_dict;
+  d->label = default_dict.label ? xstrdup (default_dict.label) : NULL;
+  if (default_dict.n_documents)
+    {
+      d->documents = malloc (default_dict.n_documents * 80);
+      memcpy (d->documents, default_dict.documents,
+             default_dict.n_documents * 80);
+    }
+  else d->documents = NULL;
+
+  /* Then the variables. */
+  d->var_by_name = avl_create (NULL, cmp_variable, NULL);
+  d->var = xmalloc (default_dict.nvar * sizeof *d->var);
+  for (i = 0; i < default_dict.nvar; i++)
+    {
+      d->var[i] = xmalloc (sizeof *d->var[i]);
+      copy_variable (d->var[i], default_dict.var[i]);
+      strcpy (d->var[i]->name, default_dict.var[i]->name);
+      d->var[i]->index = i;
+      avl_force_insert (d->var_by_name, d->var[i]);
+    }
+
+  /* Then the SPLIT FILE variables. */
+  if (default_dict.splits)
+    {
+      int i;
+
+      d->n_splits = default_dict.n_splits;
+      d->splits = xmalloc ((default_dict.n_splits + 1) * sizeof *d->splits);
+      for (i = 0; i < default_dict.n_splits; i++)
+       d->splits[i] = d->var[default_dict.splits[i]->index];
+      d->splits[default_dict.n_splits] = NULL;
+    }
+  else
+    {
+      d->n_splits = 0;
+      d->splits = NULL;
+    }
+  
+  return d;
+}
+
+/* Copies dictionary D into the active file dictionary.  Deletes
+   dictionary D. */
+void
+restore_dictionary (struct dictionary * d)
+{
+  int i;
+
+  /* 1. Delete the current dictionary. */
+  default_dict.n_splits = 0;
+  free (default_dict.splits);
+  default_dict.splits = NULL;
+  
+  avl_destroy (default_dict.var_by_name, NULL);
+  default_dict.var_by_name = NULL;
+  
+  for (i = 0; i < default_dict.nvar; i++)
+    {
+      clear_variable (&default_dict, default_dict.var[i]);
+      free (default_dict.var[i]);
+    }
+  
+  free (default_dict.var);
+  free (default_dict.label);
+  free (default_dict.documents);
+
+  /* 2. Copy dictionary D into the active file dictionary. */
+#if __CHECKER__
+  {
+    size_t offset;
+    
+    offset = offsetof (struct dictionary, filter_var) + sizeof d->filter_var;
+    strncpy (d->weight_var, d->weight_var, 9);
+    strncpy (d->filter_var, d->filter_var, 9);
+    memset (&((char *) d)[offset], '*', sizeof *d - offset);
+  }
+#endif
+  default_dict = *d;
+  if (!default_dict.var_by_name)
+    {
+      default_dict.var_by_name = avl_create (NULL, cmp_variable, NULL);
+      
+      for (i = 0; i < default_dict.nvar; i++)
+       avl_force_insert (default_dict.var_by_name, default_dict.var[i]);
+    }
+
+  /* 3. Destroy dictionary D. */
+  free (d);
+}
+
+/* Destroys dictionary D. */
+void
+free_dictionary (struct dictionary * d)
+{
+  int i;
+
+  d->n_splits = 0;
+  free (d->splits);
+  d->splits = NULL;
+  
+  if (d->var_by_name)
+    avl_destroy (d->var_by_name, NULL);
+
+  for (i = 0; i < d->nvar; i++)
+    {
+      struct variable *v = d->var[i];
+
+      if (v->val_lab)
+       {
+         avl_destroy (v->val_lab, free_val_lab);
+         v->val_lab = NULL;
+       }
+      if (v->label)
+       {
+         free (v->label);
+         v->label = NULL;
+       }
+      free (d->var[i]);
+    }
+  free (d->var);
+
+  free (d->label);
+  free (d->documents);
+
+  free (d);
+}
+
+/* Cancels the temporary transformation, if any. */
+void
+cancel_temporary (void)
+{
+  if (temporary)
+    {
+      if (temp_dict)
+       free_dictionary (temp_dict);
+      temporary = 0;
+      temp_trns = 0;
+    }
+}
diff --git a/src/title.c b/src/title.c
new file mode 100644 (file)
index 0000000..cea04c6
--- /dev/null
@@ -0,0 +1,179 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "main.h"
+#include "output.h"
+#include "var.h"
+#include "version.h"
+#include "vfm.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1 */
+#include "debug-print.h"
+
+static int get_title (const char *cmd, char **title);
+
+int
+cmd_title (void)
+{
+  return get_title ("TITLE", &outp_title);
+}
+
+int
+cmd_subtitle (void)
+{
+  return get_title ("SUBTITLE", &outp_subtitle);
+}
+
+static int
+get_title (const char *cmd, char **title)
+{
+  int c;
+
+  c = lex_look_ahead ();
+  debug_printf ((_("%s before: %s\n"), cmd, *title ? *title : _("<none>")));
+  if (c == '"' || c == '\'')
+    {
+      lex_get ();
+      if (!lex_force_string ())
+       return CMD_FAILURE;
+      if (*title)
+       free (*title);
+      *title = xstrdup (ds_value (&tokstr));
+      lex_get ();
+      if (token != '.')
+       {
+         msg (SE, _("%s: `.' expected after string."), cmd);
+         return CMD_FAILURE;
+       }
+    }
+  else
+    {
+      char *cp;
+
+      if (*title)
+       free (*title);
+      *title = xstrdup (lex_rest_of_line (NULL));
+      for (cp = *title; *cp; cp++)
+       *cp = toupper ((unsigned char) (*cp));
+      token = '.';
+    }
+  debug_printf ((_("%s after: %s\n"), cmd, *title));
+  return CMD_SUCCESS;
+}
+
+/* Performs the FILE LABEL command. */
+int
+cmd_file_label (void)
+{
+  char *label;
+
+  label = lex_rest_of_line (NULL);
+  while (isspace ((unsigned char) *label))
+    label++;
+
+  free (default_dict.label);
+  default_dict.label = xstrdup (label);
+  if (strlen (default_dict.label) > 60)
+    default_dict.label[60] = 0;
+  token = '.';
+
+  return CMD_SUCCESS;
+}
+
+/* Add LINE as a line of document information to default_dict,
+   indented by INDENT spaces. */
+static void
+add_document_line (const char *line, int indent)
+{
+  char *doc;
+
+  default_dict.n_documents++;
+  default_dict.documents = xrealloc (default_dict.documents,
+                                    80 * default_dict.n_documents);
+  doc = &default_dict.documents[80 * (default_dict.n_documents - 1)];
+  memset (doc, ' ', indent);
+  st_bare_pad_copy (&doc[indent], line, 80 - indent);
+}
+
+/* Performs the DOCUMENT command. */
+int
+cmd_document (void)
+{
+  /* Add a few header lines for reference. */
+  {
+    char buf[256];
+    struct tm *tmp = localtime (&last_vfm_invocation);
+
+    if (default_dict.n_documents)
+      add_document_line ("", 0);
+
+    sprintf (buf, _("Document entered %s %02d:%02d:%02d by %s (%s):"),
+            curdate, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, version,
+            host_system);
+    add_document_line (buf, 1);
+  }
+
+  for (;;)
+    {
+      int had_dot;
+      char *line;
+
+      line = lex_rest_of_line (&had_dot);
+      while (isspace ((unsigned char) *line))
+       line++;
+
+      if (had_dot)
+       {
+         char *cp = strchr (line, 0);
+         *cp++ = '.';
+         *cp = 0;
+       }
+
+      add_document_line (line, 3);
+
+      lex_get_line ();
+      if (had_dot)
+       break;
+    }
+
+  token = '.';
+  return CMD_SUCCESS;
+}
+
+/* Performs the DROP DOCUMENTS command. */
+int
+cmd_drop_documents (void)
+{
+  lex_match_id ("DROP");
+  lex_match_id ("DOCUMENTS");
+
+  free (default_dict.documents);
+  default_dict.documents = NULL;
+  default_dict.n_documents = 0;
+
+  return lex_end_of_command ();
+}
diff --git a/src/val-labs.c b/src/val-labs.c
new file mode 100644 (file)
index 0000000..ae9ac29
--- /dev/null
@@ -0,0 +1,306 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "avl.h"
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "str.h"
+#include "var.h"
+\f
+/* Declarations. */
+
+#undef DEBUGGING
+/*#define DEBUGGING 1 */
+#include "debug-print.h"
+
+/* Variable list. */
+static struct variable **v;
+
+/* Number of variables. */
+static int nv;
+
+static int do_value_labels (int);
+static int verify_val_labs (int erase);
+static int get_label (void);
+
+#if DEBUGGING
+static void debug_print (void);
+#endif
+\f
+/* Stubs. */
+
+static void
+init (void)
+{
+  v = NULL;
+}
+
+static void
+done (void)
+{
+  free (v);
+}
+
+int
+cmd_value_labels (void)
+{
+  int code;
+  init ();
+  lex_match_id ("VALUE");
+  lex_match_id ("LABELS");
+  code = do_value_labels (1);
+  done ();
+  return code;
+}
+
+int
+cmd_add_value_labels (void)
+{
+  int code;
+  lex_match_id ("ADD");
+  lex_match_id ("VALUE");
+  lex_match_id ("LABELS");
+  code = do_value_labels (0);
+  done ();
+  return code;
+}
+\f
+/* Do it. */
+
+static int
+do_value_labels (int erase)
+{
+  lex_match ('/');
+  
+  while (token != '.')
+    {
+      parse_variables (NULL, &v, &nv, PV_SAME_TYPE);
+      if (!verify_val_labs (erase))
+       return CMD_PART_SUCCESS_MAYBE;
+      while (token != '/' && token != '.')
+       if (!get_label ())
+         return CMD_PART_SUCCESS_MAYBE;
+
+      if (token != '/')
+       break;
+      lex_get ();
+
+      free (v);
+      v = NULL;
+    }
+
+  if (token != '.')
+    {
+      lex_error (NULL);
+      return CMD_TRAILING_GARBAGE;
+    }
+
+#if DEBUGGING
+  debug_print ();
+#endif
+  return CMD_SUCCESS;
+}
+
+static int
+verify_val_labs (int erase)
+{
+  int i;
+
+  if (!nv)
+    return 1;
+
+  for (i = 0; i < nv; i++)
+    {
+      struct variable *vp = v[i];
+
+      if (vp->type == ALPHA && vp->width > 8)
+       {
+         msg (SE, _("It is not possible to assign value labels to long "
+                    "string variables such as %s."), vp->name);
+         return 0;
+       }
+
+      if (erase && v[i]->val_lab)
+       {
+         avl_destroy (vp->val_lab, free_val_lab);
+         vp->val_lab = NULL;
+       }
+    }
+  return 1;
+}
+
+/* Parse all the labels for a particular set of variables and add the
+   specified labels to those variables. */
+static int
+get_label (void)
+{
+  int i;
+
+  /* Make sure there's some variables. */
+  if (!nv)
+    {
+      if (token != T_STRING && token != T_NUM)
+       return 0;
+      lex_get ();
+      return 1;
+    }
+
+  /* Parse all the labels and add them to the variables. */
+  do
+    {
+      struct value_label *label;
+
+      /* Allocate label. */
+      label = xmalloc (sizeof *label);
+#if __CHECKER__
+      memset (&label->v, 0, sizeof label->v);
+#endif
+      label->ref_count = nv;
+
+      /* Set label->v. */
+      if (v[0]->type == ALPHA)
+       {
+         if (token != T_STRING)
+           {
+             msg (SE, _("String expected for value."));
+             return 0;
+           }
+         st_bare_pad_copy (label->v.s, ds_value (&tokstr), MAX_SHORT_STRING);
+       }
+      else
+       {
+         if (token != T_NUM)
+           {
+             msg (SE, _("Number expected for value."));
+             return 0;
+           }
+         if (!lex_integer_p ())
+           msg (SW, _("Value label `%g' is not integer."), tokval);
+         label->v.f = tokval;
+       }
+
+      /* Set label->s. */
+      lex_get ();
+      if (!lex_force_string ())
+       return 0;
+      if (ds_length (&tokstr) > 60)
+       {
+         msg (SW, _("Truncating value label to 60 characters."));
+         ds_truncate (&tokstr, 60);
+       }
+      label->s = xstrdup (ds_value (&tokstr));
+
+      for (i = 0; i < nv; i++)
+       {
+         if (!v[i]->val_lab)
+           v[i]->val_lab = avl_create (NULL, val_lab_cmp,
+                                       (void *) (v[i]->width));
+         
+         {
+           struct value_label *old;
+           
+           old = avl_replace (v[i]->val_lab, label);
+           if (old)
+             free_value_label (old);
+         }
+       }
+
+      lex_get ();
+    }
+  while (token != '/' && token != '.');
+
+  return 1;
+}
+
+#if DEBUGGING
+static void
+debug_print ()
+{
+  int i;
+
+  puts (_("Value labels:"));
+  for (i = 0; i < nvar; i++)
+    {
+      AVLtraverser *t = NULL;
+      struct value_label *val;
+
+      printf ("  %s\n", var[i]->name);
+      if (var[i]->val_lab)
+       if (var[i]->type == NUMERIC)
+         for (val = avltrav (var[i]->val_lab, &t);
+              val; val = avltrav (var[i]->val_lab, &t))
+           printf ("    %g:  `%s'\n", val->v.f, val->s);
+       else
+         for (val = avltrav (var[i]->val_lab, &t);
+              val; val = avltrav (var[i]->val_lab, &t))
+           printf ("    `%.8s':  `%s'\n", val->v.s, val->s);
+      else
+       printf (_("    (no value labels)\n"));
+    }
+}
+#endif /* DEBUGGING */
+
+/* Compares two value labels and returns a strcmp()-type result. */
+int
+val_lab_cmp (const void *a, const void *b, void *param)
+{
+  if ((int) param)
+    return strncmp (((struct value_label *) a)->v.s,
+                   ((struct value_label *) b)->v.s,
+                   (int) param);
+  else
+    {
+      int temp = (((struct value_label *) a)->v.f
+                 - ((struct value_label *) b)->v.f);
+      if (temp > 0)
+       return 1;
+      else if (temp < 0)
+       return -1;
+      else
+       return 0;
+    }
+}
+
+/* Callback function to increment the reference count for a value
+   label. */
+void *
+inc_ref_count (void *pv, void *param unused)
+{
+  ((struct value_label *) pv)->ref_count++;
+  return pv;
+}
+
+/* Copy the avl tree of value labels and return a pointer to the
+   copy. */
+avl_tree *
+copy_value_labels (avl_tree *src)
+{
+  avl_tree *dest;
+
+  if (src == NULL)
+    return NULL;
+  dest = avl_copy (NULL, src, inc_ref_count);
+
+  return dest;
+}
diff --git a/src/var-labs.c b/src/var-labs.c
new file mode 100644 (file)
index 0000000..b573a4e
--- /dev/null
@@ -0,0 +1,100 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "str.h"
+#include "var.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+#if DEBUGGING
+static void debug_print (void);
+#endif
+
+int
+cmd_variable_labels (void)
+{
+  struct variable **v;
+  int nv;
+
+  int i;
+
+  lex_match_id ("VARIABLE");
+  lex_match_id ("LABELS");
+  lex_match ('/');
+  do
+    {
+      parse_variables (NULL, &v, &nv, PV_NONE);
+
+      if (token != T_STRING)
+       {
+         msg (SE, _("String expected for variable label."));
+         free (v);
+         return CMD_PART_SUCCESS_MAYBE;
+       }
+      if (ds_length (&tokstr) > 120)
+       {
+         msg (SW, _("Truncating variable label to 120 characters."));
+         ds_truncate (&tokstr, 120);
+       }
+      for (i = 0; i < nv; i++)
+       {
+         if (v[i]->label)
+           free (v[i]->label);
+         v[i]->label = xstrdup (ds_value (&tokstr));
+       }
+
+      lex_get ();
+      while (token == '/')
+       lex_get ();
+      free (v);
+    }
+  while (token != '.');
+#if DEBUGGING
+  debug_print ();
+#endif
+  return CMD_SUCCESS;
+}
+
+#if DEBUGGING
+static void
+debug_print (void)
+{
+  int i;
+
+  printf (_("Variable labels:\n"));
+  for (i = 0; i < nvar; i++)
+    {
+      printf ("  %8s: ", var[i]->name);
+      if (var[i]->label)
+       printf ("`%s'", var[i]->label);
+      else
+       printf (_("(no variable label)"));
+      printf ("\n");
+    }
+}
+#endif /* DEBUGGING */
diff --git a/src/var.h b/src/var.h
new file mode 100644 (file)
index 0000000..38f0d74
--- /dev/null
+++ b/src/var.h
@@ -0,0 +1,535 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !var_h
+#define var_h 1
+
+#include "format.h"
+
+/* Values. */
+
+/* Definition of the max length of a short string value, generally
+   eight characters.  */
+#define MAX_SHORT_STRING ((SIZEOF_DOUBLE)>=8 ? ((SIZEOF_DOUBLE)+1)/2*2 : 8)
+#define MIN_LONG_STRING (MAX_SHORT_STRING+1)
+
+/* FYI: It is a bad situation if sizeof(flt64) < MAX_SHORT_STRING:
+   then short string missing values can be truncated in system files
+   because there's only room for as many characters as can fit in a
+   flt64. */
+#if MAX_SHORT_STRING > 8
+#error MAX_SHORT_STRING must be less than 8.
+#endif
+
+/* Special values. */
+#define SYSMIS (-DBL_MAX)
+#define LOWEST second_lowest_value
+#define HIGHEST DBL_MAX
+
+/* Describes one value, which is either a floating-point number or a
+   short string. */
+union value
+  {
+    /* A numeric value. */
+    double f;
+
+    /* A short-string value. */
+    unsigned char s[MAX_SHORT_STRING];
+
+    /* This member is used by data-in.c to return a string result,
+       since it may need to return a long string.  As currently
+       implemented, it's a pointer to a static internal buffer in
+       data-in.c.
+
+       Also used by evaluate_expression() to return a string result.
+       As currently implemented, it's a pointer to a dynamic buffer in
+       the appropriate expression.
+
+       Also used by the AGGREGATE procedure in handling string
+       values. */
+    unsigned char *c;
+
+    /* Sometimes we insert value's in a hash table. */
+    unsigned long hash[SIZEOF_DOUBLE / SIZEOF_LONG];
+  };
+
+/* Describes one value label. */
+struct value_label
+  {
+    union value v;             /* The value being labeled. */
+    char *s;                   /* Pointer to malloc()'d label. */
+    int ref_count;             /* Reference count. */
+  };
+\f
+/* Frequency tables. */
+
+/* Frequency table entry. */
+struct freq
+  {
+    union value v;             /* The value. */
+    double c;                  /* The number of occurrences of the value. */
+  };
+
+/* Types of frequency tables. */
+enum
+  {
+    FRQM_GENERAL,
+    FRQM_INTEGER
+  };
+
+/* Entire frequency table. */
+struct freq_tab
+  {
+    int mode;                  /* FRQM_GENERAL or FRQM_INTEGER. */
+
+    /* General mode. */
+    struct avl_tree *tree;     /* Undifferentiated data. */
+
+    /* Integer mode. */
+    double *vector;            /* Frequencies proper. */
+    int min, max;              /* The boundaries of the table. */
+    double out_of_range;       /* Sum of weights of out-of-range values. */
+    double sysmis;             /* Sum of weights of SYSMIS values. */
+
+    /* All modes. */
+    struct freq *valid;                /* Valid freqs. */
+    int n_valid;               /* Number of total freqs. */
+
+    struct freq *missing;      /* Missing freqs. */
+    int n_missing;             /* Number of missing freqs. */
+
+    /* Statistics. */
+    double total_cases;                /* Sum of weights of all cases. */
+    double valid_cases;                /* Sum of weights of valid cases. */
+  };
+
+/* A complete set of 3 frequency tables. */
+struct freq_tab_set
+  {
+    struct freq_tab miss;      /* Includes user-missing values. */
+    struct freq_tab no_miss;   /* Excludes user-missing values. */
+    struct freq_tab sel;       /* Identical to either miss or no_miss. */
+  };
+\f
+/* Procedures' private per-variable data. */
+
+/* Structure name suffixes for private data:
+   _proc: for a procedure (i.e., LIST -> list_proc).
+   _trns: for a transformation (i.e., COMPUTE -> compute_trns.
+   _pgm: for an input program (i.e., DATA LIST -> data_list_pgm). */
+
+/* CROSSTABS private data. */
+struct crosstab_proc
+  {
+    /* Integer mode only. */
+    int min;                   /* Minimum value. */
+    int max;                   /* Maximum value + 1. */
+    int count;                 /* max - min. */
+  };
+
+/* FREQUENCIES private data. */
+enum
+  {
+    frq_mean = 0, frq_semean, frq_median, frq_mode, frq_stddev, frq_variance,
+    frq_kurt, frq_sekurt, frq_skew, frq_seskew, frq_range, frq_min, frq_max,
+    frq_sum, frq_n_stats
+  };
+
+struct frequencies_proc
+  {
+    /* General mode. */
+    struct freq_tab tab;       /* Frequencies table to use. */
+
+    /* Percentiles. */
+    int n_groups;              /* Number of groups. */
+    double *groups;            /* Groups. */
+
+    /* Statistics. */
+    double stat[frq_n_stats];
+  };
+
+/* LIST private data. */
+struct list_proc
+  {
+    int newline;               /* Whether a new line begins here. */
+    int width;                 /* Field width. */
+    int vert;                  /* Whether to print the varname vertically. */
+  };
+
+/* DESCRIPTIVES private data.  Note that the DESCRIPTIVES procedure also
+   has a transformation, descriptives_trns. */
+enum
+  {
+    /* As these are used as bit indexes, there must be 32 or fewer.
+       Be very careful in adjusting these, see the structure below
+       and the table in descriptives.q. */
+    dsc_mean = 0, dsc_semean, dsc_stddev, dsc_variance, dsc_kurt,
+    dsc_sekurt, dsc_skew, dsc_seskew, dsc_range, dsc_min,
+    dsc_max, dsc_sum, dsc_n_stats
+  };
+
+struct descriptives_proc
+  {
+    /* Miscellaneous. */
+    int dup;                   /* Finds duplicates in list of
+                                  variables. */
+    char zname[10];            /* Name for z-score variable. */
+
+    /* Counts. */
+    double valid, miss;                /* Valid, missing--general. */
+
+    /* Mean, moments about the mean. */
+    double X_bar, M2, M3, M4;
+    double min, max;
+
+    /* Statistics. */
+    double stats[dsc_n_stats]; /* Everything glommed together. */
+  };
+
+/* GET private data. */
+struct get_proc
+  {
+    int fv, nv;                        /* First, last, # of values. */
+  };
+
+/* Sort order. */
+enum
+  {
+    SRT_ASCEND,                        /* A, B, C, ..., X, Y, Z. */
+    SRT_DESCEND                        /* Z, Y, X, ..., C, B, A. */
+  };
+
+/* SORT CASES private data. */
+struct sort_cases_proc
+  {
+    int order;                 /* SRT_ASCEND or SRT_DESCEND. */
+  };
+
+/* MODIFY VARS private data. */
+struct modify_vars_proc
+  {
+    char new_name[9];          /* Variable's new name. */
+    int drop_this_var;         /* 0=keep this var, 1=drop this var. */
+    struct variable *next;     /* Next in linked list. */
+  };
+
+/* MEANS private data. */
+struct means_proc
+  {
+    double min, max;           /* Range for integer mode. */
+  };
+
+/* Different types of variables for MATRIX DATA procedure.  Order is
+   important: these are used for sort keys. */
+enum
+  {
+    MXD_SPLIT,                 /* SPLIT FILE variables. */
+    MXD_ROWTYPE,               /* ROWTYPE_. */
+    MXD_FACTOR,                        /* Factor variables. */
+    MXD_VARNAME,               /* VARNAME_. */
+    MXD_CONTINUOUS,            /* Continuous variables. */
+
+    MXD_COUNT
+  };
+
+/* MATRIX DATA private data. */
+struct matrix_data_proc
+  {
+    int vartype;               /* Variable type. */
+    int subtype;               /* Subtype. */
+  };
+
+/* MATCH FILES private data. */
+struct match_files_proc
+  {
+    struct variable *master;   /* Corresponding master file variable. */
+  };
+
+\f
+/* Script variables. */
+
+/* Variable type. */
+enum
+  {
+    NUMERIC,                   /* A numeric variable. */
+    ALPHA                      /* A string variable.  (STRING is pre-empted by lexer.h) */
+  };
+
+/* Types of missing values.  Order is significant, see
+   mis-val.c:parse_numeric(), sfm-read.c:sfm_read_dictionary()
+   sfm-write.c:sfm_write_dictionary(),
+   sysfile-info.c:cmd_sysfile_info(), mis-val.c:copy_missing_values(),
+   pfm-read.c:read_variables(), pfm-write.c:write_variables(),
+   apply-dict.c:cmd_apply_dictionary(), and more (?). */
+enum
+  {
+    MISSING_NONE,              /* No user-missing values. */
+    MISSING_1,                 /* One user-missing value. */
+    MISSING_2,                 /* Two user-missing values. */
+    MISSING_3,                 /* Three user-missing values. */
+    MISSING_RANGE,             /* [a,b]. */
+    MISSING_LOW,               /* (-inf,a]. */
+    MISSING_HIGH,              /* (a,+inf]. */
+    MISSING_RANGE_1,           /* [a,b], c. */
+    MISSING_LOW_1,             /* (-inf,a], b. */
+    MISSING_HIGH_1,            /* (a,+inf), b. */
+    MISSING_COUNT
+  };
+
+/* A variable's dictionary entry.  Note: don't reorder name[] from the
+   first element; a pointer to `variable' should be a pointer to
+   member `name'.*/
+struct variable
+  {
+    /* Required by parse_variables() to be in this order.  */
+    char name[9];              /* As a string. */
+    int index;                 /* Index into its dictionary's var[]. */
+    int type;                  /* NUMERIC or ALPHA. */
+    int foo;                   /* Used for temporary storage. */
+
+    /* Also important but parse_variables() doesn't need it.  Still,
+       check before reordering. */
+    int width;                 /* Size of string variables in chars. */
+    int fv, nv;                        /* Index into `value's, number of values. */
+    int left;                  /* 0=do not LEAVE, 1=LEAVE. */
+
+    /* Missing values. */
+    int miss_type;             /* One of the MISSING_* constants. */
+    union value missing[3];    /* User-missing value. */
+
+    /* Display formats. */
+    struct fmt_spec print;     /* Default format for PRINT. */
+    struct fmt_spec write;     /* Default format for WRITE. */
+
+    /* Labels. */
+    struct avl_tree *val_lab;  /* Avltree of value_label structures. */
+    char *label;               /* Variable label. */
+
+    /* Per-procedure info. */
+    struct get_proc get;
+    union
+      {
+       struct crosstab_proc crs;
+       struct descriptives_proc dsc;
+       struct frequencies_proc frq;
+       struct list_proc lst;
+       struct means_proc mns;
+       struct sort_cases_proc srt;
+       struct modify_vars_proc mfv;
+       struct matrix_data_proc mxd;
+       struct match_files_proc mtf;
+      }
+    p;
+  };
+\f
+/* Cases. */
+
+/* A single case.  (This doesn't need to be a struct anymore, but it
+   remains so for hysterical raisins.) */
+struct ccase
+  {
+    union value data[1];
+  };
+\f
+/* Dictionary. */ 
+
+/* Complete dictionary state. */
+struct dictionary
+  {
+    struct variable **var;     /* Variable descriptions. */
+    struct avl_tree *var_by_name;      /* Variables arranged by name. */
+    int nvar;                  /* Number of variables. */
+
+    int N;                     /* Current case limit (N command). */
+    int nval;                  /* Number of value structures per case. */
+
+    int n_splits;              /* Number of SPLIT FILE variables. */
+    struct variable **splits;  /* List of SPLIT FILE vars. */
+    
+    char *label;               /* File label. */
+
+    int n_documents;           /* Number of lines of documents. */
+    char *documents;           /* Documents; 80*n_documents bytes in size. */
+
+    int weight_index;          /* `value' index of $WEIGHT, or -1 if none.
+                                  Call update_weighting() before using! */
+    char weight_var[9];                /* Name of WEIGHT variable. */
+
+    char filter_var[9];                /* Name of FILTER variable. */
+    /* Do not make another field the last field! or see
+       temporary.c:restore_dictionary() before doing so! */
+  };
+
+/* This is the active file dictionary. */
+extern struct dictionary default_dict;
+\f
+/* Transformation state. */
+
+/* Default file handle for DATA LIST, REREAD, REPEATING DATA
+   commands. */
+extern struct file_handle *default_handle;
+
+/* PROCESS IF expression. */
+extern struct expression *process_if_expr;
+\f
+/* TEMPORARY support. */
+
+/* 1=TEMPORARY has been executed at some point. */
+extern int temporary;
+
+/* If temporary!=0, the saved dictionary. */
+extern struct dictionary *temp_dict;
+
+/* If temporary!=0, index into t_trns[] (declared far below) that
+   gives the point at which data should be written out.  -1 means that
+   the data shouldn't be changed since all transformations are
+   temporary. */
+extern int temp_trns;
+
+/* If FILTER is active, whether it was executed before or after
+   TEMPORARY. */
+extern int FILTER_before_TEMPORARY;
+
+void cancel_temporary (void);
+\f
+/* Functions. */
+
+int is_varname (const char *);
+int is_dict_varname (const struct dictionary *, const char *);
+
+/* Flags for passing to fill_all_vars(). */
+enum
+  {
+    FV_NONE = 0,               /* No flags. */
+    FV_NO_SYSTEM = 001,                /* Don't include system variables. */
+    FV_NO_SCRATCH = 002                /* Don't include scratch variables. */
+  };
+
+void fill_all_vars (struct variable ***, int *, int flags);
+
+int val_lab_cmp (const void *, const void *, void *);
+char *get_val_lab (const struct variable *, union value, int);
+void free_val_lab (void *, void *);
+void free_value_label (struct value_label *);
+struct avl_tree *copy_value_labels (struct avl_tree *);
+
+void dump_split_vars (const struct ccase *);
+
+int is_num_user_missing (double, const struct variable *);
+int is_str_user_missing (const unsigned char[], const struct variable *);
+int is_missing (const union value *, const struct variable *);
+int is_system_missing (const union value *, const struct variable *);
+int is_user_missing (const union value *, const struct variable *);
+void copy_missing_values (struct variable *dest, const struct variable *src);
+
+int cmp_variable (const void *, const void *, void *);
+
+#if GLOBAL_DEBUGGING
+struct variable *force_create_variable (struct dictionary *, const char *name,
+                                       int type, int width);
+struct variable *force_dup_variable (struct dictionary *,
+                                    const struct variable *src,
+                                    const char *name);
+#else
+#define force_create_variable(A, B, C, D)      \
+       create_variable (A, B, C, D)
+#define force_dup_variable(A, B, C)            \
+       dup_variable (A, B, C)
+#endif
+
+struct variable *create_variable (struct dictionary *, const char *name,
+                                 int type, int width);
+void delete_variable (struct dictionary *, struct variable *v);
+struct variable *find_variable (const char *name);
+struct variable *find_dict_variable (const struct dictionary *,
+                                    const char *name);
+void init_variable (struct dictionary *, struct variable *, const char *name,
+                   int type, int width);
+void replace_variable (struct variable *, const char *name,
+                      int type, int width);
+void clear_variable (struct dictionary *, struct variable *);
+void rename_variable (struct dictionary *, struct variable *v,
+                     const char *new_name);
+void discard_variables (void);
+void clear_default_dict (void);
+void copy_variable (struct variable *dest, const struct variable *src);
+struct variable *dup_variable (struct dictionary *dict,
+                              const struct variable *src, const char *name);
+
+struct variable *update_weighting (struct dictionary *);
+void stop_weighting (struct dictionary *);
+
+struct dictionary *save_dictionary (void);
+void restore_dictionary (struct dictionary *);
+void free_dictionary (struct dictionary *);
+struct dictionary *new_dictionary (int copy);
+\f
+/* Transformations. */
+
+/* Header for all transformations. */
+struct trns_header
+  {
+    /* Index into t_trns[]. */
+    int index;
+
+    /* Transformation proc. */
+    int (*proc) (struct trns_header *, struct ccase *);
+
+    /* Garbage collector proc. */
+    void (*free) (struct trns_header *);
+  };
+
+/* Array of transformations */
+extern struct trns_header **t_trns;
+
+/* Number of transformations, maximum number in array currently. */
+extern int n_trns, m_trns;
+
+/* Index of first transformation that is really a transformation.  Any
+   transformations before this belong to INPUT PROGRAM. */
+extern int f_trns;
+
+void add_transformation (struct trns_header *trns);
+void cancel_transformations (void);
+\f
+/* Variable parsers. */
+
+/* Only parse_variables() supports options other than PV_APPEND,
+   PV_SINGLE. */
+enum
+  {
+    PV_NONE = 0,               /* No options. */
+    PV_SINGLE = 0001,          /* Restrict to a single varname or TO use. */
+    PV_DUPLICATE = 0002,       /* Don't merge duplicates. */
+    PV_APPEND = 0004,          /* Append to existing list. */
+    PV_NO_DUPLICATE = 0010,    /* Error on duplicates. */
+    PV_NUMERIC = 0020,         /* Vars must be numeric. */
+    PV_STRING = 0040,          /* Vars must be string. */
+    PV_SAME_TYPE = 00100,      /* All vars must be the same type. */
+    PV_NO_SCRATCH = 00200      /* Disallow scratch variables. */
+  };
+
+struct variable *parse_variable (void);
+struct variable *parse_dict_variable (struct dictionary *);
+int parse_variables (struct dictionary *dict, struct variable ***v,
+                    int *nv, int pv_opts);
+int parse_DATA_LIST_vars (char ***names, int *nnames, int pv_opts);
+int parse_mixed_vars (char ***names, int *nnames, int pv_opts);
+
+#endif /* !var_h */
diff --git a/src/vars-atr.c b/src/vars-atr.c
new file mode 100644 (file)
index 0000000..3f4ea7f
--- /dev/null
@@ -0,0 +1,570 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "approx.h"
+#include "avl.h"
+#include "command.h"
+#include "do-ifP.h"
+#include "expr.h"
+#include "file-handle.h"
+#include "inpt-pgm.h"
+#include "misc.h"
+#include "str.h"
+#include "var.h"
+#include "vector.h"
+#include "vfm.h"
+
+#undef DEBUGGING
+/*#define DEBUGGING 1*/
+#include "debug-print.h"
+
+#if DEBUGGING
+/* Dumps one variable to standard output. */
+void
+dump_one_var_node (void * pnode, void *param, int level)
+{
+  variable *node = pnode;
+  int i;
+
+  for (i = 0; i < level - 1; i++)
+    printf ("   ");
+  if (node == NULL)
+    printf ("NULL_TREE\n");
+  else
+    printf ("%p=>%s\n", node, node->name ? node->name : "<null>");
+}
+
+/* Dumps a tree of the variables to standard output. */
+void
+dump_var_tree (void)
+{
+  printf (_("Vartree:\n"));
+  avl_walk_inorder (default_dict.var_by_name, dump_one_var_node, NULL);
+}
+#endif
+
+/* Clear the default dictionary.  Note: This is probably not what you
+   want to do.  Use discard_variables() instead. */
+void
+clear_default_dict (void)
+{
+  int i;
+  
+  for (i = 0; i < default_dict.nvar; i++)
+    {
+      clear_variable (&default_dict, default_dict.var[i]);
+      free (default_dict.var[i]);
+    }
+
+  assert (default_dict.splits == NULL);
+
+  default_dict.nvar = 0;
+  default_dict.N = 0;
+  default_dict.nval = 0;
+  default_handle = inline_file;
+  stop_weighting (&default_dict);
+}
+
+/* Discards all the current state in preparation for a data-input
+   command like DATA LIST or GET. */
+void
+discard_variables (void)
+{
+  clear_default_dict ();
+  
+  n_lag = 0;
+  
+  if (vfm_source)
+    {
+      vfm_source->destroy_source ();
+      vfm_source = NULL;
+    }
+
+  cancel_transformations ();
+
+  ctl_stack = NULL;
+
+  free (vec);
+  vec = NULL;
+  nvec = 0;
+
+  expr_free (process_if_expr);
+  process_if_expr = NULL;
+
+  cancel_temporary ();
+
+  pgm_state = STATE_INIT;
+}
+
+/* Find and return the variable in default_dict having name NAME, or
+   NULL if no such variable exists in default_dict. */
+struct variable *
+find_variable (const char *name)
+{
+  return avl_find (default_dict.var_by_name, (struct variable *) name);
+}
+
+/* Find and return the variable in dictionary D having name NAME, or
+   NULL if no such variable exists in D. */
+struct variable *
+find_dict_variable (const struct dictionary *d, const char *name)
+{
+  return avl_find (d->var_by_name, (struct variable *) name);
+}
+
+/* Creates a variable named NAME in dictionary DICT having type TYPE
+   (ALPHA or NUMERIC) and, if type==ALPHA, width WIDTH.  Returns a
+   pointer to the newly created variable if successful.  On failure
+   (which indicates that a variable having the specified name already
+   exists), returns NULL.  */
+struct variable *
+create_variable (struct dictionary *dict, const char *name,
+                int type, int width)
+{
+  if (find_dict_variable (dict, name))
+    return NULL;
+  
+  {
+    struct variable *new_var;
+    
+    dict->var = xrealloc (dict->var, (dict->nvar + 1) * sizeof *dict->var);
+    new_var = dict->var[dict->nvar] = xmalloc (sizeof *new_var);
+    
+    new_var->index = dict->nvar;
+    dict->nvar++;
+    
+    init_variable (dict, new_var, name, type, width);
+    
+    return new_var;
+  }
+}
+
+#if GLOBAL_DEBUGGING
+/* For situations in which we know that there are no variables with an
+   identical name in the dictionary. */
+struct variable *
+force_create_variable (struct dictionary *dict, const char *name,
+                      int type, int width)
+{
+  struct variable *new_var = create_variable (dict, name, type, width);
+  assert (new_var != NULL);
+  return new_var;
+}
+
+/* For situations in which we know that there are no variables with an
+   identical name in the dictionary. */
+struct variable *
+force_dup_variable (struct dictionary *dict, const struct variable *src,
+                   const char *name)
+{
+  struct variable *new_var = dup_variable (dict, src, name);
+  assert (new_var != NULL);
+  return new_var;
+}
+#endif
+                                
+/* Delete variable V from DICT.  It should only be used when there are
+   guaranteed to be absolutely NO REFERENCES to it, for instance in
+   the very same function that created it. */
+void
+delete_variable (struct dictionary *dict, struct variable *v)
+{
+  int i;
+
+  clear_variable (dict, v);
+  dict->nvar--;
+  for (i = v->index; i < dict->nvar; i++)
+    {
+      dict->var[i] = dict->var[i + 1];
+      dict->var[i]->index = i;
+    }
+  free (v);
+}
+
+/* Initialize fields in variable V inside dictionary D with name NAME,
+   type TYPE, and width WIDTH.  Initializes some other fields too. */
+static inline void
+common_init_stuff (struct dictionary *dict, struct variable *v,
+                  const char *name, int type, int width)
+{
+  if (v->name != name)
+    /* Avoid problems with overlap. */
+    strcpy (v->name, name);
+
+  avl_force_insert (dict->var_by_name, v);
+
+  v->type = type;
+  v->left = name[0] == '#';
+  v->width = type == NUMERIC ? 0 : width;
+  v->miss_type = MISSING_NONE;
+  if (v->type == NUMERIC)
+    {
+      v->print.type = FMT_F;
+      v->print.w = 8;
+      v->print.d = 2;
+    }
+  else
+    {
+      v->print.type = FMT_A;
+      v->print.w = v->width;
+      v->print.d = 0;
+    }
+  v->write = v->print;
+}
+
+/* Initialize (for the first time) a variable V in dictionary DICT
+   with name NAME, type TYPE, and width WIDTH.  */
+void
+init_variable (struct dictionary *dict, struct variable *v, const char *name,
+              int type, int width)
+{
+  common_init_stuff (dict, v, name, type, width);
+  v->nv = type == NUMERIC ? 1 : DIV_RND_UP (width, 8);
+  v->fv = dict->nval;
+  dict->nval += v->nv;
+  v->label = NULL;
+  v->val_lab = NULL;
+  v->get.fv = -1;
+
+  if (vfm_source == &input_program_source
+      || vfm_source == &file_type_source)
+    {
+      size_t nbytes = DIV_RND_UP (v->fv + 1, 4);
+      unsigned val = 0;
+
+      if (inp_init_size < nbytes)
+       {
+         inp_init = xrealloc (inp_init, nbytes);
+         memset (&inp_init[inp_init_size], 0, nbytes - inp_init_size);
+         inp_init_size = nbytes;
+       }
+
+      if (v->type == ALPHA)
+       val |= INP_STRING;
+      if (v->left)
+       val |= INP_LEFT;
+      inp_init[v->fv / 4] |= val << ((unsigned) (v->fv) % 4 * 2);
+    }
+}
+
+/* Replace variable V in default_dict with a different variable having
+   name NAME, type TYPE, and width WIDTH. */
+void
+replace_variable (struct variable *v, const char *name, int type, int width)
+{
+  int nv;
+
+  assert (v && name && (type == NUMERIC || type == ALPHA) && width >= 0
+         && (type == ALPHA || width == 0));
+  clear_variable (&default_dict, v);
+  common_init_stuff (&default_dict, v, name, type, width);
+
+  nv = (type == NUMERIC) ? 1 : DIV_RND_UP (width, 8);
+  if (nv > v->nv)
+    {
+      v->fv = v->nv = 0;
+      v->fv = default_dict.nval;
+      default_dict.nval += nv;
+    }
+  v->nv = nv;
+}
+
+/* Changes the name of variable V in dictionary DICT to name NEW_NAME.
+   NEW_NAME must be known not to already exist in dictionary DICT. */
+void
+rename_variable (struct dictionary * dict, struct variable *v,
+                const char *new_name)
+{
+  assert (dict && dict->var_by_name && v && new_name);
+  avl_delete (dict->var_by_name, v);
+  strncpy (v->name, new_name, 9);
+  avl_force_insert (dict->var_by_name, v);
+}
+
+/* Delete the contents of variable V within dictionary DICT.  Does not
+   remove the variable from the vector of variables in the dictionary.
+   Use with caution. */
+void
+clear_variable (struct dictionary *dict, struct variable *v)
+{
+  assert (dict && v);
+  
+#if DEBUGGING
+  printf (_("clearing variable %d:%s %s\n"), v->index, v->name,
+         (dict == &default_dict ? _("in default dictionary")
+          : _("in auxiliary dictionary")));
+  if (dict->var_by_name != NULL)
+    dump_var_tree ();
+#endif
+  
+  if (dict->var_by_name != NULL)
+    avl_force_delete (dict->var_by_name, v);
+  
+  if (v->val_lab)
+    {
+      avl_destroy (v->val_lab, free_val_lab);
+      v->val_lab = NULL;
+    }
+  
+  if (v->label)
+    {
+      free (v->label);
+      v->label = NULL;
+    }
+
+  if (dict->splits)
+    {
+      struct variable **iter, **trailer;
+
+      for (trailer = iter = dict->splits; *iter; iter++)
+       if (*iter != v)
+         *trailer++ = *iter;
+       else
+         dict->n_splits--;
+
+      *trailer = NULL;
+      
+      if (dict->n_splits == 0)
+       {
+         free (dict->splits);
+         dict->splits = NULL;
+       }
+    }
+         
+#if DEBUGGING
+  if (dict->var_by_name != NULL)
+    dump_var_tree ();
+#endif
+}
+
+/* Creates a new variable in dictionary DICT, whose properties are
+   copied from variable SRC, and returns a pointer to the new variable
+   of name NAME, if successful.  If unsuccessful (which only happens
+   if a variable of the same name NAME exists in DICT), returns
+   NULL. */
+struct variable *
+dup_variable (struct dictionary *dict, const struct variable *src,
+             const char *name)
+{
+  if (find_dict_variable (dict, name))
+    return NULL;
+  
+  {
+    struct variable *new_var;
+    
+    dict->var = xrealloc (dict->var, (dict->nvar + 1) * sizeof *dict->var);
+    new_var = dict->var[dict->nvar] = xmalloc (sizeof *new_var);
+
+    new_var->index = dict->nvar;
+    new_var->foo = -1;
+    new_var->get.fv = -1;
+    new_var->get.nv = -1;
+    dict->nvar++;
+    
+    copy_variable (new_var, src);
+
+    assert (new_var->nv >= 0);
+    new_var->fv = dict->nval;
+    dict->nval += new_var->nv;
+
+    strcpy (new_var->name, name);
+    avl_force_insert (dict->var_by_name, new_var);
+
+    return new_var;
+  }
+}
+
+   
+/* Decrements the reference count for value label V.  Destroys the
+   value label if the reference count reaches zero. */
+void
+free_value_label (struct value_label * v)
+{
+  assert (v->ref_count >= 1);
+  if (--v->ref_count == 0)
+    {
+      free (v->s);
+      free (v);
+    }
+}
+
+/* Frees value label P.  PARAM is ignored.  Used as a callback with
+   avl_destroy(). */
+void
+free_val_lab (void *p, void *param unused)
+{
+  free_value_label ((struct value_label *) p);
+}
+
+/* Returns a value label corresponding to VAL in variable V padded to
+   length N.  If N==0 then no padding is performed, and NULL is
+   returned if no label exists.  (Normally a string of spaces is
+   returned in this case.) */
+char *
+get_val_lab (const struct variable *v, union value val, int n)
+{
+  static char *buf;
+  static int bufsize;
+  struct value_label template, *find;
+
+  if (bufsize < n)
+    {
+      buf = xrealloc (buf, n + 1);
+      bufsize = n;
+    }
+  if (n)
+    buf[0] = 0;
+  template.v = val;
+  find = NULL;
+  if (v->val_lab)
+    find = avl_find (v->val_lab, &template);
+  if (find)
+    {
+      if (n)
+       {
+         st_pad_copy (buf, find->s, n + 1);
+         return buf;
+       }
+      else
+       return find->s;
+    }
+  else
+    {
+      if (n)
+       {
+         memset (buf, ' ', n);
+         buf[n] = '\0';
+         return buf;
+       }
+      else
+       return NULL;
+    }
+}
+
+/* Return nonzero only if X is a user-missing value for numeric
+   variable V. */
+inline int
+is_num_user_missing (double x, const struct variable *v)
+{
+  switch (v->miss_type)
+    {
+    case MISSING_NONE:
+      return 0;
+    case MISSING_1:
+      return approx_eq (x, v->missing[0].f);
+    case MISSING_2:
+      return (approx_eq (x, v->missing[0].f)
+             || approx_eq (x, v->missing[1].f));
+    case MISSING_3:
+      return (approx_eq (x, v->missing[0].f)
+             || approx_eq (x, v->missing[1].f)
+             || approx_eq (x, v->missing[2].f));
+    case MISSING_RANGE:
+      return (approx_ge (x, v->missing[0].f)
+             && approx_le (x, v->missing[1].f));
+    case MISSING_LOW:
+      return approx_le (x, v->missing[0].f);
+    case MISSING_HIGH:
+      return approx_ge (x, v->missing[0].f);
+    case MISSING_RANGE_1:
+      return ((approx_ge (x, v->missing[0].f)
+              && approx_le (x, v->missing[1].f))
+             || approx_eq (x, v->missing[2].f));
+    case MISSING_LOW_1:
+      return (approx_le (x, v->missing[0].f)
+             || approx_eq (x, v->missing[1].f));
+    case MISSING_HIGH_1:
+      return (approx_ge (x, v->missing[0].f)
+             || approx_eq (x, v->missing[1].f));
+    default:
+      assert (0);
+    }
+  abort ();
+}
+
+/* Return nonzero only if string S is a user-missing variable for
+   string variable V. */
+inline int
+is_str_user_missing (const unsigned char s[], const struct variable *v)
+{
+  switch (v->miss_type)
+    {
+    case MISSING_NONE:
+      return 0;
+    case MISSING_1:
+      return !strncmp (s, v->missing[0].s, v->width);
+    case MISSING_2:
+      return (!strncmp (s, v->missing[0].s, v->width)
+             || !strncmp (s, v->missing[1].s, v->width));
+    case MISSING_3:
+      return (!strncmp (s, v->missing[0].s, v->width)
+             || !strncmp (s, v->missing[1].s, v->width)
+             || !strncmp (s, v->missing[2].s, v->width));
+    default:
+      assert (0);
+    }
+  abort ();
+}
+
+/* Return nonzero only if value VAL is system-missing for variable
+   V. */
+int
+is_system_missing (const union value *val, const struct variable *v)
+{
+  return v->type == NUMERIC && val->f == SYSMIS;
+}
+
+/* Return nonzero only if value VAL is system- or user-missing for
+   variable V. */
+int
+is_missing (const union value *val, const struct variable *v)
+{
+  switch (v->type)
+    {
+    case NUMERIC:
+      if (val->f == SYSMIS)
+       return 1;
+      return is_num_user_missing (val->f, v);
+    case ALPHA:
+      return is_str_user_missing (val->s, v);
+    default:
+      assert (0);
+    }
+  abort ();
+}
+
+/* Return nonzero only if value VAL is user-missing for variable V. */
+int
+is_user_missing (const union value *val, const struct variable *v)
+{
+  switch (v->type)
+    {
+    case NUMERIC:
+      return is_num_user_missing (val->f, v);
+    case ALPHA:
+      return is_str_user_missing (val->s, v);
+    default:
+      assert (0);
+    }
+  abort ();
+}
diff --git a/src/vars-prs.c b/src/vars-prs.c
new file mode 100644 (file)
index 0000000..f12ebf4
--- /dev/null
@@ -0,0 +1,529 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "avl.h"
+#include "bitvector.h"
+#include "error.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+#include "var.h"
+
+/* Allocates an array at *V to contain all the variables in
+   default_dict.  If FV_NO_SYSTEM is set in FLAGS then system
+   variables will not be included.  If FV_NO_SCRATCH is set in FLAGS
+   then scratch variables will not be included.  *C is set to the
+   number of variables in *V. */
+void
+fill_all_vars (struct variable ***varlist, int *c, int flags)
+{
+  int i;
+
+  *varlist = xmalloc (default_dict.nvar * sizeof **varlist);
+  if (flags == FV_NONE)
+    {
+      *c = default_dict.nvar;
+      for (i = 0; i < default_dict.nvar; i++)
+       (*varlist)[i] = default_dict.var[i];
+    }
+  else
+    {
+      *c = 0;
+      
+      for (i = 0; i < default_dict.nvar; i++)
+       {
+         struct variable *v = default_dict.var[i];
+
+         if ((flags & FV_NO_SYSTEM) && v->name[0] == '$')
+           continue;
+         if ((flags & FV_NO_SCRATCH) && v->name[0] == '#')
+           continue;
+         
+         (*varlist)[*c] = v;
+         (*c)++;
+       }
+      
+      if (*c != default_dict.nvar)
+       *varlist = xrealloc (*varlist, *c * sizeof **varlist);
+    }
+}
+
+int
+is_varname (const char *s)
+{
+  return avl_find (default_dict.var_by_name, (struct variable *) s) != 0;
+}
+
+int
+is_dict_varname (const struct dictionary *dict, const char *s)
+{
+  return avl_find (dict->var_by_name, (struct variable *) s) != 0;
+}
+
+struct variable *
+parse_variable (void)
+{
+  struct variable *vp;
+
+  if (token != T_ID)
+    {
+      lex_error ("expecting variable name");
+      return NULL;
+    }
+  vp = find_variable (tokid);
+  if (!vp)
+    msg (SE, _("%s is not declared as a variable."), tokid);
+  lex_get ();
+  return vp;
+}
+
+struct variable *
+parse_dict_variable (struct dictionary * dict)
+{
+  struct variable *vp;
+
+  if (token != T_ID)
+    {
+      lex_error ("expecting variable name");
+      return NULL;
+    }
+
+  vp = avl_find (dict->var_by_name, (struct variable *) tokid);
+  if (!vp)
+    msg (SE, _("%s is not a variable name."), tokid);
+  lex_get ();
+
+  return vp;
+}
+
+/* Returns the dictionary class of an identifier based on its
+   first letter: `X' if is an ordinary identifier, `$' if it
+   designates a system variable, `#' if it designates a scratch
+   variable. */
+#define id_dict(C)                                     \
+       ((C) == '$' ? '$' : ((C) == '#' ? '#' : 'X'))
+
+/* FIXME: One interesting variation in the case of PV_APPEND would be
+   to keep the bitmap, reducing time required to an actual O(n log n)
+   instead of having to reproduce the bitmap *every* *single* *time*.
+   Later though.  (Another idea would be to keep a marker bit in each
+   variable.) */
+/* Note that if parse_variables() returns 0, *v is free()'d.
+   Conversely, if parse_variables() returns non-zero, then *nv is
+   nonzero and *v is non-NULL. */
+int
+parse_variables (struct dictionary * dict, struct variable *** v, int *nv, int pv_opts)
+{
+  int i;
+  int nbytes;
+  unsigned char *bits;
+
+  struct variable *v1, *v2;
+  int count, mv;
+  int scratch;                 /* Dictionary we're reading from. */
+  int delayed_fail = 0;
+
+  if (dict == NULL)
+    dict = &default_dict;
+
+  if (!(pv_opts & PV_APPEND))
+    {
+      *v = NULL;
+      *nv = 0;
+      mv = 0;
+    }
+  else
+    mv = *nv;
+
+#if GLOBAL_DEBUGGING
+  {
+    int corrupt = 0;
+    int i;
+
+    for (i = 0; i < dict->nvar; i++)
+      if (dict->var[i]->index != i)
+       {
+         printf ("%s index corruption: variable %s\n",
+                 dict == &default_dict ? "default_dict" : "aux dict",
+                 dict->var[i]->name);
+         corrupt = 1;
+       }
+    
+    assert (!corrupt);
+  }
+#endif
+
+  nbytes = DIV_RND_UP (dict->nvar, 8);
+  if (!(pv_opts & PV_DUPLICATE))
+    {
+      bits = local_alloc (nbytes);
+      memset (bits, 0, nbytes);
+      for (i = 0; i < *nv; i++)
+       SET_BIT (bits, (*v)[i]->index);
+    }
+
+  do
+    {
+      if (lex_match (T_ALL))
+       {
+         v1 = dict->var[0];
+         v2 = dict->var[dict->nvar - 1];
+         count = dict->nvar;
+         scratch = id_dict ('X');
+       }
+      else
+       {
+         v1 = parse_dict_variable (dict);
+         if (!v1)
+           goto fail;
+
+         if (lex_match (T_TO))
+           {
+             v2 = parse_dict_variable (dict);
+             if (!v2)
+               {
+                 lex_error ("expecting variable name");
+                 goto fail;
+               }
+
+             count = v2->index - v1->index + 1;
+             if (count < 1)
+               {
+                 msg (SE, _("%s TO %s is not valid syntax since %s "
+                      "precedes %s in the dictionary."),
+                      v1->name, v2->name, v2->name, v1->name);
+                 goto fail;
+               }
+
+             scratch = id_dict (v1->name[0]);
+             if (scratch != id_dict (v2->name[0]))
+               {
+                 msg (SE, _("When using the TO keyword to specify several "
+                      "variables, both variables must be from "
+                      "the same variable dictionaries, of either "
+                      "ordinary, scratch, or system variables.  "
+                      "%s and %s are from different dictionaries."),
+                      v1->name, v2->name);
+                 goto fail;
+               }
+           }
+         else
+           {
+             v2 = v1;
+             count = 1;
+             scratch = id_dict (v1->name[0]);
+           }
+         if (scratch == id_dict ('#') && (pv_opts & PV_NO_SCRATCH))
+           {
+             msg (SE, _("Scratch variables (such as %s) are not allowed "
+                        "here."), v1->name);
+             goto fail;
+           }
+       }
+
+      if (*nv + count > mv)
+       {
+         mv += ROUND_UP (count, 16);
+         *v = xrealloc (*v, mv * sizeof **v);
+       }
+
+      for (i = v1->index; i <= v2->index; i++)
+       {
+         struct variable *add = dict->var[i];
+
+         /* Skip over other dictionaries. */
+         if (scratch != id_dict (add->name[0]))
+           continue;
+
+         if ((pv_opts & PV_NUMERIC) && add->type != NUMERIC)
+           {
+             delayed_fail = 1;
+             msg (SW, _("%s is not a numeric variable.  It will not be "
+                        "included in the variable list."), add->name);
+           }
+         else if ((pv_opts & PV_STRING) && add->type != ALPHA)
+           {
+             delayed_fail = 1;
+             msg (SE, _("%s is not a string variable.  It will not be "
+                        "included in the variable list."), add->name);
+           }
+         else if ((pv_opts & PV_SAME_TYPE) && *nv && add->type != (*v)[0]->type)
+           {
+             delayed_fail = 1;
+             msg (SE, _("%s and %s are not the same type.  All variables in "
+                        "this variable list must be of the same type.  %s "
+                        "will be omitted from list."),
+                  (*v)[0]->name, add->name, add->name);
+           }
+         else if ((pv_opts & PV_NO_DUPLICATE) && TEST_BIT (bits, add->index))
+           {
+             delayed_fail = 1;
+             msg (SE, _("Variable %s appears twice in variable list."),
+                  add->name);
+           }
+         else if ((pv_opts & PV_DUPLICATE) || !TEST_BIT (bits, add->index))
+           {
+             (*v)[(*nv)++] = dict->var[i];
+             if (!(pv_opts & PV_DUPLICATE))
+               SET_BIT (bits, add->index);
+           }
+       }
+
+      if (pv_opts & PV_SINGLE)
+       {
+         if (delayed_fail)
+           goto fail;
+         else
+           return 1;
+       }
+      lex_match (',');
+    }
+  while ((token == T_ID && is_dict_varname (dict, tokid)) || token == T_ALL);
+
+  if (!(pv_opts & PV_DUPLICATE))
+    local_free (bits);
+  if (!nv)
+    goto fail;
+  return 1;
+
+fail:
+  free (*v);
+  *v = NULL;
+  *nv = 0;
+  if (!(pv_opts & PV_DUPLICATE))
+    local_free (bits);
+  return 0;
+}
+
+static int
+extract_num (char *s, char *r, int *n, int *d)
+{
+  char *cp;
+
+  /* Find first digit. */
+  cp = s + strlen (s) - 1;
+  while (isdigit ((unsigned char) *cp) && cp > s)
+    cp--;
+  cp++;
+
+  /* Extract root. */
+  strncpy (r, s, cp - s);
+  r[cp - s] = 0;
+
+  /* Count initial zeros. */
+  *n = *d = 0;
+  while (*cp == '0')
+    {
+      (*d)++;
+      cp++;
+    }
+
+  /* Extract value. */
+  while (isdigit ((unsigned char) *cp))
+    {
+      (*d)++;
+      *n = (*n * 10) + (*cp - '0');
+      cp++;
+    }
+
+  /* Sanity check. */
+  if (*n == 0 && *d == 0)
+    {
+      msg (SE, _("incorrect use of TO convention"));
+      return 0;
+    }
+  return 1;
+}
+
+/* Parses a list of variable names according to the DATA LIST version
+   of the TO convention.  */
+int
+parse_DATA_LIST_vars (char ***names, int *nnames, int pv_opts)
+{
+  int n1, n2;
+  int d1, d2;
+  int n;
+  int nvar, mvar;
+  char *name1, *name2;
+  char *root1, *root2;
+  int success = 0;
+
+  if (pv_opts & PV_APPEND)
+    nvar = mvar = *nnames;
+  else
+    {
+      nvar = mvar = 0;
+      *names = NULL;
+    }
+
+  name1 = xmalloc (36);
+  name2 = &name1[1 * 9];
+  root1 = &name1[2 * 9];
+  root2 = &name1[3 * 9];
+  do
+    {
+      if (token != T_ID)
+       {
+         lex_error ("expecting variable name");
+         goto fail;
+       }
+      if (tokid[0] == '#' && (pv_opts & PV_NO_SCRATCH))
+       {
+         msg (SE, _("Scratch variables not allowed here."));
+         goto fail;
+       }
+      strcpy (name1, tokid);
+      lex_get ();
+      if (token == T_TO)
+       {
+         lex_get ();
+         if (token != T_ID)
+           {
+             lex_error ("expecting variable name");
+             goto fail;
+           }
+         strcpy (name2, tokid);
+         lex_get ();
+
+         if (!extract_num (name1, root1, &n1, &d1)
+             || !extract_num (name2, root2, &n2, &d2))
+           goto fail;
+
+         if (strcmp (root1, root2))
+           {
+             msg (SE, _("Prefixes don't match in use of TO convention."));
+             goto fail;
+           }
+         if (n1 > n2)
+           {
+             msg (SE, _("Bad bounds in use of TO convention."));
+             goto fail;
+           }
+         if (d2 > d1)
+           d2 = d1;
+
+         if (mvar < nvar + (n2 - n1 + 1))
+           {
+             mvar += ROUND_UP (n2 - n1 + 1, 16);
+             *names = xrealloc (*names, mvar * sizeof **names);
+           }
+
+         for (n = n1; n <= n2; n++)
+           {
+             (*names)[nvar] = xmalloc (9);
+             sprintf ((*names)[nvar], "%s%0*d", root1, d1, n);
+             nvar++;
+           }
+       }
+      else
+       {
+         if (nvar >= mvar)
+           {
+             mvar += 16;
+             *names = xrealloc (*names, mvar * sizeof **names);
+           }
+         (*names)[nvar++] = xstrdup (name1);
+       }
+
+      lex_match (',');
+
+      if (pv_opts & PV_SINGLE)
+       break;
+    }
+  while (token == T_ID);
+  success = 1;
+
+fail:
+  *nnames = nvar;
+  free (name1);
+  if (!success)
+    {
+      int i;
+      for (i = 0; i < nvar; i++)
+       free ((*names)[i]);
+      free (*names);
+      *names = NULL;
+      *nnames = 0;
+    }
+  return success;
+}
+
+/* Parses a list of variables where some of the variables may be
+   existing and the rest are to be created.  Same args as
+   parse_variables(). */
+int
+parse_mixed_vars (char ***names, int *nnames, int pv_opts)
+{
+  int i;
+
+  if (!(pv_opts & PV_APPEND))
+    {
+      *names = NULL;
+      *nnames = 0;
+    }
+  while (token == T_ID || token == T_ALL)
+    {
+      if (token == T_ALL || is_varname (tokid))
+       {
+         struct variable **v;
+         int nv;
+
+         if (!parse_variables (NULL, &v, &nv, PV_NONE))
+           goto fail;
+         *names = xrealloc (*names, (*nnames + nv) * sizeof **names);
+         for (i = 0; i < nv; i++)
+           (*names)[*nnames + i] = xstrdup (v[i]->name);
+         free (v);
+         *nnames += nv;
+       }
+      else if (!parse_DATA_LIST_vars (names, nnames, PV_APPEND))
+       goto fail;
+    }
+  return 1;
+
+fail:
+  for (i = 0; i < *nnames; i++)
+    free ((*names)[*nnames]);
+  free (names);
+  *names = NULL;
+  *nnames = 0;
+  return 0;
+}
diff --git a/src/vector.c b/src/vector.c
new file mode 100644 (file)
index 0000000..acaa078
--- /dev/null
@@ -0,0 +1,230 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "cases.h"
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+#include "var.h"
+#include "vector.h"
+
+/* Vectors created on VECTOR. */
+struct vector *vec;
+
+/* Number of vectors in vec. */
+int nvec;
+
+int
+cmd_vector (void)
+{
+  /* Just to be different, points to a set of null terminated strings
+     containing the names of the vectors to be created.  The list
+     itself is terminated by a empty string.  So a list of three
+     elements, A B C, would look like this: "A\0B\0C\0\0". */
+  char *vecnames;
+
+  /* vecnames iterators. */
+  char *cp, *cp2;
+
+  /* Maximum allocated position for vecnames, plus one position. */
+  char *endp = NULL;
+
+  /* Variables on list (long form only). */
+  struct variable **v = NULL;
+  int nv;
+
+  lex_match_id ("VECTOR");
+
+  cp = vecnames = xmalloc (256);
+  endp = &vecnames[256];
+  do
+    {
+      /* Get the name(s) of the new vector(s). */
+      if (!lex_force_id ())
+       return CMD_FAILURE;
+      while (token == T_ID)
+       {
+         if (cp + 16 > endp)
+           {
+             char *old_vecnames = vecnames;
+             vecnames = xrealloc (vecnames, endp - vecnames + 256);
+             cp = (cp - old_vecnames) + vecnames;
+             endp = (endp - old_vecnames) + vecnames + 256;
+           }
+
+         for (cp2 = cp; cp2 < cp; cp2 += strlen (cp))
+           if (!strcmp (cp2, tokid))
+             {
+               msg (SE, _("Vector name %s is given twice."), tokid);
+               goto fail;
+             }
+
+         if (find_vector (tokid))
+           {
+             msg (SE, _("There is already a vector with name %s."), tokid);
+             goto fail;
+           }
+
+         cp = stpcpy (cp, tokid) + 1;
+         lex_get ();
+         lex_match (',');
+       }
+      *cp++ = 0;
+
+      /* Now that we have the names it's time to check for the short
+         or long forms. */
+      if (lex_match ('='))
+       {
+         /* Long form. */
+
+         if (strchr (vecnames, '\0')[1])
+           {
+             /* There's more than one vector name. */
+             msg (SE, _("A slash must be used to separate each vector "
+                  "specification when using the long form.  Commands "
+                  "such as VECTOR A,B=Q1 TO Q20 are not supported."));
+             goto fail;
+           }
+
+         if (!parse_variables (NULL, &v, &nv, PV_SAME_TYPE | PV_DUPLICATE))
+           goto fail;
+
+         vec = xrealloc (vec, sizeof *vec * (nvec + 1));
+         vec[nvec].index = nvec;
+         strcpy (vec[nvec].name, vecnames);
+         vec[nvec].v = v;
+         vec[nvec].nv = nv;
+         nvec++;
+         v = NULL;             /* prevent block from being freed on error */
+       }
+      else if (lex_match ('('))
+       {
+         int i;
+
+         /* Maximum number of digits in a number to add to the base
+            vecname. */
+         int ndig;
+
+         /* Name of an individual variable to be created. */
+         char name[9];
+
+         if (!lex_force_int ())
+           return CMD_FAILURE;
+         nv = lex_integer ();
+         lex_get ();
+         if (nv <= 0)
+           {
+             msg (SE, _("Vectors must have at least one element."));
+             goto fail;
+           }
+         if (!lex_force_match (')'))
+           goto fail;
+
+         /* First check that all the generated variable names are 8
+            characters or shorter. */
+         ndig = intlog10 (nv);
+         for (cp = vecnames; *cp;)
+           {
+             int len = strlen (cp);
+             if (len + ndig > 8)
+               {
+                 msg (SE, _("%s%d is too long for a variable name."), cp, nv);
+                 goto fail;
+               }
+             cp += len + 1;
+           }
+
+         /* Next check that none of the variables exist. */
+         for (cp = vecnames; *cp;)
+           {
+             for (i = 0; i < nv; i++)
+               {
+                 sprintf (name, "%s%d", cp, i + 1);
+                 if (is_varname (name))
+                   {
+                     msg (SE, _("There is already a variable named %s."), name);
+                     goto fail;
+                   }
+               }
+             cp += strlen (cp) + 1;
+           }
+
+         /* Finally create the variables and vectors. */
+         vec = xrealloc (vec, sizeof *vec * (nvec + nv));
+         for (cp = vecnames; *cp;)
+           {
+             vec[nvec].index = nvec;
+             strcpy (vec[nvec].name, cp);
+             vec[nvec].v = xmalloc (sizeof *vec[nvec].v * nv);
+             vec[nvec].nv = nv;
+             for (i = 0; i < nv; i++)
+               {
+                 sprintf (name, "%s%d", cp, i + 1);
+                 vec[nvec].v[i] = force_create_variable (&default_dict, name,
+                                                         NUMERIC, 0);
+                 envector (vec[nvec].v[i]);
+               }
+             nvec++;
+             cp += strlen (cp) + 1;
+           }
+       }
+      else
+       {
+         msg (SE, _("The syntax for this command does not match "
+              "the expected syntax for either the long form "
+              "or the short form of VECTOR."));
+         goto fail;
+       }
+
+      free (vecnames);
+      vecnames = NULL;
+    }
+  while (lex_match ('/'));
+
+  if (token != '.')
+    {
+      lex_error (_("expecting end of command"));
+      goto fail;
+    }
+  return CMD_SUCCESS;
+
+fail:
+  free (vecnames);
+  free (v);
+  return CMD_PART_SUCCESS_MAYBE;
+}
+
+/* Returns a pointer to the vector with name NAME, or NULL on
+   failure. */
+struct vector *
+find_vector (const char *name)
+{
+  int i;
+
+  for (i = 0; i < nvec; i++)
+    if (!strcmp (vec[i].name, name))
+      return &vec[i];
+  return NULL;
+}
diff --git a/src/vector.h b/src/vector.h
new file mode 100644 (file)
index 0000000..c75f569
--- /dev/null
@@ -0,0 +1,37 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !vector_h
+#define vector_h 1
+
+/* Represents a vector as created by the VECTOR transformation. */
+struct vector
+  {
+    int index;                 /* Index into vec[]. */
+    char name[9];              /* Name. */
+    struct variable **v;       /* Vector of variables. */
+    int nv;                    /* Number of variables. */
+  };
+
+extern struct vector *vec;
+extern int nvec;
+
+struct vector *find_vector (const char *name);
+
+#endif /* !vector_h */
diff --git a/src/version.h b/src/version.h
new file mode 100644 (file)
index 0000000..c7912d7
--- /dev/null
@@ -0,0 +1,51 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !version_h
+#define version_h 1
+
+/* "A.B.C" */
+extern const char bare_version[];
+
+/* "GNU PSPP A.B.C" */
+extern const char version[];
+
+/* "GNU PSPP version A.B (date), Copyright (C) XXXX Free Software
+   Foundation, Inc." */
+extern const char stat_version[];
+
+/* Canonical name of host system type. */
+extern const char host_system[];
+
+/* Canonical name of build system type. */
+extern const char build_system[];
+
+/* Configuration path at build time. */
+extern const char default_config_path[];
+
+/* Include path. */
+extern const char include_path[];
+
+/* Font path. */
+extern const char groff_font_path[];
+
+/* Locale directory. */
+extern const char locale_dir[];
+
+#endif /* !version_h */
diff --git a/src/vfm.c b/src/vfm.c
new file mode 100644 (file)
index 0000000..333851d
--- /dev/null
+++ b/src/vfm.c
@@ -0,0 +1,1297 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* AIX requires this to be the first thing in the file.  */
+#include <config.h>
+#if __GNUC__
+#define alloca __builtin_alloca
+#else
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+#pragma alloca
+#else
+#ifndef alloca                 /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#endif
+#endif
+#endif
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>    /* Required by SunOS4. */
+#endif
+#include "alloc.h"
+#include "approx.h"
+#include "do-ifP.h"
+#include "error.h"
+#include "expr.h"
+#include "misc.h"
+#include "random.h"
+#include "som.h"
+#include "str.h"
+#include "tab.h"
+#include "var.h"
+#include "vector.h"
+#include "vfm.h"
+#include "vfmP.h"
+
+/*
+   Virtual File Manager (vfm):
+
+   vfm is used to process data files.  It uses the model that data is
+   read from one stream (the data source), then written to another
+   (the data sink).  The data source is then deleted and the data sink
+   becomes the data source for the next procedure. */
+
+#undef DEBUGGING
+/*#define DEBUGGING 1 */
+#include "debug-print.h"
+
+/* This is used to read from the active file. */
+struct case_stream *vfm_source;
+
+/* `value' indexes to initialize to particular values for certain cases. */
+struct long_vec reinit_sysmis;         /* SYSMIS for every case. */
+struct long_vec reinit_blanks;         /* Blanks for every case. */
+struct long_vec init_zero;             /* Zero for first case only. */
+struct long_vec init_blanks;           /* Blanks for first case only. */
+
+/* This is used to write to the replacement active file. */
+struct case_stream *vfm_sink;
+
+/* Information about the data source. */
+struct stream_info vfm_source_info;
+
+/* Information about the data sink. */
+struct stream_info vfm_sink_info;
+
+/* Filter variable and  `value' index. */
+static struct variable *filter_var;
+static int filter_index;
+
+#define FILTERED                                                       \
+       (filter_index != -1                                             \
+        && (temp_case->data[filter_index].f == 0.0                     \
+            || temp_case->data[filter_index].f == SYSMIS               \
+            || is_num_user_missing (temp_case->data[filter_index].f,   \
+                                    filter_var)))
+
+/* Nonzero if the case needs to have values deleted before being
+   stored, zero otherwise. */
+int compaction_necessary;
+
+/* Number of values after compaction, or the same as
+   vfm_sink_info.nval, if compaction is not necessary. */
+int compaction_nval;
+
+/* Temporary case buffer with enough room for `compaction_nval'
+   `value's. */
+struct ccase *compaction_case;
+
+/* Within a session, when paging is turned on, it is never turned back
+   off.  This policy might be too aggressive. */
+static int paging = 0;
+
+/* Time at which vfm was last invoked. */
+time_t last_vfm_invocation;
+
+/* Functions called during procedure processing. */
+static int (*proc_func) (struct ccase *);      /* Called for each case. */
+static int (*virt_proc_func) (struct ccase *); /* From SPLIT_FILE_procfunc. */
+static void (*begin_func) (void);      /* Called at beginning of a series. */
+static void (*virt_begin_func) (void); /* Called by SPLIT_FILE_procfunc. */
+static void (*end_func) (void);        /* Called after end of a series. */
+int (*write_case) (void);
+
+/* Number of cases passed to proc_func(). */
+static int case_count;
+
+/* Lag queue. */
+int n_lag;                     /* Number of cases to lag. */
+static int lag_count;          /* Number of cases in lag_queue so far. */
+static int lag_head;           /* Index where next case will be added. */
+static struct ccase **lag_queue; /* Array of n_lag ccase * elements. */
+
+static void open_active_file (void);
+static void close_active_file (void);
+static int SPLIT_FILE_procfunc (struct ccase *);
+static void finish_compaction (void);
+static void lag_case (void);
+static int procedure_write_case (void);
+\f
+/* Public functions. */
+
+/* Reads all the cases from the active file, transforms them by the
+   active set of transformations, calls PROCFUNC with CURCASE set to
+   the case and CASENUM set to the case number, and writes them to a
+   new active file.
+
+   Divides the active file into zero or more series of one or more
+   cases each.  BEGINFUNC is called before each series.  ENDFUNC is
+   called after each series. */
+void
+procedure (void (*beginfunc) (void),
+          int (*procfunc) (struct ccase *curcase),
+          void (*endfunc) (void))
+{
+  end_func = endfunc;
+  write_case = procedure_write_case;
+
+  if (default_dict.n_splits && procfunc != NULL)
+    {
+      virt_proc_func = procfunc;
+      proc_func = SPLIT_FILE_procfunc;
+      
+      virt_begin_func = beginfunc;
+      begin_func = NULL;
+    } else {
+      begin_func = beginfunc;
+      proc_func = procfunc;
+    }
+
+  last_vfm_invocation = time (NULL);
+
+  open_active_file ();
+  vfm_source->read ();
+  close_active_file ();
+}
+\f
+/* Active file processing support.  Subtly different semantics from
+   procedure(). */
+
+static int process_active_file_write_case (void);
+
+/* The casefunc might want us to stop calling it. */
+static int not_canceled;
+
+/* Reads all the cases from the active file and passes them one-by-one
+   to CASEFUNC in temp_case.  Before any cases are passed, calls
+   BEGINFUNC.  After all the cases have been passed, calls ENDFUNC.
+   BEGINFUNC, CASEFUNC, and ENDFUNC can write temp_case to the output
+   file by calling process_active_file_output_case().
+
+   process_active_file() ignores TEMPORARY, SPLIT FILE, and N. */
+void
+process_active_file (void (*beginfunc) (void),
+                    int (*casefunc) (struct ccase *curcase),
+                    void (*endfunc) (void))
+{
+  proc_func = casefunc;
+  write_case = process_active_file_write_case;
+  not_canceled = 1;
+
+  open_active_file ();
+  beginfunc ();
+  
+  /* There doesn't necessarily need to be an active file. */
+  if (vfm_source)
+    vfm_source->read ();
+  
+  endfunc ();
+  close_active_file ();
+}
+
+/* Pass the current case to casefunc. */
+static int
+process_active_file_write_case (void)
+{
+  /* Index of current transformation. */
+  int cur_trns;
+
+  for (cur_trns = f_trns ; cur_trns != temp_trns; )
+    {
+      int code;
+       
+      code = t_trns[cur_trns]->proc (t_trns[cur_trns], temp_case);
+      switch (code)
+       {
+       case -1:
+         /* Next transformation. */
+         cur_trns++;
+         break;
+       case -2:
+         /* Delete this case. */
+         goto done;
+       default:
+         /* Go to that transformation. */
+         cur_trns = code;
+         break;
+       }
+    }
+
+  if (n_lag)
+    lag_case ();
+         
+  /* Call the procedure if FILTER and PROCESS IF don't prohibit it. */
+  if (not_canceled
+      && !FILTERED
+      && (process_if_expr == NULL ||
+         expr_evaluate (process_if_expr, temp_case, NULL) == 1.0))
+    not_canceled = proc_func (temp_case);
+  
+  case_count++;
+  
+ done:
+  {
+    long *lp;
+
+    /* This case is finished.  Initialize the variables for the next case. */
+    for (lp = reinit_sysmis.vec; *lp != -1;)
+      temp_case->data[*lp++].f = SYSMIS;
+    for (lp = reinit_blanks.vec; *lp != -1;)
+      memset (temp_case->data[*lp++].s, ' ', MAX_SHORT_STRING);
+  }
+  
+  return 1;
+}
+
+/* Write temp_case to the active file. */
+void
+process_active_file_output_case (void)
+{
+  vfm_sink_info.ncases++;
+  vfm_sink->write ();
+}
+\f
+/* Opening the active file. */
+
+/* It might be usefully noted that the following several functions are
+   given in the order that they are called by open_active_file(). */
+
+/* Prepare to write to the replacement active file. */
+static void
+prepare_for_writing (void)
+{
+  /* FIXME: If ALL the conditions listed below hold true, then the
+     replacement active file is guaranteed to be identical to the
+     original active file:
+
+     1. TEMPORARY was the first transformation, OR, there were no
+     transformations at all.
+
+     2. Input is not coming from an input program.
+
+     3. Compaction is not necessary.
+
+     So, in this case, we shouldn't have to replace the active
+     file--it's just a waste of time and space. */
+
+  vfm_sink_info.ncases = 0;
+  vfm_sink_info.nval = default_dict.nval;
+  vfm_sink_info.case_size = (sizeof (struct ccase)
+                            + (default_dict.nval - 1) * sizeof (union value));
+  
+  if (vfm_sink == NULL)
+    {
+      if (vfm_sink_info.case_size * vfm_source_info.ncases > MAX_WORKSPACE
+         && !paging)
+       {
+         msg (MW, _("Workspace overflow predicted.  Max workspace is "
+                    "currently set to %d KB (%d cases at %d bytes each).  "
+                    "Paging active file to disk."),
+              MAX_WORKSPACE / 1024, MAX_WORKSPACE / vfm_sink_info.case_size,
+              vfm_sink_info.case_size);
+         
+         paging = 1;
+       }
+      
+      vfm_sink = paging ? &vfm_disk_stream : &vfm_memory_stream;
+    }
+}
+
+/* Arrange for compacting the output cases for storage. */
+static void
+arrange_compaction (void)
+{
+  int count_values = 0;
+
+  {
+    int i;
+    
+    /* Count up the number of `value's that will be output. */
+    for (i = 0; i < temp_dict->nvar; i++)
+      if (temp_dict->var[i]->name[0] != '#')
+       {
+         assert (temp_dict->var[i]->nv > 0);
+         count_values += temp_dict->var[i]->nv;
+       }
+    assert (temporary == 2 || count_values <= temp_dict->nval);
+  }
+  
+  /* Compaction is only necessary if the number of `value's to output
+     differs from the number already present. */
+  compaction_nval = count_values;
+  compaction_necessary = temporary == 2 || count_values != temp_dict->nval;
+  
+  if (vfm_sink->init)
+    vfm_sink->init ();
+}
+
+/* Prepares the temporary case and compaction case. */
+static void
+make_temp_case (void)
+{
+  temp_case = xmalloc (vfm_sink_info.case_size);
+
+  if (compaction_necessary)
+    compaction_case = xmalloc (sizeof (struct ccase)
+                              + sizeof (union value) * (compaction_nval - 1));
+  
+#if __CHECKER__
+  /* Initialize the unused trailing parts of string variables to avoid
+     spurious warnings from Checker. */
+  {
+    int i;
+    
+    for (i = 0; i < default_dict.nvar; i++)
+      {
+       struct variable *v = default_dict.var[i];
+      
+       if (v->type == ALPHA && v->width % 8 != 0)
+         memcpy (&temp_case->data[v->fv + v->nv - 1]
+                 .s[v->width % 8], _("!ERROR!"), 8 - v->width % 8);
+      }
+  }
+#endif
+}
+
+#if DEBUGGING
+/* Returns the name of the variable that owns the index CCASE_INDEX
+   into ccase. */
+static const char *
+index_to_varname (int ccase_index)
+{
+  int i;
+
+  for (i = 0; i < default_dict.nvar; i++)
+    {
+      variable *v = default_dict.var[i];
+      
+      if (ccase_index >= v->fv && ccase_index < v->fv + v->nv)
+       return default_dict.var[i]->name;
+    }
+  return _("<NOVAR>");
+}
+#endif
+
+/* Initializes temp_case from the vectors that say which `value's need
+   to be initialized just once, and which ones need to be
+   re-initialized before every case. */
+static void
+vector_initialization (void)
+{
+  int i;
+  long *lp;
+  
+  /* Just once. */
+  for (i = 0; i < init_zero.n; i++)
+    temp_case->data[init_zero.vec[i]].f = 0.0;
+  for (i = 0; i < init_blanks.n; i++)
+    memset (temp_case->data[init_blanks.vec[i]].s, ' ', MAX_SHORT_STRING);
+
+  /* These vectors need to be repeatedly accessed, so we add a
+     sentinel to (hopefully) improve speed. */
+  vec_insert (&reinit_sysmis, -1);
+  vec_insert (&reinit_blanks, -1);
+
+  for (lp = reinit_sysmis.vec; *lp != -1;)
+    temp_case->data[*lp++].f = SYSMIS;
+  for (lp = reinit_blanks.vec; *lp != -1;)
+    memset (temp_case->data[*lp++].s, ' ', MAX_SHORT_STRING);
+  
+#if DEBUGGING
+  printf ("vfm: init_zero=");
+  for (i = 0; i < init_zero.n; i++)
+    printf ("%s%s", i ? "," : "", index_to_varname (init_zero.vec[i]));
+  printf (" init_blanks=");
+  for (i = 0; i < init_blanks.n; i++)
+    printf ("%s%s", i ? "," : "", index_to_varname (init_blanks.vec[i]));
+  printf (" reinit_sysmis=");
+  for (lp = reinit_sysmis.vec; *lp != -1; lp++)
+    printf ("%s%s", lp != reinit_sysmis.vec ? "," : "",
+           index_to_varname (*lp));
+  printf (" reinit_blanks=");
+  for (lp = reinit_blanks.vec; *lp != -1; lp++)
+    printf ("%s%s", lp != reinit_blanks.vec ? "," : "",
+           index_to_varname (*lp));
+  printf ("\n");
+#endif
+}
+
+/* Sets filter_index to an appropriate value. */
+static void
+setup_filter (void)
+{
+  filter_index = -1;
+  
+  if (default_dict.filter_var[0])
+    {
+      struct variable *fv = find_variable (default_dict.filter_var);
+      
+      if (fv == NULL || fv->type == ALPHA)
+       default_dict.filter_var[0] = 0;
+      else
+       {
+         filter_index = fv->index;
+         filter_var = fv;
+       }
+    }
+}
+
+/* Sets all the lag-related variables based on value of n_lag. */
+static void
+setup_lag (void)
+{
+  int i;
+  
+  if (n_lag == 0)
+    return;
+
+  lag_count = 0;
+  lag_head = 0;
+  lag_queue = xmalloc (n_lag * sizeof *lag_queue);
+  for (i = 0; i < n_lag; i++)
+    lag_queue[i] = xmalloc (temp_dict->nval * sizeof **lag_queue);
+}
+
+/* There is a lot of potential confusion in the vfm and related
+   routines over the number of `value's at each stage of the process.
+   Here is each nval count, with explanation, as set up by
+   open_active_file():
+
+   vfm_source_info.nval: Number of `value's in the cases returned by
+   the source stream.  This value turns out not to be very useful, but
+   we maintain it anyway.
+
+   vfm_sink_info.nval: Number of `value's in the cases after all
+   transformations have been performed.  Never less than
+   vfm_source_info.nval.
+
+   temp_dict->nval: Number of `value's in the cases after the
+   transformations leading up to TEMPORARY have been performed.  If
+   TEMPORARY was not specified, this is equal to vfm_sink_info.nval.
+   Never less than vfm_sink_info.nval.
+
+   compaction_nval: Number of `value's in the cases after the
+   transformations leading up to TEMPORARY have been performed and the
+   case has been compacted by compact_case(), if compaction is
+   necessary.  This the number of `value's in the cases saved by the
+   sink stream.  (However, note that the cases passed to the sink
+   stream have not yet been compacted.  It is the responsibility of
+   the data sink to call compact_case().)  This may be less than,
+   greater than, or equal to vfm_source_info.nval.  `compaction'
+   becomes the new value of default_dict.nval after the procedure is
+   completed.
+
+   default_dict.nval: This is often an alias for temp_dict->nval.  As
+   such it can really have no separate existence until the procedure
+   is complete.  For this reason it should *not* be referenced inside
+   the execution of a procedure. */
+/* Makes all preparations for reading from the data source and writing
+   to the data sink. */
+static void
+open_active_file (void)
+{
+  /* Sometimes we want to refer to the dictionary that applies to the
+     data actually written to the sink.  This is either temp_dict or
+     default_dict.  However, if TEMPORARY is not on, then temp_dict
+     does not apply.  So, we can set temp_dict to default_dict in this
+     case. */
+  if (!temporary)
+    {
+      temp_trns = n_trns;
+      temp_dict = &default_dict;
+    }
+
+  /* No cases passed to the procedure yet. */
+  case_count = 0;
+
+  /* The rest. */
+  prepare_for_writing ();
+  arrange_compaction ();
+  make_temp_case ();
+  vector_initialization ();
+  setup_randomize ();
+  discard_ctl_stack ();
+  setup_filter ();
+  setup_lag ();
+
+  /* Debug output. */
+  debug_printf (("vfm: reading from %s source, writing to %s sink.\n",
+                vfm_source->name, vfm_sink->name));
+  debug_printf (("vfm: vfm_source_info.nval=%d, vfm_sink_info.nval=%d, "
+                "temp_dict->nval=%d, compaction_nval=%d, "
+                "default_dict.nval=%d\n",
+                vfm_source_info.nval, vfm_sink_info.nval, temp_dict->nval,
+                compaction_nval, default_dict.nval));
+}
+\f
+/* Closes the active file. */
+static void
+close_active_file (void)
+{
+  /* Close the current case group. */
+  if (case_count && end_func != NULL)
+    end_func ();
+
+  /* Stop lagging (catch up?). */
+  if (n_lag)
+    {
+      int i;
+      
+      for (i = 0; i < n_lag; i++)
+       free (lag_queue[i]);
+      free (lag_queue);
+      n_lag = 0;
+    }
+  
+  /* Assume the dictionary from right before TEMPORARY, if any.  Turn
+     off TEMPORARY. */
+  if (temporary)
+    {
+      restore_dictionary (temp_dict);
+      temp_dict = NULL;
+    }
+
+  /* The default dictionary assumes the compacted data size. */
+  default_dict.nval = compaction_nval;
+    
+  /* Old data sink --> New data source. */
+  if (vfm_source && vfm_source->destroy_source)
+    vfm_source->destroy_source ();
+  
+  vfm_source = vfm_sink;
+  vfm_source_info.ncases = vfm_sink_info.ncases;
+  vfm_source_info.nval = compaction_nval;
+  vfm_source_info.case_size = (sizeof (struct ccase)
+                              + (compaction_nval - 1) * sizeof (union value));
+  if (vfm_source->mode)
+    vfm_source->mode ();
+
+  /* Old data sink is gone now. */
+  vfm_sink = NULL;
+
+  /* Finish compaction. */
+  if (compaction_necessary)
+    finish_compaction ();
+  cancel_temporary ();
+
+  /* Free temporary cases. */
+  free (temp_case);
+  temp_case = NULL;
+
+  free (compaction_case);
+  compaction_case = NULL;
+
+  /* Cancel PROCESS IF. */
+  expr_free (process_if_expr);
+  process_if_expr = NULL;
+
+  /* Cancel FILTER if temporary. */
+  if (filter_index != -1 && !FILTER_before_TEMPORARY)
+    default_dict.filter_var[0] = 0;
+
+  /* Cancel transformations. */
+  cancel_transformations ();
+
+  /* Clear value-initialization vectors. */
+  vec_clear (&init_zero);
+  vec_clear (&init_blanks);
+  vec_clear (&reinit_sysmis);
+  vec_clear (&reinit_blanks);
+
+  /* Turn off case limiter. */
+  default_dict.N = 0;
+
+  /* Clear VECTOR vectors. */
+  {
+    int i;
+
+    for (i = 0; i < nvec; i++)
+      free (vec[i].v);
+    free (vec);
+    vec = NULL;
+    nvec = 0;
+  }
+
+  debug_printf (("vfm: procedure complete\n\n"));
+}
+\f
+/* Disk case stream. */
+
+/* Associated files. */
+FILE *disk_source_file;
+FILE *disk_sink_file;
+
+/* Initializes the disk sink. */
+static void
+disk_stream_init (void)
+{
+  disk_sink_file = tmpfile ();
+  if (!disk_sink_file)
+    {
+      msg (ME, _("An error occurred attempting to create a temporary "
+                "file for use as the active file: %s."),
+          strerror (errno));
+      err_failure ();
+    }
+}
+
+/* Reads all cases from the disk source and passes them one by one to
+   write_case(). */
+static void
+disk_stream_read (void)
+{
+  int i;
+
+  for (i = 0; i < vfm_source_info.ncases; i++)
+    {
+      if (!fread (temp_case, vfm_source_info.case_size, 1, disk_source_file))
+       {
+         msg (ME, _("An error occurred while attempting to read from "
+              "a temporary file created for the active file: %s."),
+              strerror (errno));
+         err_failure ();
+         return;
+       }
+
+      if (!write_case ())
+       return;
+    }
+}
+
+/* Writes temp_case to the disk sink. */
+static void
+disk_stream_write (void)
+{
+  union value *src_case;
+
+  if (compaction_necessary)
+    {
+      compact_case (compaction_case, temp_case);
+      src_case = (union value *) compaction_case;
+    }
+  else src_case = (union value *) temp_case;
+
+  if (fwrite (src_case, sizeof *src_case * compaction_nval, 1,
+             disk_sink_file) != 1)
+    {
+      msg (ME, _("An error occurred while attempting to write to a "
+                "temporary file used as the active file: %s."),
+          strerror (errno));
+      err_failure ();
+    }
+}
+
+/* Switches the stream from a sink to a source. */
+static void
+disk_stream_mode (void)
+{
+  /* Rewind the sink. */
+  if (fseek (disk_sink_file, 0, SEEK_SET) != 0)
+    {
+      msg (ME, _("An error occurred while attempting to rewind a "
+                "temporary file used as the active file: %s."),
+          strerror (errno));
+      err_failure ();
+    }
+  
+  /* Sink --> source variables. */
+  disk_source_file = disk_sink_file;
+}
+
+/* Destroys the source's internal data. */
+static void
+disk_stream_destroy_source (void)
+{
+  if (disk_source_file)
+    {
+      fclose (disk_source_file);
+      disk_source_file = NULL;
+    }
+}
+
+/* Destroys the sink's internal data. */
+static void
+disk_stream_destroy_sink (void)
+{
+  if (disk_sink_file)
+    {
+      fclose (disk_sink_file);
+      disk_sink_file = NULL;
+    }
+}
+
+/* Disk stream. */
+struct case_stream vfm_disk_stream = 
+  {
+    disk_stream_init,
+    disk_stream_read,
+    disk_stream_write,
+    disk_stream_mode,
+    disk_stream_destroy_source,
+    disk_stream_destroy_sink,
+    "disk",
+  };
+\f
+/* Memory case stream. */
+
+/* List of cases stored in the stream. */
+struct case_list *memory_source_cases;
+struct case_list *memory_sink_cases;
+
+/* Current case. */
+struct case_list *memory_sink_iter;
+
+/* Maximum number of cases. */
+int memory_sink_max_cases;
+
+/* Initializes the memory stream variables for writing. */
+static void
+memory_stream_init (void)
+{
+  memory_sink_cases = NULL;
+  memory_sink_iter = NULL;
+  
+  assert (compaction_nval);
+  memory_sink_max_cases = MAX_WORKSPACE / (sizeof (union value) * compaction_nval);
+}
+
+/* Reads the case stream from memory and passes it to write_case(). */
+static void
+memory_stream_read (void)
+{
+  while (memory_source_cases != NULL)
+    {
+      memcpy (temp_case, &memory_source_cases->c, vfm_source_info.case_size);
+      
+      {
+       struct case_list *current = memory_source_cases;
+       memory_source_cases = memory_source_cases->next;
+       free (current);
+      }
+      
+      if (!write_case ())
+       return;
+    }
+}
+
+/* Writes temp_case to the memory stream. */
+static void
+memory_stream_write (void)
+{
+  struct case_list *new_case = malloc (sizeof (struct case_list)
+                                      + ((compaction_nval - 1)
+                                         * sizeof (union value)));
+
+  /* If we've got memory to spare then add it to the linked list. */
+  if (vfm_sink_info.ncases <= memory_sink_max_cases && new_case != NULL)
+    {
+      if (compaction_necessary)
+       compact_case (&new_case->c, temp_case);
+      else
+       memcpy (&new_case->c, temp_case, sizeof (union value) * compaction_nval);
+
+      /* Append case to linked list. */
+      if (memory_sink_cases)
+       memory_sink_iter = memory_sink_iter->next = new_case;
+      else
+       memory_sink_iter = memory_sink_cases = new_case;
+    }
+  else
+    {
+      /* Out of memory.  Write the active file to disk. */
+      struct case_list *cur, *next;
+
+      /* Notify the user. */
+      if (!new_case)
+       msg (MW, _("Virtual memory exhausted.  Paging active file "
+                  "to disk."));
+      else
+       msg (MW, _("Workspace limit of %d KB (%d cases at %d bytes each) "
+                  "overflowed.  Paging active file to disk."),
+            MAX_WORKSPACE / 1024, memory_sink_max_cases,
+            compaction_nval * sizeof (union value));
+
+      free (new_case);
+
+      /* Switch to a disk sink. */
+      vfm_sink = &vfm_disk_stream;
+      vfm_sink->init ();
+      paging = 1;
+
+      /* Terminate the list. */
+      if (memory_sink_iter)
+       memory_sink_iter->next = NULL;
+
+      /* Write the cases to disk and destroy them.  We can't call
+         vfm->sink->write() because of compaction. */
+      for (cur = memory_sink_cases; cur; cur = next)
+       {
+         next = cur->next;
+         if (fwrite (cur->c.data, sizeof (union value) * compaction_nval, 1,
+                     disk_sink_file) != 1)
+           {
+             msg (ME, _("An error occurred while attempting to "
+                        "write to a temporary file created as the "
+                        "active file, while paging to disk: %s."),
+                  strerror (errno));
+             err_failure ();
+           }
+         free (cur);
+       }
+
+      /* Write the current case to disk. */
+      vfm_sink->write ();
+    }
+}
+
+/* If the data is stored in memory, causes it to be written to disk.
+   To be called only *between* procedure()s, not within them. */
+void
+page_to_disk (void)
+{
+  if (vfm_source == &vfm_memory_stream)
+    {
+      /* Switch to a disk sink. */
+      vfm_sink = &vfm_disk_stream;
+      vfm_sink->init ();
+      paging = 1;
+      
+      /* Write the cases to disk and destroy them.  We can't call
+         vfm->sink->write() because of compaction. */
+      {
+       struct case_list *cur, *next;
+       
+       for (cur = memory_source_cases; cur; cur = next)
+         {
+           next = cur->next;
+           if (fwrite (cur->c.data, sizeof *cur->c.data * compaction_nval, 1,
+                       disk_sink_file) != 1)
+             {
+               msg (ME, _("An error occurred while attempting to "
+                          "write to a temporary file created as the "
+                          "active file, while paging to disk: %s."),
+                    strerror (errno));
+               err_failure ();
+             }
+           free (cur);
+         }
+      }
+      
+      vfm_source = &vfm_disk_stream;
+      vfm_source->mode ();
+
+      vfm_sink = NULL;
+    }
+}
+
+/* Switch the memory stream from sink to source mode. */
+static void
+memory_stream_mode (void)
+{
+  /* Terminate the list. */
+  if (memory_sink_iter)
+    memory_sink_iter->next = NULL;
+
+  /* Sink --> source variables. */
+  memory_source_cases = memory_sink_cases;
+  memory_sink_cases = NULL;
+}
+
+/* Destroy all memory source data. */
+static void
+memory_stream_destroy_source (void)
+{
+  struct case_list *cur, *next;
+  
+  for (cur = memory_source_cases; cur; cur = next)
+    {
+      next = cur->next;
+      free (cur);
+    }
+  memory_source_cases = NULL;
+}
+
+/* Destroy all memory sink data. */
+static void
+memory_stream_destroy_sink (void)
+{
+  struct case_list *cur, *next;
+  
+  for (cur = memory_sink_cases; cur; cur = next)
+    {
+      next = cur->next;
+      free (cur);
+    }
+  memory_sink_cases = NULL;
+}
+  
+/* Memory stream. */
+struct case_stream vfm_memory_stream = 
+  {
+    memory_stream_init,
+    memory_stream_read,
+    memory_stream_write,
+    memory_stream_mode,
+    memory_stream_destroy_source,
+    memory_stream_destroy_sink,
+    "memory",
+  };
+\f
+#undef DEBUGGING
+#include "debug-print.h"
+
+/* Add temp_case to the lag queue. */
+static void
+lag_case (void)
+{
+  if (lag_count < n_lag)
+    lag_count++;
+  memcpy (lag_queue[lag_head], temp_case, sizeof (union value) * temp_dict->nval);
+  if (++lag_head >= n_lag)
+    lag_head = 0;
+}
+
+/* Returns a pointer to the lagged case from N_BEFORE cases before the
+   current one, or NULL if there haven't been that many cases yet. */
+struct ccase *
+lagged_case (int n_before)
+{
+  assert (n_before <= n_lag);
+  if (n_before > lag_count)
+    return NULL;
+  
+  {
+    int index = lag_head - n_before;
+    if (index < 0)
+      index += n_lag;
+    return lag_queue[index];
+  }
+}
+   
+/* Transforms temp_case and writes it to the replacement active file
+   if advisable.  Returns nonzero if more cases can be accepted, zero
+   otherwise.  Do not call this function again after it has returned
+   zero once.  */
+int
+procedure_write_case (void)
+{
+  /* Index of current transformation. */
+  int cur_trns;
+
+  /* Return value: whether it's reasonable to write any more cases. */
+  int more_cases = 1;
+
+  debug_printf ((_("transform: ")));
+
+  cur_trns = f_trns;
+  for (;;)
+    {
+      /* Output the case if this is temp_trns. */
+      if (cur_trns == temp_trns)
+       {
+         debug_printf (("REC"));
+
+         if (n_lag)
+           lag_case ();
+         
+         vfm_sink_info.ncases++;
+         vfm_sink->write ();
+
+         if (default_dict.N)
+           more_cases = vfm_sink_info.ncases < default_dict.N;
+       }
+
+      /* Are we done? */
+      if (cur_trns >= n_trns)
+       break;
+      
+      debug_printf (("$%d", cur_trns));
+
+      /* Decide which transformation should come next. */
+      {
+       int code;
+       
+       code = t_trns[cur_trns]->proc (t_trns[cur_trns], temp_case);
+       switch (code)
+         {
+         case -1:
+           /* Next transformation. */
+           cur_trns++;
+           break;
+         case -2:
+           /* Delete this case. */
+           goto done;
+         default:
+           /* Go to that transformation. */
+           cur_trns = code;
+           break;
+         }
+      }
+    }
+
+  /* Call the beginning of group function. */
+  if (!case_count && begin_func != NULL)
+    begin_func ();
+
+  /* Call the procedure if there is one and FILTER and PROCESS IF
+     don't prohibit it. */
+  if (proc_func != NULL
+      && !FILTERED
+      && (process_if_expr == NULL ||
+         expr_evaluate (process_if_expr, temp_case, NULL) == 1.0))
+    proc_func (temp_case);
+
+  case_count++;
+  
+done:
+  debug_putc ('\n', stdout);
+  
+  {
+    long *lp;
+
+    /* This case is finished.  Initialize the variables for the next case. */
+    for (lp = reinit_sysmis.vec; *lp != -1;)
+      temp_case->data[*lp++].f = SYSMIS;
+    for (lp = reinit_blanks.vec; *lp != -1;)
+      memset (temp_case->data[*lp++].s, ' ', MAX_SHORT_STRING);
+  }
+  
+  /* Return previously determined value. */
+  return more_cases;
+}
+
+/* Appends TRNS to t_trns[], the list of all transformations to be
+   performed on data as it is read from the active file. */
+void
+add_transformation (struct trns_header * trns)
+{
+  if (n_trns >= m_trns)
+    {
+      m_trns += 16;
+      t_trns = xrealloc (t_trns, sizeof *t_trns * m_trns);
+    }
+  t_trns[n_trns] = trns;
+  trns->index = n_trns++;
+}
+
+/* Cancels all active transformations, including any transformations
+   created by the input program. */
+void
+cancel_transformations (void)
+{
+  int i;
+  for (i = 0; i < n_trns; i++)
+    {
+      if (t_trns[i]->free)
+       t_trns[i]->free (t_trns[i]);
+      free (t_trns[i]);
+    }
+  n_trns = f_trns = 0;
+  if (m_trns > 32)
+    {
+      free (t_trns);
+      m_trns = 0;
+    }
+}
+
+/* Dumps out the values of all the split variables for the case C. */
+static void
+dump_splits (struct ccase *c)
+{
+  struct variable **iter;
+  struct tab_table *t;
+  int i;
+
+  t = tab_create (3, default_dict.n_splits + 1, 0);
+  tab_dim (t, tab_natural_dimensions);
+  tab_vline (t, TAL_1 | TAL_SPACING, 1, 0, default_dict.n_splits);
+  tab_vline (t, TAL_1 | TAL_SPACING, 2, 0, default_dict.n_splits);
+  tab_text (t, 0, 0, TAB_NONE, _("Variable"));
+  tab_text (t, 1, 0, TAB_LEFT, _("Value"));
+  tab_text (t, 2, 0, TAB_LEFT, _("Label"));
+  for (iter = default_dict.splits, i = 0; *iter; iter++, i++)
+    {
+      struct variable *v = *iter;
+      char temp_buf[80];
+      char *val_lab;
+
+      assert (v->type == NUMERIC || v->type == ALPHA);
+      tab_text (t, 0, i + 1, TAB_LEFT | TAT_PRINTF, "%s", v->name);
+      
+      {
+       union value val = c->data[v->fv];
+       if (v->type == ALPHA)
+         val.c = c->data[v->fv].s;
+       data_out (temp_buf, &v->print, &val);
+      }
+      
+      temp_buf[v->print.w] = 0;
+      tab_text (t, 1, i + 1, TAT_PRINTF, "%.*s", v->print.w, temp_buf);
+
+      val_lab = get_val_lab (v, c->data[v->fv], 0);
+      if (val_lab)
+       tab_text (t, 2, i + 1, TAB_LEFT, val_lab);
+    }
+  tab_flags (t, SOMF_NO_TITLE);
+  tab_submit (t);
+}
+
+/* This procfunc is substituted for the user-supplied procfunc when
+   SPLIT FILE is active.  This function forms a wrapper around that
+   procfunc by dividing the input into series. */
+static int
+SPLIT_FILE_procfunc (struct ccase *c)
+{
+  static struct ccase *prev_case;
+  struct variable **iter;
+
+  /* The first case always begins a new series.  We also need to
+     preserve the values of the case for later comparison. */
+  if (case_count == 0)
+    {
+      if (prev_case)
+       free (prev_case);
+      prev_case = xmalloc (vfm_sink_info.case_size);
+      memcpy (prev_case, c, vfm_sink_info.case_size);
+
+      dump_splits (c);
+      if (virt_begin_func != NULL)
+       virt_begin_func ();
+      
+      return virt_proc_func (c);
+    }
+
+  /* Compare the value of each SPLIT FILE variable to the values on
+     the previous case. */
+  for (iter = default_dict.splits; *iter; iter++)
+    {
+      struct variable *v = *iter;
+      
+      switch (v->type)
+       {
+       case NUMERIC:
+         if (approx_ne (c->data[v->fv].f, prev_case->data[v->fv].f))
+           goto not_equal;
+         break;
+       case ALPHA:
+         if (memcmp (c->data[v->fv].s, prev_case->data[v->fv].s, v->width))
+           goto not_equal;
+         break;
+       default:
+         assert (0);
+       }
+    }
+  return virt_proc_func (c);
+  
+not_equal:
+  /* The values of the SPLIT FILE variable are different from the
+     values on the previous case.  That means that it's time to begin
+     a new series. */
+  if (end_func != NULL)
+    end_func ();
+  dump_splits (c);
+  if (virt_begin_func != NULL)
+    virt_begin_func ();
+  memcpy (prev_case, c, vfm_sink_info.case_size);
+  return virt_proc_func (c);
+}
+\f
+/* Case compaction. */
+
+/* Copies case SRC to case DEST, compacting it in the process. */
+void
+compact_case (struct ccase *dest, const struct ccase *src)
+{
+  int i;
+  int nval = 0;
+  
+  assert (compaction_necessary);
+
+  if (temporary == 2)
+    {
+      if (dest != compaction_case)
+       memcpy (dest, compaction_case, sizeof (union value) * compaction_nval);
+      return;
+    }
+
+  /* Copy all the variables except the scratch variables from SRC to
+     DEST. */
+  for (i = 0; i < default_dict.nvar; i++)
+    {
+      struct variable *v = default_dict.var[i];
+      
+      if (v->name[0] == '#')
+       continue;
+
+      if (v->type == NUMERIC)
+       dest->data[nval++] = src->data[v->fv];
+      else
+       {
+         int w = DIV_RND_UP (v->width, sizeof (union value));
+         
+         memcpy (&dest->data[nval], &src->data[v->fv], w * sizeof (union value));
+         nval += w;
+       }
+    }
+}
+
+/* Reassigns `fv' for each variable.  Deletes scratch variables. */
+static void
+finish_compaction (void)
+{
+  int copy_index = 0;
+  int nval = 0;
+  int i;
+
+  for (i = 0; i < default_dict.nvar; i++)
+    {
+      struct variable *v = default_dict.var[i];
+
+      if (v->name[0] == '#')
+       {
+         clear_variable (&default_dict, v);
+         free (v);
+         continue;
+       }
+
+      v->fv = nval;
+      if (v->type == NUMERIC)
+       nval++;
+      else
+       nval += DIV_RND_UP (v->width, sizeof (union value));
+      
+      default_dict.var[copy_index++] = v;
+    }
+  if (copy_index != default_dict.nvar)
+    {
+      default_dict.var = xrealloc (default_dict.var,
+                                  sizeof *default_dict.var * copy_index);
+      default_dict.nvar = copy_index;
+    }
+}
+
+  
diff --git a/src/vfm.h b/src/vfm.h
new file mode 100644 (file)
index 0000000..b2a1e6e
--- /dev/null
+++ b/src/vfm.h
@@ -0,0 +1,100 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !vfm_h
+#define vfm_h 1
+
+#include "cases.h"
+#include <time.h>
+
+/* This is the time at which vfm was last invoked. */
+extern time_t last_vfm_invocation;
+
+/* This is the case that is to be filled in by input programs. */
+extern struct ccase *temp_case;
+
+/* `value' indexes to initialize to particular values for certain cases. */
+extern struct long_vec reinit_sysmis;  /* SYSMIS for every case. */
+extern struct long_vec reinit_blanks;  /* Blanks for every case. */
+extern struct long_vec init_zero;      /* Zero for first case only. */
+extern struct long_vec init_blanks;    /* Blanks for first case only. */
+
+/* A case stream: either a source or a sink, depending on context. */
+struct case_stream
+  {
+    /* Initializes sink. */
+    void (*init) (void);
+    
+    /* Reads all the cases and passes them to WRITE_CASE. */
+    void (*read) (void);
+
+    /* Writes a single case, temp_case. */
+    void (*write) (void);
+
+    /* Switches mode from sink to source. */
+    void (*mode) (void);
+    
+    /* Discards source's internal data. */
+    void (*destroy_source) (void);
+
+    /* Discards sink's internal data. */
+    void (*destroy_sink) (void);
+
+    /* Identifying name for the stream. */
+    const char *name;
+  };
+
+/* This is used to read from the active file. */
+extern struct case_stream *vfm_source;
+
+/* This is used to write to the replacement active file. */
+extern struct case_stream *vfm_sink;
+
+/* General data streams. */
+extern struct case_stream vfm_memory_stream;
+extern struct case_stream vfm_disk_stream;
+extern struct case_stream sort_stream;
+extern struct case_stream flip_stream;
+
+/* Streams that are only sources. */
+extern struct case_stream data_list_source;
+extern struct case_stream input_program_source;
+extern struct case_stream file_type_source;
+extern struct case_stream get_source;
+extern struct case_stream import_source;
+extern struct case_stream matrix_data_source;
+
+/* Number of cases to lag. */
+extern int n_lag;
+
+extern int (*write_case) (void);
+
+void procedure (void (*beginfunc) (void),
+               int (*procfunc) (struct ccase *curcase),
+               void (*endfunc) (void));
+struct ccase *lagged_case (int n_before);
+void compact_case (struct ccase *dest, const struct ccase *src);
+void page_to_disk (void);
+
+void process_active_file (void (*beginfunc) (void),
+                         int (*casefunc) (struct ccase *curcase),
+                         void (*endfunc) (void));
+void process_active_file_output_case (void);
+
+#endif /* !vfm_h */
diff --git a/src/vfmP.h b/src/vfmP.h
new file mode 100644 (file)
index 0000000..6454da8
--- /dev/null
@@ -0,0 +1,72 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#if !vfmP_h
+#define vfmP_h 1
+
+#include "var.h"
+
+/* Linked list of cases. */
+struct case_list 
+  {
+    struct case_list *next;
+    struct ccase c;
+  };
+
+/* Describes a data stream, either a source or a sink. */
+struct stream_info
+  {
+    int case_size;             /* Size of one case in bytes. */
+    int ncases;                        /* Number of cases. */
+    int nval;                  /* Number of `value' elements per case. */
+  };
+
+/* Information about the data source. */
+extern struct stream_info vfm_source_info;
+
+/* Information about the data sink. */
+extern struct stream_info vfm_sink_info;
+
+/* Memory case stream. */
+
+/* List of cases stored in the stream. */
+extern struct case_list *memory_source_cases;
+extern struct case_list *memory_sink_cases;
+
+/* Current case. */
+extern struct case_list *memory_sink_iter;
+
+/* Maximum number of cases. */
+extern int memory_sink_max_cases;
+
+/* Nonzero if the case needs to have values deleted before being
+   stored, zero otherwise. */
+extern int compaction_necessary;
+
+/* Number of values after compaction, or the same as
+   vfm_sink_info.nval, if compaction is not necessary. */
+extern int compaction_nval;
+
+/* Temporary case buffer with enough room for `compaction_nval'
+   `value's. */
+extern struct ccase *compaction_case;
+
+void compact_case (struct ccase *dest, const struct ccase *src);
+
+#endif /* !vfmP_h */
diff --git a/src/weight.c b/src/weight.c
new file mode 100644 (file)
index 0000000..1d7fe9e
--- /dev/null
@@ -0,0 +1,121 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdio.h>
+#include "command.h"
+#include "error.h"
+#include "lexer.h"
+#include "str.h"
+#include "var.h"
+
+/* Notes:
+
+   If the weighting variable is deleted somehow (for instance by
+   end-of-scope of TEMPORARY), weighting must be canceled.
+
+   Scratch vars may not be used for weighting. */
+
+/* WEIGHT transformation. */
+struct weight_trns
+  {
+    struct trns_header h;
+    int src;                   /* `value' index of weighting variable. */
+    int dest;                  /* `value' index of $WEIGHT. */
+  };
+
+int
+cmd_weight (void)
+{
+  lex_match_id ("WEIGHT");
+
+  if (lex_match_id ("OFF"))
+    default_dict.weight_var[0] = 0;
+  else
+    {
+      struct variable *v;
+
+      lex_match (T_BY);
+      v = parse_variable ();
+      if (!v)
+       return CMD_FAILURE;
+      if (v->type == ALPHA)
+       {
+         msg (SE, _("The weighting variable must be numeric."));
+         return CMD_FAILURE;
+       }
+      if (v->name[0] == '#')
+       {
+         msg (SE, _("The weighting variable may not be scratch."));
+         return CMD_FAILURE;
+       }
+
+      strcpy (default_dict.weight_var, v->name);
+    }
+
+  return lex_end_of_command ();
+}
+
+#if 0 /* FIXME: dead code. */
+static int
+weight_trns_proc (any_trns * pt, ccase * c)
+{
+  weight_trns *t = (weight_trns *) pt;
+
+  c->data[t->dest].f = c->data[t->src].f;
+  return -1;
+}
+#endif
+\f
+/* Global functions. */ 
+
+/* Sets the weight_index member of dictionary D to an appropriate
+   value for the value of weight_var, and returns the weighting
+   variable if any or NULL if none. */
+struct variable *
+update_weighting (struct dictionary * d)
+{
+  if (d->weight_var[0])
+    {
+      struct variable *v = find_dict_variable (d, d->weight_var);
+      if (v && v->type == NUMERIC)
+       {
+         d->weight_index = v->fv;
+         return v;
+       }
+      else
+       {
+#if GLOBAL_DEBUGGING
+         printf (_("bad weighting variable, canceling\n"));
+#endif
+         d->weight_var[0] = 0;
+       }
+    }
+
+  d->weight_index = -1;
+  return NULL;
+}
+
+/* Turns off case weighting for dictionary D. */
+void
+stop_weighting (struct dictionary * d)
+{
+  d->weight_var[0] = 0;
+}
diff --git a/stamp-h.in b/stamp-h.in
new file mode 100644 (file)
index 0000000..9788f70
--- /dev/null
@@ -0,0 +1 @@
+timestamp
diff --git a/sysdeps/ChangeLog b/sysdeps/ChangeLog
new file mode 100644 (file)
index 0000000..74e6766
--- /dev/null
@@ -0,0 +1,9 @@
+Sun Aug  9 11:17:39 1998  Ben Pfaff  <blp@gnu.org>
+
+       * README: New file.
+
+----------------------------------------------------------------------
+Local Variables:
+mode: change-log
+version-control: never
+End:
diff --git a/sysdeps/README b/sysdeps/README
new file mode 100644 (file)
index 0000000..5a4ede6
--- /dev/null
@@ -0,0 +1,8 @@
+-*- text -*-
+
+The files in this directory were at one time useful for compiling PSPP
+under Borland C++ 5.0 for Windows.  They may or may not be useful any
+longer.  They are provided without any assurance that they are up to
+date.  Use at your own risk.
+
+-blp
diff --git a/sysdeps/borlandc5.0/ChangeLog b/sysdeps/borlandc5.0/ChangeLog
new file mode 100644 (file)
index 0000000..0beaf5b
--- /dev/null
@@ -0,0 +1,60 @@
+Sun Aug  9 11:15:17 1998  Ben Pfaff  <blp@gnu.org>
+
+       * pspp.iwz.in: Update name of sm-gnu-head.bmp.
+
+       * sm-gnu-head.bmp: Renamed sm-gnu-hd.bmp.
+
+Fri Dec  5 23:01:44 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.ico: Renamed pspp.ico.
+
+       * fiasco.ide: Renamed pspp.ide.
+
+       * fiasco.iwz.in: Renamed pspp.iwz.in.
+
+Wed Aug 20 12:52:43 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.iwz.in: Updated.
+
+Sat Aug 16 11:02:38 1997  Ben Pfaff  <blp@gnu.org>
+
+       * mk-bc5-dist: No longer run from toplevel Makefile.
+
+       * unix2dos.pl: Moved here from the top level.
+
+       * pref.h: Removed.
+
+Thu Aug 14 22:19:46 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.iwz.in: Revised.
+
+       * pref.h: Updated from pref.h.orig.
+
+       * fiasco.ide: Updated.
+
+Sun Aug  3 11:50:23 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fiasco.ico: New file, icon for Fiasco.
+
+       * fiasco.iwz.in: New file, InstallShield template for Fiasco.
+
+       * setup1.bmp: New file, Bitmap displayed during installation.
+
+       * sm-gnu-head.bmp: New file, small GNU head from
+       www.gnu.org converted to BMP format.
+
+Thu Jul 17 02:20:09 1997  Ben Pfaff  <blp@gnu.org>
+
+       * New directory for Windows support via Borland C++ 5.0.
+
+       * bc5-con32s.c: Combines _read.c and _write from the old
+       sysdeps/borlandc4.0 directory.
+
+       * config.h, libintl.h, pref.h, version.c: Standard files adapted
+       to Borland C++ 5.0.
+
+----------------------------------------------------------------------
+Local Variables:
+mode: change-log
+version-control: never
+End:
diff --git a/sysdeps/borlandc5.0/bc5-con32s.c b/sysdeps/borlandc5.0/bc5-con32s.c
new file mode 100644 (file)
index 0000000..ec55ab8
--- /dev/null
@@ -0,0 +1,95 @@
+/* con32s - emulates console under Windows.
+   Copyright (C) 1997, 1998 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* This replaces a few of the Borland C++ library functions.  It does
+   not use any of the runtime library header files, so you do not need
+   the runtime library source in order to compile it. */
+
+#include <io.h>
+#include <windef.h>
+#include <wincon.h>
+
+/* 1=It is necessary to emulate the console window. */
+int _emu_console;
+
+/* Exported by con32s.c. */
+extern int _blp_console_read (void *buf, unsigned len);
+
+/* Exported by Borland runtime library. */
+extern long _handles[];
+extern int __IOerror (int);
+extern int __NTerror (void);
+
+/* Replaces Borland library function. */
+int
+_rtl_read (int fd, void *buf, unsigned len)
+{
+  DWORD nread;
+
+  if ((unsigned) fd >= _nfile)
+    return __IOerror (ERROR_INVALID_HANDLE);
+
+  /* Redirect stdin to the faked console window. */
+  if (_emu_console && fd < 3)
+    return _blp_console_read (buf, len);
+
+  if (ReadFile ((HANDLE) _handles[fd], buf, (DWORD) len, &nread, NULL) != 1)
+    return __NTerror ();
+  else
+    return (int) nread;
+}
+
+/* Replaces Borland library function. */
+int
+_rtl_write (int fd, const void *buf, unsigned int len)
+{
+  DWORD written;
+
+  if ((unsigned) fd >= _nfile)
+    return __IOerror (ERROR_INVALID_HANDLE);
+
+  /* Redirect stdout, stderr to the faked console window. */
+  if (_emu_console && fd < 3)
+    return _blp_console_write (buf, len);
+
+  if (WriteFile ((HANDLE) _handles[fd], (PVOID) buf, (DWORD) len, &written,
+                NULL) != 1)
+    return __NTerror ();
+  else
+    return (int) written;
+}
+
+void
+determine_os (void)
+{
+#pragma startup determine_os 64
+  DWORD nButtons;
+
+  /* Try out a random console function.  If it fails then we must not
+     have a console.
+
+     Believe it or not, this seems to be the only way to determine
+     reliably whether we're running under 3.1.  If you know a better
+     way, let me know. */
+  if (GetNumberOfConsoleMouseButtons (&nButtons))
+    _emu_console = 0;
+  else
+    _emu_console = 1;
+}
+
diff --git a/sysdeps/borlandc5.0/config.h b/sysdeps/borlandc5.0/config.h
new file mode 100644 (file)
index 0000000..a90e84c
--- /dev/null
@@ -0,0 +1,303 @@
+/* config.h.in.  Generated automatically from configure.in by autoheader.  */
+/* Special definitions, to process by autoheader.
+        Copyright (C) 1997 Free Software Foundation. */
+
+/* Definitions for byte order, according to significance of bytes, from low
+        addresses to high addresses.  The value is what you get by putting '4'
+        in the most significant byte, '3' in the second most significant byte,
+        '2' in the second least significant byte, and '1' in the least
+        significant byte.  These definitions never need to be modified. */
+#define BIG    4321    /* 68k */
+#define LITTLE  1234   /* i[3456]86 */
+#define UNKNOWN 0000   /* Endianness must be determined at runtime. */
+
+/* Definitions for floating-point representation. */
+#define FPREP_IEEE754  754     /* The usual IEEE-754 format. */
+#define FPREP_UNKNOWN  666     /* Triggers an error at compile time. */
+
+/* We want prototypes for all the GNU extensions. */
+#define _GNU_SOURCE    1
+
+/* The concatenation of the strings "GNU ", and PACKAGE.  */
+#define GNU_PACKAGE "GNU PSPP"
+
+/* Define to the name of the distribution.  */
+#define PACKAGE "PSPP"
+
+/* Define to 1 if ANSI function prototypes are usable.  */
+#define PROTOTYPES 1
+
+/* Define to the version of the distribution.  */
+#define VERSION "0.1.0"
+
+/* Define if using alloca.c.  */
+#undef C_ALLOCA
+
+/* Define to empty if the keyword does not work.  */
+#undef const
+
+/* Define to one of _getb67, GETB67, getb67 for Cray-2 and Cray-YMP systems.
+        This function is required for alloca.c support on those systems.  */
+#define CRAY_STACKSEG_END
+
+/* Define if you have alloca, as a function or macro.  */
+#undef HAVE_ALLOCA
+
+/* Define if you have <alloca.h> and it should be used (not on Ultrix).  */
+#undef HAVE_ALLOCA_H
+
+/* Define if you don't have vprintf but do have _doprnt.  */
+#undef HAVE_DOPRNT
+
+/* Define if you have a working `mmap' system call.  */
+#undef HAVE_MMAP
+
+/* Define if you have the vprintf function.  */
+#define HAVE_VPRINTF 1
+
+/* Define as __inline if that's what the C compiler calls it.  */
+#define inline
+
+/* Define to `long' if <sys/types.h> doesn't define.  */
+#undef off_t
+
+/* Define if you need to in order for stat and other things to work.  */
+#undef _POSIX_SOURCE
+
+/* Define to `unsigned' if <sys/types.h> doesn't define.  */
+#undef size_t
+
+/* If using the C implementation of alloca, define if you know the
+        direction of stack growth for your system; otherwise it will be
+        automatically deduced at run-time.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown
+ */
+#undef STACK_DIRECTION
+
+/* Define if the `S_IS*' macros in <sys/stat.h> do not work properly.  */
+#undef STAT_MACROS_BROKEN
+
+/* Define if you have the ANSI C header files.  */
+#define STDC_HEADERS 1
+
+/* Define if you can safely include both <sys/time.h> and <time.h>.  */
+#undef TIME_WITH_SYS_TIME
+
+/* Define if your <sys/time.h> declares struct tm.  */
+#undef TM_IN_SYS_TIME
+
+/* Define if sprintf() returns the number of characters written to
+        the destination string, excluding the null terminator. */
+#define HAVE_GOOD_SPRINTF 1
+
+/* Define endianness of computer here as BIG or LITTLE, if known.
+        If not known, define as UNKNOWN. */
+#define ENDIAN LITTLE
+
+/* Define as floating-point representation of this computer.  For
+        i386, m68k, and other common chips, this is FPREP_IEEE754. */
+#define FPREP FPREP_IEEE754
+
+/* Number of digits in longest `long' value, including sign.  This is
+        usually 11, for 32-bit `long's, or 19, for 64-bit `long's. */
+#define INT_DIGITS 11
+
+/* Define if you have the history library (-lhistory).  */
+#undef HAVE_LIBHISTORY
+
+/* Define if you have the termcap library (-ltermcap).  */
+#undef HAVE_LIBTERMCAP
+
+/* Define if your locale.h file contains LC_MESSAGES.  */
+#define HAVE_LC_MESSAGES 1
+
+/* Define to 1 if NLS is requested.  */
+#undef ENABLE_NLS
+
+/* Define as 1 if you have catgets and don't want to use GNU gettext.  */
+#undef HAVE_CATGETS
+
+/* Define as 1 if you have gettext and don't want to use GNU gettext.  */
+#undef HAVE_GETTEXT
+
+/* Define as 1 if you have the stpcpy function.  */
+#define HAVE_STPCPY 1
+
+/* The number of bytes in a double.  */
+#define SIZEOF_DOUBLE 8
+
+/* The number of bytes in a float.  */
+#define SIZEOF_FLOAT 4
+
+/* The number of bytes in a int.  */
+#define SIZEOF_INT 4
+
+/* The number of bytes in a long.  */
+#define SIZEOF_LONG 4
+
+/* The number of bytes in a long double.  */
+#define SIZEOF_LONG_DOUBLE 12
+
+/* The number of bytes in a long long.  */
+#define SIZEOF_LONG_LONG
+
+/* The number of bytes in a short.  */
+#define SIZEOF_SHORT 2
+
+/* Define if you have the __argz_count function.  */
+#undef HAVE___ARGZ_COUNT
+
+/* Define if you have the __argz_next function.  */
+#undef HAVE___ARGZ_NEXT
+
+/* Define if you have the __argz_stringify function.  */
+#undef HAVE___ARGZ_STRINGIFY
+
+/* Define if you have the __setfpucw function.  */
+#undef HAVE___SETFPUCW
+
+/* Define if you have the dcgettext function.  */
+#undef HAVE_DCGETTEXT
+
+/* Define if you have the finite function.  */
+#undef HAVE_FINITE
+
+/* Define if you have the getcwd function.  */
+#undef HAVE_GETCWD
+
+/* Define if you have the getdelim function.  */
+#undef HAVE_GETDELIM
+
+/* Define if you have the gethostname function.  */
+#undef HAVE_GETHOSTNAME
+
+/* Define if you have the getline function.  */
+#undef HAVE_GETLINE
+
+/* Define if you have the getpagesize function.  */
+#undef HAVE_GETPAGESIZE
+
+/* Define if you have the getpid function.  */
+#define HAVE_GETPID 1
+
+/* Define if you have the isinf function.  */
+#undef HAVE_ISINF
+
+/* Define if you have the isnan function.  */
+#undef HAVE_ISNAN
+
+/* Define if you have the memchr function.  */
+#define HAVE_MEMCHR 1
+
+/* Define if you have the memmem function.  */
+#define HAVE_MEMMEM 0
+
+/* Define if you have the memmove function.  */
+#define HAVE_MEMMOVE 1
+
+/* Define if you have the memset function.  */
+#define HAVE_MEMSET 1
+
+/* Define if you have the munmap function.  */
+#undef HAVE_MUNMAP
+
+/* Define if you have the putenv function.  */
+#define HAVE_PUTENV 1
+
+/* Define if you have the setenv function.  */
+#undef HAVE_SETENV
+
+/* Define if you have the setlocale function.  */
+#define HAVE_SETLOCALE 1
+
+/* Define if you have the stpcpy function.  */
+#define HAVE_STPCPY 1
+
+/* Define if you have the strcasecmp function.  */
+#undef HAVE_STRCASECMP
+
+/* Define if you have the strchr function.  */
+#undef HAVE_STRCHR
+
+/* Define if you have the strerror function.  */
+#define HAVE_STRERROR 1
+
+/* Define if you have the strncasecmp function.  */
+#undef HAVE_STRNCASECMP
+
+/* Define if you have the strpbrk function.  */
+#define HAVE_STRPBRK 1
+
+/* Define if you have the strstr function.  */
+#define HAVE_STRSTR 1
+
+/* Define if you have the strtod function.  */
+#define HAVE_STRTOD 1
+
+/* Define if you have the strtol function.  */
+#define HAVE_STRTOL 1
+
+/* Define if you have the strtoul function.  */
+#define HAVE_STRTOUL 1
+
+/* Define if you have the <argz.h> header file.  */
+#undef HAVE_ARGZ_H
+
+/* Define if you have the <fpu_control.h> header file.  */
+#undef HAVE_FPU_CONTROL_H
+
+/* Define if you have the <limits.h> header file.  */
+#define HAVE_LIMITS_H 1
+
+/* Define if you have the <locale.h> header file.  */
+#define HAVE_LOCALE_H 1
+
+/* Define if you have the <malloc.h> header file.  */
+#define HAVE_MALLOC_H 1
+
+/* Define if you have the <memory.h> header file.  */
+#define HAVE_MEMORY_H 1
+
+/* Define if you have the <nl_types.h> header file.  */
+#undef HAVE_NL_TYPES_H
+
+/* Define if you have the <readline/history.h> header file.  */
+#undef HAVE_READLINE_HISTORY_H
+
+/* Define if you have the <readline/readline.h> header file.  */
+#undef HAVE_READLINE_READLINE_H
+
+/* Define if you have the <string.h> header file.  */
+#define HAVE_STRING_H 1
+
+/* Define if you have the <sys/time.h> header file.  */
+#undef HAVE_SYS_TIME_H
+
+/* Define if you have the <sys/types.h> header file.  */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define if you have the <termcap.h> header file.  */
+#undef HAVE_TERMCAP_H
+
+/* Define if you have the <unistd.h> header file.  */
+#undef HAVE_UNISTD_H
+
+/* Define if you have the <values.h> header file.  */
+#define HAVE_VALUES_H 1
+
+/* Define if you have the i library (-li).  */
+#undef HAVE_LIBI
+
+/* Define if you have the m library (-lm).  */
+#undef HAVE_LIBM
+
+/* Define if you have the readline library (-lreadline).  */
+#undef HAVE_LIBREADLINE
+
+#include <pref.h>
+
+/* Local Variables: */
+/* mode:c */
+/* End: */
diff --git a/sysdeps/borlandc5.0/libintl.h b/sysdeps/borlandc5.0/libintl.h
new file mode 100644 (file)
index 0000000..67dbe22
--- /dev/null
@@ -0,0 +1,20 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997, 1998 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+/* Nothing needed here. */
diff --git a/sysdeps/borlandc5.0/mk-bc5-dist b/sysdeps/borlandc5.0/mk-bc5-dist
new file mode 100755 (executable)
index 0000000..fc6e901
--- /dev/null
@@ -0,0 +1,69 @@
+#! /bin/sh -e
+
+# This script makes a source distribution for compilation under
+# Borland C++ 5.0.  It also produces a .iwz file for use with
+# InstallShield Express.
+
+if test "$1" = ""; then
+  echo "usage: $0 \'DOS-path-to-PSPP-source\' \'DOS-path-to-Borland-C++-5.0-root\'"
+fi
+
+test -f src/q2c.c || (echo "Not in PSPP source directory" && exit 1)
+
+make distdir
+
+DOSROOT="$1"
+VERSION=`sed -ne 's/^.*\[//;s/].*$//;/^[0-9]*\.[0-9]*\.[0-9]*$/p' < configure.in`
+BC5ROOT=`pwd`/pspp-$VERSION-bc5
+DISTROOT=`pwd`
+
+rm -rf $BC5ROOT
+cp -r pspp-$VERSION $BC5ROOT
+rm -f $DISTROOT/manualfiles.tmp
+rm -f $DISTROOT/testsfiles.tmp
+
+cd $BC5ROOT/doc
+texi2html -number -monolithic FAQ.texi
+
+mkdir $BC5ROOT/manual
+cd $BC5ROOT/manual
+texi2html -menu -number -split_node ../doc/pspp.texi
+cp pspp_toc.html index.html
+
+n_manual=0
+for d in *; do
+  n_manual=`expr $n_manual + 1`
+  echo "Group5File$n_manual=${DOSROOT}\\MANUAL\\$d" >> $DISTROOT/manualfiles.tmp
+done
+
+cd $BC5ROOT/tests
+n_tests=0
+for d in *; do
+  n_tests=`expr $n_tests + 1`
+  echo "Group3File$n_tests=${DOSROOT}\\TESTS\\$d" >> $DISTROOT/testsfiles.tmp
+done
+
+SEDDOSROOT=`echo "$1" | sed 's/\\\\/\\\\\\\\/'`
+echo "s%@BASEDIR@%$SEDDOSROOT%g" > $DISTROOT/bc5.sed
+SEDBC5BASEDIR=`echo "$2" | sed 's/\\\\/\\\\\\\\/'`
+echo "s%@BC5BASEDIR@%$SEDBC5BASEDIR%g" >> $DISTROOT/bc5.sed
+echo "s%@MANUALCOUNT@%$n_manual%g" >> $DISTROOT/bc5.sed
+echo "s%@TESTSCOUNT@%$n_tests%g" >> $DISTROOT/bc5.sed
+
+IN=$BC5ROOT/sysdeps/borlandc5.0/pspp.iwz.in
+OUT=$BC5ROOT/pspp.iwz
+sed -n -f $DISTROOT/bc5.sed -e '1,/^Group3Dir/p' < $IN > $OUT
+cat $DISTROOT/testsfiles.tmp >> $OUT
+sed -n -f $DISTROOT/bc5.sed -e '/^Group4Size/,/^Group5Dir/p' < $IN >> $OUT
+cat $DISTROOT/manualfiles.tmp < $IN >> $OUT
+sed -n -f $DISTROOT/bc5.sed -e '/^Group5Size/,$p' < $IN >> $OUT
+
+cp $BC5ROOT/sysdeps/borlandc5.0/pspp.ide $BC5ROOT/pspp.ide
+cp $BC5ROOT/pref.h.orig $BC5ROOT/sysdeps/borlandc5.0/pref.h
+
+rm $DISTROOT/manualfiles.tmp
+rm $DISTROOT/testsfiles.tmp
+rm $DISTROOT/bc5.sed
+
+find $BC5ROOT -type f | xargs perl $DISTROOT/sysdeps/borlandc5.0/unix2dos.pl
+find $BC5ROOT -name \*.bak | xargs rm -f
diff --git a/sysdeps/borlandc5.0/pspp.ico b/sysdeps/borlandc5.0/pspp.ico
new file mode 100755 (executable)
index 0000000..4157a06
Binary files /dev/null and b/sysdeps/borlandc5.0/pspp.ico differ
diff --git a/sysdeps/borlandc5.0/pspp.ide b/sysdeps/borlandc5.0/pspp.ide
new file mode 100644 (file)
index 0000000..be51720
Binary files /dev/null and b/sysdeps/borlandc5.0/pspp.ide differ
diff --git a/sysdeps/borlandc5.0/pspp.iwz.in b/sysdeps/borlandc5.0/pspp.iwz.in
new file mode 100755 (executable)
index 0000000..867f1ed
--- /dev/null
@@ -0,0 +1,383 @@
+[InstallShield Wizard]
+iDate=0
+iTime=0
+Flag=0
+ISX.EXE Size=668160
+ISX.EXE Date=12:49:10AM  12/18/1996
+ISX.EXE Ver=1.11.0.0
+SETUP.EXE Size=44928
+SETUP.EXE Date=1:04:12PM  11/4/1996
+SETUP.EXE Ver=3.0.107.0
+SETUP.INS Size=66760
+SETUP.INS Date=5:50:16PM  3/7/1997
+SETUP.INS Ver=Not available
+_INST16.EX_ Size=66760
+_INST16.EX_ Date=5:50:16PM  3/7/1997
+_INST16.EX_ Ver=Not available
+_INST32I.EX_ Size=320276
+_INST32I.EX_ Date=4:17:32PM  11/5/1996
+_INST32I.EX_ Ver=Not available
+ISDEPEND.INI Size=5102
+ISDEPEND.INI Date=4:31:20PM  6/11/1996
+ISDEPEND.INI Ver=Not available
+SWDEPEND.INI Size=4605
+SWDEPEND.INI Date=1:12:52AM  3/12/1997
+SWDEPEND.INI Ver=Not available
+ICOMP.EXE Size=119808
+ICOMP.EXE Date=3:05:10PM  1/15/1996
+ICOMP.EXE Ver=3.00.062
+SPLIT.EXE Size=90624
+SPLIT.EXE Date=3:09:36PM  1/15/1996
+SPLIT.EXE Ver=3.00.060
+PACKLIST.EXE Size=87552
+PACKLIST.EXE Date=3:10:30PM  1/15/1996
+PACKLIST.EXE Ver=3.00.060
+Version=1.11a
+DevTool=for Borland C++ 5.0
+Platform=Win32
+PtrBase=30100
+PtrPosY=227
+PtrPage=4
+DisksBuilt=1
+DisksDir=PSPP\144MB\
+TabsVisit=1111111110000111111
+LangNum=0
+
+[VisualDesign]
+AppName=PSPP
+AppExe=[Program Files]\pspp.exe
+Version=0.1.0
+Company=Free Software Foundation
+Title=PSPP
+TitleType=1
+BackgrndBmp=@BASEDIR@\sysdeps\borlandc5.0\sm-gnu-hd.bmp
+BackgrndAlign=4
+Backgrnd=1
+BackgrndColor=0
+Uninstall=1
+Silent=1
+SmsMode=0
+
+[RegEntries]
+Reg1Path=HKEY_CLASSES_ROOT
+Reg1Val1Type=0
+Reg1Val1Name=(Default)
+Reg1Val1Data=(value not set)
+Reg1Vals=1
+Reg2Path=HKEY_CURRENT_USER
+Reg2Val1Type=0
+Reg2Val1Name=(Default)
+Reg2Val1Data=(value not set)
+Reg2Vals=1
+Reg3Path=HKEY_LOCAL_MACHINE
+Reg3Val1Type=0
+Reg3Val1Name=(Default)
+Reg3Val1Data=(value not set)
+Reg3Vals=1
+Reg4Path=HKEY_USERS
+Reg4Val1Type=0
+Reg4Val1Name=(Default)
+Reg4Val1Data=(value not set)
+Reg4Vals=1
+Reg5Path=HKEY_CURRENT_CONFIG
+Reg5Val1Type=0
+Reg5Val1Name=(Default)
+Reg5Val1Data=(value not set)
+Reg5Vals=1
+Reg6Path=HKEY_DYN_DATA
+Reg6Val1Type=0
+Reg6Val1Name=(Default)
+Reg6Val1Data=(value not set)
+Reg6Vals=1
+Reg7Path=HKEY_CLASSES_ROOT\PSPP.Script
+Reg7PathUninstall=1
+Reg7Val1Type=0
+Reg7Val1Name=(Default)
+Reg7Val1Data=PSPP Script
+Reg7Vals=1
+Reg8Path=HKEY_CLASSES_ROOT\PSPP.Script\shell
+Reg8PathUninstall=1
+Reg8Val1Type=0
+Reg8Val1Name=(Default)
+Reg8Val1Data=(value not set)
+Reg8Vals=1
+Reg9Path=HKEY_CLASSES_ROOT\PSPP.Script\shell\open
+Reg9PathUninstall=1
+Reg9Val1Type=0
+Reg9Val1Name=(Default)
+Reg9Val1Data=(value not set)
+Reg9Vals=1
+Reg10Path=HKEY_CLASSES_ROOT\.stat
+Reg10PathUninstall=1
+Reg10Val1Type=0
+Reg10Val1Name=(Default)
+Reg10Val1Data=PSPP.Script
+Reg10Vals=1
+Reg11Path=HKEY_CLASSES_ROOT\PSPP.Script\DefaultIcon
+Reg11PathUninstall=1
+Reg11Val1Type=0
+Reg11Val1Name=(Default)
+Reg11Val1Data=<INSTALLDIR>\PSPP.ICO
+Reg11Vals=1
+Reg12Path=HKEY_CLASSES_ROOT\.sps
+Reg12PathUninstall=1
+Reg12Val1Type=0
+Reg12Val1Name=(Default)
+Reg12Val1Data=PSPP.Script
+Reg12Vals=1
+Reg13Path=HKEY_CLASSES_ROOT\.spss
+Reg13PathUninstall=1
+Reg13Val1Type=0
+Reg13Val1Name=(Default)
+Reg13Val1Data=PSPP.Script
+Reg13Vals=1
+Reg14Path=HKEY_CLASSES_ROOT\PSPP.Script\shell\open\command
+Reg14PathUninstall=1
+Reg14Val1Type=0
+Reg14Val1Name=(Default)
+Reg14Val1Data=<INSTALLDIR>\PSPP.EXE %1
+Reg14Vals=1
+Reg15Path=HKEY_CLASSES_ROOT\.lst
+Reg15PathUninstall=1
+Reg15Val1Type=0
+Reg15Val1Name=(Default)
+Reg15Val1Data=PSPP.Listing
+Reg15Vals=1
+Reg16Path=HKEY_CLASSES_ROOT\.list
+Reg16PathUninstall=1
+Reg16Val1Type=0
+Reg16Val1Name=(Default)
+Reg16Val1Data=PSPP.Listing
+Reg16Vals=1
+Reg17Path=HKEY_CLASSES_ROOT\PSPP.Listing
+Reg17PathUninstall=1
+Reg17Val1Type=0
+Reg17Val1Name=(Default)
+Reg17Val1Data=PSPP Listing Output
+Reg17Vals=1
+Reg18Path=HKEY_CLASSES_ROOT\PSPP.Listing\shell
+Reg18PathUninstall=1
+Reg18Val1Type=0
+Reg18Val1Name=(Default)
+Reg18Val1Data=(value not set)
+Reg18Vals=1
+Reg19Path=HKEY_CLASSES_ROOT\PSPP.Listing\shell\open
+Reg19PathUninstall=1
+Reg19Val1Type=0
+Reg19Val1Name=(Default)
+Reg19Val1Data=(value not set)
+Reg19Vals=1
+Reg20Path=HKEY_CLASSES_ROOT\PSPP.Listing\shell\open\command
+Reg20PathUninstall=1
+Reg20Val1Type=0
+Reg20Val1Name=(Default)
+Reg20Val1Data=<WINDIR>\NOTEPAD.EXE %1
+Reg20Vals=1
+Reg21Path=HKEY_CLASSES_ROOT\PSPP.Listing\DefaultIcon
+Reg21PathUninstall=1
+Reg21Val1Type=0
+Reg21Val1Name=(Default)
+Reg21Val1Data=<INSTALLDIR>\PSPP.ICO
+Reg21Vals=1
+Regs=21
+
+[Registry]
+Reg1Path=HKEY_CLASSES_ROOT\PSPP.Script
+Reg1PathUninstall=1
+Reg1ValName=(Default)
+Reg1ValType=0
+Reg1ValData=PSPP Script
+Reg2Path=HKEY_CLASSES_ROOT\PSPP.Script\shell
+Reg2PathUninstall=1
+Reg3Path=HKEY_CLASSES_ROOT\PSPP.Script\shell\open
+Reg3PathUninstall=1
+Reg4Path=HKEY_CLASSES_ROOT\.stat
+Reg4PathUninstall=1
+Reg4ValName=(Default)
+Reg4ValType=0
+Reg4ValData=PSPP.Script
+Reg5Path=HKEY_CLASSES_ROOT\PSPP.Script\DefaultIcon
+Reg5PathUninstall=1
+Reg5ValName=(Default)
+Reg5ValType=0
+Reg5ValData=<INSTALLDIR>\PSPP.ICO
+Reg6Path=HKEY_CLASSES_ROOT\.sps
+Reg6PathUninstall=1
+Reg6ValName=(Default)
+Reg6ValType=0
+Reg6ValData=PSPP.Script
+Reg7Path=HKEY_CLASSES_ROOT\.spss
+Reg7PathUninstall=1
+Reg7ValName=(Default)
+Reg7ValType=0
+Reg7ValData=PSPP.Script
+Reg8Path=HKEY_CLASSES_ROOT\PSPP.Script\shell\open\command
+Reg8PathUninstall=1
+Reg8ValName=(Default)
+Reg8ValType=0
+Reg8ValData=<INSTALLDIR>\PSPP.EXE %1
+Reg9Path=HKEY_CLASSES_ROOT\.lst
+Reg9PathUninstall=1
+Reg9ValName=(Default)
+Reg9ValType=0
+Reg9ValData=PSPP.Listing
+Reg10Path=HKEY_CLASSES_ROOT\.list
+Reg10PathUninstall=1
+Reg10ValName=(Default)
+Reg10ValType=0
+Reg10ValData=PSPP.Listing
+Reg11Path=HKEY_CLASSES_ROOT\PSPP.Listing
+Reg11PathUninstall=1
+Reg11ValName=(Default)
+Reg11ValType=0
+Reg11ValData=PSPP Listing Output
+Reg12Path=HKEY_CLASSES_ROOT\PSPP.Listing\shell
+Reg12PathUninstall=1
+Reg13Path=HKEY_CLASSES_ROOT\PSPP.Listing\shell\open
+Reg13PathUninstall=1
+Reg14Path=HKEY_CLASSES_ROOT\PSPP.Listing\shell\open\command
+Reg14PathUninstall=1
+Reg14ValName=(Default)
+Reg14ValType=0
+Reg14ValData=<WINDIR>\NOTEPAD.EXE %1
+Reg15Path=HKEY_CLASSES_ROOT\PSPP.Listing\DefaultIcon
+Reg15PathUninstall=1
+Reg15ValName=(Default)
+Reg15ValType=0
+Reg15ValData=<INSTALLDIR>\PSPP.ICO
+Regs=15
+
+[Groups]
+Groups=6
+Group1Size=421589
+Group1Files=6
+Group1Name=Program Files
+Group1Dir=<INSTALLDIR>
+Group1File1=@BASEDIR@\pspp.exe
+Group1File2=@BASEDIR@\config\devices
+Group1File3=@BASEDIR@\config\papersize
+Group1File4=@BASEDIR@\config\ps-prologue
+Group1File5=@BASEDIR@\COPYING
+Group1File6=@BASEDIR@\sysdeps\borlandc5.0\pspp.ICO
+Group2Size=43097
+Group2Files=4
+Group2Name=Help Files - HTML
+Group2Dir=<INSTALLDIR>\HELP
+Group2File1=@BASEDIR@\doc\BUGS.html
+Group2File2=@BASEDIR@\doc\LANGUAGE.html
+Group2File3=@BASEDIR@\doc\README.html
+Group2File4=@BASEDIR@\doc\THANKS.html
+Group3Size=104388
+Group3Files=@TESTSCOUNT@
+Group3Name=Test Files
+Group3Dir=<INSTALLDIR>\TESTS
+Group4Size=63652
+Group4Files=7
+Group4Name=Help Files - ASCII
+Group4Dir=<INSTALLDIR>\HELP\ASCII
+Group4File1=@BASEDIR@\BUGS
+Group4File2=@BASEDIR@\LANGUAGE
+Group4File3=@BASEDIR@\NEWS
+Group4File4=@BASEDIR@\README
+Group4File5=@BASEDIR@\THANKS
+Group4File6=@BASEDIR@\TODO
+Group4File7=@BASEDIR@\AUTHORS
+Group5Size=336614
+Group5Files=@MANUALCOUNT@
+Group5Name=Manual - HTML
+Group5Dir=<INSTALLDIR>\HELP\MANUAL
+Group6Size=229376
+Group6Files=1
+Group6Auto=108
+Group6ID=38308
+Group6Name=System Files
+Group6Dir=<WINSYSDIR>
+Group6File1=@BC5BASEDIR@\BIN\CW3220.DLL
+Group6File1Auto=108
+
+[Components]
+Components=4
+Component1Groups=2
+Component1Name=Application Files
+Component1Description=Application files needed to run the application
+Component1GroupList=1 6
+Component2Groups=2
+Component2Name=Help and Tutorial Files
+Component2Description=Help and tutorial needed for online help
+Component2GroupList=2 4
+Component3Groups=1
+Component3Name=Sample Files
+Component3Description=Sample data files
+Component3GroupList=3
+Component4Groups=1
+Component4Name=Manual - HTML
+Component4Description=On-line manual in World Wide Web HTML format.
+Component4GroupList=5
+
+[Types]
+Types=3
+Type1Components=4
+Type1Name=Custom
+Type1ComponentList=1 2 3 4
+Type2Components=4
+Type2Name=Typical
+Type2ComponentList=1 2 3 4
+Type3Components=1
+Type3Name=Compact
+Type3ComponentList=1
+
+[Sequence]
+DestinationLocationDir=<ProgramFilesDir>\Free Software Foundation\PSPP
+SelectProgramFolderName=PSPP
+WelcomeMessage=1
+LicenseAgreement=1
+LicenseAgreementFile=@BASEDIR@\copying
+DestinationLocation=1
+SetupType=1
+CustomSetup=1
+CustomSetupGroupSel=1
+SelectProgramFolder=1
+BeginFileTransfer=1
+ProgressBar=1
+Billboards=1
+BillboardsFile=@BASEDIR@\sysdeps\borlandc5.0\
+SetupComplete=1
+
+[Icons]
+Icons=5
+Icon1Cmd=[Program Files]\pspp.exe
+Icon1Description=PSPP
+Icon1WorkingDir=[Program Files]
+Icon1IconFile=[Program Files]\pspp.ICO
+Icon1RealFile=@BASEDIR@\sysdeps\borlandc5.0\pspp.ICO
+Icon1WhichIcon=0
+Icon1KeyVirtual=0
+Icon1KeyFlags=0
+Icon2Cmd=[Help Files - HTML]\BUGS.html
+Icon2Description=Bugs
+Icon2WorkingDir=[Help Files - HTML]
+Icon2RealFile=@BASEDIR@\doc\BUGS.html
+Icon2WhichIcon=0
+Icon2KeyVirtual=0
+Icon2KeyFlags=0
+Icon3Cmd=[Help Files - HTML]\LANGUAGE.html
+Icon3Description=Language Notes
+Icon3WorkingDir=[Help Files - HTML]
+Icon3RealFile=@BASEDIR@\doc\LANGUAGE.html
+Icon3WhichIcon=0
+Icon3KeyVirtual=0
+Icon3KeyFlags=0
+Icon4Cmd=[Help Files - HTML]\README.html
+Icon4Description=README
+Icon4WorkingDir=[Help Files - HTML]
+Icon4RealFile=@BASEDIR@\doc\README.html
+Icon4WhichIcon=0
+Icon4KeyVirtual=0
+Icon4KeyFlags=0
+Icon5Cmd=[Manual - HTML]\pspp_toc.html
+Icon5Description=Manual
+Icon5WorkingDir=[Manual - HTML]
+Icon5RealFile=@BASEDIR@\MANUAL\pspp_toc.html
+Icon5WhichIcon=0
+Icon5KeyVirtual=0
+Icon5KeyFlags=0
diff --git a/sysdeps/borlandc5.0/setup1.bmp b/sysdeps/borlandc5.0/setup1.bmp
new file mode 100755 (executable)
index 0000000..620981e
Binary files /dev/null and b/sysdeps/borlandc5.0/setup1.bmp differ
diff --git a/sysdeps/borlandc5.0/sm-gnu-hd.bmp b/sysdeps/borlandc5.0/sm-gnu-hd.bmp
new file mode 100755 (executable)
index 0000000..6aaa8af
Binary files /dev/null and b/sysdeps/borlandc5.0/sm-gnu-hd.bmp differ
diff --git a/sysdeps/borlandc5.0/unix2dos.pl b/sysdeps/borlandc5.0/unix2dos.pl
new file mode 100644 (file)
index 0000000..95ee10d
--- /dev/null
@@ -0,0 +1,19 @@
+#!/usr/bin/perl
+while (<>) {
+    if ($ARGV ne $oldargv) {
+       $translate = -T $ARGV;
+       rename($ARGV, $ARGV . '.bak');
+       open(ARGVOUT, ">$ARGV");
+       select(ARGVOUT);
+       $oldargv = $ARGV;
+    }
+    if ($translate) {
+       chop;
+       $_ .= "\r
+";
+    }
+}
+continue {
+    print;  # this prints to original filename
+}
+select(STDOUT);
diff --git a/sysdeps/borlandc5.0/version.c b/sysdeps/borlandc5.0/version.c
new file mode 100644 (file)
index 0000000..e728476
--- /dev/null
@@ -0,0 +1,7 @@
+#include <config.h>\r
+char bare_version[] = VERSION;\r
+char version[] = GNU_PACKAGE " " VERSION;\r
+char stat_version[] = GNU_PACKAGE " " VERSION \r
+       " (Fri Jul 11 12:33:09 GMT-5:00 1997).";\r
+char host_system[] = "i586-borlandc5.0";\r
+char build_system[] = "i586-borlandc5.0";\r
diff --git a/sysdeps/windows/README b/sysdeps/windows/README
new file mode 100644 (file)
index 0000000..63258b4
--- /dev/null
@@ -0,0 +1,10 @@
+PSPP currently runs only as a console-mode application under Windows
+environments.  Since Windows 3.1 does not provide a console, PSPP
+needs special assistance to run under Windows 3.1.  con32s is a small
+library that I have developed to do this.  This directory contains the
+compiler-independent portion of con32s.  There is also a small
+compiler-dependent portion that need to be written for each compiler.
+See the example for Borland C++ for more details.
+
+       -blp
+
diff --git a/sysdeps/windows/con32s.c b/sysdeps/windows/con32s.c
new file mode 100644 (file)
index 0000000..f184f87
--- /dev/null
@@ -0,0 +1,504 @@
+/* con32s - emulates Windows console.
+   Copyright (C) 1997, 1998 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <winbase.h>
+#include <wingdi.h>
+#include <winuser.h>
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+typedef struct line_struct line;
+struct line_struct
+  {
+    line *next, *prev;         /* next and previous lines */
+    char *text;                        /* text */
+    int len;                   /* number of characters in text */
+    int size;                  /* maximum allocated size for text */
+  };                           /* line */
+
+/* Pointer to tail end of text lines. */
+static line *tail;
+
+/* Console window created. */
+static int inited = 0;
+
+/* Console window title. */
+static const char *title = _("Con32s Console Emulator by Ben Pfaff");
+
+/* Important variables. */
+HINSTANCE _hInstance;
+HINSTANCE _hPrev;
+LPSTR _cmdline;
+int _nCmdShow;
+
+/* Console window. */
+HWND wnd;
+
+/* Width, height of a single character in the console font. */
+int cw, ch;
+
+/* Width, height of console window in characters. */
+int nw, nh;
+
+/* Keyboard buffer. */
+#define MAX_KBD_BUF 80         /* Maximum number of characters to buffer. */
+char kbd[MAX_KBD_BUF];
+char *hp, *tp;                 /* Keyboard buffer head, tail. */
+
+static void
+outmsg (char *format,...)
+{
+  va_list args;
+  char s[128];
+
+  va_start (args, format);
+  vsprintf (s, format, args);
+  va_end (args);
+  MessageBox (_hInstance, s, "Con32s",
+             MB_OK | MB_ICONHAND | MB_SYSTEMMODAL);
+}
+
+static void *
+xmalloc (size_t size)
+{
+  void *vp;
+  if (size == 0)
+    return NULL;
+  vp = malloc (size);
+  if (!vp)
+    {
+      MessageBox (NULL, _("xmalloc(): out of memory"), NULL, MB_OK);
+      exit (EXIT_FAILURE);
+    }
+  return vp;
+}
+
+static void *
+xrealloc (void *ptr, size_t size)
+{
+  void *vp;
+  if (!size)
+    {
+      if (ptr)
+       free (ptr);
+      return NULL;
+    }
+  if (ptr)
+    vp = realloc (ptr, size);
+  else
+    vp = malloc (size);
+  if (!vp)
+    {
+      MessageBox (NULL, _("xrealloc(): out of memory"), NULL, MB_OK);
+      exit (EXIT_FAILURE);
+    }
+  return vp;
+}
+
+void _blp_console_init (void);
+void _blp_console_yield (void);
+void _blp_console_paint (void);
+void find_console_top (line ** top);
+void find_console_bottom (int *x, int *y, line ** bottom);
+
+static void
+writechar (int c)
+{
+  int x, y;
+  line *bottom;
+
+  static HDC dc;
+
+  if (c == 10000)
+    {
+      if (dc)
+       {
+         ReleaseDC (wnd, dc);
+         dc = 0;
+       }
+      return;
+    }
+
+  if (!tail)
+    {
+      tail = xmalloc (sizeof (line));
+      tail->next = tail->prev = NULL;
+      tail->text = NULL;
+      tail->len = tail->size = 0;
+    }
+
+  switch (c)
+    {
+    case '\n':
+      {
+       tail->next = xmalloc (sizeof (line));
+       tail->next->prev = tail;
+       tail = tail->next;
+       tail->next = NULL;
+       tail->text = NULL;
+       tail->len = tail->size = 0;
+      }
+      break;
+    case '\r':
+      break;
+    case '\b':
+      {
+       find_console_bottom (&x, &y, &bottom);
+       if (tail->len)
+         tail->len--;
+       else
+         {
+           tail = tail->prev;
+           free (tail->next);
+           tail->next = NULL;
+         }
+
+       if (x > 1)
+         {
+           if (!dc)
+             {
+               dc = GetDC (wnd);
+               SelectObject (dc, GetStockObject (ANSI_FIXED_FONT));
+               assert (dc);
+             }
+           TextOut (dc, x * cw, y * ch, " ", 1);
+           return;
+         }
+      }
+      break;
+    default:
+      {
+       if (tail->len + 1 > tail->size)
+         {
+           tail->size += 16;
+           tail->text = xrealloc (tail->text, tail->size);
+         }
+
+       find_console_bottom (&x, &y, &bottom);
+       tail->text[tail->len++] = c;
+       if (y < nh)
+         {
+           if (!dc)
+             {
+               dc = GetDC (wnd);
+               SelectObject (dc, GetStockObject (ANSI_FIXED_FONT));
+               assert (dc);
+             }
+           TextOut (dc, x * cw, y * ch, &tail->text[tail->len - 1], 1);
+           return;
+         }
+      }
+      break;
+    }
+  InvalidateRect (wnd, NULL, TRUE);
+}
+
+/* Writes LEN bytes from BUF to the fake console window. */
+int
+_blp_console_write (const void *buf, unsigned len)
+{
+  int i;
+
+  if (!inited)
+    _blp_console_init ();
+  for (i = 0; i < len; i++)
+    writechar (((char *) buf)[i]);
+  writechar (10000);
+  return len;
+}
+
+/* Reads one character from the fake console window.  A whole line
+   is read at once, then spoon-fed to the runtime library. */
+#if __BORLANDC__
+#pragma argsused
+#endif
+int
+_blp_console_read (const void *t1, unsigned t2)
+{
+  static char buf[1024];
+  static int len;
+  static int n;
+
+  MSG msg;
+
+  int c;
+
+  if (!inited)
+    _blp_console_init ();
+  if (n < len)
+    {
+      *(char *) t1 = buf[n];
+      n++;
+      return 1;
+    }
+
+  printf ("_");
+  len = n = 0;
+  while (GetMessage ((LPMSG) & msg, NULL, 0, 0))
+    {
+      TranslateMessage ((LPMSG) & msg);
+      DispatchMessage ((LPMSG) & msg);
+
+      while (hp != tp)
+       {
+         c = *(unsigned char *) tp;
+         if (++tp >= &kbd[MAX_KBD_BUF])
+           tp = kbd;
+         if ((c >= 32 && c < 128) || c == '\b' || c == '\r')
+           switch (c)
+             {
+             case '\b':
+               if (len <= 0)
+                 break;
+               printf ("\b\b_");
+               len--;
+               break;
+             default:
+               if (len >= 1022)
+                 break;
+               if (c == '\r')
+                 {
+                   buf[len++] = '\n';
+                   printf ("\b\n");
+                   *(char *) t1 = buf[n];
+                   n++;
+                   return 1;
+                 }
+               buf[len++] = c;
+               printf ("\b%c_", c);
+               break;
+             }
+       }
+    }
+  len = 0;
+  return 0;
+}
+
+LRESULT CALLBACK _export _blp_console_wndproc (HWND, UINT, WPARAM, LPARAM);
+
+void
+_blp_console_init (void)
+{
+  WNDCLASS wc;
+
+  if (inited)
+    return;
+  inited = 1;
+  wc.style = CS_HREDRAW | CS_VREDRAW;
+  wc.lpfnWndProc = _blp_console_wndproc;
+  wc.cbClsExtra = 0;
+  wc.cbWndExtra = 0;
+  wc.hInstance = (HINSTANCE) _hInstance;
+  wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
+  wc.hCursor = LoadCursor (NULL, IDC_ARROW);
+  wc.hbrBackground = CreateSolidBrush (RGB (255, 255, 255));
+  wc.lpszMenuName = NULL;
+  wc.lpszClassName = "blp_console";
+  if (!RegisterClass (&wc))
+    {
+      MessageBox ((HWND) 0, _("RegisterClass(): returned 0."),
+                 "_blp_console_init()", MB_APPLMODAL | MB_OK);
+      exit (EXIT_FAILURE);
+    }
+
+  wnd = CreateWindow ("blp_console", title, WS_OVERLAPPEDWINDOW,
+                     CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+                     CW_USEDEFAULT, NULL, (HMENU) 0, (HINSTANCE) _hInstance,
+                     NULL);
+  if (!wnd)
+    {
+      MessageBox ((HWND) 0, _("CreateWindow(): returned 0."),
+                 "_blp_console_init()", MB_APPLMODAL | MB_OK);
+      exit (EXIT_FAILURE);
+    }
+
+  ShowWindow (wnd, _nCmdShow);
+
+  hp = tp = kbd;
+}
+
+LRESULT CALLBACK _export
+_blp_console_wndproc (HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
+{
+  char s[80];
+
+  switch (msg)
+    {
+    case WM_CREATE:
+      {
+       HDC dc = GetDC (hwnd);
+       TEXTMETRIC tm;
+       int success;
+
+       SelectObject (dc, GetStockObject (ANSI_FIXED_FONT));
+       success = GetTextMetrics (dc, &tm);
+       assert (success);
+       cw = tm.tmMaxCharWidth;
+       ch = tm.tmHeight;
+       success = ReleaseDC (hwnd, dc);
+       assert (success);
+       return 0;
+      }
+    case WM_PAINT:
+      _blp_console_paint ();
+      return 0;
+    case WM_CHAR:
+      {
+       if (hp + 1 != tp && (hp != &kbd[MAX_KBD_BUF - 1] || tp != kbd))
+         {
+           *hp++ = wp;
+           if (hp >= &kbd[MAX_KBD_BUF])
+             hp = kbd;
+         }
+      }
+      break;
+    }
+  return DefWindowProc (hwnd, msg, wp, lp);
+}
+
+static void
+find_console_top (line ** top)
+{
+  int success;
+
+  /* Line count. */
+  int lc;
+
+  /* Line iterator. */
+  line *iter;
+
+  /* Scratch line. */
+  static line temp;
+
+  /* Client rectangle. */
+  RECT r;
+
+  success = GetClientRect (wnd, &r);
+  assert (success);
+  nw = r.right / cw;
+  if (nw < 1)
+    nw = 1;
+  nh = r.bottom / ch;
+  if (nh < 1)
+    nh = 1;
+
+  /* Find the beginning of the text to display. */
+  for (lc = 0, iter = tail; iter; iter = iter->prev)
+    {
+      if (!iter->len)
+       lc++;
+      else
+       lc += (iter->len / nw) + (iter->len % nw > 0);
+      if (lc >= nh || !iter->prev)
+       break;
+    }
+  if (lc > nh)
+    {
+      temp = *iter;
+      temp.text += nw * (lc - nh);
+      temp.len -= nw * (lc - nh);
+      *top = &temp;
+    }
+  else
+    *top = iter;
+}
+
+static void
+find_console_bottom (int *x, int *y, line ** bottom)
+{
+  find_console_top (bottom);
+  *x = *y = 0;
+  if (!*bottom)
+    return;
+  while (1)
+    {
+      if ((*bottom)->len == 0)
+       (*y)++;
+      else
+       (*y) += ((*bottom)->len / nw) + ((*bottom)->len % nw > 0);
+      if (!(*bottom)->next)
+       break;
+      *bottom = (*bottom)->next;
+    }
+  *x = (*bottom)->len % nw;
+  (*y)--;
+}
+
+void
+_blp_console_paint (void)
+{
+  PAINTSTRUCT ps;
+  HDC dc;
+
+  /* Current screen location. */
+  int x, y;
+
+  /* Current line. */
+  line *iter;
+
+  dc = BeginPaint (wnd, &ps);
+  assert (dc);
+
+  find_console_top (&iter);
+
+  /* Display the text. */
+  SelectObject (dc, GetStockObject (ANSI_FIXED_FONT));
+  SetTextColor (dc, RGB (0, 0, 0));
+  for (y = 0; iter; iter = iter->next)
+    {
+      if (!iter->len)
+       {
+         y += ch;
+         continue;
+       }
+      for (x = 0; x < iter->len; x += nw)
+       {
+         TextOut (dc, 0, y, &iter->text[x],
+                  iter->len - x > nw ? nw : iter->len - x);
+         y += ch;
+       }
+    }
+
+  EndPaint (wnd, &ps);
+}
+
+int main (int argc, char *argv[], char *env[]);
+
+#if __BORLANDC__
+#pragma argsused
+#endif
+int CALLBACK
+WinMain (HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int nCmdShow)
+{
+  int result;
+  MSG msg;
+
+  char *pgmname = "PSPP";
+
+  _hInstance = inst;
+  _hPrev = prev;
+  _cmdline = cmdline;
+  _nCmdShow = nCmdShow;
+
+  result = main (1, &pgmname, NULL);
+
+  return result;
+}
diff --git a/tests/ChangeLog b/tests/ChangeLog
new file mode 100644 (file)
index 0000000..5ae181d
--- /dev/null
@@ -0,0 +1,351 @@
+Fri Jan  7 20:30:23 2000  Ben Pfaff  <blp@gnu.org>
+
+       * data-fmts.stat: Add more date tests.
+
+       * do-repeat.stat: SET ECHO ON.
+
+       * syntax: Replace test -L with test -h.
+
+Tue Jan  5 14:21:52 1999  Ben Pfaff  <blp@gnu.org>
+
+       * data-list.stat, data-list.data: Adjust so that it can tell if
+       DATA LIST FREE properly parses and pads string values.
+
+       * list.stat: Remove anachronistic `SET EMULATION PC'.
+
+       * Rebuilt benchmark.
+
+Tue Jan  5 14:12:58 1999  Ben Pfaff  <blp@gnu.org>
+
+       * syntax: Replaced `test' calls with `['.  This may or may not fix
+       the problems some people have reported.
+
+Sun Aug  9 11:15:38 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (EXTRA_DIST) Update for renamed files.
+
+       * autorecode.stat: Renamed autorecod.stat.
+
+       * begin-data.stat: Renamed beg-data.stat.
+
+       * data-formats.stat: Renamed data-fmts.stat.
+
+       * expression.stat: Renamed expr.stat.
+
+       * file-label.stat: Renamed file-lab.stat.
+
+       * input-program.stat: Renamed inpt-pgm.stat.
+
+       * modify-vars.stat: Renamed mdfy-vars.stat.
+
+       * match-files.stat: Renamed mtch-file.stat.
+
+       * process-if.stat: Renamed pcs-if.stat.
+
+       * split-file.stat: Renamed splt-file.stat.
+
+       * sysfile-info.stat: Renamed sys-info.stat.
+
+       * expect/: Refreshed.
+
+Sat Aug  8 00:27:07 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (EXTRA_DIST) Add `syntax'.
+       (dist-hook) New target.
+
+Wed Aug  5 00:04:16 1998  Ben Pfaff  <blp@gnu.org>
+
+       * TEST-RESULTS: Removed.
+
+       * show-check-msg: Removed.
+
+       * expect/: New.
+
+       * syntax: New.  Thanks to James R. Van Zandt <jrv@vanzandt.mv.com>
+       for this implementation of automatic testing.
+
+       * Makefile: (TESTS) Set to the syntax script.
+       (bench) New target.
+       (EXTRA_DIST) Remove TEST-RESULTS.  Add `syntax'.
+
+Sun Jul  5 14:16:18 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (EXTRA_DIST) Add flip.stat.
+
+Sun Jul  5 00:50:41 1998  Ben Pfaff  <blp@gnu.org>
+
+       * crosstabs.stat: Change to test /MISSING=REPORT.
+
+Tue Jun  2 23:42:23 1998  Ben Pfaff  <blp@gnu.org>
+
+       * flip.stat: New file.
+       
+       * weighting.stat: Update.
+
+Mon May 25 12:45:46 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (EXTRA_DIST) Add crosstabs.stat, match-files.stat.
+
+       * crosstabs.stat: Turn off cells=all.
+
+Tue May 12 16:22:06 1998  Ben Pfaff  <blp@gnu.org>
+
+       * crosstabs.stat: Minor changes.
+
+Thu May  7 23:16:03 1998  Ben Pfaff  <blp@gnu.org>
+
+       * crosstabs.stat: Replace with a test that is hopefully better.
+
+Tue Apr 14 01:00:46 1998  Ben Pfaff  <blp@gnu.org>
+
+       * crosstabs.stat: New.
+
+Mon Mar  9 15:40:25 1998  Ben Pfaff  <blp@gnu.org>
+
+       * match-files.stat: More thorough.
+
+Mon Mar  9 01:14:14 1998  Ben Pfaff  <blp@gnu.org>
+
+       * match-files.stat: More thorough.
+
+1998-03-05  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Use ./gengarbage instead of gengarbage.
+
+1998-02-23  Ben Pfaff  <blp@gnu.org>
+
+       * Many tests: Remove final finish command.
+
+1998-02-16  Ben Pfaff  <blp@gnu.org>
+
+       * (DISTCLEANFILES) Clean *.save, pspp.*, foo*
+
+       * file-label.stat, sysfile-info.stat: Replace .sav with .save.
+
+       * match-files.stat: New file.
+       
+Fri Feb 13 15:58:11 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (EXTRA_DIST) Add TEST-RESULTS.
+
+Tue Jan 13 01:11:36 1998  Ben Pfaff  <blp@gnu.org>
+
+       * aggregate.stat: Some more testing.
+
+Sat Jan 10 23:57:14 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (DISTCLEANFILES) Add aggregate.save.
+
+       * aggregate.stat: Slightly more thorough.
+
+Sat Jan 10 02:17:00 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (EXTRA_DIST) Add means.stat, t-test.stat.
+       
+       * means.stat: New file.
+
+Thu Jan  8 22:38:59 1998  Ben Pfaff  <blp@gnu.org>
+
+       * Many tests: Removed extra newlines from REMARKs.
+
+Mon Jan  5 11:18:44 1998  Ben Pfaff  <blp@gnu.org>
+
+       * sysfile-info.stat: Test most of the DISPLAY commands.  Update
+       title.
+
+       * vector.stat: Display vectors.
+
+Sun Jan  4 18:31:36 1998  Ben Pfaff  <blp@gnu.org>
+
+       * All tests: Added title.
+
+       * begin-data.stat: Updated REMARK format.
+
+       * descript.stat: Comment fix.
+
+Sun Dec 21 16:57:31 1997  Ben Pfaff  <blp@gnu.org>
+
+       * TEST-RESULTS: New file.
+
+Fri Dec  5 22:02:20 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (DISTCLEANFILES) Add fiasco.html.
+
+Tue Dec  2 14:55:22 1997  Ben Pfaff  <blp@gnu.org>
+
+       * t-test.stat: New file.
+
+Fri Nov 14 00:17:25 1997  Ben Pfaff  <blp@gnu.org>
+
+       * aggregate.stat: Changed.      
+
+Tue Oct 28 16:26:25 1997  Ben Pfaff  <blp@gnu.org>
+
+       * aggregate.stat: New file.
+
+       * Makefile.am: (EXTRA_DIST) Add aggregate.stat.
+
+Sun Oct  5 16:02:02 1997  Ben Pfaff  <blp@gnu.org>
+
+       * fall92.stat, fall92.data: Removed (unknown copyright).
+
+       * gengarbage.c: Define EXIT_SUCCESS if not defined by headers.
+       From Alexandre Oliva <oliva@dcc.unicamp.br>.
+
+Sat Oct  4 16:35:59 1997  Ben Pfaff  <blp@gnu.org>
+
+       * repeating.stat: New file.
+
+       * Makefile.am: (EXTRA_DIST) Add repeating.stat.
+
+Thu Sep 18 21:40:50 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (EXTRA_DIST) Add lag.stat.
+
+Mon Aug 18 18:31:42 1997  Ben Pfaff  <blp@gnu.org>
+
+       * do-repeat.stat: Even more useful.
+
+       * lag.stat: New file.
+
+Sun Aug 17 22:47:53 1997  Ben Pfaff  <blp@gnu.org>
+
+       * do-repeat.stat: Made actually useful, not stupid.
+
+Sun Aug  3 11:46:00 1997  Ben Pfaff  <blp@gnu.org>
+
+       * In several files, replace usage of deprecated term `script' by
+       `syntax file'.
+
+Thu Jul 17 02:12:17 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (EXTRA_DIST) Add tabs.stat.
+
+       * file-label.stat: Improved.
+
+       * sysfile-info.stat: Tests DISPLAY DICTIONARY now as well.
+
+Fri Jul 11 14:13:49 1997  Ben Pfaff  <blp@gnu.org>
+
+       * gengarbage.c: Reformat.  #include's <time.h>.  Uses ANSI C
+       rand() in place of random().  Calls the randomizer srand().
+
+Thu Jul 10 22:16:34 1997  Ben Pfaff  <blp@gnu.org>
+
+       * tabs.stat: New file.
+
+Wed Jun 25 22:54:40 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: (EXTRA_DIST) Removed bug.stat, file-type.stat.
+
+Sun Jun  8 01:24:55 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Added fiasco.ps, fiasco.list, foo, foo.sav, msgs to
+       DISTCLEANFILES.
+
+       * input-program.stat: Made some variables scratch.
+
+Fri Jun  6 22:53:03 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Many files: Comment fixes, removed `set output raw.' commands.
+
+       * Other miscellaneous changes.
+
+Tue Jun  3 23:44:46 1997  Ben Pfaff  <blp@gnu.org>
+
+       * list.stat: Re-enabled some of it.
+
+Wed Apr 23 21:33:48 1997  Ben Pfaff  <blp@gnu.org>
+
+       * sysfile-info.stat: A little more generalized now.
+
+Fri Apr 18 15:42:22 1997  Ben Pfaff  <blp@gnu.org>
+
+       * Makefile.am: Maintainer-clean Makefile.in.
+       
+Thu Mar 27 01:11:29 1997  Ben Pfaff  <blp@gnu.org>
+
+       * gengarbage.pl: Removed.
+       
+Sat Feb 15 21:26:53 1997  Ben Pfaff  <blp@gnu.org>
+
+       * descript.stat: Syntax fixes.
+
+       * process-if.stat: New test for PROCESS IF.
+
+Sun Jan 19 14:22:11 1997  Ben Pfaff  <blp@gnu.org>
+
+       * autorecode.stat, modify-vars.stat: More thorough.
+
+       * data-formats.stat, file-label.stat: New tests.
+       
+Thu Jan 16 13:08:57 1997  Ben Pfaff  <blp@gnu.org>
+
+       * bug.stat: Comment fix.
+
+Wed Jan  1 22:08:10 1997  Ben Pfaff  <blp@gnu.org>
+
+       * filter.stat: New file; tests FILTER behavior.
+
+Wed Jan  1 17:00:59 1997  Ben Pfaff  <blp@gnu.org>
+
+       * gengarbage.pl: New perl program equivalent to gengarbage.c.
+
+Sun Dec 29 21:36:48 1996  Ben Pfaff  <blp@gnu.org>
+
+       * gengarbage.c: Changed.
+
+       * sort.stat: Changed.
+
+Sun Dec 22 23:10:39 1996  Ben Pfaff  <blp@gnu.org>
+
+       * sort.stat: New file.
+
+Fri Dec 13 21:30:53 1996  Ben Pfaff  <blp@gnu.org>
+
+       * autorecode.stat: New file.
+
+       * fall92.stat: Mods for practicality.
+
+       * test.bat, testall.bat: Removed.
+       
+Thu Nov 28 23:14:07 1996  Ben Pfaff  <blp@gnu.org>
+
+       * list.stat, weighting.stat: Changed SET COMPATIBILITY subcommand
+       to SET EMULATION in anticipation of change.
+
+Sat Oct 26 23:06:06 1996  Ben Pfaff  <blp@gnu.org>
+
+       * recode.stat: Removed comment about bug, since I fixed that.
+
+Thu Oct 24 20:13:42 1996  Ben Pfaff  <blp@gnu.org>
+
+       * print.stat: Slightly more thorough.
+
+Thu Oct 24 17:47:14 1996  Ben Pfaff  <blp@gnu.org>
+
+       * time-date.stat: Slightly more thorough.
+
+Wed Oct 23 21:53:43 1996  Ben Pfaff  <blp@gnu.org>
+
+       * time-date.stat: New file.
+
+Thu Sep 26 22:20:26 1996  Ben Pfaff  <blp@gnu.org>
+
+       * list.data: More data.
+
+       * list.stat: Handles all that extra data.
+
+       * weighting.stat: Doesn't try to list $WEIGHT because PC+ isn't
+       quite supported yet.
+
+Wed Sep  4 21:45:35 1996  Ben Pfaff  <blp@gnu.org>
+
+       * weighting.stat: Tests for proper ligatures.  Won't work until
+       encodings are correct, of course...
+
+----------------------------------------------------------------------
+Local Variables:
+mode: change-log
+version-control: never
+End:
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644 (file)
index 0000000..1109a24
--- /dev/null
@@ -0,0 +1,29 @@
+## Process this file with automake to produce Makefile.in  -*- makefile -*-
+
+bench:
+       make BENCHMARK=1 check
+
+TESTS = syntax
+
+noinst_PROGRAMS = gengarbage
+
+noinst_DATA = sort.data
+sort.data: gengarbage
+       ./gengarbage | head -1000 > $@
+
+EXTRA_DIST = aggregate.stat autorecod.stat beg-data.stat bignum.data   \
+bignum.stat compute.stat count.stat crosstabs.stat data-fmts.stat      \
+data-list.data data-list.stat descript.stat do-if.stat do-repeat.stat  \
+expr.stat file-lab.stat filter.stat flip.stat gengarbage.c             \
+inpt-pgm.stat lag.stat list.data list.stat loop.stat mtch-file.stat    \
+means.stat mdfy-vars.stat print.stat pcs-if.stat recode.stat           \
+repeating.stat reread.data reread.stat sample.stat sort.stat           \
+splt-file.stat sys-info.stat t-test.stat tabs.stat temporary.stat      \
+time-date.stat vector.stat weighting.data weighting.stat syntax
+
+dist-hook:
+       cp -rp $(srcdir)/expect $(distdir)
+
+DISTCLEANFILES = *.save sort.data pspp.* foo* msgs *.actual
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/tests/aggregate.stat b/tests/aggregate.stat
new file mode 100644 (file)
index 0000000..3a95532
--- /dev/null
@@ -0,0 +1,20 @@
+title 'Test for AGGREGATE procedure'.
+
+data list /x y 1-2.
+begin data.
+13
+27
+30
+12
+26
+11
+10
+28
+29
+14
+15
+end data.
+sort cases by x.
+list.
+aggregate outfile='aggregate.save'/missing=columnwise /document /presorted/break=x(a) /z'label for z'=sum(y)/foo=nu.
+list.
diff --git a/tests/autorecod.stat b/tests/autorecod.stat
new file mode 100644 (file)
index 0000000..b035c33
--- /dev/null
@@ -0,0 +1,30 @@
+title 'Test for AUTORECODE procedure'.
+
+/* Tries AUTORECODE on some random but similar strings of characters.
+data list /x 1-5(a) y 7.
+begin data.
+lasdj 1 1                                                           3
+asdfk 0 3 <---- These are the numbers that should be produced for a 4
+asdfj 2 4                                                           2
+asdfj 1 4                                                           3
+asdfk 2 3                                                           2
+asdfj 9 4                                                           1
+lajks 9 2                                                           1
+asdfk 0 3 These are the numbers that should be produced for b ----> 4
+asdfk 1 3                                                           3
+end data.
+list.
+autorecode x y into a b/descend.
+
+/* This should produce the values indicated in the data above.
+list.
+
+/* Just to make sure it works on second & subsequent executions,
+/* try it again.
+compute z=trunc(y/2).
+autorecode z into w.
+
+/* This should display:
+/*   z 0 0 1 0 1 4 4 0 0
+/*   w 1 1 2 1 2 3 3 1 1
+list z w.
diff --git a/tests/beg-data.stat b/tests/beg-data.stat
new file mode 100644 (file)
index 0000000..6bf5a8f
--- /dev/null
@@ -0,0 +1,32 @@
+title 'Test BEGIN DATA ... END DATA'.
+
+remark EOF
+----------------------------------------------------------------------
+First we show that we can input data with BEGIN DATA/END DATA after
+a procedure.
+----------------------------------------------------------------------
+EOF
+data list /A B 1-2.
+list.
+begin data.
+12
+34
+56
+78
+90
+end data.
+
+remark EOF
+----------------------------------------------------------------------
+Next we show that BEGIN DATA/END DATA work fine on their own as well.
+----------------------------------------------------------------------
+EOF
+data list /A B 1-2.
+begin data.
+09
+87
+65
+43
+21
+end data.
+list.
diff --git a/tests/bignum.data b/tests/bignum.data
new file mode 100644 (file)
index 0000000..1a1421f
--- /dev/null
@@ -0,0 +1,62 @@
+0
+0.1
+0.5
+0.8
+0.9
+0.999
+1
+2
+3
+4
+5
+12
+123
+1234
+12345
+123456
+1234567
+12345678
+123456789
+1234567890
+19999999999
+199999999999
+1234567890123
+19999999999999
+199999999999999
+1234567890123456
+19999999999999999
+123456789012345678
+1999999999999999999
+12345678901234567890
+199999999999999999999
+1234567890123456789012
+19999999999999999999999
+123456789012345678901234
+1999999999999999999999999
+12345678901234567890123456
+199999999999999999999999999
+1234567890123456789012345678
+19999999999999999999999999999
+123456789012345678901234567890
+1999999999999999999999999999999
+12345678901234567890123456789012
+199999999999999999999999999999999
+1234567890123456789012345678901234
+19999999999999999999999999999999999
+123456789012345678901234567890123456
+1999999999999999999999999999999999999
+12345678901234567890123456789012345678
+199999999999999999999999999999999999999
+1234567890123456789012345678901234567890
+1999999999999999999999999999999999999999
+1e40
+1.1e40
+1.5e40
+1e41
+1e50
+1e100
+1e150
+1e200
+1e250
+1e300
+1.79641e308
diff --git a/tests/bignum.stat b/tests/bignum.stat
new file mode 100644 (file)
index 0000000..df5cae3
--- /dev/null
@@ -0,0 +1,28 @@
+title 'Test use of big numbers'.
+
+*** Do the portable output.
+remark EOF
+----------------------------------------------------------------------
+Testing use of big numbers.\n
+The numbers in the data file are designed for IEEE754 double
+format--if your system uses something different then the test needs to
+be adjusted for whatever are big numbers to your system.
+----------------------------------------------------------------------
+EOF
+data list file='bignum.data'/BIGNUM 1-40.
+list.
+
+*** Do the nonportable output for fun. 
+remark EOF
+NOCOMP
+SUCCESS?
+----------------------------------------------------------------------
+This test merely shows whether your system can successfully handle
+floating-point overflow.  If you get a fatal exception at this point,
+the source needs some editing--glob.c should mask overflow exceptions
+in init_glob().  Again, the numbers are specific to IEEE754 double
+format.
+----------------------------------------------------------------------
+EOF
+descriptives BIGNUM.
+rem-SUCCESS
diff --git a/tests/compute.stat b/tests/compute.stat
new file mode 100644 (file)
index 0000000..5538724
--- /dev/null
@@ -0,0 +1,22 @@
+title 'Test COMPUTE expressions'.
+
+data list /w 1-3(a).
+begin data.
+123
+456
+919
+572
+end data.
+*string y z(a6).
+*compute y=ltrim(lpad(w,6,'*'),'*').
+*compute z=rtrim(rpad(w,6,'*'),'*').
+string z(a6).
+compute x=number(w).
+compute y=number(w,f8).
+compute z=lpad(rpad(substr(string(x,f6),4,1),3,'@'),6,'*').
+compute y=y+1e-10.
+if(x<=456) y=500.
+select if x<=456.
+compute #caseseq=#caseseq+1.
+list.
+list.
diff --git a/tests/count.stat b/tests/count.stat
new file mode 100644 (file)
index 0000000..f118947
--- /dev/null
@@ -0,0 +1,15 @@
+title 'Test COUNT transformation'.
+
+data list /v1 to v2 1-4(a).
+begin data.
+1234
+321
+2 13
+4121
+1104
+03 4
+0193
+end data.
+*count c=v1 to v4(1).
+count c=v1 to v2('2',' 4','1').
+list.
diff --git a/tests/crosstabs.stat b/tests/crosstabs.stat
new file mode 100644 (file)
index 0000000..e201d26
--- /dev/null
@@ -0,0 +1,19 @@
+clear transformations.
+input program.
+loop a=1 to 1000.
+compute a=trunc(uniform(5)).
+compute b=trunc(uniform(5)).
+compute c=trunc(uniform(5)).
+compute d=trunc(uniform(5)).
+compute e=trunc(uniform(5)).
+compute f=trunc(uniform(5)).
+end case.
+end loop.
+end file.
+end input program.
+select if not ((a=1 and e=3) or (c=2)).
+print /all.
+missing value all (2).
+*crosstabs/missing=report/variables=a to f(0,4)/tables=a by e by f/statistics=all/pivot on.
+crosstabs a by e by f/statistics=all/pivot on.
+execute.
diff --git a/tests/data-fmts.stat b/tests/data-fmts.stat
new file mode 100644 (file)
index 0000000..87b86bb
--- /dev/null
@@ -0,0 +1,179 @@
+set echo off.
+title 'Test non-binary data input formats'.
+
+data list /a 1-8(a)                    /* 1
+           ahex 9-16(ahex)             /* 2
+           comma 17-24(comma)          /* 3
+           dollar 25-32(dollar)        /* 4
+           f 33-40(f)                  /* 5
+           pibhex 41-48(pibhex)                /* 6
+           n 49-56(n)                  /* 7
+           e 57-64(e).                 /* 8
+formats comma(comma8.2) dollar(dollar8.2).
+print /a ahex comma dollar f pibhex n e.
+
+/* This set of data should produce errors:
+/*
+/*  - First line should be error-free.
+/*  - Second line should have errors for F, PIBHEX, and N fields.
+/*  - Third line should have errors for COMMA, DOLLAR, F, and N fields.
+/*  - Fourth line should have errors for F and N fields.
+/*
+/*      2       3       4       5       6       7       8       .
+begin data.
+abcdefgh414243441,2,3.4,$1,2,3.4123456.6100023451234567812345678
+        4a4b4c4d1,234.56$1234.56  abcd    ghjk  -12345671234+56
+        4C4D4E4F1234+56 $1234+56   0 1    abcd  12345e671234e+56
+        555657581234e+561234e+56 1.2 3  000000001234e67 1234-5
+end data.
+
+data list /date 1-32(date)              /* 1
+           time 33-64(time,4).          /* 2
+print /date time.
+
+/* This set of data should produce errors:
+/*
+/*                              2               
+begin data.
+11-july-1982                    12:10:55.59     
+8-xii-23                        55:56:75.105    
+end data.
+
+data list /datetime 1-32(datetime,4)    /* 1
+           adate 33-64(adate).          /* 2
+print /datetime adate.
+/*                              2
+begin data.
+7-7-8 12:10:55.5                10/15/1582
+11-jul-1982 9:00                10/01/78
+end data.
+
+data list /jdate 1-32(jdate)           /* 1
+          dtime 33-64(dtime).          /* 2
+print /jdate dtime.
+/*                              2
+begin data.
+1582365                         10 15:30:00.32
+1996001                         0 8:23:59.99
+end data.
+
+data list /wkday 1-32(wkday)           /* 1
+          month 33-64(month).          /* 2
+print /wkday month.
+/*                              2
+begin data.
+mon                             xii
+Saturday                        12
+thursday                        march
+                                ixiii
+end data.
+
+data list /moyr 1-32(moyr)             /* 1
+          qyr 33-64(qyr).              /* 2
+print /moyr qyr.
+/*                              2
+begin data.
+jan 96                          4 q 1986
+mar 1896                        1q1600
+end data.
+
+data list /wkyr 1-32(wkyr)              /* 1
+           pct 33-64(pct).              /* 2
+print /wkyr pct.
+/*                              2
+begin data.
+4 wk 97                         105%
+52 wk 1996                      55.5%
+end data.
+
+data list /dot 1-32(dot)                /* 1
+           edate 33-64(edate).          /* 2
+formats dot(dot32.3).
+print /dot edate.
+/*                              2
+begin data.
+123.456.789,348                 1.10.1978
+                                30.12.1996
+end data.
+
+formats dot (comma32.3).
+set decimal comma.
+print /dot.
+execute.
+
+set decimal dot.
+print /dot.
+execute.
+
+data list /sdate 1-32(sdate).           /* 1
+print /sdate.
+begin data.
+1923/7/3
+1992.5.2
+end data.
+
+data list /date 1-32.
+compute date = date * 86400.
+print /date (date32).
+begin data.
+1
+50000
+102
+1157
+14288
+87365
+109623
+153211
+152371
+144623
+end data.
+
+data list /date 1-32(date).
+compute jdate = date / 86400.
+print /jdate.
+begin data.
+15-OCT-1582
+06-SEP-1719
+24-JAN-1583
+14-DEC-1585
+26-NOV-1621
+25-DEC-1821
+03-DEC-1882
+06-APR-2002
+18-DEC-1999
+01-OCT-1978
+end data.
+
+set cca 'NPX,PFX,SFX,NSX'.
+set ccb 'NPX.PFX.SFX.NSX'.
+set ccc '-,$,,'.
+set ccd '(.SFR..)'.
+set cce 'asld,adl,lfj,lfs'.
+
+data list /cc 1-32(f).
+print /'cca: ' cc (cca32.2).
+print /'ccb: ' cc (ccb32.2).
+print /'ccc: ' cc (ccc32.2).
+print /'ccd: ' cc (ccd32.2).
+print /'cce: ' cc (cce32.2).
+begin data.
+78.19
+-78.19
+123456789.12
+1e23
+end data.
+
+print /'cca: ' cc (cca16.2).
+print /'ccb: ' cc (ccb16.2).
+print /'ccc: ' cc (ccc16.2).
+print /'ccd: ' cc (ccd16.2).
+print /'cce: ' cc (cce16.2).
+execute.
+
+print /'cca: ' cc (cca8.2).
+print /'ccb: ' cc (ccb8.2).
+print /'ccc: ' cc (ccc8.2).
+print /'ccd: ' cc (ccd8.2).
+print /'cce: ' cc (cce8.2).
+execute.
+
diff --git a/tests/data-list.data b/tests/data-list.data
new file mode 100644 (file)
index 0000000..71ea15d
--- /dev/null
@@ -0,0 +1,6 @@
+SHORT 2 3 4
+RIGHTLEN 6
+7
+8 TOOLONGLEN
+10 11
+12
diff --git a/tests/data-list.stat b/tests/data-list.stat
new file mode 100644 (file)
index 0000000..5df73cb
--- /dev/null
@@ -0,0 +1,24 @@
+title 'Test DATA LIST FREE and DATA LIST LIST'.
+
+remark EOF
+----------------------------------------------------------------------
+There is no test for DATA LIST FIXED since it is imagined that the
+rest of the tests give it a pretty good workout.
+----------------------------------------------------------------------
+EOF
+remark EOF
+----------------------------------------------------------------------
+Testing use of DATA LIST FREE.
+----------------------------------------------------------------------
+EOF
+data list free table file='data-list.data'/A(A8) B C D.
+list.
+
+remark EOF
+----------------------------------------------------------------------
+Testing use of DATA LIST LIST.
+----------------------------------------------------------------------
+EOF
+data list list table file='data-list.data'/A(A8) B C D.
+list.
+
diff --git a/tests/descript.stat b/tests/descript.stat
new file mode 100644 (file)
index 0000000..334ea2b
--- /dev/null
@@ -0,0 +1,17 @@
+title 'Test DESCRIPTIVES procedure'.
+
+data list / v0 to v16 1-17.
+begin data.
+12128989012389023
+34128080123890128
+56127781237893217
+78127378123793112
+90913781237892318
+37978547878935789
+52878237892378279
+12377912789378932
+26787654347894348
+29137178947891888
+end data.
+
+descript all/stat=all/format=serial.
diff --git a/tests/do-if.stat b/tests/do-if.stat
new file mode 100644 (file)
index 0000000..8f1816e
--- /dev/null
@@ -0,0 +1,24 @@
+title 'Test DO IF control structure'.
+
+data list /x y z 1-6.
+begin data.
+000099
+019900
+019820
+001089
+020000
+end data.
+do if x~=2.
+loop i=1 to x.
+do if x=0.
+print /i x z.
+else.
+do if x=1.
+print /i x y.
+else.
+print /i 'Huh?'.
+end if.
+end if.
+end loop.
+end if.
+execute.
diff --git a/tests/do-repeat.stat b/tests/do-repeat.stat
new file mode 100644 (file)
index 0000000..c26951f
--- /dev/null
@@ -0,0 +1,17 @@
+title 'Test DO REPEAT control structure'.
+
+set echo on.
+data list file='list.data'/var00001 to var00005 1-5.
+string s1 to s5(a8).
+vector s=s1 to s5.
+do repeat v=var1 to var5/r=region1 to region5/x=0 to 4
+   /y=10 to 6/z='abcd',x'010203','alksdj'+'fklasdjfladsf','al''ksj','iouio'
+   /longname=var00001 to var00005.
+compute v=2**x.
+compute v=x.
+compute r=v.
+compute longname=r.
+compute s(x+1)=z.
+end repeat print.
+list.
+
diff --git a/tests/expect/crosstabs.stat b/tests/expect/crosstabs.stat
new file mode 100644 (file)
index 0000000..db64d4a
--- /dev/null
@@ -0,0 +1,7 @@
+crosstabs.stat:1: CLEAR TRANSFORMATIONS is not allowed (1) before a command to
+        specify the input program, such as DATA LIST, (2) between FILE TYPE and
+        END FILE TYPE, (3) between INPUT PROGRAM and END INPUT PROGRAM.
+crosstabs.stat:1: warning: This command not executed.
+crosstabs.stat:17: CROSSTABS: Syntax error expecting end of command at
+        `PIVOT'.
+crosstabs.stat:17: warning: This command not executed.
diff --git a/tests/expect/data-fmts.stat b/tests/expect/data-fmts.stat
new file mode 100644 (file)
index 0000000..531d1fe
--- /dev/null
@@ -0,0 +1,21 @@
+data-fmts.stat:24: data-file error: (columns 33-40, field type F8.0) Field
+        does not form a valid floating-point constant: "  abcd  ".
+data-fmts.stat:24: data-file error: (columns 41-48, field type PIBHEX8.0)
+        Trailing characters in field: "  ghjk  ".
+data-fmts.stat:24: data-file error: (columns 49-56, field type N8.0) All
+        characters in field must be digits.  Field actually contained:
+        "-1234567".
+data-fmts.stat:25: data-file error: (columns 17-24, field type COMMA8.0)
+        Trailing characters in field: "1234+56 ".
+data-fmts.stat:25: data-file error: (columns 25-32, field type DOLLAR8.0)
+        Trailing characters in field: "$1234+56".
+data-fmts.stat:25: data-file error: (columns 33-40, field type F8.0) Trailing
+        characters in field: "   0 1  ".
+data-fmts.stat:25: data-file error: (columns 49-56, field type N8.0) All
+        characters in field must be digits.  Field actually contained:
+        "12345e67".
+data-fmts.stat:26: data-file error: (columns 33-40, field type F8.0) Trailing
+        characters in field: " 1.2 3  ".
+data-fmts.stat:26: data-file error: (columns 49-56, field type N8.0) All
+        characters in field must be digits.  Field actually contained:
+        "1234e67 ".
diff --git a/tests/expect/data-list.stat b/tests/expect/data-list.stat
new file mode 100644 (file)
index 0000000..4e4a079
--- /dev/null
@@ -0,0 +1,17 @@
+data-list.data:2: warning: LIST: Missing value(s) for all variables from C
+        onward.  These will be filled with the system-missing value or blanks,
+        as appropriate.
+data-list.data:3: warning: LIST: Missing value(s) for all variables from B
+        onward.  These will be filled with the system-missing value or blanks,
+        as appropriate.
+data-list.data:4: data-file error: (columns 3-10, field type F8.0) Field does
+        not form a valid floating-point constant: "TOOLONGL".
+data-list.data:4: warning: LIST: Missing value(s) for all variables from C
+        onward.  These will be filled with the system-missing value or blanks,
+        as appropriate.
+data-list.data:5: warning: LIST: Missing value(s) for all variables from C
+        onward.  These will be filled with the system-missing value or blanks,
+        as appropriate.
+data-list.data:6: warning: LIST: Missing value(s) for all variables from B
+        onward.  These will be filled with the system-missing value or blanks,
+        as appropriate.
diff --git a/tests/expect/expr.stat b/tests/expect/expr.stat
new file mode 100644 (file)
index 0000000..1a3b1ca
--- /dev/null
@@ -0,0 +1,144 @@
+expr.stat:10: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:10: warning: This command not executed.
+expr.stat:11: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:11: warning: This command not executed.
+expr.stat:12: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:12: warning: This command not executed.
+expr.stat:15: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:15: warning: This command not executed.
+expr.stat:16: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:16: warning: This command not executed.
+expr.stat:17: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:17: warning: This command not executed.
+expr.stat:18: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:18: warning: This command not executed.
+expr.stat:20: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:20: warning: This command not executed.
+expr.stat:21: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:21: warning: This command not executed.
+expr.stat:22: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:22: warning: This command not executed.
+expr.stat:23: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:23: warning: This command not executed.
+expr.stat:25: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:25: warning: This command not executed.
+expr.stat:26: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:26: warning: This command not executed.
+expr.stat:28: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:28: warning: This command not executed.
+expr.stat:29: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:29: warning: This command not executed.
+expr.stat:30: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:30: warning: This command not executed.
+expr.stat:32: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:32: warning: This command not executed.
+expr.stat:33: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:33: warning: This command not executed.
+expr.stat:34: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:34: warning: This command not executed.
+expr.stat:35: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:35: warning: This command not executed.
+expr.stat:36: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:36: warning: This command not executed.
+expr.stat:37: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:37: warning: This command not executed.
+expr.stat:38: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:38: warning: This command not executed.
+expr.stat:39: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:39: warning: This command not executed.
+expr.stat:40: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:40: warning: This command not executed.
+expr.stat:41: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:41: warning: This command not executed.
+expr.stat:42: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:42: warning: This command not executed.
+expr.stat:43: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:43: warning: This command not executed.
+expr.stat:44: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:44: warning: This command not executed.
+expr.stat:45: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:45: warning: This command not executed.
+expr.stat:46: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:46: warning: This command not executed.
+expr.stat:47: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:47: warning: This command not executed.
+expr.stat:48: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:48: warning: This command not executed.
+expr.stat:49: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:49: warning: This command not executed.
+expr.stat:50: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:50: warning: This command not executed.
+expr.stat:51: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:51: warning: This command not executed.
+expr.stat:52: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:52: warning: This command not executed.
+expr.stat:53: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:53: warning: This command not executed.
+expr.stat:54: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:54: warning: This command not executed.
+expr.stat:55: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:55: warning: This command not executed.
+expr.stat:56: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:56: warning: This command not executed.
+expr.stat:57: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:57: warning: This command not executed.
+expr.stat:58: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:58: warning: This command not executed.
+expr.stat:59: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:59: warning: This command not executed.
+expr.stat:60: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:60: warning: This command not executed.
+expr.stat:61: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:61: warning: This command not executed.
+expr.stat:63: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:63: warning: This command not executed.
+expr.stat:64: The identifier(s) specified do not form a valid command name:
+        EVAL.
+expr.stat:64: warning: This command not executed.
diff --git a/tests/expect/loop.stat b/tests/expect/loop.stat
new file mode 100644 (file)
index 0000000..af21733
--- /dev/null
@@ -0,0 +1 @@
+loop.stat:12: warning: BREAK: BREAK not enclosed in DO IF structure.
diff --git a/tests/expect/mdfy-vars.stat b/tests/expect/mdfy-vars.stat
new file mode 100644 (file)
index 0000000..c9a9472
--- /dev/null
@@ -0,0 +1,3 @@
+mdfy-vars.stat:22: RENAME VARIABLES: Duplicate variable name `T2' after
+        renaming.
+mdfy-vars.stat:22: warning: This command not executed.
diff --git a/tests/expect/means.stat b/tests/expect/means.stat
new file mode 100644 (file)
index 0000000..03dee4e
--- /dev/null
@@ -0,0 +1,2 @@
+MEANS VARIABLES=V1(1,4) V2(1,9) V3(LO,HI)
+ TABLES=V1 BY V2 BY V3
diff --git a/tests/expect/print.stat b/tests/expect/print.stat
new file mode 100644 (file)
index 0000000..09ac7ff
--- /dev/null
@@ -0,0 +1,36 @@
+data-list.data:1: data-file error: (columns 1-8, field type F8.0) Field does
+        not form a valid floating-point constant: "SHORT   ".
+data-list.data:1: warning: LIST: The expression on PRINT SPACE evaluated to -
+        2147483648.  It's not possible to PRINT SPACE a negative number of
+        lines.
+data-list.data:2: data-file error: (columns 1-8, field type F8.0) Field does
+        not form a valid floating-point constant: "RIGHTLEN".
+data-list.data:4: warning: LIST: The expression on PRINT SPACE evaluated to -
+        2147483648.  It's not possible to PRINT SPACE a negative number of
+        lines.
+data-list.data:4: data-file error: (columns 3-10, field type F8.0) Field does
+        not form a valid floating-point constant: "TOOLONGL".
+data-list.data:6: warning: LIST: The expression on PRINT SPACE evaluated to -
+        2147483648.  It's not possible to PRINT SPACE a negative number of
+        lines.
+data-list.data:1: data-file error: (columns 1-8, field type F8.0) Field does
+        not form a valid floating-point constant: "SHORT   ".
+data-list.data:2: data-file error: (columns 1-8, field type F8.0) Field does
+        not form a valid floating-point constant: "RIGHTLEN".
+data-list.data:2: warning: LIST: Missing value(s) for all variables from C
+        onward.  These will be filled with the system-missing value or blanks,
+        as appropriate.
+data-list.data:3: warning: LIST: Missing value(s) for all variables from B
+        onward.  These will be filled with the system-missing value or blanks,
+        as appropriate.
+data-list.data:4: data-file error: (columns 3-10, field type F8.0) Field does
+        not form a valid floating-point constant: "TOOLONGL".
+data-list.data:4: warning: LIST: Missing value(s) for all variables from C
+        onward.  These will be filled with the system-missing value or blanks,
+        as appropriate.
+data-list.data:5: warning: LIST: Missing value(s) for all variables from C
+        onward.  These will be filled with the system-missing value or blanks,
+        as appropriate.
+data-list.data:6: warning: LIST: Missing value(s) for all variables from B
+        onward.  These will be filled with the system-missing value or blanks,
+        as appropriate.
diff --git a/tests/expect/t-test.stat b/tests/expect/t-test.stat
new file mode 100644 (file)
index 0000000..f423900
--- /dev/null
@@ -0,0 +1,23 @@
+T-TEST
+  PAIRS=V1 V2 WITH V3 V4 (PAIRED)
+  MISSING=ANALYSIS EXCLUDE
+  FORMAT=LABELS
+Missing value
+Missing value
+Missing value
+             Number of        2-tail
+ Variable      pairs    Corr   Sig      Mean    SD   SE of Mean
+---------------------------------------------------------------
+V1                                    0.0000   1.1909   0.3591
+            11.0000  -0.3835  0.8779
+V3                                    0.0000   2.4680   0.7441
+---------------------------------------------------------------
+
+
+
+      Paired Differences              |
+ Mean          SD         SE of Mean  |  t-value   df   2-tail Sig
+--------------------------------------|---------------------------
+ -1.1818      3.1247      0.9421      |  -1.2544  10.0000   0.2382
+95pc CI ( -3.2810,   0.9174)          |
+
diff --git a/tests/expect/vector.stat b/tests/expect/vector.stat
new file mode 100644 (file)
index 0000000..01682e2
--- /dev/null
@@ -0,0 +1,2 @@
+vector.stat:24: LIST: 6 is not a valid index value for vector X.  The result
+        will be set to the empty string.
diff --git a/tests/expect/weighting.stat b/tests/expect/weighting.stat
new file mode 100644 (file)
index 0000000..10f28d0
--- /dev/null
@@ -0,0 +1,2 @@
+weighting.stat:6: warning: VALUE LABELS: Truncating value label to 60
+        characters.
diff --git a/tests/expr.stat b/tests/expr.stat
new file mode 100644 (file)
index 0000000..0b9a4da
--- /dev/null
@@ -0,0 +1,65 @@
+title 'Test optimization of constant expressions'.
+
+remark EOF
+----------------------------------------------------------------------
+Testing ability of stat to optimize constant expressions.
+stat must have been compiled with debugging enabled in order to
+run these tests.
+----------------------------------------------------------------------
+EOF
+eval 1+2+3+(4*5).
+eval (4-2+6.323)*(0/4-1)*(1-3+abs(-9.78)).
+eval 3**(2**(8/2*3/4)).
+
+* Truth tables. 
+eval 0 and 0.
+eval 0 and 1.
+eval 1 and 0.
+eval 1 and 1.
+
+eval 0 or 0.
+eval 0 or 1.
+eval 1 or 0.
+eval 1 or 1.
+
+eval not 0.
+eval not 1.
+
+eval (1 gt 2) and (2 gt 1).
+eval (7/8+1 lt 2) or (2-1/6 lt 1).
+eval not (7 ne 6).
+
+eval -(7/8).
+eval abs(-6.5).
+eval arcos(1.0).
+eval arcos(sqrt(2)/2).
+eval arsin(sqrt(2)/2).
+eval artan(0).
+eval artan(1.0).
+eval cos(3.141592654/4).
+eval cos(3.141592654/6).
+eval exp(1).
+eval exp(-1).
+eval lg10(10).
+eval lg10(128.910).
+eval ln(2.71828182846).
+eval ln(50).
+eval mod10(128.910).
+eval rnd(128.9).
+eval rnd(-128.9).
+eval rnd(128.1).
+eval rnd(-128.1).
+eval rnd(128).
+eval rnd(-128).
+eval rnd(128.5).
+eval rnd(-128.5).
+eval sin(2*3.141592654).
+eval sin(3.141592654/6).
+eval tan(0).
+eval tan(3.141592654/8).
+eval trunc(3.141592654).
+eval trunc(-9.99).
+
+eval 'x'.
+eval concat('x','y').
+
diff --git a/tests/file-lab.stat b/tests/file-lab.stat
new file mode 100644 (file)
index 0000000..741a6dd
--- /dev/null
@@ -0,0 +1,53 @@
+title 'Test FILE LABEL, DOCUMENT, DROP DOCUMENTS'.
+
+/* Set up a dummy active file in memory.
+data list /x 1 y 2.
+begin data.
+16
+27
+38
+49
+50
+end data.
+
+/* Add value labels for some further testing of value labels.
+value labels x y 1 'first label' 2 'second label' 3 'third label'.
+add value labels x 1 'first label mark two'.
+
+/* Add a file label and a few documents.
+file label This is a test file label.
+document First line of a document
+This is the second very long line of a document in an attempt to overflow the input buffer with a really long line
+Note that the last line should end with a period: .
+
+/* Display the documents.
+display documents.
+display file label.    /* undocumented feature of PSPP
+
+/* Save the active file then get it and display the documents again.
+save 'foo.save'.
+get 'foo.save'.
+display documents.
+display file label.    /* undocumented feature of PSPP
+
+/* There is an interesting interaction that occurs if the `execute'
+/* command below.  What happens is that an error message is output
+/* at the next `save' command that `foo.save' is already open for
+/* input.  This is because the `get' hasn't been executed yet and
+/* therefore PSPP would be reading from and writing to the same
+/* file at once, which is obviously a Bad Thing.  But `execute'
+/* here clears up that potential problem.
+execute.
+
+/* Add another (shorter) document and try again.
+document There should be another document now.
+display documents.
+
+/* Save and get.
+save 'foo.save'.
+get 'foo.save'.
+display documents.
+display file label.    /* undocumented feature of PSPP
+
+/* Done.
+
diff --git a/tests/filter.stat b/tests/filter.stat
new file mode 100644 (file)
index 0000000..3761e38
--- /dev/null
@@ -0,0 +1,21 @@
+title 'Test FILTER'.
+
+data list /x 1-2.
+begin data.
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+end data.
+compute filter_$ = mod(x,2).
+filter by filter_$.
+list.
+compute filter_$ = 1 - filter_$.
+list.
+
diff --git a/tests/flip.stat b/tests/flip.stat
new file mode 100644 (file)
index 0000000..e1ee8be
--- /dev/null
@@ -0,0 +1,13 @@
+data list /n 1 (a) a b c d 2-9.
+list.
+begin data.
+v 1 2 3 4 5
+w 6 7 8 910
+x1112131415
+y1617181920
+z2122232425
+end data.
+flip newnames=n.
+list.
+flip.
+list.
diff --git a/tests/gengarbage.c b/tests/gengarbage.c
new file mode 100644 (file)
index 0000000..f079e91
--- /dev/null
@@ -0,0 +1,41 @@
+/* gengarbage - Generates 127-character lines of random digits. 
+   Copyright (C) 1997, 1998 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA. */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#ifndef EXIT_SUCCESS
+#define EXIT_SUCCESS 0
+#endif
+
+int
+main (void)
+{
+  srand (time (0));
+  for (;;)
+    {
+      int c;
+      
+      for (c = 0; c < 127; c++)
+       putchar ('0' + rand() % 10);
+      putchar ('\n');
+    }
+  exit (EXIT_SUCCESS);
+}
diff --git a/tests/inpt-pgm.stat b/tests/inpt-pgm.stat
new file mode 100644 (file)
index 0000000..69e934c
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+/*
+/* Tests for INPUT PROGRAM.
+/*
+/*
+
+remark EOF
+----------------------------------------------------------------------
+Testing use of INPUT PROGRAM.
+----------------------------------------------------------------------
+EOF
+input program.
+data list free/A #B.
+end case.
+data list free/C D.
+end case.
+data list fixed/#E 2.
+end case.
+end input program.
+list.
+
+begin data.
+1 2 3 4 5
+6 7 8 9 0
+end data.
+
diff --git a/tests/lag.stat b/tests/lag.stat
new file mode 100644 (file)
index 0000000..5b9e936
--- /dev/null
@@ -0,0 +1,16 @@
+title 'Test LAG'.
+
+data list /w 1.
+begin data.
+1
+2
+3
+4
+5
+end data.
+
+compute x=lag(w,1).
+compute y=lag(x).
+compute z=lag(w,2).
+list.
+
diff --git a/tests/list.data b/tests/list.data
new file mode 100644 (file)
index 0000000..1205b46
--- /dev/null
@@ -0,0 +1,25 @@
+7675324663485137890734831064091758592958428152951137532659418752338157675324663485137890734831064091758592958428152951137532658
+8886930894241775423783341867603681475586900279731022668741684555067148886930894241775423783341867603681475586900279731022668746
+4926115079091871527264278522424838562069980278342188725586260041526024926115079091871527264278522424838562069980278342188725589
+8198488920231958758793320014917736896880133221016088440640236265727008198488920231958758793320014917736896880133221016088440641
+4527778987095637298455415166506950647746645120849887640160659505391494527778987095637298455415166506950647746645120849887640166
+2399619670777327606635251150735186059118186910880465190328196246896752399619670777327606635251150735186059118186910880465190325
+1667799691266476994404743989237315394276412079760310706395103299441571667799691266476994404743989237315394276412079760310706394
+1623914684196892316847117011543627119597908599740525246164191508013201623914684196892316847117011543627119597908599740525246168
+3681393233760129489113121829599857288501099123283196628714148965084573681393233760129489113121829599857288501099123283196628710
+6418731145431082994856816505035997982096732150359754547299618487885306418731145431082994856816505035997982096732150359754547297
+2284534083749507716651086429071219765163759829793478587147234341234422284534083749507716651086429071219765163759829793478587149
+6617637452040749181349911788974757522469664838867901014182486697572956617637452040749181349911788974757522469664838867901014185
+9865713582686612007222010782682778269839299871393015436402026985409089865713582686612007222010782682778269839299871393015436406
+1163234537762200807794960252447773098443340762844734350378750440902951163234537762200807794960252447773098443340762844734350377
+9981663637563833300035426136702893989464123526087380834445132807905549981663637563833300035426136702893989464123526087380834449
+6821567746059103565005738960248842198995590602288700476282307110291686821567746059103565005738960248842198995590602288700476284
+0952774952675261545955280805340357545942400156201918638742082134243330952774952675261545955280805340357545942400156201918638748
+1641790193211861509106839217119496865877118406579619492614744114869021641790193211861509106839217119496865877118406579619492615
+3763182871580174789328837194968536876074344562932187960893275881656443763182871580174789328837194968536876074344562932187960891
+2046820753062224045535890932721137819807333757171926425442973439426792046820753062224045535890932721137819807333757171926425441
+7970620091940385928762632764618525899890186135929797170456339589318347970620091940385928762632764618525899890186135929797170458
+4841176017025105774506500896252757076690392034601283834048308843632644841176017025105774506500896252757076690392034601283834045
+6949973797990956291072158123887473582962673878519619834868801568536326949973797990956291072158123887473582962673878519619834868
+1396285996535489440816124700682933874365128786823824758133461156649721396285996535489440816124700682933874365128786823824758138
+0700489524358208358697349450036208378421878800636427151211185320194660700489524358208358697349450036208378421878800636427151218
diff --git a/tests/list.stat b/tests/list.stat
new file mode 100644 (file)
index 0000000..2264334
--- /dev/null
@@ -0,0 +1,25 @@
+title 'Test LIST procedure.'
+
+*** Single lines.
+remark EOF
+----------------------------------------------------------------------
+Testing use of LIST in single-line cases.
+----------------------------------------------------------------------
+EOF
+data list file='weighting.data'/AVAR 1-5 BVAR 6-10.
+weight by BVAR.
+list.
+*list /cases=from 5 to 20 by 2 /format numbered.
+list /format numbered weight.
+
+*** Multiple lines.
+remark EOF
+----------------------------------------------------------------------
+Testing use of LIST in multi-line cases.
+----------------------------------------------------------------------
+EOF
+data list file='list.data' notable /X000 to X126 1-127.
+*list /cases=from 1 to 25 by 5 /format numbered.
+list x000 to x030.
+list /cases=from 1 to 25.
+
diff --git a/tests/loop.stat b/tests/loop.stat
new file mode 100644 (file)
index 0000000..b1aa4c1
--- /dev/null
@@ -0,0 +1,14 @@
+title 'Test LOOP procedure'.
+
+data list /x 1 y 2 z 3.
+begin data.
+125
+256
+397
+401
+end data.
+loop i=y to z by abs(z-y)/(z-y).
+print /x i.
+break.         /* Generates warning.
+end loop.
+execute.
diff --git a/tests/mdfy-vars.stat b/tests/mdfy-vars.stat
new file mode 100644 (file)
index 0000000..d53cddb
--- /dev/null
@@ -0,0 +1,31 @@
+title 'Test MODIFY VARS, RENAME VARIABLES'.
+
+/* Note that these are not quite in alphabetic order.
+data list /a b c d e f g h i j k m n o p l q r s t u v w x y z 1-26 (a).
+
+/* Dummy data that's not actually examined.
+begin data.
+ABCDEFGHIJKLMNOPQRSTUVWXYZ
+NOPQRSTUVWXYZABCDEFGHIJKLM
+end data.
+
+/* This should display z y x w s q p n m l k j i h t7 t6 t5 t4 t3 t2 t1.
+modify vars /reorder=backward alpha/drop o r t u v/rename (a to g=t1 to t7).
+display var.
+
+/* This should display z y x w s q p n m l x14 x13 x12 x11 t1 t6 t5 t4 t3
+/* t2 t7.
+rename variables (t1=t7)(t7=t1)(h i j k=x11 to x14).
+display var.
+
+/* The command below should fail with an error message.
+rename variables (t1=t2)(t2=t2).
+
+/* This should display y x w s q p n m l x14 x13 x12 x11 t1 t6 t5 t3 t7.
+modify vars /reorder=forward positional/keep y to t5 t3 x14 t7.
+display var.
+
+/* This should display t7 x11 q s x y. */
+modify vars /reorder=backward positional/keep x s y x11 t7 q.
+display var.
+
diff --git a/tests/means.stat b/tests/means.stat
new file mode 100644 (file)
index 0000000..dbc00df
--- /dev/null
@@ -0,0 +1,14 @@
+title 'Preliminary test for MEANS procedure'.
+
+data list /v1 to v4 1-4.
+begin data.
+1234
+321
+2 13
+4121
+1104
+03 4
+0193
+end data.
+means variables=v1(1,4) v2(1,9) v3(lo,hi)
+  /tables=v1 by v2 by v3.
diff --git a/tests/mtch-file.stat b/tests/mtch-file.stat
new file mode 100644 (file)
index 0000000..ba6a033
--- /dev/null
@@ -0,0 +1,55 @@
+title 'Test MATCH FILES'.
+data list /x a b c 1-4.
+begin data.
+0243
+1983
+2924
+2853
+3195
+4862
+2056
+end data.
+save 'mtf-1.save'.
+
+data list /x d e f 1-4.
+begin data.
+0837
+1834
+2843
+2049
+3853
+5029
+2853
+end data.
+save 'mtf-2.save'.
+
+data list /x g h i 1-4.
+begin data.
+0743
+1823
+6845
+2875
+3945
+4341
+2723
+end data.
+
+match files /file='mtf-1.save' /file='mtf-2.save' /table=* /by x.
+list.
+
+data list /x g h i 1-4.
+begin data.
+0743
+1823
+6845
+2875
+3945
+4341
+2723
+end data.
+
+match files /file='mtf-1.save' /file='mtf-2.save' /file=* /by x.
+list.
+
+match files/file='mtf-1.save' /file='mtf-2.save'/by x.
+list.
diff --git a/tests/pcs-if.stat b/tests/pcs-if.stat
new file mode 100644 (file)
index 0000000..29ee48c
--- /dev/null
@@ -0,0 +1,21 @@
+title 'Test PROCESS IF utility'.
+
+data list /x 1-2.
+begin data.
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+end data.
+process if mod(x,2) ~= 0.
+list.
+compute x = x*3.
+process if mod(x,2) = 0.
+list.
+
diff --git a/tests/print.stat b/tests/print.stat
new file mode 100644 (file)
index 0000000..3652096
--- /dev/null
@@ -0,0 +1,28 @@
+title 'Test PRINT transformation'.
+
+remark EOF
+----------------------------------------------------------------------
+There is no test for DATA LIST FIXED since it is imagined that the
+rest of the tests give it a pretty good workout.
+----------------------------------------------------------------------
+EOF
+remark EOF
+----------------------------------------------------------------------
+Testing use of DATA LIST FREE.
+----------------------------------------------------------------------
+EOF
+data list free table file='data-list.data'/A B C D.
+print outfile="foo" table/A(f8.2) '/' B(e8.2) '/' C(n10) '/' D(rbhex16) '/'.
+print space a.
+print outfile="foo" /a b c d.
+list.
+
+remark EOF
+----------------------------------------------------------------------
+Testing use of DATA LIST LIST.
+----------------------------------------------------------------------
+EOF
+data list list table file='data-list.data'/A B C D.
+print table/A B C D.
+list.
+
diff --git a/tests/recode.stat b/tests/recode.stat
new file mode 100644 (file)
index 0000000..07a64f3
--- /dev/null
@@ -0,0 +1,21 @@
+title 'Test RECODE transformation'.
+
+data list /A B 1-20(a).
+begin data.
+12345678901234567890
+a         b
+jkl;      aklsdf
+aklsd     ioqeur
+ ioquer   pasdflk
+end data.
+*recode        A B(1,2,3,4=5)(5 thru hi=9)(lo thru 10=4) into A D
+       /A B(lo thru hi=copy)(sysmis=0)(else=sysmis) into C D.
+string c d(a10).
+leave c d.
+recode A B('a'='b')('jkl;'='jkl;p')('ioqeur'='sdjfkla') into C D.
+*recode A B(1,2,3,4="asdf")(else="xyzw")(sysmis="bdfg") into C D.
+*recode A B("asdf"=copy)(convert)("lkjf"=sysmis)(convert)(else=123) into C D.
+*recode A B(1,3,5,6,7=COPY)(SYSMIS=5e5) into C D.
+*recode a (convert)('xx'=50) into b.
+list.
+
diff --git a/tests/repeating.stat b/tests/repeating.stat
new file mode 100644 (file)
index 0000000..fdfd330
--- /dev/null
@@ -0,0 +1,16 @@
+title 'Test REPEATING DATA utility'.
+
+input program.
+data list /x 1 n 3.
+repeating data starts=11-20 /continues=2-11 /length=10 /occurs=n 
+              /id=1=x /data=name 1-5 (a) number 6-10.
+end input program.
+
+begin data.
+1 3       foo  1    
+1bar  2    
+1baz  3
+end data.
+
+list.
+
diff --git a/tests/reread.data b/tests/reread.data
new file mode 100644 (file)
index 0000000..caa7a04
--- /dev/null
@@ -0,0 +1,5 @@
+5510ACME  5
+   5MISC  8901
+8974ACME  9
+1928ACME  4
+   6MISC  8973
diff --git a/tests/reread.stat b/tests/reread.stat
new file mode 100644 (file)
index 0000000..694e61d
--- /dev/null
@@ -0,0 +1,16 @@
+title 'Test REREAD transformation'.
+
+file handle INPUT /name='reread.data'.
+
+input program.
+  data list file=INPUT/BRAND 5-10(a).
+  do if(BRAND='ACME').
+    reread.
+    data list /PART 1-4 COUNT 11-15.
+  else if(BRAND='MISC').
+    reread.
+    data list /PART 11-15 COUNT 1-4.
+  end if.
+  end case.
+end input program.
+list.
diff --git a/tests/sample.stat b/tests/sample.stat
new file mode 100644 (file)
index 0000000..9a2c007
--- /dev/null
@@ -0,0 +1,19 @@
+title 'Test SAMPLE utility'.
+
+set seed=random.
+data list /a 1-2.
+begin data.
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+end data.
+sample .5.
+n 5.
+list.
diff --git a/tests/sort.stat b/tests/sort.stat
new file mode 100644 (file)
index 0000000..ff85e43
--- /dev/null
@@ -0,0 +1,8 @@
+title 'Test SORT procedure'.
+
+data list file='sort.data' notable /X000 to X126 1-127(a).
+*data list file='sort.data' notable /X000 to X005 1-6(a).
+sort by X000 to x005.
+print /X000 to X005.
+execute.
+
diff --git a/tests/splt-file.stat b/tests/splt-file.stat
new file mode 100644 (file)
index 0000000..422d381
--- /dev/null
@@ -0,0 +1,23 @@
+title 'Test SPLIT FILE utility'.
+
+data list /x 1 y 2.
+begin data.
+12
+16
+17
+19
+15
+14
+27
+20
+26
+25
+28
+29
+24
+end data.
+split file by x.
+formats x(e20.2).
+value labels x 1 'This is the value label for value 1.'
+               2 'This is the value label for value 2.'.
+descriptives y.
diff --git a/tests/syntax b/tests/syntax
new file mode 100755 (executable)
index 0000000..3f2ced7
--- /dev/null
@@ -0,0 +1,51 @@
+#! /bin/sh
+set -e
+RESULT=pass
+
+srcdir=${srcdir:-.}
+for x in `cd $srcdir; echo *.stat *.data`; do 
+    if [ ! -e $x ]; then
+       ln -s $srcdir/$x .
+    fi
+done
+
+rm -f *.actual
+if [ -z "$BENCHMARK" ]; then
+    for x in *.stat; do
+       echo -n "$x ... "
+       ../src/pspp --testing-mode $x >$x.actual
+       if [ -f $srcdir/expect/$x ]; then
+           if diff -u $srcdir/expect/$x $x.actual; then 
+               echo "pass"; rm $x.actual
+           else 
+               echo "FAIL"; RESULT=fail
+           fi
+       else
+           if [ -s $x.actual ]; then 
+               echo "FAIL"; RESULT=fail
+           else 
+               echo "pass"; rm $x.actual
+           fi
+       fi
+    done
+else
+    mkdir benchmark || true
+    rm -f benchmark/*
+    for x in *.stat; do
+       echo -n "$x ... "
+       ../src/pspp --testing-mode $x > benchmark/$x
+       if [ ! -s benchmark/$x ]; then
+           rm benchmark/$x
+       fi
+       echo
+    done
+fi
+
+for x in *.stat *.data; do
+    if [ -h $x ]; then
+       rm $x
+    fi
+done
+
+if [ $RESULT = fail ]; then exit 1; fi
+
diff --git a/tests/sys-info.stat b/tests/sys-info.stat
new file mode 100644 (file)
index 0000000..070e0d5
--- /dev/null
@@ -0,0 +1,13 @@
+title 'Test SYSFILE INFO, DISPLAY utilities'.
+
+/* Run file-label.stat before running this syntax file, as it
+/* creates foo.save.
+sysfile info file='foo.save'.
+
+get 'foo.save'.
+display names.
+display index.
+display labels.
+display variables.
+display dictionary.
+
diff --git a/tests/t-test.stat b/tests/t-test.stat
new file mode 100644 (file)
index 0000000..3395a50
--- /dev/null
@@ -0,0 +1,13 @@
+title 'Preliminary test for T-TEST procedure'.
+
+data list /v1 to v4 1-4.
+begin data.
+1234
+321
+2 13
+4121
+1104
+03 4
+0193
+end data.
+t-test v1 v2 with v3 v4 (paired).
diff --git a/tests/tabs.stat b/tests/tabs.stat
new file mode 100644 (file)
index 0000000..fbeede6
--- /dev/null
@@ -0,0 +1,12 @@
+title 'Test handling of tab characters in user data'.
+
+/* The program contains separate code for the case of a single
+/* tab on a line and multiple tabs, so we try both below.
+data list /x 1-80 (a).
+begin data.
+       1       12      123     1234    12345   123456  1234567 12345678
+asdf   jkl;
+end data.
+print /x.
+execute.
+
diff --git a/tests/temporary.stat b/tests/temporary.stat
new file mode 100644 (file)
index 0000000..08645a3
--- /dev/null
@@ -0,0 +1,29 @@
+title 'Test TEMPORARY transformation'.
+
+set echo on/screen on.
+data list /z 1 x 2.
+formats x(f3).
+split file by z.
+list.
+begin data.
+12
+13
+14
+15
+16
+23
+24
+25
+26
+27
+28
+end data.
+compute x=x+1.
+temporary.
+compute x=x+1.
+compute y=x+1.
+sel if y<7.
+descriptives x y.
+list.
+compute x=x-10.
+list.
diff --git a/tests/time-date.stat b/tests/time-date.stat
new file mode 100644 (file)
index 0000000..f9a9c73
--- /dev/null
@@ -0,0 +1,77 @@
+title 'Test time and date input and output formats'.
+
+data list fixed
+       /a(date20)/b(adate20)/c(jdate20)/d(qyr20)/e(moyr20)/f(wkyr20)/g(wkday20)
+       /h(month20)/i(time20.2)/j(dtime20.2)/k(datetime24.2).
+begin data.
+01-oct-78
+10/1/78
+78101
+3q96
+nov 52
+38wk23
+saturday
+xi
+10:01
+4 5:12.9
+01-feb-1903 04:05:06.07
+end data.
+print /a to e.
+print /f to k.
+execute.
+
+data list /x 1.
+begin data.
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+end data.
+compute y=$time.
+formats y(datetime25).
+list.
+compute z=$time.
+formats z(datetime25).
+list.
+compute p=$time.
+formats p(datetime25).
+list.
+
diff --git a/tests/vector.stat b/tests/vector.stat
new file mode 100644 (file)
index 0000000..8f70226
--- /dev/null
@@ -0,0 +1,24 @@
+title 'Test VECTOR utility'.
+
+data list /c 1(a).
+begin data.
+5
+3
+4
+1
+2
+end data.
+
+string x1 to x5 y1 to y5(a1).
+vector x=x1 to x5.
+*formats all(f1).
+compute x(number(c))=c.
+leave x1 to x5.
+list.
+
+vector x=x1 to x5.
+vector y=y1 to y5.
+*formats all(f1).
+compute y(number(c))=x(number(c)+1).
+display vector.
+list.
diff --git a/tests/weighting.data b/tests/weighting.data
new file mode 100644 (file)
index 0000000..bf74fa0
--- /dev/null
@@ -0,0 +1,52 @@
+   18    1
+   19    7
+   20   26
+   21   76
+   22   57
+   23   58
+   24   38
+   25   38
+   26   30
+   27   21
+   28   23
+   29   24
+   30   23
+   31   14
+   32   21
+   33   21
+   34   14
+   35   14
+   36   17
+   37   11
+   38   16
+   39   14
+   40   15
+   41   14
+   42   14
+   43    8
+   44   15
+   45   10
+   46   12
+   47   13
+   48   13
+   49    5
+   50    5
+   51    3
+   52    7
+   53    6
+   54    2
+   55    2
+   56    2
+   57    3
+   58    1
+   59    3
+   61    1
+   62    3
+   63    1
+   64    1
+   65    2
+   70    1
+   78    1
+   79    1
+   80    1
+   94    1
\ No newline at end of file
diff --git a/tests/weighting.stat b/tests/weighting.stat
new file mode 100644 (file)
index 0000000..2611e74
--- /dev/null
@@ -0,0 +1,8 @@
+title 'Test WEIGHT'.
+
+data list file='weighting.data'/AVAR 1-5 BVAR 6-10.
+weight by BVAR.
+value labels avar 18 'This is a value label that is really long so it takes up lots of room.'
+                  19 'flustered and flim-flammed by fields'.
+descriptives AVAR /statistics all /format serial.
+frequencies AVAR /statistics all /format condensed.