Added new files resulting from directory restructuring.
authorJohn Darrington <john@darrington.wattle.id.au>
Sat, 4 Mar 2006 01:11:56 +0000 (01:11 +0000)
committerJohn Darrington <john@darrington.wattle.id.au>
Sat, 4 Mar 2006 01:11:56 +0000 (01:11 +0000)
294 files changed:
lib/gtksheet/COPYING.LESSER [new file with mode: 0644]
lib/gtksheet/ChangeLog [new file with mode: 0644]
lib/gtksheet/README [new file with mode: 0644]
lib/gtksheet/automake.mk [new file with mode: 0644]
lib/gtksheet/gsheet-column-iface.c [new file with mode: 0644]
lib/gtksheet/gsheet-column-iface.h [new file with mode: 0644]
lib/gtksheet/gsheet-hetero-column.c [new file with mode: 0644]
lib/gtksheet/gsheet-hetero-column.h [new file with mode: 0644]
lib/gtksheet/gsheet-row-iface.c [new file with mode: 0644]
lib/gtksheet/gsheet-row-iface.h [new file with mode: 0644]
lib/gtksheet/gsheet-uniform-column.c [new file with mode: 0644]
lib/gtksheet/gsheet-uniform-column.h [new file with mode: 0644]
lib/gtksheet/gsheet-uniform-row.c [new file with mode: 0644]
lib/gtksheet/gsheet-uniform-row.h [new file with mode: 0644]
lib/gtksheet/gsheetmodel.c [new file with mode: 0644]
lib/gtksheet/gsheetmodel.h [new file with mode: 0644]
lib/gtksheet/gtkextra-marshal.c [new file with mode: 0644]
lib/gtksheet/gtkextra-marshal.h [new file with mode: 0644]
lib/gtksheet/gtkextra-sheet.h [new file with mode: 0644]
lib/gtksheet/gtkextra.c [new file with mode: 0644]
lib/gtksheet/gtkextrafeatures.h [new file with mode: 0644]
lib/gtksheet/gtkiconlist.c [new file with mode: 0644]
lib/gtksheet/gtkiconlist.h [new file with mode: 0644]
lib/gtksheet/gtkitementry.c [new file with mode: 0644]
lib/gtksheet/gtkitementry.h [new file with mode: 0644]
lib/gtksheet/gtksheet.c [new file with mode: 0644]
lib/gtksheet/gtksheet.h [new file with mode: 0644]
src/data/ChangeLog [new file with mode: 0644]
src/data/any-reader.c [new file with mode: 0644]
src/data/any-reader.h [new file with mode: 0644]
src/data/any-writer.c [new file with mode: 0644]
src/data/any-writer.h [new file with mode: 0644]
src/data/calendar.c [new file with mode: 0644]
src/data/calendar.h [new file with mode: 0644]
src/data/case.c [new file with mode: 0644]
src/data/case.h [new file with mode: 0644]
src/data/casefile.c [new file with mode: 0644]
src/data/casefile.h [new file with mode: 0644]
src/data/cat-routines.h [new file with mode: 0644]
src/data/category.c [new file with mode: 0644]
src/data/category.h [new file with mode: 0644]
src/data/data-in.c [new file with mode: 0644]
src/data/data-in.h [new file with mode: 0644]
src/data/data-out.c [new file with mode: 0644]
src/data/dictionary.c [new file with mode: 0644]
src/data/dictionary.h [new file with mode: 0644]
src/data/file-handle-def.c [new file with mode: 0644]
src/data/file-handle-def.h [new file with mode: 0644]
src/data/filename.c [new file with mode: 0644]
src/data/filename.h [new file with mode: 0644]
src/data/format.c [new file with mode: 0644]
src/data/format.def [new file with mode: 0644]
src/data/format.h [new file with mode: 0644]
src/data/identifier.c [new file with mode: 0644]
src/data/identifier.h [new file with mode: 0644]
src/data/make-file.c [new file with mode: 0644]
src/data/make-file.h [new file with mode: 0644]
src/data/missing-values.c [new file with mode: 0644]
src/data/missing-values.h [new file with mode: 0644]
src/data/por-file-reader.c [new file with mode: 0644]
src/data/por-file-reader.h [new file with mode: 0644]
src/data/por-file-writer.c [new file with mode: 0644]
src/data/por-file-writer.h [new file with mode: 0644]
src/data/scratch-handle.c [new file with mode: 0644]
src/data/scratch-handle.h [new file with mode: 0644]
src/data/scratch-reader.c [new file with mode: 0644]
src/data/scratch-reader.h [new file with mode: 0644]
src/data/scratch-writer.c [new file with mode: 0644]
src/data/scratch-writer.h [new file with mode: 0644]
src/data/settings.c [new file with mode: 0644]
src/data/settings.h [new file with mode: 0644]
src/data/sfm-private.h [new file with mode: 0644]
src/data/sys-file-reader.c [new file with mode: 0644]
src/data/sys-file-reader.h [new file with mode: 0644]
src/data/sys-file-writer.c [new file with mode: 0644]
src/data/sys-file-writer.h [new file with mode: 0644]
src/data/value-labels.c [new file with mode: 0644]
src/data/value-labels.h [new file with mode: 0644]
src/data/value.h [new file with mode: 0644]
src/data/variable.c [new file with mode: 0644]
src/data/variable.h [new file with mode: 0644]
src/language/ChangeLog [new file with mode: 0644]
src/language/command.c [new file with mode: 0644]
src/language/command.def [new file with mode: 0644]
src/language/command.h [new file with mode: 0644]
src/language/control/ChangeLog [new file with mode: 0644]
src/language/control/control-stack.c [new file with mode: 0644]
src/language/control/control-stack.h [new file with mode: 0644]
src/language/control/do-if.c [new file with mode: 0644]
src/language/control/loop.c [new file with mode: 0644]
src/language/control/repeat.c [new file with mode: 0644]
src/language/control/repeat.h [new file with mode: 0644]
src/language/control/temporary.c [new file with mode: 0644]
src/language/data-io/ChangeLog [new file with mode: 0644]
src/language/data-io/data-list.c [new file with mode: 0644]
src/language/data-io/data-list.h [new file with mode: 0644]
src/language/data-io/data-reader.c [new file with mode: 0644]
src/language/data-io/data-reader.h [new file with mode: 0644]
src/language/data-io/data-writer.c [new file with mode: 0644]
src/language/data-io/data-writer.h [new file with mode: 0644]
src/language/data-io/file-handle.h [new file with mode: 0644]
src/language/data-io/file-handle.q [new file with mode: 0644]
src/language/data-io/file-type.c [new file with mode: 0644]
src/language/data-io/get.c [new file with mode: 0644]
src/language/data-io/inpt-pgm.c [new file with mode: 0644]
src/language/data-io/list.q [new file with mode: 0644]
src/language/data-io/matrix-data.c [new file with mode: 0644]
src/language/data-io/print.c [new file with mode: 0644]
src/language/dictionary/ChangeLog [new file with mode: 0644]
src/language/dictionary/apply-dictionary.c [new file with mode: 0644]
src/language/dictionary/formats.c [new file with mode: 0644]
src/language/dictionary/missing-values.c [new file with mode: 0644]
src/language/dictionary/modify-variables.c [new file with mode: 0644]
src/language/dictionary/numeric.c [new file with mode: 0644]
src/language/dictionary/rename-variables.c [new file with mode: 0644]
src/language/dictionary/split-file.c [new file with mode: 0644]
src/language/dictionary/sys-file-info.c [new file with mode: 0644]
src/language/dictionary/value-labels.c [new file with mode: 0644]
src/language/dictionary/variable-display.c [new file with mode: 0644]
src/language/dictionary/variable-label.c [new file with mode: 0644]
src/language/dictionary/vector.c [new file with mode: 0644]
src/language/dictionary/weight.c [new file with mode: 0644]
src/language/expressions/.cvsignore [new file with mode: 0644]
src/language/expressions/ChangeLog [new file with mode: 0644]
src/language/expressions/TODO [new file with mode: 0644]
src/language/expressions/evaluate.c [new file with mode: 0644]
src/language/expressions/evaluate.h.pl [new file with mode: 0644]
src/language/expressions/evaluate.inc.pl [new file with mode: 0644]
src/language/expressions/generate.pl [new file with mode: 0644]
src/language/expressions/helpers.c [new file with mode: 0644]
src/language/expressions/helpers.h [new file with mode: 0644]
src/language/expressions/operations.def [new file with mode: 0644]
src/language/expressions/operations.h.pl [new file with mode: 0644]
src/language/expressions/optimize.c [new file with mode: 0644]
src/language/expressions/optimize.inc.pl [new file with mode: 0644]
src/language/expressions/parse.c [new file with mode: 0644]
src/language/expressions/parse.inc.pl [new file with mode: 0644]
src/language/expressions/private.h [new file with mode: 0644]
src/language/expressions/public.h [new file with mode: 0644]
src/language/lexer/ChangeLog [new file with mode: 0644]
src/language/lexer/format-parser.c [new file with mode: 0644]
src/language/lexer/lexer.c [new file with mode: 0644]
src/language/lexer/lexer.h [new file with mode: 0644]
src/language/lexer/q2c.c [new file with mode: 0644]
src/language/lexer/range-parser.c [new file with mode: 0644]
src/language/lexer/range-parser.h [new file with mode: 0644]
src/language/lexer/subcommand-list.c [new file with mode: 0644]
src/language/lexer/subcommand-list.h [new file with mode: 0644]
src/language/lexer/variable-parser.c [new file with mode: 0644]
src/language/line-buffer.c [new file with mode: 0644]
src/language/line-buffer.h [new file with mode: 0644]
src/language/stats/ChangeLog [new file with mode: 0644]
src/language/stats/aggregate.c [new file with mode: 0644]
src/language/stats/autorecode.c [new file with mode: 0644]
src/language/stats/correlations.q [new file with mode: 0644]
src/language/stats/crosstabs.q [new file with mode: 0644]
src/language/stats/descriptives.c [new file with mode: 0644]
src/language/stats/examine.q [new file with mode: 0644]
src/language/stats/flip.c [new file with mode: 0644]
src/language/stats/frequencies.q [new file with mode: 0644]
src/language/stats/means.q [new file with mode: 0644]
src/language/stats/oneway.q [new file with mode: 0644]
src/language/stats/rank.q [new file with mode: 0644]
src/language/stats/regression-export.h [new file with mode: 0644]
src/language/stats/regression.q [new file with mode: 0644]
src/language/stats/sort-cases.c [new file with mode: 0644]
src/language/stats/sort-criteria.c [new file with mode: 0644]
src/language/stats/sort-criteria.h [new file with mode: 0644]
src/language/stats/t-test.q [new file with mode: 0644]
src/language/tests/ChangeLog [new file with mode: 0644]
src/language/tests/casefile-test.c [new file with mode: 0644]
src/language/tests/moments-test.c [new file with mode: 0644]
src/language/tests/pool-test.c [new file with mode: 0644]
src/language/utilities/ChangeLog [new file with mode: 0644]
src/language/utilities/date.c [new file with mode: 0644]
src/language/utilities/echo.c [new file with mode: 0644]
src/language/utilities/include.c [new file with mode: 0644]
src/language/utilities/permissions.c [new file with mode: 0644]
src/language/utilities/set.q [new file with mode: 0644]
src/language/utilities/title.c [new file with mode: 0644]
src/language/xforms/ChangeLog [new file with mode: 0644]
src/language/xforms/compute.c [new file with mode: 0644]
src/language/xforms/count.c [new file with mode: 0644]
src/language/xforms/recode.c [new file with mode: 0644]
src/language/xforms/sample.c [new file with mode: 0644]
src/language/xforms/select-if.c [new file with mode: 0644]
src/libpspp/ChangeLog [new file with mode: 0644]
src/libpspp/alloc.c [new file with mode: 0644]
src/libpspp/alloc.h [new file with mode: 0644]
src/libpspp/array.c [new file with mode: 0644]
src/libpspp/array.h [new file with mode: 0644]
src/libpspp/bit-vector.h [new file with mode: 0644]
src/libpspp/copyleft.c [new file with mode: 0644]
src/libpspp/copyleft.h [new file with mode: 0644]
src/libpspp/debug-print.h [new file with mode: 0644]
src/libpspp/hash.c [new file with mode: 0644]
src/libpspp/hash.h [new file with mode: 0644]
src/libpspp/magic.c [new file with mode: 0644]
src/libpspp/magic.h [new file with mode: 0644]
src/libpspp/message.h [new file with mode: 0644]
src/libpspp/misc.c [new file with mode: 0644]
src/libpspp/misc.h [new file with mode: 0644]
src/libpspp/pool.c [new file with mode: 0644]
src/libpspp/pool.h [new file with mode: 0644]
src/libpspp/start-date.c [new file with mode: 0644]
src/libpspp/start-date.h [new file with mode: 0644]
src/libpspp/str.c [new file with mode: 0644]
src/libpspp/str.h [new file with mode: 0644]
src/libpspp/version.h [new file with mode: 0644]
src/math/ChangeLog [new file with mode: 0644]
src/math/chart-geometry.c [new file with mode: 0644]
src/math/chart-geometry.h [new file with mode: 0644]
src/math/design-matrix.c [new file with mode: 0644]
src/math/design-matrix.h [new file with mode: 0644]
src/math/factor-stats.c [new file with mode: 0644]
src/math/factor-stats.h [new file with mode: 0644]
src/math/group-proc.h [new file with mode: 0644]
src/math/group.c [new file with mode: 0644]
src/math/group.h [new file with mode: 0644]
src/math/histogram.c [new file with mode: 0644]
src/math/histogram.h [new file with mode: 0644]
src/math/levene.c [new file with mode: 0644]
src/math/levene.h [new file with mode: 0644]
src/math/moments.c [new file with mode: 0644]
src/math/moments.h [new file with mode: 0644]
src/math/percentiles.c [new file with mode: 0644]
src/math/percentiles.h [new file with mode: 0644]
src/math/random.c [new file with mode: 0644]
src/math/random.h [new file with mode: 0644]
src/math/sort.c [new file with mode: 0644]
src/math/sort.h [new file with mode: 0644]
src/message.c [new file with mode: 0644]
src/output/ChangeLog [new file with mode: 0644]
src/output/Makefile [new file with mode: 0644]
src/output/ascii.c [new file with mode: 0644]
src/output/chart.c [new file with mode: 0644]
src/output/chart.h [new file with mode: 0644]
src/output/dummy-chart.c [new file with mode: 0644]
src/output/font.h [new file with mode: 0644]
src/output/groff-font.c [new file with mode: 0644]
src/output/html.c [new file with mode: 0644]
src/output/htmlP.h [new file with mode: 0644]
src/output/manager.c [new file with mode: 0644]
src/output/manager.h [new file with mode: 0644]
src/output/output.c [new file with mode: 0644]
src/output/output.h [new file with mode: 0644]
src/output/postscript.c [new file with mode: 0644]
src/output/table.c [new file with mode: 0644]
src/output/table.h [new file with mode: 0644]
src/procedure.c [new file with mode: 0644]
src/procedure.h [new file with mode: 0644]
src/ui/ChangeLog [new file with mode: 0644]
src/ui/gui/ChangeLog [new file with mode: 0644]
src/ui/gui/customentry.c [new file with mode: 0644]
src/ui/gui/customentry.h [new file with mode: 0644]
src/ui/gui/data-sheet.c [new file with mode: 0644]
src/ui/gui/data-sheet.h [new file with mode: 0644]
src/ui/gui/helper.c [new file with mode: 0644]
src/ui/gui/helper.h [new file with mode: 0644]
src/ui/gui/menu-actions.c [new file with mode: 0644]
src/ui/gui/menu-actions.h [new file with mode: 0644]
src/ui/gui/message-dialog.c [new file with mode: 0644]
src/ui/gui/message-dialog.h [new file with mode: 0644]
src/ui/gui/missing-val-dialog.c [new file with mode: 0644]
src/ui/gui/missing-val-dialog.h [new file with mode: 0644]
src/ui/gui/psppicon.png [new file with mode: 0644]
src/ui/gui/psppire-case-array.c [new file with mode: 0644]
src/ui/gui/psppire-case-array.h [new file with mode: 0644]
src/ui/gui/psppire-data-store.c [new file with mode: 0644]
src/ui/gui/psppire-data-store.h [new file with mode: 0644]
src/ui/gui/psppire-dict.c [new file with mode: 0644]
src/ui/gui/psppire-dict.h [new file with mode: 0644]
src/ui/gui/psppire-object.c [new file with mode: 0644]
src/ui/gui/psppire-object.h [new file with mode: 0644]
src/ui/gui/psppire-var-store.c [new file with mode: 0644]
src/ui/gui/psppire-var-store.h [new file with mode: 0644]
src/ui/gui/psppire-variable.c [new file with mode: 0644]
src/ui/gui/psppire-variable.h [new file with mode: 0644]
src/ui/gui/psppire.c [new file with mode: 0644]
src/ui/gui/psppire.glade [new file with mode: 0644]
src/ui/gui/pspplogo.png [new file with mode: 0644]
src/ui/gui/val-labs-dialog.c [new file with mode: 0644]
src/ui/gui/val-labs-dialog.h [new file with mode: 0644]
src/ui/gui/var-sheet.c [new file with mode: 0644]
src/ui/gui/var-sheet.h [new file with mode: 0644]
src/ui/gui/var-type-dialog.c [new file with mode: 0644]
src/ui/gui/var-type-dialog.h [new file with mode: 0644]
src/ui/terminal/ChangeLog [new file with mode: 0644]
src/ui/terminal/command-line.c [new file with mode: 0644]
src/ui/terminal/command-line.h [new file with mode: 0644]
src/ui/terminal/main.c [new file with mode: 0644]
src/ui/terminal/read-line.c [new file with mode: 0644]
src/ui/terminal/read-line.h [new file with mode: 0644]
tests/command/do-repeat.sh [new file with mode: 0755]

diff --git a/lib/gtksheet/COPYING.LESSER b/lib/gtksheet/COPYING.LESSER
new file mode 100644 (file)
index 0000000..8add30a
--- /dev/null
@@ -0,0 +1,504 @@
+                 GNU LESSER GENERAL PUBLIC LICENSE
+                      Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     51 Franklin St, Fifth Floor, Boston, MA  02110-1301  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 Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                           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 Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+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 and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+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 other code 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.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+\f
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  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, whereas the latter must
+be combined with the library in order to run.
+\f
+                 GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser 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 combine 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) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) 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.
+
+    d) 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.
+
+    e) 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 materials to be 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 with
+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 Lesser 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 Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  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/gtksheet/ChangeLog b/lib/gtksheet/ChangeLog
new file mode 100644 (file)
index 0000000..0094fa4
--- /dev/null
@@ -0,0 +1,3 @@
+    * Separated the data out of the GtkSheet.  The gtksheet should now be
+    regarded as a way of looking into the data.  The data is represented by a
+    GSheetModel and the rows and columns by  GSheetRow and GSheetColumn.
diff --git a/lib/gtksheet/README b/lib/gtksheet/README
new file mode 100644 (file)
index 0000000..ebfce1d
--- /dev/null
@@ -0,0 +1,11 @@
+This is not part of the GNU PSPP program, but is used with GNU PSPP.
+
+This directory contains a version of the GtkSheet widget from the gtk-extra 
+project at http://gtkextra.sourceforge.net  The version found here has 
+major modifications developed for the needs of PSPP.  Every effort has been
+made to keep GtkSheet application independent. Thus, it should be possible
+to use this modified software for other applications.  However, the API is
+substantially different from the original.
+
+Files in this directory ONLY are licensed under the GNU Lesser General Public 
+License.  See COPYING.LGPL
diff --git a/lib/gtksheet/automake.mk b/lib/gtksheet/automake.mk
new file mode 100644 (file)
index 0000000..0aa3d78
--- /dev/null
@@ -0,0 +1,34 @@
+## Process this file with automake to produce Makefile.in  -*- makefile -*-
+
+noinst_LIBRARIES += lib/gtksheet/libgtksheet.a
+
+lib/gtksheet/%: AM_CPPFLAGS = \
+       -I$(top_builddir)/lib/gtksheet 
+
+lib/gtksheet/%: AM_CFLAGS = $(GTK_CFLAGS) -Wall
+
+
+lib_gtksheet_libgtksheet_a_SOURCES = \
+       lib/gtksheet/gsheet-column-iface.c \
+       lib/gtksheet/gsheet-column-iface.h \
+       lib/gtksheet/gsheet-hetero-column.c \
+       lib/gtksheet/gsheet-hetero-column.h \
+       lib/gtksheet/gsheetmodel.c \
+       lib/gtksheet/gsheetmodel.h \
+       lib/gtksheet/gsheet-row-iface.c \
+       lib/gtksheet/gsheet-row-iface.h \
+       lib/gtksheet/gsheet-uniform-column.c \
+       lib/gtksheet/gsheet-uniform-column.h \
+       lib/gtksheet/gsheet-uniform-row.c \
+       lib/gtksheet/gsheet-uniform-row.h \
+       lib/gtksheet/gtkextra.c \
+       lib/gtksheet/gtkextrafeatures.h \
+       lib/gtksheet/gtkextra-marshal.c \
+       lib/gtksheet/gtkextra-marshal.h \
+       lib/gtksheet/gtkextra-sheet.h \
+       lib/gtksheet/gtkiconlist.c \
+       lib/gtksheet/gtkiconlist.h \
+       lib/gtksheet/gtkitementry.c \
+       lib/gtksheet/gtkitementry.h \
+       lib/gtksheet/gtksheet.c \
+       lib/gtksheet/gtksheet.h 
diff --git a/lib/gtksheet/gsheet-column-iface.c b/lib/gtksheet/gsheet-column-iface.c
new file mode 100644 (file)
index 0000000..f1b9155
--- /dev/null
@@ -0,0 +1,281 @@
+/* GSheetColumn --- an abstract model of the column geometry of a 
+   GSheet widget.
+
+ * Copyright (C) 2006 Free Software Foundation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <gobject/gvaluecollector.h>
+#include "gsheet-column-iface.h"
+#include "gtkextra-marshal.h"
+
+
+enum {
+  COLUMNS_CHANGED,
+  LAST_SIGNAL
+};
+
+static guint sheet_column_signals[LAST_SIGNAL];
+
+
+
+static void      g_sheet_column_base_init   (gpointer g_class);
+
+
+inline GType
+g_sheet_column_get_type (void)
+{
+  static GType sheet_column_type = 0;
+
+  if (! sheet_column_type)
+    {
+      static const GTypeInfo sheet_column_info =
+
+      {
+        sizeof (GSheetColumnIface), /* class_size */
+       g_sheet_column_base_init,   /* base_init */
+       NULL,           /* base_finalize */
+       NULL,
+       NULL,           /* class_finalize */
+       NULL,           /* class_data */
+       0,
+       0,              /* n_preallocs */
+       NULL
+      };
+
+      sheet_column_type =
+       g_type_register_static (G_TYPE_INTERFACE, "GSheetColumn",
+                               &sheet_column_info, 0);
+
+      g_assert(sheet_column_type);
+
+      g_type_interface_add_prerequisite (sheet_column_type, G_TYPE_OBJECT);
+    }
+
+  return sheet_column_type;
+}
+
+
+static GtkSheetButton default_button;
+
+static void
+g_sheet_column_base_init (gpointer g_class)
+{
+  static gboolean initialized = FALSE;
+
+  if (! initialized)
+    {
+
+      sheet_column_signals[COLUMNS_CHANGED] =
+       g_signal_new ("columns_changed",
+                     G_TYPE_SHEET_COLUMN,
+                     G_SIGNAL_RUN_LAST,
+                     G_STRUCT_OFFSET (GSheetColumnIface, columns_changed),
+                     NULL, NULL,
+                     gtkextra_VOID__INT_INT,
+                     G_TYPE_NONE, 2,
+                     G_TYPE_INT,
+                     G_TYPE_INT);
+
+
+      default_button.state = GTK_STATE_NORMAL;
+      default_button.label = NULL;
+      default_button.label_visible = TRUE;
+      default_button.child = NULL;
+      default_button.justification = GTK_JUSTIFY_FILL;
+
+      initialized = TRUE;
+    }
+}
+
+
+inline void  
+g_sheet_column_set_width (GSheetColumn *column,
+                               gint col, gint size, const GtkSheet *sheet)
+{
+  g_return_if_fail (G_IS_SHEET_COLUMN (column));
+
+  if ((G_SHEET_COLUMN_GET_IFACE (column)->set_width) ) 
+    (G_SHEET_COLUMN_GET_IFACE (column)->set_width) (column, col, 
+                                                       size, sheet);
+}
+
+
+inline gint 
+g_sheet_column_get_width     (const GSheetColumn *column, 
+                                   gint col, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_COLUMN (column), -1);
+
+  g_assert (G_SHEET_COLUMN_GET_IFACE (column)->get_width);
+  
+  return (G_SHEET_COLUMN_GET_IFACE (column)->get_width) (column, col, 
+                                                            sheet);
+}
+
+
+
+inline gboolean  
+g_sheet_column_get_visibility(const GSheetColumn *column,
+                                           gint col, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_COLUMN (column), FALSE);
+
+  g_assert (G_SHEET_COLUMN_GET_IFACE (column)->get_visibility);
+  
+  return (G_SHEET_COLUMN_GET_IFACE (column)->get_visibility) (column, 
+                                                                 col, sheet);
+
+}
+
+inline gboolean  
+g_sheet_column_get_sensitivity(const GSheetColumn *column,
+                                            gint col, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_COLUMN (column), FALSE);
+
+  g_assert (G_SHEET_COLUMN_GET_IFACE (column)->get_sensitivity);
+  
+  return (G_SHEET_COLUMN_GET_IFACE (column)->get_sensitivity) (column, 
+                                                                  col, sheet);
+
+}
+
+
+inline const GtkSheetButton *
+g_sheet_column_get_button(const GSheetColumn *column,
+                             gint col, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_COLUMN (column), FALSE);
+
+  GSheetColumnIface *iface = G_SHEET_COLUMN_GET_IFACE (column);
+
+  static GtkSheetButton button ;
+  memcpy(&button, &default_button, sizeof (button));
+
+  if ( iface->get_button_label)
+    button.label = iface->get_button_label(column, col, sheet);
+
+  return &button;
+}
+
+inline GtkJustification 
+g_sheet_column_get_justification(const GSheetColumn *column, 
+                                    gint col, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_COLUMN (column), FALSE);
+
+  g_assert (G_SHEET_COLUMN_GET_IFACE (column)->get_justification);
+  
+  return (G_SHEET_COLUMN_GET_IFACE (column)->get_justification) (column, col, sheet);
+}
+
+
+
+
+inline gint  
+g_sheet_column_get_left_text_column (const GSheetColumn *column,
+                                        gint col, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_COLUMN (column), -1);
+
+  if  ( ! G_SHEET_COLUMN_GET_IFACE (column)->get_left_text_column)
+    return col;
+  
+  return (G_SHEET_COLUMN_GET_IFACE (column)->get_left_text_column) (column, col, sheet);
+
+}
+
+inline gint  
+g_sheet_column_get_right_text_column (const GSheetColumn *column,
+                                         gint col, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_COLUMN (column), -1);
+
+  if  ( ! G_SHEET_COLUMN_GET_IFACE (column)->get_right_text_column)
+    return col;
+  
+  return (G_SHEET_COLUMN_GET_IFACE (column)->get_right_text_column) (column, col, sheet);
+
+}
+
+inline void 
+g_sheet_column_set_left_text_column (const GSheetColumn *column,
+                                        gint col, gint i, const GtkSheet *sheet)
+{
+  g_return_if_fail (G_IS_SHEET_COLUMN (column));
+
+  if  ( G_SHEET_COLUMN_GET_IFACE (column)->set_left_text_column)
+    (G_SHEET_COLUMN_GET_IFACE (column)->set_left_text_column) (column, col, i, sheet);
+
+}
+
+
+inline void 
+g_sheet_column_set_right_text_column (const GSheetColumn *column,
+                                         gint col, gint i, const GtkSheet *sheet)
+{
+  g_return_if_fail (G_IS_SHEET_COLUMN (column));
+
+  if  ( G_SHEET_COLUMN_GET_IFACE (column)->set_right_text_column)
+    (G_SHEET_COLUMN_GET_IFACE (column)->set_right_text_column) (column, col, i, sheet);
+}
+
+inline gint  
+g_sheet_column_get_column_count(const GSheetColumn *geo, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_COLUMN (geo), -1);
+
+  g_assert  ( G_SHEET_COLUMN_GET_IFACE (geo)->get_column_count);
+
+  return (G_SHEET_COLUMN_GET_IFACE (geo)->get_column_count) (geo, sheet);
+}
+
+inline gint  
+g_sheet_column_start_pixel(const GSheetColumn *geo, gint col, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_COLUMN (geo), -1);
+  g_return_val_if_fail (col < 
+                       g_sheet_column_get_column_count(geo, sheet),-1);
+
+  gint i;
+  gint start_pixel = 0;
+
+  for ( i = 0 ; i < col ; ++i ) 
+    {
+      if ( g_sheet_column_get_visibility(geo, i, sheet))
+       start_pixel += g_sheet_column_get_width(geo, i, sheet);
+    }
+  
+  return start_pixel;
+
+}
+
+
+
+inline void
+g_sheet_column_columns_deleted(GSheetColumn *geo, 
+                                gint first, gint n_columns)
+{
+  g_return_if_fail (G_IS_SHEET_COLUMN (geo));
+
+  g_signal_emit (geo, sheet_column_signals[COLUMNS_CHANGED], 0, 
+                first, n_columns);
+}
diff --git a/lib/gtksheet/gsheet-column-iface.h b/lib/gtksheet/gsheet-column-iface.h
new file mode 100644 (file)
index 0000000..4e3e5a1
--- /dev/null
@@ -0,0 +1,132 @@
+/* GSheetColumn --- an abstract model of the column geometry of a 
+ * GSheet widget.
+ * Copyright (C) 2006 Free Software Foundation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __GSHEET_COLUMN_IFACE_H
+#define __GSHEET_COLUMN_IFACE_H
+
+#include <glib-object.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "gtkextra-sheet.h"
+
+
+G_BEGIN_DECLS
+
+#define G_TYPE_SHEET_COLUMN            (g_sheet_column_get_type ())
+#define G_SHEET_COLUMN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_SHEET_COLUMN, GSheetColumn))
+#define G_IS_SHEET_COLUMN(obj)        (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_SHEET_COLUMN))
+#define G_SHEET_COLUMN_GET_IFACE(obj)  (G_TYPE_INSTANCE_GET_INTERFACE ((obj), G_TYPE_SHEET_COLUMN, GSheetColumnIface))
+
+
+typedef struct _GSheetColumn        GSheetColumn; 
+typedef struct _GSheetColumnIface   GSheetColumnIface;
+struct _GSheetColumnIface
+{
+  GTypeInterface g_iface;
+
+
+  /* Signals */
+  void         (* columns_changed)     (GSheetColumn *geo,
+                                     gint col, gint n_columns);
+
+  /* Virtual Table */
+  gint (* get_width) (const GSheetColumn *gcolumn, gint col, const GtkSheet *);
+  void (* set_width) (GSheetColumn *gcolumn, gint col, gint width, const GtkSheet *);
+
+  gboolean (* get_visibility) (const GSheetColumn *gcolumn, gint col, const GtkSheet *);
+  gboolean (* get_sensitivity) (const GSheetColumn *gcolumn, gint col, const GtkSheet *);
+  const GtkSheetButton * (* get_button) (const GSheetColumn *gcolumn, gint col, const GtkSheet *);
+  GtkJustification (* get_justification) (const GSheetColumn *gcolumn, gint col, const GtkSheet *);
+
+  gint  (*get_left_text_column) (const GSheetColumn *gcolumn,
+                                gint col, const GtkSheet *);
+
+  gint  (*get_right_text_column) (const GSheetColumn *gcolumn,
+                                 gint col, const GtkSheet *);
+
+  void (* set_left_text_column) (const GSheetColumn *gcolumn,
+                                gint col, gint i, const GtkSheet *);
+
+  void (* set_right_text_column) (const GSheetColumn *gcolumn,
+                                 gint col, gint i, const GtkSheet *);
+
+  gint  (* get_column_count) (const GSheetColumn *geo, const GtkSheet *);
+
+
+  GtkStateType  (*get_button_state)(const GSheetColumn *geo, gint col, const GtkSheet *);
+  const gchar * (*get_button_label)(const GSheetColumn *geo, gint col, const GtkSheet *);
+  gboolean      (*get_button_visibility)(const GSheetColumn *geo, 
+                                       gint col, const GtkSheet *);
+  const GtkSheetChild * (*get_button_child)(const GSheetColumn *geo, 
+                                          gint col, const GtkSheet *);
+  GtkJustification * (*get_button_justification)(const GSheetColumn *geo, 
+                                               gint col, const GtkSheet *);
+};
+
+
+inline GType g_sheet_column_get_type   (void) G_GNUC_CONST;
+
+
+inline gint  g_sheet_column_get_width (const GSheetColumn *gcolumn,
+                                       gint col, const GtkSheet *sheet);
+
+
+inline void  g_sheet_column_set_width (GSheetColumn *gcolumn,
+                                       gint col, gint size, const GtkSheet *sheet);
+
+
+inline gboolean  g_sheet_column_get_visibility(const GSheetColumn *gcolumn,
+                                           gint col, const GtkSheet *sheet);
+
+inline gboolean  g_sheet_column_get_sensitivity(const GSheetColumn *gcolumn,
+                                            gint col, const GtkSheet *sheet);
+
+
+inline const GtkSheetButton *g_sheet_column_get_button(const GSheetColumn *gcolumn,
+                                            gint col, const GtkSheet *sheet);
+
+inline GtkJustification g_sheet_column_get_justification(const GSheetColumn *gcolumn, gint col, const GtkSheet *sheet);
+
+
+inline gint  g_sheet_column_get_left_text_column (const GSheetColumn *gcolumn,
+                                       gint col, const GtkSheet *sheet);
+
+inline gint  g_sheet_column_get_right_text_column (const GSheetColumn *gcolumn,
+                                       gint col, const GtkSheet *sheet);
+
+inline void g_sheet_column_set_left_text_column (const GSheetColumn *gcolumn,
+                                       gint col, gint i, const GtkSheet *sheet);
+
+
+inline void g_sheet_column_set_right_text_column (const GSheetColumn *gcolumn,
+                                       gint col, gint i, const GtkSheet *sheet);
+
+
+inline gint  g_sheet_column_get_column_count(const GSheetColumn *geo, const GtkSheet *sheet);
+
+inline gint  g_sheet_column_start_pixel(const GSheetColumn *geo, gint col, const GtkSheet *sheet);
+
+inline void g_sheet_column_columns_deleted(GSheetColumn *geo, 
+                                     gint first, gint n_columns);
+
+
+G_END_DECLS
+
+#endif /* __G_SHEET_COLUMN_IFACE_H__ */
diff --git a/lib/gtksheet/gsheet-hetero-column.c b/lib/gtksheet/gsheet-hetero-column.c
new file mode 100644 (file)
index 0000000..a4781e9
--- /dev/null
@@ -0,0 +1,231 @@
+/* gsheet-hetero-column.c
+ * PSPPIRE --- A Graphical User Interface for PSPP
+ * Copyright (C) 2006  Free Software Foundation
+ * Written by John Darrington
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+#include "gsheet-column-iface.h"
+#include "gsheet-hetero-column.h"
+#include <string.h>
+
+
+static void  g_sheet_hetero_column_init       (GSheetHeteroColumn      *hg);
+static void  g_sheet_hetero_column_class_init (GSheetHeteroColumnClass *class);
+static void  g_sheet_hetero_column_finalize   (GObject           *object);
+
+static void g_sheet_column_init (GSheetColumnIface *iface);
+
+
+static GObjectClass *parent_class = NULL;
+
+GType
+g_sheet_hetero_column_get_type (void)
+{
+  static GType hetero_column_type = 0;
+
+  if (!hetero_column_type)
+    {
+      static const GTypeInfo hetero_column_info =
+      {
+       sizeof (GSheetHeteroColumnClass),
+       NULL,           /* base_init */
+       NULL,           /* base_finalize */
+        (GClassInitFunc) g_sheet_hetero_column_class_init,
+       NULL,           /* class_finalize */
+       NULL,           /* class_data */
+        sizeof (GSheetHeteroColumn),
+       0,
+        (GInstanceInitFunc) g_sheet_hetero_column_init,
+      };
+
+      static const GInterfaceInfo column_info =
+      {
+       (GInterfaceInitFunc) g_sheet_column_init,
+       NULL,
+       NULL
+      };
+
+      hetero_column_type = 
+       g_type_register_static (G_TYPE_OBJECT, "g_sheet_hetero_column",
+                               &hetero_column_info, 0);
+
+      g_type_add_interface_static (hetero_column_type,
+                                  G_TYPE_SHEET_COLUMN,
+                                  &column_info);
+    }
+
+  return hetero_column_type;
+}
+
+
+static GtkSheetButton default_button;
+   
+
+
+/**
+ * g_sheet_hetero_column_new:
+ * @width: The size of columns in this hetero column
+ *
+ * Return value: a new #g_sheet_hetero_column
+ **/
+GObject *
+g_sheet_hetero_column_new (gint default_width, gint n_columns)
+{
+  GSheetHeteroColumn *hg;
+  GObject *retval;
+
+  retval = g_object_new (G_TYPE_SHEET_HETERO_COLUMN, NULL);
+
+  hg = G_SHEET_HETERO_COLUMN(retval);
+  hg->n_columns = n_columns;
+  hg->default_width = default_width;
+  hg->col = g_new0(struct GSheetHeteroColumnUnit, n_columns);
+
+  gint i;
+  for (i = 0 ; i < hg->n_columns; ++i ) 
+    {
+      hg->col[i].button = default_button;
+    }
+
+  return retval;
+}
+
+static gint 
+g_sheet_hetero_column_get_width(const GSheetColumn *geom, gint i)
+{
+  GSheetHeteroColumn *hg = G_SHEET_HETERO_COLUMN(geom);
+
+  g_return_val_if_fail(i < hg->n_columns, -1);
+  
+  return hg->col[i].width;
+}
+
+static gint 
+g_sheet_hetero_column_get_sensitivity(const GSheetColumn *geom, gint u)
+{
+  GSheetHeteroColumn *hg = G_SHEET_HETERO_COLUMN(geom);
+
+  return TRUE;
+}
+
+
+static gint 
+g_sheet_hetero_column_get_visibility(const GSheetColumn *geom, gint u)
+{
+  GSheetHeteroColumn *hg = G_SHEET_HETERO_COLUMN(geom);
+
+  return TRUE;
+}
+
+
+
+static const gchar *
+g_sheet_hetero_column_get_button_label(const GSheetColumn *geom, gint u)
+{
+  GSheetHeteroColumn *hg = G_SHEET_HETERO_COLUMN(geom);
+  
+  return hg->col[u].button.label;
+}
+
+
+static GtkJustification
+g_sheet_hetero_column_get_justification(const GSheetColumn *geom, gint u)
+{
+  GSheetHeteroColumn *hg = G_SHEET_HETERO_COLUMN(geom);
+  
+  return GTK_JUSTIFY_FILL;
+}
+
+
+
+static gint 
+g_sheet_hetero_column_get_column_count(const GSheetColumn *geom)
+{
+  GSheetHeteroColumn *hg = G_SHEET_HETERO_COLUMN(geom);
+
+  return hg->n_columns;
+}
+
+static void
+g_sheet_hetero_column_class_init (GSheetHeteroColumnClass *class)
+{
+  GObjectClass *object_class;
+
+  parent_class = g_type_class_peek_parent (class);
+  object_class = (GObjectClass*) class;
+
+  object_class->finalize = g_sheet_hetero_column_finalize;
+
+  default_button.label=NULL;
+  default_button.child=NULL;
+  default_button.state=GTK_STATE_NORMAL;
+  default_button.justification=GTK_JUSTIFY_CENTER;
+  default_button.label_visible = TRUE;
+}
+
+
+static void
+g_sheet_hetero_column_init (GSheetHeteroColumn *o)
+{
+}
+
+static void         
+g_sheet_hetero_column_finalize (GObject           *object)
+{
+  GSheetHeteroColumn *hg = G_SHEET_HETERO_COLUMN(object);
+
+  g_free(hg->col);
+}
+
+
+static void
+g_sheet_column_init (GSheetColumnIface *iface)
+{
+  iface->get_width = g_sheet_hetero_column_get_width ;
+  iface->set_width = g_sheet_hetero_column_set_width ;
+  iface->get_sensitivity = g_sheet_hetero_column_get_sensitivity ;
+  iface->get_visibility = g_sheet_hetero_column_get_visibility ;
+  iface->get_justification = g_sheet_hetero_column_get_justification;
+  iface->get_column_count = g_sheet_hetero_column_get_column_count;
+
+  iface->get_button_label = g_sheet_hetero_column_get_button_label;
+}
+
+
+void 
+g_sheet_hetero_column_set_button_label(GSheetHeteroColumn *geo,
+                                             gint i, const gchar *label)
+{
+  g_return_if_fail(i < geo->n_columns);
+
+  g_free(geo->col[i].button.label);
+  geo->col[i].button.label = g_malloc(strlen(label) + 1);
+  
+  g_stpcpy(geo->col[i].button.label, label);
+}
+
+
+
+void 
+g_sheet_hetero_column_set_width(GSheetHeteroColumn *geo,
+                                     gint i, gint size)
+{
+  g_return_if_fail(i < geo->n_columns);
+
+  geo->col[i].width = size;
+}
+
+
diff --git a/lib/gtksheet/gsheet-hetero-column.h b/lib/gtksheet/gsheet-hetero-column.h
new file mode 100644 (file)
index 0000000..59fd7cd
--- /dev/null
@@ -0,0 +1,88 @@
+/* GtkSheet widget for Gtk+.
+ * Copyright (C) 2006 Free Software Foundation
+
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __G_SHEET_HETERO_COLUMN_H__
+#define __G_SHEET_HETERO_COLUMN_H__
+
+#include <glib-object.h>
+#include <glib.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define G_TYPE_SHEET_HETERO_COLUMN (g_sheet_hetero_column_get_type ())
+
+#define G_SHEET_HETERO_COLUMN(obj)    G_TYPE_CHECK_INSTANCE_CAST (obj, G_TYPE_SHEET_HETERO_COLUMN, GSheetHeteroColumn )
+#define G_SHEET_HETERO_COLUMN_CLASS(klass)  G_TYPE_CHECK_CLASS_CAST (klass, g_sheet_hetero_column_get_type (), GSheetHeteroColumnClass)
+#define G_IS_SHEET_HETERO_COLUMN(obj)  G_TYPE_CHECK_INSTANCE_TYPE (obj, G_TYPE_SHEET_HETERO_COLUMN)
+
+
+  struct GSheetHeteroColumnUnit
+  {
+    GtkSheetButton button;
+
+    gint width;
+    gboolean is_sensitive;
+    gboolean is_visible;
+  };
+
+
+  struct _GSheetHeteroColumn{
+    GObject parent;
+
+    gint n_columns;
+    gint default_width;
+    
+    struct GSheetHeteroColumnUnit *col;
+
+  };
+
+  struct _GSheetHeteroColumnClass
+  {
+    GObjectClass parent_class;
+  };
+
+
+
+  
+  /* create a new column */
+  GObject * g_sheet_hetero_column_new (gint default_width, gint n_columns);
+
+  GType g_sheet_hetero_column_get_type (void);
+
+
+  typedef struct _GSheetHeteroColumn GSheetHeteroColumn;
+  typedef struct _GSheetHeteroColumnClass GSheetHeteroColumnClass;
+
+
+  void g_sheet_hetero_column_set_button_label(GSheetHeteroColumn *geo,
+                                               gint i, const gchar *label);
+
+  void g_sheet_hetero_column_set_width(GSheetHeteroColumn *geo,
+                                            gint i, gint size);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __G_SHEET_HETERO_COLUMN_H__ */
+
+
diff --git a/lib/gtksheet/gsheet-row-iface.c b/lib/gtksheet/gsheet-row-iface.c
new file mode 100644 (file)
index 0000000..37d246b
--- /dev/null
@@ -0,0 +1,259 @@
+/* GSheetRow --- an abstract model of the row geometry of a 
+ * GSheet widget.
+ * Copyright (C) 2006 Free Software Foundation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <gobject/gvaluecollector.h>
+#include "gsheet-row-iface.h"
+#include "gtkextra-marshal.h"
+
+
+enum {
+  ROWS_CHANGED,
+  LAST_SIGNAL
+};
+
+static guint sheet_row_signals[LAST_SIGNAL];
+
+
+
+static void      g_sheet_row_base_init   (gpointer g_class);
+
+
+GType
+g_sheet_row_get_type (void)
+{
+  static GType sheet_row_type = 0;
+
+  if (! sheet_row_type)
+    {
+      static const GTypeInfo sheet_row_info =
+
+      {
+        sizeof (GSheetRowIface), /* class_size */
+       g_sheet_row_base_init,   /* base_init */
+       NULL,           /* base_finalize */
+       NULL,
+       NULL,           /* class_finalize */
+       NULL,           /* class_data */
+       0,
+       0,              /* n_preallocs */
+       NULL
+      };
+
+      sheet_row_type =
+       g_type_register_static (G_TYPE_INTERFACE, "GSheetRow",
+                               &sheet_row_info, 0);
+
+      g_type_interface_add_prerequisite (sheet_row_type, G_TYPE_OBJECT);
+    }
+
+  return sheet_row_type;
+}
+
+
+static GtkSheetButton default_button;
+
+static void
+g_sheet_row_base_init (gpointer g_class)
+{
+  static gboolean initialized = FALSE;
+
+  if (! initialized)
+    {
+
+      sheet_row_signals[ROWS_CHANGED] =
+       g_signal_new ("rows_changed",
+                     G_TYPE_SHEET_ROW,
+                     G_SIGNAL_RUN_LAST,
+                     G_STRUCT_OFFSET (GSheetRowIface, rows_changed),
+                     NULL, NULL,
+                     gtkextra_VOID__INT_INT,
+                     G_TYPE_NONE, 2,
+                     G_TYPE_INT,
+                     G_TYPE_INT);
+
+
+      default_button.state = GTK_STATE_NORMAL;
+      default_button.label = NULL;
+      default_button.label_visible = TRUE;
+      default_button.child = NULL;
+      default_button.justification = GTK_JUSTIFY_FILL;
+
+      initialized = TRUE;
+    }
+}
+
+void  
+g_sheet_row_set_height (GSheetRow *row_geo,
+                               gint row, gint size, const GtkSheet *sheet)
+{
+  g_return_if_fail (G_IS_SHEET_ROW (row_geo));
+
+  if ((G_SHEET_ROW_GET_IFACE (row_geo)->set_height) ) 
+    (G_SHEET_ROW_GET_IFACE (row_geo)->set_height) (row_geo, row, 
+                                                       size, sheet);
+}
+
+
+gint 
+g_sheet_row_get_height     (const GSheetRow *row_geo, 
+                                   gint row, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_ROW (row_geo), -1);
+
+  g_assert (G_SHEET_ROW_GET_IFACE (row_geo)->get_height);
+  
+  return (G_SHEET_ROW_GET_IFACE (row_geo)->get_height) (row_geo, row, 
+                                                            sheet);
+}
+
+
+
+gboolean  
+g_sheet_row_get_visibility(const GSheetRow *row_geo,
+                                           gint row, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_ROW (row_geo), FALSE);
+
+  g_assert (G_SHEET_ROW_GET_IFACE (row_geo)->get_visibility);
+  
+  return (G_SHEET_ROW_GET_IFACE (row_geo)->get_visibility) (row_geo, 
+                                                                 row, sheet);
+
+}
+
+gboolean  
+g_sheet_row_get_sensitivity(const GSheetRow *row_geo,
+                                            gint row, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_ROW (row_geo), FALSE);
+
+  g_assert (G_SHEET_ROW_GET_IFACE (row_geo)->get_sensitivity);
+  
+  return (G_SHEET_ROW_GET_IFACE (row_geo)->get_sensitivity) (row_geo, 
+                                                                  row, sheet);
+
+}
+
+
+const GtkSheetButton *
+g_sheet_row_get_button(const GSheetRow *row_geo,
+                             gint row, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_ROW (row_geo), FALSE);
+
+  GSheetRowIface *iface = G_SHEET_ROW_GET_IFACE (row_geo);
+
+  static GtkSheetButton button ;
+  memcpy(&button, &default_button, sizeof (button));
+
+  if ( iface->get_button_label)
+    button.label = iface->get_button_label(row_geo, row, sheet);
+
+  return &button;
+}
+
+
+gint  
+g_sheet_row_get_row_count(const GSheetRow *geo, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_ROW (geo), -1);
+
+  g_assert  ( G_SHEET_ROW_GET_IFACE (geo)->get_row_count);
+
+  return (G_SHEET_ROW_GET_IFACE (geo)->get_row_count) (geo, sheet);
+}
+
+/**
+ * g_sheet_row_start_pixel:
+ * @geo: the row model
+ * @row: the row number
+ * @sheet: pointer to the sheet 
+ *
+ * Returns the top y pixel for ROW.
+ * Instances may override this method in order to achieve time and/or memory
+ * optmisation.
+ *
+ * Returns: the y coordinate of the top of the row.
+ */
+
+gint  
+g_sheet_row_start_pixel(const GSheetRow *geo, gint row, const GtkSheet *sheet)
+{
+  g_return_val_if_fail (G_IS_SHEET_ROW (geo), -1);
+  g_return_val_if_fail (row >= 0, -1);
+  g_return_val_if_fail (row < 
+                       g_sheet_row_get_row_count(geo, sheet),-1);
+
+  gint i;
+  gint start_pixel = 0;
+
+  if ( G_SHEET_ROW_GET_IFACE(geo)->top_ypixel) 
+    return (G_SHEET_ROW_GET_IFACE(geo)->top_ypixel)(geo, row, sheet);
+
+  for ( i = 0 ; i < row ; ++i ) 
+    {
+      if ( g_sheet_row_get_visibility(geo, i, sheet))
+       start_pixel += g_sheet_row_get_height(geo, i, sheet);
+    }
+  
+  return start_pixel;
+}
+
+
+gint  
+g_sheet_row_pixel_to_row(const GSheetRow *geo, gint pixel, 
+                        const GtkSheet *s)
+{
+  gint i, cy;
+  g_return_val_if_fail (G_IS_SHEET_ROW (geo), -1);
+  g_return_val_if_fail (pixel >= 0, -1) ;
+
+  if ( G_SHEET_ROW_GET_IFACE(geo)->pixel_to_row) 
+    return (G_SHEET_ROW_GET_IFACE(geo)->pixel_to_row)(geo, pixel, s);
+
+  cy = 0;
+  for (i = 0; i < g_sheet_row_get_row_count(geo, s); ++i ) 
+    {
+      if (pixel >= cy  && 
+         pixel <= (cy + g_sheet_row_get_height(geo, i, s)) && 
+         g_sheet_row_get_visibility(geo, i, s))
+       return i;
+
+      if(g_sheet_row_get_visibility(geo, i, s))
+       cy += g_sheet_row_get_height(geo, i, s);
+    }
+
+  /* no match */
+  return g_sheet_row_get_row_count(geo, s) - 1;
+}
+
+
+
+void
+g_sheet_row_rows_deleted(GSheetRow *geo, 
+                                gint first, gint n_rows)
+{
+  g_return_if_fail (G_IS_SHEET_ROW (geo));
+
+  g_signal_emit (geo, sheet_row_signals[ROWS_CHANGED], 0, 
+                first, n_rows);
+}
diff --git a/lib/gtksheet/gsheet-row-iface.h b/lib/gtksheet/gsheet-row-iface.h
new file mode 100644 (file)
index 0000000..a6a62b2
--- /dev/null
@@ -0,0 +1,125 @@
+/* GSheetRow --- an abstract model of the row geometry of a 
+ * GSheet widget.
+ * Copyright (C) 2006 Free Software Foundation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __G_SHEET_ROW_IFACE_H__
+#define __G_SHEET_ROW_IFACE_H__
+
+#include <glib-object.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "gtkextra-sheet.h"
+
+
+G_BEGIN_DECLS
+
+#define G_TYPE_SHEET_ROW            (g_sheet_row_get_type ())
+#define G_SHEET_ROW(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_SHEET_ROW, GSheetRow))
+#define G_IS_SHEET_ROW(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_SHEET_ROW))
+#define G_SHEET_ROW_GET_IFACE(obj)  (G_TYPE_INSTANCE_GET_INTERFACE ((obj), G_TYPE_SHEET_ROW, GSheetRowIface))
+
+
+
+
+typedef struct _GSheetRow        GSheetRow; 
+typedef struct _GSheetRowIface   GSheetRowIface;
+
+struct _GSheetRowIface
+{
+  GTypeInterface g_iface;
+
+
+  /* Signals */
+  void         (* rows_changed)     (GSheetRow *geo,
+                                     gint row, gint n_rows);
+
+  /* Virtual Table */
+  gint (* get_height) (const GSheetRow *grow, gint row, const GtkSheet *);
+  void (* set_height) (GSheetRow *grow, gint row, gint height, 
+                      const GtkSheet *);
+
+  gboolean (* get_visibility) (const GSheetRow *grow, gint row, 
+                              const GtkSheet *);
+
+  gboolean (* get_sensitivity) (const GSheetRow *grow, gint row, 
+                               const GtkSheet *);
+
+  const GtkSheetButton * (* get_button) (const GSheetRow *grow, gint row, 
+                                        const GtkSheet *);
+
+  gint  (* get_row_count) (const GSheetRow *geo, const GtkSheet *);
+
+
+  GtkStateType  (*get_button_state)(const GSheetRow *geo, gint row, 
+                                   const GtkSheet *);
+
+  const gchar * (*get_button_label)(const GSheetRow *geo, gint row, 
+                                   const GtkSheet *);
+
+  gboolean      (*get_button_visibility)(const GSheetRow *geo, 
+                                       gint row, const GtkSheet *);
+
+  const GtkSheetChild * (*get_button_child)(const GSheetRow *geo, 
+                                          gint row, const GtkSheet *);
+
+  guint (*top_ypixel)(const GSheetRow *geo, gint row, const GtkSheet *);
+  gint (*pixel_to_row)(const GSheetRow *geo, guint pixel, const GtkSheet *);
+};
+
+
+GType g_sheet_row_get_type   (void) G_GNUC_CONST;
+
+
+gint  g_sheet_row_get_height (const GSheetRow *grow,
+                                       gint row, const GtkSheet *sheet);
+
+
+void  g_sheet_row_set_height (GSheetRow *grow,
+                                       gint row, gint size, const GtkSheet *sheet);
+
+
+gboolean  g_sheet_row_get_visibility(const GSheetRow *grow,
+                                           gint row, const GtkSheet *sheet);
+
+gboolean  g_sheet_row_get_sensitivity(const GSheetRow *grow,
+                                            gint row, const GtkSheet *sheet);
+
+
+const GtkSheetButton *g_sheet_row_get_button(const GSheetRow *grow,
+                                            gint row, const GtkSheet *sheet);
+
+
+gint  g_sheet_row_get_row_count(const GSheetRow *geo, const GtkSheet *sheet);
+
+/* Return the top pixel of row ROW */
+gint  g_sheet_row_start_pixel(const GSheetRow *geo, gint row, 
+                             const GtkSheet *sheet);
+
+/* Return the row contained by pixel PIXEL */
+gint  g_sheet_row_pixel_to_row(const GSheetRow *geo, gint pixel, 
+                              const GtkSheet *sheet);
+
+
+void g_sheet_row_rows_deleted(GSheetRow *geo, 
+                                     gint first, gint n_rows);
+
+
+G_END_DECLS
+
+#endif /* __G_SHEET_ROW_IFACE_H__ */
diff --git a/lib/gtksheet/gsheet-uniform-column.c b/lib/gtksheet/gsheet-uniform-column.c
new file mode 100644 (file)
index 0000000..be7a37f
--- /dev/null
@@ -0,0 +1,192 @@
+/* gsheet-uniform-column.c
+ * 
+ * PSPPIRE --- A Graphical User Interface for PSPP
+ * Copyright (C) 2006  Free Software Foundation
+ * Written by John Darrington
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "gsheet-column-iface.h"
+#include "gsheet-uniform-column.h"
+
+
+static void  g_sheet_uniform_column_init       (GSheetUniformColumn      *ug);
+static void  g_sheet_uniform_column_class_init (GSheetUniformColumnClass *class);
+static void  g_sheet_uniform_column_finalize   (GObject           *object);
+
+static void g_sheet_column_init (GSheetColumnIface *iface);
+
+
+static GObjectClass *parent_class = NULL;
+
+GType
+g_sheet_uniform_column_get_type (void)
+{
+  static GType uniform_column_type = 0;
+
+  if (!uniform_column_type)
+    {
+      static const GTypeInfo uniform_column_info =
+      {
+       sizeof (GSheetUniformColumnClass),
+       NULL,           /* base_init */
+       NULL,           /* base_finalize */
+        (GClassInitFunc) g_sheet_uniform_column_class_init,
+       NULL,           /* class_finalize */
+       NULL,           /* class_data */
+        sizeof (GSheetUniformColumn),
+       0,
+        (GInstanceInitFunc) g_sheet_uniform_column_init,
+      };
+
+      static const GInterfaceInfo column_info =
+      {
+       (GInterfaceInitFunc) g_sheet_column_init,
+       NULL,
+       NULL
+      };
+
+      uniform_column_type = 
+       g_type_register_static (G_TYPE_OBJECT, "g_sheet_uniform_column",
+                               &uniform_column_info, 0);
+
+      g_type_add_interface_static (uniform_column_type,
+                                  G_TYPE_SHEET_COLUMN,
+                                  &column_info);
+    }
+
+  return uniform_column_type;
+}
+
+
+/**
+ * g_sheet_uniform_column_new:
+ * @width: The size of columns in this uniform column
+ *
+ * Return value: a new #g_sheet_uniform_column
+ **/
+GObject *
+g_sheet_uniform_column_new (gint width, gint n_columns)
+{
+  GSheetUniformColumn *ug;
+  GObject *retval;
+
+  retval = g_object_new (G_TYPE_SHEET_UNIFORM_COLUMN, NULL);
+
+  ug = G_SHEET_UNIFORM_COLUMN(retval);
+  ug->n_columns = n_columns;
+  ug->width = width;
+  ug->is_visible = TRUE;
+  ug->is_sensitive = FALSE;
+
+  return retval;
+}
+
+static gint 
+g_sheet_uniform_column_get_width(const GSheetColumn *geom, gint u)
+{
+  GSheetUniformColumn *ug = G_SHEET_UNIFORM_COLUMN(geom);
+  
+  return ug->width;
+}
+
+static gint 
+g_sheet_uniform_column_get_sensitivity(const GSheetColumn *geom, gint u)
+{
+  GSheetUniformColumn *ug = G_SHEET_UNIFORM_COLUMN(geom);
+  
+  return ug->is_sensitive;
+}
+
+
+static gint 
+g_sheet_uniform_column_get_visibility(const GSheetColumn *geom, gint u)
+{
+  GSheetUniformColumn *ug = G_SHEET_UNIFORM_COLUMN(geom);
+  
+  return ug->is_visible;
+}
+
+
+static const gchar *
+g_sheet_uniform_column_get_button_label(const GSheetColumn *geom, gint u)
+{
+  GSheetUniformColumn *ug = G_SHEET_UNIFORM_COLUMN(geom);
+  
+  static gchar *label; 
+
+  g_free(label);
+  label = g_strdup_printf("%d", u);
+
+  return label;
+}
+
+
+static GtkJustification
+g_sheet_uniform_column_get_justification(const GSheetColumn *geom, gint u)
+{
+       /* 
+  GSheetUniformColumn *ug = G_SHEET_UNIFORM_COLUMN(geom);
+  */
+  
+  return GTK_JUSTIFY_FILL;
+}
+
+
+
+static gint 
+g_sheet_uniform_column_get_column_count(const GSheetColumn *geom)
+{
+  GSheetUniformColumn *ug = G_SHEET_UNIFORM_COLUMN(geom);
+
+  return ug->n_columns;
+}
+
+static void
+g_sheet_uniform_column_class_init (GSheetUniformColumnClass *class)
+{
+  GObjectClass *object_class;
+
+  parent_class = g_type_class_peek_parent (class);
+  object_class = (GObjectClass*) class;
+
+  object_class->finalize = g_sheet_uniform_column_finalize;
+
+}
+
+
+static void
+g_sheet_uniform_column_init (GSheetUniformColumn *o)
+{
+}
+
+static void         
+g_sheet_uniform_column_finalize (GObject           *object)
+{
+}
+
+
+static void
+g_sheet_column_init (GSheetColumnIface *iface)
+{
+  iface->get_width = g_sheet_uniform_column_get_width ;
+  iface->get_sensitivity = g_sheet_uniform_column_get_sensitivity ;
+  iface->get_visibility = g_sheet_uniform_column_get_visibility ;
+  iface->get_justification = g_sheet_uniform_column_get_justification;
+  iface->get_column_count = g_sheet_uniform_column_get_column_count;
+  iface->get_button_label = g_sheet_uniform_column_get_button_label;
+}
+
diff --git a/lib/gtksheet/gsheet-uniform-column.h b/lib/gtksheet/gsheet-uniform-column.h
new file mode 100644 (file)
index 0000000..b6c8102
--- /dev/null
@@ -0,0 +1,68 @@
+/* GtkSheet widget for Gtk+.
+ * Copyright (C) 2006 Free Software Foundation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef  __G_SHEET_UNIFORM_COLUMN_H__
+#define  __G_SHEET_UNIFORM_COLUMN_H__
+
+
+#include <glib-object.h>
+#include <glib.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define G_TYPE_SHEET_UNIFORM_COLUMN (g_sheet_uniform_column_get_type ())
+
+#define G_SHEET_UNIFORM_COLUMN(obj)    G_TYPE_CHECK_INSTANCE_CAST (obj, G_TYPE_SHEET_UNIFORM_COLUMN, GSheetUniformColumn )
+#define G_SHEET_UNIFORM_COLUMN_CLASS(klass)  G_TYPE_CHECK_CLASS_CAST (klass, g_sheet_uniform_column_get_type (), GSheetUniformColumnClass)
+#define G_IS_SHEET_UNIFORM_COLUMN(obj)  G_TYPE_CHECK_INSTANCE_TYPE (obj, G_TYPE_SHEET_UNIFORM_COLUMN)
+
+
+  struct _GSheetUniformColumn{
+    GObject parent;
+
+    gint n_columns;
+    gint width;
+    gboolean is_sensitive;
+    gboolean is_visible;
+  };
+
+  struct _GSheetUniformColumnClass
+  {
+    GObjectClass parent_class;
+  };
+  
+  /* create a new column */
+  GObject * g_sheet_uniform_column_new (gint width, gint n_columns);
+
+  GType g_sheet_uniform_column_get_type (void);
+
+
+  typedef struct _GSheetUniformColumn GSheetUniformColumn;
+  typedef struct _GSheetUniformColumnClass GSheetUniformColumnClass;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __G_SHEET_UNIFORM_COLUMN_H__ */
+
+
diff --git a/lib/gtksheet/gsheet-uniform-row.c b/lib/gtksheet/gsheet-uniform-row.c
new file mode 100644 (file)
index 0000000..48dd38a
--- /dev/null
@@ -0,0 +1,205 @@
+/* gsheet-uniform-row.c
+ * 
+ *  PSPPIRE --- A Graphical User Interface for PSPP
+ * Copyright (C) 2006  Free Software Foundation
+ * Written by John Darrington
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "gsheet-row-iface.h"
+#include "gsheet-uniform-row.h"
+
+
+static void  g_sheet_uniform_row_init       (GSheetUniformRow      *ug);
+static void  g_sheet_uniform_row_class_init (GSheetUniformRowClass *class);
+static void  g_sheet_uniform_row_finalize   (GObject           *object);
+
+static void g_sheet_row_init (GSheetRowIface *iface);
+
+
+static GObjectClass *parent_class = NULL;
+
+GType
+g_sheet_uniform_row_get_type (void)
+{
+  static GType uniform_row_type = 0;
+
+  if (!uniform_row_type)
+    {
+      static const GTypeInfo uniform_row_info =
+      {
+       sizeof (GSheetUniformRowClass),
+       NULL,           /* base_init */
+       NULL,           /* base_finalize */
+        (GClassInitFunc) g_sheet_uniform_row_class_init,
+       NULL,           /* class_finalize */
+       NULL,           /* class_data */
+        sizeof (GSheetUniformRow),
+       0,
+        (GInstanceInitFunc) g_sheet_uniform_row_init,
+      };
+
+      static const GInterfaceInfo row_info =
+      {
+       (GInterfaceInitFunc) g_sheet_row_init,
+       NULL,
+       NULL
+      };
+
+      uniform_row_type = 
+       g_type_register_static (G_TYPE_OBJECT, "g_sheet_uniform_row",
+                               &uniform_row_info, 0);
+
+      g_type_add_interface_static (uniform_row_type,
+                                  G_TYPE_SHEET_ROW,
+                                  &row_info);
+    }
+
+  return uniform_row_type;
+}
+
+
+/**
+ * g_sheet_uniform_row_new:
+ * @height: The size of rows in this uniform row
+ *
+ * Return value: a new #g_sheet_uniform_row
+ **/
+GObject *
+g_sheet_uniform_row_new (gint height, gint n_rows)
+{
+  GSheetUniformRow *ug;
+  GObject *retval;
+
+  retval = g_object_new (G_TYPE_SHEET_UNIFORM_ROW, NULL);
+
+  ug = G_SHEET_UNIFORM_ROW(retval);
+  ug->n_rows = n_rows;
+  ug->height = height;
+  ug->is_visible = TRUE;
+
+  return retval;
+}
+
+static gint 
+g_sheet_uniform_row_get_height(const GSheetRow *geom, gint u)
+{
+  GSheetUniformRow *ug = G_SHEET_UNIFORM_ROW(geom);
+  
+  return ug->height;
+}
+
+static gboolean
+g_sheet_uniform_row_get_sensitivity(const GSheetRow *geom, gint u)
+{
+  GSheetUniformRow *ug = G_SHEET_UNIFORM_ROW(geom);
+  
+  return (u < ug->n_rows);
+}
+
+
+static gboolean
+g_sheet_uniform_row_get_visibility(const GSheetRow *geom, gint u)
+{
+  GSheetUniformRow *ug = G_SHEET_UNIFORM_ROW(geom);
+  
+  return ug->is_visible;
+}
+
+
+static const gchar *
+g_sheet_uniform_row_get_button_label(const GSheetRow *geom, gint u)
+{
+  GSheetUniformRow *ug = G_SHEET_UNIFORM_ROW(geom);
+  
+  static gchar *label; 
+  g_free(label);
+  label = g_strdup_printf("%d", u);
+
+  return label;
+}
+
+
+
+static gint 
+g_sheet_uniform_row_get_row_count(const GSheetRow *geom)
+{
+  GSheetUniformRow *ug = G_SHEET_UNIFORM_ROW(geom);
+
+  return ug->n_rows;
+}
+
+
+static void
+g_sheet_uniform_row_class_init (GSheetUniformRowClass *class)
+{
+  GObjectClass *object_class;
+
+  parent_class = g_type_class_peek_parent (class);
+  object_class = (GObjectClass*) class;
+
+  object_class->finalize = g_sheet_uniform_row_finalize;
+
+}
+
+
+static void
+g_sheet_uniform_row_init (GSheetUniformRow *o)
+{
+}
+
+static void         
+g_sheet_uniform_row_finalize (GObject           *object)
+{
+}
+
+
+static guint
+g_sheet_uniform_row_top_ypixel(GSheetRow *geo, gint row, const GtkSheet *sheet)
+{
+  GSheetUniformRow *ug = G_SHEET_UNIFORM_ROW(geo);
+
+  return row * ug->height;
+}
+
+static guint
+g_sheet_uniform_row_pixel_to_row(GSheetRow *geo, 
+                                gint pixel, const GtkSheet *sheet)
+{
+  GSheetUniformRow *ug = G_SHEET_UNIFORM_ROW(geo);
+
+  gint row = pixel / ug->height;
+
+  if (row >= g_sheet_uniform_row_get_row_count(geo))
+    row = g_sheet_uniform_row_get_row_count(geo) -1;
+
+  return row;
+}
+
+
+
+static void
+g_sheet_row_init (GSheetRowIface *iface)
+{
+  iface->get_height = g_sheet_uniform_row_get_height;
+  iface->get_sensitivity = g_sheet_uniform_row_get_sensitivity ;
+  iface->get_visibility = g_sheet_uniform_row_get_visibility;
+  iface->get_row_count = g_sheet_uniform_row_get_row_count;
+  iface->get_button_label = g_sheet_uniform_row_get_button_label;
+  iface->top_ypixel = g_sheet_uniform_row_top_ypixel;
+  iface->pixel_to_row = g_sheet_uniform_row_pixel_to_row;
+}
+
diff --git a/lib/gtksheet/gsheet-uniform-row.h b/lib/gtksheet/gsheet-uniform-row.h
new file mode 100644 (file)
index 0000000..4101ffd
--- /dev/null
@@ -0,0 +1,66 @@
+/* GtkSheet widget for Gtk+.
+ * Copyright (C) 2006 Free Software Foundation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __G_SHEET_UNIFORM_ROW_H__
+#define __G_SHEET_UNIFORM_ROW_H__
+
+#include <glib-object.h>
+#include <glib.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define G_TYPE_SHEET_UNIFORM_ROW (g_sheet_uniform_row_get_type ())
+
+#define G_SHEET_UNIFORM_ROW(obj)    G_TYPE_CHECK_INSTANCE_CAST (obj, G_TYPE_SHEET_UNIFORM_ROW, GSheetUniformRow )
+#define G_SHEET_UNIFORM_ROW_CLASS(klass)  G_TYPE_CHECK_CLASS_CAST (klass, g_sheet_uniform_row_get_type (), GSheetUniformRowClass)
+#define G_IS_SHEET_UNIFORM_ROW(obj)  G_TYPE_CHECK_INSTANCE_TYPE (obj, G_TYPE_SHEET_UNIFORM_ROW)
+
+
+  struct _GSheetUniformRow{
+    GObject parent;
+
+    gint n_rows;
+    gint height;
+    gboolean is_visible;
+  };
+
+  struct _GSheetUniformRowClass
+  {
+    GObjectClass parent_class;
+  };
+  
+  /* create a new row */
+  GObject * g_sheet_uniform_row_new (gint height, gint n_rows);
+
+  GType g_sheet_uniform_row_get_type (void);
+
+
+  typedef struct _GSheetUniformRow GSheetUniformRow;
+  typedef struct _GSheetUniformRowClass GSheetUniformRowClass;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __G_SHEET_UNIFORM_ROW_H__ */
+
+
diff --git a/lib/gtksheet/gsheetmodel.c b/lib/gtksheet/gsheetmodel.c
new file mode 100644 (file)
index 0000000..704b86e
--- /dev/null
@@ -0,0 +1,395 @@
+/* GSheetModel --- an abstract model for the GSheet widget.
+ * Copyright (C) 2006 Free Software Foundation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <glib.h>
+#include "gsheetmodel.h"
+#include "gtkextra-marshal.h"
+
+enum {
+  RANGE_CHANGED,
+  ROWS_INSERTED,
+  ROWS_DELETED,
+  LAST_SIGNAL
+};
+
+static guint sheet_model_signals[LAST_SIGNAL] = { 0 };
+
+
+static void      g_sheet_model_base_init   (gpointer           g_class);
+
+
+inline GType
+g_sheet_model_get_type (void)
+{
+  static GType sheet_model_type = 0;
+
+  if (! sheet_model_type)
+    {
+      static const GTypeInfo sheet_model_info =
+      {
+        sizeof (GSheetModelIface), /* class_size */
+       g_sheet_model_base_init,   /* base_init */
+       NULL,           /* base_finalize */
+       NULL,
+       NULL,           /* class_finalize */
+       NULL,           /* class_data */
+       0,
+       0,              /* n_preallocs */
+       NULL
+      };
+
+      sheet_model_type =
+       g_type_register_static (G_TYPE_INTERFACE, "GSheetModel",
+                               &sheet_model_info, 0);
+
+      g_type_interface_add_prerequisite (sheet_model_type, G_TYPE_OBJECT);
+    }
+
+  return sheet_model_type;
+}
+
+static void
+g_sheet_model_base_init (gpointer g_class)
+{
+  static gboolean initialized = FALSE;
+
+  if (! initialized)
+    {
+      sheet_model_signals[RANGE_CHANGED] =
+       g_signal_new ("range_changed",
+                     G_TYPE_SHEET_MODEL,
+                     G_SIGNAL_RUN_LAST,
+                     G_STRUCT_OFFSET (GSheetModelIface, range_changed),
+                     NULL, NULL,
+                     gtkextra_VOID__INT_INT_INT_INT,
+                     G_TYPE_NONE, 4,
+                     G_TYPE_INT,
+                     G_TYPE_INT,
+                     G_TYPE_INT,
+                     G_TYPE_INT);
+
+
+
+      sheet_model_signals[ROWS_INSERTED] =
+       g_signal_new ("rows_inserted",
+                     G_TYPE_SHEET_MODEL,
+                     G_SIGNAL_RUN_LAST,
+                     G_STRUCT_OFFSET (GSheetModelIface, rows_inserted),
+                     NULL, NULL,
+                     gtkextra_VOID__INT_INT,
+                     G_TYPE_NONE, 2,
+                     G_TYPE_INT,
+                     G_TYPE_INT);
+
+
+      sheet_model_signals[ROWS_DELETED] =
+       g_signal_new ("rows_deleted",
+                     G_TYPE_SHEET_MODEL,
+                     G_SIGNAL_RUN_LAST,
+                     G_STRUCT_OFFSET (GSheetModelIface, rows_deleted),
+                     NULL, NULL,
+                     gtkextra_VOID__INT_INT,
+                     G_TYPE_NONE, 2,
+                     G_TYPE_INT,
+                     G_TYPE_INT);
+
+                   
+      initialized = TRUE;
+    }
+}
+
+/**
+ * g_sheet_model_get_string:
+ * @sheet_model: A #GSheetModel
+ * @row: The row of the cell to be retrieved.
+ * @column: The column of the cell to be retrieved.
+ * 
+ * Retrieves the datum at location ROW, COLUMN in the form of a string.
+ * Returns: The string representation of the datum, or NULL on error.
+ **/
+inline const gchar *const      
+g_sheet_model_get_string (const GSheetModel *sheet_model, 
+                         gint row, gint column)
+{
+  g_return_val_if_fail (G_IS_SHEET_MODEL (sheet_model), 0);
+
+  g_assert (G_SHEET_MODEL_GET_IFACE (sheet_model)->get_string);
+  
+  return (G_SHEET_MODEL_GET_IFACE (sheet_model)->get_string) (sheet_model, row, column);
+}
+
+/**
+ * g_sheet_model_set_string
+ * @sheet_model: A #GSheetModel
+ * @text: The text describing the datum to be set.
+ * @row: The row of the cell to be cleared.
+ * @column: The column of the cell to be cleared.
+ * 
+ * Sets the datum at a location from a string.
+ * Returns: TRUE if the datum was changed, FALSE otherwise.
+ **/
+gboolean
+g_sheet_model_set_string      (GSheetModel *sheet_model, 
+                                const gchar *text, 
+                                gint row, gint column)
+{
+  g_return_val_if_fail (G_IS_SHEET_MODEL (sheet_model), FALSE);
+
+  g_assert (G_SHEET_MODEL_GET_IFACE (sheet_model)->set_string);
+
+  return G_SHEET_MODEL_GET_IFACE (sheet_model)->set_string (sheet_model, 
+                                                           text, row, column);
+}
+
+
+
+/**
+ * g_sheet_model_datum_clear:
+ * @sheet_model: A #GSheetModel
+ * @row: The row of the cell to be cleared.
+ * @column: The column of the cell to be cleared.
+ * 
+ * Called when the datum at a location is to be cleared.
+ * Returns: TRUE if the datum was cleared, FALSE otherwise.
+ **/
+gboolean
+g_sheet_model_datum_clear    (GSheetModel *sheet_model, 
+                               gint row, gint column)
+{
+  g_return_val_if_fail (G_IS_SHEET_MODEL (sheet_model), FALSE);
+
+  g_assert (G_SHEET_MODEL_GET_IFACE (sheet_model)->clear_datum);
+
+  return G_SHEET_MODEL_GET_IFACE (sheet_model)->clear_datum (sheet_model, 
+                                                               row, column);
+}
+
+
+/**
+ * g_sheet_model_range_changed:
+ * @sheet_model: A #GSheetModel
+ * @range: The #GSheetRange range of cells which have changed.
+ * 
+ * Emits the "range_changed" signal on @sheet_model.
+ **/
+void
+g_sheet_model_range_changed (GSheetModel *sheet_model,
+                              gint row0, gint col0,
+                              gint rowi, gint coli)
+{
+  g_return_if_fail (G_IS_SHEET_MODEL (sheet_model));
+
+  g_signal_emit (sheet_model, sheet_model_signals[RANGE_CHANGED], 0, 
+                row0, col0, rowi, coli);
+}
+
+
+
+
+/**
+ * g_sheet_model_rows_inserted:
+ * @sheet_model: A #GSheetModel
+ * @row: The row before which the new rows should be inserted.
+ * @n_rows: The number of rows to insert.
+ * 
+ * Emits the "rows_inserted" signal on @sheet_model.
+ **/
+void
+g_sheet_model_rows_inserted (GSheetModel *sheet_model,
+                              gint row, gint n_rows)
+{
+  g_return_if_fail (G_IS_SHEET_MODEL (sheet_model));
+
+  g_signal_emit (sheet_model, sheet_model_signals[ROWS_INSERTED], 0, 
+                row, n_rows);
+}
+
+
+
+
+/**
+ * g_sheet_model_rows_deleted:
+ * @sheet_model: A #GSheetModel
+ * @row: The first row to be deleted.
+ * @n_rows: The number of rows to delete.
+ * 
+ * Emits the "rows_deleted" signal on @sheet_model.
+ **/
+void
+g_sheet_model_rows_deleted (GSheetModel *sheet_model,
+                              gint row, gint n_rows)
+{
+  g_return_if_fail (G_IS_SHEET_MODEL (sheet_model));
+
+  g_signal_emit (sheet_model, sheet_model_signals[ROWS_DELETED], 0, 
+                row, n_rows);
+}
+
+
+
+
+/**
+ * g_sheet_model_is_editable:
+ * @sheet_model: A #GSheetModel
+ * @row: The row 
+ * @column: The column
+ * 
+ * Returns: TRUE if the cell is editable, FALSE otherwise
+ **/
+inline gboolean 
+g_sheet_model_is_editable (const GSheetModel *model, 
+                            gint row, gint column)
+{
+  g_return_val_if_fail (G_IS_SHEET_MODEL (model), TRUE);
+
+  if ( ! G_SHEET_MODEL_GET_IFACE (model)->is_editable )
+    return TRUE;
+
+  return G_SHEET_MODEL_GET_IFACE (model)->is_editable (model, 
+                                                         row, column);
+}
+
+/**
+ * g_sheet_model_is_visible:
+ * @sheet_model: A #GSheetModel
+ * @row: The row 
+ * @column: The column
+ * 
+ * Returns: TRUE if the cell is visible, FALSE otherwise
+ **/
+inline gboolean 
+g_sheet_model_is_visible (const GSheetModel *model, 
+                         gint row, gint column)
+{
+  g_return_val_if_fail (G_IS_SHEET_MODEL (model), TRUE);
+
+  if ( ! G_SHEET_MODEL_GET_IFACE (model)->is_visible )
+    return TRUE;
+
+  return G_SHEET_MODEL_GET_IFACE (model)->is_visible (model, 
+                                                       row, column);
+}
+
+
+/**
+ * g_sheet_model_get_foreground:
+ * @sheet_model: A #GSheetModel
+ * @row: The row 
+ * @column: The column
+ *
+ * Returns the foreground colour of the cell at @row, @column
+ * Returns: the foreground colour, or NULL on error.
+ **/
+inline const GdkColor *
+g_sheet_model_get_foreground (const GSheetModel *model, 
+                               gint row, gint column)
+{
+  g_return_val_if_fail (G_IS_SHEET_MODEL (model), NULL);
+
+  if ( ! G_SHEET_MODEL_GET_IFACE (model)->get_foreground )
+    return NULL;
+
+  return G_SHEET_MODEL_GET_IFACE (model)->get_foreground (model, 
+                                                           row, column);
+}
+
+/**
+ * g_sheet_model_get_background:
+ * @sheet_model: A #GSheetModel
+ * @row: The row 
+ * @column: The column
+ *
+ * Returns the background colour of the cell at @row, @column
+ * Returns: the background colour, or NULL on error.
+ **/
+inline const GdkColor *
+g_sheet_model_get_background (const GSheetModel *model, 
+                               gint row, gint column)
+{
+  g_return_val_if_fail (G_IS_SHEET_MODEL (model), NULL);
+
+  if ( ! G_SHEET_MODEL_GET_IFACE (model)->get_background )
+    return NULL;
+
+  return G_SHEET_MODEL_GET_IFACE (model)->get_background (model, 
+                                                           row, column);
+}
+
+/**
+ * g_sheet_model_get_justification:
+ * @sheet_model: A #GSheetModel
+ * @row: The row 
+ * @column: The column
+ *
+ * Returns the justification of the cell at @row, @column
+ * Returns: the justification, or NULL on error.
+ **/
+inline const GtkJustification *
+g_sheet_model_get_justification (const GSheetModel *model, 
+                                  gint row, gint column)
+{
+  g_return_val_if_fail (G_IS_SHEET_MODEL (model), NULL);
+
+  if ( ! G_SHEET_MODEL_GET_IFACE (model)->get_justification)
+    return NULL;
+
+  return G_SHEET_MODEL_GET_IFACE (model)->get_justification (model, 
+                                                              row, column);
+}
+
+/**
+ * g_sheet_model_get_font_desc:
+ * @sheet_model: A #GSheetModel
+ * @row: The row 
+ * @column: The column
+ *
+ * Returns the font description of the cell at @row, @column
+ * Returns: the font description, or NULL on error.
+ **/
+inline const PangoFontDescription *
+g_sheet_model_get_font_desc(const GSheetModel *model,
+                             gint row, gint column)
+{
+  g_return_val_if_fail (G_IS_SHEET_MODEL (model), NULL);
+  if ( ! G_SHEET_MODEL_GET_IFACE (model)->get_font_desc)
+    return NULL;
+
+  return G_SHEET_MODEL_GET_IFACE (model)->get_font_desc (model, 
+                                                          row, column);
+}
+
+/**
+ * g_sheet_model_get_cell_border:
+ * @sheet_model: A #GSheetModel
+ * @row: The row 
+ * @column: The column
+ *
+ * Returns the cell border of the cell at @row, @column
+ * Returns: the cell border, or NULL on error.
+ **/
+inline const GtkSheetCellBorder * 
+g_sheet_model_get_cell_border (const GSheetModel *model, 
+                                gint row, gint column)
+{
+  g_return_val_if_fail (G_IS_SHEET_MODEL (model), NULL);
+  if ( ! G_SHEET_MODEL_GET_IFACE (model)->get_cell_border)
+    return NULL;
+
+  return G_SHEET_MODEL_GET_IFACE (model)->get_cell_border (model, 
+                                                          row, column);
+}
diff --git a/lib/gtksheet/gsheetmodel.h b/lib/gtksheet/gsheetmodel.h
new file mode 100644 (file)
index 0000000..2865e78
--- /dev/null
@@ -0,0 +1,171 @@
+/* GSheetModel --- an abstract model for the GtkSheet widget.
+ * Copyright (C) 2006 Free Software Foundation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+#ifndef __G_SHEET_MODEL_H__
+#define __G_SHEET_MODEL_H__
+
+
+/* This file provides an abstract interface or the data displayed by the  
+   GtkSheet widget */
+
+#include <glib-object.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+
+G_BEGIN_DECLS
+
+#define G_TYPE_SHEET_MODEL            (g_sheet_model_get_type ())
+#define G_SHEET_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_SHEET_MODEL, GSheetModel))
+#define G_IS_SHEET_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_SHEET_MODEL))
+#define G_SHEET_MODEL_GET_IFACE(obj)  (G_TYPE_INSTANCE_GET_INTERFACE ((obj), G_TYPE_SHEET_MODEL, GSheetModelIface))
+
+typedef enum
+{
+  GTK_SHEET_LEFT_BORDER     = 1 << 0, 
+  GTK_SHEET_RIGHT_BORDER    = 1 << 1, 
+  GTK_SHEET_TOP_BORDER      = 1 << 2, 
+  GTK_SHEET_BOTTOM_BORDER   = 1 << 3 
+} GtkSheetBorderType ; 
+
+
+typedef struct _GSheetModel        GSheetModel; /* Dummy typedef */
+typedef struct _GSheetModelIface   GSheetModelIface;
+typedef struct _GtkSheetRange GtkSheetRange;
+typedef struct _GtkSheetCellBorder     GtkSheetCellBorder;
+
+struct _GtkSheetRange
+{
+  gint row0,col0; /* upper-left cell */
+  gint rowi,coli; /* lower-right cell */
+};
+
+struct _GtkSheetCellBorder
+{
+  GtkSheetBorderType mask;
+  guint width;
+  GdkLineStyle line_style;
+  GdkCapStyle cap_style;
+  GdkJoinStyle join_style;
+  GdkColor color;
+};
+
+
+
+struct _GSheetModelIface
+{
+  GTypeInterface g_iface;
+
+  /* Signals */
+  void         (* range_changed)    (GSheetModel *sheet_model,
+                                    gint row0, gint col0, 
+                                    gint rowi, gint coli);
+
+  void         (* rows_inserted)    (GSheetModel *sheet_model,
+                                    gint row, gint n_rows);
+
+  void         (* rows_deleted)     (GSheetModel *sheet_model,
+                                    gint row, gint n_rows);
+
+
+
+  /* Virtual Table */
+  const gchar *const      (* get_string)      (const GSheetModel *sheet_model, 
+                                              gint row, gint column);
+
+  gboolean  (* set_string) (GSheetModel *sheet_model, 
+                           const gchar *s, gint row, gint column);
+
+  gboolean  (* clear_datum) (GSheetModel *sheet_model, 
+                            gint row, gint column);
+
+  gboolean (* is_visible) (const GSheetModel *sheet_model, gint row, gint column);
+  gboolean (* is_editable) (const GSheetModel *sheet_model, gint row, gint column);
+
+  const GdkColor *  (* get_foreground) (const GSheetModel *sheet_model, 
+                                   gint row, gint column);
+
+  const GdkColor *  (* get_background) (const GSheetModel *sheet_model, 
+                                   gint row, gint column);
+
+  const GtkJustification *  (* get_justification) (const GSheetModel *sheet_model, 
+                                                  gint row, gint column);
+
+  const PangoFontDescription *  (* get_font_desc) (const GSheetModel *sheet_model, 
+                                                  gint row, gint column);
+
+  const GtkSheetCellBorder *  (* get_cell_border) (const GSheetModel *sheet_model, 
+                                                  gint row, gint column);
+
+};
+
+
+
+GType              g_sheet_model_get_type   (void) G_GNUC_CONST;
+
+
+inline const gchar *const g_sheet_model_get_string (const GSheetModel *sheet_model, 
+                                              gint row, gint column);
+
+inline gboolean  g_sheet_model_set_string (GSheetModel *sheet_model, 
+                                     const gchar *s, 
+                                     gint row, gint column);
+
+inline gboolean g_sheet_model_datum_clear    (GSheetModel *sheet_model, 
+                                        gint row, gint column);
+
+
+inline void g_sheet_model_range_changed (GSheetModel *sheet_model,
+                                   gint row0, gint col0,
+                                   gint rowi, gint coli);
+
+inline void g_sheet_model_rows_deleted (GSheetModel *sheet_model,
+                                  gint row, gint n_rows);
+
+inline void g_sheet_model_rows_inserted (GSheetModel *sheet_model,
+                                   gint row, gint n_rows);
+
+inline gboolean g_sheet_model_is_editable (const GSheetModel *model, 
+                                     gint row, gint column);
+
+inline gboolean g_sheet_model_is_visible 
+                   (const GSheetModel *model, gint row, gint column);
+
+
+inline const GdkColor *g_sheet_model_get_foreground 
+                   (const GSheetModel *model, gint row, gint column);
+
+inline const GdkColor *g_sheet_model_get_background 
+                   (const GSheetModel *model, gint row, gint column);
+
+
+inline const GtkJustification *g_sheet_model_get_justification 
+                   (const GSheetModel *model, gint row, gint column);
+
+
+inline const PangoFontDescription *g_sheet_model_get_font_desc
+                   (const GSheetModel *model, gint row, gint column);
+
+inline const GtkSheetCellBorder * g_sheet_model_get_cell_border 
+                   (const GSheetModel *model, gint row, gint column);
+
+
+
+
+G_END_DECLS
+
+#endif /* __G_SHEET_MODEL_H__ */
diff --git a/lib/gtksheet/gtkextra-marshal.c b/lib/gtksheet/gtkextra-marshal.c
new file mode 100644 (file)
index 0000000..058d8e5
--- /dev/null
@@ -0,0 +1,892 @@
+
+#include       <glib-object.h>
+
+
+#ifdef G_ENABLE_DEBUG
+#define g_marshal_value_peek_boolean(v)  g_value_get_boolean (v)
+#define g_marshal_value_peek_char(v)     g_value_get_char (v)
+#define g_marshal_value_peek_uchar(v)    g_value_get_uchar (v)
+#define g_marshal_value_peek_int(v)      g_value_get_int (v)
+#define g_marshal_value_peek_uint(v)     g_value_get_uint (v)
+#define g_marshal_value_peek_long(v)     g_value_get_long (v)
+#define g_marshal_value_peek_ulong(v)    g_value_get_ulong (v)
+#define g_marshal_value_peek_int64(v)    g_value_get_int64 (v)
+#define g_marshal_value_peek_uint64(v)   g_value_get_uint64 (v)
+#define g_marshal_value_peek_enum(v)     g_value_get_enum (v)
+#define g_marshal_value_peek_flags(v)    g_value_get_flags (v)
+#define g_marshal_value_peek_float(v)    g_value_get_float (v)
+#define g_marshal_value_peek_double(v)   g_value_get_double (v)
+#define g_marshal_value_peek_string(v)   (char*) g_value_get_string (v)
+#define g_marshal_value_peek_param(v)    g_value_get_param (v)
+#define g_marshal_value_peek_boxed(v)    g_value_get_boxed (v)
+#define g_marshal_value_peek_pointer(v)  g_value_get_pointer (v)
+#define g_marshal_value_peek_object(v)   g_value_get_object (v)
+#else /* !G_ENABLE_DEBUG */
+/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
+ *          Do not access GValues directly in your code. Instead, use the
+ *          g_value_get_*() functions
+ */
+#define g_marshal_value_peek_boolean(v)  (v)->data[0].v_int
+#define g_marshal_value_peek_char(v)     (v)->data[0].v_int
+#define g_marshal_value_peek_uchar(v)    (v)->data[0].v_uint
+#define g_marshal_value_peek_int(v)      (v)->data[0].v_int
+#define g_marshal_value_peek_uint(v)     (v)->data[0].v_uint
+#define g_marshal_value_peek_long(v)     (v)->data[0].v_long
+#define g_marshal_value_peek_ulong(v)    (v)->data[0].v_ulong
+#define g_marshal_value_peek_int64(v)    (v)->data[0].v_int64
+#define g_marshal_value_peek_uint64(v)   (v)->data[0].v_uint64
+#define g_marshal_value_peek_enum(v)     (v)->data[0].v_int
+#define g_marshal_value_peek_flags(v)    (v)->data[0].v_uint
+#define g_marshal_value_peek_float(v)    (v)->data[0].v_float
+#define g_marshal_value_peek_double(v)   (v)->data[0].v_double
+#define g_marshal_value_peek_string(v)   (v)->data[0].v_pointer
+#define g_marshal_value_peek_param(v)    (v)->data[0].v_pointer
+#define g_marshal_value_peek_boxed(v)    (v)->data[0].v_pointer
+#define g_marshal_value_peek_pointer(v)  (v)->data[0].v_pointer
+#define g_marshal_value_peek_object(v)   (v)->data[0].v_pointer
+#endif /* !G_ENABLE_DEBUG */
+
+
+/* BOOL:INT,INT,POINTER,POINTER (gtkextra-marshal.list:1) */
+void
+gtkextra_BOOLEAN__INT_INT_POINTER_POINTER (GClosure     *closure,
+                                           GValue       *return_value,
+                                           guint         n_param_values,
+                                           const GValue *param_values,
+                                           gpointer      invocation_hint,
+                                           gpointer      marshal_data)
+{
+  typedef gboolean (*GMarshalFunc_BOOLEAN__INT_INT_POINTER_POINTER) (gpointer     data1,
+                                                                     gint         arg_1,
+                                                                     gint         arg_2,
+                                                                     gpointer     arg_3,
+                                                                     gpointer     arg_4,
+                                                                     gpointer     data2);
+  register GMarshalFunc_BOOLEAN__INT_INT_POINTER_POINTER callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+  gboolean v_return;
+
+  g_return_if_fail (return_value != NULL);
+  g_return_if_fail (n_param_values == 5);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_BOOLEAN__INT_INT_POINTER_POINTER) (marshal_data ? marshal_data : cc->callback);
+
+  v_return = callback (data1,
+                       g_marshal_value_peek_int (param_values + 1),
+                       g_marshal_value_peek_int (param_values + 2),
+                       g_marshal_value_peek_pointer (param_values + 3),
+                       g_marshal_value_peek_pointer (param_values + 4),
+                       data2);
+
+  g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOL:BOXED,POINTER (gtkextra-marshal.list:2) */
+void
+gtkextra_BOOLEAN__BOXED_POINTER (GClosure     *closure,
+                                 GValue       *return_value,
+                                 guint         n_param_values,
+                                 const GValue *param_values,
+                                 gpointer      invocation_hint,
+                                 gpointer      marshal_data)
+{
+  typedef gboolean (*GMarshalFunc_BOOLEAN__BOXED_POINTER) (gpointer     data1,
+                                                           gpointer     arg_1,
+                                                           gpointer     arg_2,
+                                                           gpointer     data2);
+  register GMarshalFunc_BOOLEAN__BOXED_POINTER callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+  gboolean v_return;
+
+  g_return_if_fail (return_value != NULL);
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_BOOLEAN__BOXED_POINTER) (marshal_data ? marshal_data : cc->callback);
+
+  v_return = callback (data1,
+                       g_marshal_value_peek_boxed (param_values + 1),
+                       g_marshal_value_peek_pointer (param_values + 2),
+                       data2);
+
+  g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOL:BOXED,STRING (gtkextra-marshal.list:3) */
+void
+gtkextra_BOOLEAN__BOXED_STRING (GClosure     *closure,
+                                GValue       *return_value,
+                                guint         n_param_values,
+                                const GValue *param_values,
+                                gpointer      invocation_hint,
+                                gpointer      marshal_data)
+{
+  typedef gboolean (*GMarshalFunc_BOOLEAN__BOXED_STRING) (gpointer     data1,
+                                                          gpointer     arg_1,
+                                                          gpointer     arg_2,
+                                                          gpointer     data2);
+  register GMarshalFunc_BOOLEAN__BOXED_STRING callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+  gboolean v_return;
+
+  g_return_if_fail (return_value != NULL);
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_BOOLEAN__BOXED_STRING) (marshal_data ? marshal_data : cc->callback);
+
+  v_return = callback (data1,
+                       g_marshal_value_peek_boxed (param_values + 1),
+                       g_marshal_value_peek_string (param_values + 2),
+                       data2);
+
+  g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOL:BOXED,BOXED (gtkextra-marshal.list:4) */
+void
+gtkextra_BOOLEAN__BOXED_BOXED (GClosure     *closure,
+                               GValue       *return_value,
+                               guint         n_param_values,
+                               const GValue *param_values,
+                               gpointer      invocation_hint,
+                               gpointer      marshal_data)
+{
+  typedef gboolean (*GMarshalFunc_BOOLEAN__BOXED_BOXED) (gpointer     data1,
+                                                         gpointer     arg_1,
+                                                         gpointer     arg_2,
+                                                         gpointer     data2);
+  register GMarshalFunc_BOOLEAN__BOXED_BOXED callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+  gboolean v_return;
+
+  g_return_if_fail (return_value != NULL);
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_BOOLEAN__BOXED_BOXED) (marshal_data ? marshal_data : cc->callback);
+
+  v_return = callback (data1,
+                       g_marshal_value_peek_boxed (param_values + 1),
+                       g_marshal_value_peek_boxed (param_values + 2),
+                       data2);
+
+  g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOL:BOXED,DOUBLE,DOUBLE (gtkextra-marshal.list:5) */
+void
+gtkextra_BOOLEAN__BOXED_DOUBLE_DOUBLE (GClosure     *closure,
+                                       GValue       *return_value,
+                                       guint         n_param_values,
+                                       const GValue *param_values,
+                                       gpointer      invocation_hint,
+                                       gpointer      marshal_data)
+{
+  typedef gboolean (*GMarshalFunc_BOOLEAN__BOXED_DOUBLE_DOUBLE) (gpointer     data1,
+                                                                 gpointer     arg_1,
+                                                                 gdouble      arg_2,
+                                                                 gdouble      arg_3,
+                                                                 gpointer     data2);
+  register GMarshalFunc_BOOLEAN__BOXED_DOUBLE_DOUBLE callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+  gboolean v_return;
+
+  g_return_if_fail (return_value != NULL);
+  g_return_if_fail (n_param_values == 4);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_BOOLEAN__BOXED_DOUBLE_DOUBLE) (marshal_data ? marshal_data : cc->callback);
+
+  v_return = callback (data1,
+                       g_marshal_value_peek_boxed (param_values + 1),
+                       g_marshal_value_peek_double (param_values + 2),
+                       g_marshal_value_peek_double (param_values + 3),
+                       data2);
+
+  g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOL:POINTER,POINTER (gtkextra-marshal.list:6) */
+void
+gtkextra_BOOLEAN__POINTER_POINTER (GClosure     *closure,
+                                   GValue       *return_value,
+                                   guint         n_param_values,
+                                   const GValue *param_values,
+                                   gpointer      invocation_hint,
+                                   gpointer      marshal_data)
+{
+  typedef gboolean (*GMarshalFunc_BOOLEAN__POINTER_POINTER) (gpointer     data1,
+                                                             gpointer     arg_1,
+                                                             gpointer     arg_2,
+                                                             gpointer     data2);
+  register GMarshalFunc_BOOLEAN__POINTER_POINTER callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+  gboolean v_return;
+
+  g_return_if_fail (return_value != NULL);
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_BOOLEAN__POINTER_POINTER) (marshal_data ? marshal_data : cc->callback);
+
+  v_return = callback (data1,
+                       g_marshal_value_peek_pointer (param_values + 1),
+                       g_marshal_value_peek_pointer (param_values + 2),
+                       data2);
+
+  g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOL:POINTER,BOXED (gtkextra-marshal.list:7) */
+void
+gtkextra_BOOLEAN__POINTER_BOXED (GClosure     *closure,
+                                 GValue       *return_value,
+                                 guint         n_param_values,
+                                 const GValue *param_values,
+                                 gpointer      invocation_hint,
+                                 gpointer      marshal_data)
+{
+  typedef gboolean (*GMarshalFunc_BOOLEAN__POINTER_BOXED) (gpointer     data1,
+                                                           gpointer     arg_1,
+                                                           gpointer     arg_2,
+                                                           gpointer     data2);
+  register GMarshalFunc_BOOLEAN__POINTER_BOXED callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+  gboolean v_return;
+
+  g_return_if_fail (return_value != NULL);
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_BOOLEAN__POINTER_BOXED) (marshal_data ? marshal_data : cc->callback);
+
+  v_return = callback (data1,
+                       g_marshal_value_peek_pointer (param_values + 1),
+                       g_marshal_value_peek_boxed (param_values + 2),
+                       data2);
+
+  g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOL:POINTER,STRING (gtkextra-marshal.list:8) */
+void
+gtkextra_BOOLEAN__POINTER_STRING (GClosure     *closure,
+                                  GValue       *return_value,
+                                  guint         n_param_values,
+                                  const GValue *param_values,
+                                  gpointer      invocation_hint,
+                                  gpointer      marshal_data)
+{
+  typedef gboolean (*GMarshalFunc_BOOLEAN__POINTER_STRING) (gpointer     data1,
+                                                            gpointer     arg_1,
+                                                            gpointer     arg_2,
+                                                            gpointer     data2);
+  register GMarshalFunc_BOOLEAN__POINTER_STRING callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+  gboolean v_return;
+
+  g_return_if_fail (return_value != NULL);
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_BOOLEAN__POINTER_STRING) (marshal_data ? marshal_data : cc->callback);
+
+  v_return = callback (data1,
+                       g_marshal_value_peek_pointer (param_values + 1),
+                       g_marshal_value_peek_string (param_values + 2),
+                       data2);
+
+  g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOL:POINTER (gtkextra-marshal.list:9) */
+void
+gtkextra_BOOLEAN__POINTER (GClosure     *closure,
+                           GValue       *return_value,
+                           guint         n_param_values,
+                           const GValue *param_values,
+                           gpointer      invocation_hint,
+                           gpointer      marshal_data)
+{
+  typedef gboolean (*GMarshalFunc_BOOLEAN__POINTER) (gpointer     data1,
+                                                     gpointer     arg_1,
+                                                     gpointer     data2);
+  register GMarshalFunc_BOOLEAN__POINTER callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+  gboolean v_return;
+
+  g_return_if_fail (return_value != NULL);
+  g_return_if_fail (n_param_values == 2);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_BOOLEAN__POINTER) (marshal_data ? marshal_data : cc->callback);
+
+  v_return = callback (data1,
+                       g_marshal_value_peek_pointer (param_values + 1),
+                       data2);
+
+  g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOL:BOXED (gtkextra-marshal.list:10) */
+void
+gtkextra_BOOLEAN__BOXED (GClosure     *closure,
+                         GValue       *return_value,
+                         guint         n_param_values,
+                         const GValue *param_values,
+                         gpointer      invocation_hint,
+                         gpointer      marshal_data)
+{
+  typedef gboolean (*GMarshalFunc_BOOLEAN__BOXED) (gpointer     data1,
+                                                   gpointer     arg_1,
+                                                   gpointer     data2);
+  register GMarshalFunc_BOOLEAN__BOXED callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+  gboolean v_return;
+
+  g_return_if_fail (return_value != NULL);
+  g_return_if_fail (n_param_values == 2);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_BOOLEAN__BOXED) (marshal_data ? marshal_data : cc->callback);
+
+  v_return = callback (data1,
+                       g_marshal_value_peek_boxed (param_values + 1),
+                       data2);
+
+  g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOL:INT,INT (gtkextra-marshal.list:11) */
+void
+gtkextra_BOOLEAN__INT_INT (GClosure     *closure,
+                           GValue       *return_value,
+                           guint         n_param_values,
+                           const GValue *param_values,
+                           gpointer      invocation_hint,
+                           gpointer      marshal_data)
+{
+  typedef gboolean (*GMarshalFunc_BOOLEAN__INT_INT) (gpointer     data1,
+                                                     gint         arg_1,
+                                                     gint         arg_2,
+                                                     gpointer     data2);
+  register GMarshalFunc_BOOLEAN__INT_INT callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+  gboolean v_return;
+
+  g_return_if_fail (return_value != NULL);
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_BOOLEAN__INT_INT) (marshal_data ? marshal_data : cc->callback);
+
+  v_return = callback (data1,
+                       g_marshal_value_peek_int (param_values + 1),
+                       g_marshal_value_peek_int (param_values + 2),
+                       data2);
+
+  g_value_set_boolean (return_value, v_return);
+}
+
+/* VOID:INT (gtkextra-marshal.list:12) */
+
+/* VOID:INT,STRING (gtkextra-marshal.list:13) */
+void
+gtkextra_VOID__INT_STRING (GClosure     *closure,
+                           GValue       *return_value,
+                           guint         n_param_values,
+                           const GValue *param_values,
+                           gpointer      invocation_hint,
+                           gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__INT_STRING) (gpointer     data1,
+                                                 gint         arg_1,
+                                                 gpointer     arg_2,
+                                                 gpointer     data2);
+  register GMarshalFunc_VOID__INT_STRING callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__INT_STRING) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_int (param_values + 1),
+            g_marshal_value_peek_string (param_values + 2),
+            data2);
+}
+
+/* VOID:BOXED (gtkextra-marshal.list:14) */
+
+/* VOID:VOID (gtkextra-marshal.list:15) */
+
+/* VOID:BOOL (gtkextra-marshal.list:16) */
+
+/* VOID:POINTER (gtkextra-marshal.list:17) */
+
+/* VOID:INT,INT (gtkextra-marshal.list:18) */
+void
+gtkextra_VOID__INT_INT (GClosure     *closure,
+                        GValue       *return_value,
+                        guint         n_param_values,
+                        const GValue *param_values,
+                        gpointer      invocation_hint,
+                        gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__INT_INT) (gpointer     data1,
+                                              gint         arg_1,
+                                              gint         arg_2,
+                                              gpointer     data2);
+  register GMarshalFunc_VOID__INT_INT callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__INT_INT) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_int (param_values + 1),
+            g_marshal_value_peek_int (param_values + 2),
+            data2);
+}
+
+
+/* VOID:INT,INT,INT,INT (Added by JMD 1/1/2006) */
+void
+gtkextra_VOID__INT_INT_INT_INT (GClosure     *closure,
+                        GValue       *return_value,
+                        guint         n_param_values,
+                        const GValue *param_values,
+                        gpointer      invocation_hint,
+                        gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__INT_INT_INT_INT) (gpointer     data1,
+                                              gint         arg_1,
+                                              gint         arg_2,
+                                              gint         arg_3,
+                                              gint         arg_4,
+                                              gpointer     data2);
+  register GMarshalFunc_VOID__INT_INT_INT_INT callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 5);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__INT_INT_INT_INT) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_int (param_values + 1),
+            g_marshal_value_peek_int (param_values + 2),
+            g_marshal_value_peek_int (param_values + 3),
+            g_marshal_value_peek_int (param_values + 4),
+            data2);
+}
+
+
+/* VOID:INT,POINTER (gtkextra-marshal.list:19) */
+void
+gtkextra_VOID__INT_POINTER (GClosure     *closure,
+                            GValue       *return_value,
+                            guint         n_param_values,
+                            const GValue *param_values,
+                            gpointer      invocation_hint,
+                            gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__INT_POINTER) (gpointer     data1,
+                                                  gint         arg_1,
+                                                  gpointer     arg_2,
+                                                  gpointer     data2);
+  register GMarshalFunc_VOID__INT_POINTER callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__INT_POINTER) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_int (param_values + 1),
+            g_marshal_value_peek_pointer (param_values + 2),
+            data2);
+}
+
+/* VOID:INT,BOXED (gtkextra-marshal.list:20) */
+void
+gtkextra_VOID__INT_BOXED (GClosure     *closure,
+                          GValue       *return_value,
+                          guint         n_param_values,
+                          const GValue *param_values,
+                          gpointer      invocation_hint,
+                          gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__INT_BOXED) (gpointer     data1,
+                                                gint         arg_1,
+                                                gpointer     arg_2,
+                                                gpointer     data2);
+  register GMarshalFunc_VOID__INT_BOXED callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__INT_BOXED) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_int (param_values + 1),
+            g_marshal_value_peek_boxed (param_values + 2),
+            data2);
+}
+
+/* VOID:POINTER,POINTER (gtkextra-marshal.list:21) */
+void
+gtkextra_VOID__POINTER_POINTER (GClosure     *closure,
+                                GValue       *return_value,
+                                guint         n_param_values,
+                                const GValue *param_values,
+                                gpointer      invocation_hint,
+                                gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__POINTER_POINTER) (gpointer     data1,
+                                                      gpointer     arg_1,
+                                                      gpointer     arg_2,
+                                                      gpointer     data2);
+  register GMarshalFunc_VOID__POINTER_POINTER callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__POINTER_POINTER) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_pointer (param_values + 1),
+            g_marshal_value_peek_pointer (param_values + 2),
+            data2);
+}
+
+/* VOID:BOXED,POINTER (gtkextra-marshal.list:22) */
+void
+gtkextra_VOID__BOXED_POINTER (GClosure     *closure,
+                              GValue       *return_value,
+                              guint         n_param_values,
+                              const GValue *param_values,
+                              gpointer      invocation_hint,
+                              gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__BOXED_POINTER) (gpointer     data1,
+                                                    gpointer     arg_1,
+                                                    gpointer     arg_2,
+                                                    gpointer     data2);
+  register GMarshalFunc_VOID__BOXED_POINTER callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__BOXED_POINTER) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_boxed (param_values + 1),
+            g_marshal_value_peek_pointer (param_values + 2),
+            data2);
+}
+
+/* VOID:BOXED,BOXED (gtkextra-marshal.list:23) */
+void
+gtkextra_VOID__BOXED_BOXED (GClosure     *closure,
+                            GValue       *return_value,
+                            guint         n_param_values,
+                            const GValue *param_values,
+                            gpointer      invocation_hint,
+                            gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__BOXED_BOXED) (gpointer     data1,
+                                                  gpointer     arg_1,
+                                                  gpointer     arg_2,
+                                                  gpointer     data2);
+  register GMarshalFunc_VOID__BOXED_BOXED callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__BOXED_BOXED) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_boxed (param_values + 1),
+            g_marshal_value_peek_boxed (param_values + 2),
+            data2);
+}
+
+/* VOID:OBJECT,OBJECT (gtkextra-marshal.list:24) */
+void
+gtkextra_VOID__OBJECT_OBJECT (GClosure     *closure,
+                              GValue       *return_value,
+                              guint         n_param_values,
+                              const GValue *param_values,
+                              gpointer      invocation_hint,
+                              gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT) (gpointer     data1,
+                                                    gpointer     arg_1,
+                                                    gpointer     arg_2,
+                                                    gpointer     data2);
+  register GMarshalFunc_VOID__OBJECT_OBJECT callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__OBJECT_OBJECT) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_object (param_values + 1),
+            g_marshal_value_peek_object (param_values + 2),
+            data2);
+}
+
+/* VOID:DOUBLE,DOUBLE,DOUBLE,DOUBLE (gtkextra-marshal.list:25) */
+void
+gtkextra_VOID__DOUBLE_DOUBLE_DOUBLE_DOUBLE (GClosure     *closure,
+                                            GValue       *return_value,
+                                            guint         n_param_values,
+                                            const GValue *param_values,
+                                            gpointer      invocation_hint,
+                                            gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__DOUBLE_DOUBLE_DOUBLE_DOUBLE) (gpointer     data1,
+                                                                  gdouble      arg_1,
+                                                                  gdouble      arg_2,
+                                                                  gdouble      arg_3,
+                                                                  gdouble      arg_4,
+                                                                  gpointer     data2);
+  register GMarshalFunc_VOID__DOUBLE_DOUBLE_DOUBLE_DOUBLE callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 5);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__DOUBLE_DOUBLE_DOUBLE_DOUBLE) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_double (param_values + 1),
+            g_marshal_value_peek_double (param_values + 2),
+            g_marshal_value_peek_double (param_values + 3),
+            g_marshal_value_peek_double (param_values + 4),
+            data2);
+}
+
diff --git a/lib/gtksheet/gtkextra-marshal.h b/lib/gtksheet/gtkextra-marshal.h
new file mode 100644 (file)
index 0000000..ea9ed5e
--- /dev/null
@@ -0,0 +1,208 @@
+
+#ifndef __gtkextra_MARSHAL_H__
+#define __gtkextra_MARSHAL_H__
+
+#include       <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* BOOL:INT,INT,POINTER,POINTER (gtkextra-marshal.list:1) */
+extern void gtkextra_BOOLEAN__INT_INT_POINTER_POINTER (GClosure     *closure,
+                                                       GValue       *return_value,
+                                                       guint         n_param_values,
+                                                       const GValue *param_values,
+                                                       gpointer      invocation_hint,
+                                                       gpointer      marshal_data);
+#define gtkextra_BOOL__INT_INT_POINTER_POINTER gtkextra_BOOLEAN__INT_INT_POINTER_POINTER
+
+/* BOOL:BOXED,POINTER (gtkextra-marshal.list:2) */
+extern void gtkextra_BOOLEAN__BOXED_POINTER (GClosure     *closure,
+                                             GValue       *return_value,
+                                             guint         n_param_values,
+                                             const GValue *param_values,
+                                             gpointer      invocation_hint,
+                                             gpointer      marshal_data);
+#define gtkextra_BOOL__BOXED_POINTER   gtkextra_BOOLEAN__BOXED_POINTER
+
+/* BOOL:BOXED,STRING (gtkextra-marshal.list:3) */
+extern void gtkextra_BOOLEAN__BOXED_STRING (GClosure     *closure,
+                                            GValue       *return_value,
+                                            guint         n_param_values,
+                                            const GValue *param_values,
+                                            gpointer      invocation_hint,
+                                            gpointer      marshal_data);
+#define gtkextra_BOOL__BOXED_STRING    gtkextra_BOOLEAN__BOXED_STRING
+
+/* BOOL:BOXED,BOXED (gtkextra-marshal.list:4) */
+extern void gtkextra_BOOLEAN__BOXED_BOXED (GClosure     *closure,
+                                           GValue       *return_value,
+                                           guint         n_param_values,
+                                           const GValue *param_values,
+                                           gpointer      invocation_hint,
+                                           gpointer      marshal_data);
+#define gtkextra_BOOL__BOXED_BOXED     gtkextra_BOOLEAN__BOXED_BOXED
+
+/* BOOL:BOXED,DOUBLE,DOUBLE (gtkextra-marshal.list:5) */
+extern void gtkextra_BOOLEAN__BOXED_DOUBLE_DOUBLE (GClosure     *closure,
+                                                   GValue       *return_value,
+                                                   guint         n_param_values,
+                                                   const GValue *param_values,
+                                                   gpointer      invocation_hint,
+                                                   gpointer      marshal_data);
+#define gtkextra_BOOL__BOXED_DOUBLE_DOUBLE     gtkextra_BOOLEAN__BOXED_DOUBLE_DOUBLE
+
+/* BOOL:POINTER,POINTER (gtkextra-marshal.list:6) */
+extern void gtkextra_BOOLEAN__POINTER_POINTER (GClosure     *closure,
+                                               GValue       *return_value,
+                                               guint         n_param_values,
+                                               const GValue *param_values,
+                                               gpointer      invocation_hint,
+                                               gpointer      marshal_data);
+#define gtkextra_BOOL__POINTER_POINTER gtkextra_BOOLEAN__POINTER_POINTER
+
+/* BOOL:POINTER,BOXED (gtkextra-marshal.list:7) */
+extern void gtkextra_BOOLEAN__POINTER_BOXED (GClosure     *closure,
+                                             GValue       *return_value,
+                                             guint         n_param_values,
+                                             const GValue *param_values,
+                                             gpointer      invocation_hint,
+                                             gpointer      marshal_data);
+#define gtkextra_BOOL__POINTER_BOXED   gtkextra_BOOLEAN__POINTER_BOXED
+
+/* BOOL:POINTER,STRING (gtkextra-marshal.list:8) */
+extern void gtkextra_BOOLEAN__POINTER_STRING (GClosure     *closure,
+                                              GValue       *return_value,
+                                              guint         n_param_values,
+                                              const GValue *param_values,
+                                              gpointer      invocation_hint,
+                                              gpointer      marshal_data);
+#define gtkextra_BOOL__POINTER_STRING  gtkextra_BOOLEAN__POINTER_STRING
+
+/* BOOL:POINTER (gtkextra-marshal.list:9) */
+extern void gtkextra_BOOLEAN__POINTER (GClosure     *closure,
+                                       GValue       *return_value,
+                                       guint         n_param_values,
+                                       const GValue *param_values,
+                                       gpointer      invocation_hint,
+                                       gpointer      marshal_data);
+#define gtkextra_BOOL__POINTER gtkextra_BOOLEAN__POINTER
+
+/* BOOL:BOXED (gtkextra-marshal.list:10) */
+extern void gtkextra_BOOLEAN__BOXED (GClosure     *closure,
+                                     GValue       *return_value,
+                                     guint         n_param_values,
+                                     const GValue *param_values,
+                                     gpointer      invocation_hint,
+                                     gpointer      marshal_data);
+#define gtkextra_BOOL__BOXED   gtkextra_BOOLEAN__BOXED
+
+/* BOOL:INT,INT (gtkextra-marshal.list:11) */
+extern void gtkextra_BOOLEAN__INT_INT (GClosure     *closure,
+                                       GValue       *return_value,
+                                       guint         n_param_values,
+                                       const GValue *param_values,
+                                       gpointer      invocation_hint,
+                                       gpointer      marshal_data);
+#define gtkextra_BOOL__INT_INT gtkextra_BOOLEAN__INT_INT
+
+/* VOID:INT (gtkextra-marshal.list:12) */
+#define gtkextra_VOID__INT     g_cclosure_marshal_VOID__INT
+
+/* VOID:INT,STRING (gtkextra-marshal.list:13) */
+extern void gtkextra_VOID__INT_STRING (GClosure     *closure,
+                                       GValue       *return_value,
+                                       guint         n_param_values,
+                                       const GValue *param_values,
+                                       gpointer      invocation_hint,
+                                       gpointer      marshal_data);
+
+/* VOID:BOXED (gtkextra-marshal.list:14) */
+#define gtkextra_VOID__BOXED   g_cclosure_marshal_VOID__BOXED
+
+/* VOID:VOID (gtkextra-marshal.list:15) */
+#define gtkextra_VOID__VOID    g_cclosure_marshal_VOID__VOID
+
+/* VOID:BOOL (gtkextra-marshal.list:16) */
+#define gtkextra_VOID__BOOLEAN g_cclosure_marshal_VOID__BOOLEAN
+#define gtkextra_VOID__BOOL    gtkextra_VOID__BOOLEAN
+
+/* VOID:POINTER (gtkextra-marshal.list:17) */
+#define gtkextra_VOID__POINTER g_cclosure_marshal_VOID__POINTER
+
+/* VOID:INT,INT (gtkextra-marshal.list:18) */
+extern void gtkextra_VOID__INT_INT (GClosure     *closure,
+                                    GValue       *return_value,
+                                    guint         n_param_values,
+                                    const GValue *param_values,
+                                    gpointer      invocation_hint,
+                                    gpointer      marshal_data);
+
+/* VOID:INT,INT,INT,INT (Added by JMD 1/1/26) */
+extern void gtkextra_VOID__INT_INT_INT_INT (GClosure     *closure,
+                                           GValue       *return_value,
+                                           guint         n_param_values,
+                                           const GValue *param_values,
+                                           gpointer      invocation_hint,
+                                           gpointer      marshal_data);
+
+
+/* VOID:INT,POINTER (gtkextra-marshal.list:19) */
+extern void gtkextra_VOID__INT_POINTER (GClosure     *closure,
+                                        GValue       *return_value,
+                                        guint         n_param_values,
+                                        const GValue *param_values,
+                                        gpointer      invocation_hint,
+                                        gpointer      marshal_data);
+
+/* VOID:INT,BOXED (gtkextra-marshal.list:20) */
+extern void gtkextra_VOID__INT_BOXED (GClosure     *closure,
+                                      GValue       *return_value,
+                                      guint         n_param_values,
+                                      const GValue *param_values,
+                                      gpointer      invocation_hint,
+                                      gpointer      marshal_data);
+
+/* VOID:POINTER,POINTER (gtkextra-marshal.list:21) */
+extern void gtkextra_VOID__POINTER_POINTER (GClosure     *closure,
+                                            GValue       *return_value,
+                                            guint         n_param_values,
+                                            const GValue *param_values,
+                                            gpointer      invocation_hint,
+                                            gpointer      marshal_data);
+
+/* VOID:BOXED,POINTER (gtkextra-marshal.list:22) */
+extern void gtkextra_VOID__BOXED_POINTER (GClosure     *closure,
+                                          GValue       *return_value,
+                                          guint         n_param_values,
+                                          const GValue *param_values,
+                                          gpointer      invocation_hint,
+                                          gpointer      marshal_data);
+
+/* VOID:BOXED,BOXED (gtkextra-marshal.list:23) */
+extern void gtkextra_VOID__BOXED_BOXED (GClosure     *closure,
+                                        GValue       *return_value,
+                                        guint         n_param_values,
+                                        const GValue *param_values,
+                                        gpointer      invocation_hint,
+                                        gpointer      marshal_data);
+
+/* VOID:OBJECT,OBJECT (gtkextra-marshal.list:24) */
+extern void gtkextra_VOID__OBJECT_OBJECT (GClosure     *closure,
+                                          GValue       *return_value,
+                                          guint         n_param_values,
+                                          const GValue *param_values,
+                                          gpointer      invocation_hint,
+                                          gpointer      marshal_data);
+
+/* VOID:DOUBLE,DOUBLE,DOUBLE,DOUBLE (gtkextra-marshal.list:25) */
+extern void gtkextra_VOID__DOUBLE_DOUBLE_DOUBLE_DOUBLE (GClosure     *closure,
+                                                        GValue       *return_value,
+                                                        guint         n_param_values,
+                                                        const GValue *param_values,
+                                                        gpointer      invocation_hint,
+                                                        gpointer      marshal_data);
+
+G_END_DECLS
+
+#endif /* __gtkextra_MARSHAL_H__ */
+
diff --git a/lib/gtksheet/gtkextra-sheet.h b/lib/gtksheet/gtkextra-sheet.h
new file mode 100644 (file)
index 0000000..bfc714c
--- /dev/null
@@ -0,0 +1,75 @@
+/* This version of GtkSheet has been heavily modified, for the specific 
+ *  requirements of PSPPIRE. 
+ *
+ * GtkSheet widget for Gtk+.
+ * Copyright (C) 1999-2001 Adrian E. Feiguin <adrian@ifir.ifir.edu.ar>
+ *
+ * Based on GtkClist widget by Jay Painter, but major changes.
+ * Memory allocation routines inspired on SC (Spreadsheet Calculator)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+#ifndef __GTK_EXTRA_SHEET_H__
+#define __GTK_EXTRA_SHEET_H__
+
+
+struct _GtkSheet ;
+
+typedef struct _GtkSheet GtkSheet;
+
+
+struct _GtkSheetChild
+{
+  GtkWidget *widget;
+  gint x,y ;
+  gboolean attached_to_cell;
+  gboolean floating;
+  gint row, col;
+  guint16 xpadding;
+  guint16 ypadding;
+  gboolean xexpand;
+  gboolean yexpand;
+  gboolean xshrink;
+  gboolean yshrink;
+  gboolean xfill;
+  gboolean yfill;
+};
+
+typedef struct _GtkSheetChild GtkSheetChild;
+
+
+
+struct _GtkSheetButton
+{
+  GtkStateType state;
+  gchar *label;
+
+  gboolean label_visible;
+  GtkSheetChild *child;
+
+  GtkJustification justification;
+};
+
+typedef struct _GtkSheetButton GtkSheetButton;
+
+
+
+
+
+#endif /* __GTK_EXTRA_SHEET_H__ */
+
+
diff --git a/lib/gtksheet/gtkextra.c b/lib/gtksheet/gtkextra.c
new file mode 100644 (file)
index 0000000..65bd374
--- /dev/null
@@ -0,0 +1,135 @@
+/* gtkextra
+ * Copyright 1999-2001 Adrian E. Feiguin <feiguin@ifir.edu.ar>
+ *
+ * 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 Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <gtk/gtk.h>
+#include "gtkextrafeatures.h"
+#include <gobject/gvaluecollector.h>
+
+const guint gtkextra_major_version = GTKEXTRA_MAJOR_VERSION;
+const guint gtkextra_minor_version = GTKEXTRA_MINOR_VERSION;
+const guint gtkextra_micro_version = GTKEXTRA_MICRO_VERSION;
+const guint gtkextra_binary_age = GTKEXTRA_BINARY_AGE;
+const guint gtkextra_interface_age = GTKEXTRA_INTERFACE_AGE;
+
+gchar * 
+gtkextra_check_version (guint required_major,
+                        guint required_minor,
+                        guint required_micro)
+{
+  if (required_major > GTKEXTRA_MAJOR_VERSION)
+    return "GtkExtra version too old (major mismatch)";
+  if (required_major < GTKEXTRA_MAJOR_VERSION)
+    return "GtkExtra version too new (major mismatch)";
+  if (required_minor > GTKEXTRA_MINOR_VERSION)
+    return "GtkExtra version too old (minor mismatch)";
+  if (required_minor < GTKEXTRA_MINOR_VERSION)
+    return "GtkExtra version too new (minor mismatch)";
+  if (required_micro < GTKEXTRA_MICRO_VERSION - GTKEXTRA_BINARY_AGE)
+    return "GtkExtra version too new (micro mismatch)";
+  if (required_micro > GTKEXTRA_MICRO_VERSION)
+    return "GtkExtra version too old (micro mismatch)";
+  return NULL;
+}
+
+/*
+void
+_gtkextra_signal_test(GtkObject *object, guint signal_id, gint arg1, gint arg2, gboolean *default_ret)
+{
+  gboolean result;
+  GValue ret = { 0, };
+  GValue instance_and_param[3] = { { 0, }, {0, }, {0, } };
+
+  g_value_init(instance_and_param + 0, GTK_OBJECT_TYPE(object));
+  g_value_set_instance(instance_and_param + 0, G_OBJECT(object));
+
+  g_value_init(instance_and_param + 1, G_TYPE_INT);
+  g_value_set_int(instance_and_param + 1, arg1);
+
+  g_value_init(instance_and_param + 2, G_TYPE_INT);
+  g_value_set_int(instance_and_param + 2, arg2);
+
+  g_value_init(&ret, G_TYPE_BOOLEAN);
+  g_value_set_boolean(&ret, *default_ret);
+
+  g_signal_emitv(instance_and_param, signal_id, 0, &ret);
+  *default_ret = g_value_get_boolean(&ret);
+
+  g_value_unset(instance_and_param + 0);
+  g_value_unset(instance_and_param + 1);
+  g_value_unset(instance_and_param + 2);
+}
+*/
+
+void
+_gtkextra_signal_emit(GtkObject *object, guint signal_id, ...)
+{
+  gboolean *result;
+  GValue ret = { 0, };
+  GValue instance_and_params [10] = { {0, }, };
+  va_list var_args;
+  GSignalQuery query;
+  gchar *error;
+  int i;
+
+  va_start (var_args, signal_id);
+
+  g_value_init(instance_and_params + 0, GTK_OBJECT_TYPE(object));
+  g_value_set_instance (instance_and_params + 0, G_OBJECT(object));
+
+  g_signal_query(signal_id, &query);
+
+  for (i = 0; i < query.n_params; i++)
+    {
+      gboolean static_scope = query.param_types[i]&~G_SIGNAL_TYPE_STATIC_SCOPE;
+      g_value_init(instance_and_params + i + 1, query.param_types[i]);
+
+
+      G_VALUE_COLLECT (instance_and_params + i + 1,
+                       var_args,
+                       static_scope ? G_VALUE_NOCOPY_CONTENTS : 0,
+                       &error);
+
+      if (error)
+        {
+          g_warning ("%s: %s", G_STRLOC, error);
+          g_free (error);
+          while (i-- > 0)
+            g_value_unset (instance_and_params + i);
+
+          va_end (var_args);
+          return;
+        }
+  
+
+    }
+
+  g_value_init(&ret, query.return_type);
+  result = va_arg(var_args,gboolean *);
+  g_value_set_boolean(&ret, *result);    
+  g_signal_emitv(instance_and_params, signal_id, 0, &ret);
+  *result = g_value_get_boolean(&ret);    
+  g_value_unset (&ret);
+
+  for (i = 0; i < query.n_params; i++)
+    g_value_unset (instance_and_params + 1 + i);
+  g_value_unset (instance_and_params + 0);
+
+  va_end (var_args);
+}
diff --git a/lib/gtksheet/gtkextrafeatures.h b/lib/gtksheet/gtkextrafeatures.h
new file mode 100644 (file)
index 0000000..8b526a8
--- /dev/null
@@ -0,0 +1,57 @@
+/* gtkextra - set of widgets for gtk+
+ * Copyright 1999-2001  Adrian E. Feiguin <feiguin@ifir.edu.ar>
+ *
+ * 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 Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTK_EXTRA_FEATURES_H
+#define GTK_EXTRA_FEATURES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* GtkExtra version.
+ */
+
+#define GTKEXTRA_MAJOR_VERSION                 (2)
+#define GTKEXTRA_MINOR_VERSION                 (1)
+#define GTKEXTRA_MICRO_VERSION                 (1)
+#define GTKEXTRA_BINARY_AGE                    (0)
+#define GTKEXTRA_INTERFACE_AGE                 (0)
+#define GTKEXTRA_CHECK_VERSION(major,minor,micro)    \
+   (GTKEXTRA_MAJOR_VERSION > (major) || \
+    (GTKEXTRA_MAJOR_VERSION == (major) && GTKEXTRA_MINOR_VERSION > (minor)) || \
+    (GTKEXTRA_MAJOR_VERSION == (major) && GTKEXTRA_MINOR_VERSION == (minor) && \
+     GTKEXTRA_MICRO_VERSION >= (micro)))
+
+
+extern const guint gtkextra_major_version;
+extern const guint gtkextra_minor_version;
+extern const guint gtkextra_micro_version;
+extern const guint gtkextra_binary_age;
+extern const guint gtkextra_interface_age;
+gchar* gtkextra_check_version (guint required_major,
+                               guint required_minor,
+                               guint required_micro);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* GTK_EXTRA_FEATURES_H */
diff --git a/lib/gtksheet/gtkiconlist.c b/lib/gtksheet/gtkiconlist.c
new file mode 100644 (file)
index 0000000..fc7a91f
--- /dev/null
@@ -0,0 +1,1759 @@
+/* gtkiconlist - gtkiconlist widget for gtk+
+ * Copyright 1999-2001  Adrian E. Feiguin <feiguin@ifir.edu.ar>
+ *
+ * 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 Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <pango/pango.h>
+#include "gtkitementry.h"
+#include "gtkiconlist.h"
+#include "gtkextra-marshal.h"
+#include <math.h>
+
+#define DEFAULT_ROW_SPACING    4
+#define DEFAULT_COL_SPACING    10      
+#define DEFAULT_TEXT_SPACE     60
+#define DEFAULT_ICON_BORDER    2
+#define DEFAULT_WIDTH 150
+#define DEFAULT_HEIGHT 120
+
+#define EVENTS_MASK    (GDK_EXPOSURE_MASK |            \
+                       GDK_POINTER_MOTION_MASK |       \
+                       GDK_POINTER_MOTION_HINT_MASK |  \
+                       GDK_BUTTON_PRESS_MASK |         \
+                       GDK_BUTTON_RELEASE_MASK)
+
+/* Signals */
+
+extern void 
+_gtkextra_signal_emit(GtkObject *object, guint signal_id, ...);
+
+enum{
+      SELECT_ICON,                       
+      UNSELECT_ICON,                       
+      TEXT_CHANGED,                       
+      ACTIVATE_ICON,                       
+      DEACTIVATE_ICON,                       
+      CLICK_EVENT,                       
+      LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+static void gtk_icon_list_class_init           (GtkIconListClass *class);
+static void gtk_icon_list_init                         (GtkIconList *icon_list);
+static void gtk_icon_list_destroy              (GtkObject *object);
+static void gtk_icon_list_finalize             (GObject *object);
+
+static void gtk_icon_list_size_allocate                (GtkWidget *widget,
+                                                 GtkAllocation *allocation);
+
+static void gtk_icon_list_realize              (GtkWidget *widget);
+static gint gtk_icon_list_expose               (GtkWidget *widget, 
+                                                GdkEventExpose *event);
+static gint gtk_icon_list_button_press         (GtkWidget *widget, 
+                                                GdkEventButton *event);
+static gint deactivate_entry                   (GtkIconList *iconlist);
+static gint entry_in                           (GtkWidget *widget,
+                                                GdkEventButton *event,
+                                                gpointer data);
+static gint entry_changed                      (GtkWidget *widget, 
+                                                gpointer data);
+static void select_icon                                (GtkIconList *iconlist, 
+                                                GtkIconListItem *item,
+                                                GdkEvent *event);
+static void unselect_icon                      (GtkIconList *iconlist, 
+                                                GtkIconListItem *item,
+                                                GdkEvent *event);
+static void unselect_all                       (GtkIconList *iconlist); 
+static void set_labels                         (GtkIconList *iconlist, 
+                                                GtkIconListItem *item, 
+                                                const gchar *label); 
+static GtkIconListItem *get_icon_from_entry    (GtkIconList *iconlist, 
+                                                GtkWidget *widget);
+static void reorder_icons                      (GtkIconList *iconlist);
+static void item_size_request                  (GtkIconList *iconlist, 
+                                                GtkIconListItem *item,
+                                                GtkRequisition *requisition);
+static void gtk_icon_list_move                 (GtkIconList *iconlist, 
+                                                GtkIconListItem *icon, 
+                                                guint x, guint y);
+static GtkIconListItem *gtk_icon_list_real_add (GtkIconList *iconualist,
+                                                GdkPixmap *pixmap,
+                                                GdkBitmap *mask,
+                                                const gchar *label,
+                                                 gpointer data);
+static GtkIconListItem *gtk_icon_list_put      (GtkIconList *iconlist, 
+                                                guint x, guint y, 
+                                                GdkPixmap *pixmap,
+                                                GdkBitmap *mask,
+                                                const gchar *label,
+                                                 gpointer data);
+static gint icon_key_press                     (GtkWidget *widget, 
+                                                GdkEventKey *key, 
+                                                gpointer data);
+static gint sort_list                          (gpointer a, gpointer b);
+static void pixmap_destroy                      (GtkPixmap* pixmap);
+
+
+static GtkFixedClass *parent_class = NULL;
+
+static inline guint STRING_WIDTH(GtkWidget *widget,
+                                 PangoFontDescription *font, const gchar *text)
+{
+  PangoRectangle rect;
+  PangoLayout *layout;
+
+  layout = gtk_widget_create_pango_layout (widget, text);
+  pango_layout_set_font_description (layout, font);
+
+  pango_layout_get_pixel_extents (layout, NULL, &rect);
+
+  g_object_unref(G_OBJECT(layout));
+  return rect.width;
+}
+
+
+GtkType
+gtk_icon_list_get_type (void)
+{
+  static GtkType icon_list_type = 0;
+
+  if (!icon_list_type)
+    {
+      static const GtkTypeInfo icon_list_info =
+      {
+       "GtkIconList",
+       sizeof (GtkIconList),
+       sizeof (GtkIconListClass),
+       (GtkClassInitFunc) gtk_icon_list_class_init,
+       (GtkObjectInitFunc) gtk_icon_list_init,
+       /* reserved 1*/ NULL,
+        /* reserved 2 */ NULL,
+        (GtkClassInitFunc) NULL,
+      };
+
+      icon_list_type = gtk_type_unique (GTK_TYPE_FIXED, &icon_list_info);
+    }
+  return icon_list_type;
+}
+
+static GtkIconListItem*
+gtk_icon_list_item_copy (const GtkIconListItem *item)
+{
+  GtkIconListItem *new_item;
+
+  g_return_val_if_fail (item != NULL, NULL);
+
+  new_item = g_new (GtkIconListItem, 1);
+
+  *new_item = *item;
+
+  return new_item;
+}
+
+static void
+gtk_icon_list_item_free (GtkIconListItem *item)
+{
+  g_return_if_fail (item != NULL);
+
+  g_free (item);
+}
+
+
+GType
+gtk_icon_list_item_get_type (void)
+{
+  static GType icon_list_item_type;
+
+  if(!icon_list_item_type)
+  {
+    icon_list_item_type = g_boxed_type_register_static("GtkIconListItem", (GBoxedCopyFunc)gtk_icon_list_item_copy, (GBoxedFreeFunc)gtk_icon_list_item_free);
+  }
+  return icon_list_item_type;
+}
+
+static void
+gtk_icon_list_class_init (GtkIconListClass *klass)
+{
+  GtkObjectClass *object_class;
+  GObjectClass *gobject_class;
+  GtkWidgetClass *widget_class;
+
+  parent_class = gtk_type_class (GTK_TYPE_FIXED);
+
+  object_class = (GtkObjectClass *) klass;
+  gobject_class = (GObjectClass *) klass;
+  widget_class = (GtkWidgetClass *) klass;
+
+  object_class->destroy = gtk_icon_list_destroy;
+  gobject_class->finalize = gtk_icon_list_finalize;
+
+  widget_class->realize = gtk_icon_list_realize;
+
+  widget_class->size_allocate = gtk_icon_list_size_allocate;
+
+  widget_class->expose_event = gtk_icon_list_expose;
+  widget_class->button_press_event = gtk_icon_list_button_press;
+
+  signals[SELECT_ICON] =
+      gtk_signal_new("select_icon",
+                    GTK_RUN_LAST,
+                    GTK_CLASS_TYPE(object_class),
+                    GTK_SIGNAL_OFFSET(GtkIconListClass, select_icon),
+                     gtkextra_BOOLEAN__BOXED_BOXED,
+                    GTK_TYPE_BOOL, 2,
+                    GTK_TYPE_ICON_LIST_ITEM, GDK_TYPE_EVENT); 
+
+  signals[UNSELECT_ICON] =
+      gtk_signal_new("unselect_icon",
+                    GTK_RUN_FIRST,
+                    GTK_CLASS_TYPE(object_class),
+                    GTK_SIGNAL_OFFSET(GtkIconListClass, unselect_icon),
+                     gtkextra_VOID__BOXED_BOXED,
+                    GTK_TYPE_NONE, 2,
+                    GTK_TYPE_ICON_LIST_ITEM, GDK_TYPE_EVENT); 
+
+  signals[TEXT_CHANGED] =
+      gtk_signal_new("text_changed",
+                    GTK_RUN_LAST,
+                    GTK_CLASS_TYPE(object_class),
+                    GTK_SIGNAL_OFFSET(GtkIconListClass, text_changed),
+                     gtkextra_BOOLEAN__BOXED_STRING,
+                    GTK_TYPE_BOOL, 2,
+                    GTK_TYPE_ICON_LIST_ITEM, GTK_TYPE_STRING); 
+
+  signals[ACTIVATE_ICON] =
+      gtk_signal_new("activate_icon",
+                    GTK_RUN_LAST,
+                    GTK_CLASS_TYPE(object_class),
+                    GTK_SIGNAL_OFFSET(GtkIconListClass, activate_icon),
+                     gtkextra_BOOLEAN__BOXED,
+                    GTK_TYPE_BOOL, 1,
+                    GTK_TYPE_ICON_LIST_ITEM); 
+
+  signals[DEACTIVATE_ICON] =
+      gtk_signal_new("deactivate_icon",
+                    GTK_RUN_LAST,
+                    GTK_CLASS_TYPE(object_class),
+                    GTK_SIGNAL_OFFSET(GtkIconListClass, deactivate_icon),
+                     gtkextra_BOOLEAN__BOXED,
+                    GTK_TYPE_BOOL, 1,
+                    GTK_TYPE_ICON_LIST_ITEM); 
+
+  signals[CLICK_EVENT] =
+      gtk_signal_new("click_event",
+                    GTK_RUN_LAST,
+                    GTK_CLASS_TYPE(object_class),
+                    GTK_SIGNAL_OFFSET(GtkIconListClass, click_event),
+                     gtkextra_VOID__BOXED,
+                    GTK_TYPE_NONE, 1,
+                    GDK_TYPE_EVENT); 
+}
+
+void
+gtk_icon_list_freeze(GtkIconList *iconlist)
+{
+ iconlist->freeze_count++;
+}
+
+void
+gtk_icon_list_thaw(GtkIconList *iconlist)
+{
+ if(iconlist->freeze_count == 0) return;
+ iconlist->freeze_count--;
+ if(iconlist->freeze_count == 0)
+               reorder_icons(iconlist);
+}
+
+void
+gtk_icon_list_update(GtkIconList *iconlist)
+{
+  reorder_icons(iconlist);
+}
+
+static void
+reorder_icons(GtkIconList *iconlist)
+{
+  GtkWidget *widget;
+  GtkIconListItem *item;
+  GtkRequisition req;
+  GList *icons;
+  gint hspace = 0;
+  gint vspace = 0;
+  gint x = 0;
+  gint y = 0;
+  gint old_width, old_height;
+
+  widget = GTK_WIDGET(iconlist);
+
+  if(iconlist->freeze_count > 0) return;
+
+/*
+  gdk_threads_enter();
+*/
+
+  old_width = widget->allocation.width;
+  old_height = widget->allocation.height;
+  if(GTK_WIDGET_REALIZED(widget)){
+    if(GTK_IS_VIEWPORT(widget->parent))
+     gdk_window_get_size(GTK_VIEWPORT(widget->parent)->view_window, &old_width, &old_height);
+  }
+
+  y = iconlist->row_spacing;
+  x = iconlist->col_spacing;
+
+  icons = iconlist->icons;
+  while(icons){
+    item = (GtkIconListItem *) icons->data;
+
+    gtk_icon_list_move(iconlist, item, x, y);
+
+    item_size_request(iconlist, item, &req);
+    vspace = req.height + iconlist->row_spacing;
+    hspace = req.width + iconlist->col_spacing;
+
+    switch(iconlist->mode){
+      case GTK_ICON_LIST_TEXT_RIGHT:
+        y += vspace;
+        if(y + vspace >= old_height - DEFAULT_COL_SPACING){
+                x += hspace;
+                y = iconlist->row_spacing;
+        }
+        break;
+      case GTK_ICON_LIST_TEXT_BELOW:
+      case GTK_ICON_LIST_ICON:
+      default:
+        x += hspace;
+        if(x + hspace >= old_width - DEFAULT_COL_SPACING){
+                x = iconlist->col_spacing;
+                y += vspace;
+        }
+        break;
+    }
+
+    icons = icons->next;
+
+   }
+/*
+  gdk_threads_leave();
+*/
+}
+
+
+static void
+gtk_icon_list_move(GtkIconList *iconlist, GtkIconListItem *icon, 
+                   guint x, guint y)
+{
+  GtkRequisition req1, req2;
+  GtkRequisition req;
+  GtkAllocation a;
+  const gchar *text;
+  gint old_width, old_height, width, height;
+  gint old_x, old_y;
+  gint size;
+
+  old_x = icon->x;
+  old_y = icon->y;
+
+  icon->x = x;
+  icon->y = y;
+
+  if(x == old_x && y == old_y) return;
+
+  item_size_request(iconlist, icon, &req);
+  req1 = icon->pixmap->requisition;
+  req2 = icon->entry->requisition;
+  req2.width = iconlist->text_space;
+
+  req1.width += 2*iconlist->icon_border;
+  req1.height += 2*iconlist->icon_border;
+  if(iconlist->mode == GTK_ICON_LIST_TEXT_BELOW){
+     req1.width = MAX(req1.width, req.width);
+  }
+
+  if(iconlist->mode == GTK_ICON_LIST_ICON) 
+          req2.width = req2.height = 0;
+
+  old_width = width = GTK_WIDGET(iconlist)->allocation.width;
+  old_height = height = GTK_WIDGET(iconlist)->allocation.height;
+
+  gtk_fixed_move(GTK_FIXED(iconlist), icon->pixmap, 
+                 x + req1.width/2 - icon->pixmap->requisition.width/2, 
+                 y + iconlist->icon_border);
+
+  icon->pixmap->allocation.x += (x - old_x);
+  icon->pixmap->allocation.y += (y - old_y);
+  icon->entry->allocation.x += (x - old_x);
+  icon->entry->allocation.y += (y - old_y);
+  icon->entry->allocation.width = req2.width;
+
+
+  switch(iconlist->mode){
+   case GTK_ICON_LIST_TEXT_BELOW:
+        text = gtk_entry_get_text(GTK_ENTRY(icon->entry));
+        size = STRING_WIDTH(icon->entry, icon->entry->style->font_desc, text);
+        size = MIN(size, req2.width);
+
+       gtk_fixed_move(GTK_FIXED(iconlist), icon->entry, 
+                       x - req2.width/2 + req1.width/2, 
+                        y + req1.height + iconlist->icon_border);
+
+        if(y + req.height > height) 
+           height += req.height;
+        break;
+   case GTK_ICON_LIST_TEXT_RIGHT:
+       gtk_fixed_move(GTK_FIXED(iconlist), icon->entry, 
+                       x + req1.width + iconlist->icon_border, 
+                        y + req1.height/2 - req2.height/2); 
+
+        if(x + req.width > width) 
+            width += req.width;
+       break;
+   case GTK_ICON_LIST_ICON:
+   default: ;
+  }
+
+  a = icon->entry->allocation;
+
+  gtk_widget_size_allocate(icon->pixmap, &icon->pixmap->allocation);
+  if(icon->entry){
+    gtk_widget_size_allocate(icon->entry, &a);
+    gtk_widget_draw(icon->entry, NULL);
+  }
+
+}
+
+
+static void
+gtk_icon_list_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
+{
+  GtkAllocation *old = gtk_object_get_data(GTK_OBJECT(widget),"viewport");
+  GTK_WIDGET_CLASS(parent_class)->size_allocate(widget, allocation);
+  if(GTK_WIDGET_REALIZED(widget) && old){
+    gint new_width, new_height;
+    gdk_window_get_size(GTK_VIEWPORT(widget->parent)->view_window, &new_width, &new_height);
+    if(old->width != new_width || old->height != new_height)
+      reorder_icons(GTK_ICON_LIST(widget));
+    old->width = new_width; 
+    old->height = new_height; 
+  }
+}
+
+
+static void
+gtk_icon_list_realize(GtkWidget *widget)
+{
+  GList *icons;
+  GtkIconList *iconlist;
+  GtkIconListItem *item;
+  GtkStyle *style;
+  
+  GTK_WIDGET_CLASS(parent_class)->realize (widget);
+
+  iconlist = GTK_ICON_LIST(widget);
+
+  style = gtk_style_copy(widget->style);
+
+  style->bg[0] = iconlist->background;
+  gtk_widget_set_style(widget, style);
+  gtk_style_set_background(style, widget->window, GTK_STATE_NORMAL);
+  gtk_style_set_background(style, widget->window, GTK_STATE_ACTIVE);
+
+  icons = iconlist->icons;
+  while(icons){
+    item = (GtkIconListItem *) icons->data;
+    gtk_widget_draw(item->pixmap, NULL);
+    if(iconlist->mode != GTK_ICON_LIST_ICON){
+      GtkStyle *style;
+
+      gtk_widget_realize(item->entry);
+
+      style = gtk_style_copy(item->entry->style);
+      style->bg[GTK_STATE_ACTIVE] = iconlist->background;
+      style->bg[GTK_STATE_NORMAL] = iconlist->background;
+      gtk_widget_set_style(item->entry, style);
+      gtk_widget_show(item->entry);
+    }
+    if(item->entry) gtk_widget_draw(item->entry, NULL);
+    icons = icons->next;
+  }
+
+/*  
+  if(GTK_IS_VIEWPORT(widget->parent) && GTK_WIDGET_REALIZED(widget->parent)){
+    GtkAllocation *allocation = gtk_object_get_data(GTK_OBJECT(widget),"viewport");
+    gdk_window_get_size(GTK_VIEWPORT(widget->parent)->view_window, &allocation->width, &allocation->height);
+  }
+  reorder_icons(iconlist);
+*/
+}
+
+
+static void
+gtk_icon_list_init (GtkIconList *icon_list)
+{
+  GtkWidget *widget;
+  widget = GTK_WIDGET(icon_list);
+
+  gtk_widget_ensure_style(widget);
+  gdk_color_black(gtk_widget_get_colormap(widget), &widget->style->black);
+  gdk_color_white(gtk_widget_get_colormap(widget), &widget->style->white);
+
+  gtk_fixed_set_has_window(GTK_FIXED(widget), TRUE);
+
+  gtk_widget_set_events (widget, gtk_widget_get_events(widget)|
+                         EVENTS_MASK);
+
+  icon_list->selection = NULL;
+  icon_list->is_editable = TRUE;
+
+  icon_list->num_icons = 0;
+  icon_list->background = widget->style->white;
+
+  icon_list->row_spacing = DEFAULT_ROW_SPACING;
+  icon_list->col_spacing = DEFAULT_COL_SPACING;
+  icon_list->text_space = DEFAULT_TEXT_SPACE;
+  icon_list->icon_border = DEFAULT_ICON_BORDER;
+
+  icon_list->active_icon = NULL;
+  icon_list->compare_func = (GCompareFunc)sort_list;
+}
+
+static gint 
+sort_list(gpointer a, gpointer b)
+{
+  GtkIconListItem *itema;
+  GtkIconListItem *itemb;
+
+  itema = (GtkIconListItem *)a;
+  itemb = (GtkIconListItem *)b;
+
+  return (strcmp(itema->label, itemb->label));
+}
+
+static gboolean
+gtk_icon_list_expose (GtkWidget *widget, GdkEventExpose *event)
+{
+  GtkIconList *icon_list;
+
+  icon_list = GTK_ICON_LIST(widget);
+
+  if(!GTK_WIDGET_DRAWABLE(widget)) return FALSE;
+
+  gtk_paint_flat_box (widget->style, widget->window, GTK_STATE_NORMAL,
+                      GTK_SHADOW_NONE, &event->area, widget, "base", 0, 0, -1, -1);
+
+  GTK_WIDGET_CLASS(parent_class)->expose_event(widget, event);
+
+  if(icon_list->active_icon && icon_list->active_icon->entry){
+       gdk_draw_rectangle(widget->window,
+                           widget->style->black_gc,
+                           FALSE,
+                           icon_list->active_icon->entry->allocation.x-2,
+                           icon_list->active_icon->entry->allocation.y-2,
+                           icon_list->active_icon->entry->allocation.width+4,
+                           icon_list->active_icon->entry->allocation.height+4);
+  }
+
+  return FALSE;
+}
+
+static gint
+gtk_icon_list_button_press(GtkWidget *widget, GdkEventButton *event)
+{
+  GtkIconList *iconlist;
+  GtkIconListItem *item;
+  gint x, y;
+  GtkAllocation *allocation;
+
+  if(!GTK_IS_ICON_LIST(widget)) return FALSE;
+
+  iconlist = GTK_ICON_LIST(widget);
+
+
+  gtk_widget_get_pointer(widget, &x, &y);
+  item = gtk_icon_list_get_icon_at(iconlist, x , y );
+
+  if(!item){ 
+     gtk_signal_emit(GTK_OBJECT(iconlist), signals[CLICK_EVENT],
+                     event);
+     return FALSE;
+  }
+
+  if(item->entry){
+    allocation = &item->entry->allocation;
+    if(x >= allocation->x &&
+       x <= allocation->x + allocation->width &&
+       y >= allocation->y &&
+       y <= allocation->y + allocation->height) return FALSE;
+  }
+
+  if(item)
+   switch(iconlist->selection_mode){
+     case GTK_SELECTION_SINGLE:
+     case GTK_SELECTION_BROWSE:
+        unselect_all(iconlist);
+     case GTK_SELECTION_MULTIPLE:
+        select_icon(iconlist, item, (GdkEvent *)event);
+     case GTK_SELECTION_NONE:
+        break;
+   }
+
+  return FALSE;
+}
+
+
+static gint 
+deactivate_entry(GtkIconList *iconlist)
+{
+  GdkGC *gc;
+  GtkEntry *entry;
+  gboolean veto = TRUE;
+
+  if(iconlist->active_icon) {
+     _gtkextra_signal_emit(GTK_OBJECT(iconlist), signals[DEACTIVATE_ICON], 
+                     iconlist->active_icon, &veto);
+     if(!veto) return FALSE;
+
+     entry = GTK_ENTRY(iconlist->active_icon->entry);
+
+     if(!entry || !GTK_WIDGET_REALIZED(entry)) return TRUE;
+     gtk_entry_set_editable(entry, FALSE);    
+     gtk_entry_select_region(entry, 0, 0);
+     gtk_item_entry_set_cursor_visible(GTK_ITEM_ENTRY(entry), FALSE);
+
+     switch(iconlist->mode){
+       case GTK_ICON_LIST_TEXT_RIGHT:
+         gtk_item_entry_set_text(GTK_ITEM_ENTRY(entry), 
+                                 iconlist->active_icon->entry_label,
+                                 GTK_JUSTIFY_LEFT);
+         break;
+       case GTK_ICON_LIST_TEXT_BELOW:
+         gtk_item_entry_set_text(GTK_ITEM_ENTRY(entry), 
+                                 iconlist->active_icon->entry_label,
+                                 GTK_JUSTIFY_CENTER);
+         break;
+       case GTK_ICON_LIST_ICON:
+       default:
+         break;
+     }
+
+     if(GTK_WIDGET_REALIZED(iconlist->active_icon->entry)){
+       gc = gdk_gc_new(GTK_WIDGET(iconlist)->window);
+       gdk_gc_set_foreground(gc, &iconlist->background);
+       gdk_draw_rectangle(GTK_WIDGET(iconlist)->window,
+                          gc,
+                          FALSE,
+                          GTK_WIDGET(entry)->allocation.x-2,
+                          GTK_WIDGET(entry)->allocation.y-2,
+                          GTK_WIDGET(entry)->allocation.width+4,
+                          GTK_WIDGET(entry)->allocation.height+4);
+       gdk_gc_unref(gc);
+     }
+
+     iconlist->active_icon->state = GTK_STATE_NORMAL;
+     iconlist->active_icon = NULL;
+  }
+
+  return TRUE;
+}
+
+
+static void
+select_icon(GtkIconList *iconlist, GtkIconListItem *item, GdkEvent *event)
+{
+  gboolean veto = TRUE;
+
+  if(item == NULL) return;
+
+  _gtkextra_signal_emit(GTK_OBJECT(iconlist), signals[SELECT_ICON], item, 
+                  event, &veto);                        
+
+  if(!veto) return;
+
+  if(iconlist->mode != GTK_ICON_LIST_ICON){ 
+    if(!deactivate_entry(iconlist)) return;
+
+    if(item->entry && GTK_WIDGET_REALIZED(item->entry)){
+      GtkStyle *style = gtk_style_copy(item->entry->style);
+      style->bg[GTK_STATE_ACTIVE] = style->base[GTK_STATE_SELECTED];
+      style->bg[GTK_STATE_NORMAL] = style->base[GTK_STATE_SELECTED];
+      style->text[GTK_STATE_ACTIVE] = style->text[GTK_STATE_SELECTED];
+      style->text[GTK_STATE_NORMAL] = style->text[GTK_STATE_SELECTED];
+      gtk_widget_set_style(item->entry, style);
+      gtk_style_unref(style);
+
+      switch(iconlist->mode){
+        case GTK_ICON_LIST_TEXT_RIGHT:
+          gtk_item_entry_set_text(GTK_ITEM_ENTRY(item->entry), 
+                                  item->label,
+                                  GTK_JUSTIFY_LEFT);
+          break;
+        case GTK_ICON_LIST_TEXT_BELOW:
+          gtk_item_entry_set_text(GTK_ITEM_ENTRY(item->entry), 
+                                  item->label,
+                                  GTK_JUSTIFY_CENTER);
+          break;
+        case GTK_ICON_LIST_ICON:
+        default:
+          break;
+      }
+    }
+  }
+  if(item->state == GTK_STATE_SELECTED) return;
+  iconlist->selection = g_list_append(iconlist->selection, item);
+
+  item->state = GTK_STATE_SELECTED;  
+  if(item->entry) gtk_widget_grab_focus(item->entry);
+}
+
+static void
+unselect_icon(GtkIconList *iconlist, GtkIconListItem *item, GdkEvent *event)
+{
+  GList *selection;
+  GtkIconListItem *icon;
+
+  if(!item) return;
+
+  if(item->state == GTK_STATE_NORMAL) return;
+
+  selection = iconlist->selection;
+  while(selection){
+    icon = (GtkIconListItem *)selection->data; 
+    if(item == icon) break;
+    selection = selection->next;
+  }
+
+  if(selection){
+       iconlist->selection = g_list_remove_link(iconlist->selection, selection);
+  }
+  item->state = GTK_STATE_NORMAL;
+
+  if(iconlist->mode != GTK_ICON_LIST_ICON){
+   if(item->entry && GTK_WIDGET_REALIZED(item->entry)){
+     GtkStyle *style = gtk_style_copy(item->entry->style);
+
+     style->bg[GTK_STATE_ACTIVE] = iconlist->background;
+     style->bg[GTK_STATE_NORMAL] = iconlist->background;
+     style->text[GTK_STATE_ACTIVE] = GTK_WIDGET(iconlist)->style->text[GTK_STATE_ACTIVE];
+     style->text[GTK_STATE_NORMAL] = GTK_WIDGET(iconlist)->style->text[GTK_STATE_NORMAL];
+     gtk_widget_set_style(item->entry, style);
+     gtk_style_unref(style);
+
+     gtk_entry_select_region(GTK_ENTRY(item->entry), 0, 0);
+     gtk_entry_set_text(GTK_ENTRY(item->entry), item->entry_label);
+     gtk_entry_set_editable(GTK_ENTRY(item->entry), FALSE);
+     gtk_widget_draw(item->entry, NULL);
+
+   }
+  }
+
+  gtk_signal_emit(GTK_OBJECT(iconlist), signals[UNSELECT_ICON], item, event);                        
+}
+
+static void
+unselect_all(GtkIconList *iconlist)
+{
+  GList *selection;
+  GtkIconListItem *item;
+
+  selection = iconlist->selection;
+  while(selection){
+    item = (GtkIconListItem *)selection->data;
+    unselect_icon(iconlist, item, NULL);
+    selection = iconlist->selection;
+  }
+
+  g_list_free(iconlist->selection);
+  iconlist->selection = NULL;
+}
+
+GtkWidget*
+gtk_icon_list_new (guint icon_width, GtkIconListMode mode)
+{
+  GtkIconList *icon_list;
+  GtkAllocation *allocation;
+
+  icon_list = gtk_type_new (gtk_icon_list_get_type ());
+
+  gtk_icon_list_construct(icon_list, icon_width, mode);
+  allocation = g_new0(GtkAllocation, 1);
+  gtk_object_set_data(GTK_OBJECT(icon_list), "viewport", allocation);
+
+  return GTK_WIDGET (icon_list);
+}
+
+void
+gtk_icon_list_construct (GtkIconList *icon_list, guint icon_width, GtkIconListMode mode)
+{
+  icon_list->icon_width = icon_width;
+  icon_list->mode = mode;
+  icon_list->icons = NULL;
+  icon_list->selection = NULL;
+  icon_list->selection_mode = GTK_SELECTION_SINGLE;
+}
+
+void
+gtk_icon_list_set_mode (GtkIconList *iconlist, GtkIconListMode mode)
+{
+  GList *icons;
+
+  iconlist->mode = mode;
+
+  icons = iconlist->icons;
+  while(icons){
+    GtkIconListItem *item;
+
+    item = (GtkIconListItem *)icons->data;
+
+    switch(mode){
+      case GTK_ICON_LIST_TEXT_RIGHT:
+        gtk_item_entry_set_justification(GTK_ITEM_ENTRY(item->entry), 
+                                         GTK_JUSTIFY_LEFT);
+        break;
+      case GTK_ICON_LIST_TEXT_BELOW:
+        gtk_item_entry_set_justification(GTK_ITEM_ENTRY(item->entry), 
+                                         GTK_JUSTIFY_CENTER);
+        break;
+      case GTK_ICON_LIST_ICON:
+      default:
+        break;
+    }
+    
+    icons = icons->next;
+  }
+  reorder_icons(iconlist); 
+}
+
+GtkIconListMode
+gtk_icon_list_get_mode (GtkIconList *iconlist)
+{
+  return(iconlist->mode);
+}
+
+void
+gtk_icon_list_set_text_space (GtkIconList *iconlist, guint text_space)
+{
+  GList *icons;
+
+  iconlist->text_space = text_space;
+
+  icons = iconlist->icons;
+  while(icons){
+    GtkIconListItem *item;
+
+    item = (GtkIconListItem *)icons->data;
+    
+    if(item->entry) GTK_ITEM_ENTRY(item->entry)->text_max_size = text_space;
+    
+    icons = icons->next;
+  }
+  reorder_icons(iconlist); 
+}
+
+
+static void
+gtk_icon_list_destroy (GtkObject *object)
+{
+  GtkIconList *icon_list;
+
+  g_return_if_fail (object != NULL);
+  g_return_if_fail (GTK_IS_ICON_LIST (object));
+
+  icon_list = GTK_ICON_LIST (object);
+
+  gtk_icon_list_clear(icon_list);
+
+  if (GTK_OBJECT_CLASS (parent_class)->destroy)
+    (*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static void
+gtk_icon_list_finalize (GObject *object)
+{
+  GtkIconList *icon_list;
+  GtkAllocation *allocation;
+
+  icon_list = GTK_ICON_LIST (object);
+
+  allocation = gtk_object_get_data(GTK_OBJECT(icon_list), "viewport");
+  if(allocation) g_free(allocation);
+  gtk_object_set_data(GTK_OBJECT(icon_list), "viewport", NULL);
+
+  if (G_OBJECT_CLASS (parent_class)->finalize)
+    (*G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+void
+gtk_icon_list_set_background (GtkIconList *iconlist, GdkColor *color)
+{
+  GtkWidget *widget;
+  GtkStyle *style;
+  
+  g_return_if_fail (iconlist != NULL);
+  g_return_if_fail (GTK_IS_ICON_LIST (iconlist));
+
+  widget = GTK_WIDGET(iconlist);
+
+  iconlist->background = *color;
+
+  style = gtk_style_copy(widget->style);
+  style->bg[0] = iconlist->background;
+
+  gtk_widget_set_style(widget, style);
+  if(widget->window) gdk_window_set_background(widget->window, color);
+  gtk_style_unref(style);
+
+}
+
+static gint
+entry_changed (GtkWidget *widget, gpointer data)
+{
+  GtkIconList *iconlist;
+  GtkIconListItem *item;
+  gboolean veto = TRUE;
+  const gchar *text;
+
+  iconlist = GTK_ICON_LIST(data);
+  item = get_icon_from_entry(iconlist, widget);
+  text = gtk_entry_get_text(GTK_ENTRY(widget));
+
+  _gtkextra_signal_emit(GTK_OBJECT(data), signals[TEXT_CHANGED],
+                  item, text, &veto);
+
+  if(!veto) return veto; 
+
+  if(item->entry && gtk_editable_get_editable(GTK_EDITABLE(item->entry))){
+    if(item->label) g_free(item->label);
+    if(text) item->label = g_strdup(text); 
+    if(item->entry_label) g_free(item->entry_label);
+    set_labels(iconlist, item, text); 
+  }
+
+  return veto;
+} 
+                  
+static gint
+entry_in (GtkWidget *widget, GdkEventButton *event, gpointer data)
+{
+  GtkIconList *iconlist;
+  GtkIconListItem *item;
+  gboolean veto = TRUE;
+
+  if(!GTK_IS_ENTRY(widget)) return FALSE;
+  iconlist = GTK_ICON_LIST(data);
+
+  item = get_icon_from_entry(iconlist, widget);
+  if(iconlist->active_icon && iconlist->active_icon->entry == widget) 
+          return FALSE;
+
+  _gtkextra_signal_emit(GTK_OBJECT(iconlist), signals[ACTIVATE_ICON], &item, &veto);
+
+  if(!veto) return FALSE; 
+  if(!deactivate_entry(iconlist)) return FALSE;
+
+  if(item->state == GTK_STATE_SELECTED){
+   if(iconlist->is_editable && !gtk_editable_get_editable(GTK_EDITABLE(widget))){
+     unselect_all(iconlist);
+
+     gtk_entry_set_editable(GTK_ENTRY(widget), TRUE);
+     gtk_item_entry_set_cursor_visible(GTK_ITEM_ENTRY(widget), TRUE);
+     if(item->label) gtk_entry_set_text(GTK_ENTRY(widget), item->label);
+     iconlist->active_icon = item;
+     item->state = GTK_STATE_NORMAL;
+
+     if(GTK_WIDGET_DRAWABLE(widget)) 
+       gdk_draw_rectangle(GTK_WIDGET(iconlist)->window,
+                          widget->style->black_gc,
+                          FALSE,
+                          iconlist->active_icon->entry->allocation.x-2,
+                          iconlist->active_icon->entry->allocation.y-2,
+                          iconlist->active_icon->entry->allocation.width+4,
+                          iconlist->active_icon->entry->allocation.height+4);
+   }else{
+     gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "button_press_event"); 
+     if(iconlist->selection_mode == GTK_SELECTION_SINGLE ||
+        iconlist->selection_mode == GTK_SELECTION_BROWSE) 
+          unselect_all(iconlist);
+     select_icon(iconlist, item, (GdkEvent *)event);
+   }
+  }else{
+     if(iconlist->selection_mode == GTK_SELECTION_SINGLE ||
+        iconlist->selection_mode == GTK_SELECTION_BROWSE) 
+          unselect_all(iconlist);
+     select_icon(iconlist, item, (GdkEvent *)event);
+  }
+
+  return FALSE;
+}
+
+GtkIconListItem *
+gtk_icon_list_get_active_icon(GtkIconList *iconlist)
+{
+  return iconlist->active_icon;
+}
+
+static GtkIconListItem *
+get_icon_from_entry(GtkIconList *iconlist, GtkWidget *widget)
+{
+  GList *icons;
+  GtkIconListItem *item;
+
+  icons = iconlist->icons;
+  while(icons){
+    item = (GtkIconListItem *) icons->data;
+    if(widget == item->entry) return item;
+    icons = icons->next;
+  }
+
+  return NULL;
+}
+
+GtkIconListItem *
+gtk_icon_list_get_icon_at(GtkIconList *iconlist, gint x, gint y)
+{
+  GList *icons;
+  GtkIconListItem *item;
+  GtkRequisition req;
+
+  icons = iconlist->icons;
+  while(icons){
+    item = (GtkIconListItem *) icons->data;
+    item_size_request(iconlist, item, &req);
+    if(x >= item->x && x <= item->x + req.width &&
+       y >= item->y && y <= item->y + req.height) return item;
+    icons = icons->next;
+  }
+
+  return NULL;
+}
+
+GtkIconListItem *
+gtk_icon_list_add (GtkIconList *iconlist, 
+                   const gchar *file,
+                   const gchar *label,
+                   gpointer link)
+{
+  GtkIconListItem *item;
+  GdkColormap *colormap;
+  GdkPixmap *pixmap;
+  GdkBitmap *mask;
+
+  colormap = gdk_colormap_get_system();
+  pixmap = gdk_pixmap_colormap_create_from_xpm(NULL, colormap, &mask, NULL, 
+                                               file);
+  item = gtk_icon_list_real_add(iconlist, pixmap, mask, label, link);
+  return item;
+}
+
+GtkIconListItem *
+gtk_icon_list_add_from_data (GtkIconList *iconlist, 
+                             gchar **data,
+                             const gchar *label,
+                             gpointer link)
+{
+  GtkIconListItem *item;
+  GdkColormap *colormap;
+  GdkPixmap *pixmap;
+  GdkBitmap *mask;
+
+  colormap = gdk_colormap_get_system();
+  pixmap = gdk_pixmap_colormap_create_from_xpm_d(NULL, colormap, &mask, NULL, 
+                                                 data);
+  item = gtk_icon_list_real_add(iconlist, pixmap, mask, label, link);
+  return item;
+}
+
+GtkIconListItem *
+gtk_icon_list_add_from_pixmap (GtkIconList *iconlist, 
+                               GdkPixmap *pixmap,
+                               GdkBitmap *mask,
+                               const gchar *label,
+                               gpointer link)
+{
+  GtkIconListItem *item;
+
+  gdk_pixmap_ref(pixmap);
+  if(mask) gdk_bitmap_ref(mask);
+  item = gtk_icon_list_real_add(iconlist, pixmap, mask, label, link);
+  return item;
+}
+
+
+static GtkIconListItem*
+gtk_icon_list_real_add (GtkIconList *iconlist, 
+                        GdkPixmap *pixmap,
+                        GdkBitmap *mask,
+                        const gchar *label,
+                        gpointer data)
+{
+  GtkIconListItem *item;
+  GtkRequisition requisition;
+  gint hspace = 0;
+  gint vspace = 0;
+  gint x = 0;
+  gint y = 0;
+  gint width, height;
+
+  width = GTK_WIDGET(iconlist)->allocation.width;
+  height = GTK_WIDGET(iconlist)->allocation.height;
+
+  if(iconlist->num_icons > 0){
+    item = gtk_icon_list_get_nth(iconlist, iconlist->num_icons-1);
+    x = item->x;
+    y = item->y;
+    item_size_request(iconlist, item, &requisition);
+    vspace = requisition.height + iconlist->row_spacing;
+    hspace = requisition.width + iconlist->col_spacing;
+
+    switch(iconlist->mode){
+      case GTK_ICON_LIST_TEXT_RIGHT:
+        y += vspace;
+        if(y >= height){
+                x += hspace;
+                y = iconlist->row_spacing;
+        }
+        break;
+      case GTK_ICON_LIST_TEXT_BELOW:
+      case GTK_ICON_LIST_ICON:
+      default:
+        x += hspace;
+        if(x >= width){
+                x = iconlist->col_spacing;
+                y += vspace;
+        }
+        break;
+    }
+  } else {
+    y = iconlist->row_spacing;
+    x = iconlist->col_spacing;
+  }
+
+  item = gtk_icon_list_put(iconlist, x, y, pixmap, mask, label, data);
+  return item;
+}
+
+static GtkIconListItem *
+gtk_icon_list_put (GtkIconList *iconlist, 
+                   guint x, guint y, 
+                   GdkPixmap *pixmap,
+                   GdkBitmap *mask,
+                   const gchar *label,
+                   gpointer data)
+{
+  GtkIconListItem *icon;
+  GtkIconListItem *active_icon;
+  GtkWidget *widget;
+  GtkRequisition req, req1, req2;
+  gint text_width;
+  gint width, height, old_width, old_height;
+  GtkAllocation alloc;
+
+  widget = GTK_WIDGET(iconlist);
+
+  old_width = width = GTK_WIDGET(iconlist)->allocation.width;
+  old_height = height = GTK_WIDGET(iconlist)->allocation.height;
+
+  active_icon = iconlist->active_icon;
+  gtk_icon_list_set_active_icon(iconlist, NULL);
+
+  icon = g_new(GtkIconListItem, 1);
+  icon->x = x;
+  icon->y = y;
+  icon->state = GTK_STATE_NORMAL;
+  icon->label = NULL;
+  icon->entry_label = NULL;
+  if(label) icon->label = g_strdup(label);
+  icon->entry = gtk_item_entry_new();
+  icon->pixmap = gtk_pixmap_new(pixmap, mask);
+  icon->link = data;
+
+  GTK_ITEM_ENTRY(icon->entry)->text_max_size = iconlist->text_space; 
+  item_size_request(iconlist, icon, &req);
+  req1 = icon->pixmap->requisition;
+  req2 = icon->entry->requisition;
+  req2.width = iconlist->text_space; 
+
+  req1.width += 2*iconlist->icon_border;
+  req1.height += 2*iconlist->icon_border;
+  if(iconlist->mode == GTK_ICON_LIST_TEXT_BELOW){
+     req1.width = MAX(req1.width, req.width);
+  }
+
+  if(iconlist->mode == GTK_ICON_LIST_ICON) 
+          req2.width = req2.height = 0;
+  else
+          set_labels(iconlist, icon, label);
+
+  text_width = 0;
+  if(label) text_width = STRING_WIDTH(icon->entry, icon->entry->style->font_desc, label);
+
+  gtk_fixed_put(GTK_FIXED(iconlist), icon->pixmap, 
+                 x + req1.width/2 - icon->pixmap->requisition.width/2, 
+                 y + iconlist->icon_border);
+
+  alloc.x = x + req1.width/2 - icon->pixmap->requisition.width/2; 
+  alloc.y = y + iconlist->icon_border; 
+  alloc.width =  req1.width;
+  alloc.height =  req1.height;
+  gtk_widget_size_allocate(icon->pixmap, &alloc);
+
+  switch(iconlist->mode){
+   case GTK_ICON_LIST_TEXT_BELOW:
+        gtk_item_entry_set_text(GTK_ITEM_ENTRY(icon->entry), icon->entry_label, 
+                                GTK_JUSTIFY_CENTER);
+       gtk_fixed_put(GTK_FIXED(iconlist), icon->entry, 
+                      x - req2.width/2 + req1.width/2, 
+                       y + req1.height + iconlist->icon_border);
+        alloc.x = x - req2.width/2 + req1.width/2; 
+        alloc.y = y + req1.height + iconlist->icon_border;
+        alloc.width = req2.width;
+        alloc.height = req2.height;
+        gtk_widget_size_allocate(icon->entry, &alloc);
+
+        if(y + req1.height + iconlist->icon_border + req2.height > height) 
+           height += req1.height + iconlist->icon_border + req2.height;
+        break;
+   case GTK_ICON_LIST_TEXT_RIGHT:
+        gtk_item_entry_set_text(GTK_ITEM_ENTRY(icon->entry), icon->entry_label, 
+                                GTK_JUSTIFY_LEFT);
+       gtk_fixed_put(GTK_FIXED(iconlist), icon->entry, 
+                      x + req1.width + iconlist->icon_border, 
+                       y + req1.height/2 - req2.height/2); 
+        alloc.x = x + req1.width + iconlist->icon_border; 
+        alloc.y = y + req1.height/2 - req2.height/2; 
+        alloc.width = req2.width;
+        alloc.height = req2.height;
+        gtk_widget_size_allocate(icon->entry, &alloc);
+
+        if(x + req1.width + iconlist->icon_border + text_width > width) 
+            width += req1.width + iconlist->icon_border + text_width;
+       break;
+   case GTK_ICON_LIST_ICON:
+   default: ;
+  }
+
+  if(GTK_WIDGET_REALIZED(iconlist))
+    if(iconlist->mode != GTK_ICON_LIST_ICON){
+      GtkStyle *style = gtk_style_copy(icon->entry->style);
+      style->bg[GTK_STATE_ACTIVE] = iconlist->background;
+      style->bg[GTK_STATE_NORMAL] = iconlist->background;
+      gtk_widget_set_style(icon->entry, style);
+      gtk_style_unref(style);
+      gtk_widget_show(icon->entry);
+    }
+
+  gtk_widget_show(icon->pixmap);
+
+  if(iconlist->compare_func)
+    iconlist->icons = g_list_insert_sorted(iconlist->icons, icon, iconlist->compare_func);
+  else
+    iconlist->icons = g_list_append(iconlist->icons, icon);
+
+  iconlist->num_icons++;
+
+  if(GTK_WIDGET_REALIZED(iconlist))
+                reorder_icons(iconlist);
+
+  gtk_entry_set_editable(GTK_ENTRY(icon->entry), FALSE);
+
+  gtk_signal_connect(GTK_OBJECT(icon->entry), "key_press_event",
+                    (GtkSignalFunc)icon_key_press, iconlist);
+  gtk_signal_connect(GTK_OBJECT(icon->entry), "button_press_event", 
+                     (GtkSignalFunc)entry_in, iconlist);
+  gtk_signal_connect(GTK_OBJECT(icon->entry), "changed", 
+                     (GtkSignalFunc)entry_changed, iconlist);
+
+  gtk_icon_list_set_active_icon(iconlist, active_icon);
+  return icon;
+}
+
+static void 
+set_labels(GtkIconList *iconlist, GtkIconListItem *icon, const gchar *label)
+{
+  gint text_width;
+  gint point_width;
+  gint max_width;
+  gchar *entry_label = NULL;
+  gint n, space;
+
+  if(!label) return;
+
+  entry_label = (gchar *)g_malloc(strlen(label) + 5);
+  entry_label[0] = label[0];
+  entry_label[1] = '\0';
+
+  text_width = STRING_WIDTH(icon->entry, icon->entry->style->font_desc, label);
+  point_width = STRING_WIDTH(icon->entry, icon->entry->style->font_desc, "X");
+
+  max_width = iconlist->text_space;
+
+  for(n = 0; n < strlen(label); n++){
+     space = strlen(label) - n + 1;
+     if(space > 3 && 
+        STRING_WIDTH(icon->entry, icon->entry->style->font_desc, entry_label) +
+        3 * point_width > max_width) 
+                                                   break;
+     entry_label[n] = label[n];
+     entry_label[n + 1] = '\0';
+  }
+
+
+  if(strlen(entry_label) < strlen(label))
+      sprintf(entry_label,"%s...", entry_label);
+
+  icon->entry_label = g_strdup(entry_label);
+
+  g_free(entry_label);
+}
+
+static gint
+icon_key_press(GtkWidget *widget, GdkEventKey *key, gpointer data)
+{
+  GtkIconList *iconlist;
+
+  iconlist = GTK_ICON_LIST(data);
+  if(key->keyval != GDK_Return) return FALSE;
+
+  if(iconlist->active_icon)
+          select_icon(iconlist, iconlist->active_icon, NULL);
+
+  return FALSE;
+}
+
+static void
+item_size_request(GtkIconList *iconlist, 
+                  GtkIconListItem *item,
+                  GtkRequisition *requisition)
+{
+  GtkRequisition req2;
+
+  gtk_widget_size_request(item->entry, &req2);
+  req2.width = iconlist->text_space;
+
+  gtk_widget_size_request(item->pixmap, requisition);
+  requisition->width = MAX(iconlist->icon_width, requisition->width);
+  requisition->width += 2*iconlist->icon_border;
+  requisition->height += 2*iconlist->icon_border;
+
+  switch(iconlist->mode){
+   case GTK_ICON_LIST_TEXT_BELOW:
+        requisition->height += req2.height;
+        requisition->width = MAX(requisition->width, req2.width);
+        break;
+   case GTK_ICON_LIST_TEXT_RIGHT:
+        requisition->width += req2.width;
+        break;
+   case GTK_ICON_LIST_ICON:
+   default: ;
+  }
+
+}
+
+                 
+void
+gtk_icon_list_set_editable (GtkIconList *iconlist, gboolean editable)
+{
+  GList *icons;
+  GtkIconListItem *item;
+  
+  icons = iconlist->icons;
+  while(icons){
+    item = (GtkIconListItem *) icons->data;
+    gtk_entry_set_editable(GTK_ENTRY(item->entry), editable);
+    icons = icons->next;
+  }
+
+  iconlist->is_editable = editable;
+} 
+
+GtkIconListItem *
+gtk_icon_list_get_nth(GtkIconList *iconlist, guint n)
+{
+  return (GtkIconListItem *)g_list_nth_data(iconlist->icons, n);
+}
+
+gint
+gtk_icon_list_get_index(GtkIconList *iconlist, GtkIconListItem *item)
+{
+  GList *icons;
+  GtkIconListItem *icon;
+  gint n = 0;
+
+  if(item == NULL) return -1;
+  icons = iconlist->icons;
+  while(icons){
+    n++;
+    icon = (GtkIconListItem *) icons->data;
+    if(item == icon) break;
+    icons = icons->next;
+  }
+
+  if(icons) return n;
+
+  return -1;
+}
+
+static void
+remove_from_fixed(GtkIconList *iconlist, GtkWidget *widget)
+{
+  GtkFixed *fixed;
+  GList *children;
+
+  fixed = GTK_FIXED(iconlist);
+  children = fixed->children;
+  while(children){
+    GtkFixedChild *child;
+
+    child = children->data;
+
+    if(child->widget == widget){
+      gtk_widget_unparent(widget);
+      fixed->children = g_list_remove_link (fixed->children, children);
+      g_list_free (children);
+      g_free (child);
+
+      break;
+    }
+
+    children = children->next;
+  }
+}
+
+static void
+pixmap_destroy(GtkPixmap* pixmap)
+{
+  /* release pixmap */
+  if (pixmap){
+    GdkPixmap* pm = NULL;
+    GdkBitmap* bm = NULL;
+
+    gtk_pixmap_get(pixmap, &pm, &bm);
+
+    /* HB: i don't know enough about Gtk+ to call this a design flaw, but it
+     * appears the pixmaps need to be destroyed by hand ...
+     */
+    if (pm) gdk_pixmap_unref(pm);
+    if (bm) gdk_pixmap_unref(bm);
+  }
+}
+  
+void
+gtk_icon_list_remove (GtkIconList *iconlist, GtkIconListItem *item)
+{
+  GList *icons;
+  GtkIconListItem *icon = 0;
+
+  if(item == NULL) return;
+  icons = iconlist->icons;
+  while(icons){
+    icon = (GtkIconListItem *) icons->data;
+    if(item == icon) break;
+    icons = icons->next;
+  }
+
+  if(icons){
+     if(icon->state == GTK_STATE_SELECTED) unselect_icon(iconlist, icon, NULL);
+     if(icon == iconlist->active_icon) deactivate_entry(iconlist);
+     pixmap_destroy(GTK_PIXMAP(icon->pixmap));
+     if(icon->entry && iconlist->mode != GTK_ICON_LIST_ICON){
+       remove_from_fixed(iconlist, icon->entry);
+       icon->entry = NULL;
+     }
+     if(icon->pixmap){
+       remove_from_fixed(iconlist, icon->pixmap);
+       icon->pixmap = NULL;
+     }
+     if(icon->label){
+        g_free(icon->label);
+        icon->label = NULL;
+     }
+     if(icon->entry_label){
+        g_free(icon->entry_label);
+        icon->entry_label = NULL;
+     }
+
+     g_free(icon);
+     iconlist->icons = g_list_remove_link(iconlist->icons, icons);
+     g_list_free_1(icons);
+     iconlist->num_icons--;
+  }
+
+  if(iconlist->num_icons == 0){
+      iconlist->icons = NULL;
+      iconlist->selection = NULL;
+  }
+}
+
+void
+gtk_icon_list_remove_nth (GtkIconList *iconlist, guint n)
+{
+  GtkIconListItem *item;
+
+  item = gtk_icon_list_get_nth(iconlist, n);
+  gtk_icon_list_remove(iconlist, item);
+}
+
+void
+gtk_icon_list_clear(GtkIconList *iconlist)
+{
+  GList *icons;
+  GtkIconListItem *icon;
+
+  if(iconlist->num_icons == 0) return;
+  if(!deactivate_entry(iconlist)) return;
+
+  unselect_all(iconlist);
+
+  icons = iconlist->icons;
+
+  while(icons){
+    icon = (GtkIconListItem *) icons->data;
+    pixmap_destroy(GTK_PIXMAP(icon->pixmap));
+    if(icon->entry && iconlist->mode != GTK_ICON_LIST_ICON){
+      remove_from_fixed(iconlist, icon->entry);
+      icon->entry = NULL;
+    }
+    if(icon->pixmap){
+      gtk_widget_hide(icon->pixmap);
+      remove_from_fixed(iconlist, icon->pixmap);
+      icon->pixmap = NULL;
+    }
+    if(icon->label){
+        g_free(icon->label);
+        icon->label = NULL;
+    }
+    if(icon->entry_label){
+        g_free(icon->entry_label);
+        icon->entry_label = NULL;
+    }
+
+    g_free(icon);
+    icon = NULL;
+
+    iconlist->icons = g_list_remove_link(iconlist->icons, icons);
+    g_list_free_1(icons);
+    icons = iconlist->icons;
+  }
+
+  iconlist->icons = NULL;
+  iconlist->selection = NULL;
+  iconlist->active_icon = NULL;
+  iconlist->num_icons = 0;
+}    
+
+void
+gtk_icon_list_link(GtkIconListItem *item, gpointer data)
+{
+  item->link = data;
+} 
+
+gpointer
+gtk_icon_list_get_link(GtkIconListItem *item)
+{
+  return item->link;
+}
+
+GtkIconListItem *
+gtk_icon_list_get_icon_from_link(GtkIconList *iconlist, gpointer data)
+{
+  GList *icons;
+  GtkIconListItem *item;
+
+  icons = iconlist->icons;
+  while(icons){
+    item = (GtkIconListItem *) icons->data;
+    if(data == item->link) return item;
+    icons = icons->next;
+  }
+
+  return NULL;
+}
+
+GtkWidget *
+gtk_icon_list_get_entry(GtkIconListItem *item)
+{
+  return item->entry;
+}
+
+GtkWidget *
+gtk_icon_list_get_pixmap(GtkIconListItem *item)
+{
+  return item->pixmap;
+}
+
+void
+gtk_icon_list_set_pixmap(GtkIconListItem *item, 
+                        GdkPixmap *pixmap, 
+                        GdkBitmap *mask)
+{
+
+  if(item->pixmap) gtk_widget_destroy(item->pixmap);  
+  item->pixmap = gtk_pixmap_new(pixmap, mask);
+
+}
+
+void 
+gtk_icon_list_set_label(GtkIconList *iconlist, GtkIconListItem *item, const gchar *label)
+{
+  if(item->label){
+      g_free(item->label);
+      item->label = NULL;
+  }
+  if(item->entry_label){
+      g_free(item->entry_label);
+      item->entry_label = NULL;
+  }
+  if(label) item->label = g_strdup(label);
+  gtk_entry_set_text(GTK_ENTRY(item->entry), label);
+  set_labels(iconlist, item, label);
+}
+/**********************************
+ * gtk_icon_list_set_icon_width
+ * gtk_icon_list_set_row_spacing
+ * gtk_icon_list_set_col_spacing
+ * gtk_icon_list_set_text_space
+ * gtk_icon_list_icon_border
+ **********************************/
+
+void
+gtk_icon_list_set_icon_width(GtkIconList *iconlist, guint width)
+{
+  iconlist->icon_width = width;
+  reorder_icons(iconlist);
+}
+
+void
+gtk_icon_list_set_icon_border(GtkIconList *iconlist, guint border)
+{
+  iconlist->icon_border = border;
+  reorder_icons(iconlist);
+}
+
+void
+gtk_icon_list_set_row_spacing(GtkIconList *iconlist, guint spacing)
+{
+  iconlist->row_spacing = spacing;
+  reorder_icons(iconlist);
+}
+
+void
+gtk_icon_list_set_col_spacing(GtkIconList *iconlist, guint spacing)
+{
+  iconlist->col_spacing = spacing;
+  reorder_icons(iconlist);
+}
+
+
+/**********************************
+ * gtk_icon_list_set_selection_mode
+ * gtk_icon_list_select_icon
+ * gtk_icon_list_unselect_icon
+ * gtk_icon_list_unselect_all
+ **********************************/
+
+void
+gtk_icon_list_set_selection_mode(GtkIconList *iconlist, gint mode)
+{
+  iconlist->selection_mode = mode;
+}
+
+void
+gtk_icon_list_select_icon(GtkIconList *iconlist, GtkIconListItem *item)
+{
+  select_icon(iconlist, item, NULL);
+}
+
+void
+gtk_icon_list_unselect_icon(GtkIconList *iconlist, GtkIconListItem *item)
+{
+  unselect_icon(iconlist, item, NULL);
+}
+
+void
+gtk_icon_list_unselect_all(GtkIconList *iconlist)
+{
+  unselect_all(iconlist);
+}
+
+void
+gtk_icon_list_set_active_icon(GtkIconList *iconlist, GtkIconListItem *icon)
+{
+  if(!icon){
+    deactivate_entry(iconlist);
+    unselect_all(iconlist);
+    return;
+  }
+
+  if(icon->entry){
+    icon->state = GTK_STATE_SELECTED;
+    entry_in(icon->entry, NULL, iconlist);
+    gtk_widget_grab_focus(icon->entry);
+  }
+}
+
+
+gboolean
+gtk_icon_list_is_editable       (GtkIconList *iconlist)
+{
+  return (iconlist->is_editable);
+}
+
+guint
+gtk_icon_list_get_row_spacing       (GtkIconList *iconlist)
+{
+  return(iconlist->row_spacing);
+}
+
+guint
+gtk_icon_list_get_col_spacing       (GtkIconList *iconlist)
+{
+  return(iconlist->col_spacing);
+}
+
+guint
+gtk_icon_list_get_text_space       (GtkIconList *iconlist)
+{
+  return(iconlist->text_space);
+}
+
+guint
+gtk_icon_list_get_icon_border       (GtkIconList *iconlist)
+{
+  return(iconlist->icon_border);
+}
+
+guint
+gtk_icon_list_get_icon_width       (GtkIconList *iconlist)
+{
+  return(iconlist->icon_width);
+}
+
diff --git a/lib/gtksheet/gtkiconlist.h b/lib/gtksheet/gtkiconlist.h
new file mode 100644 (file)
index 0000000..d98314c
--- /dev/null
@@ -0,0 +1,201 @@
+/* gtkiconlist - gtkiconlist widget for gtk+
+ * Copyright 1999-2001  Adrian E. Feiguin <feiguin@ifir.edu.ar>
+ *
+ * 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 Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GTK_ICON_LIST_H__
+#define __GTK_ICON_LIST_H__
+
+
+#include <gdk/gdk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum
+{
+  GTK_ICON_LIST_ICON,
+  GTK_ICON_LIST_TEXT_RIGHT,
+  GTK_ICON_LIST_TEXT_BELOW,
+} GtkIconListMode;
+
+#define GTK_ICON_LIST(obj)        GTK_CHECK_CAST (obj, gtk_icon_list_get_type (), GtkIconList)
+#define GTK_ICON_LIST_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_icon_list_get_type, GtkIconListClass)
+#define GTK_IS_ICON_LIST(obj)     GTK_CHECK_TYPE (obj, gtk_icon_list_get_type ())
+#define GTK_TYPE_ICON_LIST (gtk_icon_list_get_type ())
+#define GTK_TYPE_ICON_LIST_ITEM (gtk_icon_list_item_get_type ())
+
+
+typedef struct _GtkIconList    GtkIconList;
+typedef struct _GtkIconListClass       GtkIconListClass;
+typedef struct _GtkIconListItem        GtkIconListItem;
+
+struct _GtkIconListItem
+{
+  guint x, y;
+
+  gint state;
+
+  gchar *entry_label;
+  gchar *label;
+  GtkWidget *pixmap;
+  GtkWidget *entry;
+
+  gpointer link;
+};
+
+struct _GtkIconList
+{
+  GtkFixed fixed;
+
+  GtkIconListMode mode;
+  GtkSelectionMode selection_mode;
+
+  guint freeze_count;
+
+  guint icon_width;
+
+  guint text_space;   /* entry width */
+  guint row_spacing;  /* space between rows */ 
+  guint col_spacing;  /* space between columns */
+  guint icon_border;  /* space between icon and entry */
+  
+  gboolean is_editable;
+
+  GtkIconListItem *active_icon;
+
+  GdkColor background;
+
+  gint num_icons;
+  GList *icons;
+
+  GList *selection;
+
+  GCompareFunc compare_func;
+};
+
+struct _GtkIconListClass
+{
+  GtkFixedClass parent_class;
+
+  gboolean (*select_icon)      (GtkIconList *iconlist,
+                                GtkIconListItem *icon,
+                                GdkEvent *event);
+  void (*unselect_icon)                (GtkIconList *iconlist,
+                                GtkIconListItem *icon,
+                                GdkEvent *event);
+  gboolean (*text_changed)     (GtkIconList *iconlist,
+                                GtkIconListItem *icon,
+                                gchar *new_text);
+  gboolean (*activate_icon)    (GtkIconList *iconlist,
+                                 GtkIconListItem *icon);
+  gboolean (*deactivate_icon)  (GtkIconList *iconlist,
+                                 GtkIconListItem *icon);
+  void (*click_event)          (GtkIconList *iconlist,
+                                GdkEvent *event);
+};
+
+
+GtkType                gtk_icon_list_get_type  (void);
+GtkType                gtk_icon_list_item_get_type     (void);
+GtkWidget*     gtk_icon_list_new               (guint icon_width,
+                                                GtkIconListMode mode);
+void           gtk_icon_list_construct         (GtkIconList *icon_list,
+                                                        guint icon_width,
+                                                GtkIconListMode mode);
+void           gtk_icon_list_set_mode          (GtkIconList *iconlist,
+                                                GtkIconListMode mode);
+GtkIconListMode        gtk_icon_list_get_mode          (GtkIconList *iconlist);
+void           gtk_icon_list_set_editable      (GtkIconList *iconlist,
+                                                gboolean editable);
+gboolean       gtk_icon_list_is_editable       (GtkIconList *iconlist);
+void           gtk_icon_list_set_row_spacing   (GtkIconList *iconlist,
+                                                guint spacing);
+guint          gtk_icon_list_get_row_spacing   (GtkIconList *iconlist);
+void           gtk_icon_list_set_col_spacing   (GtkIconList *iconlist,
+                                                guint spacing);
+guint          gtk_icon_list_get_col_spacing   (GtkIconList *iconlist);
+void           gtk_icon_list_set_text_space    (GtkIconList *iconlist,
+                                                guint space);
+guint          gtk_icon_list_get_text_space    (GtkIconList *iconlist);
+void           gtk_icon_list_set_icon_border   (GtkIconList *iconlist,
+                                                guint space);
+guint          gtk_icon_list_get_icon_border   (GtkIconList *iconlist);
+void           gtk_icon_list_set_icon_width    (GtkIconList *iconlist,
+                                                guint space);
+guint          gtk_icon_list_get_icon_width    (GtkIconList *iconlist);
+void           gtk_icon_list_freeze            (GtkIconList *icon_list);
+void           gtk_icon_list_thaw              (GtkIconList *icon_list);
+void           gtk_icon_list_set_background    (GtkIconList *icon_list,
+                                                GdkColor *color);
+GtkIconListItem *gtk_icon_list_add_from_pixmap (GtkIconList *icon_list,
+                                                GdkPixmap *pixmap,
+                                                GdkBitmap *bitmap,
+                                                const gchar *label,
+                                                 gpointer link);
+GtkIconListItem *gtk_icon_list_add_from_data   (GtkIconList *icon_list,
+                                                gchar **data,
+                                                const gchar *label,
+                                                 gpointer link);
+GtkIconListItem *gtk_icon_list_add             (GtkIconList *icon_list,
+                                                const gchar *pixmap_file,
+                                                const gchar *label,
+                                                 gpointer link);
+GtkIconListItem        *gtk_icon_list_get_nth          (GtkIconList *iconlist,
+                                                guint n);
+gint           gtk_icon_list_get_index         (GtkIconList *iconlist,
+                                                GtkIconListItem *item);
+void           gtk_icon_list_remove            (GtkIconList *iconlist,
+                                                GtkIconListItem *item);
+void           gtk_icon_list_set_active_icon   (GtkIconList *iconlist,
+                                                GtkIconListItem *icon);
+void           gtk_icon_list_remove_nth        (GtkIconList *iconlist,
+                                                guint n);
+void           gtk_icon_list_update            (GtkIconList *iconlist);
+void           gtk_icon_list_clear             (GtkIconList *iconlist);
+void           gtk_icon_list_link              (GtkIconListItem *item,
+                                                gpointer data);
+gpointer       gtk_icon_list_get_link          (GtkIconListItem *item);
+GtkIconListItem *gtk_icon_list_get_icon_from_link(GtkIconList *iconlist,
+                                                gpointer data);
+GtkIconListItem *gtk_icon_list_get_icon_at     (GtkIconList *iconlist,
+                                                gint x, gint y);
+GtkIconListItem *gtk_icon_list_get_active_icon  (GtkIconList *iconlist);
+GtkWidget      *gtk_icon_list_get_entry        (GtkIconListItem *item);
+GtkWidget      *gtk_icon_list_get_pixmap       (GtkIconListItem *item);
+void           gtk_icon_list_set_pixmap        (GtkIconListItem *item,
+                                                GdkPixmap *pixmap,
+                                                GdkBitmap *bitmap);
+void           gtk_icon_list_set_label         (GtkIconList *iconlist,
+                                                 GtkIconListItem *item, 
+                                                const gchar *label);
+void           gtk_icon_list_set_selection_mode(GtkIconList *iconlist, 
+                                                gint mode);
+void           gtk_icon_list_select_icon       (GtkIconList *iconlist, 
+                                                GtkIconListItem *item);
+void           gtk_icon_list_unselect_icon     (GtkIconList *iconlist, 
+                                                GtkIconListItem *item);
+void           gtk_icon_list_unselect_all      (GtkIconList *iconlist);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __GTK_ICON_LIST_H__ */
diff --git a/lib/gtksheet/gtkitementry.c b/lib/gtksheet/gtkitementry.c
new file mode 100644 (file)
index 0000000..f8355bc
--- /dev/null
@@ -0,0 +1,2404 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+
+#include <string.h>
+
+#include <pango/pango.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include "gtkitementry.h"
+
+#define MIN_ENTRY_WIDTH  150
+#define DRAW_TIMEOUT     20
+#define INNER_BORDER     0
+
+/* Initial size of buffer, in bytes */
+#define MIN_SIZE 16
+
+/* Maximum size of text buffer, in bytes */
+#define MAX_SIZE G_MAXUSHORT
+
+typedef enum {
+  CURSOR_STANDARD,
+  CURSOR_DND
+} CursorType;
+
+/* GObject, GtkObject methods
+ */
+static void   gtk_item_entry_class_init           (GtkItemEntryClass        *klass);
+static void   gtk_item_entry_init                 (GtkItemEntry         *entry);
+static void   gtk_item_entry_editable_init (GtkEditableClass *iface);
+
+/* GtkWidget methods
+ */
+static void   gtk_entry_realize              (GtkWidget        *widget);
+static void   gtk_entry_size_request         (GtkWidget        *widget,
+                                             GtkRequisition   *requisition);
+static void   gtk_entry_size_allocate        (GtkWidget        *widget,
+                                             GtkAllocation    *allocation);
+static void   gtk_entry_draw_frame           (GtkWidget        *widget);
+static gint   gtk_entry_expose               (GtkWidget        *widget,
+                                             GdkEventExpose   *event);
+static void   gtk_entry_grab_focus           (GtkWidget        *widget);
+static void   gtk_entry_style_set            (GtkWidget        *widget,
+                                             GtkStyle         *previous_style);
+static void   gtk_entry_direction_changed    (GtkWidget        *widget,
+                                             GtkTextDirection  previous_dir);
+static void   gtk_entry_state_changed        (GtkWidget        *widget,
+                                             GtkStateType      previous_state);
+
+/* GtkEditable method implementations
+ */
+static void     gtk_entry_insert_text          (GtkEditable *editable,
+                                               const gchar *new_text,
+                                               gint         new_text_length,
+                                               gint        *position);
+static void     gtk_entry_delete_text          (GtkEditable *editable,
+                                               gint         start_pos,
+                                               gint         end_pos);
+
+static void     gtk_entry_real_set_position    (GtkEditable *editable,
+                                                gint         position);
+static gint     gtk_entry_get_position         (GtkEditable *editable);
+
+/* Default signal handlers
+ */
+static void gtk_entry_real_insert_text   (GtkEditable     *editable,
+                                         const gchar     *new_text,
+                                         gint             new_text_length,
+                                         gint            *position);
+static void gtk_entry_real_delete_text   (GtkEditable     *editable,
+                                         gint             start_pos,
+                                         gint             end_pos);
+static void gtk_entry_move_cursor        (GtkEntry        *entry,
+                                         GtkMovementStep  step,
+                                         gint             count,
+                                         gboolean         extend_selection);
+static void gtk_entry_insert_at_cursor   (GtkEntry        *entry,
+                                         const gchar     *str);
+static void gtk_entry_delete_from_cursor (GtkEntry        *entry,
+                                         GtkDeleteType    type,
+                                         gint             count);
+
+/* IM Context Callbacks
+ */
+static void     gtk_entry_commit_cb               (GtkIMContext *context,
+                                                   const gchar  *str,
+                                                   GtkEntry     *entry);
+static void     gtk_entry_preedit_changed_cb      (GtkIMContext *context,
+                                                   GtkEntry     *entry);
+static gboolean gtk_entry_retrieve_surrounding_cb (GtkIMContext *context,
+                                                   GtkEntry     *entry);
+static gboolean gtk_entry_delete_surrounding_cb   (GtkIMContext *context,
+                                                   gint          offset,
+                                                   gint          n_chars,
+                                                   GtkEntry     *entry);
+
+/* Internal routines
+ */
+static void         gtk_entry_enter_text               (GtkEntry       *entry,
+                                                        const gchar    *str);
+static void         gtk_entry_set_positions            (GtkEntry       *entry,
+                                                        gint            current_pos,
+                                                        gint            selection_bound);
+static void         gtk_entry_draw_text                (GtkEntry       *entry);
+static void         gtk_entry_draw_cursor              (GtkEntry       *entry,
+                                                       CursorType      type);
+static PangoLayout *gtk_entry_ensure_layout            (GtkEntry       *entry,
+                                                        gboolean        include_preedit);
+static void         gtk_entry_queue_draw               (GtkEntry       *entry);
+static void         gtk_entry_reset_im_context         (GtkEntry       *entry);
+static void         gtk_entry_recompute                (GtkEntry       *entry);
+static void         gtk_entry_get_cursor_locations     (GtkEntry       *entry,
+                                                       CursorType      type,
+                                                       gint           *strong_x,
+                                                       gint           *weak_x);
+static void         gtk_entry_adjust_scroll            (GtkEntry       *entry);
+static gint         gtk_entry_move_visually            (GtkEntry       *editable,
+                                                       gint            start,
+                                                       gint            count);
+static gint         gtk_entry_move_logically           (GtkEntry       *entry,
+                                                       gint            start,
+                                                       gint            count);
+static gint         gtk_entry_move_forward_word        (GtkEntry       *entry,
+                                                       gint            start);
+static gint         gtk_entry_move_backward_word       (GtkEntry       *entry,
+                                                       gint            start);
+static void         gtk_entry_delete_whitespace        (GtkEntry       *entry);
+static char *       gtk_entry_get_public_chars         (GtkEntry       *entry,
+                                                       gint            start,
+                                                       gint            end);
+static void         gtk_entry_update_primary_selection (GtkEntry       *entry);
+static void         gtk_entry_state_changed            (GtkWidget      *widget,
+                                                       GtkStateType    previous_state);
+static void         gtk_entry_check_cursor_blink       (GtkEntry       *entry);
+static void         gtk_entry_pend_cursor_blink        (GtkEntry       *entry);
+static void         get_text_area_size                 (GtkEntry       *entry,
+                                                       gint           *x,
+                                                       gint           *y,
+                                                       gint           *width,
+                                                       gint           *height);
+static void         get_widget_window_size             (GtkEntry       *entry,
+                                                       gint           *x,
+                                                       gint           *y,
+                                                       gint           *width,
+                                                       gint           *height);
+
+static GtkEntryClass *parent_class = NULL;
+
+GtkType
+gtk_item_entry_get_type (void)
+{
+  static GtkType item_entry_type = 0;
+
+  if (!item_entry_type)
+    {
+      static const GtkTypeInfo item_entry_info =
+      {
+       "GtkItemEntry",
+       sizeof (GtkItemEntry),
+       sizeof (GtkItemEntryClass),
+       (GtkClassInitFunc) gtk_item_entry_class_init,
+       (GtkObjectInitFunc) gtk_item_entry_init,
+       /* reserved_1 */ NULL,
+       /* reserved_2 */ NULL,
+        (GtkClassInitFunc) NULL,
+      };
+
+      static const GInterfaceInfo item_editable_info =
+      {
+        (GInterfaceInitFunc) gtk_item_entry_editable_init,    /* interface_init */
+        NULL,                                            /* interface_finalize */
+        NULL                                             /* interface_data */
+      };
+
+
+      item_entry_type = gtk_type_unique (GTK_TYPE_ENTRY, &item_entry_info);
+
+      g_type_add_interface_static (item_entry_type,
+                                   GTK_TYPE_EDITABLE,
+                                   &item_editable_info);
+
+    }
+
+  return item_entry_type;
+}
+
+static void
+gtk_item_entry_class_init (GtkItemEntryClass *class)
+{
+  GtkObjectClass *object_class;
+  GtkWidgetClass *widget_class;
+  GtkEntryClass *entry_class;
+
+  object_class = (GtkObjectClass*) class;
+  widget_class = (GtkWidgetClass*) class;
+  parent_class = gtk_type_class (GTK_TYPE_ENTRY);
+  entry_class = (GtkEntryClass *) class;
+
+  widget_class->realize = gtk_entry_realize;
+  widget_class->size_request = gtk_entry_size_request;
+  widget_class->size_allocate = gtk_entry_size_allocate;
+  widget_class->expose_event = gtk_entry_expose;
+  widget_class->grab_focus = gtk_entry_grab_focus;
+  widget_class->style_set = gtk_entry_style_set;
+  widget_class->direction_changed = gtk_entry_direction_changed;
+  widget_class->state_changed = gtk_entry_state_changed;
+
+  entry_class->move_cursor = gtk_entry_move_cursor;
+  entry_class->insert_at_cursor = gtk_entry_insert_at_cursor;
+  entry_class->delete_from_cursor = gtk_entry_delete_from_cursor;
+
+}
+
+static void
+gtk_item_entry_editable_init (GtkEditableClass *iface)
+{
+  iface->do_insert_text = gtk_entry_insert_text;
+  iface->do_delete_text = gtk_entry_delete_text;
+  iface->insert_text = gtk_entry_real_insert_text;
+  iface->delete_text = gtk_entry_real_delete_text;
+  iface->set_position = gtk_entry_real_set_position;
+  iface->get_position = gtk_entry_get_position;
+}
+
+static void
+gtk_item_entry_init (GtkItemEntry *entry)
+{
+  entry->justification = GTK_JUSTIFY_LEFT;
+  entry->text_max_size = 0;
+  GTK_ENTRY(entry)->has_frame = FALSE;
+
+  g_object_unref(G_OBJECT(GTK_ENTRY(entry)->im_context));
+
+  GTK_ENTRY(entry)->im_context = gtk_im_multicontext_new ();
+
+  g_signal_connect (G_OBJECT (GTK_ENTRY(entry)->im_context), "commit",
+                    G_CALLBACK (gtk_entry_commit_cb), entry);
+  g_signal_connect (G_OBJECT (GTK_ENTRY(entry)->im_context), "preedit_changed",
+                    G_CALLBACK (gtk_entry_preedit_changed_cb), entry);
+  g_signal_connect (G_OBJECT (GTK_ENTRY(entry)->im_context), "retrieve_surrounding",
+                    G_CALLBACK (gtk_entry_retrieve_surrounding_cb), entry);
+  g_signal_connect (G_OBJECT (GTK_ENTRY(entry)->im_context), "delete_surrounding",
+                    G_CALLBACK (gtk_entry_delete_surrounding_cb), entry);
+
+}
+
+static void
+gtk_entry_realize (GtkWidget *widget)
+{
+  GtkEntry *entry;
+  GtkEditable *editable;
+  GdkWindowAttr attributes;
+  gint attributes_mask;
+
+  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+  entry = GTK_ENTRY (widget);
+  editable = GTK_EDITABLE (widget);
+
+  attributes.window_type = GDK_WINDOW_CHILD;
+  get_widget_window_size (entry, &attributes.x, &attributes.y, &attributes.width, &attributes.height);
+
+  attributes.wclass = GDK_INPUT_OUTPUT;
+  attributes.visual = gtk_widget_get_visual (widget);
+  attributes.colormap = gtk_widget_get_colormap (widget);
+  attributes.event_mask = gtk_widget_get_events (widget);
+  attributes.event_mask |= (GDK_EXPOSURE_MASK |
+                            GDK_BUTTON_PRESS_MASK |
+                            GDK_BUTTON_RELEASE_MASK |
+                            GDK_BUTTON1_MOTION_MASK |
+                            GDK_BUTTON3_MOTION_MASK |
+                            GDK_POINTER_MOTION_HINT_MASK |
+                            GDK_POINTER_MOTION_MASK |
+                            GDK_ENTER_NOTIFY_MASK |
+                            GDK_LEAVE_NOTIFY_MASK);
+  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+
+  widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
+  gdk_window_set_user_data (widget->window, entry);
+
+  get_text_area_size (entry, &attributes.x, &attributes.y, &attributes.width, &attributes.height);
+
+  attributes.cursor = gdk_cursor_new (GDK_XTERM);
+  attributes_mask |= GDK_WA_CURSOR;
+
+  entry->text_area = gdk_window_new (widget->window, &attributes, attributes_mask);
+  gdk_window_set_user_data (entry->text_area, entry);
+
+  gdk_cursor_unref (attributes.cursor);
+
+  widget->style = gtk_style_attach (widget->style, widget->window);
+
+  gdk_window_set_background (widget->window, &widget->style->bg[GTK_WIDGET_STATE(widget)]);
+  gdk_window_set_background (entry->text_area, &widget->style->bg[GTK_WIDGET_STATE (widget)]);
+
+  gdk_window_show (entry->text_area);
+
+  gtk_im_context_set_client_window (entry->im_context, entry->text_area);
+
+  gtk_entry_adjust_scroll (entry);
+}
+
+static void
+get_borders (GtkEntry *entry,
+             gint     *xborder,
+             gint     *yborder)
+{
+  GtkWidget *widget = GTK_WIDGET (entry);
+  gint focus_width;
+  gboolean interior_focus;
+
+  gtk_widget_style_get (widget,
+                       "interior-focus", &interior_focus,
+                       "focus-line-width", &focus_width,
+                       NULL);
+
+  if (entry->has_frame)
+    {
+      *xborder = widget->style->xthickness;
+      *yborder = widget->style->ythickness;
+    }
+  else
+    {
+      *xborder = 0;
+      *yborder = 0;
+    }
+
+  if (!interior_focus)
+    {
+      *xborder += focus_width;
+      *yborder += focus_width;
+    }
+
+}
+
+static void
+gtk_entry_size_request (GtkWidget      *widget,
+                       GtkRequisition *requisition)
+{
+  GtkEntry *entry = GTK_ENTRY (widget);
+  PangoFontMetrics *metrics;
+  gint xborder, yborder;
+  PangoContext *context;
+  
+  context = gtk_widget_get_pango_context (widget);
+  metrics = pango_context_get_metrics (context,
+                                      widget->style->font_desc,
+                                      pango_context_get_language (context));
+
+  entry->ascent = pango_font_metrics_get_ascent (metrics);
+  entry->descent = pango_font_metrics_get_descent (metrics);
+
+  get_borders (entry, &xborder, &yborder);
+  
+  xborder += INNER_BORDER;
+  yborder += INNER_BORDER;
+  
+  if (entry->width_chars < 0)
+    requisition->width = MIN_ENTRY_WIDTH + xborder * 2;
+  else
+    {
+      gint char_width = pango_font_metrics_get_approximate_char_width (metrics);
+      requisition->width = PANGO_PIXELS (char_width) * entry->width_chars + xborder * 2;
+    }
+    
+  requisition->height = PANGO_PIXELS (entry->ascent + entry->descent) + yborder * 2;
+
+  pango_font_metrics_unref (metrics);
+}
+
+static void
+get_text_area_size (GtkEntry *entry,
+                    gint     *x,
+                    gint     *y,
+                    gint     *width,
+                    gint     *height)
+{
+  gint xborder, yborder;
+  GtkRequisition requisition;
+  GtkWidget *widget = GTK_WIDGET (entry);
+
+  gtk_widget_get_child_requisition (widget, &requisition);
+
+  get_borders (entry, &xborder, &yborder);
+
+  if (x)
+    *x = xborder;
+
+  if (y)
+    *y = yborder;
+  
+  if (width)
+    *width = GTK_WIDGET (entry)->allocation.width - xborder * 2;
+
+  if (height)
+    *height = requisition.height - yborder * 2;
+}
+
+static void
+get_widget_window_size (GtkEntry *entry,
+                        gint     *x,
+                        gint     *y,
+                        gint     *width,
+                        gint     *height)
+{
+  GtkRequisition requisition;
+  GtkWidget *widget = GTK_WIDGET (entry);
+      
+  gtk_widget_get_child_requisition (widget, &requisition);
+
+  if (x)
+    *x = widget->allocation.x;
+
+  if (y)
+    {
+      if (entry->is_cell_renderer)
+       *y = widget->allocation.y;
+      else
+       *y = widget->allocation.y + (widget->allocation.height - requisition.height) / 2;
+    }
+
+  if (width)
+    *width = widget->allocation.width;
+
+  if (height)
+    {
+      if (entry->is_cell_renderer)
+       *height = widget->allocation.height;
+      else
+       *height = requisition.height;
+    }
+}
+
+static void
+gtk_entry_size_allocate (GtkWidget     *widget,
+                        GtkAllocation *allocation)
+{
+  GtkEntry *entry = GTK_ENTRY (widget);
+  GtkItemEntry *ientry = GTK_ITEM_ENTRY (widget);
+
+  if(ientry->text_max_size > 0)
+    allocation->width = MIN(ientry->text_max_size, allocation->width);
+  widget->allocation = *allocation;
+  if (GTK_WIDGET_REALIZED (widget))
+    {
+      /* We call gtk_widget_get_child_requisition, since we want (for
+       * backwards compatibility reasons) the realization here to
+       * be affected by the usize of the entry, if set
+       */
+      gint x, y, width, height;
+
+      get_widget_window_size (entry, &x, &y, &width, &height);
+     
+      gdk_window_move_resize (widget->window,
+                              allocation->x, allocation->y, allocation->width, allocation->height);   
+
+      get_text_area_size (entry, &x, &y, &width, &height);
+      
+      gdk_window_move_resize (entry->text_area,
+                              0, allocation->height - height, allocation->width, height);   
+
+      gtk_entry_recompute (entry);
+    }
+}
+
+static void
+gtk_entry_draw_frame (GtkWidget *widget)
+{
+}
+
+static gint
+gtk_entry_expose (GtkWidget      *widget,
+                 GdkEventExpose *event)
+{
+  GtkEntry *entry = GTK_ENTRY (widget);
+
+  if (widget->window == event->window)
+    gtk_entry_draw_frame (widget);
+  else if (entry->text_area == event->window)
+    {
+      gint area_width, area_height;
+
+      get_text_area_size (entry, NULL, NULL, &area_width, &area_height);
+
+      gdk_draw_rectangle (entry->text_area,
+                          widget->style->bg_gc[GTK_WIDGET_STATE(widget)],
+                          TRUE,
+                          0, 0, area_width, area_height);
+
+      if ((entry->visible || entry->invisible_char != 0) &&
+         GTK_WIDGET_HAS_FOCUS (widget) &&
+         entry->selection_bound == entry->current_pos && entry->cursor_visible)
+       gtk_entry_draw_cursor (GTK_ENTRY (widget), CURSOR_STANDARD);
+
+      if (entry->dnd_position != -1)
+       gtk_entry_draw_cursor (GTK_ENTRY (widget), CURSOR_DND);
+      
+      gtk_entry_draw_text (GTK_ENTRY (widget));
+    }
+
+  return FALSE;
+}
+
+static void
+gtk_entry_grab_focus (GtkWidget        *widget)
+{
+  GtkEntry *entry = GTK_ENTRY (widget);
+  gboolean select_on_focus;
+
+  GTK_WIDGET_CLASS (parent_class)->grab_focus (widget);
+
+  g_object_get (G_OBJECT (gtk_settings_get_default ()),
+               "gtk-entry-select-on-focus",
+               &select_on_focus,
+               NULL);
+  
+  if (select_on_focus && entry->editable && !entry->in_click)
+    gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1);
+}
+
+static void 
+gtk_entry_direction_changed (GtkWidget        *widget,
+                            GtkTextDirection  previous_dir)
+{
+  GtkEntry *entry = GTK_ENTRY (widget);
+
+  gtk_entry_recompute (entry);
+      
+  GTK_WIDGET_CLASS (parent_class)->direction_changed (widget, previous_dir);
+}
+
+static void
+gtk_entry_state_changed (GtkWidget      *widget,
+                        GtkStateType    previous_state)
+{
+  GtkEntry *entry = GTK_ENTRY (widget);
+  
+  if (GTK_WIDGET_REALIZED (widget))
+    {
+      gdk_window_set_background (widget->window, &widget->style->bg[GTK_WIDGET_STATE (widget)]);
+      gdk_window_set_background (entry->text_area, &widget->style->bg[GTK_WIDGET_STATE (widget)]);
+    }
+
+  if (!GTK_WIDGET_IS_SENSITIVE (widget))
+    {
+      /* Clear any selection */
+      gtk_editable_select_region (GTK_EDITABLE (entry), entry->current_pos, entry->current_pos);      
+    }
+  
+  gtk_widget_queue_clear (widget);
+}
+
+/* GtkEditable method implementations
+ */
+static void
+gtk_entry_insert_text (GtkEditable *editable,
+                      const gchar *new_text,
+                      gint         new_text_length,
+                      gint        *position)
+{
+  GtkEntry *entry = GTK_ENTRY (editable);
+  gchar buf[64];
+  gchar *text;
+
+  if (*position < 0 || *position > entry->text_length)
+    *position = entry->text_length;
+  
+  g_object_ref (G_OBJECT (editable));
+  
+  if (new_text_length <= 63)
+    text = buf;
+  else
+    text = g_new (gchar, new_text_length + 1);
+
+  text[new_text_length] = '\0';
+  strncpy (text, new_text, new_text_length);
+  
+  g_signal_emit_by_name (editable, "insert_text", text, new_text_length, position);
+
+  if (new_text_length > 63)
+    g_free (text);
+
+  g_object_unref (G_OBJECT (editable));
+}
+
+static void
+gtk_entry_delete_text (GtkEditable *editable,
+                      gint         start_pos,
+                      gint         end_pos)
+{
+  GtkEntry *entry = GTK_ENTRY (editable);
+
+  if (end_pos < 0 || end_pos > entry->text_length)
+    end_pos = entry->text_length;
+  if (start_pos < 0)
+    start_pos = 0;
+  if (start_pos > end_pos)
+    start_pos = end_pos;
+  
+  g_object_ref (G_OBJECT (editable));
+
+  g_signal_emit_by_name (editable, "delete_text", start_pos, end_pos);
+
+  g_object_unref (G_OBJECT (editable));
+}
+
+static void 
+gtk_entry_style_set    (GtkWidget      *widget,
+                        GtkStyle       *previous_style)
+{
+  GtkEntry *entry = GTK_ENTRY (widget);
+
+  if (previous_style && GTK_WIDGET_REALIZED (widget))
+    {
+      gtk_entry_recompute (entry);
+
+      gdk_window_set_background (widget->window, &widget->style->bg[GTK_WIDGET_STATE(widget)]);
+      gdk_window_set_background (entry->text_area, &widget->style->bg[GTK_WIDGET_STATE (widget)]);
+    }
+}
+
+static void
+gtk_entry_real_set_position (GtkEditable *editable,
+                             gint         position)
+{
+  GtkEntry *entry = GTK_ENTRY (editable);
+
+  if (position < 0 || position > entry->text_length)
+    position = entry->text_length;
+
+  if (position != entry->current_pos ||
+      position != entry->selection_bound)
+    {
+      gtk_entry_reset_im_context (entry);
+      gtk_entry_set_positions (entry, position, position);
+    }
+}
+
+static gint
+gtk_entry_get_position (GtkEditable *editable)
+{
+  return GTK_ENTRY (editable)->current_pos;
+}
+
+
+/* Default signal handlers
+ */
+static void
+gtk_entry_real_insert_text (GtkEditable *editable,
+                           const gchar *new_text,
+                           gint         new_text_length,
+                           gint        *position)
+{
+  gint index;
+  gint n_chars;
+
+  GtkEntry *entry = GTK_ENTRY (editable);
+
+  if (new_text_length < 0)
+    new_text_length = strlen (new_text);
+
+  n_chars = g_utf8_strlen (new_text, new_text_length);
+  if (entry->text_max_length > 0 && n_chars + entry->text_length > entry->text_max_length)
+    {
+      gdk_beep ();
+      n_chars = entry->text_max_length - entry->text_length;
+      new_text_length = g_utf8_offset_to_pointer (new_text, n_chars) - new_text;
+    }
+
+  if (new_text_length + entry->n_bytes + 1 > entry->text_size)
+    {
+      while (new_text_length + entry->n_bytes + 1 > entry->text_size)
+       {
+         if (entry->text_size == 0)
+           entry->text_size = MIN_SIZE;
+         else
+           {
+             if (2 * (guint)entry->text_size < MAX_SIZE &&
+                 2 * (guint)entry->text_size > entry->text_size)
+               entry->text_size *= 2;
+             else
+               {
+                 entry->text_size = MAX_SIZE;
+                 if (new_text_length > (gint)entry->text_size - (gint)entry->n_bytes - 1)
+                   {
+                     new_text_length = (gint)entry->text_size - (gint)entry->n_bytes - 1;
+                     new_text_length = g_utf8_find_prev_char (new_text, new_text + new_text_length + 1) - new_text;
+                     n_chars = g_utf8_strlen (new_text, new_text_length);
+                   }
+                 break;
+               }
+           }
+       }
+
+      entry->text = g_realloc (entry->text, entry->text_size);
+    }
+
+  index = g_utf8_offset_to_pointer (entry->text, *position) - entry->text;
+
+  g_memmove (entry->text + index + new_text_length, entry->text + index, entry->n_bytes - index);
+  memcpy (entry->text + index, new_text, new_text_length);
+
+  entry->n_bytes += new_text_length;
+  entry->text_length += n_chars;
+
+  /* NUL terminate for safety and convenience */
+  entry->text[entry->n_bytes] = '\0';
+  
+  if (entry->current_pos > *position)
+    entry->current_pos += n_chars;
+  
+  if (entry->selection_bound > *position)
+    entry->selection_bound += n_chars;
+
+  *position += n_chars;
+
+  gtk_entry_recompute (entry);
+
+  g_signal_emit_by_name (editable, "changed");
+  g_object_notify (G_OBJECT (editable), "text");
+}
+
+static void
+gtk_entry_real_delete_text (GtkEditable *editable,
+                           gint         start_pos,
+                           gint         end_pos)
+{
+  GtkEntry *entry = GTK_ENTRY (editable);
+
+  if (start_pos < 0)
+    start_pos = 0;
+  if (end_pos < 0 || end_pos > entry->text_length)
+    end_pos = entry->text_length;
+  
+  if (start_pos < end_pos)
+    {
+      gint start_index = g_utf8_offset_to_pointer (entry->text, start_pos) - entry->text;
+      gint end_index = g_utf8_offset_to_pointer (entry->text, end_pos) - entry->text;
+
+      g_memmove (entry->text + start_index, entry->text + end_index, entry->n_bytes + 1 - end_index);
+      entry->text_length -= (end_pos - start_pos);
+      entry->n_bytes -= (end_index - start_index);
+      
+      if (entry->current_pos > start_pos)
+       entry->current_pos -= MIN (entry->current_pos, end_pos) - start_pos;
+
+      if (entry->selection_bound > start_pos)
+       entry->selection_bound -= MIN (entry->selection_bound, end_pos) - start_pos;
+      /* We might have deleted the selection
+       */
+      gtk_entry_update_primary_selection (entry);
+      
+      gtk_entry_recompute (entry);
+      
+      g_signal_emit_by_name (editable, "changed");
+      g_object_notify (G_OBJECT (editable), "text");
+    }
+}
+
+/* Compute the X position for an offset that corresponds to the "more important
+ * cursor position for that offset. We use this when trying to guess to which
+ * end of the selection we should go to when the user hits the left or
+ * right arrow key.
+ */
+static gint
+get_better_cursor_x (GtkEntry *entry,
+                    gint      offset)
+{
+  GtkTextDirection keymap_direction =
+    (gdk_keymap_get_direction (gdk_keymap_get_default ()) == PANGO_DIRECTION_LTR) ?
+    GTK_TEXT_DIR_LTR : GTK_TEXT_DIR_RTL;
+  GtkTextDirection widget_direction = gtk_widget_get_direction (GTK_WIDGET (entry));
+  gboolean split_cursor;
+  
+  PangoLayout *layout = gtk_entry_ensure_layout (entry, TRUE);
+  gint index = g_utf8_offset_to_pointer (entry->text, offset) - entry->text;
+  
+  PangoRectangle strong_pos, weak_pos;
+  
+  g_object_get (gtk_widget_get_settings (GTK_WIDGET (entry)),
+               "gtk-split-cursor", &split_cursor,
+               NULL);
+
+  pango_layout_get_cursor_pos (layout, index, &strong_pos, &weak_pos);
+
+  if (split_cursor)
+    return strong_pos.x / PANGO_SCALE;
+  else
+    return (keymap_direction == widget_direction) ? strong_pos.x / PANGO_SCALE : weak_pos.x / PANGO_SCALE;
+}
+
+static void
+gtk_entry_move_cursor (GtkEntry       *entry,
+                      GtkMovementStep step,
+                      gint            count,
+                      gboolean        extend_selection)
+{
+  gint new_pos = entry->current_pos;
+
+  gtk_entry_reset_im_context (entry);
+
+  if (entry->current_pos != entry->selection_bound && !extend_selection)
+    {
+      /* If we have a current selection and aren't extending it, move to the
+       * start/or end of the selection as appropriate
+       */
+      switch (step)
+       {
+       case GTK_MOVEMENT_VISUAL_POSITIONS:
+         {
+           gint current_x = get_better_cursor_x (entry, entry->current_pos);
+           gint bound_x = get_better_cursor_x (entry, entry->selection_bound);
+
+           if (count < 0)
+             new_pos = current_x < bound_x ? entry->current_pos : entry->selection_bound;
+           else
+             new_pos = current_x > bound_x ? entry->current_pos : entry->selection_bound;
+
+           break;
+         }
+       case GTK_MOVEMENT_LOGICAL_POSITIONS:
+       case GTK_MOVEMENT_WORDS:
+         if (count < 0)
+           new_pos = MIN (entry->current_pos, entry->selection_bound);
+         else
+           new_pos = MAX (entry->current_pos, entry->selection_bound);
+         break;
+       case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
+       case GTK_MOVEMENT_PARAGRAPH_ENDS:
+       case GTK_MOVEMENT_BUFFER_ENDS:
+         new_pos = count < 0 ? 0 : entry->text_length;
+         break;
+       case GTK_MOVEMENT_DISPLAY_LINES:
+       case GTK_MOVEMENT_PARAGRAPHS:
+       case GTK_MOVEMENT_PAGES:
+         break;
+       default:
+         break;
+       }
+    }
+  else
+    {
+      switch (step)
+       {
+       case GTK_MOVEMENT_LOGICAL_POSITIONS:
+         new_pos = gtk_entry_move_logically (entry, new_pos, count);
+         break;
+       case GTK_MOVEMENT_VISUAL_POSITIONS:
+         new_pos = gtk_entry_move_visually (entry, new_pos, count);
+         break;
+       case GTK_MOVEMENT_WORDS:
+         while (count > 0)
+           {
+             new_pos = gtk_entry_move_forward_word (entry, new_pos);
+             count--;
+           }
+         while (count < 0)
+           {
+             new_pos = gtk_entry_move_backward_word (entry, new_pos);
+             count++;
+           }
+         break;
+       case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
+       case GTK_MOVEMENT_PARAGRAPH_ENDS:
+       case GTK_MOVEMENT_BUFFER_ENDS:
+         new_pos = count < 0 ? 0 : entry->text_length;
+         break;
+       case GTK_MOVEMENT_DISPLAY_LINES:
+       case GTK_MOVEMENT_PARAGRAPHS:
+       case GTK_MOVEMENT_PAGES:
+         break;
+       default:
+         break;
+       }
+    }
+
+  if (extend_selection)
+    gtk_editable_select_region (GTK_EDITABLE (entry), entry->selection_bound, new_pos);
+  else
+    gtk_editable_set_position (GTK_EDITABLE (entry), new_pos);
+  
+  gtk_entry_pend_cursor_blink (entry);
+}
+
+static void
+gtk_entry_insert_at_cursor (GtkEntry    *entry,
+                           const gchar *str)
+{
+  GtkEditable *editable = GTK_EDITABLE (entry);
+  gint pos = entry->current_pos;
+
+  if (entry->editable)
+    {
+      gtk_entry_reset_im_context (entry);
+
+      gtk_editable_insert_text (editable, str, -1, &pos);
+      gtk_editable_set_position (editable, pos);
+    }
+}
+
+static void
+gtk_entry_delete_from_cursor (GtkEntry       *entry,
+                             GtkDeleteType   type,
+                             gint            count)
+{
+  GtkEditable *editable = GTK_EDITABLE (entry);
+  gint start_pos = entry->current_pos;
+  gint end_pos = entry->current_pos;
+  
+  gtk_entry_reset_im_context (entry);
+
+  if (!entry->editable)
+    return;
+
+  if (entry->selection_bound != entry->current_pos)
+    {
+      gtk_editable_delete_selection (editable);
+      return;
+    }
+  
+  switch (type)
+    {
+    case GTK_DELETE_CHARS:
+      end_pos = gtk_entry_move_logically (entry, entry->current_pos, count);
+      gtk_editable_delete_text (editable, MIN (start_pos, end_pos), MAX (start_pos, end_pos));
+      break;
+    case GTK_DELETE_WORDS:
+      if (count < 0)
+       {
+         /* Move to end of current word, or if not on a word, end of previous word */
+         end_pos = gtk_entry_move_backward_word (entry, end_pos);
+         end_pos = gtk_entry_move_forward_word (entry, end_pos);
+       }
+      else if (count > 0)
+       {
+         /* Move to beginning of current word, or if not on a word, begining of next word */
+         start_pos = gtk_entry_move_forward_word (entry, start_pos);
+         start_pos = gtk_entry_move_backward_word (entry, start_pos);
+       }
+       
+      /* Fall through */
+    case GTK_DELETE_WORD_ENDS:
+      while (count < 0)
+       {
+         start_pos = gtk_entry_move_backward_word (entry, start_pos);
+         count++;
+       }
+      while (count > 0)
+       {
+         end_pos = gtk_entry_move_forward_word (entry, end_pos);
+         count--;
+       }
+      gtk_editable_delete_text (editable, start_pos, end_pos);
+      break;
+    case GTK_DELETE_DISPLAY_LINE_ENDS:
+    case GTK_DELETE_PARAGRAPH_ENDS:
+      if (count < 0)
+       gtk_editable_delete_text (editable, 0, entry->current_pos);
+      else
+       gtk_editable_delete_text (editable, entry->current_pos, -1);
+      break;
+    case GTK_DELETE_DISPLAY_LINES:
+    case GTK_DELETE_PARAGRAPHS:
+      gtk_editable_delete_text (editable, 0, -1);  
+      break;
+    case GTK_DELETE_WHITESPACE:
+      gtk_entry_delete_whitespace (entry);
+      break;
+    }
+  
+  gtk_entry_pend_cursor_blink (entry);
+}
+
+/* IM Context Callbacks
+ */
+
+static void
+gtk_entry_commit_cb (GtkIMContext *context,
+                     const gchar  *str,
+                     GtkEntry     *entry)
+{
+  gtk_entry_enter_text (entry, str);
+}
+
+static void
+gtk_entry_preedit_changed_cb (GtkIMContext *context,
+                              GtkEntry     *entry)
+{
+  gchar *preedit_string;
+  gint cursor_pos;
+
+  gtk_im_context_get_preedit_string (entry->im_context,
+                                     &preedit_string, NULL,
+                                     &cursor_pos);
+  entry->preedit_length = strlen (preedit_string);
+  cursor_pos = CLAMP (cursor_pos, 0, g_utf8_strlen (preedit_string, -1));
+  entry->preedit_cursor = cursor_pos;
+  g_free (preedit_string);
+
+  gtk_entry_recompute (entry);
+}
+
+static gboolean
+gtk_entry_retrieve_surrounding_cb (GtkIMContext *context,
+                               GtkEntry     *entry)
+{
+  gtk_im_context_set_surrounding (context,
+                                  entry->text,
+                                  entry->n_bytes,
+                                  g_utf8_offset_to_pointer (entry->text, entry->current_pos) - entry->text);
+
+  return TRUE;
+}
+
+static gboolean
+gtk_entry_delete_surrounding_cb (GtkIMContext *slave,
+                                 gint          offset,
+                                 gint          n_chars,
+                                 GtkEntry     *entry)
+{
+  gtk_editable_delete_text (GTK_EDITABLE (entry),
+                            entry->current_pos + offset,
+                            entry->current_pos + offset + n_chars);
+
+  return TRUE;
+}
+
+
+/* Internal functions
+ */
+
+/* Used for im_commit_cb and inserting Unicode chars */
+static void
+gtk_entry_enter_text (GtkEntry       *entry,
+                      const gchar    *str)
+{
+  GtkEditable *editable = GTK_EDITABLE (entry);
+  gint tmp_pos;
+
+  if (gtk_editable_get_selection_bounds (editable, NULL, NULL))
+    gtk_editable_delete_selection (editable);
+  else
+    {
+      if (entry->overwrite_mode)
+        gtk_entry_delete_from_cursor (entry, GTK_DELETE_CHARS, 1);
+    }
+
+  tmp_pos = entry->current_pos;
+  gtk_editable_insert_text (editable, str, strlen (str), &tmp_pos);
+  gtk_editable_set_position (editable, tmp_pos);
+}
+
+/* All changes to entry->current_pos and entry->selection_bound
+ * should go through this function.
+ */
+static void
+gtk_entry_set_positions (GtkEntry *entry,
+                         gint      current_pos,
+                         gint      selection_bound)
+{
+  gboolean changed = FALSE;
+
+  g_object_freeze_notify (G_OBJECT (entry));
+
+  if (current_pos != -1 &&
+      entry->current_pos != current_pos)
+    {
+      entry->current_pos = current_pos;
+      changed = TRUE;
+
+      g_object_notify (G_OBJECT (entry), "cursor_position");
+    }
+
+  if (selection_bound != -1 &&
+      entry->selection_bound != selection_bound)
+    {
+      entry->selection_bound = selection_bound;
+      changed = TRUE;
+
+      g_object_notify (G_OBJECT (entry), "selection_bound");
+    }
+
+  g_object_thaw_notify (G_OBJECT (entry));
+
+  if (changed)
+    gtk_entry_recompute (entry);
+}
+
+static void
+gtk_entry_reset_layout (GtkEntry *entry)
+{
+  if (entry->cached_layout)
+    {
+      g_object_unref (G_OBJECT (entry->cached_layout));
+      entry->cached_layout = NULL;
+    }
+}
+
+static void
+update_im_cursor_location (GtkEntry *entry)
+{
+  GdkRectangle area;
+  gint strong_x;
+  gint strong_xoffset;
+  gint x, y, area_width, area_height;
+
+  gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, NULL)
+;
+  get_text_area_size (entry, &x, &y, &area_width, &area_height);
+
+  strong_xoffset = strong_x - entry->scroll_offset;
+  if (strong_xoffset < 0)
+    {
+      strong_xoffset = 0;
+    }
+  else if (strong_xoffset > area_width)
+    {
+      strong_xoffset = area_width;
+    }
+  area.x = x + strong_xoffset;
+  area.y = y + area_height;
+  area.width = area_width;
+  area.height = area_height;
+
+  gtk_im_context_set_cursor_location (entry->im_context, &area);
+}
+
+static gboolean
+recompute_idle_func (gpointer data)
+{
+  GtkEntry *entry;
+
+  GDK_THREADS_ENTER ();
+
+  entry = GTK_ENTRY (data);
+
+  gtk_entry_adjust_scroll (entry);
+  gtk_entry_queue_draw (entry);
+
+  entry->recompute_idle = FALSE;
+  
+  update_im_cursor_location (entry);
+
+  GDK_THREADS_LEAVE ();
+
+  return FALSE;
+}
+
+static void
+gtk_entry_recompute (GtkEntry *entry)
+{
+  gtk_entry_reset_layout (entry);
+  gtk_entry_check_cursor_blink (entry);
+
+
+  if (!entry->recompute_idle)
+    {
+      entry->recompute_idle = g_idle_add_full (G_PRIORITY_HIGH_IDLE + 15, /* between resize and redraw */
+                                              recompute_idle_func, entry, NULL); 
+    }
+}
+
+static void
+append_char (GString *str,
+             gunichar ch,
+             gint     count)
+{
+  gint i;
+  gint char_len;
+  gchar buf[7];
+  
+  char_len = g_unichar_to_utf8 (ch, buf);
+  
+  i = 0;
+  while (i < count)
+    {
+      g_string_append_len (str, buf, char_len);
+      ++i;
+    }
+}
+     
+static PangoLayout *
+gtk_entry_create_layout (GtkEntry *entry,
+                        gboolean  include_preedit)
+{
+  PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (entry), NULL);
+  PangoAttrList *tmp_attrs = pango_attr_list_new ();
+  
+  gchar *preedit_string = NULL;
+  gint preedit_length = 0;
+  PangoAttrList *preedit_attrs = NULL;
+
+  pango_layout_set_single_paragraph_mode (layout, TRUE);
+  
+  if (include_preedit)
+    {
+      gtk_im_context_get_preedit_string (entry->im_context,
+                                        &preedit_string, &preedit_attrs, NULL);
+      preedit_length = entry->preedit_length;
+    }
+
+  if (preedit_length)
+    {
+      GString *tmp_string = g_string_new (NULL);
+      
+      gint cursor_index = g_utf8_offset_to_pointer (entry->text, entry->current_pos) - entry->text;
+      
+      if (entry->visible)
+        {
+          g_string_prepend_len (tmp_string, entry->text, entry->n_bytes);
+          g_string_insert (tmp_string, cursor_index, preedit_string);
+        }
+      else
+        {
+          gint ch_len;
+          gint preedit_len_chars;
+          gunichar invisible_char;
+          
+          ch_len = g_utf8_strlen (entry->text, entry->n_bytes);
+          preedit_len_chars = g_utf8_strlen (preedit_string, -1);
+          ch_len += preedit_len_chars;
+
+          if (entry->invisible_char != 0)
+            invisible_char = entry->invisible_char;
+          else
+            invisible_char = ' '; /* just pick a char */
+          
+          append_char (tmp_string, invisible_char, ch_len);
+          
+          /* Fix cursor index to point to invisible char corresponding
+           * to the preedit, fix preedit_length to be the length of
+           * the invisible chars representing the preedit
+           */
+          cursor_index =
+            g_utf8_offset_to_pointer (tmp_string->str, entry->current_pos) -
+            tmp_string->str;
+          preedit_length =
+            preedit_len_chars *
+            g_unichar_to_utf8 (invisible_char, NULL);
+        }
+      
+      pango_layout_set_text (layout, tmp_string->str, tmp_string->len);
+      
+      pango_attr_list_splice (tmp_attrs, preedit_attrs,
+                             cursor_index, preedit_length);
+      
+      g_string_free (tmp_string, TRUE);
+    }
+  else
+    {
+      if (entry->visible)
+        {
+          pango_layout_set_text (layout, entry->text, entry->n_bytes);
+        }
+      else
+        {
+          GString *str = g_string_new (NULL);
+          gunichar invisible_char;
+          
+          if (entry->invisible_char != 0)
+            invisible_char = entry->invisible_char;
+          else
+            invisible_char = ' '; /* just pick a char */
+          
+          append_char (str, invisible_char, entry->text_length);
+          pango_layout_set_text (layout, str->str, str->len);
+          g_string_free (str, TRUE);
+        }
+    }
+      
+  pango_layout_set_attributes (layout, tmp_attrs);
+
+  if (preedit_string)
+    g_free (preedit_string);
+  if (preedit_attrs)
+    pango_attr_list_unref (preedit_attrs);
+      
+  pango_attr_list_unref (tmp_attrs);
+
+  return layout;
+}
+
+static PangoLayout *
+gtk_entry_ensure_layout (GtkEntry *entry,
+                         gboolean  include_preedit)
+{
+  if (entry->preedit_length > 0 &&
+      !include_preedit != !entry->cache_includes_preedit)
+    gtk_entry_reset_layout (entry);
+
+  if (!entry->cached_layout)
+    {
+      entry->cached_layout = gtk_entry_create_layout (entry, include_preedit);
+      entry->cache_includes_preedit = include_preedit;
+    }
+  
+  return entry->cached_layout;
+}
+
+static void
+get_layout_position (GtkEntry *entry,
+                     gint     *x,
+                     gint     *y)
+{
+  PangoLayout *layout;
+  PangoRectangle logical_rect;
+  gint area_width, area_height;
+  gint y_pos;
+  PangoLayoutLine *line;
+  
+  layout = gtk_entry_ensure_layout (entry, TRUE);
+
+  get_text_area_size (entry, NULL, NULL, &area_width, &area_height);      
+      
+  area_height = PANGO_SCALE * (area_height);
+  
+  line = pango_layout_get_lines (layout)->data;
+  pango_layout_line_get_extents (line, NULL, &logical_rect);
+  
+  /* Align primarily for locale's ascent/descent */
+
+  y_pos = ((area_height - entry->ascent - entry->descent) / 2 + 
+           entry->ascent + logical_rect.y);
+
+  
+  /* Now see if we need to adjust to fit in actual drawn string */
+
+  if (logical_rect.height > area_height)
+    y_pos = (area_height - logical_rect.height) / 2;
+  else if (y_pos < 0)
+    y_pos = 0;
+  else if (y_pos + logical_rect.height > area_height)
+    y_pos = area_height - logical_rect.height;
+  
+  y_pos = y_pos / PANGO_SCALE;
+
+  if (x)
+    *x = - entry->scroll_offset;
+
+  if (y)
+    *y = y_pos;
+}
+
+static void
+gtk_entry_draw_text (GtkEntry *entry)
+{
+  GtkWidget *widget;
+  PangoLayoutLine *line;
+  
+  if (!entry->visible && entry->invisible_char == 0)
+    return;
+  
+  if (GTK_WIDGET_DRAWABLE (entry))
+    {
+      PangoLayout *layout = gtk_entry_ensure_layout (entry, TRUE);
+      gint area_width, area_height;
+
+      gint x, y;
+      gint start_pos, end_pos;
+      
+      widget = GTK_WIDGET (entry);
+      
+      get_layout_position (entry, &x, &y);
+
+      get_text_area_size (entry, NULL, NULL, &area_width, &area_height);
+
+
+      gdk_draw_layout (entry->text_area, widget->style->text_gc [widget->state],       
+                       x, y,
+                      layout);
+     
+      if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start_pos, &end_pos))
+       {
+         gint *ranges;
+         gint n_ranges, i;
+          PangoRectangle logical_rect;
+         const gchar *text = pango_layout_get_text (layout);
+         gint start_index = g_utf8_offset_to_pointer (text, start_pos) - text;
+         gint end_index = g_utf8_offset_to_pointer (text, end_pos) - text;
+         GdkRegion *clip_region = gdk_region_new ();
+         GdkGC *text_gc;
+         GdkGC *selection_gc;
+
+          line = pango_layout_get_lines (layout)->data;
+          
+         pango_layout_line_get_x_ranges (line, start_index, end_index, &ranges, &n_ranges);
+
+          pango_layout_get_extents (layout, NULL, &logical_rect);
+          
+         if (GTK_WIDGET_HAS_FOCUS (entry))
+           {
+             selection_gc = widget->style->base_gc [GTK_STATE_SELECTED];
+             text_gc = widget->style->text_gc [GTK_STATE_SELECTED];
+           }
+         else
+           {
+             selection_gc = widget->style->base_gc [GTK_STATE_ACTIVE];
+             text_gc = widget->style->text_gc [GTK_STATE_ACTIVE];
+           }
+         
+         for (i=0; i < n_ranges; i++)
+           {
+             GdkRectangle rect;
+
+             rect.x = INNER_BORDER - entry->scroll_offset + ranges[2*i] / PANGO_SCALE;
+             rect.y = y;
+             rect.width = (ranges[2*i + 1] - ranges[2*i]) / PANGO_SCALE;
+             rect.height = logical_rect.height / PANGO_SCALE;
+               
+             gdk_draw_rectangle (entry->text_area, selection_gc, TRUE,
+                                 rect.x, rect.y, rect.width, rect.height);
+
+             gdk_region_union_with_rect (clip_region, &rect);
+           }
+
+         gdk_gc_set_clip_region (text_gc, clip_region);
+         gdk_draw_layout (entry->text_area, text_gc, 
+                          x, y,
+                          layout);
+         gdk_gc_set_clip_region (text_gc, NULL);
+         
+         gdk_region_destroy (clip_region);
+         g_free (ranges);
+       }
+    }
+}
+
+/*
+ * From _gtk_get_insertion_cursor_gc
+ */
+
+typedef struct _CursorInfo CursorInfo;
+
+struct _CursorInfo
+{
+  GType for_type;
+  GdkGC *primary_gc;
+  GdkGC *secondary_gc;
+};
+
+static GdkGC *
+make_cursor_gc (GtkWidget *widget,
+               const gchar *property_name,
+               GdkColor *fallback)
+{
+  GdkGCValues gc_values;
+  GdkGCValuesMask gc_values_mask;
+  GdkColor *cursor_color;
+
+  gtk_widget_style_get (widget, property_name, &cursor_color, NULL);
+  
+  gc_values_mask = GDK_GC_FOREGROUND;
+  if (cursor_color)
+    {
+      gc_values.foreground = *cursor_color;
+      gdk_color_free (cursor_color);
+    }
+  else
+    gc_values.foreground = *fallback;
+  
+  gdk_rgb_find_color (widget->style->colormap, &gc_values.foreground);
+  return gtk_gc_get (widget->style->depth, widget->style->colormap,
+    &gc_values, gc_values_mask);
+}
+
+static GdkGC *
+_gtkextra_get_insertion_cursor_gc (GtkWidget *widget,
+                                  gboolean   is_primary)
+{
+  CursorInfo *cursor_info;
+
+  cursor_info = g_object_get_data (G_OBJECT (widget->style), "gtk-style-cursor-info");
+  if (!cursor_info)
+    {
+      cursor_info = g_new (CursorInfo, 1);
+      g_object_set_data (G_OBJECT (widget->style), "gtk-style-cursor-info", cursor_info);
+      cursor_info->primary_gc = NULL;
+      cursor_info->secondary_gc = NULL;
+      cursor_info->for_type = G_TYPE_INVALID;
+    }
+
+  /* We have to keep track of the type because gtk_widget_style_get()
+   * can return different results when called on the same property and
+   * same style but for different widgets. :-(. That is,
+   * GtkEntry::cursor-color = "red" in a style will modify the cursor
+   * color for entries but not for text view.
+   */
+  if (cursor_info->for_type != G_OBJECT_TYPE (widget))
+    {
+      cursor_info->for_type = G_OBJECT_TYPE (widget);
+      if (cursor_info->primary_gc)
+       {
+         gtk_gc_release (cursor_info->primary_gc);
+         cursor_info->primary_gc = NULL;
+       }
+      if (cursor_info->secondary_gc)
+       {
+         gtk_gc_release (cursor_info->secondary_gc);
+         cursor_info->secondary_gc = NULL;
+       }
+    }
+
+  if (is_primary)
+    {
+      if (!cursor_info->primary_gc)
+       cursor_info->primary_gc = make_cursor_gc (widget,
+                                                 "cursor-color",
+                                                 &widget->style->black);
+       
+      return g_object_ref (cursor_info->primary_gc);
+    }
+  else
+    {
+      static GdkColor gray = { 0, 0x8888, 0x8888, 0x8888 };
+      
+      if (!cursor_info->secondary_gc)
+       cursor_info->secondary_gc = make_cursor_gc (widget,
+                                                   "secondary-cursor-color",
+                                                   &gray);
+       
+      return g_object_ref (cursor_info->secondary_gc);
+    }
+}
+
+/*
+ * From _gtk_draw_insertion_cursor
+ */
+static void
+_gtkextra_draw_insertion_cursor (GtkWidget *widget,
+                                GdkDrawable *drawable,
+                                GdkGC *gc,
+                                GdkRectangle *location,
+                                GtkTextDirection direction,
+                                gboolean draw_arrow)
+{
+  gint stem_width;
+  gint arrow_width;
+  gint x, y;
+  gint i;
+  gfloat cursor_aspect_ratio;
+  gint offset;
+  
+  g_return_if_fail (direction != GTK_TEXT_DIR_NONE);
+  
+  gtk_widget_style_get (widget, "cursor-aspect-ratio", &cursor_aspect_ratio, NULL);
+  
+  stem_width = location->height * cursor_aspect_ratio + 1;
+  arrow_width = stem_width + 1;
+
+  /* put (stem_width % 2) on the proper side of the cursor */
+  if (direction == GTK_TEXT_DIR_LTR)
+    offset = stem_width / 2;
+  else
+    offset = stem_width - stem_width / 2;
+  
+  for (i = 0; i < stem_width; i++)
+    gdk_draw_line (drawable, gc,
+                  location->x + i - offset, location->y,
+                  location->x + i - offset, location->y + location->height - 1);
+
+  if (draw_arrow)
+    {
+      if (direction == GTK_TEXT_DIR_RTL)
+        {
+          x = location->x - offset - 1;
+          y = location->y + location->height - arrow_width * 2 - arrow_width + 1;
+  
+          for (i = 0; i < arrow_width; i++)
+            {
+              gdk_draw_line (drawable, gc,
+                             x, y + i + 1,
+                             x, y + 2 * arrow_width - i - 1);
+              x --;
+            }
+        }
+      else if (direction == GTK_TEXT_DIR_LTR)
+        {
+          x = location->x + stem_width - offset;
+          y = location->y + location->height - arrow_width * 2 - arrow_width + 1;
+  
+          for (i = 0; i < arrow_width; i++) 
+            {
+              gdk_draw_line (drawable, gc,
+                             x, y + i + 1,
+                             x, y + 2 * arrow_width - i - 1);
+              x++;
+            }
+        }
+    }
+}
+
+static void
+gtk_entry_draw_cursor (GtkEntry  *entry,
+                      CursorType type)
+{
+  GtkTextDirection keymap_direction =
+    (gdk_keymap_get_direction (gdk_keymap_get_default ()) == PANGO_DIRECTION_LTR) ?
+    GTK_TEXT_DIR_LTR : GTK_TEXT_DIR_RTL;
+  GtkTextDirection widget_direction = gtk_widget_get_direction (GTK_WIDGET (entry));
+  if (GTK_WIDGET_DRAWABLE (entry) && GTK_ENTRY(entry)->cursor_visible)
+    {
+      GtkWidget *widget = GTK_WIDGET (entry);
+      GdkRectangle cursor_location;
+      gboolean split_cursor;
+
+      gint xoffset = INNER_BORDER - entry->scroll_offset;
+      gint strong_x, weak_x;
+      gint text_area_height;
+      GtkTextDirection dir1 = GTK_TEXT_DIR_NONE;
+      GtkTextDirection dir2 = GTK_TEXT_DIR_NONE;
+      gint x1 = 0;
+      gint x2 = 0;
+      GdkGC *gc;
+
+      gdk_window_get_size (entry->text_area, NULL, &text_area_height);
+      
+      gtk_entry_get_cursor_locations (entry, type, &strong_x, &weak_x);
+
+      g_object_get (gtk_widget_get_settings (widget),
+                   "gtk-split-cursor", &split_cursor,
+                   NULL);
+
+      dir1 = widget_direction;
+      
+      if (split_cursor)
+       {
+         x1 = strong_x;
+
+         if (weak_x != strong_x)
+           {
+             dir2 = (widget_direction == GTK_TEXT_DIR_LTR) ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR;
+             x2 = weak_x;
+           }
+       }
+      else
+       {
+         if (keymap_direction == widget_direction)
+           x1 = strong_x;
+         else
+           x1 = weak_x;
+       }
+
+      cursor_location.x = xoffset + x1;
+      cursor_location.y = INNER_BORDER;
+      cursor_location.width = 0;
+      cursor_location.height = text_area_height - 2 * INNER_BORDER ;
+
+      gc = _gtkextra_get_insertion_cursor_gc (widget, TRUE);
+      _gtkextra_draw_insertion_cursor (widget, entry->text_area, gc,
+                                 &cursor_location, dir1,
+                                  dir2 != GTK_TEXT_DIR_NONE);
+      g_object_unref (gc);
+      
+      if (dir2 != GTK_TEXT_DIR_NONE)
+       {
+         cursor_location.x = xoffset + x2;
+         gc = _gtkextra_get_insertion_cursor_gc (widget, FALSE);
+         _gtkextra_draw_insertion_cursor (widget, entry->text_area, gc,
+                                     &cursor_location, dir2,
+                                      TRUE);
+         g_object_unref (gc);
+       }
+    }
+}
+
+static void
+gtk_entry_queue_draw (GtkEntry *entry)
+{
+  if (GTK_WIDGET_REALIZED (entry))
+    gdk_window_invalidate_rect (entry->text_area, NULL, FALSE);
+}
+
+static void
+gtk_entry_reset_im_context (GtkEntry *entry)
+{
+  if (entry->need_im_reset)
+    {
+      entry->need_im_reset = 0;
+      gtk_im_context_reset (entry->im_context);
+    }
+}
+
+static void
+gtk_entry_get_cursor_locations (GtkEntry   *entry,
+                               CursorType  type,
+                               gint       *strong_x,
+                               gint       *weak_x)
+{
+  PangoLayout *layout = gtk_entry_ensure_layout (entry, TRUE);
+  const gchar *text;
+  PangoRectangle strong_pos, weak_pos;
+  gint index;
+  
+  if (type == CURSOR_STANDARD)
+    {
+      text = pango_layout_get_text (layout);
+      index = g_utf8_offset_to_pointer (text, entry->current_pos + entry->preedit_cursor) - text;
+    }
+  else /* type == CURSOR_DND */
+    {
+      index = g_utf8_offset_to_pointer (entry->text, entry->dnd_position) - entry->text;
+      if (entry->dnd_position > entry->current_pos)
+       index += entry->preedit_length;
+    }
+      
+  pango_layout_get_cursor_pos (layout, index, &strong_pos, &weak_pos);
+
+  if (strong_x)
+    *strong_x = strong_pos.x / PANGO_SCALE;
+
+  if (weak_x)
+    *weak_x = weak_pos.x / PANGO_SCALE;
+}
+
+static void
+gtk_entry_adjust_scroll (GtkEntry *entry)
+{
+  gint min_offset, max_offset;
+  gint text_area_width;
+  gint strong_x, weak_x;
+  PangoLayout *layout;
+  PangoLayoutLine *line;
+  PangoRectangle logical_rect;
+  GtkItemEntry *item_entry;
+  gint text_width;
+
+  if (!GTK_WIDGET_REALIZED (entry))
+    return;
+
+  item_entry = GTK_ITEM_ENTRY(entry);
+  
+  gdk_window_get_size (entry->text_area, &text_area_width, NULL);
+  text_area_width -= 2 * INNER_BORDER;
+
+  layout = gtk_entry_ensure_layout (entry, TRUE);
+  line = pango_layout_get_lines (layout)->data;
+
+  pango_layout_line_get_extents (line, NULL, &logical_rect);
+  text_width = logical_rect.width / PANGO_SCALE + 2; /* 2 for cursor */
+
+  gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, &weak_x);
+
+  /* Display as much text as we can */
+
+  if (gtk_widget_get_direction (GTK_WIDGET (entry)) == GTK_TEXT_DIR_LTR)
+    {
+      entry->scroll_offset = 0;
+      switch(item_entry->justification){
+
+        case GTK_JUSTIFY_FILL:
+        case GTK_JUSTIFY_LEFT:
+
+/* LEFT JUSTIFICATION */
+
+          strong_x -= entry->scroll_offset;
+          if (strong_x < 0)
+            entry->scroll_offset += strong_x;
+          else if (strong_x > text_area_width){
+            if(item_entry->text_max_size != 0 &&
+               text_area_width + 2 <= item_entry->text_max_size){
+               GtkAllocation allocation;
+               allocation = GTK_WIDGET(entry)->allocation;
+               allocation.width += text_width - text_area_width;
+               entry->scroll_offset = 0;
+               gtk_entry_size_allocate(GTK_WIDGET(entry), &allocation);
+            }else{
+               entry->scroll_offset += (strong_x - text_area_width) + 1;
+            }
+          }
+          break;
+
+        case GTK_JUSTIFY_RIGHT:
+    
+    /* RIGHT JUSTIFICATION FOR NUMBERS */
+          if(entry->text){
+
+            entry->scroll_offset=  -(text_area_width - text_width) + 1;
+            if(entry->scroll_offset > 0){
+              if(item_entry->text_max_size != 0 &&
+                text_area_width + 2 <= item_entry->text_max_size){
+                GtkAllocation allocation;
+                allocation = GTK_WIDGET(entry)->allocation;
+                allocation.x -= text_width - text_area_width;
+                allocation.width += text_width - text_area_width;
+                entry->scroll_offset = 0;
+                gtk_entry_size_allocate(GTK_WIDGET(entry), &allocation);
+              }
+              else
+              {
+                entry->scroll_offset= -(text_area_width - strong_x) + 1;
+                if(entry->scroll_offset < 0) entry->scroll_offset = 0;
+              }
+            } 
+          }
+          else
+            entry->scroll_offset=0;
+
+          break;
+        case GTK_JUSTIFY_CENTER:
+    
+          if(entry->text){
+     
+            entry->scroll_offset=  -(text_area_width - text_width)/2;
+            if(entry->scroll_offset > 0){
+              if(item_entry->text_max_size != 0 &&
+                          text_area_width+1<=item_entry->text_max_size){
+                GtkAllocation allocation;
+                allocation = GTK_WIDGET(entry)->allocation;
+                allocation.x += (text_area_width/2 - text_width/2);
+                allocation.width += text_width - text_area_width;
+                entry->scroll_offset = 0;
+                gtk_entry_size_allocate(GTK_WIDGET(entry), &allocation);
+              }
+              else
+              {
+                entry->scroll_offset= -(text_area_width - strong_x) + 1;
+                if(entry->scroll_offset < 0) entry->scroll_offset = 0;
+              }
+            }
+          }
+          else
+            entry->scroll_offset=0;
+
+          break;
+
+      }
+
+    }
+  else
+    {
+      max_offset = text_width - text_area_width;
+      min_offset = MIN (0, max_offset);
+      entry->scroll_offset = CLAMP (entry->scroll_offset, min_offset, max_offset);
+    }
+
+  g_object_notify (G_OBJECT (entry), "scroll_offset");
+}
+
+static gint
+gtk_entry_move_visually (GtkEntry *entry,
+                        gint      start,
+                        gint      count)
+{
+  gint index;
+  PangoLayout *layout = gtk_entry_ensure_layout (entry, FALSE);
+  const gchar *text;
+
+  text = pango_layout_get_text (layout);
+  
+  index = g_utf8_offset_to_pointer (text, start) - text;
+
+  while (count != 0)
+    {
+      int new_index, new_trailing;
+      gboolean split_cursor;
+      gboolean strong;
+
+      g_object_get (gtk_widget_get_settings (GTK_WIDGET (entry)),
+                   "gtk-split-cursor", &split_cursor,
+                   NULL);
+
+      if (split_cursor)
+       strong = TRUE;
+      else
+       {
+         GtkTextDirection keymap_direction =
+           (gdk_keymap_get_direction (gdk_keymap_get_default ()) == PANGO_DIRECTION_LTR) ?
+           GTK_TEXT_DIR_LTR : GTK_TEXT_DIR_RTL;
+
+         strong = keymap_direction == gtk_widget_get_direction (GTK_WIDGET (entry));
+       }
+      
+      if (count > 0)
+       {
+         pango_layout_move_cursor_visually (layout, strong, index, 0, 1, &new_index, &new_trailing);
+         count--;
+       }
+      else
+       {
+         pango_layout_move_cursor_visually (layout, strong, index, 0, -1, &new_index, &new_trailing);
+         count++;
+       }
+
+      if (new_index < 0 || new_index == G_MAXINT)
+       break;
+
+      index = new_index;
+      
+      while (new_trailing--)
+       index = g_utf8_next_char (entry->text + new_index) - entry->text;
+    }
+  
+  return g_utf8_pointer_to_offset (text, text + index);
+}
+
+static gint
+gtk_entry_move_logically (GtkEntry *entry,
+                         gint      start,
+                         gint      count)
+{
+  gint new_pos = start;
+
+  /* Prevent any leak of information */
+  if (!entry->visible)
+    {
+      new_pos = CLAMP (start + count, 0, entry->text_length);
+    }
+  else if (entry->text)
+    {
+      PangoLayout *layout = gtk_entry_ensure_layout (entry, FALSE);
+      PangoLogAttr *log_attrs;
+      gint n_attrs;
+
+      pango_layout_get_log_attrs (layout, &log_attrs, &n_attrs);
+
+      while (count > 0 && new_pos < entry->text_length)
+       {
+         do
+           new_pos++;
+         while (new_pos < entry->text_length && !log_attrs[new_pos].is_cursor_position);
+         
+         count--;
+       }
+      while (count < 0 && new_pos > 0)
+       {
+         do
+           new_pos--;
+         while (new_pos > 0 && !log_attrs[new_pos].is_cursor_position);
+         
+         count++;
+       }
+      
+      g_free (log_attrs);
+    }
+
+  return new_pos;
+}
+
+static gint
+gtk_entry_move_forward_word (GtkEntry *entry,
+                            gint      start)
+{
+  gint new_pos = start;
+
+  /* Prevent any leak of information */
+  if (!entry->visible)
+    {
+      new_pos = entry->text_length;
+    }
+  else if (entry->text && (new_pos < entry->text_length))
+    {
+      PangoLayout *layout = gtk_entry_ensure_layout (entry, FALSE);
+      PangoLogAttr *log_attrs;
+      gint n_attrs;
+
+      pango_layout_get_log_attrs (layout, &log_attrs, &n_attrs);
+      
+      /* Find the next word end */
+      new_pos++;
+      while (new_pos < n_attrs && !log_attrs[new_pos].is_word_end)
+       new_pos++;
+
+      g_free (log_attrs);
+    }
+
+  return new_pos;
+}
+
+
+static gint
+gtk_entry_move_backward_word (GtkEntry *entry,
+                             gint      start)
+{
+  gint new_pos = start;
+
+  /* Prevent any leak of information */
+  if (!entry->visible)
+    {
+      new_pos = 0;
+    }
+  else if (entry->text && start > 0)
+    {
+      PangoLayout *layout = gtk_entry_ensure_layout (entry, FALSE);
+      PangoLogAttr *log_attrs;
+      gint n_attrs;
+
+      pango_layout_get_log_attrs (layout, &log_attrs, &n_attrs);
+
+      new_pos = start - 1;
+
+      /* Find the previous word beginning */
+      while (new_pos > 0 && !log_attrs[new_pos].is_word_start)
+       new_pos--;
+
+      g_free (log_attrs);
+    }
+
+  return new_pos;
+}
+
+static void
+gtk_entry_delete_whitespace (GtkEntry *entry)
+{
+  PangoLayout *layout = gtk_entry_ensure_layout (entry, FALSE);
+  PangoLogAttr *log_attrs;
+  gint n_attrs;
+  gint start, end;
+
+  pango_layout_get_log_attrs (layout, &log_attrs, &n_attrs);
+
+  start = end = entry->current_pos;
+  
+  while (start > 0 && log_attrs[start-1].is_white)
+    start--;
+
+  while (end < n_attrs && log_attrs[end].is_white)
+    end++;
+
+  g_free (log_attrs);
+
+  if (start != end)
+    gtk_editable_delete_text (GTK_EDITABLE (entry), start, end);
+}
+
+
+/*
+ * Like gtk_editable_get_chars, but if the editable is not
+ * visible, return asterisks; also convert result to UTF-8.
+ */
+static char *    
+gtk_entry_get_public_chars (GtkEntry *entry,
+                           gint      start,
+                           gint      end)
+{
+  if (end < 0)
+    end = entry->text_length;
+  
+  if (entry->visible)
+    return gtk_editable_get_chars (GTK_EDITABLE (entry), start, end);
+  else
+    {
+      gchar *str;
+      gint i;
+      gint n_chars = end - start;
+       
+      str = g_malloc (n_chars + 1);
+      for (i = 0; i < n_chars; i++)
+       str[i] = '*';
+      str[i] = '\0';
+      
+      return str;
+    }
+
+}
+
+static void
+primary_get_cb (GtkClipboard     *clipboard,
+               GtkSelectionData *selection_data,
+               guint             info,
+               gpointer          data)
+{
+  GtkEntry *entry = GTK_ENTRY (data);
+  gint start, end;
+  
+  if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end))
+    {
+      gchar *str = gtk_entry_get_public_chars (entry, start, end);
+      gtk_selection_data_set_text (selection_data, str, -1);
+      g_free (str);
+    }
+}
+
+static void
+primary_clear_cb (GtkClipboard *clipboard,
+                 gpointer      data)
+{
+  GtkEntry *entry = GTK_ENTRY (data);
+
+  gtk_editable_select_region (GTK_EDITABLE (entry), entry->current_pos, entry->current_pos);
+}
+
+static void
+gtk_entry_update_primary_selection (GtkEntry *entry)
+{
+  static const GtkTargetEntry targets[] = {
+    { "UTF8_STRING", 0, 0 },
+    { "STRING", 0, 0 },
+    { "TEXT",   0, 0 }, 
+    { "COMPOUND_TEXT", 0, 0 }
+  };
+  
+  GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
+  gint start, end;
+  
+  if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end))
+    {
+      if (!gtk_clipboard_set_with_owner (clipboard, targets, G_N_ELEMENTS (targets),
+                                        primary_get_cb, primary_clear_cb, G_OBJECT (entry)))
+       primary_clear_cb (clipboard, entry);
+    }
+  else
+    {
+      if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (entry))
+       gtk_clipboard_clear (clipboard);
+    }
+}
+
+/* Public API
+ */
+
+GtkWidget*
+gtk_item_entry_new (void)
+{
+  GtkWidget *widget;
+
+  widget = GTK_WIDGET (gtk_type_new (GTK_TYPE_ITEM_ENTRY));
+  return widget;
+}
+
+GtkWidget*
+gtk_item_entry_new_with_max_length (gint max)
+{
+  GtkItemEntry *entry;
+
+  entry = gtk_type_new (GTK_TYPE_ITEM_ENTRY);
+  gtk_entry_set_max_length(GTK_ENTRY(entry), max);
+
+  return GTK_WIDGET (entry);
+}
+
+void
+gtk_item_entry_set_text (GtkItemEntry    *entry,
+                        const gchar *text,
+                         GtkJustification justification)
+{
+  gint tmp_pos;
+
+  g_return_if_fail (GTK_IS_ITEM_ENTRY (entry));
+  g_return_if_fail (text != NULL);
+
+  entry->justification = justification;
+
+  /* Actually setting the text will affect the cursor and selection;
+   * if the contents don't actually change, this will look odd to the user.
+   */
+  if (strcmp (GTK_ENTRY(entry)->text, text) == 0)
+    return;
+
+  if (GTK_ENTRY(entry)->recompute_idle){
+    g_source_remove (GTK_ENTRY(entry)->recompute_idle);
+    GTK_ENTRY(entry)->recompute_idle = 0;
+  }
+  if (GTK_ENTRY(entry)->blink_timeout){
+    g_source_remove (GTK_ENTRY(entry)->blink_timeout);
+    GTK_ENTRY(entry)->blink_timeout = 0;
+  }
+
+  gtk_editable_delete_text (GTK_EDITABLE (entry), 0, -1);
+
+  tmp_pos = 0;
+  gtk_editable_insert_text (GTK_EDITABLE (entry), text, strlen (text), &tmp_pos);
+}
+
+/**
+ * gtk_entry_get_layout_offsets:
+ * @entry: a #GtkEntry
+ * @x: location to store X offset of layout, or %NULL
+ * @y: location to store Y offset of layout, or %NULL
+ *
+ *
+ * Obtains the position of the #PangoLayout used to render text
+ * in the entry, in widget coordinates. Useful if you want to line
+ * up the text in an entry with some other text, e.g. when using the
+ * entry to implement editable cells in a sheet widget.
+ *
+ * Also useful to convert mouse events into coordinates inside the
+ * #PangoLayout, e.g. to take some action if some part of the entry text
+ * is clicked.
+ * 
+ * Note that as the user scrolls around in the entry the offsets will
+ * change; you'll need to connect to the "notify::scroll_offset"
+ * signal to track this. Remember when using the #PangoLayout
+ * functions you need to convert to and from pixels using
+ * PANGO_PIXELS() or #PANGO_SCALE.
+ *
+ * Keep in mind that the layout text may contain a preedit string, so
+ * gtk_entry_layout_index_to_text_index() and
+ * gtk_entry_text_index_to_layout_index() are needed to convert byte
+ * indices in the layout to byte indices in the entry contents.
+ * 
+ **/
+void
+gtk_item_entry_get_layout_offsets (GtkItemEntry *entry,
+                                   gint     *x,
+                                   gint     *y)
+{
+  gint text_area_x, text_area_y;
+  
+  g_return_if_fail (GTK_IS_ITEM_ENTRY (entry));
+
+  /* this gets coords relative to text area */
+  get_layout_position (GTK_ENTRY(entry), x, y);
+
+  /* convert to widget coords */
+  get_text_area_size (GTK_ENTRY(entry), &text_area_x, &text_area_y, NULL, NULL);
+  
+  if (x)
+    *x += text_area_x;
+
+  if (y)
+    *y += text_area_y;
+}
+
+void
+gtk_item_entry_set_justification(GtkItemEntry *entry, GtkJustification just)
+{
+  g_return_if_fail (GTK_IS_ITEM_ENTRY (entry));
+
+  entry->justification = just;
+}
+
+
+/* We display the cursor when
+ *
+ *  - the selection is empty, AND
+ *  - the widget has focus
+ */
+
+#define CURSOR_ON_MULTIPLIER 0.66
+#define CURSOR_OFF_MULTIPLIER 0.34
+#define CURSOR_PEND_MULTIPLIER 1.0
+
+static gboolean
+cursor_blinks (GtkEntry *entry)
+{
+  GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (entry));
+  gboolean blink;
+
+  if (GTK_WIDGET_HAS_FOCUS (entry) &&
+      entry->selection_bound == entry->current_pos)
+    {
+      g_object_get (G_OBJECT (settings), "gtk-cursor-blink", &blink, NULL);
+      return blink;
+    }
+  else
+    return FALSE;
+}
+
+static gint
+get_cursor_time (GtkEntry *entry)
+{
+  GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (entry));
+  gint time;
+
+  g_object_get (G_OBJECT (settings), "gtk-cursor-blink-time", &time, NULL);
+
+  return time;
+}
+
+static void
+show_cursor (GtkEntry *entry)
+{
+  if (!entry->cursor_visible)
+    {
+      entry->cursor_visible = TRUE;
+
+      if (GTK_WIDGET_HAS_FOCUS (entry) && entry->selection_bound == entry->current_pos)
+       gtk_widget_queue_draw (GTK_WIDGET (entry));
+    }
+}
+
+static void
+hide_cursor (GtkEntry *entry)
+{
+  if (entry->cursor_visible)
+    {
+      entry->cursor_visible = FALSE;
+
+      if (GTK_WIDGET_HAS_FOCUS (entry) && entry->selection_bound == entry->current_pos)
+       gtk_widget_queue_draw (GTK_WIDGET (entry));
+    }
+}
+
+/*
+ * Blink!
+ */
+static gint
+blink_cb (gpointer data)
+{
+  GtkEntry *entry;
+
+  GDK_THREADS_ENTER ();
+
+  entry = GTK_ENTRY (data);
+  
+  g_assert (GTK_WIDGET_HAS_FOCUS (entry));
+  g_assert (entry->selection_bound == entry->current_pos);
+
+  if (entry->cursor_visible)
+    {
+      hide_cursor (entry);
+      entry->blink_timeout = gtk_timeout_add (get_cursor_time (entry) * CURSOR_OFF_MULTIPLIER,
+                                             blink_cb,
+                                             entry);
+    }
+  else
+    {
+      show_cursor (entry);
+      entry->blink_timeout = gtk_timeout_add (get_cursor_time (entry) * CURSOR_ON_MULTIPLIER,
+                                             blink_cb,
+                                             entry);
+    }
+
+  GDK_THREADS_LEAVE ();
+
+  /* Remove ourselves */
+  return FALSE;
+}
+
+static void
+gtk_entry_check_cursor_blink (GtkEntry *entry)
+{
+  if (cursor_blinks (entry))
+    {
+      if (!entry->blink_timeout)
+       {
+         entry->blink_timeout = gtk_timeout_add (get_cursor_time (entry) * CURSOR_ON_MULTIPLIER,
+                                                 blink_cb,
+                                                 entry);
+         show_cursor (entry);
+       }
+    }
+  else
+    {
+      if (entry->blink_timeout)  
+       { 
+         gtk_timeout_remove (entry->blink_timeout);
+         entry->blink_timeout = 0;
+       }
+      
+      entry->cursor_visible = TRUE;
+    }
+  
+}
+
+static void
+gtk_entry_pend_cursor_blink (GtkEntry *entry)
+{
+  if (cursor_blinks (entry))
+    {
+      if (entry->blink_timeout != 0)
+       gtk_timeout_remove (entry->blink_timeout);
+      
+      entry->blink_timeout = gtk_timeout_add (get_cursor_time (entry) * CURSOR_PEND_MULTIPLIER,
+                                             blink_cb,
+                                             entry);
+      show_cursor (entry);
+    }
+}
+
+void
+gtk_item_entry_set_cursor_visible(GtkItemEntry *entry, gboolean visible)
+{
+  g_return_if_fail (GTK_IS_ITEM_ENTRY (entry));
+
+  GTK_ENTRY(entry)->cursor_visible = visible;
+}
+
+gboolean
+gtk_item_entry_get_cursor_visible(GtkItemEntry *entry)
+{
+  g_return_val_if_fail (GTK_IS_ITEM_ENTRY (entry), FALSE);
+
+  return(GTK_ENTRY(entry)->cursor_visible);
+}
diff --git a/lib/gtksheet/gtkitementry.h b/lib/gtksheet/gtkitementry.h
new file mode 100644 (file)
index 0000000..839c2a8
--- /dev/null
@@ -0,0 +1,76 @@
+/* GtkItemEntry - widget for gtk+
+ * Copyright (C) 1999-2001 Adrian E. Feiguin <adrian@ifir.ifir.edu.ar>
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * GtkItemEntry widget by Adrian E. Feiguin
+ * Based on GtkEntry widget 
+ *
+ * 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
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#ifndef __GTK_ITEM_ENTRY_H__
+#define __GTK_ITEM_ENTRY_H__
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define GTK_TYPE_ITEM_ENTRY            (gtk_item_entry_get_type ())
+#define GTK_ITEM_ENTRY(obj)            (GTK_CHECK_CAST (obj, gtk_item_entry_get_type (), GtkItemEntry))
+#define GTK_ITEM_ENTRY_CLASS(klass)    (GTK_CHECK_CLASS_CAST (klass, gtk_item_entry_get_type (), GtkItemEntryClass))
+#define GTK_IS_ITEM_ENTRY(obj)         (GTK_CHECK_TYPE (obj, gtk_item_entry_get_type ()))
+#define GTK_IS_ITEM_ENTRY_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_ENTRY))
+
+
+typedef struct _GtkItemEntry       GtkItemEntry;
+typedef struct _GtkItemEntryClass  GtkItemEntryClass;
+
+struct _GtkItemEntry
+{
+  GtkEntry parent;
+
+  gint text_max_size;
+
+  GtkJustification justification;
+};
+
+struct _GtkItemEntryClass
+{
+  GtkEntryClass parent_class;
+};
+
+GtkType    gtk_item_entry_get_type       (void);
+GtkWidget* gtk_item_entry_new            (void);
+GtkWidget* gtk_item_entry_new_with_max_length (gint   max);
+void       gtk_item_entry_set_text            (GtkItemEntry *item_entry, 
+                                               const gchar *text,
+                                               GtkJustification justification);
+
+void       gtk_item_entry_set_justification (GtkItemEntry        *item_entry,
+                                            GtkJustification   justification);
+
+void       gtk_item_entry_set_cursor_visible   (GtkItemEntry *entry,
+                                                gboolean visible);
+gboolean   gtk_item_entry_get_cursor_visible   (GtkItemEntry *entry);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __GTK_ITEM_ENTRY_H__ */
diff --git a/lib/gtksheet/gtksheet.c b/lib/gtksheet/gtksheet.c
new file mode 100644 (file)
index 0000000..7f5aced
--- /dev/null
@@ -0,0 +1,7805 @@
+/* This version of GtkSheet has been heavily modified, for the specific 
+   requirements of PSPPIRE. */
+
+/* GtkSheet widget for Gtk+.
+ * Copyright (C) 1999-2001 Adrian E. Feiguin <adrian@ifir.ifir.edu.ar>
+ *
+ * Based on GtkClist widget by Jay Painter, but major changes.
+ * Memory allocation routines inspired on SC (Spreadsheet Calculator)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+/**
+ * SECTION:gtksheet
+ * @short_description: spreadsheet widget for gtk2
+ *
+ * GtkSheet is a matrix widget for GTK+. It consists of an scrollable grid of 
+ * cells where you can allocate text. Cell contents can be edited interactively
+ * through a specially designed entry, GtkItemEntry. It is also a container
+ * subclass, allowing you to display buttons, curves, pixmaps and any other 
+ * widgets in it.
+ *
+ * You can also set many attributes as: border, foreground and background color,
+ * text justification, and more.
+ *
+ * The testgtksheet program shows how easy is to create a spreadsheet-like GUI
+ * using this widget.
+ */
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtksignal.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkadjustment.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtkbox.h>
+#include <gtk/gtkmain.h>
+#include <gtk/gtktypeutils.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkcontainer.h>
+#include <gtk/gtkpixmap.h>
+#include <pango/pango.h>
+#include "gtkitementry.h"
+#include "gtksheet.h"
+#include "gtkextra-marshal.h"
+#include "gsheetmodel.h"
+
+
+/* sheet flags */
+enum
+{ 
+  GTK_SHEET_IS_LOCKED       = 1 << 0,
+  GTK_SHEET_IS_FROZEN       = 1 << 1,
+  GTK_SHEET_IN_XDRAG        = 1 << 2,
+  GTK_SHEET_IN_YDRAG        = 1 << 3,
+  GTK_SHEET_IN_DRAG         = 1 << 4,
+  GTK_SHEET_IN_SELECTION    = 1 << 5,
+  GTK_SHEET_IN_RESIZE       = 1 << 6,
+  GTK_SHEET_IN_CLIP         = 1 << 7,
+  GTK_SHEET_REDRAW_PENDING  = 1 << 8,
+};
+
+#define GTK_SHEET_FLAGS(sheet)             (GTK_SHEET (sheet)->flags)
+#define GTK_SHEET_SET_FLAGS(sheet,flag)    (GTK_SHEET_FLAGS (sheet) |= (flag))
+#define GTK_SHEET_UNSET_FLAGS(sheet,flag)  (GTK_SHEET_FLAGS (sheet) &= ~(flag))
+
+#define GTK_SHEET_IS_FROZEN(sheet)   (GTK_SHEET_FLAGS (sheet) & GTK_SHEET_IS_FROZEN)
+#define GTK_SHEET_IN_XDRAG(sheet)    (GTK_SHEET_FLAGS (sheet) & GTK_SHEET_IN_XDRAG)
+#define GTK_SHEET_IN_YDRAG(sheet)    (GTK_SHEET_FLAGS (sheet) & GTK_SHEET_IN_YDRAG)
+#define GTK_SHEET_IN_DRAG(sheet)     (GTK_SHEET_FLAGS (sheet) & GTK_SHEET_IN_DRAG)
+#define GTK_SHEET_IN_SELECTION(sheet) (GTK_SHEET_FLAGS (sheet) & GTK_SHEET_IN_SELECTION)
+#define GTK_SHEET_IN_RESIZE(sheet) (GTK_SHEET_FLAGS (sheet) & GTK_SHEET_IN_RESIZE)
+#define GTK_SHEET_IN_CLIP(sheet) (GTK_SHEET_FLAGS (sheet) & GTK_SHEET_IN_CLIP)
+#define GTK_SHEET_REDRAW_PENDING(sheet)   (GTK_SHEET_FLAGS (sheet) & GTK_SHEET_REDRAW_PENDING)
+#define CELL_SPACING 1
+#define DRAG_WIDTH 6
+#define TIMEOUT_SCROLL 20
+#define TIMEOUT_FLASH 200
+#define TIME_INTERVAL 8
+#define COLUMN_MIN_WIDTH 10
+#define MINROWS 1
+#define MINCOLS 1
+#define MAXLENGTH 30
+#define CELLOFFSET 4
+#define DEFAULT_COLUMN_WIDTH 80
+
+
+
+
+
+static inline 
+guint DEFAULT_ROW_HEIGHT(GtkWidget *widget) 
+{ 
+  if(!widget->style->font_desc) return 24;
+  else {
+    PangoContext *context = gtk_widget_get_pango_context(widget); 
+    PangoFontMetrics *metrics = pango_context_get_metrics(context,
+                                  widget->style->font_desc,
+                                  pango_context_get_language(context)); 
+    guint val = pango_font_metrics_get_descent(metrics) + 
+                pango_font_metrics_get_ascent(metrics);
+    pango_font_metrics_unref(metrics);
+    return PANGO_PIXELS(val)+2*CELLOFFSET;
+  }
+}
+static inline guint DEFAULT_FONT_ASCENT(GtkWidget *widget) 
+{ 
+  if(!widget->style->font_desc) return 12;
+  else {
+    PangoContext *context = gtk_widget_get_pango_context(widget); 
+    PangoFontMetrics *metrics = pango_context_get_metrics(context,
+                                  widget->style->font_desc,
+                                  pango_context_get_language(context)); 
+    guint val = pango_font_metrics_get_ascent(metrics);
+    pango_font_metrics_unref(metrics);
+    return PANGO_PIXELS(val);
+  }
+}
+static inline guint STRING_WIDTH(GtkWidget *widget,
+                                 const PangoFontDescription *font, const gchar *text)
+{
+  PangoRectangle rect;
+  PangoLayout *layout;
+
+  layout = gtk_widget_create_pango_layout (widget, text);
+  pango_layout_set_font_description (layout, font);
+
+  pango_layout_get_extents (layout, NULL, &rect);
+
+  g_object_unref(G_OBJECT(layout));
+  return PANGO_PIXELS(rect.width);
+}
+
+static inline guint DEFAULT_FONT_DESCENT(GtkWidget *widget) 
+{ 
+  if(!widget->style->font_desc) return 12;
+  else {
+    PangoContext *context = gtk_widget_get_pango_context(widget); 
+    PangoFontMetrics *metrics = pango_context_get_metrics(context,
+                                  widget->style->font_desc,
+                                  pango_context_get_language(context)); 
+    guint val =  pango_font_metrics_get_descent(metrics);
+    pango_font_metrics_unref(metrics);
+    return PANGO_PIXELS(val);
+  }
+}
+
+
+static gint
+yyy_row_is_visible(GtkSheet *sheet, gint row)
+{
+  GSheetRow *row_geo = sheet->row_geometry;
+
+  return g_sheet_row_get_visibility(row_geo, row, sheet);
+}
+
+
+static gint
+yyy_row_is_sensitive(GtkSheet *sheet, gint row)
+{
+  GSheetRow *row_geo = sheet->row_geometry;
+
+  return g_sheet_row_get_sensitivity(row_geo, row, sheet);
+}
+
+
+
+static inline gint
+yyy_row_count(const GtkSheet *sheet)
+{
+  GSheetRow *row_geo = sheet->row_geometry;
+
+  return g_sheet_row_get_row_count(row_geo, sheet);
+}
+
+static inline gint
+yyy_row_height(const GtkSheet *sheet, gint row)
+{
+  GSheetRow *row_geo = sheet->row_geometry;
+
+  return g_sheet_row_get_height(row_geo, row, sheet);
+}
+
+static gint
+yyy_row_top_ypixel(GtkSheet *sheet, gint row)
+{
+  GSheetRow *geo = sheet->row_geometry;
+
+  gint y = g_sheet_row_start_pixel(geo, row, sheet);
+
+  if ( sheet->column_titles_visible ) 
+    y += sheet->column_title_area.height;
+
+  return y;
+}
+
+
+/* Return the row containing pixel Y */
+static gint
+yyy_row_ypixel_to_row(GtkSheet *sheet, gint y)
+{
+  GSheetRow *geo = sheet->row_geometry;
+
+  gint cy = sheet->voffset;
+
+  if(sheet->column_titles_visible) 
+    cy += sheet->column_title_area.height;
+  
+  if(y < cy) return 0;
+
+
+  return g_sheet_row_pixel_to_row(geo, y - cy, sheet);
+}
+
+
+/* gives the top pixel of the given row in context of
+ * the sheet's voffset */
+static inline gint
+ROW_TOP_YPIXEL(GtkSheet *sheet, gint row)
+{
+  return (sheet->voffset + yyy_row_top_ypixel(sheet, row));
+}
+
+
+/* returns the row index from a y pixel location in the 
+ * context of the sheet's voffset */
+static inline gint 
+ROW_FROM_YPIXEL(GtkSheet *sheet, gint y)
+{
+  return (yyy_row_ypixel_to_row(sheet, y));
+}
+
+static inline const GtkSheetButton *
+xxx_column_button(GtkSheet *sheet, gint col)
+{
+  GSheetColumn *col_geo = sheet->column_geometry;
+
+  return g_sheet_column_get_button(col_geo, col, sheet);
+}
+
+
+static inline gint
+xxx_column_left_xpixel(GtkSheet *sheet, gint col)
+{
+  GSheetColumn *geo = sheet->column_geometry;
+
+  gint x = g_sheet_column_start_pixel(geo, col, sheet);
+
+  if ( sheet->row_titles_visible ) 
+    x += sheet->row_title_area.width;
+
+  return x;
+}
+
+static inline gint
+xxx_column_width(const GtkSheet *sheet, gint col)
+{
+  GSheetColumn *col_geo = sheet->column_geometry;
+
+  return g_sheet_column_get_width(col_geo, col, sheet);
+}
+
+
+static inline void
+xxx_set_column_width(GtkSheet *sheet, gint col, gint width)
+{
+  if ( sheet->column_geometry ) 
+    g_sheet_column_set_width(sheet->column_geometry, col, width, sheet);
+}
+
+static inline void
+xxx_column_set_left_column(GtkSheet *sheet, gint col, gint i)
+{
+  GSheetColumn *col_geo = sheet->column_geometry;
+
+  g_sheet_column_set_left_text_column(col_geo, col, i, sheet);
+}
+
+static inline gint
+xxx_column_left_column(const GtkSheet *sheet, gint col)
+{
+  GSheetColumn *col_geo = sheet->column_geometry;
+
+  return g_sheet_column_get_left_text_column(col_geo, col, sheet);
+}
+
+static inline void
+xxx_column_set_right_column(GtkSheet *sheet, gint col, gint i)
+{
+  GSheetColumn *col_geo = sheet->column_geometry;
+
+  g_sheet_column_set_right_text_column(col_geo, col, i, sheet);
+}
+
+static inline gint
+xxx_column_right_column(const GtkSheet *sheet, gint col)
+{
+  GSheetColumn *col_geo = sheet->column_geometry;
+
+  return g_sheet_column_get_right_text_column(col_geo, col, sheet);
+}
+
+static inline GtkJustification
+xxx_column_justification(const GtkSheet *sheet, gint col)
+{
+  GSheetColumn *col_geo = sheet->column_geometry;
+
+  return g_sheet_column_get_justification(col_geo, col, sheet);
+}
+
+static inline gint
+xxx_column_is_visible(GtkSheet *sheet, gint col)
+{
+  GSheetColumn *col_geo = sheet->column_geometry;
+
+  return g_sheet_column_get_visibility(col_geo, col, sheet);
+}
+
+
+static inline gint
+xxx_column_is_sensitive(GtkSheet *sheet, gint col)
+{
+  GSheetColumn *col_geo = sheet->column_geometry;
+
+  return g_sheet_column_get_sensitivity(col_geo, col, sheet);
+}
+
+
+/* gives the left pixel of the given column in context of
+ * the sheet's hoffset */
+static inline gint
+COLUMN_LEFT_XPIXEL(GtkSheet *sheet, gint ncol)
+{
+  return (sheet->hoffset + xxx_column_left_xpixel(sheet, ncol));
+}
+
+static inline gint
+xxx_column_count(const GtkSheet *sheet)
+{
+  GSheetColumn *col_geo = sheet->column_geometry;
+
+  return g_sheet_column_get_column_count(col_geo, sheet);
+}
+
+/* returns the column index from a x pixel location in the 
+ * context of the sheet's hoffset */
+static inline gint
+COLUMN_FROM_XPIXEL (GtkSheet * sheet,
+                   gint x)
+{
+  gint i, cx;
+
+  cx = sheet->hoffset;
+  if( sheet->row_titles_visible ) 
+    cx += sheet->row_title_area.width;
+
+  if(x < cx) return 0;
+  for (i = 0; i < xxx_column_count(sheet); i++)
+    {
+      if (x >= cx  && x <= (cx + xxx_column_width(sheet, i)) && 
+         xxx_column_is_visible(sheet, i))
+       return i;
+      if( xxx_column_is_visible(sheet, i))
+       cx += xxx_column_width(sheet, i);
+    }
+
+  /* no match */
+  return xxx_column_count(sheet) - 1;
+}
+
+/* returns the total height of the sheet */
+static inline gint SHEET_HEIGHT(GtkSheet *sheet)
+{
+  const gint n_rows = yyy_row_count(sheet);
+
+  return yyy_row_top_ypixel(sheet, n_rows - 1) + 
+    yyy_row_height(sheet, n_rows - 1);
+}
+
+
+static inline const GtkSheetButton *
+yyy_row_button(GtkSheet *sheet, gint row)
+{
+  GSheetRow *row_geo = sheet->row_geometry;
+
+  return g_sheet_row_get_button(row_geo, row, sheet);
+}
+
+
+
+
+static inline void
+yyy_set_row_height(GtkSheet *sheet, gint row, gint height)
+{
+  if ( sheet->row_geometry ) 
+    g_sheet_row_set_height(sheet->row_geometry, row, height, sheet);
+}
+
+
+
+/* returns the total width of the sheet */
+static inline gint SHEET_WIDTH(GtkSheet *sheet)
+{
+  gint i,cx;
+
+  cx = ( sheet->row_titles_visible ? sheet->row_title_area.width : 0);
+  for (i=0; i < xxx_column_count(sheet); i++)
+    if(xxx_column_is_visible(sheet, i)) cx += xxx_column_width(sheet, i);
+  
+  return cx;
+}
+
+#define MIN_VISIBLE_ROW(sheet) sheet->view.row0
+#define MAX_VISIBLE_ROW(sheet) sheet->view.rowi
+#define MIN_VISIBLE_COLUMN(sheet) sheet->view.col0
+#define MAX_VISIBLE_COLUMN(sheet) sheet->view.coli
+
+
+static inline gint
+POSSIBLE_XDRAG(GtkSheet *sheet, gint x, gint *drag_column)
+{
+ gint column, xdrag;
+
+ column=COLUMN_FROM_XPIXEL(sheet, x);
+ *drag_column=column;
+
+ xdrag=COLUMN_LEFT_XPIXEL(sheet,column)+CELL_SPACING;
+ if(x <= xdrag+DRAG_WIDTH/2 && column != 0){
+   while(! xxx_column_is_visible(sheet, column-1) && column>0) column--;
+   *drag_column=column-1;
+   return xxx_column_is_sensitive(sheet, column-1);
+ }
+
+ xdrag+= xxx_column_width(sheet, column);
+ if(x >= xdrag-DRAG_WIDTH/2 && x <= xdrag+DRAG_WIDTH/2)
+   return xxx_column_is_sensitive(sheet, column);
+
+ return FALSE;
+} 
+
+static inline gint
+POSSIBLE_YDRAG(GtkSheet *sheet, gint y, gint *drag_row)
+{
+ gint row, ydrag;
+
+ row=ROW_FROM_YPIXEL(sheet, y);
+ *drag_row=row;
+
+ ydrag=ROW_TOP_YPIXEL(sheet,row)+CELL_SPACING;
+ if(y <= ydrag+DRAG_WIDTH/2 && row != 0){
+   while(!yyy_row_is_visible(sheet, row-1) && row>0) row--;
+   *drag_row=row-1;
+   return yyy_row_is_sensitive(sheet, row-1);
+ }
+
+ ydrag+=yyy_row_height(sheet, row);
+
+ if(y >= ydrag-DRAG_WIDTH/2 && y <= ydrag+DRAG_WIDTH/2)
+   return yyy_row_is_sensitive(sheet, row);
+ return FALSE;
+}        
+
+static inline gint POSSIBLE_DRAG(GtkSheet *sheet, gint x, gint y,
+                            gint *drag_row, gint *drag_column)
+{
+  gint ydrag, xdrag;
+
+  *drag_column=COLUMN_FROM_XPIXEL(sheet,x);
+  *drag_row=ROW_FROM_YPIXEL(sheet,y);
+
+  if(x>=COLUMN_LEFT_XPIXEL(sheet,sheet->range.col0)-DRAG_WIDTH/2 &&
+     x<=COLUMN_LEFT_XPIXEL(sheet,sheet->range.coli)+
+     xxx_column_width(sheet, sheet->range.coli) + DRAG_WIDTH/2){
+    ydrag=ROW_TOP_YPIXEL(sheet,sheet->range.row0);
+     if(y>=ydrag-DRAG_WIDTH/2 && y<=ydrag+DRAG_WIDTH/2){
+        *drag_row=sheet->range.row0;
+        return TRUE;
+     }
+     ydrag=ROW_TOP_YPIXEL(sheet,sheet->range.rowi)+
+           yyy_row_height(sheet, sheet->range.rowi);
+     if(y>=ydrag-DRAG_WIDTH/2 && y<=ydrag+DRAG_WIDTH/2){
+        *drag_row=sheet->range.rowi;
+        return TRUE;
+     }
+  }
+
+  if(y>=ROW_TOP_YPIXEL(sheet,sheet->range.row0)-DRAG_WIDTH/2 &&
+     y<=ROW_TOP_YPIXEL(sheet,sheet->range.rowi)+
+        yyy_row_height(sheet, sheet->range.rowi)+DRAG_WIDTH/2){
+     xdrag=COLUMN_LEFT_XPIXEL(sheet,sheet->range.col0);
+     if(x>=xdrag-DRAG_WIDTH/2 && x<=xdrag+DRAG_WIDTH/2){
+        *drag_column=sheet->range.col0;
+        return TRUE;
+     }
+     xdrag=COLUMN_LEFT_XPIXEL(sheet,sheet->range.coli)+
+           xxx_column_width(sheet, sheet->range.coli);
+     if(x>=xdrag-DRAG_WIDTH/2 && x<=xdrag+DRAG_WIDTH/2){
+        *drag_column=sheet->range.coli;
+        return TRUE;
+     }
+  }
+  return FALSE;
+}
+
+static inline gint POSSIBLE_RESIZE(GtkSheet *sheet, gint x, gint y,
+                            gint *drag_row, gint *drag_column)
+{
+  gint xdrag, ydrag;
+  
+  xdrag=COLUMN_LEFT_XPIXEL(sheet,sheet->range.coli)+
+    xxx_column_width(sheet, sheet->range.coli);
+
+  ydrag=ROW_TOP_YPIXEL(sheet,sheet->range.rowi)+
+           yyy_row_height(sheet, sheet->range.rowi);
+
+  if(sheet->state == GTK_SHEET_COLUMN_SELECTED) 
+        ydrag = ROW_TOP_YPIXEL(sheet, sheet->view.row0);
+
+  if(sheet->state == GTK_SHEET_ROW_SELECTED)
+        xdrag = COLUMN_LEFT_XPIXEL(sheet, sheet->view.col0);
+
+  *drag_column=COLUMN_FROM_XPIXEL(sheet,x);
+  *drag_row=ROW_FROM_YPIXEL(sheet,y);
+
+  if(x>=xdrag-DRAG_WIDTH/2 && x<=xdrag+DRAG_WIDTH/2 &&
+     y>=ydrag-DRAG_WIDTH/2 && y<=ydrag+DRAG_WIDTH/2) return TRUE;
+
+  return FALSE;  
+}
+
+static void gtk_sheet_class_init               (GtkSheetClass * klass);
+static void gtk_sheet_init                     (GtkSheet * sheet);
+static void gtk_sheet_destroy                  (GtkObject * object);
+static void gtk_sheet_finalize                         (GObject * object);
+static void gtk_sheet_style_set                (GtkWidget *widget,
+                                                GtkStyle  *previous_style);
+static void gtk_sheet_realize                  (GtkWidget * widget);
+static void gtk_sheet_unrealize                (GtkWidget * widget);
+static void gtk_sheet_map                      (GtkWidget * widget);
+static void gtk_sheet_unmap                    (GtkWidget * widget);
+static gint gtk_sheet_expose                   (GtkWidget * widget,
+                                                GdkEventExpose * event);
+static void gtk_sheet_forall                   (GtkContainer *container,
+                                                gboolean include_internals,
+                                                GtkCallback  callback, 
+                                                gpointer  callback_data); 
+
+static void gtk_sheet_set_scroll_adjustments   (GtkSheet *sheet,
+                                                GtkAdjustment *hadjustment,
+                                                GtkAdjustment *vadjustment);
+
+static gint gtk_sheet_button_press             (GtkWidget * widget,
+                                                GdkEventButton * event);
+static gint gtk_sheet_button_release           (GtkWidget * widget,
+                                                GdkEventButton * event);
+static gint gtk_sheet_motion                   (GtkWidget * widget,
+                                                GdkEventMotion * event);
+static gint gtk_sheet_entry_key_press          (GtkWidget *widget,
+                                                GdkEventKey *key);
+static gint gtk_sheet_key_press                        (GtkWidget *widget,
+                                                GdkEventKey *key);
+static void gtk_sheet_size_request             (GtkWidget * widget,
+                                                GtkRequisition * requisition);
+static void gtk_sheet_size_allocate            (GtkWidget * widget,
+                                                GtkAllocation * allocation);
+
+/* Sheet queries */
+
+static gint gtk_sheet_range_isvisible          (GtkSheet * sheet,
+                                                GtkSheetRange range);
+static gint gtk_sheet_cell_isvisible           (GtkSheet * sheet,
+                                                gint row, gint column);
+/* Clipped Range */
+
+static gint gtk_sheet_scroll                   (gpointer data);
+static gint gtk_sheet_flash                    (gpointer data);
+
+/* Drawing Routines */
+
+/* draw cell background and frame */
+static void gtk_sheet_cell_draw_default        (GtkSheet *sheet, 
+                                                gint row, gint column);
+
+/* draw cell border */
+static void gtk_sheet_cell_draw_border                 (GtkSheet *sheet, 
+                                                gint row, gint column, 
+                                                gint mask);
+
+/* draw cell contents */
+static void gtk_sheet_cell_draw_label          (GtkSheet *sheet, 
+                                                gint row, gint column);
+
+/* draw visible part of range. If range==NULL then draw the whole screen */
+static void gtk_sheet_range_draw               (GtkSheet *sheet, 
+                                                const GtkSheetRange *range);
+
+/* highlight the visible part of the selected range */
+static void gtk_sheet_range_draw_selection     (GtkSheet *sheet, 
+                                                GtkSheetRange range);
+
+/* Selection */
+
+static gint gtk_sheet_move_query               (GtkSheet *sheet, 
+                                                gint row, gint column);
+static void gtk_sheet_real_select_range        (GtkSheet * sheet,
+                                                GtkSheetRange * range);
+static void gtk_sheet_real_unselect_range      (GtkSheet * sheet,
+                                                const GtkSheetRange * range);
+static void gtk_sheet_extend_selection         (GtkSheet *sheet, 
+                                                gint row, gint column);
+static void gtk_sheet_new_selection            (GtkSheet *sheet, 
+                                                GtkSheetRange *range);
+static void gtk_sheet_draw_border              (GtkSheet *sheet, 
+                                                GtkSheetRange range);
+static void gtk_sheet_draw_corners             (GtkSheet *sheet,
+                                                GtkSheetRange range);
+
+
+/* Active Cell handling */
+
+static void gtk_sheet_entry_changed            (GtkWidget *widget, 
+                                                gpointer data);
+static gboolean gtk_sheet_deactivate_cell      (GtkSheet *sheet);
+static void gtk_sheet_hide_active_cell         (GtkSheet *sheet);
+static gboolean gtk_sheet_activate_cell                (GtkSheet *sheet, 
+                                                gint row, gint col);
+static void gtk_sheet_draw_active_cell         (GtkSheet *sheet);
+static void gtk_sheet_show_active_cell         (GtkSheet *sheet);
+static void gtk_sheet_click_cell               (GtkSheet *sheet, 
+                                                gint row, 
+                                                gint column,
+                                                gboolean *veto);
+
+/* Backing Pixmap */
+
+static void gtk_sheet_make_backing_pixmap      (GtkSheet *sheet, 
+                                                guint width, guint height);
+static void gtk_sheet_draw_backing_pixmap      (GtkSheet *sheet, 
+                                                GtkSheetRange range);
+/* Scrollbars */
+
+static void adjust_scrollbars                  (GtkSheet * sheet);
+static void vadjustment_changed                        (GtkAdjustment * adjustment,
+                                                gpointer data);
+static void hadjustment_changed                (GtkAdjustment * adjustment,
+                                                gpointer data);
+static void vadjustment_value_changed          (GtkAdjustment * adjustment,
+                                                gpointer data);
+static void hadjustment_value_changed          (GtkAdjustment * adjustment,
+                                                gpointer data);
+
+
+static void draw_xor_vline                     (GtkSheet * sheet);
+static void draw_xor_hline                     (GtkSheet * sheet);
+static void draw_xor_rectangle                 (GtkSheet *sheet, 
+                                                GtkSheetRange range);
+static void gtk_sheet_draw_flashing_range      (GtkSheet *sheet, 
+                                                GtkSheetRange range);
+static guint new_column_width                  (GtkSheet * sheet,
+                                                gint column,
+                                                gint * x);
+static guint new_row_height                    (GtkSheet * sheet,
+                                                gint row,
+                                                gint * y);
+/* Sheet Button */
+
+static void create_global_button               (GtkSheet *sheet);
+static void global_button_clicked              (GtkWidget *widget, 
+                                                gpointer data);
+/* Sheet Entry */
+
+static void create_sheet_entry                 (GtkSheet *sheet);
+static void gtk_sheet_size_allocate_entry      (GtkSheet *sheet);
+static void gtk_sheet_entry_set_max_size       (GtkSheet *sheet);
+
+/* Sheet button gadgets */
+
+static void size_allocate_column_title_buttons         (GtkSheet * sheet);
+static void size_allocate_row_title_buttons    (GtkSheet * sheet);
+
+
+static void gtk_sheet_button_draw              (GtkSheet *sheet, 
+                                                gint row, gint column);
+static void size_allocate_global_button        (GtkSheet *sheet);
+static void gtk_sheet_button_size_request      (GtkSheet *sheet,
+                                                const GtkSheetButton *button, 
+                                                GtkRequisition *requisition);
+
+/* Attributes routines */
+static void init_attributes                    (GtkSheet *sheet, gint col,  
+                                                GtkSheetCellAttr *attributes);
+
+
+/* Memory allocation routines */
+static void gtk_sheet_real_range_clear                 (GtkSheet *sheet, 
+                                                const GtkSheetRange *range, 
+                                                gboolean delete);
+static void gtk_sheet_real_cell_clear          (GtkSheet *sheet, 
+                                                gint row,
+                                                gint column,
+                                                gboolean delete);
+
+
+/* Container Functions */
+static void gtk_sheet_remove                   (GtkContainer *container,
+                                                GtkWidget *widget);
+static void gtk_sheet_realize_child            (GtkSheet *sheet,
+                                                GtkSheetChild *child);
+static void gtk_sheet_position_child           (GtkSheet *sheet,
+                                                GtkSheetChild *child);
+static void gtk_sheet_position_children                (GtkSheet *sheet);
+static void gtk_sheet_child_show               (GtkSheetChild *child); 
+static void gtk_sheet_child_hide               (GtkSheetChild *child); 
+static void gtk_sheet_column_size_request       (GtkSheet *sheet,
+                                                 gint col,
+                                                 guint *requisition);
+static void gtk_sheet_row_size_request          (GtkSheet *sheet,
+                                                 gint row,
+                                                 guint *requisition);
+
+
+/* Signals */
+
+extern void 
+_gtkextra_signal_emit(GtkObject *object, guint signal_id, ...);
+
+enum {
+      SELECT_ROW, 
+      SELECT_COLUMN, 
+      DOUBLE_CLICK_ROW,
+      DOUBLE_CLICK_COLUMN,
+      SELECT_RANGE,
+      CLIP_RANGE,
+      RESIZE_RANGE,
+      MOVE_RANGE,
+      TRAVERSE, 
+      DEACTIVATE, 
+      ACTIVATE,
+      SET_CELL,
+      CLEAR_CELL,
+      CHANGED,
+      NEW_COL_WIDTH,
+      NEW_ROW_HEIGHT,
+      LAST_SIGNAL
+};
+
+static GtkContainerClass *parent_class = NULL;
+static guint sheet_signals[LAST_SIGNAL] = {0};
+
+
+GType
+gtk_sheet_get_type ()
+{
+  static GType sheet_type = 0;
+                                                                                
+  if (!sheet_type)
+    {
+      static const GTypeInfo sheet_info =
+      {
+        sizeof (GtkSheetClass),
+        NULL,
+        NULL,
+        (GClassInitFunc) gtk_sheet_class_init,
+        NULL,        
+        NULL,       
+        sizeof (GtkSheet),
+        0,         
+        (GInstanceInitFunc) gtk_sheet_init,
+        NULL,
+      };
+      sheet_type =
+        g_type_register_static (GTK_TYPE_CONTAINER, "GtkSheet",
+                                &sheet_info, 0);
+    }
+  return sheet_type;
+}
+
+static GtkSheetRange*
+gtk_sheet_range_copy (const GtkSheetRange *range)
+{
+  GtkSheetRange *new_range;
+
+  g_return_val_if_fail (range != NULL, NULL);
+
+  new_range = g_new (GtkSheetRange, 1);
+
+  *new_range = *range;
+
+  return new_range;
+}
+
+static void
+gtk_sheet_range_free (GtkSheetRange *range)
+{
+  g_return_if_fail (range != NULL);
+
+  g_free (range);
+}
+
+GType
+gtk_sheet_range_get_type (void)
+{
+  static GType sheet_range_type=0;
+
+  if(!sheet_range_type)
+  {
+    sheet_range_type = g_boxed_type_register_static("GtkSheetRange", (GBoxedCopyFunc)gtk_sheet_range_copy, (GBoxedFreeFunc)gtk_sheet_range_free);
+  }
+  return sheet_range_type;
+
+}
+
+static void
+gtk_sheet_class_init (GtkSheetClass * klass)
+{
+  GtkObjectClass *object_class;
+  GtkWidgetClass *widget_class;
+  GtkContainerClass *container_class;
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  object_class = (GtkObjectClass *) klass;
+  widget_class = (GtkWidgetClass *) klass;
+  container_class = (GtkContainerClass *) klass;
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  /**
+   * GtkSheet::select-row
+   * @sheet: the sheet widget that emitted the signal
+   * @row: the newly selected row index
+   *
+   * A row has been selected.
+   */
+  sheet_signals[SELECT_ROW] =
+    gtk_signal_new ("select-row",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   GTK_SIGNAL_OFFSET (GtkSheetClass, select_row),
+                   gtkextra_VOID__INT,
+                    GTK_TYPE_NONE, 1, GTK_TYPE_INT);
+
+  /**
+   * GtkSheet::select-column
+   * @sheet: the sheet widget that emitted the signal
+   * @column: the newly selected column index
+   *
+   * A column has been selected.
+   */
+  sheet_signals[SELECT_COLUMN] =
+    gtk_signal_new ("select-column",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   GTK_SIGNAL_OFFSET (GtkSheetClass, select_column),
+                   gtkextra_VOID__INT,
+                    GTK_TYPE_NONE, 1, GTK_TYPE_INT);
+
+
+  /**
+   * GtkSheet::double-click-row
+   * @sheet: the sheet widget that emitted the signal
+   * @row: the row that was double clicked.
+   *
+   * A row's title button has been double clicked
+   */
+  sheet_signals[DOUBLE_CLICK_ROW] =
+    gtk_signal_new ("double-click-row",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   0,
+                   gtkextra_VOID__INT,
+                    GTK_TYPE_NONE, 1, GTK_TYPE_INT);
+
+
+  /**
+   * GtkSheet::double-click-column
+   * @sheet: the sheet widget that emitted the signal
+   * @column: the column that was double clicked.
+   *
+   * A column's title button has been double clicked
+   */
+  sheet_signals[DOUBLE_CLICK_COLUMN] =
+    gtk_signal_new ("double-click-column",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   0,
+                   gtkextra_VOID__INT,
+                    GTK_TYPE_NONE, 1, GTK_TYPE_INT);
+
+  sheet_signals[SELECT_RANGE] =
+    gtk_signal_new ("select-range",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   GTK_SIGNAL_OFFSET (GtkSheetClass, select_range),
+                    gtkextra_VOID__BOXED,
+                   GTK_TYPE_NONE, 1, GTK_TYPE_SHEET_RANGE);
+
+  sheet_signals[CLIP_RANGE] =
+    gtk_signal_new ("clip-range",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   GTK_SIGNAL_OFFSET (GtkSheetClass, clip_range),
+                    gtkextra_VOID__BOXED,
+                   GTK_TYPE_NONE, 1, GTK_TYPE_SHEET_RANGE);
+
+  sheet_signals[RESIZE_RANGE] =
+    gtk_signal_new ("resize-range",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   GTK_SIGNAL_OFFSET (GtkSheetClass, resize_range),
+                   gtkextra_VOID__BOXED_BOXED,
+                   GTK_TYPE_NONE, 2, GTK_TYPE_SHEET_RANGE, GTK_TYPE_SHEET_RANGE);
+  sheet_signals[MOVE_RANGE] =
+    gtk_signal_new ("move-range",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   GTK_SIGNAL_OFFSET (GtkSheetClass, move_range),
+                   gtkextra_VOID__BOXED_BOXED,
+                    GTK_TYPE_NONE, 2, GTK_TYPE_SHEET_RANGE, GTK_TYPE_SHEET_RANGE);
+  sheet_signals[TRAVERSE] =
+    gtk_signal_new ("traverse",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   GTK_SIGNAL_OFFSET (GtkSheetClass, traverse),
+                    gtkextra_BOOLEAN__INT_INT_POINTER_POINTER,
+                   GTK_TYPE_BOOL, 4, GTK_TYPE_INT, GTK_TYPE_INT,
+                                      GTK_TYPE_POINTER, GTK_TYPE_POINTER);
+
+  sheet_signals[DEACTIVATE] =
+    gtk_signal_new ("deactivate",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   GTK_SIGNAL_OFFSET (GtkSheetClass, deactivate),
+                    gtkextra_BOOLEAN__INT_INT,
+                   GTK_TYPE_BOOL, 2, GTK_TYPE_INT, GTK_TYPE_INT);
+
+  sheet_signals[ACTIVATE] =
+    gtk_signal_new ("activate",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   GTK_SIGNAL_OFFSET (GtkSheetClass, activate),
+                    gtkextra_BOOLEAN__INT_INT,
+                   GTK_TYPE_BOOL, 2, GTK_TYPE_INT, GTK_TYPE_INT);
+
+  sheet_signals[SET_CELL] =
+    gtk_signal_new ("set-cell",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   GTK_SIGNAL_OFFSET (GtkSheetClass, set_cell),
+                    gtkextra_VOID__INT_INT,
+                   GTK_TYPE_NONE, 2, GTK_TYPE_INT, GTK_TYPE_INT);
+
+  sheet_signals[CLEAR_CELL] =
+    gtk_signal_new ("clear-cell",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   GTK_SIGNAL_OFFSET (GtkSheetClass, clear_cell),
+                    gtkextra_VOID__INT_INT,
+                   GTK_TYPE_NONE, 2, GTK_TYPE_INT, GTK_TYPE_INT);
+
+  sheet_signals[CHANGED] =
+    gtk_signal_new ("changed",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   GTK_SIGNAL_OFFSET (GtkSheetClass, changed),
+                    gtkextra_VOID__INT_INT,
+                   GTK_TYPE_NONE, 2, GTK_TYPE_INT, GTK_TYPE_INT);
+
+  sheet_signals[NEW_COL_WIDTH] =
+    gtk_signal_new ("new-column-width",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   GTK_SIGNAL_OFFSET (GtkSheetClass, changed),
+                    gtkextra_VOID__INT_INT,
+                   GTK_TYPE_NONE, 2, GTK_TYPE_INT, GTK_TYPE_INT);
+
+  sheet_signals[NEW_ROW_HEIGHT] =
+    gtk_signal_new ("new-row-height",
+                   GTK_RUN_LAST,
+                   GTK_CLASS_TYPE(object_class),
+                   GTK_SIGNAL_OFFSET (GtkSheetClass, changed),
+                    gtkextra_VOID__INT_INT,
+                   GTK_TYPE_NONE, 2, GTK_TYPE_INT, GTK_TYPE_INT);
+
+  widget_class->set_scroll_adjustments_signal =
+    gtk_signal_new ("set-scroll-adjustments",
+                    GTK_RUN_LAST,
+                    GTK_CLASS_TYPE(object_class),
+                    GTK_SIGNAL_OFFSET (GtkSheetClass, set_scroll_adjustments),
+                    gtkextra_VOID__OBJECT_OBJECT,
+                    GTK_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
+
+
+  container_class->add = NULL;
+  container_class->remove = gtk_sheet_remove;
+  container_class->forall = gtk_sheet_forall;
+
+  object_class->destroy = gtk_sheet_destroy;
+  gobject_class->finalize = gtk_sheet_finalize;
+
+  widget_class->realize = gtk_sheet_realize;
+  widget_class->unrealize = gtk_sheet_unrealize;
+  widget_class->map = gtk_sheet_map;
+  widget_class->unmap = gtk_sheet_unmap;
+  widget_class->style_set = gtk_sheet_style_set;
+  widget_class->button_press_event = gtk_sheet_button_press;
+  widget_class->button_release_event = gtk_sheet_button_release;
+  widget_class->motion_notify_event = gtk_sheet_motion;
+  widget_class->key_press_event = gtk_sheet_key_press;
+  widget_class->expose_event = gtk_sheet_expose;
+  widget_class->size_request = gtk_sheet_size_request;
+  widget_class->size_allocate = gtk_sheet_size_allocate;
+  widget_class->focus_in_event = NULL;
+  widget_class->focus_out_event = NULL;
+
+  klass->set_scroll_adjustments = gtk_sheet_set_scroll_adjustments;
+  klass->select_row = NULL;
+  klass->select_column = NULL;
+  klass->select_range = NULL;
+  klass->clip_range = NULL;
+  klass->resize_range = NULL;
+  klass->move_range = NULL;
+  klass->traverse = NULL;
+  klass->deactivate = NULL;
+  klass->activate = NULL;
+  klass->set_cell = NULL;
+  klass->clear_cell = NULL;
+  klass->changed = NULL;
+
+}
+
+static void 
+gtk_sheet_init (GtkSheet *sheet)
+{
+  sheet->column_geometry = NULL;
+  sheet->row_geometry = NULL;
+
+  sheet->children = NULL;
+
+  sheet->flags = 0;
+  sheet->selection_mode = GTK_SELECTION_BROWSE;
+  sheet->freeze_count = 0;
+  sheet->state = GTK_SHEET_NORMAL;
+
+  GTK_WIDGET_UNSET_FLAGS (sheet, GTK_NO_WINDOW);
+  GTK_WIDGET_SET_FLAGS (sheet, GTK_CAN_FOCUS);
+
+  sheet->view.row0 = 0;
+  sheet->view.col0 = 0;
+  sheet->view.rowi = 0;
+  sheet->view.coli = 0;
+
+  sheet->column_title_window=NULL;
+  sheet->column_title_area.x=0;
+  sheet->column_title_area.y=0;
+  sheet->column_title_area.width=0;
+  sheet->column_title_area.height=DEFAULT_ROW_HEIGHT(GTK_WIDGET(sheet));
+  sheet->row_title_window=NULL;
+  sheet->row_title_area.x=0;
+  sheet->row_title_area.y=0;
+  sheet->row_title_area.width = DEFAULT_COLUMN_WIDTH;
+  sheet->row_title_area.height=0;
+
+
+  sheet->active_cell.row=0;
+  sheet->active_cell.col=0;
+  sheet->selection_cell.row=0;
+  sheet->selection_cell.col=0;
+
+  sheet->sheet_entry=NULL;
+  sheet->pixmap=NULL;
+
+  sheet->range.row0=0;
+  sheet->range.rowi=0;
+  sheet->range.col0=0;
+  sheet->range.coli=0;
+
+  sheet->state=GTK_SHEET_NORMAL;
+
+  sheet->sheet_window = NULL;
+  sheet->sheet_window_width = 0;
+  sheet->sheet_window_height = 0;
+  sheet->sheet_entry = NULL;
+  sheet->button = NULL;
+
+  sheet->hoffset = 0;
+  sheet->voffset = 0;
+
+  sheet->hadjustment = NULL;
+  sheet->vadjustment = NULL;
+
+  sheet->cursor_drag = gdk_cursor_new(GDK_PLUS);
+  sheet->xor_gc = NULL;
+  sheet->fg_gc = NULL;
+  sheet->bg_gc = NULL;
+  sheet->x_drag = 0;
+  sheet->y_drag = 0;
+
+  gdk_color_parse("white", &sheet->bg_color);
+  gdk_color_alloc(gdk_colormap_get_system(), &sheet->bg_color);
+  gdk_color_parse("gray", &sheet->grid_color);
+  gdk_color_alloc(gdk_colormap_get_system(), &sheet->grid_color);
+  sheet->show_grid = TRUE;
+}
+
+
+static void 
+rows_deleted_callback (GSheetModel *m, gint first_row, gint n_rows,
+                     gpointer data)
+{
+  GtkSheet *sheet = GTK_SHEET(data);
+
+  GtkSheetRange range;
+
+  range.row0 = first_row;
+  range.col0 = 0;
+  range.rowi = yyy_row_count(sheet) - 1;
+  range.coli = xxx_column_count(sheet) - 1;
+  
+  gtk_sheet_range_draw(sheet, &range);
+}
+
+/* 
+   If row0 or rowi are negative, then all rows will be updated.
+   If col0 or coli are negative, then all columns will be updated.
+*/
+static void 
+range_update_callback (GSheetModel *m, gint row0, gint col0, 
+     gint rowi, gint coli, gpointer data)
+{
+  GtkSheet *sheet = GTK_SHEET(data);
+
+  GtkSheetRange range;
+
+  range.row0 = row0;
+  range.col0 = col0;
+  range.rowi = rowi;
+  range.coli = coli;
+
+  if( ( row0 < 0  && col0 < 0 ) || ( rowi < 0  && coli < 0 ) )
+    {
+      gtk_sheet_range_draw(sheet, NULL);
+      return;
+    }
+  else if ( row0 < 0 || rowi < 0 )
+    {
+      range.row0=MIN_VISIBLE_ROW(sheet);
+      range.rowi=MAX_VISIBLE_ROW(sheet);
+    }
+  else if ( col0 < 0 || coli < 0 )
+    {
+      range.col0=MIN_VISIBLE_COLUMN(sheet);
+      range.coli=MAX_VISIBLE_COLUMN(sheet);
+    }
+  
+  gtk_sheet_range_draw(sheet, &range);
+}
+
+
+static void gtk_sheet_construct        (GtkSheet *sheet,
+                                GSheetRow *vgeo,
+                                GSheetColumn *hgeo,
+                                const gchar *title);
+
+
+/**
+ * gtk_sheet_new:
+ * @rows: initial number of rows
+ * @columns: initial number of columns
+ * @title: sheet title
+ * @model: the model to use for the sheet data
+ *
+ * Creates a new sheet widget with the given number of rows and columns.
+ *
+ * Returns: the new sheet widget
+ */
+GtkWidget *
+gtk_sheet_new (GSheetRow *vgeo, GSheetColumn *hgeo, const gchar *title,
+              GSheetModel *model)
+{
+  GtkWidget *widget;
+
+  widget = gtk_type_new (gtk_sheet_get_type ());
+
+  gtk_sheet_construct(GTK_SHEET(widget), vgeo, hgeo, title);
+
+  if (model) 
+    gtk_sheet_set_model(GTK_SHEET(widget), model);
+
+
+  return widget;
+}
+
+
+/**
+ * gtk_sheet_set_model
+ * @sheet: the sheet to set the model for
+ * @model: the model to use for the sheet data
+ *
+ * Sets the model for a GtkSheet
+ *
+ */
+void
+gtk_sheet_set_model(GtkSheet *sheet, GSheetModel *model)
+{
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+  g_return_if_fail (G_IS_SHEET_MODEL (model));
+
+  sheet->model = model;
+
+  g_signal_connect(model, "range_changed", 
+                  G_CALLBACK(range_update_callback), sheet);
+
+  g_signal_connect(model, "rows_deleted",
+                  G_CALLBACK(rows_deleted_callback), sheet);
+
+}
+
+
+
+static void
+column_titles_changed(GtkWidget *w, gint first, gint n_columns, gpointer data)
+{
+  GtkSheet *sheet = GTK_SHEET(data);
+
+
+  if(!GTK_SHEET_IS_FROZEN(sheet)){  
+    gint i;
+    for ( i = first ; i <= MAX_VISIBLE_COLUMN(sheet) ; ++i ) 
+      {
+       gtk_sheet_button_draw(sheet, -1, i);
+       gtk_signal_emit(GTK_OBJECT(sheet), sheet_signals[CHANGED], -1, i);
+      }
+  }
+}
+
+static void
+gtk_sheet_construct (GtkSheet *sheet, 
+                    GSheetRow *vgeo,
+                    GSheetColumn *hgeo,
+                    const gchar *title)
+{
+  g_return_if_fail(G_IS_SHEET_COLUMN(hgeo));
+  g_return_if_fail(G_IS_SHEET_ROW(vgeo));
+
+  sheet->column_geometry = hgeo;
+  sheet->row_geometry = vgeo;
+  
+
+  sheet->columns_resizable = TRUE;
+  sheet->rows_resizable = TRUE;
+
+  sheet->row_titles_visible = TRUE;
+  sheet->row_title_area.width = DEFAULT_COLUMN_WIDTH;
+
+  sheet->column_titles_visible = TRUE;
+  sheet->autoscroll = TRUE;
+  sheet->justify_entry = TRUE;
+  sheet->locked = FALSE;
+
+
+  /* create sheet entry */
+  sheet->entry_type = 0;
+  create_sheet_entry (sheet);
+
+  /* create global selection button */
+  create_global_button(sheet);
+
+  if(title)
+     sheet->name = g_strdup(title);
+
+  g_signal_connect(sheet->column_geometry, "columns_changed", 
+                  G_CALLBACK(column_titles_changed), sheet);
+
+}
+
+
+GtkWidget *
+gtk_sheet_new_with_custom_entry (GSheetRow *rows, GSheetColumn *columns, const gchar *title,
+                                 GtkType entry_type)
+{
+  GtkWidget *widget;
+  
+  widget = gtk_type_new (gtk_sheet_get_type ());
+
+  gtk_sheet_construct_with_custom_entry(GTK_SHEET(widget), 
+                                       rows, columns, title, entry_type);
+  return widget;
+}
+
+void
+gtk_sheet_construct_with_custom_entry (GtkSheet *sheet, 
+                                      GSheetRow *vgeo,
+                                      GSheetColumn *hgeo,
+                                      const gchar *title,
+                                      GtkType entry_type)
+{
+  gtk_sheet_construct(sheet, vgeo, hgeo, title);
+
+  sheet->entry_type = entry_type;
+  create_sheet_entry(sheet);
+}
+
+
+
+void
+gtk_sheet_change_entry(GtkSheet *sheet, GtkType entry_type)
+{
+  gint state;
+
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  state = sheet->state;
+
+  if(sheet->state == GTK_SHEET_NORMAL)
+      gtk_sheet_hide_active_cell(sheet);
+
+  sheet->entry_type = entry_type;
+
+  create_sheet_entry(sheet);
+
+  if(state == GTK_SHEET_NORMAL)
+    {
+      gtk_sheet_show_active_cell(sheet); 
+      g_signal_connect(G_OBJECT(gtk_sheet_get_entry(sheet)),
+                        "changed",
+                        G_CALLBACK(gtk_sheet_entry_changed),
+                        sheet);
+
+
+    }
+}
+
+void
+gtk_sheet_show_grid(GtkSheet *sheet, gboolean show)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  if(show == sheet->show_grid) return;
+  sheet->show_grid = show;
+
+  if(!GTK_SHEET_IS_FROZEN(sheet)) 
+    gtk_sheet_range_draw(sheet, NULL);
+}
+
+gboolean
+gtk_sheet_grid_visible(GtkSheet *sheet)
+{
+  g_return_val_if_fail (sheet != NULL, 0);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), 0);
+
+  return sheet->show_grid;
+}
+
+void
+gtk_sheet_set_background(GtkSheet *sheet, GdkColor *color)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  if(!color) {
+    gdk_color_parse("white", &sheet->bg_color);
+    gdk_color_alloc(gdk_colormap_get_system(), &sheet->bg_color);
+  } else
+    sheet->bg_color = *color;
+
+  if(!GTK_SHEET_IS_FROZEN(sheet)) 
+    gtk_sheet_range_draw(sheet, NULL);
+}
+
+void
+gtk_sheet_set_grid(GtkSheet *sheet, GdkColor *color)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  if(!color){
+    gdk_color_parse("black", &sheet->grid_color);
+    gdk_color_alloc(gdk_colormap_get_system(), &sheet->grid_color);
+  }else
+    sheet->grid_color = *color;
+
+  if(!GTK_SHEET_IS_FROZEN(sheet)) 
+    gtk_sheet_range_draw(sheet, NULL);
+}
+
+guint
+gtk_sheet_get_columns_count(GtkSheet *sheet)
+{
+  g_return_val_if_fail (sheet != NULL, 0);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), 0);
+
+  return xxx_column_count(sheet);
+}
+
+guint
+gtk_sheet_get_rows_count(GtkSheet *sheet)
+{
+  g_return_val_if_fail (sheet != NULL, 0);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), 0);
+
+  return yyy_row_count(sheet);
+}
+
+gint
+gtk_sheet_get_state(GtkSheet *sheet)
+{
+  g_return_val_if_fail (sheet != NULL, 0);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), 0);
+
+  return (sheet->state);
+}
+
+void
+gtk_sheet_set_selection_mode(GtkSheet *sheet, gint mode)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  if(GTK_WIDGET_REALIZED(sheet))
+   gtk_sheet_real_unselect_range(sheet, NULL);
+
+  sheet->selection_mode = mode;
+}
+
+void
+gtk_sheet_set_autoresize                (GtkSheet *sheet, gboolean autoresize)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  sheet->autoresize = autoresize;
+}
+
+gboolean
+gtk_sheet_autoresize                    (GtkSheet *sheet)
+{
+  g_return_val_if_fail (sheet != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), FALSE);
+
+  return sheet->autoresize;
+}
+
+static void
+gtk_sheet_set_column_width (GtkSheet * sheet,
+                           gint column,
+                           guint width);
+
+
+static void
+gtk_sheet_autoresize_column (GtkSheet *sheet, gint column)
+{
+  gint text_width = 0;
+  gint row;
+
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+  if (column >= xxx_column_count(sheet) || column < 0) return;
+
+  g_print("%s:%d Iterating rows\n",__FILE__, __LINE__);
+  for (row = 0; row < yyy_row_count(sheet); row++){
+    const gchar *text = gtk_sheet_cell_get_text(sheet, row, column);
+    if (text && strlen(text) > 0){
+      GtkSheetCellAttr attributes;
+
+      gtk_sheet_get_attributes(sheet, row, column, &attributes);
+      if(attributes.is_visible){
+        gint width = STRING_WIDTH(GTK_WIDGET(sheet),
+                                  attributes.font_desc,
+                                 text)
+                   + 2*CELLOFFSET + attributes.border.width;
+        text_width = MAX (text_width, width);
+      }
+    }
+  }
+
+  if(text_width > xxx_column_width(sheet, column) )
+    {
+      gtk_sheet_set_column_width(sheet, column, text_width);
+      GTK_SHEET_SET_FLAGS(sheet, GTK_SHEET_REDRAW_PENDING);
+    }
+}
+
+
+void
+gtk_sheet_set_autoscroll                (GtkSheet *sheet, gboolean autoscroll)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  sheet->autoscroll = autoscroll;
+}
+
+gboolean
+gtk_sheet_autoscroll                    (GtkSheet *sheet)
+{
+  g_return_val_if_fail (sheet != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), FALSE);
+
+  return sheet->autoscroll;
+}
+
+void
+gtk_sheet_set_clip_text                (GtkSheet *sheet, gboolean clip_text)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  sheet->clip_text = clip_text;
+}
+
+gboolean
+gtk_sheet_clip_text                    (GtkSheet *sheet)
+{
+  g_return_val_if_fail (sheet != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), FALSE);
+
+  return sheet->clip_text;
+}
+
+void
+gtk_sheet_set_justify_entry             (GtkSheet *sheet, gboolean justify)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  sheet->justify_entry = justify;
+}
+
+gboolean
+gtk_sheet_justify_entry                    (GtkSheet *sheet)
+{
+  g_return_val_if_fail (sheet != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), FALSE);
+
+  return sheet->justify_entry;
+}
+
+void
+gtk_sheet_set_locked             (GtkSheet *sheet, gboolean locked)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  sheet->locked = locked;
+}
+
+gboolean
+gtk_sheet_locked                    (GtkSheet *sheet)
+{
+  g_return_val_if_fail (sheet != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), FALSE);
+
+  return sheet->locked;
+}
+
+/* This routine has problems with gtk+-1.2 related with the
+ * label/button drawing - I think it's a bug in gtk+-1.2 */
+
+void
+gtk_sheet_set_title(GtkSheet *sheet, const gchar *title)
+{
+/*  GtkWidget *old_widget;
+*/  GtkWidget *label;
+
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (title != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  if (sheet->name)
+    g_free (sheet->name);
+
+  sheet->name = g_strdup (title);
+
+  if(!GTK_WIDGET_REALIZED(GTK_WIDGET(sheet)) || !title) return;
+
+  if(GTK_BIN(sheet->button)->child)
+           label = GTK_BIN(sheet->button)->child;
+/*
+  gtk_label_set_text(GTK_LABEL(label), title);
+*/
+  size_allocate_global_button(sheet);
+
+  /* remove and destroy the old widget */
+/*
+  old_widget = GTK_BIN (sheet->button)->child;
+  if (old_widget)
+    {
+      gtk_container_remove (GTK_CONTAINER (sheet->button), old_widget);
+    }
+
+  label = gtk_label_new (title);
+  gtk_misc_set_alignment(GTK_MISC(label), 0.5 , 0.5 );
+
+  gtk_container_add (GTK_CONTAINER (sheet->button), label);
+  gtk_widget_show (label);
+
+  size_allocate_global_button(sheet);
+
+  gtk_signal_emit(GTK_OBJECT(sheet),sheet_signals[CHANGED], -1, -1);
+
+  if(old_widget)
+      gtk_widget_destroy (old_widget);
+*/
+}
+
+void
+gtk_sheet_freeze (GtkSheet *sheet)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  sheet->freeze_count++;
+  GTK_SHEET_SET_FLAGS(sheet, GTK_SHEET_IS_FROZEN);
+}
+
+void
+gtk_sheet_thaw(GtkSheet *sheet)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  if(sheet->freeze_count == 0) return;
+
+  sheet->freeze_count--;
+  if(sheet->freeze_count > 0) return;
+
+  adjust_scrollbars(sheet);
+
+  GTK_SHEET_UNSET_FLAGS(sheet, GTK_SHEET_IS_FROZEN);
+
+  sheet->old_vadjustment = -1.;
+  sheet->old_hadjustment = -1.;
+
+  if(sheet->hadjustment)
+      gtk_signal_emit_by_name (GTK_OBJECT (sheet->hadjustment), 
+                             "value_changed");
+  if(sheet->vadjustment)
+      gtk_signal_emit_by_name (GTK_OBJECT (sheet->vadjustment), 
+                             "value_changed");
+
+  if(sheet->state == GTK_STATE_NORMAL)
+     if(sheet->sheet_entry && GTK_WIDGET_MAPPED(sheet->sheet_entry)){
+        gtk_sheet_activate_cell(sheet, sheet->active_cell.row, sheet->active_cell.col);
+/*
+        gtk_signal_connect(GTK_OBJECT(gtk_sheet_get_entry(sheet)),
+                          "changed",
+                           (GtkSignalFunc)gtk_sheet_entry_changed,
+                           GTK_OBJECT(GTK_WIDGET(sheet)));
+        gtk_sheet_show_active_cell(sheet);
+*/
+     }
+
+}
+
+void
+gtk_sheet_set_row_titles_width(GtkSheet *sheet, guint width)
+{
+ if(width < COLUMN_MIN_WIDTH) return;
+
+ sheet->row_title_area.width = width;
+ sheet->view.col0 = COLUMN_FROM_XPIXEL(sheet, sheet->row_title_area.width+1);
+ sheet->view.coli = COLUMN_FROM_XPIXEL(sheet, sheet->sheet_window_width);
+
+
+ adjust_scrollbars(sheet);
+
+ sheet->old_hadjustment = -1.;
+ if(sheet->hadjustment)
+     gtk_signal_emit_by_name (GTK_OBJECT (sheet->hadjustment), 
+                             "value_changed");
+ size_allocate_global_button(sheet);
+}
+
+void
+gtk_sheet_set_column_titles_height(GtkSheet *sheet, guint height)
+{
+ if(height < DEFAULT_ROW_HEIGHT(GTK_WIDGET(sheet))) return;
+
+ sheet->column_title_area.height = height;
+ sheet->view.row0=ROW_FROM_YPIXEL(sheet, sheet->column_title_area.height+1);
+ sheet->view.rowi=ROW_FROM_YPIXEL(sheet, sheet->sheet_window_height-1);
+
+ adjust_scrollbars(sheet);
+
+ sheet->old_vadjustment = -1.;
+ if(sheet->vadjustment)
+     gtk_signal_emit_by_name (GTK_OBJECT (sheet->vadjustment), 
+                             "value_changed");
+ size_allocate_global_button(sheet);
+}
+
+void
+gtk_sheet_show_column_titles(GtkSheet *sheet)
+{
+ gint col;
+
+ if(sheet->column_titles_visible) return;
+
+ sheet->column_titles_visible = TRUE;
+
+
+ if(GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))){
+  gdk_window_show(sheet->column_title_window);
+  gdk_window_move_resize (sheet->column_title_window,
+                          sheet->column_title_area.x,
+                          sheet->column_title_area.y,
+                          sheet->column_title_area.width,
+                          sheet->column_title_area.height);
+
+  for(col = MIN_VISIBLE_COLUMN(sheet); 
+      col <= MAX_VISIBLE_COLUMN(sheet); 
+      col++)
+    {
+      const GtkSheetButton *button = xxx_column_button(sheet, col);
+      GtkSheetChild *child = button->child;
+      if(child)
+        gtk_sheet_child_show(child);
+    }
+  adjust_scrollbars(sheet);
+ } 
+
+ sheet->old_vadjustment = -1.;
+ if(sheet->vadjustment)
+     gtk_signal_emit_by_name (GTK_OBJECT (sheet->vadjustment), 
+                             "value_changed");
+ size_allocate_global_button(sheet);
+}
+
+
+void
+gtk_sheet_show_row_titles(GtkSheet *sheet)
+{
+ gint row;
+
+ if(sheet->row_titles_visible) return;
+
+ sheet->row_titles_visible = TRUE;
+
+
+ if(GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))){
+  gdk_window_show(sheet->row_title_window);
+  gdk_window_move_resize (sheet->row_title_window,
+                          sheet->row_title_area.x,
+                          sheet->row_title_area.y,
+                          sheet->row_title_area.width,
+                          sheet->row_title_area.height);
+
+  for(row = MIN_VISIBLE_ROW(sheet); 
+      row <= MAX_VISIBLE_ROW(sheet); 
+      row++)
+    {
+      const GtkSheetButton *button = yyy_row_button(sheet, row);
+      GtkSheetChild *child = button->child;
+
+      if(child){
+       gtk_sheet_child_show(child);
+      }
+  }
+  adjust_scrollbars(sheet);
+ }
+
+ sheet->old_hadjustment = -1.;
+ if(sheet->hadjustment)
+     gtk_signal_emit_by_name (GTK_OBJECT (sheet->hadjustment), 
+                             "value_changed");
+ size_allocate_global_button(sheet);
+}
+
+void
+gtk_sheet_hide_column_titles(GtkSheet *sheet)
+{
+ gint col;
+
+ if(!sheet->column_titles_visible) return;
+
+ sheet->column_titles_visible = FALSE;
+
+ if(GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))){
+  if(sheet->column_title_window) 
+    gdk_window_hide(sheet->column_title_window);
+  if(GTK_WIDGET_VISIBLE(sheet->button)) 
+    gtk_widget_hide(sheet->button);
+
+  for(col = MIN_VISIBLE_COLUMN(sheet); 
+      col <= MAX_VISIBLE_COLUMN(sheet); 
+      col++)
+    {
+      const GtkSheetButton *button = xxx_column_button(sheet, col);
+      GtkSheetChild *child = button->child;
+      if(child)
+        gtk_sheet_child_hide(child);
+  }
+  adjust_scrollbars(sheet);
+ }
+ sheet->old_vadjustment = -1.;
+ if(sheet->vadjustment)
+     gtk_signal_emit_by_name (GTK_OBJECT (sheet->vadjustment), 
+                             "value_changed");
+}
+
+void
+gtk_sheet_hide_row_titles(GtkSheet *sheet)
+{
+ gint row;
+
+ if(!sheet->row_titles_visible) return;
+
+ sheet->row_titles_visible = FALSE;
+
+
+ if(GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))){
+  if(sheet->row_title_window) 
+    gdk_window_hide(sheet->row_title_window);
+  if(GTK_WIDGET_VISIBLE(sheet->button)) 
+    gtk_widget_hide(sheet->button);
+  for(row = MIN_VISIBLE_ROW(sheet); 
+      row <= MAX_VISIBLE_ROW(sheet); 
+      row++)
+    {
+      const GtkSheetButton *button = yyy_row_button(sheet, row);
+      GtkSheetChild *child = button->child;
+
+      if(child)
+        gtk_sheet_child_hide(child);
+    }
+  adjust_scrollbars(sheet);
+ }
+
+ sheet->old_hadjustment = -1.;
+ if(sheet->hadjustment)
+     gtk_signal_emit_by_name (GTK_OBJECT (sheet->hadjustment), 
+                             "value_changed");
+}
+
+gboolean
+gtk_sheet_column_titles_visible(GtkSheet *sheet)
+{
+  g_return_val_if_fail (sheet != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), FALSE);
+  return sheet->column_titles_visible;
+}
+
+gboolean
+gtk_sheet_row_titles_visible(GtkSheet *sheet)
+{
+  g_return_val_if_fail (sheet != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), FALSE);
+  return sheet->row_titles_visible;
+}
+
+
+
+void
+gtk_sheet_moveto (GtkSheet * sheet,
+                 gint row,
+                 gint column,
+                 gfloat row_align,
+                  gfloat col_align)
+{
+  gint x, y;
+  guint width, height;
+  gint adjust;
+  gint min_row, min_col;
+
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+  g_return_if_fail (sheet->hadjustment != NULL);
+  g_return_if_fail (sheet->vadjustment != NULL);
+
+  if (row < 0 || row >= yyy_row_count(sheet))
+    return;
+  if (column < 0 || column >= xxx_column_count(sheet))
+    return;
+
+  height = sheet->sheet_window_height;
+  width = sheet->sheet_window_width;
+
+  /* adjust vertical scrollbar */
+
+  if (row >= 0 && row_align >=0.)
+    {
+/*
+      y = ROW_TOP_YPIXEL(sheet, row) - sheet->voffset -
+          row_align*height-
+          (1.-row_align)*yyy_row_height(sheet, row);
+*/
+      y = ROW_TOP_YPIXEL (sheet, row) - sheet->voffset
+        - (gint) ( row_align*height + (1. - row_align) * yyy_row_height(sheet, row));
+
+      /* This forces the sheet to scroll when you don't see the entire cell */
+      min_row = row;
+      adjust = 0;
+      if(row_align == 1.){
+        while(min_row >= 0 && min_row > MIN_VISIBLE_ROW(sheet)){
+         if(yyy_row_is_visible(sheet, min_row))
+                adjust += yyy_row_height(sheet, min_row);
+         if(adjust >= height){
+           break;
+         }
+         min_row--;
+        }
+        min_row = MAX(min_row, 0);
+        y = ROW_TOP_YPIXEL(sheet, min_row) - sheet->voffset +
+            yyy_row_height(sheet, min_row) - 1;
+      }
+
+      if (y < 0)
+       sheet->vadjustment->value = 0.0;
+      else
+       sheet->vadjustment->value = y;
+
+      sheet->old_vadjustment = -1.;
+      gtk_signal_emit_by_name (GTK_OBJECT (sheet->vadjustment), 
+                              "value_changed");
+
+    } 
+     
+  /* adjust horizontal scrollbar */
+  if (column >= 0 && col_align >= 0.)
+    {
+/*
+      x = COLUMN_LEFT_XPIXEL (sheet, column) - sheet->hoffset -
+          col_align*width -
+          (1.-col_align)*sheet->column[column].width;
+*/
+      x = COLUMN_LEFT_XPIXEL (sheet, column) - sheet->hoffset
+        - (gint) ( col_align*width + (1.-col_align)*
+                  xxx_column_width(sheet, column));
+
+
+      /* This forces the sheet to scroll when you don't see the entire cell */
+      min_col = column;
+      adjust = 0;
+      if(col_align == 1.){
+        while(min_col >= 0 && min_col > MIN_VISIBLE_COLUMN(sheet)){
+         if(xxx_column_is_visible(sheet, min_col))
+          adjust += xxx_column_width(sheet, min_col);
+        
+         if(adjust >= width){
+           break;
+         }
+         min_col--;
+        }
+        min_col = MAX(min_col, 0);
+        x = COLUMN_LEFT_XPIXEL(sheet, min_col) - sheet->hoffset +
+         xxx_column_width(sheet, min_col) - 1;
+      }
+
+      if (x < 0)
+       sheet->hadjustment->value = 0.0;
+      else
+       sheet->hadjustment->value = x;
+
+      sheet->old_vadjustment = -1.;
+      gtk_signal_emit_by_name (GTK_OBJECT (sheet->hadjustment), 
+                              "value_changed");
+
+    }
+}
+
+
+void
+gtk_sheet_columns_set_resizable (GtkSheet *sheet, gboolean resizable)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  sheet->columns_resizable = resizable;
+}
+
+gboolean
+gtk_sheet_columns_resizable (GtkSheet *sheet)
+{
+  g_return_val_if_fail (sheet != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), FALSE);
+
+  return sheet->columns_resizable;
+}
+
+
+void
+gtk_sheet_rows_set_resizable (GtkSheet *sheet, gboolean resizable)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  sheet->rows_resizable = resizable;
+}
+
+gboolean
+gtk_sheet_rows_resizable (GtkSheet *sheet)
+{
+  g_return_val_if_fail (sheet != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), FALSE);
+
+  return sheet->rows_resizable;
+}
+
+
+void
+gtk_sheet_select_row (GtkSheet * sheet,
+                     gint row)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  if (row < 0 || row >= yyy_row_count(sheet))
+    return;
+
+  if(sheet->state != GTK_SHEET_NORMAL) 
+     gtk_sheet_real_unselect_range(sheet, NULL);
+  else
+  {
+     gboolean veto = TRUE;
+     veto = gtk_sheet_deactivate_cell(sheet);
+     if(!veto) return;
+  }
+
+  sheet->state=GTK_SHEET_ROW_SELECTED;                     
+  sheet->range.row0=row;
+  sheet->range.col0=0;
+  sheet->range.rowi=row;
+  sheet->range.coli = xxx_column_count(sheet) - 1;
+  sheet->active_cell.row=row;
+  sheet->active_cell.col=0;
+
+  gtk_signal_emit (GTK_OBJECT (sheet), sheet_signals[SELECT_ROW], row);
+  gtk_sheet_real_select_range(sheet, NULL);
+
+}
+
+
+void
+gtk_sheet_select_column (GtkSheet * sheet,
+                        gint column)
+{
+  
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  if (column < 0 || column >= xxx_column_count(sheet))
+    return;
+
+  if(sheet->state != GTK_SHEET_NORMAL) 
+     gtk_sheet_real_unselect_range(sheet, NULL);
+  else
+  {
+     gboolean veto = TRUE;
+     veto = gtk_sheet_deactivate_cell(sheet);
+     if(!veto) return;
+  }
+
+  sheet->state=GTK_SHEET_COLUMN_SELECTED;                     
+  sheet->range.row0=0;
+  sheet->range.col0=column;
+  sheet->range.rowi= yyy_row_count(sheet) - 1;
+  sheet->range.coli=column;
+  sheet->active_cell.row=0;
+  sheet->active_cell.col=column;
+
+  gtk_signal_emit (GTK_OBJECT (sheet), sheet_signals[SELECT_COLUMN], column);
+  gtk_sheet_real_select_range(sheet, NULL);
+
+}
+
+void
+gtk_sheet_clip_range (GtkSheet *sheet, const GtkSheetRange *range)
+{
+
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  if(GTK_SHEET_IN_CLIP(sheet)) return;
+
+  GTK_SHEET_SET_FLAGS(sheet, GTK_SHEET_IN_CLIP);
+
+  if(range == NULL)
+    sheet->clip_range = sheet->range;
+  else
+    sheet->clip_range=*range;
+
+  sheet->interval=0;
+  sheet->clip_timer=gtk_timeout_add(TIMEOUT_FLASH, gtk_sheet_flash, sheet); 
+
+  gtk_signal_emit(GTK_OBJECT(sheet), sheet_signals[CLIP_RANGE],
+                                     &sheet->clip_range);
+
+}
+
+void
+gtk_sheet_unclip_range(GtkSheet *sheet)
+{
+
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  if(!GTK_SHEET_IN_CLIP(sheet)) return;
+
+  GTK_SHEET_UNSET_FLAGS(sheet, GTK_SHEET_IN_CLIP);
+  gtk_timeout_remove(sheet->clip_timer);
+  gtk_sheet_range_draw(sheet, &sheet->clip_range);
+
+  if(gtk_sheet_range_isvisible(sheet, sheet->range))
+    gtk_sheet_range_draw(sheet, &sheet->range);
+}
+
+gboolean
+gtk_sheet_in_clip (GtkSheet *sheet)
+{
+  g_return_val_if_fail (sheet != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), FALSE);
+
+  return GTK_SHEET_IN_CLIP(sheet);
+}
+
+static gint
+gtk_sheet_flash(gpointer data)
+{
+  GtkSheet *sheet;
+  gint x,y,width,height;
+  GdkRectangle clip_area;
+
+  sheet=GTK_SHEET(data);
+
+  if(!GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))) return TRUE;
+  if(!GTK_WIDGET_DRAWABLE(GTK_WIDGET(sheet))) return TRUE;
+  if(!gtk_sheet_range_isvisible(sheet, sheet->clip_range)) return TRUE;
+  if(GTK_SHEET_IN_XDRAG(sheet)) return TRUE; 
+  if(GTK_SHEET_IN_YDRAG(sheet)) return TRUE; 
+
+  GDK_THREADS_ENTER();
+  x=COLUMN_LEFT_XPIXEL(sheet,sheet->clip_range.col0)+1;
+  y=ROW_TOP_YPIXEL(sheet,sheet->clip_range.row0)+1;
+  width=COLUMN_LEFT_XPIXEL(sheet,sheet->clip_range.coli)-x+ 
+    xxx_column_width(sheet, sheet->clip_range.coli) - 1;
+  height=ROW_TOP_YPIXEL(sheet,sheet->clip_range.rowi)-y+
+    yyy_row_height(sheet, sheet->clip_range.rowi)-1;
+
+  clip_area.x=COLUMN_LEFT_XPIXEL(sheet, MIN_VISIBLE_COLUMN(sheet));
+  clip_area.y=ROW_TOP_YPIXEL(sheet, MIN_VISIBLE_ROW(sheet));
+  clip_area.width=sheet->sheet_window_width;
+  clip_area.height=sheet->sheet_window_height;
+
+  if(x<0) {
+     width=width+x+1;
+     x=-1;
+  }
+  if(width>clip_area.width) width=clip_area.width+10;
+  if(y<0) {
+     height=height+y+1;
+     y=-1;
+  }
+  if(height>clip_area.height) height=clip_area.height+10;
+
+  gdk_draw_pixmap(sheet->sheet_window,
+                  GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                  sheet->pixmap,
+                  x, y,
+                  x, y,
+                  1, height);
+
+  gdk_draw_pixmap(sheet->sheet_window,
+                  GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                  sheet->pixmap,
+                  x, y,
+                  x, y,
+                  width, 1);
+
+  gdk_draw_pixmap(sheet->sheet_window,
+                  GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                  sheet->pixmap,
+                  x, y+height,
+                  x, y+height,
+                  width, 1);
+
+  gdk_draw_pixmap(sheet->sheet_window,
+                  GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                  sheet->pixmap,
+                  x+width, y,
+                  x+width, y,
+                  1, height);
+
+
+  sheet->interval=sheet->interval+1;
+  if(sheet->interval==TIME_INTERVAL) sheet->interval=0;
+
+  gdk_gc_set_dashes(sheet->xor_gc, sheet->interval, (gint8*)"\4\4", 2);
+  gtk_sheet_draw_flashing_range(sheet,sheet->clip_range);
+  gdk_gc_set_dashes(sheet->xor_gc, 0, (gint8*)"\4\4", 2);
+
+  GDK_THREADS_LEAVE();
+
+  return TRUE;
+
+}
+
+static void
+gtk_sheet_draw_flashing_range(GtkSheet *sheet, GtkSheetRange range)
+{
+  GdkRectangle clip_area;
+  gint x,y,width,height;
+
+  if(!gtk_sheet_range_isvisible(sheet, sheet->clip_range)) return;
+  
+  clip_area.x=COLUMN_LEFT_XPIXEL(sheet, MIN_VISIBLE_COLUMN(sheet));
+  clip_area.y=ROW_TOP_YPIXEL(sheet, MIN_VISIBLE_ROW(sheet));
+  clip_area.width=sheet->sheet_window_width;
+  clip_area.height=sheet->sheet_window_height;
+
+  gdk_gc_set_clip_rectangle(sheet->xor_gc, &clip_area);  
+
+  x=COLUMN_LEFT_XPIXEL(sheet,sheet->clip_range.col0)+1;
+  y=ROW_TOP_YPIXEL(sheet,sheet->clip_range.row0)+1;
+  width=COLUMN_LEFT_XPIXEL(sheet,sheet->clip_range.coli)-x+ 
+    xxx_column_width(sheet, sheet->clip_range.coli) - 1;
+  height=ROW_TOP_YPIXEL(sheet,sheet->clip_range.rowi)-y+
+             yyy_row_height(sheet, sheet->clip_range.rowi)-1;
+
+  if(x<0) {
+     width=width+x+1;
+     x=-1;
+  }
+  if(width>clip_area.width) width=clip_area.width+10;
+  if(y<0) {
+     height=height+y+1;
+     y=-1;
+  }
+  if(height>clip_area.height) height=clip_area.height+10;
+
+  gdk_gc_set_line_attributes(sheet->xor_gc, 1, 1, 0 ,0 );
+
+  gdk_draw_rectangle(sheet->sheet_window, sheet->xor_gc, FALSE, 
+                     x, y,
+                     width, height);
+
+  gdk_gc_set_line_attributes (sheet->xor_gc, 1, 0, 0, 0);
+
+  gdk_gc_set_clip_rectangle(sheet->xor_gc, NULL);
+
+}
+
+static gint
+gtk_sheet_range_isvisible (GtkSheet * sheet,
+                        GtkSheetRange range)
+{
+  g_return_val_if_fail (sheet != NULL, FALSE);
+
+  if (range.row0 < 0 || range.row0 >= yyy_row_count(sheet))
+    return FALSE;
+
+  if (range.rowi < 0 || range.rowi >= yyy_row_count(sheet))
+    return FALSE;
+
+  if (range.col0 < 0 || range.col0 >= xxx_column_count(sheet))
+    return FALSE;
+
+  if (range.coli < 0 || range.coli >= xxx_column_count(sheet))
+    return FALSE;
+
+  if (range.rowi < MIN_VISIBLE_ROW (sheet))
+    return FALSE;
+
+  if (range.row0 > MAX_VISIBLE_ROW (sheet))
+    return FALSE;
+
+  if (range.coli < MIN_VISIBLE_COLUMN (sheet))
+    return FALSE;
+
+  if (range.col0 > MAX_VISIBLE_COLUMN (sheet))
+    return FALSE;
+
+  return TRUE;
+}
+
+static gint
+gtk_sheet_cell_isvisible (GtkSheet * sheet,
+                         gint row, gint column)
+{
+  GtkSheetRange range;
+
+  range.row0 = row;
+  range.col0 = column;
+  range.rowi = row;
+  range.coli = column;
+
+  return gtk_sheet_range_isvisible(sheet, range);
+}
+
+void 
+gtk_sheet_get_visible_range(GtkSheet *sheet, GtkSheetRange *range)
+{
+
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet)) ;
+  g_return_if_fail (range != NULL);
+
+  range->row0 = MIN_VISIBLE_ROW(sheet);
+  range->col0 = MIN_VISIBLE_COLUMN(sheet);
+  range->rowi = MAX_VISIBLE_ROW(sheet);
+  range->coli = MAX_VISIBLE_COLUMN(sheet);
+
+}
+
+GtkAdjustment *
+gtk_sheet_get_vadjustment (GtkSheet * sheet)
+{
+  g_return_val_if_fail (sheet != NULL, NULL);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), NULL);
+
+  return sheet->vadjustment;
+}
+
+GtkAdjustment *
+gtk_sheet_get_hadjustment (GtkSheet * sheet)
+{
+  g_return_val_if_fail (sheet != NULL, NULL);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), NULL);
+
+  return sheet->hadjustment;
+}
+
+void
+gtk_sheet_set_vadjustment (GtkSheet      *sheet,
+                          GtkAdjustment *adjustment)
+{
+  GtkAdjustment *old_adjustment;
+
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+  if (adjustment)
+    g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+  
+  if (sheet->vadjustment == adjustment)
+    return;
+  
+  old_adjustment = sheet->vadjustment;
+
+  if (sheet->vadjustment)
+    {
+      gtk_signal_disconnect_by_data (GTK_OBJECT (sheet->vadjustment), sheet);
+      gtk_object_unref (GTK_OBJECT (sheet->vadjustment));
+    }
+
+  sheet->vadjustment = adjustment;
+
+  if (sheet->vadjustment)
+    {
+      gtk_object_ref (GTK_OBJECT (sheet->vadjustment));
+      gtk_object_sink (GTK_OBJECT (sheet->vadjustment));
+
+      gtk_signal_connect (GTK_OBJECT (sheet->vadjustment), "changed",
+                         (GtkSignalFunc) vadjustment_changed,
+                         (gpointer) sheet);
+      gtk_signal_connect (GTK_OBJECT (sheet->vadjustment), "value_changed",
+                         (GtkSignalFunc) vadjustment_value_changed,
+                         (gpointer) sheet);
+    }
+
+  if (!sheet->vadjustment || !old_adjustment)
+     {
+       gtk_widget_queue_resize (GTK_WIDGET (sheet));
+       return;
+     }
+
+  sheet->old_vadjustment = sheet->vadjustment->value;
+}
+
+void
+gtk_sheet_set_hadjustment (GtkSheet      *sheet,
+                          GtkAdjustment *adjustment)
+{
+  GtkAdjustment *old_adjustment;
+
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+  if (adjustment)
+    g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+  
+  if (sheet->hadjustment == adjustment)
+    return;
+  
+  old_adjustment = sheet->hadjustment;
+
+  if (sheet->hadjustment)
+    {
+      gtk_signal_disconnect_by_data (GTK_OBJECT (sheet->hadjustment), sheet);
+      gtk_object_unref (GTK_OBJECT (sheet->hadjustment));
+    }
+
+  sheet->hadjustment = adjustment;
+
+  if (sheet->hadjustment)
+    {
+      gtk_object_ref (GTK_OBJECT (sheet->hadjustment));
+      gtk_object_sink (GTK_OBJECT (sheet->hadjustment));
+
+      gtk_signal_connect (GTK_OBJECT (sheet->hadjustment), "changed",
+                         (GtkSignalFunc) hadjustment_changed,
+                         (gpointer) sheet);
+      gtk_signal_connect (GTK_OBJECT (sheet->hadjustment), "value_changed",
+                         (GtkSignalFunc) hadjustment_value_changed,
+                         (gpointer) sheet);
+    }
+
+  if (!sheet->hadjustment || !old_adjustment)
+     {
+       gtk_widget_queue_resize (GTK_WIDGET (sheet));
+       return;
+     }
+
+  sheet->old_hadjustment = sheet->hadjustment->value;
+}
+
+static void
+gtk_sheet_set_scroll_adjustments (GtkSheet *sheet,
+                                 GtkAdjustment *hadjustment,
+                                 GtkAdjustment *vadjustment)
+{
+   if(sheet->hadjustment != hadjustment)
+         gtk_sheet_set_hadjustment (sheet, hadjustment);
+   if(sheet->vadjustment != vadjustment)
+         gtk_sheet_set_vadjustment (sheet, vadjustment);
+}
+
+static void
+gtk_sheet_finalize (GObject * object)
+{
+  GtkSheet *sheet;
+
+  g_return_if_fail (object != NULL);
+  g_return_if_fail (GTK_IS_SHEET (object));
+
+  sheet = GTK_SHEET (object);
+
+  /* get rid of all the cells */
+  gtk_sheet_range_clear (sheet, NULL);
+  gtk_sheet_range_delete(sheet, NULL);
+
+  if(sheet->name){
+      g_free(sheet->name);
+      sheet->name = NULL;
+  }
+
+  if (G_OBJECT_CLASS (parent_class)->finalize)
+    (*G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+gtk_sheet_destroy (GtkObject * object)
+{
+  GtkSheet *sheet;
+  GList *children;
+
+  g_return_if_fail (object != NULL);
+  g_return_if_fail (GTK_IS_SHEET (object));
+
+  sheet = GTK_SHEET (object);
+
+  /* destroy the entry */
+  if(sheet->sheet_entry && GTK_IS_WIDGET(sheet->sheet_entry)){
+    gtk_widget_destroy (sheet->sheet_entry);
+    sheet->sheet_entry = NULL;
+  }
+
+  /* destroy the global selection button */
+  if(sheet->button && GTK_IS_WIDGET(sheet->button)){
+    gtk_widget_destroy (sheet->button);
+    sheet->button = NULL;
+  }
+
+  if(sheet->timer){
+     gtk_timeout_remove(sheet->timer);
+     sheet->timer = 0;
+  }
+
+  if(sheet->clip_timer){
+     gtk_timeout_remove(sheet->clip_timer);
+     sheet->clip_timer = 0;
+  }
+
+  /* unref adjustments */
+  if (sheet->hadjustment)
+    {
+      gtk_signal_disconnect_by_data (GTK_OBJECT (sheet->hadjustment), sheet);
+      gtk_object_unref (GTK_OBJECT (sheet->hadjustment));
+      sheet->hadjustment = NULL;
+    }
+  if (sheet->vadjustment)
+    {
+      gtk_signal_disconnect_by_data (GTK_OBJECT (sheet->vadjustment), sheet);
+      gtk_object_unref (GTK_OBJECT (sheet->vadjustment));
+      sheet->vadjustment = NULL;
+    }
+
+  children = sheet->children;
+  while(children){
+    GtkSheetChild *child = (GtkSheetChild *)children->data;
+    if(child && child->widget) 
+      gtk_sheet_remove(GTK_CONTAINER(sheet), child->widget);
+    children = sheet->children;
+  }  
+  sheet->children = NULL;
+
+  if (GTK_OBJECT_CLASS (parent_class)->destroy)
+    (*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static void
+gtk_sheet_style_set (GtkWidget *widget,
+                    GtkStyle  *previous_style)
+{
+  GtkSheet *sheet;
+
+  g_return_if_fail (widget != NULL);
+  g_return_if_fail (GTK_IS_SHEET (widget));
+
+  if (GTK_WIDGET_CLASS (parent_class)->style_set)
+    (*GTK_WIDGET_CLASS (parent_class)->style_set) (widget, previous_style);
+
+  sheet = GTK_SHEET (widget);
+
+  if(GTK_WIDGET_REALIZED(widget))
+     {
+       gtk_style_set_background (widget->style, widget->window, widget->state);
+     }
+
+}
+
+static void
+gtk_sheet_realize (GtkWidget * widget)
+{
+  GtkSheet *sheet;
+  GdkWindowAttr attributes;
+  gint attributes_mask;
+  GdkGCValues values, auxvalues;
+  GdkColormap *colormap;
+  gchar *name;
+  GtkSheetChild *child;
+  GList *children;
+
+  g_return_if_fail (widget != NULL);
+  g_return_if_fail (GTK_IS_SHEET (widget));
+
+  sheet = GTK_SHEET (widget);
+
+  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.x = widget->allocation.x;
+  attributes.y = widget->allocation.y;
+  attributes.width = widget->allocation.width;
+  attributes.height = widget->allocation.height;
+  attributes.wclass = GDK_INPUT_OUTPUT;
+
+  attributes.visual = gtk_widget_get_visual (widget);
+  attributes.colormap = gtk_widget_get_colormap (widget);
+
+  attributes.event_mask = gtk_widget_get_events (widget);
+  attributes.event_mask |= (GDK_EXPOSURE_MASK |
+                           GDK_BUTTON_PRESS_MASK |
+                           GDK_BUTTON_RELEASE_MASK |
+                           GDK_KEY_PRESS_MASK |
+                           GDK_POINTER_MOTION_MASK |
+                           GDK_POINTER_MOTION_HINT_MASK);
+  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP |
+                    GDK_WA_CURSOR;
+
+  attributes.cursor = gdk_cursor_new(GDK_TOP_LEFT_ARROW);
+
+  /* main window */
+  widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
+
+  gdk_window_set_user_data (widget->window, sheet);
+
+  widget->style = gtk_style_attach (widget->style, widget->window);
+
+  gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
+
+  attributes.x = 0;
+  if(sheet->row_titles_visible)
+       attributes.x = sheet->row_title_area.width;
+  attributes.y = 0;
+  attributes.width = sheet->column_title_area.width;
+  attributes.height = sheet->column_title_area.height;
+
+  /* column-title window */
+  sheet->column_title_window = gdk_window_new (widget->window, &attributes, attributes_mask);
+  gdk_window_set_user_data (sheet->column_title_window, sheet);
+  gtk_style_set_background (widget->style, sheet->column_title_window, GTK_STATE_NORMAL);
+
+  attributes.x = 0;
+  attributes.y = 0;
+  if(sheet->column_titles_visible)
+       attributes.y = sheet->column_title_area.height;
+  attributes.width = sheet->row_title_area.width;
+  attributes.height = sheet->row_title_area.height;
+
+  /* row-title window */
+  sheet->row_title_window = gdk_window_new (widget->window, &attributes, attributes_mask);
+  gdk_window_set_user_data (sheet->row_title_window, sheet);
+  gtk_style_set_background (widget->style, sheet->row_title_window, GTK_STATE_NORMAL);
+
+  /* sheet-window */
+  attributes.cursor = gdk_cursor_new(GDK_PLUS);
+
+  attributes.x = 0;
+  attributes.y = 0;
+  attributes.width = sheet->sheet_window_width, 
+  attributes.height = sheet->sheet_window_height;
+
+  sheet->sheet_window = gdk_window_new (widget->window, &attributes, attributes_mask);
+  gdk_window_set_user_data (sheet->sheet_window, sheet);
+
+  gdk_cursor_unref(attributes.cursor);
+
+  gdk_window_set_background (sheet->sheet_window, &widget->style->white);
+  gdk_window_show (sheet->sheet_window);
+
+  /* backing_pixmap */
+  gtk_sheet_make_backing_pixmap(sheet, 0, 0);  
+
+  /* GCs */
+  if(sheet->fg_gc) 
+      gdk_gc_unref(sheet->fg_gc);
+  if(sheet->bg_gc) 
+      gdk_gc_unref(sheet->bg_gc);
+  sheet->fg_gc = gdk_gc_new (widget->window);
+  sheet->bg_gc = gdk_gc_new (widget->window);
+
+  colormap = gtk_widget_get_colormap(widget);
+
+  gdk_color_white(colormap, &widget->style->white);
+  gdk_color_black(colormap, &widget->style->black);
+
+  gdk_gc_get_values(sheet->fg_gc, &auxvalues);
+
+  values.foreground = widget->style->white;
+  values.function = GDK_INVERT;
+  values.subwindow_mode = GDK_INCLUDE_INFERIORS;
+  if(sheet->xor_gc)
+    gdk_gc_unref(sheet->xor_gc);
+  sheet->xor_gc = gdk_gc_new_with_values (widget->window,
+                                         &values,
+                                         GDK_GC_FOREGROUND |
+                                         GDK_GC_FUNCTION |
+                                         GDK_GC_SUBWINDOW);
+
+  if(sheet->sheet_entry->parent){
+          gtk_widget_ref(sheet->sheet_entry);
+          gtk_widget_unparent(sheet->sheet_entry);
+  }
+  gtk_widget_set_parent_window (sheet->sheet_entry, sheet->sheet_window);
+  gtk_widget_set_parent(sheet->sheet_entry, GTK_WIDGET(sheet));
+
+  if(sheet->button && sheet->button->parent){
+          gtk_widget_ref(sheet->button);
+          gtk_widget_unparent(sheet->button);
+  }
+  gtk_widget_set_parent_window(sheet->button, sheet->sheet_window);
+  gtk_widget_set_parent(sheet->button, GTK_WIDGET(sheet));
+
+/*
+  gtk_sheet_activate_cell(sheet, sheet->active_cell.row, sheet->active_cell.col);
+*/
+  if(!sheet->cursor_drag)
+       sheet->cursor_drag = gdk_cursor_new(GDK_PLUS);
+  if(sheet->column_titles_visible)
+     gdk_window_show(sheet->column_title_window);
+  if(sheet->row_titles_visible)
+     gdk_window_show(sheet->row_title_window);
+
+  size_allocate_row_title_buttons(sheet);
+  size_allocate_column_title_buttons(sheet);
+
+  name = g_strdup(sheet->name);
+  gtk_sheet_set_title(sheet, name);
+
+  g_free(name);
+
+  children = sheet->children;
+  while(children)
+    {
+      child = children->data;
+      children = children->next;
+      gtk_sheet_realize_child(sheet, child);
+    }
+}
+
+static void
+create_global_button(GtkSheet *sheet)
+{
+   sheet->button = gtk_button_new_with_label(" ");
+
+   gtk_signal_connect (GTK_OBJECT (sheet->button),
+                     "pressed",
+                     (GtkSignalFunc) global_button_clicked,
+                     (gpointer) sheet);
+}
+
+static void
+size_allocate_global_button(GtkSheet *sheet)
+{
+  GtkAllocation allocation;
+
+  if(!sheet->column_titles_visible) return;
+  if(!sheet->row_titles_visible) return;
+
+  gtk_widget_size_request(sheet->button, NULL);
+
+  allocation.x=0;
+  allocation.y=0;
+  allocation.width=sheet->row_title_area.width;
+  allocation.height=sheet->column_title_area.height;
+
+  gtk_widget_size_allocate(sheet->button, &allocation);
+  gtk_widget_show(sheet->button);
+}
+
+static void
+global_button_clicked(GtkWidget *widget, gpointer data)
+{
+  gboolean veto;
+
+  gtk_sheet_click_cell(GTK_SHEET(data), -1, -1, &veto);
+  gtk_widget_grab_focus(GTK_WIDGET(data));
+}
+
+
+static void
+gtk_sheet_unrealize (GtkWidget * widget)
+{
+  GtkSheet *sheet;
+
+  g_return_if_fail (widget != NULL);
+  g_return_if_fail (GTK_IS_SHEET (widget));
+
+  sheet = GTK_SHEET (widget);
+
+  gdk_cursor_destroy (sheet->cursor_drag);
+
+  gdk_gc_destroy (sheet->xor_gc);
+  gdk_gc_destroy (sheet->fg_gc);
+  gdk_gc_destroy (sheet->bg_gc);
+
+  gdk_window_destroy (sheet->sheet_window);
+  gdk_window_destroy (sheet->column_title_window);
+  gdk_window_destroy (sheet->row_title_window);
+
+  if (sheet->pixmap){
+    g_free (sheet->pixmap);
+    sheet->pixmap = NULL;
+  }
+
+  sheet->column_title_window=NULL;
+  sheet->sheet_window = NULL;
+  sheet->cursor_drag = NULL;
+  sheet->xor_gc = NULL;
+  sheet->fg_gc = NULL;
+  sheet->bg_gc = NULL;
+
+  if (GTK_WIDGET_CLASS (parent_class)->unrealize)
+    (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
+}
+
+static void
+gtk_sheet_map (GtkWidget * widget)
+{
+  GtkSheet *sheet;
+  GtkSheetChild *child;
+  GList *children;
+
+  g_return_if_fail (widget != NULL);
+  g_return_if_fail (GTK_IS_SHEET (widget));
+
+  sheet = GTK_SHEET (widget);
+
+  if (!GTK_WIDGET_MAPPED (widget))
+    {
+      GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
+
+      if(!sheet->cursor_drag) sheet->cursor_drag=gdk_cursor_new(GDK_PLUS);
+
+      gdk_window_show (widget->window);
+
+      gdk_window_show (sheet->sheet_window);
+
+      if(sheet->column_titles_visible){
+           size_allocate_column_title_buttons(sheet);
+           gdk_window_show (sheet->column_title_window);
+      }
+      if(sheet->row_titles_visible){
+           size_allocate_row_title_buttons(sheet);
+           gdk_window_show (sheet->row_title_window);
+      }
+
+      if(!GTK_WIDGET_MAPPED (sheet->sheet_entry)){
+                 gtk_widget_show (sheet->sheet_entry);
+                 gtk_widget_map (sheet->sheet_entry);
+      }
+
+      if (GTK_WIDGET_VISIBLE (sheet->button) &&
+         !GTK_WIDGET_MAPPED (sheet->button)){
+                  gtk_widget_show(sheet->button);
+                 gtk_widget_map (sheet->button);
+      }
+
+      if(GTK_BIN(sheet->button)->child)
+        if (GTK_WIDGET_VISIBLE (GTK_BIN(sheet->button)->child) &&
+          !GTK_WIDGET_MAPPED (GTK_BIN(sheet->button)->child))
+                 gtk_widget_map (GTK_BIN(sheet->button)->child);
+
+      gtk_sheet_range_draw(sheet, NULL);
+      gtk_sheet_activate_cell(sheet, 
+                              sheet->active_cell.row, 
+                              sheet->active_cell.col);
+
+      children = sheet->children;
+      while (children)
+      {
+        child = children->data;
+        children = children->next;
+
+        if (GTK_WIDGET_VISIBLE (child->widget) &&
+           !GTK_WIDGET_MAPPED (child->widget)){
+         gtk_widget_map (child->widget);
+          gtk_sheet_position_child(sheet, child);
+        }
+      }
+
+    }
+}
+
+static void
+gtk_sheet_unmap (GtkWidget * widget)
+{
+  GtkSheet *sheet;
+  GtkSheetChild *child;
+  GList *children;
+
+  g_return_if_fail (widget != NULL);
+  g_return_if_fail (GTK_IS_SHEET (widget));
+
+  sheet = GTK_SHEET (widget);
+
+  if (GTK_WIDGET_MAPPED (widget))
+    {
+      GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
+
+      gdk_window_hide (sheet->sheet_window);
+      if(sheet->column_titles_visible)
+          gdk_window_hide (sheet->column_title_window);
+      if(sheet->row_titles_visible)
+          gdk_window_hide (sheet->row_title_window);
+      gdk_window_hide (widget->window);
+
+      if (GTK_WIDGET_MAPPED (sheet->sheet_entry))
+       gtk_widget_unmap (sheet->sheet_entry);
+
+      if (GTK_WIDGET_MAPPED (sheet->button))
+       gtk_widget_unmap (sheet->button);
+
+      children = sheet->children;
+      while (children)
+        {
+          child = children->data;
+          children = children->next;
+
+          if (GTK_WIDGET_VISIBLE (child->widget) &&
+             GTK_WIDGET_MAPPED (child->widget))
+                {
+                    gtk_widget_unmap (child->widget);
+                }
+        }
+
+    }
+}
+
+
+static void
+gtk_sheet_cell_draw_default (GtkSheet *sheet, gint row, gint col)
+{
+  GtkWidget *widget;
+  GdkGC *fg_gc, *bg_gc;
+  GtkSheetCellAttr attributes;
+  GdkRectangle area;
+
+  g_return_if_fail (sheet != NULL);
+
+  /* bail now if we arn't drawable yet */
+  if (!GTK_WIDGET_DRAWABLE (sheet)) return;
+
+  if (row < 0 || row >= yyy_row_count(sheet)) return;
+  if (col < 0 || col >= xxx_column_count(sheet)) return;
+  if (! xxx_column_is_visible(sheet, col)) return;
+  if (! yyy_row_is_visible(sheet, row)) return;
+
+  widget = GTK_WIDGET (sheet);
+
+  gtk_sheet_get_attributes(sheet, row, col, &attributes);
+  /* select GC for background rectangle */
+  gdk_gc_set_foreground (sheet->fg_gc, &attributes.foreground);
+  gdk_gc_set_foreground (sheet->bg_gc, &attributes.background);
+
+  fg_gc = sheet->fg_gc;
+  bg_gc = sheet->bg_gc;
+
+  area.x=COLUMN_LEFT_XPIXEL(sheet,col);
+  area.y=ROW_TOP_YPIXEL(sheet,row);
+  area.width= xxx_column_width(sheet, col);
+  area.height=yyy_row_height(sheet, row);
+
+  gdk_draw_rectangle (sheet->pixmap,
+                     bg_gc,
+                     TRUE,
+                     area.x,
+                      area.y,
+                     area.width,
+                      area.height);
+
+  gdk_gc_set_line_attributes (sheet->fg_gc, 1, 0, 0, 0);
+
+  if(sheet->show_grid){
+    gdk_gc_set_foreground (sheet->bg_gc, &sheet->grid_color);
+
+    gdk_draw_rectangle (sheet->pixmap,
+                        sheet->bg_gc,
+                       FALSE,
+                       area.x, area.y,
+                       area.width, area.height);
+  }
+}
+
+static void
+gtk_sheet_cell_draw_border (GtkSheet *sheet, gint row, gint col, gint mask)
+{
+  GtkWidget *widget;
+  GdkGC *fg_gc, *bg_gc;
+  GtkSheetCellAttr attributes;
+  GdkRectangle area;
+  guint width;
+
+  g_return_if_fail (sheet != NULL);
+
+  /* bail now if we arn't drawable yet */
+  if (!GTK_WIDGET_DRAWABLE (sheet)) return;
+
+  if (row < 0 || row >= yyy_row_count(sheet)) return;
+  if (col < 0 || col >= xxx_column_count(sheet)) return;
+  if (!xxx_column_is_visible(sheet, col)) return;
+  if (!yyy_row_is_visible(sheet, row)) return;
+
+  widget = GTK_WIDGET (sheet);
+
+  gtk_sheet_get_attributes(sheet, row, col, &attributes);
+
+  /* select GC for background rectangle */
+  gdk_gc_set_foreground (sheet->fg_gc, &attributes.border.color);
+  gdk_gc_set_foreground (sheet->bg_gc, &attributes.background);
+
+  fg_gc = sheet->fg_gc;
+  bg_gc = sheet->bg_gc;
+
+  area.x=COLUMN_LEFT_XPIXEL(sheet,col);
+  area.y=ROW_TOP_YPIXEL(sheet,row);
+  area.width=xxx_column_width(sheet, col);
+  area.height=yyy_row_height(sheet, row);
+
+  width = attributes.border.width;
+  gdk_gc_set_line_attributes(sheet->fg_gc, attributes.border.width,
+                                           attributes.border.line_style,
+                                           attributes.border.cap_style,
+                                           attributes.border.join_style);
+  if(width>0){
+
+   if(attributes.border.mask & GTK_SHEET_LEFT_BORDER & mask)
+      gdk_draw_line(sheet->pixmap, sheet->fg_gc,
+                    area.x, area.y-width/2,
+                    area.x, area.y+area.height+width/2+1);
+
+   if(attributes.border.mask & GTK_SHEET_RIGHT_BORDER & mask)
+      gdk_draw_line(sheet->pixmap, sheet->fg_gc,
+                    area.x+area.width, area.y-width/2,
+                    area.x+area.width, 
+                    area.y+area.height+width/2+1);
+
+   if(attributes.border.mask & GTK_SHEET_TOP_BORDER & mask)
+      gdk_draw_line(sheet->pixmap, sheet->fg_gc,
+                    area.x-width/2,area.y,
+                    area.x+area.width+width/2+1, 
+                    area.y);
+
+   if(attributes.border.mask & GTK_SHEET_BOTTOM_BORDER & mask)
+      gdk_draw_line(sheet->pixmap, sheet->fg_gc,
+                    area.x-width/2, area.y+area.height,
+                    area.x+area.width+width/2+1, 
+                    area.y+area.height);
+  }
+
+}
+
+
+static void
+gtk_sheet_cell_draw_label (GtkSheet *sheet, gint row, gint col)
+{
+  GtkWidget *widget;
+  GdkRectangle area, clip_area;
+  gint i;
+  gint text_width, text_height, y;
+  gint xoffset=0;  
+  gint size, sizel, sizer;
+  GdkGC *fg_gc, *bg_gc;
+  GtkSheetCellAttr attributes;
+  PangoLayout *layout;
+  PangoRectangle rect;
+  PangoRectangle logical_rect;
+  PangoLayoutLine *line;
+  PangoFontMetrics *metrics;
+  PangoContext *context = gtk_widget_get_pango_context(GTK_WIDGET(sheet)); 
+  gint ascent, descent, y_pos;
+
+  const gchar *label;
+
+  g_return_if_fail (sheet != NULL);
+
+   /* bail now if we aren't drawable yet */
+   if (!GTK_WIDGET_DRAWABLE (sheet))
+    return;
+
+  label = gtk_sheet_cell_get_text(sheet, row, col);
+  if (!label)
+      return;
+
+  if (row < 0 || row >= yyy_row_count(sheet)) return;
+  if (col < 0 || col >= xxx_column_count(sheet)) return;
+  if (! xxx_column_is_visible(sheet, col)) return;
+  if (!yyy_row_is_visible(sheet, row)) return;
+
+
+  widget = GTK_WIDGET(sheet);
+
+  gtk_sheet_get_attributes(sheet, row, col, &attributes);
+
+  /* select GC for background rectangle */
+  gdk_gc_set_foreground (sheet->fg_gc, &attributes.foreground);
+  gdk_gc_set_foreground (sheet->bg_gc, &attributes.background);
+
+  fg_gc = sheet->fg_gc;
+  bg_gc = sheet->bg_gc;
+
+  area.x=COLUMN_LEFT_XPIXEL(sheet,col);
+  area.y=ROW_TOP_YPIXEL(sheet,row);
+  area.width  = xxx_column_width(sheet, col);
+  area.height = yyy_row_height(sheet, row);
+
+  clip_area = area;
+
+  layout = gtk_widget_create_pango_layout (GTK_WIDGET(sheet), label);
+  pango_layout_set_font_description (layout, attributes.font_desc);
+
+  pango_layout_get_pixel_extents (layout, NULL, &rect);
+
+  line = pango_layout_get_lines (layout)->data;
+  pango_layout_line_get_extents (line, NULL, &logical_rect);
+
+  metrics = pango_context_get_metrics(context,
+                                  attributes.font_desc,
+                                  pango_context_get_language(context)); 
+
+  ascent = pango_font_metrics_get_ascent(metrics) / PANGO_SCALE;
+  descent = pango_font_metrics_get_descent(metrics) / PANGO_SCALE;
+
+  pango_font_metrics_unref(metrics);
+
+  /* Align primarily for locale's ascent/descent */
+
+  logical_rect.height /= PANGO_SCALE;
+  logical_rect.y /= PANGO_SCALE;
+  y_pos =  area.height - logical_rect.height;
+
+  if (logical_rect.height > area.height)
+    y_pos = (logical_rect.height - area.height - 2*CELLOFFSET) / 2;
+  else if (y_pos < 0)
+    y_pos = 0;
+  else if (y_pos + logical_rect.height > area.height)
+    y_pos = area.height - logical_rect.height;
+
+  text_width = rect.width;
+  text_height = rect.height;
+  y = area.y + y_pos - CELLOFFSET;
+
+  switch(attributes.justification){
+    case GTK_JUSTIFY_RIGHT:
+          size=area.width;
+          area.x+=area.width;
+          if(!gtk_sheet_clip_text(sheet)){          
+           for(i=col-1; i>=MIN_VISIBLE_COLUMN(sheet); i--){
+             if(gtk_sheet_cell_get_text(sheet, row, i)) break;
+             if(size>=text_width+CELLOFFSET) break;
+             size+=xxx_column_width(sheet, i);
+            xxx_column_set_right_column(sheet, i, 
+                                        MAX(col,
+                                            xxx_column_right_column(sheet, i)));
+           }
+           area.width=size;
+          }
+          area.x-=size;
+          xoffset+=area.width-text_width - 2 * CELLOFFSET -
+                   attributes.border.width/2;
+          break;
+     case GTK_JUSTIFY_CENTER:
+          sizel=area.width/2;
+          sizer=area.width/2;
+         area.x+=area.width/2;
+          if(!gtk_sheet_clip_text(sheet)){          
+           for(i=col+1; i<=MAX_VISIBLE_COLUMN(sheet); i++){
+             if(gtk_sheet_cell_get_text(sheet, row, i)) break;
+             if(sizer>=text_width/2) break;
+             sizer+= xxx_column_width(sheet, i);
+            xxx_column_set_left_column(sheet, i, 
+                                       MIN(
+                                           col, 
+                                           xxx_column_left_column(sheet, i)));
+           }
+           for(i=col-1; i>=MIN_VISIBLE_COLUMN(sheet); i--){
+             if(gtk_sheet_cell_get_text(sheet, row, i)) break;
+             if(sizel>=text_width/2) break;
+             sizel+=xxx_column_width(sheet, i);
+            xxx_column_set_right_column(sheet, i, 
+                                        MAX(col,
+                                            xxx_column_right_column(sheet, i)));
+           }
+           size=MIN(sizel, sizer);
+          }
+         area.x-=sizel;
+          xoffset+= sizel - text_width/2 - CELLOFFSET;
+         area.width=sizel+sizer;
+          break;
+      case GTK_JUSTIFY_LEFT:
+      default:
+          size=area.width;
+          if(!gtk_sheet_clip_text(sheet)){          
+           for(i=col+1; i<=MAX_VISIBLE_COLUMN(sheet); i++){
+             if(gtk_sheet_cell_get_text(sheet, row, i)) break;
+             if(size>=text_width+CELLOFFSET) break;
+             size+=xxx_column_width(sheet, i);
+            xxx_column_set_left_column(sheet, i, 
+                                       MIN(
+                                           col, 
+                                           xxx_column_left_column(sheet, i)));
+
+           }
+           area.width=size;
+          }
+          xoffset += attributes.border.width/2;
+          break;
+   }
+
+  if(!gtk_sheet_clip_text(sheet)) clip_area = area;
+  gdk_gc_set_clip_rectangle(fg_gc, &clip_area);
+
+
+  gdk_draw_layout (sheet->pixmap, fg_gc,
+                   area.x + xoffset + CELLOFFSET,
+                  y,
+                   layout);
+
+  gdk_gc_set_clip_rectangle(fg_gc, NULL);
+  g_object_unref(G_OBJECT(layout));
+
+  gdk_draw_pixmap(sheet->sheet_window,
+                  GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                  sheet->pixmap,
+                  area.x,
+                  area.y,
+                  area.x,
+                  area.y,
+                  area.width,
+                  area.height);      
+
+}
+
+
+
+static void
+gtk_sheet_range_draw(GtkSheet *sheet, const GtkSheetRange *range)
+{
+ gint i,j;
+ GtkSheetRange drawing_range;
+ GdkRectangle area;
+
+ g_return_if_fail(sheet != NULL);
+ g_return_if_fail(GTK_SHEET(sheet));
+ if(!GTK_WIDGET_DRAWABLE(GTK_WIDGET(sheet))) return;
+ if(!GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))) return;
+ if(!GTK_WIDGET_MAPPED(GTK_WIDGET(sheet))) return;
+
+ if(range == NULL)
+ {
+   drawing_range.row0=MIN_VISIBLE_ROW(sheet);
+   drawing_range.col0=MIN_VISIBLE_COLUMN(sheet);
+   drawing_range.rowi=MAX_VISIBLE_ROW(sheet);
+   drawing_range.coli=MAX_VISIBLE_COLUMN(sheet);
+/*
+   gdk_draw_rectangle (sheet->pixmap,
+                      GTK_WIDGET(sheet)->style->white_gc,
+                      TRUE,
+                      0,0,
+                      sheet->sheet_window_width,sheet->sheet_window_height);
+*/
+ }
+ else
+ {
+   drawing_range.row0=MAX(range->row0, MIN_VISIBLE_ROW(sheet));
+   drawing_range.col0=MAX(range->col0, MIN_VISIBLE_COLUMN(sheet));
+   drawing_range.rowi=MIN(range->rowi, MAX_VISIBLE_ROW(sheet));
+   drawing_range.coli=MIN(range->coli, MAX_VISIBLE_COLUMN(sheet));
+ }
+
+
+ if(drawing_range.coli == xxx_column_count(sheet) - 1)
+   {
+     area.x=COLUMN_LEFT_XPIXEL(sheet,
+                              xxx_column_count(sheet) - 1) +
+       xxx_column_width(sheet, xxx_column_count(sheet) - 1) + 1;
+
+     area.y=0;
+
+     gdk_gc_set_foreground(sheet->fg_gc, &sheet->bg_color);
+
+     gdk_draw_rectangle (sheet->pixmap,
+                        sheet->fg_gc,
+                        TRUE,
+                        area.x,area.y,
+                        sheet->sheet_window_width - area.x, 
+                        sheet->sheet_window_height);
+
+     gdk_draw_pixmap(sheet->sheet_window,
+                    GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                    sheet->pixmap,
+                    area.x,
+                    area.y,
+                    area.x,
+                    area.y,
+                    sheet->sheet_window_width - area.x, 
+                    sheet->sheet_window_height);                  
+   }
+ if(drawing_range.rowi == yyy_row_count(sheet) - 1){
+  area.x=0;
+  area.y=ROW_TOP_YPIXEL(sheet,
+                       yyy_row_count(sheet) - 1) + 
+    yyy_row_height(sheet, yyy_row_count(sheet) - 1) + 1;
+
+  gdk_gc_set_foreground(sheet->fg_gc, &sheet->bg_color);
+
+  gdk_draw_rectangle (sheet->pixmap,
+                     sheet->fg_gc,
+                     TRUE,
+                     area.x,area.y,
+                     sheet->sheet_window_width,
+                      sheet->sheet_window_height - area.y);
+
+  gdk_draw_pixmap(sheet->sheet_window,
+                  GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                  sheet->pixmap,
+                  area.x,
+                  area.y,
+                  area.x,
+                  area.y,
+                  sheet->sheet_window_width,
+                  sheet->sheet_window_height - area.y);
+ }
+
+ for(i=drawing_range.row0; i<=drawing_range.rowi; i++)
+  for(j=drawing_range.col0; j<=drawing_range.coli; j++){
+     gtk_sheet_cell_draw_default(sheet, i, j);
+  }
+
+ for(i=drawing_range.row0; i<=drawing_range.rowi; i++)
+  for(j=drawing_range.col0; j<=drawing_range.coli; j++){
+     gtk_sheet_cell_draw_border(sheet, i-1, j, GTK_SHEET_BOTTOM_BORDER);
+     gtk_sheet_cell_draw_border(sheet, i+1, j, GTK_SHEET_TOP_BORDER);
+     gtk_sheet_cell_draw_border(sheet, i, j-1, GTK_SHEET_RIGHT_BORDER);
+     gtk_sheet_cell_draw_border(sheet, i, j+1, GTK_SHEET_LEFT_BORDER);
+     gtk_sheet_cell_draw_border(sheet, i, j, 15);
+  }
+
+ for(i=drawing_range.row0; i<=drawing_range.rowi; i++)
+  for(j=drawing_range.col0; j<=drawing_range.coli; j++)
+                  gtk_sheet_cell_draw_label (sheet, i, j);
+     
+ for(i=drawing_range.row0; i<=drawing_range.rowi; i++)
+   for(j= xxx_column_left_column(sheet, drawing_range.col0);
+       j<drawing_range.col0; j++)
+     gtk_sheet_cell_draw_label (sheet, i, j);
+    
+ for(i=drawing_range.row0; i<=drawing_range.rowi; i++)
+   for(j = drawing_range.coli+1; 
+       j <= xxx_column_right_column(sheet, drawing_range.coli);
+       j++)
+     gtk_sheet_cell_draw_label (sheet, i, j); 
+
+  gtk_sheet_draw_backing_pixmap(sheet, drawing_range);
+
+  if(sheet->state != GTK_SHEET_NORMAL && gtk_sheet_range_isvisible(sheet, sheet->range))
+       gtk_sheet_range_draw_selection(sheet, drawing_range);
+  
+  if(sheet->state == GTK_STATE_NORMAL && 
+     sheet->active_cell.row >= drawing_range.row0 &&
+     sheet->active_cell.row <= drawing_range.rowi &&
+     sheet->active_cell.col >= drawing_range.col0 &&
+     sheet->active_cell.col <= drawing_range.coli)    
+                            gtk_sheet_show_active_cell(sheet);
+
+}
+
+static void
+gtk_sheet_range_draw_selection(GtkSheet *sheet, GtkSheetRange range)
+{
+  GdkRectangle area;
+  gint i,j;
+  GtkSheetRange aux;
+
+  if(range.col0 > sheet->range.coli || range.coli < sheet->range.col0 ||
+     range.row0 > sheet->range.rowi || range.rowi < sheet->range.row0)
+     return;
+
+  if(!gtk_sheet_range_isvisible(sheet, range)) return;
+  if(!GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))) return;
+
+  aux=range;
+
+  range.col0=MAX(sheet->range.col0, range.col0);
+  range.coli=MIN(sheet->range.coli, range.coli);
+  range.row0=MAX(sheet->range.row0, range.row0);
+  range.rowi=MIN(sheet->range.rowi, range.rowi);
+
+  range.col0=MAX(range.col0, MIN_VISIBLE_COLUMN(sheet));
+  range.coli=MIN(range.coli, MAX_VISIBLE_COLUMN(sheet));
+  range.row0=MAX(range.row0, MIN_VISIBLE_ROW(sheet));
+  range.rowi=MIN(range.rowi, MAX_VISIBLE_ROW(sheet));
+
+  for(i=range.row0; i<=range.rowi; i++){
+   for(j=range.col0; j<=range.coli; j++){
+
+    if(gtk_sheet_cell_get_state(sheet, i, j)==GTK_STATE_SELECTED && 
+       xxx_column_is_visible(sheet, j) && yyy_row_is_visible(sheet, i)){
+
+#if 0
+      row_button_set(sheet, i);
+      column_button_set(sheet, j);
+#endif
+
+      area.x=COLUMN_LEFT_XPIXEL(sheet,j);
+      area.y=ROW_TOP_YPIXEL(sheet,i);
+      area.width= xxx_column_width(sheet, j);
+      area.height=yyy_row_height(sheet, i);
+
+      if(i==sheet->range.row0){
+            area.y=area.y+2;
+            area.height=area.height-2;
+      }
+      if(i==sheet->range.rowi) area.height=area.height-3;
+      if(j==sheet->range.col0){
+            area.x=area.x+2;
+            area.width=area.width-2;
+      }
+      if(j==sheet->range.coli) area.width=area.width-3;
+
+      if(i!=sheet->active_cell.row || j!=sheet->active_cell.col){
+       gdk_draw_rectangle (sheet->sheet_window,
+                          sheet->xor_gc,
+                          TRUE,
+                          area.x+1,area.y+1,
+                          area.width,area.height);
+      }
+    }
+
+   }
+  }
+
+  gtk_sheet_draw_border(sheet, sheet->range);
+
+}
+
+static void
+gtk_sheet_draw_backing_pixmap(GtkSheet *sheet, GtkSheetRange range)
+{
+  gint x,y,width,height;
+  
+  if(!GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))) return;
+  x = COLUMN_LEFT_XPIXEL(sheet,range.col0);
+  y = ROW_TOP_YPIXEL(sheet, range.row0);  
+  width = COLUMN_LEFT_XPIXEL(sheet, range.coli) - x +
+    xxx_column_width(sheet, range.coli);
+
+  height=ROW_TOP_YPIXEL(sheet, range.rowi)-y+yyy_row_height(sheet, range.rowi);
+
+  if(range.row0==sheet->range.row0){
+          y=y-5;
+          height=height+5;
+  }
+  if(range.rowi==sheet->range.rowi) height=height+5;
+  if(range.col0==sheet->range.col0){
+            x=x-5;
+            width=width+5;
+  }
+  if(range.coli==sheet->range.coli) width=width+5;
+
+  
+  width=MIN(width, sheet->sheet_window_width-x);
+  height=MIN(height, sheet->sheet_window_height-y);
+
+  x--; 
+  y--;
+  width+=2;
+  height+=2;
+
+  x = (sheet->row_titles_visible)
+       ? MAX(x, sheet->row_title_area.width) : MAX(x, 0);
+  y = (sheet->column_titles_visible)
+       ? MAX(y, sheet->column_title_area.height) : MAX(y, 0);
+
+  if(range.coli == xxx_column_count(sheet) - 1) 
+    width = sheet->sheet_window_width - x; 
+  if(range.rowi == yyy_row_count(sheet)    - 1) 
+    height=sheet->sheet_window_height - y;
+
+  gdk_draw_pixmap(sheet->sheet_window,
+                  GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                  sheet->pixmap,
+                  x,
+                  y,
+                  x,
+                  y,
+                  width+1,
+                  height+1);                  
+}
+
+
+void 
+gtk_sheet_set_cell_text(GtkSheet *sheet, gint row, gint col, const gchar *text)
+{
+ GtkSheetCellAttr attributes;
+
+ g_return_if_fail (sheet != NULL);
+ g_return_if_fail (GTK_IS_SHEET (sheet));
+ if (col >= xxx_column_count(sheet) || row >= yyy_row_count(sheet)) return;
+ if (col < 0 || row < 0) return;
+
+ gtk_sheet_get_attributes(sheet, row, col, &attributes);
+ gtk_sheet_set_cell(sheet, row, col, attributes.justification, text);
+}
+
+static inline gint 
+safe_strcmp(const gchar *s1, const gchar *s2)
+{
+  if ( !s1 && !s2) return 0;
+  if ( !s1) return -1;
+  if ( !s2) return +1;
+  return strcmp(s1, s2);
+}
+
+void 
+gtk_sheet_set_cell(GtkSheet *sheet, gint row, gint col, 
+                   GtkJustification justification,
+                   const gchar *text)
+{
+ GtkSheetRange range;
+ gint text_width;
+ GtkSheetCellAttr attributes;
+
+ g_return_if_fail (sheet != NULL);
+ g_return_if_fail (GTK_IS_SHEET (sheet));
+ if (col >= xxx_column_count(sheet) || row >= yyy_row_count(sheet)) return;
+ if (col < 0 || row < 0) return;
+
+ gtk_sheet_get_attributes(sheet, row, col, &attributes);
+
+ attributes.justification = justification;
+
+ GSheetModel *model =  gtk_sheet_get_model(sheet);
+
+ const gchar *old_text = g_sheet_model_get_string(model, row, col);
+
+ gboolean changed = FALSE;
+
+ if (0 != safe_strcmp(old_text, text))
+   changed = g_sheet_model_set_string(model, text, row, col);
+
+ if(changed && attributes.is_visible){
+   const gchar *s = gtk_sheet_cell_get_text(sheet, row, col);
+   text_width = 0;
+   if(s && strlen(s) > 0) {
+     text_width = STRING_WIDTH(GTK_WIDGET(sheet), attributes.font_desc, text);
+   }
+
+   range.row0 = row;
+   range.rowi = row;
+   range.col0 = sheet->view.col0;
+   range.coli = sheet->view.coli;
+
+   if(gtk_sheet_autoresize(sheet) &&
+      text_width > xxx_column_width(sheet, col) - 2*CELLOFFSET-attributes.border.width){
+      gtk_sheet_set_column_width(sheet, col, text_width+2*CELLOFFSET+attributes.border.width);
+      GTK_SHEET_SET_FLAGS(sheet, GTK_SHEET_REDRAW_PENDING);
+   }
+   else
+     if(!GTK_SHEET_IS_FROZEN(sheet))
+       gtk_sheet_range_draw(sheet, &range);
+ }
+
+ if ( changed ) 
+   gtk_signal_emit(GTK_OBJECT(sheet),sheet_signals[CHANGED], row, col);
+
+}
+
+
+void
+gtk_sheet_cell_clear (GtkSheet *sheet, gint row, gint column)
+{
+  GtkSheetRange range;
+
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+  if (column >= xxx_column_count(sheet) || row >= yyy_row_count(sheet)) return;
+  if (column < 0 || row < 0) return;
+
+  range.row0 = row;
+  range.rowi = row;
+  range.col0 = sheet->view.col0;
+  range.coli = sheet->view.coli;
+
+  gtk_sheet_real_cell_clear(sheet, row, column, FALSE);
+
+  if(!GTK_SHEET_IS_FROZEN(sheet)){
+     gtk_sheet_range_draw(sheet, &range);
+  }
+}
+
+void
+gtk_sheet_cell_delete (GtkSheet *sheet, gint row, gint column)
+{
+  GtkSheetRange range;
+
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+  if (column >= xxx_column_count(sheet) || row >= yyy_row_count(sheet)) return;
+  if (column < 0 || row < 0) return;
+
+  range.row0 = row;
+  range.rowi = row;
+  range.col0 = sheet->view.col0;
+  range.coli = sheet->view.coli;
+
+  gtk_sheet_real_cell_clear(sheet, row, column, TRUE);
+
+  if(!GTK_SHEET_IS_FROZEN(sheet)){
+     gtk_sheet_range_draw(sheet, &range);
+  }
+}
+
+static void
+gtk_sheet_real_cell_clear (GtkSheet *sheet, gint row, gint column, gboolean delete)
+{
+  const gchar *old_text;
+
+  old_text = gtk_sheet_cell_get_text(sheet, row, column); 
+  GSheetModel *model =  gtk_sheet_get_model(sheet);
+
+  if (old_text && strlen(old_text) > 0 )
+    {
+      g_sheet_model_datum_clear(model, row, column);
+      
+      if(GTK_IS_OBJECT(sheet) && G_OBJECT(sheet)->ref_count > 0)
+       gtk_signal_emit(GTK_OBJECT(sheet),sheet_signals[CLEAR_CELL], 
+                       row, column);
+    }  
+
+}
+    
+void
+gtk_sheet_range_clear (GtkSheet *sheet, const GtkSheetRange *range)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  gtk_sheet_real_range_clear(sheet, range, FALSE);
+}
+
+void
+gtk_sheet_range_delete (GtkSheet *sheet, const GtkSheetRange *range)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  gtk_sheet_real_range_clear(sheet, range, TRUE);
+}
+
+static void
+gtk_sheet_real_range_clear (GtkSheet *sheet, const GtkSheetRange *range, 
+                            gboolean delete)
+{
+  gint i, j;
+  GtkSheetRange clear;
+
+  if(!range){
+    clear.row0=0;
+    clear.rowi = yyy_row_count(sheet) - 1;
+    clear.col0=0;
+    clear.coli = xxx_column_count(sheet) - 1;
+  }else
+    clear=*range;  
+
+  clear.row0=MAX(clear.row0, 0);
+  clear.col0=MAX(clear.col0, 0);
+  clear.rowi=MIN(clear.rowi, yyy_row_count(sheet) - 1 );
+  clear.coli=MIN(clear.coli, xxx_column_count(sheet) - 1 );
+
+  for(i=clear.row0; i<=clear.rowi; i++)
+    for(j=clear.col0; j<=clear.coli; j++){
+      gtk_sheet_real_cell_clear(sheet, i, j, delete);
+    }
+
+  gtk_sheet_range_draw(sheet, NULL);
+}
+
+
+const gchar *     
+gtk_sheet_cell_get_text (GtkSheet *sheet, gint row, gint col)
+{
+  GSheetModel *model;
+  g_return_val_if_fail (sheet != NULL, NULL);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), NULL);
+
+  if(col >= xxx_column_count(sheet) || row >= yyy_row_count(sheet)) 
+    return NULL;
+  if(col < 0 || row < 0) return NULL;
+
+  model =  gtk_sheet_get_model(sheet);
+
+  if ( !model ) 
+    return NULL;
+
+  return g_sheet_model_get_string(model, row, col);
+}
+
+
+GtkStateType
+gtk_sheet_cell_get_state (GtkSheet *sheet, gint row, gint col)
+{
+ gint state;
+ GtkSheetRange *range;
+
+ g_return_val_if_fail (sheet != NULL, 0);
+ g_return_val_if_fail (GTK_IS_SHEET (sheet), 0);
+ if(col >= xxx_column_count(sheet) || row >= yyy_row_count(sheet)) return 0;
+ if(col < 0 || row < 0) return 0;
+
+ state = sheet->state;
+ range = &sheet->range;
+
+ switch (state)
+   {
+   case GTK_SHEET_NORMAL:
+     return GTK_STATE_NORMAL;
+     break;
+   case GTK_SHEET_ROW_SELECTED:
+     if(row>=range->row0 && row<=range->rowi) 
+       return GTK_STATE_SELECTED;
+     break;
+   case GTK_SHEET_COLUMN_SELECTED:
+     if(col>=range->col0 && col<=range->coli) 
+       return GTK_STATE_SELECTED;
+     break;
+   case GTK_SHEET_RANGE_SELECTED:
+     if(row >= range->row0 && row <= range->rowi && \
+       col >= range->col0 && col <= range->coli)
+       return GTK_STATE_SELECTED;
+     break;
+   }
+ return GTK_STATE_NORMAL;
+}
+
+gboolean
+gtk_sheet_get_pixel_info (GtkSheet * sheet,
+                         gint x,
+                         gint y,
+                         gint * row,
+                         gint * column)
+{
+  gint trow, tcol;
+
+  g_return_val_if_fail (sheet != NULL, 0);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), 0);
+
+  /* bounds checking, return false if the user clicked 
+   * on a blank area */
+  trow = ROW_FROM_YPIXEL (sheet, y);
+  if (trow >= yyy_row_count(sheet))
+    return FALSE;
+
+  *row = trow;
+
+  tcol = COLUMN_FROM_XPIXEL (sheet, x);
+  if (tcol >= xxx_column_count(sheet))
+    return FALSE;
+
+ *column = tcol;
+
+  return TRUE;
+}
+
+gboolean
+gtk_sheet_get_cell_area  (GtkSheet * sheet,
+                         gint row,
+                          gint column,
+                         GdkRectangle *area)
+{
+  g_return_val_if_fail (sheet != NULL, 0);
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), 0);
+
+  if(row >= yyy_row_count(sheet) || column >= xxx_column_count(sheet)) 
+    return FALSE;
+
+  area->x = (column == -1) ? 0 : (COLUMN_LEFT_XPIXEL(sheet, column) -
+                                 (sheet->row_titles_visible
+                                   ? sheet->row_title_area.width
+                                   : 0));
+  area->y = (row == -1) ? 0 : (ROW_TOP_YPIXEL(sheet, row) -
+                              (sheet->column_titles_visible
+                               ? sheet->column_title_area.height
+                               : 0));
+  area->width= (column == -1) ? sheet->row_title_area.width
+    : xxx_column_width(sheet, column);
+
+  area->height= (row == -1) ? sheet->column_title_area.height
+                            : yyy_row_height(sheet, row);
+
+/*
+  if(row < 0 || column < 0) return FALSE;
+
+  area->x = COLUMN_LEFT_XPIXEL(sheet, column);
+  area->y = ROW_TOP_YPIXEL(sheet, row);
+  if(sheet->row_titles_visible)
+           area->x -= sheet->row_title_area.width;
+  if(sheet->column_titles_visible)
+           area->y -= sheet->column_title_area.height;
+
+  area->width=sheet->column[column].width;
+  area->height=yyy_row_height(sheet, row);  
+*/
+  return TRUE;
+}
+
+gboolean 
+gtk_sheet_set_active_cell (GtkSheet *sheet, gint row, gint column)
+{
+ g_return_val_if_fail (sheet != NULL, 0);
+ g_return_val_if_fail (GTK_IS_SHEET (sheet), 0);
+
+ if(row < 0 || column < 0) return FALSE;
+ if(row >= yyy_row_count(sheet) || column >= xxx_column_count(sheet)) 
+   return FALSE;
+
+ if(GTK_WIDGET_REALIZED(GTK_WIDGET(sheet)))
+   {
+       if(!gtk_sheet_deactivate_cell(sheet)) return FALSE;
+   }
+
+ sheet->active_cell.row=row;
+ sheet->active_cell.col=column;
+ if(!gtk_sheet_activate_cell(sheet, row, column)) return FALSE;
+ if(gtk_sheet_autoscroll(sheet))
+   gtk_sheet_move_query(sheet, row, column);
+
+ return TRUE;
+}
+
+void
+gtk_sheet_get_active_cell (GtkSheet *sheet, gint *row, gint *column)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  *row = sheet->active_cell.row;
+  *column = sheet->active_cell.col;
+}
+
+static void
+gtk_sheet_entry_changed(GtkWidget *widget, gpointer data)
+{
+ GtkSheet *sheet;
+ gint row,col;
+ const char *text;
+ GtkJustification justification;
+ GtkSheetCellAttr attributes;
+
+ g_return_if_fail (data != NULL);
+ g_return_if_fail (GTK_IS_SHEET (data));
+
+ sheet=GTK_SHEET(data);
+
+ if(!GTK_WIDGET_VISIBLE(widget)) return;
+ if(sheet->state != GTK_STATE_NORMAL) return;
+
+ row=sheet->active_cell.row;
+ col=sheet->active_cell.col;
+
+ if(row<0 || col<0) return;
+
+ sheet->active_cell.row=-1;
+ sheet->active_cell.col=-1;
+
+ text = gtk_entry_get_text(GTK_ENTRY(gtk_sheet_get_entry(sheet)));
+
+ GTK_SHEET_SET_FLAGS(sheet, GTK_SHEET_IS_FROZEN);
+
+ if(text && strlen(text) > 0){
+      gtk_sheet_get_attributes(sheet, row, col, &attributes); 
+      justification=attributes.justification;
+      gtk_sheet_set_cell(sheet, row, col, justification, text);
+ }
+
+ if(sheet->freeze_count == 0)
+        GTK_SHEET_UNSET_FLAGS(sheet, GTK_SHEET_IS_FROZEN);
+ sheet->active_cell.row=row;;
+ sheet->active_cell.col=col;
+}
+
+
+static gboolean 
+gtk_sheet_deactivate_cell(GtkSheet *sheet)
+{
+ gboolean veto = TRUE;
+
+ g_return_val_if_fail (sheet != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_SHEET (sheet), FALSE);
+
+ if(!GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))) return FALSE;
+ if(sheet->state != GTK_SHEET_NORMAL) return FALSE;
+
+ _gtkextra_signal_emit(GTK_OBJECT(sheet),sheet_signals[DEACTIVATE], 
+                                   sheet->active_cell.row,
+                                   sheet->active_cell.col, &veto);
+
+ if(!veto) return FALSE;
+
+ gtk_signal_disconnect_by_func(GTK_OBJECT(gtk_sheet_get_entry(sheet)),
+                              (GtkSignalFunc) gtk_sheet_entry_changed,
+                              GTK_OBJECT(GTK_WIDGET(sheet)));
+
+ gtk_sheet_hide_active_cell(sheet);
+ sheet->active_cell.row=-1;
+ sheet->active_cell.col=-1;
+ if(GTK_SHEET_REDRAW_PENDING(sheet)){
+   GTK_SHEET_UNSET_FLAGS(sheet, GTK_SHEET_REDRAW_PENDING);
+   gtk_sheet_range_draw(sheet, NULL);
+ }
+
+ return TRUE;
+}      
+
+static void
+gtk_sheet_hide_active_cell(GtkSheet *sheet)
+{
+ const char *text;
+ gint row,col;
+ GtkJustification justification;
+ GtkSheetCellAttr attributes;
+
+ if(!GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))) return;
+
+ row=sheet->active_cell.row;
+ col=sheet->active_cell.col;
+
+ if(row < 0 || col < 0) return;
+
+ if(sheet->freeze_count == 0)
+     GTK_SHEET_UNSET_FLAGS(sheet, GTK_SHEET_IS_FROZEN);
+
+ text=gtk_entry_get_text(GTK_ENTRY(gtk_sheet_get_entry(sheet)));
+
+ gtk_sheet_get_attributes(sheet, row, col, &attributes); 
+ justification=attributes.justification;
+
+ if(text && strlen(text)!=0){
+      gtk_sheet_set_cell(sheet, row, col, justification, text);
+      gtk_signal_emit(GTK_OBJECT(sheet),sheet_signals[SET_CELL], row, col);
+ }
+ else
+ {
+      gtk_sheet_cell_clear(sheet, row, col);
+ }
+
+ row=sheet->active_cell.row;
+ col=sheet->active_cell.col;
+
+#if 0
+ column_button_release(sheet, col);
+ row_button_release(sheet, row);
+#endif
+
+ gtk_widget_unmap(sheet->sheet_entry);
+
+ if(row != -1 && col != -1)
+   gdk_draw_pixmap(sheet->sheet_window,
+                   GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                   sheet->pixmap,
+                   COLUMN_LEFT_XPIXEL(sheet,col)-1,
+                   ROW_TOP_YPIXEL(sheet,row)-1,
+                   COLUMN_LEFT_XPIXEL(sheet,col)-1,
+                   ROW_TOP_YPIXEL(sheet,row)-1,
+                  xxx_column_width(sheet, col) + 4,
+                   yyy_row_height(sheet, row)+4);   
+
+ gtk_widget_grab_focus(GTK_WIDGET(sheet));
+
+ GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(sheet->sheet_entry), GTK_VISIBLE);
+
+}
+
+static gboolean
+gtk_sheet_activate_cell(GtkSheet *sheet, gint row, gint col)
+{
+ gboolean veto = TRUE;
+
+ g_return_val_if_fail (sheet != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_SHEET (sheet), FALSE);
+
+ if(row < 0 || col < 0) return FALSE;
+ if(row >= yyy_row_count(sheet) || col >= xxx_column_count(sheet)) 
+   return FALSE;
+
+/* _gtkextra_signal_emit(GTK_OBJECT(sheet),sheet_signals[ACTIVATE], row, col, &veto);
+ if(!GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))) return veto;
+*/
+
+ if(!veto) return FALSE;
+ if(sheet->state != GTK_SHEET_NORMAL){
+        sheet->state=GTK_SHEET_NORMAL;
+        gtk_sheet_real_unselect_range(sheet, NULL);
+ }
+
+ sheet->range.row0=row;
+ sheet->range.col0=col;
+ sheet->range.rowi=row;
+ sheet->range.coli=col;
+ sheet->active_cell.row=row;
+ sheet->active_cell.col=col;
+ sheet->selection_cell.row=row;
+ sheet->selection_cell.col=col;
+#if 0
+ row_button_set(sheet, row);
+ column_button_set(sheet, col); 
+#endif
+
+ GTK_SHEET_UNSET_FLAGS(sheet, GTK_SHEET_IN_SELECTION);
+ gtk_sheet_show_active_cell(sheet);
+
+   g_signal_connect(G_OBJECT(gtk_sheet_get_entry(sheet)),
+                   "changed",
+                   G_CALLBACK(gtk_sheet_entry_changed),
+                   sheet);
+
+ _gtkextra_signal_emit(GTK_OBJECT(sheet),sheet_signals[ACTIVATE], row, col, &veto);
+
+ return TRUE;
+}
+
+static void
+gtk_sheet_show_active_cell(GtkSheet *sheet)
+{
+ GtkEntry *sheet_entry;
+ GtkSheetCellAttr attributes;
+ gchar *text = NULL;
+ const gchar *old_text;
+ GtkJustification justification;
+ gint row, col;
+
+ g_return_if_fail (sheet != NULL);
+ g_return_if_fail (GTK_IS_SHEET (sheet));
+
+ row = sheet->active_cell.row;
+ col = sheet->active_cell.col;
+
+ /* Don't show the active cell, if there is no active cell: */
+ if(!(row >= 0 && col >= 0)) /* e.g row or coll == -1. */
+   return;
+  
+ if(!GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))) return;
+ if(sheet->state != GTK_SHEET_NORMAL) return;
+ if(GTK_SHEET_IN_SELECTION(sheet)) return;
+
+ GTK_WIDGET_SET_FLAGS(GTK_WIDGET(sheet->sheet_entry), GTK_VISIBLE);
+
+ sheet_entry = GTK_ENTRY(gtk_sheet_get_entry(sheet));
+
+ gtk_sheet_get_attributes(sheet, row, col, &attributes); 
+
+ justification = GTK_JUSTIFY_LEFT;
+
+ if(gtk_sheet_justify_entry(sheet))
+      justification = attributes.justification;
+
+ text = g_strdup(gtk_sheet_cell_get_text(sheet, row, col));
+
+ if(!text) text = g_strdup("");
+
+ gtk_entry_set_visibility(GTK_ENTRY(sheet_entry), attributes.is_visible);
+
+ if(gtk_sheet_locked(sheet) || !attributes.is_editable){ 
+            gtk_entry_set_editable(GTK_ENTRY(sheet_entry), FALSE);
+ }else{
+            gtk_entry_set_editable(GTK_ENTRY(sheet_entry), TRUE);
+ }
+
+/*** Added by John Gotts. Mar 25, 2005 *********/
+ old_text = gtk_entry_get_text(GTK_ENTRY(sheet_entry));
+ if (strcmp(old_text, text) != 0) 
+{
+  if(!GTK_IS_ITEM_ENTRY(sheet_entry))
+     gtk_entry_set_text(GTK_ENTRY(sheet_entry), text);
+  else
+     gtk_item_entry_set_text(GTK_ITEM_ENTRY(sheet_entry), text, justification);
+ }
+
+ gtk_sheet_entry_set_max_size(sheet);
+ gtk_sheet_size_allocate_entry(sheet);
+
+ gtk_widget_map(sheet->sheet_entry);
+ gtk_sheet_draw_active_cell(sheet);
+
+ gtk_widget_grab_focus(GTK_WIDGET(sheet_entry));
+
+ g_free(text);
+}
+
+static void
+gtk_sheet_draw_active_cell(GtkSheet *sheet)
+{
+    gint row, col;
+
+    if(!GTK_WIDGET_DRAWABLE(GTK_WIDGET(sheet))) return;
+    if(!GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))) return;
+
+    row = sheet->active_cell.row;
+    col = sheet->active_cell.col;
+    if(row<0 || col<0) return;
+
+    if(!gtk_sheet_cell_isvisible(sheet, row, col)) return;
+#if 0
+    row_button_set(sheet, row);
+    column_button_set(sheet, col);
+#endif
+    gtk_sheet_draw_backing_pixmap(sheet, sheet->range);
+    gtk_sheet_draw_border(sheet, sheet->range);
+
+}
+
+
+static void
+gtk_sheet_make_backing_pixmap (GtkSheet *sheet, guint width, guint height)
+{
+  gint pixmap_width, pixmap_height;
+
+  if(!GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))) return;
+
+  if(width == 0 && height == 0){
+     width=sheet->sheet_window_width+80;
+     height=sheet->sheet_window_height+80;
+  }
+
+  if (!sheet->pixmap)
+    {
+      /* allocate */
+      sheet->pixmap = gdk_pixmap_new (sheet->sheet_window,
+                                     width, height,
+                                     -1);
+      if(!GTK_SHEET_IS_FROZEN(sheet)) gtk_sheet_range_draw(sheet, NULL);
+    }
+  else
+    {
+      /* reallocate if sizes don't match */
+      gdk_window_get_size (sheet->pixmap,
+                          &pixmap_width, &pixmap_height);
+      if ((pixmap_width != width) || (pixmap_height != height))
+       {
+          g_free(sheet->pixmap);
+         sheet->pixmap = gdk_pixmap_new (sheet->sheet_window,
+                                              width, height,
+                                              -1);
+          if(!GTK_SHEET_IS_FROZEN(sheet)) gtk_sheet_range_draw(sheet, NULL);
+       }
+    }
+}
+
+static void
+gtk_sheet_new_selection(GtkSheet *sheet, GtkSheetRange *range)
+{
+  gint i,j, mask1, mask2;
+  gint state, selected;
+  gint x,y,width,height;
+  GtkSheetRange new_range, aux_range;
+
+  g_return_if_fail (sheet != NULL);
+
+  if(range==NULL) range=&sheet->range;
+
+  new_range=*range;
+
+  range->row0=MIN(range->row0, sheet->range.row0);
+  range->rowi=MAX(range->rowi, sheet->range.rowi);
+  range->col0=MIN(range->col0, sheet->range.col0);
+  range->coli=MAX(range->coli, sheet->range.coli);
+
+  range->row0=MAX(range->row0, MIN_VISIBLE_ROW(sheet));
+  range->rowi=MIN(range->rowi, MAX_VISIBLE_ROW(sheet));
+  range->col0=MAX(range->col0, MIN_VISIBLE_COLUMN(sheet));
+  range->coli=MIN(range->coli, MAX_VISIBLE_COLUMN(sheet));
+
+  aux_range.row0=MAX(new_range.row0, MIN_VISIBLE_ROW(sheet));
+  aux_range.rowi=MIN(new_range.rowi, MAX_VISIBLE_ROW(sheet));
+  aux_range.col0=MAX(new_range.col0, MIN_VISIBLE_COLUMN(sheet));
+  aux_range.coli=MIN(new_range.coli, MAX_VISIBLE_COLUMN(sheet));
+
+  for(i=range->row0; i<=range->rowi; i++){
+   for(j=range->col0; j<=range->coli; j++){     
+
+    state=gtk_sheet_cell_get_state(sheet, i, j);
+    selected=(i<=new_range.rowi && i>=new_range.row0 && 
+        j<=new_range.coli && j>=new_range.col0) ? TRUE : FALSE;
+
+    if(state==GTK_STATE_SELECTED && selected &&
+       xxx_column_is_visible(sheet, j) && yyy_row_is_visible(sheet, i) &&
+       (i==sheet->range.row0 || i==sheet->range.rowi ||
+        j==sheet->range.col0 || j==sheet->range.coli ||
+        i==new_range.row0 || i==new_range.rowi ||
+        j==new_range.col0 || j==new_range.coli)){
+
+       mask1 = i==sheet->range.row0 ? 1 : 0;
+       mask1 = i==sheet->range.rowi ? mask1+2 : mask1;
+       mask1 = j==sheet->range.col0 ? mask1+4 : mask1;
+       mask1 = j==sheet->range.coli ? mask1+8 : mask1;
+
+       mask2 = i==new_range.row0 ? 1 : 0;
+       mask2 = i==new_range.rowi ? mask2+2 : mask2;
+       mask2 = j==new_range.col0 ? mask2+4 : mask2;
+       mask2 = j==new_range.coli ? mask2+8 : mask2;     
+
+       if(mask1 != mask2){
+         x=COLUMN_LEFT_XPIXEL(sheet,j);
+         y=ROW_TOP_YPIXEL(sheet, i);  
+         width=COLUMN_LEFT_XPIXEL(sheet, j)-x+
+          xxx_column_width(sheet, j);
+         height=ROW_TOP_YPIXEL(sheet, i)-y+yyy_row_height(sheet, i);
+
+         if(i==sheet->range.row0){
+            y=y-3;
+            height=height+3;
+         }
+         if(i==sheet->range.rowi) height=height+3;
+         if(j==sheet->range.col0){
+            x=x-3;
+            width=width+3;
+         }
+         if(j==sheet->range.coli) width=width+3;
+
+         gdk_draw_pixmap(sheet->sheet_window,
+                  GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                  sheet->pixmap,
+                  x+1,
+                  y+1,
+                  x+1,
+                  y+1,
+                  width,
+                  height);           
+
+         if(i != sheet->active_cell.row || j != sheet->active_cell.col){
+           x=COLUMN_LEFT_XPIXEL(sheet,j);
+           y=ROW_TOP_YPIXEL(sheet, i);  
+           width=COLUMN_LEFT_XPIXEL(sheet, j)-x+
+            xxx_column_width(sheet, j);
+
+           height=ROW_TOP_YPIXEL(sheet, i)-y+yyy_row_height(sheet, i);
+
+           if(i==new_range.row0){
+               y=y+2;
+               height=height-2;
+            }
+            if(i==new_range.rowi) height=height-3;
+            if(j==new_range.col0){
+               x=x+2;
+               width=width-2;
+            }
+            if(j==new_range.coli) width=width-3;
+
+            gdk_draw_rectangle (sheet->sheet_window,
+                          sheet->xor_gc,
+                          TRUE,
+                          x+1,y+1,
+                          width,height);
+          }
+       }
+    }
+   }
+  }
+
+  for(i=range->row0; i<=range->rowi; i++){
+   for(j=range->col0; j<=range->coli; j++){     
+
+    state=gtk_sheet_cell_get_state(sheet, i, j);
+    selected=(i<=new_range.rowi && i>=new_range.row0 && 
+        j<=new_range.coli && j>=new_range.col0) ? TRUE : FALSE;
+
+    if(state==GTK_STATE_SELECTED && !selected &&   
+       xxx_column_is_visible(sheet, j) && yyy_row_is_visible(sheet, i)){
+
+      x=COLUMN_LEFT_XPIXEL(sheet,j);
+      y=ROW_TOP_YPIXEL(sheet, i);  
+      width=COLUMN_LEFT_XPIXEL(sheet, j)-x+ xxx_column_width(sheet, j);
+      height=ROW_TOP_YPIXEL(sheet, i)-y+yyy_row_height(sheet, i);
+
+      if(i==sheet->range.row0){
+            y=y-3;
+            height=height+3;
+      }
+      if(i==sheet->range.rowi) height=height+3;
+      if(j==sheet->range.col0){
+            x=x-3;
+            width=width+3;
+      }
+      if(j==sheet->range.coli) width=width+3;
+
+      gdk_draw_pixmap(sheet->sheet_window,
+                  GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                  sheet->pixmap,
+                  x+1,
+                  y+1,
+                  x+1,
+                  y+1,
+                  width,
+                  height);           
+    }
+   }
+  }
+
+  for(i=range->row0; i<=range->rowi; i++){
+   for(j=range->col0; j<=range->coli; j++){     
+
+    state=gtk_sheet_cell_get_state(sheet, i, j);
+    selected=(i<=new_range.rowi && i>=new_range.row0 && 
+        j<=new_range.coli && j>=new_range.col0) ? TRUE : FALSE;
+
+    if(state!=GTK_STATE_SELECTED && selected &&
+       xxx_column_is_visible(sheet, j) && yyy_row_is_visible(sheet, i) &&
+       (i != sheet->active_cell.row || j != sheet->active_cell.col)){
+
+      x=COLUMN_LEFT_XPIXEL(sheet,j);
+      y=ROW_TOP_YPIXEL(sheet, i);  
+      width=COLUMN_LEFT_XPIXEL(sheet, j)-x+ xxx_column_width(sheet, j);
+      height=ROW_TOP_YPIXEL(sheet, i)-y+yyy_row_height(sheet, i);
+
+      if(i==new_range.row0){
+            y=y+2;
+            height=height-2;
+       }
+       if(i==new_range.rowi) height=height-3;
+       if(j==new_range.col0){
+            x=x+2;
+            width=width-2;
+       }
+       if(j==new_range.coli) width=width-3;
+
+       gdk_draw_rectangle (sheet->sheet_window,
+                          sheet->xor_gc,
+                          TRUE,
+                          x+1,y+1,
+                          width,height);
+
+    }   
+
+   }
+  }
+
+  for(i=aux_range.row0; i<=aux_range.rowi; i++){
+   for(j=aux_range.col0; j<=aux_range.coli; j++){     
+
+    if(xxx_column_is_visible(sheet, j) && yyy_row_is_visible(sheet, i)){
+
+       state=gtk_sheet_cell_get_state(sheet, i, j);
+
+       mask1 = i==sheet->range.row0 ? 1 : 0;
+       mask1 = i==sheet->range.rowi ? mask1+2 : mask1;
+       mask1 = j==sheet->range.col0 ? mask1+4 : mask1;
+       mask1 = j==sheet->range.coli ? mask1+8 : mask1;
+
+       mask2 = i==new_range.row0 ? 1 : 0;
+       mask2 = i==new_range.rowi ? mask2+2 : mask2;
+       mask2 = j==new_range.col0 ? mask2+4 : mask2;
+       mask2 = j==new_range.coli ? mask2+8 : mask2;    
+       if(mask2!=mask1 || (mask2==mask1 && state!=GTK_STATE_SELECTED)){
+         x=COLUMN_LEFT_XPIXEL(sheet,j);
+         y=ROW_TOP_YPIXEL(sheet, i);  
+         width=xxx_column_width(sheet, j);
+         height=yyy_row_height(sheet, i);
+         if(mask2 & 1)
+               gdk_draw_rectangle (sheet->sheet_window,
+                                  sheet->xor_gc,
+                                  TRUE,
+                                  x+1,y-1,
+                                  width,3);
+
+           
+         if(mask2 & 2)
+               gdk_draw_rectangle (sheet->sheet_window,
+                                  sheet->xor_gc,
+                                  TRUE,
+                                  x+1,y+height-1,
+                                  width,3);
+
+         if(mask2 & 4)
+               gdk_draw_rectangle (sheet->sheet_window,
+                                  sheet->xor_gc,
+                                  TRUE,
+                                  x-1,y+1,
+                                  3,height);
+
+
+         if(mask2 & 8)
+               gdk_draw_rectangle (sheet->sheet_window,
+                                  sheet->xor_gc,
+                                  TRUE,
+                                  x+width-1,y+1,
+                                  3,height);
+
+       
+
+       }         
+
+    } 
+
+   }
+  } 
+
+
+  *range=new_range;
+  gtk_sheet_draw_corners(sheet, new_range);
+
+}
+
+static void
+gtk_sheet_draw_border (GtkSheet *sheet, GtkSheetRange new_range)
+{
+  GtkWidget *widget;
+  GdkRectangle area;
+  gint i;
+  gint x,y,width,height;
+
+  widget = GTK_WIDGET(sheet);
+
+  x=COLUMN_LEFT_XPIXEL(sheet,new_range.col0);
+  y=ROW_TOP_YPIXEL(sheet,new_range.row0);
+  width=COLUMN_LEFT_XPIXEL(sheet,new_range.coli)-x+ 
+    xxx_column_width(sheet, new_range.coli);
+
+  height=ROW_TOP_YPIXEL(sheet,new_range.rowi)-y+
+             yyy_row_height(sheet, new_range.rowi);
+
+  area.x=COLUMN_LEFT_XPIXEL(sheet, MIN_VISIBLE_COLUMN(sheet));
+  area.y=ROW_TOP_YPIXEL(sheet, MIN_VISIBLE_ROW(sheet));
+  area.width=sheet->sheet_window_width;
+  area.height=sheet->sheet_window_height;
+
+  if(x<0) {
+      width=width+x;
+      x=0;
+  }
+  if(width>area.width) width=area.width+10;
+  if(y<0) {
+      height=height+y;
+      y=0;
+  }
+  if(height>area.height) height=area.height+10;
+
+  gdk_gc_set_clip_rectangle(sheet->xor_gc, &area);
+
+  for(i=-1; i<=1; ++i)
+     gdk_draw_rectangle (sheet->sheet_window,
+                        sheet->xor_gc,
+                         FALSE,
+                        x+i,y+i,
+                        width-2*i,height-2*i);
+
+  gdk_gc_set_clip_rectangle(sheet->xor_gc, NULL);
+  
+  gtk_sheet_draw_corners(sheet, new_range);
+
+}
+
+static void
+gtk_sheet_draw_corners(GtkSheet *sheet, GtkSheetRange range)
+{
+  gint x,y;
+  guint width = 1;
+
+  if(gtk_sheet_cell_isvisible(sheet, range.row0, range.col0)){
+       x=COLUMN_LEFT_XPIXEL(sheet,range.col0);
+       y=ROW_TOP_YPIXEL(sheet,range.row0);
+       gdk_draw_pixmap(sheet->sheet_window,
+                       GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                       sheet->pixmap,
+                       x-1,
+                       y-1,
+                       x-1,
+                       y-1,
+                       3,
+                       3);         
+       gdk_draw_rectangle (sheet->sheet_window,
+                          sheet->xor_gc,
+                           TRUE,
+                          x-1,y-1,
+                          3,3);
+  }
+
+  if(gtk_sheet_cell_isvisible(sheet, range.row0, range.coli) ||
+     sheet->state == GTK_SHEET_COLUMN_SELECTED){
+       x=COLUMN_LEFT_XPIXEL(sheet,range.coli)+
+         xxx_column_width(sheet, range.coli);
+       y=ROW_TOP_YPIXEL(sheet,range.row0);
+       width = 1;
+       if(sheet->state == GTK_SHEET_COLUMN_SELECTED)
+         {
+             y = ROW_TOP_YPIXEL(sheet, sheet->view.row0)+3;
+             width = 3;
+         }
+       gdk_draw_pixmap(sheet->sheet_window,
+                       GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                       sheet->pixmap,
+                       x-width,
+                       y-width,
+                       x-width,
+                       y-width,
+                       2*width+1,
+                       2*width+1);         
+       gdk_draw_rectangle (sheet->sheet_window,
+                          sheet->xor_gc,
+                           TRUE,
+                          x-width+width/2,y-width+width/2,
+                          2+width,2+width);
+  }
+
+  if(gtk_sheet_cell_isvisible(sheet, range.rowi, range.col0) ||
+     sheet->state == GTK_SHEET_ROW_SELECTED){
+       x=COLUMN_LEFT_XPIXEL(sheet,range.col0);
+       y=ROW_TOP_YPIXEL(sheet,range.rowi)+
+         yyy_row_height(sheet, range.rowi);
+       width = 1;
+       if(sheet->state == GTK_SHEET_ROW_SELECTED) 
+         {
+             x = COLUMN_LEFT_XPIXEL(sheet, sheet->view.col0)+3;
+             width = 3;
+         }
+       gdk_draw_pixmap(sheet->sheet_window,
+                       GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                       sheet->pixmap,
+                       x-width,
+                       y-width,
+                       x-width,
+                       y-width,
+                       2*width+1,
+                       2*width+1);         
+       gdk_draw_rectangle (sheet->sheet_window,
+                          sheet->xor_gc,
+                           TRUE,
+                          x-width+width/2,y-width+width/2,
+                          2+width,2+width);
+  }
+
+  if(gtk_sheet_cell_isvisible(sheet, range.rowi, range.coli)){
+       x=COLUMN_LEFT_XPIXEL(sheet,range.coli)+
+         xxx_column_width(sheet, range.coli);
+       y=ROW_TOP_YPIXEL(sheet,range.rowi)+
+         yyy_row_height(sheet, range.rowi);
+       width = 1;
+       if(sheet->state == GTK_SHEET_RANGE_SELECTED) width = 3;
+       if(sheet->state == GTK_SHEET_NORMAL) width = 3;
+       gdk_draw_pixmap(sheet->sheet_window,
+                       GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                       sheet->pixmap,
+                       x-width,
+                       y-width,
+                       x-width,
+                       y-width,
+                       2*width+1,
+                       2*width+1);         
+       gdk_draw_rectangle (sheet->sheet_window,
+                          sheet->xor_gc,
+                           TRUE,
+                          x-width+width/2,y-width+width/2,
+                          2+width,2+width);
+
+  }
+
+}
+
+
+static void
+gtk_sheet_real_select_range (GtkSheet * sheet,
+                            GtkSheetRange * range)
+{
+  gint state;
+
+  g_return_if_fail (sheet != NULL);
+
+  if(range==NULL) range=&sheet->range;
+
+  if(range->row0 < 0 || range->rowi < 0) return;
+  if(range->col0 < 0 || range->coli < 0) return;
+
+  state=sheet->state;
+
+#if 0
+  if(state==GTK_SHEET_COLUMN_SELECTED || state==GTK_SHEET_RANGE_SELECTED){
+   for(i=sheet->range.col0; i< range->col0; i++)
+    column_button_release(sheet, i);
+   for(i=range->coli+1; i<= sheet->range.coli; i++)
+    column_button_release(sheet, i);
+   for(i=range->col0; i<=range->coli; i++){
+    column_button_set(sheet, i);
+   }
+  }
+  if(state==GTK_SHEET_ROW_SELECTED || state==GTK_SHEET_RANGE_SELECTED){
+   for(i=sheet->range.row0; i< range->row0; i++)
+    row_button_release(sheet, i);
+   for(i=range->rowi+1; i<= sheet->range.rowi; i++)
+    row_button_release(sheet, i);
+   for(i=range->row0; i<=range->rowi; i++){
+    row_button_set(sheet, i);
+   }
+  }
+#endif
+
+  if(range->coli != sheet->range.coli || range->col0 != sheet->range.col0 ||
+     range->rowi != sheet->range.rowi || range->row0 != sheet->range.row0)
+         {
+
+           gtk_sheet_new_selection(sheet, range);
+
+          sheet->range.col0=range->col0;
+          sheet->range.coli=range->coli;
+          sheet->range.row0=range->row0;
+          sheet->range.rowi=range->rowi;
+
+        }
+  else
+         {
+          gtk_sheet_draw_backing_pixmap(sheet, sheet->range);
+           gtk_sheet_range_draw_selection(sheet, sheet->range);
+         }
+
+  gtk_signal_emit(GTK_OBJECT(sheet), sheet_signals[SELECT_RANGE], range);
+}
+
+void
+gtk_sheet_select_range(GtkSheet * sheet, const GtkSheetRange *range)
+{
+  g_return_if_fail (sheet != NULL);
+
+  if(range==NULL) range=&sheet->range;
+
+  if(range->row0 < 0 || range->rowi < 0) return;
+  if(range->col0 < 0 || range->coli < 0) return;
+
+  if(sheet->state != GTK_SHEET_NORMAL) 
+       gtk_sheet_real_unselect_range(sheet, NULL);
+  else
+  {
+     gboolean veto = TRUE;
+     veto = gtk_sheet_deactivate_cell(sheet);
+     if(!veto) return;
+  }
+
+  sheet->range.row0=range->row0;
+  sheet->range.rowi=range->rowi;
+  sheet->range.col0=range->col0;
+  sheet->range.coli=range->coli;
+  sheet->active_cell.row=range->row0;
+  sheet->active_cell.col=range->col0;
+  sheet->selection_cell.row=range->rowi;
+  sheet->selection_cell.col=range->coli;
+
+  sheet->state = GTK_SHEET_RANGE_SELECTED;
+  gtk_sheet_real_select_range(sheet, NULL);
+
+}
+
+void
+gtk_sheet_unselect_range (GtkSheet * sheet)
+{
+  gtk_sheet_real_unselect_range(sheet, NULL);
+  sheet->state = GTK_STATE_NORMAL;
+  gtk_sheet_activate_cell(sheet, sheet->active_cell.row, sheet->active_cell.col);
+}
+
+
+static void
+gtk_sheet_real_unselect_range (GtkSheet * sheet,
+                              const GtkSheetRange *range)
+{
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_WIDGET_REALIZED(GTK_WIDGET(sheet)));
+
+  if(range==NULL){
+     range=&sheet->range;
+  }
+
+  if(range->row0 < 0 || range->rowi < 0) return;
+  if(range->col0 < 0 || range->coli < 0) return;
+
+  if (gtk_sheet_range_isvisible (sheet, *range)){
+    gtk_sheet_draw_backing_pixmap(sheet, *range);
+  }
+
+#if 0
+  for(i=range->col0; i<=range->coli; i++){
+     column_button_release(sheet, i);
+  }
+
+  for(i=range->row0; i<=range->rowi; i++){
+     row_button_release(sheet, i);
+  }
+#endif
+
+  gtk_sheet_position_children(sheet);
+}
+
+
+static gint
+gtk_sheet_expose (GtkWidget * widget,
+                 GdkEventExpose * event)
+{
+  GtkSheet *sheet;
+  GtkSheetRange range;
+
+  g_return_val_if_fail (widget != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_SHEET (widget), FALSE);
+  g_return_val_if_fail (event != NULL, FALSE);
+
+  sheet = GTK_SHEET (widget);
+
+  if (GTK_WIDGET_DRAWABLE (widget))
+  {
+      range.row0=ROW_FROM_YPIXEL(sheet,event->area.y);
+      range.col0=COLUMN_FROM_XPIXEL(sheet,event->area.x);
+      range.rowi=ROW_FROM_YPIXEL(sheet,event->area.y+event->area.height);
+      range.coli=COLUMN_FROM_XPIXEL(sheet,event->area.x+event->area.width);
+
+      /* exposure events on the sheet */
+      if(event->window == sheet->row_title_window && sheet->row_titles_visible){
+         gint i;
+         for(i = MIN_VISIBLE_ROW(sheet); i <= MAX_VISIBLE_ROW(sheet); i++)
+           gtk_sheet_button_draw(sheet,i,-1);
+      }
+
+      if(event->window == sheet->column_title_window && sheet->column_titles_visible){
+         gint i;
+         for(i = MIN_VISIBLE_COLUMN(sheet); i <= MAX_VISIBLE_COLUMN(sheet); i++)
+           gtk_sheet_button_draw(sheet,-1,i);
+      }
+
+      if (event->window == sheet->sheet_window){
+        gtk_sheet_draw_backing_pixmap(sheet, range);
+              
+        if(sheet->state != GTK_SHEET_NORMAL){
+                if(gtk_sheet_range_isvisible(sheet, sheet->range))          
+                   gtk_sheet_draw_backing_pixmap(sheet, sheet->range);
+                if(GTK_SHEET_IN_RESIZE(sheet) || GTK_SHEET_IN_DRAG(sheet))
+                   gtk_sheet_draw_backing_pixmap(sheet, sheet->drag_range);
+
+                if(gtk_sheet_range_isvisible(sheet, sheet->range))          
+                   gtk_sheet_range_draw_selection(sheet, sheet->range);
+                if(GTK_SHEET_IN_RESIZE(sheet) || GTK_SHEET_IN_DRAG(sheet))
+                   draw_xor_rectangle(sheet, sheet->drag_range);
+        }
+
+        if((!GTK_SHEET_IN_XDRAG(sheet)) && (!GTK_SHEET_IN_YDRAG(sheet))){
+             if(sheet->state == GTK_SHEET_NORMAL){ 
+                 gtk_sheet_draw_active_cell(sheet);
+                 if(!GTK_SHEET_IN_SELECTION(sheet))
+                         gtk_widget_queue_draw(sheet->sheet_entry);
+             }
+        }
+
+
+      }
+
+  }
+
+  if(sheet->state != GTK_SHEET_NORMAL && GTK_SHEET_IN_SELECTION(sheet))
+     gtk_widget_grab_focus(GTK_WIDGET(sheet));
+
+  (* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
+
+  return FALSE;
+}
+
+
+static gint
+gtk_sheet_button_press (GtkWidget * widget,
+                       GdkEventButton * event)
+{
+  GtkSheet *sheet;
+  GdkModifierType mods;
+  gint x, y, row, column;
+  gboolean veto;
+
+  g_return_val_if_fail (widget != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_SHEET (widget), FALSE);
+  g_return_val_if_fail (event != NULL, FALSE);
+
+  sheet = GTK_SHEET (widget);
+
+  if ( event->type == GDK_2BUTTON_PRESS)
+    {
+      gtk_widget_get_pointer (widget, &x, &y);
+      gtk_sheet_get_pixel_info (sheet, x, y, &row, &column);
+
+      if (event->window == sheet->column_title_window ) 
+       {
+         gtk_signal_emit (GTK_OBJECT (sheet), 
+                          sheet_signals[DOUBLE_CLICK_COLUMN], column);
+       }
+      else if (event->window == sheet->row_title_window ) 
+       {
+         gtk_signal_emit (GTK_OBJECT (sheet), 
+                          sheet_signals[DOUBLE_CLICK_ROW], row);
+       }
+    }
+
+  
+/*
+  if(event->type != GDK_BUTTON_PRESS) return TRUE;
+*/
+  gdk_window_get_pointer(widget->window, NULL, NULL, &mods);
+
+  if(!(mods & GDK_BUTTON1_MASK)) return TRUE;
+
+
+  /* press on resize windows */
+  if (event->window == sheet->column_title_window &&
+      gtk_sheet_columns_resizable(sheet))
+      {
+       gtk_widget_get_pointer (widget, &sheet->x_drag, NULL);
+        if(POSSIBLE_XDRAG(sheet, sheet->x_drag, &sheet->drag_cell.col)){
+          guint req;
+          if (event->type == GDK_2BUTTON_PRESS){
+            gtk_sheet_autoresize_column (sheet, sheet->drag_cell.col);
+            GTK_SHEET_UNSET_FLAGS(sheet, GTK_SHEET_IN_XDRAG);
+            return TRUE;
+          }
+          gtk_sheet_column_size_request(sheet, sheet->drag_cell.col, &req);
+         GTK_SHEET_SET_FLAGS (sheet, GTK_SHEET_IN_XDRAG);
+         gdk_pointer_grab (sheet->column_title_window, FALSE,
+                           GDK_POINTER_MOTION_HINT_MASK |
+                           GDK_BUTTON1_MOTION_MASK |
+                           GDK_BUTTON_RELEASE_MASK,
+                           NULL, NULL, event->time);
+
+         draw_xor_vline (sheet);
+         return TRUE;
+        }
+      }
+
+  if (event->window == sheet->row_title_window && gtk_sheet_rows_resizable(sheet))
+      {
+       gtk_widget_get_pointer (widget, NULL, &sheet->y_drag);
+
+        if(POSSIBLE_YDRAG(sheet, sheet->y_drag, &sheet->drag_cell.row)){
+          guint req;
+          gtk_sheet_row_size_request(sheet, sheet->drag_cell.row, &req);
+         GTK_SHEET_SET_FLAGS (sheet, GTK_SHEET_IN_YDRAG);
+         gdk_pointer_grab (sheet->row_title_window, FALSE,
+                           GDK_POINTER_MOTION_HINT_MASK |
+                           GDK_BUTTON1_MOTION_MASK |
+                           GDK_BUTTON_RELEASE_MASK,
+                           NULL, NULL, event->time);
+
+         draw_xor_hline (sheet);
+         return TRUE;
+        }
+      }
+
+  /* the sheet itself does not handle other than single click events */
+  if(event->type != GDK_BUTTON_PRESS) return FALSE;
+
+  /* selections on the sheet */
+    if(event->window == sheet->sheet_window){
+     gtk_widget_get_pointer (widget, &x, &y);
+     gtk_sheet_get_pixel_info (sheet, x, y, &row, &column);
+     gdk_pointer_grab (sheet->sheet_window, FALSE,
+                      GDK_POINTER_MOTION_HINT_MASK |
+                      GDK_BUTTON1_MOTION_MASK |
+                      GDK_BUTTON_RELEASE_MASK,
+                      NULL, NULL, event->time);
+     gtk_grab_add(GTK_WIDGET(sheet));
+     sheet->timer=gtk_timeout_add(TIMEOUT_SCROLL, gtk_sheet_scroll, sheet); 
+     gtk_widget_grab_focus(GTK_WIDGET(sheet));
+
+     if(sheet->selection_mode != GTK_SELECTION_SINGLE &&
+        sheet->cursor_drag->type==GDK_SIZING &&
+        !GTK_SHEET_IN_SELECTION(sheet) && !GTK_SHEET_IN_RESIZE(sheet)){
+        if(sheet->state==GTK_STATE_NORMAL) {
+          row=sheet->active_cell.row;
+          column=sheet->active_cell.col;
+          if(!gtk_sheet_deactivate_cell(sheet)) return FALSE;
+          sheet->active_cell.row=row;
+          sheet->active_cell.col=column;
+          sheet->drag_range=sheet->range;
+          sheet->state=GTK_SHEET_RANGE_SELECTED;
+          gtk_sheet_select_range(sheet, &sheet->drag_range);
+        }
+        sheet->x_drag=x;
+        sheet->y_drag=y;
+        if(row > sheet->range.rowi) row--;
+        if(column > sheet->range.coli) column--;
+        sheet->drag_cell.row = row;
+        sheet->drag_cell.col = column;
+        sheet->drag_range=sheet->range;
+        draw_xor_rectangle(sheet, sheet->drag_range);
+        GTK_SHEET_SET_FLAGS(sheet, GTK_SHEET_IN_RESIZE);
+     }
+     else if(sheet->cursor_drag->type==GDK_TOP_LEFT_ARROW &&
+            !GTK_SHEET_IN_SELECTION(sheet) && !GTK_SHEET_IN_DRAG(sheet)) {
+            if(sheet->state==GTK_STATE_NORMAL) {
+              row=sheet->active_cell.row;
+              column=sheet->active_cell.col;
+              if(!gtk_sheet_deactivate_cell(sheet)) return FALSE;
+              sheet->active_cell.row=row;
+              sheet->active_cell.col=column;
+              sheet->drag_range=sheet->range;
+              sheet->state=GTK_SHEET_RANGE_SELECTED;
+              gtk_sheet_select_range(sheet, &sheet->drag_range);
+            }
+            sheet->x_drag=x;
+            sheet->y_drag=y;
+            if(row < sheet->range.row0) row++;
+            if(row > sheet->range.rowi) row--;
+            if(column < sheet->range.col0) column++;
+            if(column > sheet->range.coli) column--;
+            sheet->drag_cell.row=row;
+            sheet->drag_cell.col=column;
+            sheet->drag_range=sheet->range;
+            draw_xor_rectangle(sheet, sheet->drag_range);
+            GTK_SHEET_SET_FLAGS(sheet, GTK_SHEET_IN_DRAG);
+          }
+          else 
+          {
+           gtk_sheet_click_cell(sheet, row, column, &veto);
+           if(veto) GTK_SHEET_SET_FLAGS(sheet, GTK_SHEET_IN_SELECTION);
+          }
+
+    }
+
+    if(event->window == sheet->column_title_window){
+     gtk_widget_get_pointer (widget, &x, &y);
+     column = COLUMN_FROM_XPIXEL(sheet, x);
+     if(xxx_column_is_sensitive(sheet, column)){
+       gtk_sheet_click_cell(sheet, -1, column, &veto);
+       gtk_grab_add(GTK_WIDGET(sheet));
+       sheet->timer=gtk_timeout_add(TIMEOUT_SCROLL, gtk_sheet_scroll, sheet); 
+       gtk_widget_grab_focus(GTK_WIDGET(sheet));
+       GTK_SHEET_SET_FLAGS(sheet, GTK_SHEET_IN_SELECTION);
+     }
+    }
+
+    if(event->window == sheet->row_title_window){
+     gtk_widget_get_pointer (widget, &x, &y);
+     row = ROW_FROM_YPIXEL(sheet, y);
+     if(yyy_row_is_sensitive(sheet, row)){
+       gtk_sheet_click_cell(sheet, row, -1, &veto);
+       gtk_grab_add(GTK_WIDGET(sheet));
+       sheet->timer=gtk_timeout_add(TIMEOUT_SCROLL, gtk_sheet_scroll, sheet); 
+       gtk_widget_grab_focus(GTK_WIDGET(sheet));
+       GTK_SHEET_SET_FLAGS(sheet, GTK_SHEET_IN_SELECTION);
+     }
+    }
+
+    return TRUE;
+}
+
+static gint
+gtk_sheet_scroll(gpointer data)
+{
+ GtkSheet *sheet;
+ gint x,y,row,column;
+ gint move;
+  
+ sheet=GTK_SHEET(data);
+
+ GDK_THREADS_ENTER();
+
+ gtk_widget_get_pointer (GTK_WIDGET(sheet), &x, &y);
+ gtk_sheet_get_pixel_info (sheet, x, y, &row, &column);
+
+ move=TRUE;
+
+ if(GTK_SHEET_IN_SELECTION(sheet))
+      gtk_sheet_extend_selection(sheet, row, column);
+
+ if(GTK_SHEET_IN_DRAG(sheet) || GTK_SHEET_IN_RESIZE(sheet)){
+       move=gtk_sheet_move_query(sheet, row, column);
+       if(move) draw_xor_rectangle(sheet, sheet->drag_range);      
+ }       
+
+ GDK_THREADS_LEAVE();
+
+ return TRUE;
+      
+}
+
+static void
+gtk_sheet_click_cell(GtkSheet *sheet, gint row, gint column, gboolean *veto)
+{
+      *veto = TRUE;
+
+      if(row >= yyy_row_count(sheet) || column >= xxx_column_count(sheet)){
+          *veto = FALSE;
+          return;
+      }
+
+      if(column >= 0 && row >= 0)
+       if(! xxx_column_is_visible(sheet, column) || !yyy_row_is_visible(sheet, row)) 
+         {
+           *veto = FALSE;
+           return;
+         }
+
+      _gtkextra_signal_emit(GTK_OBJECT(sheet), sheet_signals[TRAVERSE],
+                            sheet->active_cell.row, sheet->active_cell.col, 
+                            &row, &column, veto);
+
+      if(!*veto){
+           if(sheet->state == GTK_STATE_NORMAL) return;
+
+           row = sheet->active_cell.row;
+           column = sheet->active_cell.col;
+
+           gtk_sheet_activate_cell(sheet, row, column);
+           return;
+      }
+
+      if(row == -1 && column >= 0){
+          if(gtk_sheet_autoscroll(sheet))
+            gtk_sheet_move_query(sheet, row, column);
+         gtk_sheet_select_column(sheet, column);
+          return;
+      }
+      if(column == -1 && row >= 0){
+          if(gtk_sheet_autoscroll(sheet))
+            gtk_sheet_move_query(sheet, row, column);
+         gtk_sheet_select_row(sheet, row);
+          return;
+      }
+
+      if(row==-1 && column ==-1){
+          sheet->range.row0=0;
+          sheet->range.col0=0;
+          sheet->range.rowi = yyy_row_count(sheet) - 1;
+          sheet->range.coli = xxx_column_count(sheet) - 1;
+         sheet->active_cell.row=0;
+         sheet->active_cell.col=0;
+         gtk_sheet_select_range(sheet, NULL);
+         return;
+      }
+
+      if(row!=-1 && column !=-1){
+          if(sheet->state != GTK_SHEET_NORMAL){
+            sheet->state = GTK_SHEET_NORMAL;
+            gtk_sheet_real_unselect_range(sheet, NULL);
+          }
+          else
+          {
+            if(!gtk_sheet_deactivate_cell(sheet)){
+              *veto = FALSE;
+              return;
+            }
+          }
+
+          if(gtk_sheet_autoscroll(sheet))
+            gtk_sheet_move_query(sheet, row, column);
+          sheet->active_cell.row=row;
+          sheet->active_cell.col=column;
+         sheet->selection_cell.row=row;
+          sheet->selection_cell.col=column;
+          sheet->range.row0=row;
+          sheet->range.col0=column;
+          sheet->range.rowi=row;
+          sheet->range.coli=column;
+         sheet->state=GTK_SHEET_NORMAL;
+          GTK_SHEET_SET_FLAGS(sheet, GTK_SHEET_IN_SELECTION);
+         gtk_sheet_draw_active_cell(sheet);
+         return;
+      }
+
+      g_assert_not_reached();
+      gtk_sheet_activate_cell(sheet, sheet->active_cell.row,
+                                     sheet->active_cell.col);
+}
+
+static gint
+gtk_sheet_button_release (GtkWidget * widget,
+                       GdkEventButton * event)
+{
+  GtkSheet *sheet;
+  gint x,y;
+  sheet=GTK_SHEET(widget);
+
+  /* release on resize windows */
+  if (GTK_SHEET_IN_XDRAG (sheet)){
+         GTK_SHEET_UNSET_FLAGS (sheet, GTK_SHEET_IN_XDRAG);
+          GTK_SHEET_UNSET_FLAGS (sheet, GTK_SHEET_IN_SELECTION);
+         gtk_widget_get_pointer (widget, &x, NULL);
+         gdk_pointer_ungrab (event->time);
+         draw_xor_vline (sheet);
+         
+         gtk_sheet_set_column_width (sheet, sheet->drag_cell.col, new_column_width (sheet, sheet->drag_cell.col, &x));
+          sheet->old_hadjustment = -1.;
+          gtk_signal_emit_by_name (GTK_OBJECT (sheet->hadjustment), "value_changed");
+         return TRUE;
+  }
+
+  if (GTK_SHEET_IN_YDRAG (sheet)){
+         GTK_SHEET_UNSET_FLAGS (sheet, GTK_SHEET_IN_YDRAG);
+          GTK_SHEET_UNSET_FLAGS (sheet, GTK_SHEET_IN_SELECTION);
+         gtk_widget_get_pointer (widget, NULL, &y);
+         gdk_pointer_ungrab (event->time);
+         draw_xor_hline (sheet);
+         
+         gtk_sheet_set_row_height (sheet, sheet->drag_cell.row, new_row_height (sheet, sheet->drag_cell.row, &y));
+          sheet->old_vadjustment = -1.;
+          gtk_signal_emit_by_name (GTK_OBJECT (sheet->vadjustment), "value_changed");
+         return TRUE;
+  }
+
+  
+  if (GTK_SHEET_IN_DRAG(sheet)){
+      GtkSheetRange old_range;
+      draw_xor_rectangle(sheet, sheet->drag_range);
+      GTK_SHEET_UNSET_FLAGS(sheet, GTK_SHEET_IN_DRAG);
+      gdk_pointer_ungrab (event->time);
+
+      gtk_sheet_real_unselect_range(sheet, NULL);
+      
+      sheet->active_cell.row = sheet->active_cell.row +
+                               (sheet->drag_range.row0 - sheet->range.row0);
+      sheet->active_cell.col = sheet->active_cell.col +
+                               (sheet->drag_range.col0 - sheet->range.col0);
+      sheet->selection_cell.row = sheet->selection_cell.row +
+                                  (sheet->drag_range.row0 - sheet->range.row0);
+      sheet->selection_cell.col = sheet->selection_cell.col +
+                                  (sheet->drag_range.col0 - sheet->range.col0);
+      old_range=sheet->range;
+      sheet->range=sheet->drag_range;
+      sheet->drag_range=old_range;
+      gtk_signal_emit(GTK_OBJECT(sheet),sheet_signals[MOVE_RANGE],
+                      &sheet->drag_range, &sheet->range);
+      gtk_sheet_select_range(sheet, &sheet->range);
+  }
+
+  if (GTK_SHEET_IN_RESIZE(sheet)){
+      GtkSheetRange old_range;
+      draw_xor_rectangle(sheet, sheet->drag_range);
+      GTK_SHEET_UNSET_FLAGS(sheet, GTK_SHEET_IN_RESIZE);
+      gdk_pointer_ungrab (event->time);
+
+      gtk_sheet_real_unselect_range(sheet, NULL);
+      
+      sheet->active_cell.row = sheet->active_cell.row +
+                               (sheet->drag_range.row0 - sheet->range.row0);
+      sheet->active_cell.col = sheet->active_cell.col +
+                               (sheet->drag_range.col0 - sheet->range.col0);
+      if(sheet->drag_range.row0 < sheet->range.row0)
+                     sheet->selection_cell.row = sheet->drag_range.row0;
+      if(sheet->drag_range.rowi >= sheet->range.rowi)
+                     sheet->selection_cell.row = sheet->drag_range.rowi;
+      if(sheet->drag_range.col0 < sheet->range.col0)
+                     sheet->selection_cell.col = sheet->drag_range.col0;
+      if(sheet->drag_range.coli >= sheet->range.coli)
+                     sheet->selection_cell.col = sheet->drag_range.coli;
+      old_range = sheet->range;
+      sheet->range = sheet->drag_range;
+      sheet->drag_range = old_range;
+
+      if(sheet->state==GTK_STATE_NORMAL) sheet->state=GTK_SHEET_RANGE_SELECTED;
+      gtk_signal_emit(GTK_OBJECT(sheet),sheet_signals[RESIZE_RANGE],
+                      &sheet->drag_range, &sheet->range);
+      gtk_sheet_select_range(sheet, &sheet->range);
+  }
+
+  if(sheet->state == GTK_SHEET_NORMAL && GTK_SHEET_IN_SELECTION(sheet)){
+      GTK_SHEET_UNSET_FLAGS(sheet, GTK_SHEET_IN_SELECTION);
+      gdk_pointer_ungrab (event->time);
+      gtk_sheet_activate_cell(sheet, sheet->active_cell.row, 
+                                     sheet->active_cell.col);
+  }
+
+  if(GTK_SHEET_IN_SELECTION)
+         gdk_pointer_ungrab (event->time);
+  if(sheet->timer)
+         gtk_timeout_remove(sheet->timer);
+  gtk_grab_remove(GTK_WIDGET(sheet));
+
+  GTK_SHEET_UNSET_FLAGS(sheet, GTK_SHEET_IN_SELECTION);
+
+  return TRUE;
+}
+
+static gint
+gtk_sheet_motion (GtkWidget * widget,
+                 GdkEventMotion * event)
+{
+  GtkSheet *sheet;
+  GdkModifierType mods;
+  GdkCursorType new_cursor;
+  gint x, y, row, column;
+
+  g_return_val_if_fail (widget != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_SHEET (widget), FALSE);
+  g_return_val_if_fail (event != NULL, FALSE);
+
+
+  sheet = GTK_SHEET (widget);
+
+  /* selections on the sheet */
+  x = event->x;
+  y = event->y;
+
+  if(event->window == sheet->column_title_window && gtk_sheet_columns_resizable(sheet)){
+    gtk_widget_get_pointer(widget, &x, &y);
+    if(!GTK_SHEET_IN_SELECTION(sheet) && POSSIBLE_XDRAG(sheet, x, &column)){
+      new_cursor=GDK_SB_H_DOUBLE_ARROW;
+      if(new_cursor != sheet->cursor_drag->type){
+        gdk_cursor_destroy(sheet->cursor_drag);
+        sheet->cursor_drag=gdk_cursor_new(GDK_SB_H_DOUBLE_ARROW);
+        gdk_window_set_cursor(sheet->column_title_window,sheet->cursor_drag);
+      }
+    }else{
+      new_cursor=GDK_TOP_LEFT_ARROW;
+      if(!GTK_SHEET_IN_XDRAG(sheet) && new_cursor != sheet->cursor_drag->type){
+        gdk_cursor_destroy(sheet->cursor_drag);
+        sheet->cursor_drag=gdk_cursor_new(GDK_TOP_LEFT_ARROW);
+        gdk_window_set_cursor(sheet->column_title_window,sheet->cursor_drag);
+      }
+    }
+  }      
+
+  if(event->window == sheet->row_title_window && gtk_sheet_rows_resizable(sheet)){
+    gtk_widget_get_pointer(widget, &x, &y);
+    if(!GTK_SHEET_IN_SELECTION(sheet) && POSSIBLE_YDRAG(sheet,y, &column)){
+      new_cursor=GDK_SB_V_DOUBLE_ARROW;
+      if(new_cursor != sheet->cursor_drag->type){
+        gdk_cursor_destroy(sheet->cursor_drag);
+        sheet->cursor_drag=gdk_cursor_new(GDK_SB_V_DOUBLE_ARROW);
+        gdk_window_set_cursor(sheet->row_title_window,sheet->cursor_drag);
+      }
+    }else{
+      new_cursor=GDK_TOP_LEFT_ARROW;
+      if(!GTK_SHEET_IN_YDRAG(sheet) && new_cursor != sheet->cursor_drag->type){
+        gdk_cursor_destroy(sheet->cursor_drag);
+        sheet->cursor_drag=gdk_cursor_new(GDK_TOP_LEFT_ARROW);
+        gdk_window_set_cursor(sheet->row_title_window,sheet->cursor_drag);
+      }
+    }
+  }      
+
+  new_cursor=GDK_PLUS;
+  if(!POSSIBLE_DRAG(sheet,x,y,&row,&column) && !GTK_SHEET_IN_DRAG(sheet) &&
+     !POSSIBLE_RESIZE(sheet,x,y,&row,&column) && !GTK_SHEET_IN_RESIZE(sheet) &&
+     event->window == sheet->sheet_window && 
+     new_cursor != sheet->cursor_drag->type){
+         gdk_cursor_destroy(sheet->cursor_drag);
+         sheet->cursor_drag=gdk_cursor_new(GDK_PLUS);
+         gdk_window_set_cursor(sheet->sheet_window,sheet->cursor_drag);
+  }
+
+  new_cursor=GDK_TOP_LEFT_ARROW;
+  if(!(POSSIBLE_RESIZE(sheet,x,y,&row,&column) || GTK_SHEET_IN_RESIZE(sheet)) &&
+     (POSSIBLE_DRAG(sheet, x,y,&row,&column) || GTK_SHEET_IN_DRAG(sheet)) && 
+     event->window == sheet->sheet_window && 
+     new_cursor != sheet->cursor_drag->type){
+         gdk_cursor_destroy(sheet->cursor_drag);
+         sheet->cursor_drag=gdk_cursor_new(GDK_TOP_LEFT_ARROW);
+         gdk_window_set_cursor(sheet->sheet_window,sheet->cursor_drag);
+  }
+
+  new_cursor=GDK_SIZING;
+  if(!GTK_SHEET_IN_DRAG(sheet) &&
+     (POSSIBLE_RESIZE(sheet,x,y,&row,&column) || GTK_SHEET_IN_RESIZE(sheet)) &&
+     event->window == sheet->sheet_window && 
+     new_cursor != sheet->cursor_drag->type){
+         gdk_cursor_destroy(sheet->cursor_drag);
+         sheet->cursor_drag=gdk_cursor_new(GDK_SIZING);
+         gdk_window_set_cursor(sheet->sheet_window,sheet->cursor_drag);
+  }
+
+  gdk_window_get_pointer (widget->window, &x, &y, &mods);
+  if(!(mods & GDK_BUTTON1_MASK)) return FALSE;
+
+  if (GTK_SHEET_IN_XDRAG (sheet)){
+       if (event->is_hint || event->window != widget->window)
+           gtk_widget_get_pointer (widget, &x, NULL);
+         else
+           x = event->x;
+
+         new_column_width (sheet, sheet->drag_cell.col, &x);
+         if (x != sheet->x_drag)
+           {
+             draw_xor_vline (sheet);
+             sheet->x_drag = x;
+             draw_xor_vline (sheet);
+           }
+          return TRUE;
+  }
+
+  if (GTK_SHEET_IN_YDRAG (sheet)){
+         if (event->is_hint || event->window != widget->window)
+           gtk_widget_get_pointer (widget, NULL, &y);
+         else
+           y = event->y;
+
+         new_row_height (sheet, sheet->drag_cell.row, &y);
+         if (y != sheet->y_drag)
+           {
+             draw_xor_hline (sheet);
+             sheet->y_drag = y;
+             draw_xor_hline (sheet);
+           }
+          return TRUE;
+  }
+
+  if (GTK_SHEET_IN_DRAG(sheet)){
+       GtkSheetRange aux;
+       column=COLUMN_FROM_XPIXEL(sheet,x)-sheet->drag_cell.col;
+       row=ROW_FROM_YPIXEL(sheet,y)-sheet->drag_cell.row;
+       if(sheet->state==GTK_SHEET_COLUMN_SELECTED) row=0;
+       if(sheet->state==GTK_SHEET_ROW_SELECTED) column=0;
+       sheet->x_drag=x;
+       sheet->y_drag=y;
+       aux=sheet->range;
+       if(aux.row0+row >= 0 && aux.rowi+row < yyy_row_count(sheet) &&
+          aux.col0+column >= 0 && aux.coli+column < xxx_column_count(sheet)){
+             aux=sheet->drag_range;
+             sheet->drag_range.row0=sheet->range.row0+row;
+             sheet->drag_range.col0=sheet->range.col0+column;
+             sheet->drag_range.rowi=sheet->range.rowi+row;
+             sheet->drag_range.coli=sheet->range.coli+column;
+             if(aux.row0 != sheet->drag_range.row0 ||
+                aux.col0 != sheet->drag_range.col0){
+                draw_xor_rectangle (sheet, aux);
+                draw_xor_rectangle (sheet, sheet->drag_range);
+             }
+       }
+       return TRUE;
+  }
+
+  if (GTK_SHEET_IN_RESIZE(sheet)){
+       GtkSheetRange aux;
+       gint v_h, current_col, current_row, col_threshold, row_threshold;
+       v_h=1;
+
+       if(abs(x-COLUMN_LEFT_XPIXEL(sheet,sheet->drag_cell.col)) >
+          abs(y-ROW_TOP_YPIXEL(sheet,sheet->drag_cell.row))) v_h=2;
+        
+       current_col = COLUMN_FROM_XPIXEL(sheet,x);
+       current_row = ROW_FROM_YPIXEL(sheet,y);
+       column = current_col-sheet->drag_cell.col;
+       row    = current_row-sheet->drag_cell.row;
+
+       /*use half of column width resp. row height as threshold to expand selection*/
+       col_threshold = COLUMN_LEFT_XPIXEL(sheet,current_col)+xxx_column_width (sheet,current_col)/2;
+       if (column > 0){
+         if (x < col_threshold)
+           column-=1;
+       }
+       else if (column < 0){
+         if (x > col_threshold) 
+          column+=1;
+       }
+       row_threshold = ROW_TOP_YPIXEL(sheet,current_row)+yyy_row_height (sheet, current_row)/2;
+       if (row > 0){
+         if(y < row_threshold)
+           row-=1;
+       }
+       else if (row < 0){
+         if(y > row_threshold)
+           row+=1;       
+       }
+
+       if(sheet->state==GTK_SHEET_COLUMN_SELECTED) row=0;
+       if(sheet->state==GTK_SHEET_ROW_SELECTED) column=0;
+       sheet->x_drag=x;
+       sheet->y_drag=y;
+       aux=sheet->range;
+
+       if(v_h==1) 
+           column=0;
+       else
+           row=0;
+
+       if(aux.row0+row >= 0 && aux.rowi+row < yyy_row_count(sheet) &&
+          aux.col0+column >= 0 && aux.coli+column < xxx_column_count(sheet)){
+
+             aux=sheet->drag_range;
+             sheet->drag_range=sheet->range;
+
+             if(row<0) sheet->drag_range.row0=sheet->range.row0+row;
+             if(row>0) sheet->drag_range.rowi=sheet->range.rowi+row;
+             if(column<0) sheet->drag_range.col0=sheet->range.col0+column;
+             if(column>0) sheet->drag_range.coli=sheet->range.coli+column;
+             
+             if(aux.row0 != sheet->drag_range.row0 ||
+                aux.rowi != sheet->drag_range.rowi ||
+                aux.col0 != sheet->drag_range.col0 ||
+                aux.coli != sheet->drag_range.coli){
+                     draw_xor_rectangle (sheet, aux);
+                     draw_xor_rectangle (sheet, sheet->drag_range);
+             }
+       }
+       return TRUE;
+  }
+
+  
+
+  gtk_sheet_get_pixel_info (sheet, x, y, &row, &column);
+
+  if(sheet->state==GTK_SHEET_NORMAL && row==sheet->active_cell.row &&
+     column==sheet->active_cell.col) return TRUE;
+
+  if(GTK_SHEET_IN_SELECTION(sheet) && mods&GDK_BUTTON1_MASK)
+                          gtk_sheet_extend_selection(sheet, row, column);
+
+  return TRUE;
+}
+
+static gint
+gtk_sheet_move_query(GtkSheet *sheet, gint row, gint column)
+{
+  gint row_move, column_move;
+  gfloat row_align, col_align;
+  guint height, width;
+  gint new_row = row;
+  gint new_col = column;
+
+  row_move=FALSE;
+  column_move=FALSE;
+  row_align=-1.;
+  col_align=-1.;
+
+  height = sheet->sheet_window_height;
+  width = sheet->sheet_window_width;
+
+  if(row>=MAX_VISIBLE_ROW(sheet) && sheet->state!=GTK_SHEET_COLUMN_SELECTED) {
+          row_align = 1.;
+         new_row = MIN(yyy_row_count(sheet), row + 1);
+          row_move = TRUE;
+          if(MAX_VISIBLE_ROW(sheet) == yyy_row_count(sheet) - 1 &&
+             ROW_TOP_YPIXEL(sheet, yyy_row_count(sheet)-1) + 
+             yyy_row_height(sheet, yyy_row_count(sheet)-1) < height){
+                 row_move = FALSE;
+                row_align = -1.;
+          }
+  }
+  if(row<MIN_VISIBLE_ROW(sheet) && sheet->state!=GTK_SHEET_COLUMN_SELECTED) {
+          row_align= 0.;
+          row_move = TRUE;
+  }
+  if(column>=MAX_VISIBLE_COLUMN(sheet) && sheet->state!=GTK_SHEET_ROW_SELECTED) {
+          col_align = 1.;
+          new_col = MIN(xxx_column_count(sheet) - 1, column + 1);
+          column_move = TRUE;
+          if(MAX_VISIBLE_COLUMN(sheet) == (xxx_column_count(sheet) - 1) &&
+             COLUMN_LEFT_XPIXEL(sheet, xxx_column_count(sheet) - 1) + 
+             xxx_column_width(sheet, xxx_column_count(sheet) - 1) < width)
+           {
+             column_move = FALSE;
+             col_align = -1.;
+           }
+  } 
+  if(column<MIN_VISIBLE_COLUMN(sheet) && sheet->state!=GTK_SHEET_ROW_SELECTED) {
+         col_align = 0.;
+          column_move = TRUE;
+  }
+
+  if(row_move || column_move){
+        gtk_sheet_moveto(sheet, new_row, new_col, row_align, col_align);
+  }
+
+  return(row_move || column_move);
+}
+
+static void
+gtk_sheet_extend_selection(GtkSheet *sheet, gint row, gint column)
+{
+   GtkSheetRange range;
+   gint state;
+   gint r,c;
+
+   if(row == sheet->selection_cell.row && column == sheet->selection_cell.col)
+        return;
+
+   if(sheet->selection_mode == GTK_SELECTION_SINGLE) return;
+
+   gtk_sheet_move_query(sheet, row, column);
+   gtk_widget_grab_focus(GTK_WIDGET(sheet));
+
+   if(GTK_SHEET_IN_DRAG(sheet)) return;
+
+   state=sheet->state;
+
+   switch(sheet->state){
+    case GTK_SHEET_ROW_SELECTED:
+        column = xxx_column_count(sheet) - 1;
+         break;
+    case GTK_SHEET_COLUMN_SELECTED:
+        row = yyy_row_count(sheet) - 1;
+         break; 
+    case GTK_SHEET_NORMAL:
+        sheet->state=GTK_SHEET_RANGE_SELECTED;
+         r=sheet->active_cell.row;
+         c=sheet->active_cell.col;
+         sheet->range.col0=c;
+         sheet->range.row0=r;
+         sheet->range.coli=c;
+         sheet->range.rowi=r;
+         gdk_draw_pixmap(sheet->sheet_window,
+                   GTK_WIDGET(sheet)->style->fg_gc[GTK_STATE_NORMAL],
+                   sheet->pixmap,
+                   COLUMN_LEFT_XPIXEL(sheet,c)-1,
+                   ROW_TOP_YPIXEL(sheet,r)-1,
+                   COLUMN_LEFT_XPIXEL(sheet,c)-1,
+                   ROW_TOP_YPIXEL(sheet,r)-1,
+                   xxx_column_width(sheet, c)+4,
+                   yyy_row_height(sheet, r)+4);   
+         gtk_sheet_range_draw_selection(sheet, sheet->range);
+    case GTK_SHEET_RANGE_SELECTED:
+         sheet->state=GTK_SHEET_RANGE_SELECTED;
+   }
+
+   sheet->selection_cell.row = row;
+   sheet->selection_cell.col = column;
+
+   range.col0=MIN(column,sheet->active_cell.col);
+   range.coli=MAX(column,sheet->active_cell.col);
+   range.row0=MIN(row,sheet->active_cell.row);
+   range.rowi=MAX(row,sheet->active_cell.row);
+
+   if(range.row0 != sheet->range.row0 || range.rowi != sheet->range.rowi ||
+      range.col0 != sheet->range.col0 || range.coli != sheet->range.coli ||
+      state==GTK_SHEET_NORMAL)
+               gtk_sheet_real_select_range(sheet, &range);
+
+}
+
+static gint
+gtk_sheet_entry_key_press(GtkWidget *widget,
+                         GdkEventKey *key)
+{
+  gboolean focus;
+  gtk_signal_emit_by_name(GTK_OBJECT(widget), "key_press_event", key, &focus);
+  return focus;
+}
+
+static gint
+gtk_sheet_key_press(GtkWidget *widget,
+                   GdkEventKey *key)
+{
+  GtkSheet *sheet;
+  gint row, col;
+  gint state;
+  gboolean extend_selection = FALSE;
+  gboolean force_move = FALSE;
+  gboolean in_selection = FALSE;
+  gboolean veto = TRUE;
+  gint scroll = 1;
+
+  sheet = GTK_SHEET(widget);
+
+  if(key->state & GDK_CONTROL_MASK || key->keyval==GDK_Control_L ||
+     key->keyval==GDK_Control_R) return FALSE;
+
+/*
+  {
+    if(key->keyval=='c' || key->keyval == 'C' && sheet->state != GTK_STATE_NORMAL)
+            gtk_sheet_clip_range(sheet, sheet->range);
+    if(key->keyval=='x' || key->keyval == 'X')
+            gtk_sheet_unclip_range(sheet);    
+    return FALSE;
+  }
+*/
+
+  extend_selection = (key->state & GDK_SHIFT_MASK) || key->keyval==GDK_Shift_L 
+|| key->keyval==GDK_Shift_R;
+
+  state=sheet->state;
+  in_selection = GTK_SHEET_IN_SELECTION(sheet);
+  GTK_SHEET_UNSET_FLAGS(sheet, GTK_SHEET_IN_SELECTION);
+
+  switch(key->keyval){
+    case GDK_Return: case GDK_KP_Enter:
+      if(sheet->state == GTK_SHEET_NORMAL && 
+         !GTK_SHEET_IN_SELECTION(sheet))
+         gtk_signal_emit_stop_by_name(GTK_OBJECT(gtk_sheet_get_entry(sheet)), 
+                                     "key_press_event");
+      row = sheet->active_cell.row;
+      col = sheet->active_cell.col;
+      if(sheet->state == GTK_SHEET_COLUMN_SELECTED)
+           row = MIN_VISIBLE_ROW(sheet)-1;
+      if(sheet->state == GTK_SHEET_ROW_SELECTED)
+           col = MIN_VISIBLE_COLUMN(sheet);
+      if(row < yyy_row_count(sheet) - 1){
+           row = row + scroll;
+           while(!yyy_row_is_visible(sheet, row) && row<yyy_row_count(sheet)-1) 
+            row++;
+      }
+      gtk_sheet_click_cell(sheet, row, col, &veto);
+      extend_selection = FALSE;
+      break;
+   case GDK_ISO_Left_Tab:
+      row = sheet->active_cell.row;
+      col = sheet->active_cell.col;
+      if(sheet->state == GTK_SHEET_ROW_SELECTED) 
+           col = MIN_VISIBLE_COLUMN(sheet)-1;
+      if(sheet->state == GTK_SHEET_COLUMN_SELECTED) 
+           row = MIN_VISIBLE_ROW(sheet);
+      if(col > 0){
+           col = col - scroll; 
+           while(! xxx_column_is_visible(sheet, col) && col>0) col--;
+          col=MAX(0, col);
+      }       
+      gtk_sheet_click_cell(sheet, row, col, &veto);
+      extend_selection = FALSE;
+      break;
+   case GDK_Tab:
+      row = sheet->active_cell.row;
+      col = sheet->active_cell.col;
+      if(sheet->state == GTK_SHEET_ROW_SELECTED) 
+           col = MIN_VISIBLE_COLUMN(sheet)-1;
+      if(sheet->state == GTK_SHEET_COLUMN_SELECTED) 
+           row = MIN_VISIBLE_ROW(sheet);
+      if(col < xxx_column_count(sheet) - 1)
+       {
+         col = col + scroll; 
+         while(! xxx_column_is_visible(sheet, col) && 
+               col < xxx_column_count(sheet) - 1) 
+           col++;
+       }       
+      gtk_sheet_click_cell(sheet, row, col, &veto);
+      extend_selection = FALSE;
+      break;
+/*    case GDK_BackSpace:
+      if(sheet->active_cell.row >= 0 && sheet->active_cell.col >= 0){
+       if(sheet->active_cell.col > 0){
+            col = sheet->active_cell.col - scroll; 
+           row = sheet->active_cell.row;
+            while(!sheet->column[col].is_visible && col > 0) col--;
+       }       
+      }
+      gtk_sheet_click_cell(sheet, row, col, &veto);
+      extend_selection = FALSE;
+      break;
+*/
+    case GDK_Page_Up:
+      scroll=MAX_VISIBLE_ROW(sheet)-MIN_VISIBLE_ROW(sheet)+1;
+    case GDK_Up:
+      if(extend_selection){
+        if(state==GTK_STATE_NORMAL){
+           row=sheet->active_cell.row;
+           col=sheet->active_cell.col;
+           gtk_sheet_click_cell(sheet, row, col, &veto);
+           if(!veto) break;
+        }
+        if(sheet->selection_cell.row > 0){
+          row = sheet->selection_cell.row - scroll;
+          while(!yyy_row_is_visible(sheet, row) && row > 0) row--;
+          row = MAX(0, row);
+          gtk_sheet_extend_selection(sheet, row, sheet->selection_cell.col);
+        }
+        return TRUE;
+      }
+      col = sheet->active_cell.col;
+      row = sheet->active_cell.row;
+      if(state==GTK_SHEET_COLUMN_SELECTED) 
+             row = MIN_VISIBLE_ROW(sheet);
+      if(state==GTK_SHEET_ROW_SELECTED) 
+             col = MIN_VISIBLE_COLUMN(sheet);
+      row = row - scroll;
+      while(!yyy_row_is_visible(sheet, row) && row > 0) row--;
+      row = MAX(0,row);
+      gtk_sheet_click_cell(sheet, row, col, &veto);
+      extend_selection = FALSE;
+      break;
+    case GDK_Page_Down:
+      scroll=MAX_VISIBLE_ROW(sheet)-MIN_VISIBLE_ROW(sheet)+1;
+    case GDK_Down:
+      if(extend_selection){
+        if(state==GTK_STATE_NORMAL){
+           row=sheet->active_cell.row;
+           col=sheet->active_cell.col;
+           gtk_sheet_click_cell(sheet, row, col, &veto);
+           if(!veto) break;
+        }
+        if(sheet->selection_cell.row < yyy_row_count(sheet)-1){
+          row = sheet->selection_cell.row + scroll;
+          while(!yyy_row_is_visible(sheet, row) && row < yyy_row_count(sheet)-1) row++;
+          row = MIN(yyy_row_count(sheet)-1, row);
+          gtk_sheet_extend_selection(sheet, row, sheet->selection_cell.col);
+        }
+        return TRUE;
+      }
+      col = sheet->active_cell.col;
+      row = sheet->active_cell.row;
+      if(sheet->active_cell.row < yyy_row_count(sheet)-1){
+          if(state==GTK_SHEET_COLUMN_SELECTED) 
+                row = MIN_VISIBLE_ROW(sheet)-1;
+          if(state==GTK_SHEET_ROW_SELECTED) 
+                col = MIN_VISIBLE_COLUMN(sheet);
+          row = row + scroll;
+           while(!yyy_row_is_visible(sheet, row) && row < yyy_row_count(sheet)-1) row++;
+           row = MIN(yyy_row_count(sheet)-1, row);
+      }
+      gtk_sheet_click_cell(sheet, row, col, &veto);
+      extend_selection = FALSE;
+      break;
+    case GDK_Right:
+      if(extend_selection){
+        if(state==GTK_STATE_NORMAL){
+           row=sheet->active_cell.row;
+           col=sheet->active_cell.col;
+           gtk_sheet_click_cell(sheet, row, col, &veto);
+           if(!veto) break;
+        }
+        if(sheet->selection_cell.col < xxx_column_count(sheet) - 1)
+         {
+           col = sheet->selection_cell.col + 1;
+           while(! xxx_column_is_visible(sheet, col) && col < xxx_column_count(sheet) - 1) 
+             col++;
+           gtk_sheet_extend_selection(sheet, sheet->selection_cell.row, col);
+         }
+        return TRUE;
+      }
+      col = sheet->active_cell.col;
+      row = sheet->active_cell.row;
+      if(sheet->active_cell.col < xxx_column_count(sheet) - 1){
+           col ++;
+          if(state==GTK_SHEET_ROW_SELECTED) 
+                col = MIN_VISIBLE_COLUMN(sheet)-1;
+          if(state==GTK_SHEET_COLUMN_SELECTED) 
+                row = MIN_VISIBLE_ROW(sheet);
+           while(! xxx_column_is_visible(sheet, col) && col < xxx_column_count(sheet) - 1) col++;
+           if(strlen(gtk_entry_get_text(GTK_ENTRY(gtk_sheet_get_entry(sheet)))) == 0 
+              || force_move) {
+               gtk_sheet_click_cell(sheet, row, col, &veto);
+           }
+           else
+              return FALSE;
+      }
+      extend_selection = FALSE;
+      break;
+    case GDK_Left:
+      if(extend_selection){
+        if(state==GTK_STATE_NORMAL){
+           row=sheet->active_cell.row;
+           col=sheet->active_cell.col;
+           gtk_sheet_click_cell(sheet, row, col, &veto);
+           if(!veto) break;
+        }
+        if(sheet->selection_cell.col > 0){
+          col = sheet->selection_cell.col - 1;
+          while(! xxx_column_is_visible(sheet, col) && col > 0) col--;          
+          gtk_sheet_extend_selection(sheet, sheet->selection_cell.row, col);
+        }
+       return TRUE;
+      }
+      col = sheet->active_cell.col - 1;
+      row = sheet->active_cell.row;
+      if(state==GTK_SHEET_ROW_SELECTED) 
+                col = MIN_VISIBLE_COLUMN(sheet)-1;
+      if(state==GTK_SHEET_COLUMN_SELECTED) 
+                row = MIN_VISIBLE_ROW(sheet);
+      while(! xxx_column_is_visible(sheet, col) && col > 0) col--;
+      col = MAX(0, col);
+
+      if(strlen(gtk_entry_get_text(GTK_ENTRY(gtk_sheet_get_entry(sheet)))) == 0
+         || force_move){
+                gtk_sheet_click_cell(sheet, row, col, &veto);
+      }
+      else
+         return FALSE;
+      extend_selection = FALSE;
+      break;
+    case GDK_Home:
+      row=0;
+      while(!yyy_row_is_visible(sheet, row) && row < yyy_row_count(sheet)-1) row++;
+      gtk_sheet_click_cell(sheet, row, sheet->active_cell.col, &veto);
+      extend_selection = FALSE;
+      break;
+    case GDK_End:
+      row=yyy_row_count(sheet)-1;
+      while(!yyy_row_is_visible(sheet, row) && row > 0) row--;
+      gtk_sheet_click_cell(sheet, row, sheet->active_cell.col, &veto);
+      extend_selection = FALSE;
+      break;
+    default:
+      if(in_selection) {
+        GTK_SHEET_SET_FLAGS(sheet, GTK_SHEET_IN_SELECTION);
+        if(extend_selection) return TRUE;
+      }
+      if(state == GTK_SHEET_ROW_SELECTED) 
+        sheet->active_cell.col=MIN_VISIBLE_COLUMN(sheet);
+      if(state == GTK_SHEET_COLUMN_SELECTED)
+        sheet->active_cell.row=MIN_VISIBLE_ROW(sheet);
+      return FALSE;
+  }
+
+  if(extend_selection) return TRUE;
+
+  gtk_sheet_activate_cell(sheet, sheet->active_cell.row,
+                                 sheet->active_cell.col);
+
+  return TRUE;
+} 
+
+static void
+gtk_sheet_size_request (GtkWidget * widget,
+                       GtkRequisition * requisition)
+{
+  GtkSheet *sheet;
+  GList *children;
+  GtkSheetChild *child;
+  GtkRequisition child_requisition;
+
+  g_return_if_fail (widget != NULL);
+  g_return_if_fail (GTK_IS_SHEET (widget));
+  g_return_if_fail (requisition != NULL);
+
+  sheet = GTK_SHEET (widget);
+
+  requisition->width = 3*DEFAULT_COLUMN_WIDTH;
+  requisition->height = 3*DEFAULT_ROW_HEIGHT(widget);
+
+  /* compute the size of the column title area */
+  if(sheet->column_titles_visible) 
+     requisition->height += sheet->column_title_area.height;
+
+  /* compute the size of the row title area */
+  if(sheet->row_titles_visible) 
+     requisition->width += sheet->row_title_area.width;
+
+  sheet->view.row0=ROW_FROM_YPIXEL(sheet, sheet->column_title_area.height+1);
+  sheet->view.rowi=ROW_FROM_YPIXEL(sheet, sheet->sheet_window_height-1);
+  sheet->view.col0=COLUMN_FROM_XPIXEL(sheet, sheet->row_title_area.width+1);
+  sheet->view.coli=COLUMN_FROM_XPIXEL(sheet, sheet->sheet_window_width);
+
+  if(!sheet->column_titles_visible) 
+     sheet->view.row0=ROW_FROM_YPIXEL(sheet, 1);
+
+  if(!sheet->row_titles_visible) 
+     sheet->view.col0=COLUMN_FROM_XPIXEL(sheet, 1);
+
+  children = sheet->children;
+  while (children)
+  {
+    child = children->data;
+    children = children->next;
+
+    gtk_widget_size_request(child->widget, &child_requisition);
+  }
+}
+
+static void
+gtk_sheet_size_allocate (GtkWidget * widget,
+                        GtkAllocation * allocation)
+{
+  GtkSheet *sheet;
+  GtkAllocation sheet_allocation;
+  gint border_width;
+
+  g_return_if_fail (widget != NULL);
+  g_return_if_fail (GTK_IS_SHEET (widget));
+  g_return_if_fail (allocation != NULL);
+
+  sheet = GTK_SHEET (widget);
+  widget->allocation = *allocation;
+  border_width = GTK_CONTAINER(widget)->border_width;
+
+  if (GTK_WIDGET_REALIZED (widget))
+    gdk_window_move_resize (widget->window,
+                           allocation->x + border_width,
+                           allocation->y + border_width,
+                            allocation->width - 2*border_width,
+                           allocation->height - 2*border_width);
+
+  /* use internal allocation structure for all the math
+   * because it's easier than always subtracting the container
+   * border width */
+  sheet->internal_allocation.x = 0;
+  sheet->internal_allocation.y = 0;
+  sheet->internal_allocation.width = allocation->width - 2*border_width;
+  sheet->internal_allocation.height = allocation->height - 2*border_width;
+       
+  sheet_allocation.x = 0;
+  sheet_allocation.y = 0;
+  sheet_allocation.width = allocation->width - 2*border_width;
+  sheet_allocation.height = allocation->height - 2*border_width;
+
+  sheet->sheet_window_width = sheet_allocation.width;
+  sheet->sheet_window_height = sheet_allocation.height;
+
+  if (GTK_WIDGET_REALIZED (widget))
+    gdk_window_move_resize (sheet->sheet_window,
+                           sheet_allocation.x,
+                           sheet_allocation.y,
+                           sheet_allocation.width,
+                           sheet_allocation.height);
+
+    /* position the window which holds the column title buttons */
+  sheet->column_title_area.x = 0;
+  sheet->column_title_area.y = 0;
+  if(sheet->row_titles_visible)
+       sheet->column_title_area.x = sheet->row_title_area.width;
+  sheet->column_title_area.width = sheet_allocation.width - 
+                                     sheet->column_title_area.x;
+  if(GTK_WIDGET_REALIZED(widget) && sheet->column_titles_visible)
+      gdk_window_move_resize (sheet->column_title_window,
+                             sheet->column_title_area.x,
+                             sheet->column_title_area.y,
+                             sheet->column_title_area.width,
+                             sheet->column_title_area.height);
+
+  sheet->sheet_window_width = sheet_allocation.width;
+  sheet->sheet_window_height = sheet_allocation.height;
+
+  /* column button allocation */
+  size_allocate_column_title_buttons (sheet);
+
+  /* position the window which holds the row title buttons */
+  sheet->row_title_area.x = 0;
+  sheet->row_title_area.y = 0;
+  if(sheet->column_titles_visible)
+       sheet->row_title_area.y = sheet->column_title_area.height;
+  sheet->row_title_area.height = sheet_allocation.height -
+                                   sheet->row_title_area.y;
+
+  if(GTK_WIDGET_REALIZED(widget) && sheet->row_titles_visible)
+      gdk_window_move_resize (sheet->row_title_window,
+                             sheet->row_title_area.x,
+                             sheet->row_title_area.y,
+                             sheet->row_title_area.width,
+                             sheet->row_title_area.height);
+
+
+  /* row button allocation */
+  size_allocate_row_title_buttons (sheet);
+
+  sheet->view.row0=ROW_FROM_YPIXEL(sheet, sheet->column_title_area.height+1);
+  sheet->view.rowi=ROW_FROM_YPIXEL(sheet, sheet->sheet_window_height-1);
+  sheet->view.col0=COLUMN_FROM_XPIXEL(sheet, sheet->row_title_area.width+1);
+  sheet->view.coli=COLUMN_FROM_XPIXEL(sheet, sheet->sheet_window_width);
+
+  if(!sheet->column_titles_visible)
+       sheet->view.row0=ROW_FROM_YPIXEL(sheet, 1);
+      
+  if(!sheet->row_titles_visible)
+       sheet->view.col0=COLUMN_FROM_XPIXEL(sheet, 1);
+
+  size_allocate_column_title_buttons(sheet);
+  size_allocate_row_title_buttons(sheet);
+
+  /* re-scale backing pixmap */
+  gtk_sheet_make_backing_pixmap(sheet, 0, 0); 
+  gtk_sheet_position_children(sheet);
+
+  /* set the scrollbars adjustments */
+  adjust_scrollbars (sheet);
+}
+
+static void
+size_allocate_column_title_buttons (GtkSheet * sheet)
+{
+  gint i;
+  gint x,width;
+
+  if (!sheet->column_titles_visible) return;
+  if (!GTK_WIDGET_REALIZED (sheet))
+    return;
+
+  width = sheet->sheet_window_width;
+  x = 0;
+
+  if(sheet->row_titles_visible)
+    {
+      width -= sheet->row_title_area.width;
+      x = sheet->row_title_area.width;
+    }
+
+  if(sheet->column_title_area.width != width || sheet->column_title_area.x != x)
+  {
+     sheet->column_title_area.width = width;
+     sheet->column_title_area.x = x;
+     gdk_window_move_resize (sheet->column_title_window,
+                            sheet->column_title_area.x,
+                            sheet->column_title_area.y,
+                            sheet->column_title_area.width,
+                            sheet->column_title_area.height);
+  }
+
+
+  if(MAX_VISIBLE_COLUMN(sheet) == xxx_column_count(sheet) - 1)
+     gdk_window_clear_area (sheet->column_title_window,
+                           0,0,
+                           sheet->column_title_area.width, 
+                            sheet->column_title_area.height);
+
+  if(!GTK_WIDGET_DRAWABLE(sheet)) return;
+
+  for (i = MIN_VISIBLE_COLUMN(sheet); i <= MAX_VISIBLE_COLUMN(sheet); i++)
+      gtk_sheet_button_draw(sheet,-1,i);
+}
+       
+static void
+size_allocate_row_title_buttons (GtkSheet * sheet)
+{
+  gint i;
+  gint y, height;
+
+  if (!sheet->row_titles_visible) return;
+  if (!GTK_WIDGET_REALIZED (sheet))
+    return;
+
+  height = sheet->sheet_window_height;
+  y = 0;
+
+  if(sheet->column_titles_visible)
+    {
+      height -= sheet->column_title_area.height;
+      y = sheet->column_title_area.height;
+    }
+    
+  if(sheet->row_title_area.height != height || sheet->row_title_area.y != y)
+    {
+      sheet->row_title_area.y = y;
+      sheet->row_title_area.height = height;
+      gdk_window_move_resize (sheet->row_title_window,
+                             sheet->row_title_area.x,
+                             sheet->row_title_area.y,
+                             sheet->row_title_area.width,
+                             sheet->row_title_area.height);
+    }
+  if(MAX_VISIBLE_ROW(sheet) == yyy_row_count(sheet)-1)
+    gdk_window_clear_area (sheet->row_title_window,
+                          0,0,
+                          sheet->row_title_area.width, 
+                           sheet->row_title_area.height);
+
+  if(!GTK_WIDGET_DRAWABLE(sheet)) return;
+
+  for(i = MIN_VISIBLE_ROW(sheet); i <= MAX_VISIBLE_ROW(sheet); i++)
+      gtk_sheet_button_draw(sheet,i,-1);
+}
+         
+
+static void
+gtk_sheet_size_allocate_entry(GtkSheet *sheet)
+{
+ GtkAllocation shentry_allocation;
+ GtkSheetCellAttr attributes;
+ GtkEntry *sheet_entry;
+ GtkStyle *style = NULL, *previous_style = NULL;
+ gint row, col;
+ gint size, max_size, text_size, column_width;
+ const gchar *text;
+
+ if(!GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))) return;
+ if(!GTK_WIDGET_MAPPED(GTK_WIDGET(sheet))) return;
+
+ sheet_entry = GTK_ENTRY(gtk_sheet_get_entry(sheet));
+
+ gtk_sheet_get_attributes(sheet, sheet->active_cell.row, sheet->active_cell.col, &attributes); 
+
+ if(GTK_WIDGET_REALIZED(sheet->sheet_entry)){
+
+  if(!GTK_WIDGET(sheet_entry)->style) 
+        gtk_widget_ensure_style(GTK_WIDGET(sheet_entry));
+
+  previous_style = GTK_WIDGET(sheet_entry)->style;
+
+  style = gtk_style_copy(previous_style);
+  style->bg[GTK_STATE_NORMAL] = attributes.background;
+  style->fg[GTK_STATE_NORMAL] = attributes.foreground;
+  style->text[GTK_STATE_NORMAL] = attributes.foreground;
+  style->bg[GTK_STATE_ACTIVE] = attributes.background;
+  style->fg[GTK_STATE_ACTIVE] = attributes.foreground;
+  style->text[GTK_STATE_ACTIVE] = attributes.foreground;
+
+  pango_font_description_free(style->font_desc);
+  style->font_desc = pango_font_description_copy(attributes.font_desc);
+
+  GTK_WIDGET(sheet_entry)->style = style;
+  gtk_widget_size_request(sheet->sheet_entry, NULL);
+  GTK_WIDGET(sheet_entry)->style = previous_style;
+
+  if(style != previous_style){
+    if(!GTK_IS_ITEM_ENTRY(sheet->sheet_entry)){
+      style->bg[GTK_STATE_NORMAL] = previous_style->bg[GTK_STATE_NORMAL];
+      style->fg[GTK_STATE_NORMAL] = previous_style->fg[GTK_STATE_NORMAL];
+      style->bg[GTK_STATE_ACTIVE] = previous_style->bg[GTK_STATE_ACTIVE];
+      style->fg[GTK_STATE_ACTIVE] = previous_style->fg[GTK_STATE_ACTIVE];
+    }
+    gtk_widget_set_style(GTK_WIDGET(sheet_entry), style);
+  }
+ }
+
+ if(GTK_IS_ITEM_ENTRY(sheet_entry))
+    max_size = GTK_ITEM_ENTRY(sheet_entry)->text_max_size;
+ else
+    max_size = 0;
+
+ text_size = 0;
+ text = gtk_entry_get_text(GTK_ENTRY(sheet_entry));
+ if(text && strlen(text) > 0){ 
+     text_size = STRING_WIDTH(GTK_WIDGET(sheet), attributes.font_desc, text);
+ }
+
+ column_width=xxx_column_width(sheet, sheet->active_cell.col);
+
+ size=MIN(text_size, max_size);
+ size=MAX(size,column_width-2*CELLOFFSET);
+
+ row=sheet->active_cell.row;
+ col=sheet->active_cell.col;
+
+ shentry_allocation.x = COLUMN_LEFT_XPIXEL(sheet,sheet->active_cell.col);
+ shentry_allocation.y = ROW_TOP_YPIXEL(sheet,sheet->active_cell.row);
+ shentry_allocation.width = column_width;
+ shentry_allocation.height = yyy_row_height(sheet, sheet->active_cell.row);
+
+ if(GTK_IS_ITEM_ENTRY(sheet->sheet_entry)){
+
+   shentry_allocation.height -= 2*CELLOFFSET;
+   shentry_allocation.y += CELLOFFSET;
+   if(gtk_sheet_clip_text(sheet))
+     shentry_allocation.width = column_width - 2*CELLOFFSET;
+   else
+     shentry_allocation.width = size;
+
+   switch(GTK_ITEM_ENTRY(sheet_entry)->justification){
+     case GTK_JUSTIFY_CENTER:
+       shentry_allocation.x += (column_width)/2 - size/2;
+       break;
+     case GTK_JUSTIFY_RIGHT:
+       shentry_allocation.x += column_width - size - CELLOFFSET;
+       break;
+     case GTK_JUSTIFY_LEFT:
+     case GTK_JUSTIFY_FILL:
+       shentry_allocation.x += CELLOFFSET;
+       break;
+    }
+
+ }
+
+ if(!GTK_IS_ITEM_ENTRY(sheet->sheet_entry)){
+   shentry_allocation.x += 2;
+   shentry_allocation.y += 2;
+   shentry_allocation.width -= MIN(shentry_allocation.width, 3);
+   shentry_allocation.height -= MIN(shentry_allocation.height, 3);
+ }
+
+ gtk_widget_size_allocate(sheet->sheet_entry, &shentry_allocation);
+
+ if(previous_style == style) gtk_style_unref(previous_style);
+}
+
+static void
+gtk_sheet_entry_set_max_size(GtkSheet *sheet)
+{
+ gint i;
+ gint size=0;
+ gint sizel=0, sizer=0;
+ gint row,col;
+ GtkJustification justification;
+
+ row=sheet->active_cell.row;
+ col=sheet->active_cell.col;
+
+ if(!GTK_IS_ITEM_ENTRY(sheet->sheet_entry) || gtk_sheet_clip_text(sheet)) return;
+
+ justification = GTK_ITEM_ENTRY(sheet->sheet_entry)->justification;
+
+ switch(justification){
+  case GTK_JUSTIFY_FILL:
+  case GTK_JUSTIFY_LEFT:
+    for(i=col+1; i<=MAX_VISIBLE_COLUMN(sheet); i++){
+     if(gtk_sheet_cell_get_text(sheet, row, i)) break;
+     size+=xxx_column_width(sheet, i);
+    }
+    size = MIN(size, sheet->sheet_window_width - COLUMN_LEFT_XPIXEL(sheet, col));
+    break;
+  case GTK_JUSTIFY_RIGHT:
+    for(i=col-1; i>=MIN_VISIBLE_COLUMN(sheet); i--){
+     if(gtk_sheet_cell_get_text(sheet, row, i)) break;
+     size+=xxx_column_width(sheet, i);
+    }
+    break;
+  case GTK_JUSTIFY_CENTER:
+    for(i=col+1; i<=MAX_VISIBLE_COLUMN(sheet); i++){
+/*     if(gtk_sheet_cell_get_text(sheet, row, i)) break;
+*/
+     sizer+=xxx_column_width(sheet, i);
+    }
+    for(i=col-1; i>=MIN_VISIBLE_COLUMN(sheet); i--){
+     if(gtk_sheet_cell_get_text(sheet, row, i)) break;
+     sizel+=xxx_column_width(sheet, i);
+    }
+    size=2*MIN(sizel, sizer);
+    break;
+ }
+
+ if(size!=0) size+=xxx_column_width(sheet, col);
+ GTK_ITEM_ENTRY(sheet->sheet_entry)->text_max_size=size;
+
+}
+
+static void
+create_sheet_entry(GtkSheet *sheet)
+{
+ GtkWidget *widget;
+ GtkWidget *parent;
+ GtkWidget *entry;
+ gint found_entry = FALSE;
+
+ widget = GTK_WIDGET(sheet);
+
+ if(sheet->sheet_entry){
+    /* avoids warnings */
+    gtk_widget_ref(sheet->sheet_entry);
+    gtk_widget_unparent(sheet->sheet_entry);
+    gtk_widget_destroy(sheet->sheet_entry);
+ }
+
+ if(sheet->entry_type){
+
+   if(!g_type_is_a (sheet->entry_type, GTK_TYPE_ENTRY)){
+
+     parent = GTK_WIDGET(gtk_type_new(sheet->entry_type));
+
+     sheet->sheet_entry = parent;
+
+     entry = gtk_sheet_get_entry (sheet);
+     if(GTK_IS_ENTRY(entry)) found_entry = TRUE;
+
+   } else {
+
+     parent = GTK_WIDGET(gtk_type_new(sheet->entry_type));
+     entry = parent;
+     found_entry = TRUE;
+
+   }             
+                                    
+   if(!found_entry){
+
+     g_warning ("Entry type must be GtkEntry subclass, using default");
+     entry = gtk_item_entry_new();
+     sheet->sheet_entry = entry;
+
+   } else {
+
+     sheet->sheet_entry = parent;
+
+   }
+
+
+ } else {
+
+     entry = gtk_item_entry_new();
+     sheet->sheet_entry = entry;
+
+ }
+ gtk_widget_size_request(sheet->sheet_entry, NULL);
+ if(GTK_WIDGET_REALIZED(sheet))
+   {
+      gtk_widget_set_parent_window (sheet->sheet_entry, sheet->sheet_window);
+      gtk_widget_set_parent(sheet->sheet_entry, GTK_WIDGET(sheet));
+      gtk_widget_realize(sheet->sheet_entry);
+   }
+
+ gtk_signal_connect_object(GTK_OBJECT(entry),"key_press_event",
+                           (GtkSignalFunc) gtk_sheet_entry_key_press,
+                           GTK_OBJECT(sheet)); 
+
+ gtk_widget_show (sheet->sheet_entry); 
+}
+
+
+/* Finds the last child widget that happens to be of type GtkEntry */
+static void
+find_entry(GtkWidget *w, gpointer user_data)
+{
+  GtkWidget **entry = user_data;
+  if ( GTK_IS_ENTRY(w))
+    {
+      *entry = w;
+    }
+}
+
+GtkWidget * 
+gtk_sheet_get_entry(GtkSheet *sheet)
+{
+ GtkWidget *parent;
+ GtkWidget *entry = NULL;
+ GtkTableChild *table_child;
+ GtkBoxChild *box_child;
+ GList *children = NULL;
+
+ g_return_val_if_fail (sheet != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_SHEET (sheet), NULL);
+ g_return_val_if_fail (sheet->sheet_entry != NULL, NULL);
+
+ if(GTK_IS_ENTRY(sheet->sheet_entry)) return (sheet->sheet_entry);
+
+ parent = GTK_WIDGET(sheet->sheet_entry);
+
+ if(GTK_IS_TABLE(parent)) children = GTK_TABLE(parent)->children;
+ if(GTK_IS_BOX(parent)) children = GTK_BOX(parent)->children;
+
+ if(GTK_IS_CONTAINER(parent)) 
+   {
+     gtk_container_forall(GTK_CONTAINER(parent), find_entry, &entry);
+     
+     if(GTK_IS_ENTRY(entry))  
+       return entry;
+   }
+
+ if(!children) return NULL;
+
+ while(children){
+      if(GTK_IS_TABLE(parent)) {
+                 table_child = children->data;
+                 entry = table_child->widget;
+      }
+      if(GTK_IS_BOX(parent)){
+                 box_child = children->data; 
+                 entry = box_child->widget;
+      }
+
+      if(GTK_IS_ENTRY(entry))  
+                                break;
+      children = children->next;                        
+ } 
+
+
+ if(!GTK_IS_ENTRY(entry))   return NULL;
+
+ return (entry);
+
+}
+
+GtkWidget * 
+gtk_sheet_get_entry_widget(GtkSheet *sheet)
+{
+ g_return_val_if_fail (sheet != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_SHEET (sheet), NULL);
+ g_return_val_if_fail (sheet->sheet_entry != NULL, NULL);
+
+ return (sheet->sheet_entry);
+}
+
+#if 0
+/* BUTTONS */
+static void
+row_button_set (GtkSheet *sheet, gint row)
+{
+  if(sheet->row[row].button.state == GTK_STATE_ACTIVE) return;
+
+  sheet->row[row].button.state = GTK_STATE_ACTIVE;
+  gtk_sheet_button_draw(sheet, row, -1);
+}
+
+static void
+row_button_release (GtkSheet *sheet, gint row)
+{
+  if(sheet->row[row].button.state == GTK_STATE_NORMAL) return;
+
+  sheet->row[row].button.state = GTK_STATE_NORMAL;
+  gtk_sheet_button_draw(sheet, row, -1);
+}
+#endif
+
+static void
+gtk_sheet_button_draw (GtkSheet *sheet, gint row, gint column)
+{
+  GdkWindow *window = NULL;
+  GtkShadowType shadow_type;
+  guint width = 0, height = 0;
+  gint x = 0, y = 0;
+  gint index = 0;
+  gint text_width = 0, text_height = 0;
+  const GtkSheetButton *button = NULL;
+  GtkSheetChild *child = NULL;
+  GdkRectangle allocation;
+  gboolean is_sensitive = FALSE;
+  gint state = 0;
+  gint len = 0;
+  gchar *line = 0;
+
+  PangoAlignment align = PANGO_ALIGN_LEFT; 
+  gboolean rtl;
+
+  rtl = gtk_widget_get_direction(GTK_WIDGET(sheet)) == GTK_TEXT_DIR_RTL;
+
+  if(!GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))) return;
+
+  if(row >= 0 && !yyy_row_is_visible(sheet, row)) return;
+  if(column >= 0 && ! xxx_column_is_visible(sheet, column)) return;
+  if(row >= 0 && !sheet->row_titles_visible) return;
+  if(column >= 0 && !sheet->column_titles_visible) return;
+  if(column>=0 && column < MIN_VISIBLE_COLUMN(sheet)) return;
+  if(column>=0 && column > MAX_VISIBLE_COLUMN(sheet)) return;
+  if(row>=0 && row < MIN_VISIBLE_ROW(sheet)) return;
+  if(row>=0 && row > MAX_VISIBLE_ROW(sheet)) return;
+  if( (row == -1) && (column == -1) ) return; 
+
+  if(row==-1){
+     window=sheet->column_title_window;
+     button= xxx_column_button(sheet, column);
+     index=column;
+     x = COLUMN_LEFT_XPIXEL(sheet, column)+CELL_SPACING;
+     if(sheet->row_titles_visible) x -= sheet->row_title_area.width;
+     y = 0;
+     width = xxx_column_width(sheet, column);
+     height = sheet->column_title_area.height;
+     is_sensitive=xxx_column_is_sensitive(sheet, column);
+  }
+  if(column==-1){
+     window=sheet->row_title_window;
+     button = yyy_row_button(sheet, row);
+     index=row;
+     x = 0;
+     y = ROW_TOP_YPIXEL(sheet, row)+CELL_SPACING;
+     if(sheet->column_titles_visible) y-=sheet->column_title_area.height;
+     width = sheet->row_title_area.width;
+     height = yyy_row_height(sheet, row);
+     is_sensitive=yyy_row_is_sensitive(sheet, row);
+  }
+
+  allocation.x = x;
+  allocation.y = y;
+  allocation.width = width;
+  allocation.height = height;
+  gdk_window_clear_area (window,
+                         x, y,
+                        width, height);
+
+  gtk_paint_box (sheet->button->style, window,
+                 GTK_STATE_NORMAL, GTK_SHADOW_OUT, 
+                 &allocation, GTK_WIDGET(sheet->button),
+                 "buttondefault", x, y, width, height);
+
+  state = button->state;
+  if(!is_sensitive) state=GTK_STATE_INSENSITIVE;
+
+  if (state == GTK_STATE_ACTIVE)
+     shadow_type = GTK_SHADOW_IN;
+  else
+     shadow_type = GTK_SHADOW_OUT;
+
+  if(state != GTK_STATE_NORMAL && state != GTK_STATE_INSENSITIVE)
+  gtk_paint_box (sheet->button->style, window,
+                 button->state, shadow_type, 
+                 &allocation, GTK_WIDGET(sheet->button),
+                 "button", x, y, width, height);
+
+  if(button->label_visible){
+
+    text_height=DEFAULT_ROW_HEIGHT(GTK_WIDGET(sheet))-2*CELLOFFSET;
+
+    gdk_gc_set_clip_rectangle(GTK_WIDGET(sheet)->style->fg_gc[button->state], 
+                              &allocation);
+    gdk_gc_set_clip_rectangle(GTK_WIDGET(sheet)->style->white_gc, &allocation);
+
+    y += 2*sheet->button->style->ythickness;
+
+
+    if(button->label && strlen(button->label)>0){
+           gchar *words = 0;
+           PangoLayout *layout = NULL;
+           gint real_x = x, real_y = y;
+
+           words=button->label;
+           line = g_new(gchar, 1);
+           line[0]='\0';
+
+           while(words && *words != '\0'){
+             if(*words != '\n'){
+                len=strlen(line);
+                line=g_realloc(line, len+2);
+                line[len]=*words;
+                line[len+1]='\0';
+             }
+             if(*words == '\n' || *(words+1) == '\0'){
+               text_width = STRING_WIDTH(GTK_WIDGET(sheet), GTK_WIDGET(sheet)->style->font_desc, line);
+
+               layout = gtk_widget_create_pango_layout (GTK_WIDGET(sheet), line);
+               switch(button->justification){
+                 case GTK_JUSTIFY_LEFT:
+                   real_x = x + CELLOFFSET;
+                   align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
+                   break;
+                 case GTK_JUSTIFY_RIGHT:
+                   real_x = x + width - text_width - CELLOFFSET;
+                   align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
+                   break;
+                 case GTK_JUSTIFY_CENTER:
+                 default:
+                   real_x = x + (width - text_width)/2;
+                   align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
+                   pango_layout_set_justify (layout, TRUE);
+               }
+               pango_layout_set_alignment (layout, align);
+               gtk_paint_layout (GTK_WIDGET(sheet)->style,
+                                 window,
+                                 state,
+                                 FALSE,
+                                 &allocation,
+                                 GTK_WIDGET(sheet),
+                                 "label",
+                                 real_x, real_y,
+                                 layout);
+               g_object_unref(G_OBJECT(layout));
+
+               real_y += text_height + 2;
+
+               g_free(line);
+               line = g_new(gchar, 1);
+               line[0]='\0';
+             }
+             words++;
+           }
+           g_free(line);
+    }
+    if(button->label && strlen(button->label) > 0){
+      PangoLayout *layout = NULL;
+      gint real_x = x, real_y = y;
+
+      text_width = STRING_WIDTH(GTK_WIDGET(sheet), GTK_WIDGET(sheet)->style->font_desc, button->label);
+
+      layout = gtk_widget_create_pango_layout (GTK_WIDGET(sheet), button->label);
+      switch(button->justification){
+      case GTK_JUSTIFY_LEFT:
+       real_x = x + CELLOFFSET;
+       align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
+       break;
+      case GTK_JUSTIFY_RIGHT:
+       real_x = x + width - text_width - CELLOFFSET;
+       align = rtl ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT;
+       break;
+      case GTK_JUSTIFY_CENTER:
+      default:
+       real_x = x + (width - text_width)/2;
+       align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
+       pango_layout_set_justify (layout, TRUE);
+      }
+      pango_layout_set_alignment (layout, align);
+      gtk_paint_layout (GTK_WIDGET(sheet)->style,
+                       window,
+                       state,
+                       FALSE,
+                       &allocation,
+                       GTK_WIDGET(sheet),
+                       "label",
+                       real_x, real_y,
+                       layout);
+      g_object_unref(G_OBJECT(layout));
+    }
+
+    gdk_gc_set_clip_rectangle(GTK_WIDGET(sheet)->style->fg_gc[button->state],
+                            NULL);
+    gdk_gc_set_clip_rectangle(GTK_WIDGET(sheet)->style->white_gc, NULL);
+
+  }
+
+  if((child = button->child) && (child->widget)){
+      child->x = allocation.x;
+      child->y = allocation.y;
+
+      child->x += (width - child->widget->requisition.width) / 2; 
+      child->y += (height - child->widget->requisition.height) / 2;
+      allocation.x = child->x;
+      allocation.y = child->y;
+      allocation.width = child->widget->requisition.width;
+      allocation.height = child->widget->requisition.height;
+
+      x = child->x;
+      y = child->y;
+
+      gtk_widget_set_state(child->widget, button->state);
+
+      if(GTK_WIDGET_REALIZED(GTK_WIDGET(sheet)) &&
+         GTK_WIDGET_MAPPED(child->widget))
+            {
+              gtk_widget_size_allocate(child->widget, 
+                                       &allocation);
+              gtk_widget_queue_draw(child->widget);
+            }
+  }
+   
+}
+
+
+/* SCROLLBARS
+ *
+ * functions:
+ *   adjust_scrollbars
+ *   vadjustment_changed
+ *   hadjustment_changed
+ *   vadjustment_value_changed
+ *   hadjustment_value_changed */
+
+static void
+adjust_scrollbars (GtkSheet * sheet)
+{
+
+ if(sheet->vadjustment){ 
+  sheet->vadjustment->page_size = sheet->sheet_window_height;
+  sheet->vadjustment->page_increment = sheet->sheet_window_height / 2;
+  sheet->vadjustment->step_increment = DEFAULT_ROW_HEIGHT(GTK_WIDGET(sheet));
+  sheet->vadjustment->lower = 0;
+  sheet->vadjustment->upper = SHEET_HEIGHT (sheet) + 80;
+/*
+  if (sheet->sheet_window_height - sheet->voffset > SHEET_HEIGHT (sheet))
+    {
+      sheet->vadjustment->value = MAX(0, SHEET_HEIGHT (sheet) - 
+       sheet->sheet_window_height);
+      gtk_signal_emit_by_name (GTK_OBJECT (sheet->vadjustment), 
+                              "value_changed");
+    }
+*/
+    gtk_signal_emit_by_name (GTK_OBJECT(sheet->vadjustment), "changed");
+
+ }
+
+ if(sheet->hadjustment){
+  sheet->hadjustment->page_size = sheet->sheet_window_width;
+  sheet->hadjustment->page_increment = sheet->sheet_window_width / 2;
+  sheet->hadjustment->step_increment = DEFAULT_COLUMN_WIDTH;
+  sheet->hadjustment->lower = 0;
+  sheet->hadjustment->upper = SHEET_WIDTH (sheet)+ 80;
+/*
+  if (sheet->sheet_window_width - sheet->hoffset > SHEET_WIDTH (sheet))
+    {
+      sheet->hadjustment->value = MAX(0, SHEET_WIDTH (sheet) - 
+       sheet->sheet_window_width);
+      gtk_signal_emit_by_name (GTK_OBJECT(sheet->hadjustment), 
+                              "value_changed");
+    }
+*/
+    gtk_signal_emit_by_name (GTK_OBJECT(sheet->hadjustment), "changed");
+
+ }
+/*
+ if(GTK_WIDGET_REALIZED(sheet)) 
+   {
+     if(sheet->row_titles_visible){
+                 size_allocate_row_title_buttons(sheet);
+                 gdk_window_show(sheet->row_title_window);
+     }
+
+     if(sheet->column_titles_visible){
+                 size_allocate_column_title_buttons(sheet);
+                 gdk_window_show(sheet->column_title_window);
+     }
+
+     gtk_sheet_range_draw(sheet, NULL);
+   }
+*/
+}
+
+
+static void
+vadjustment_changed (GtkAdjustment * adjustment,
+                              gpointer data)
+{
+  GtkSheet *sheet;
+
+  g_return_if_fail (adjustment != NULL);
+  g_return_if_fail (data != NULL);
+
+  sheet = GTK_SHEET (data);
+
+}
+
+static void
+hadjustment_changed (GtkAdjustment * adjustment,
+                              gpointer data)
+{
+  GtkSheet *sheet;
+
+  g_return_if_fail (adjustment != NULL);
+  g_return_if_fail (data != NULL);
+
+  sheet = GTK_SHEET (data);
+}
+
+
+static void
+vadjustment_value_changed (GtkAdjustment * adjustment,
+                                    gpointer data)
+{
+  GtkSheet *sheet;
+  gint diff, value, old_value;
+  gint i;
+  gint row, new_row;
+  gint y=0;
+
+  g_return_if_fail (adjustment != NULL);
+  g_return_if_fail (data != NULL);
+  g_return_if_fail (GTK_IS_SHEET (data));
+
+  sheet = GTK_SHEET (data);
+
+  if(GTK_SHEET_IS_FROZEN(sheet)) return;
+
+  row=ROW_FROM_YPIXEL(sheet,sheet->column_title_area.height + CELL_SPACING);
+  if(!sheet->column_titles_visible)
+     row=ROW_FROM_YPIXEL(sheet,CELL_SPACING);
+    
+  old_value = -sheet->voffset;
+
+  new_row = g_sheet_row_pixel_to_row(sheet->row_geometry,
+                                    adjustment->value,sheet);
+
+  y = g_sheet_row_start_pixel(sheet->row_geometry, new_row, sheet);
+
+  if (adjustment->value > sheet->old_vadjustment && sheet->old_vadjustment > 0. &&
+      yyy_row_height(sheet, i) > sheet->vadjustment->step_increment){
+/* This avoids embarrassing twitching */
+          if(row == new_row && row != yyy_row_count(sheet) - 1 &&
+             adjustment->value - sheet->old_vadjustment >= 
+                          sheet->vadjustment->step_increment &&
+             new_row + 1 != MIN_VISIBLE_ROW(sheet)){
+                new_row+=1;
+                y=y+yyy_row_height(sheet, row);
+          }
+  }
+
+/* Negative old_adjustment enforces the redraw, otherwise avoid spureous redraw */
+  if(sheet->old_vadjustment >= 0. && row == new_row){
+      sheet->old_vadjustment = sheet->vadjustment->value;
+      return;
+  }
+
+  sheet->old_vadjustment = sheet->vadjustment->value;
+  adjustment->value=y;
+
+  if(new_row == 0){
+   sheet->vadjustment->step_increment=  yyy_row_height(sheet, 0);
+  }else{
+   sheet->vadjustment->step_increment=
+   MIN(yyy_row_height(sheet, new_row), yyy_row_height(sheet, new_row-1));
+  }
+
+  sheet->vadjustment->value=adjustment->value;
+
+  value = adjustment->value;
+
+  if (value >= -sheet->voffset)
+       {
+         /* scroll down */
+         diff = value + sheet->voffset;
+       }
+  else
+       {
+         /* scroll up */
+         diff = -sheet->voffset - value;
+       }
+
+      sheet->voffset = -value;
+  sheet->view.row0=ROW_FROM_YPIXEL(sheet, sheet->column_title_area.height+1);
+  sheet->view.rowi=ROW_FROM_YPIXEL(sheet, sheet->sheet_window_height-1);
+  if(!sheet->column_titles_visible)
+     sheet->view.row0=ROW_FROM_YPIXEL(sheet, 1);
+
+  if(GTK_WIDGET_REALIZED(sheet->sheet_entry) &&
+     sheet->state == GTK_SHEET_NORMAL && 
+     sheet->active_cell.row >= 0 && sheet->active_cell.col >= 0 &&
+     !gtk_sheet_cell_isvisible(sheet, sheet->active_cell.row,
+                                      sheet->active_cell.col))
+    {
+      const gchar *text;
+
+      text = gtk_entry_get_text(GTK_ENTRY(gtk_sheet_get_entry(sheet)));
+
+      if(!text || strlen(text)==0) 
+             gtk_sheet_cell_clear(sheet,
+                                  sheet->active_cell.row,
+                                  sheet->active_cell.col);
+       gtk_widget_unmap(sheet->sheet_entry);
+    }
+
+  gtk_sheet_position_children(sheet);
+
+  gtk_sheet_range_draw(sheet, NULL);
+  size_allocate_row_title_buttons(sheet);
+  size_allocate_global_button(sheet);
+}
+
+static void
+hadjustment_value_changed (GtkAdjustment * adjustment,
+                          gpointer data)
+{
+  GtkSheet *sheet;
+  gint i, diff, value, old_value;
+  gint column, new_column;
+  gint x=0;
+
+  g_return_if_fail (adjustment != NULL);
+  g_return_if_fail (data != NULL);
+  g_return_if_fail (GTK_IS_SHEET (data));
+
+  sheet = GTK_SHEET (data);
+
+  if(GTK_SHEET_IS_FROZEN(sheet)) return;
+
+  column=COLUMN_FROM_XPIXEL(sheet,sheet->row_title_area.width + CELL_SPACING);
+  if(!sheet->row_titles_visible)
+     column=COLUMN_FROM_XPIXEL(sheet, CELL_SPACING);
+
+  old_value = -sheet->hoffset;
+
+  for(i=0; i < xxx_column_count(sheet); i++)
+    {
+      if(xxx_column_is_visible(sheet, i)) x += xxx_column_width(sheet, i);
+      if(x > adjustment->value) break;
+    }
+  x-=xxx_column_width(sheet, i);
+  new_column=i;
+
+  if (adjustment->value > sheet->old_hadjustment && sheet->old_hadjustment > 0 &&
+      xxx_column_width(sheet, i) > sheet->hadjustment->step_increment){
+/* This avoids embarrassing twitching */
+          if(column == new_column && column != xxx_column_count(sheet) - 1 &&
+             adjustment->value - sheet->old_hadjustment >= 
+                          sheet->hadjustment->step_increment &&
+             new_column + 1 != MIN_VISIBLE_COLUMN(sheet)){
+             new_column+=1;
+             x=x+xxx_column_width(sheet, column);
+          }
+  }
+
+/* Negative old_adjustment enforces the redraw, otherwise avoid spureous redraw */
+  if(sheet->old_hadjustment >= 0. && new_column == column){
+     sheet->old_hadjustment = sheet->hadjustment->value;
+     return;
+  }
+
+  sheet->old_hadjustment = sheet->hadjustment->value;
+  adjustment->value=x;
+
+  if(new_column == 0){
+   sheet->hadjustment->step_increment=
+   xxx_column_width(sheet, 0);
+  }else{
+   sheet->hadjustment->step_increment=
+   MIN(xxx_column_width(sheet, new_column), xxx_column_width(sheet, new_column-1));
+  }
+
+
+  sheet->hadjustment->value=adjustment->value;
+
+  value = adjustment->value;
+
+  if (value >= -sheet->hoffset)
+        {
+         /* scroll right */
+         diff = value + sheet->hoffset;
+       }
+  else
+       {
+         /* scroll left */
+         diff = -sheet->hoffset - value;
+       }
+
+  sheet->hoffset = -value;
+
+  sheet->view.col0=COLUMN_FROM_XPIXEL(sheet, sheet->row_title_area.width+1);
+  sheet->view.coli=COLUMN_FROM_XPIXEL(sheet, sheet->sheet_window_width);
+  if(!sheet->row_titles_visible)
+    sheet->view.col0=COLUMN_FROM_XPIXEL(sheet, 1);
+
+  if(GTK_WIDGET_REALIZED(sheet->sheet_entry) &&
+     sheet->state == GTK_SHEET_NORMAL && 
+     sheet->active_cell.row >= 0 && sheet->active_cell.col >= 0 &&
+     !gtk_sheet_cell_isvisible(sheet, sheet->active_cell.row,
+                                      sheet->active_cell.col))
+    {
+      const gchar *text;
+
+      text = gtk_entry_get_text(GTK_ENTRY(gtk_sheet_get_entry(sheet)));
+      if(!text || strlen(text)==0) 
+             gtk_sheet_cell_clear(sheet,
+                                  sheet->active_cell.row,
+                                  sheet->active_cell.col);
+
+      gtk_widget_unmap(sheet->sheet_entry);
+    }
+
+  gtk_sheet_position_children(sheet);
+
+  gtk_sheet_range_draw(sheet, NULL);
+  size_allocate_column_title_buttons(sheet);
+}
+       
+
+/* COLUMN RESIZING */
+static void                          
+draw_xor_vline (GtkSheet * sheet)
+{
+  GtkWidget *widget;
+  
+  g_return_if_fail (sheet != NULL);
+  
+  widget = GTK_WIDGET (sheet);
+
+  gdk_draw_line (widget->window, sheet->xor_gc,  
+                 sheet->x_drag,                                       
+                 sheet->column_title_area.height,                               
+                 sheet->x_drag,                                             
+                 sheet->sheet_window_height + 1);
+}
+
+/* ROW RESIZING */
+static void                          
+draw_xor_hline (GtkSheet * sheet)
+{
+  GtkWidget *widget;
+  
+  g_return_if_fail (sheet != NULL);
+  
+  widget = GTK_WIDGET (sheet);
+
+  gdk_draw_line (widget->window, sheet->xor_gc,  
+                sheet->row_title_area.width,
+                 sheet->y_drag,                                       
+                        
+                sheet->sheet_window_width + 1,                      
+                 sheet->y_drag);                                             
+}
+
+/* SELECTED RANGE */
+static void
+draw_xor_rectangle(GtkSheet *sheet, GtkSheetRange range)
+{
+   gint i;
+   GdkRectangle clip_area, area;
+   GdkGCValues values;
+
+   area.x=COLUMN_LEFT_XPIXEL(sheet, range.col0);
+   area.y=ROW_TOP_YPIXEL(sheet, range.row0);
+   area.width=COLUMN_LEFT_XPIXEL(sheet, range.coli)-area.x+
+                                        xxx_column_width(sheet, range.coli);
+   area.height=ROW_TOP_YPIXEL(sheet, range.rowi)-area.y+
+                                        yyy_row_height(sheet, range.rowi);
+
+   clip_area.x=sheet->row_title_area.width;
+   clip_area.y=sheet->column_title_area.height;
+   clip_area.width=sheet->sheet_window_width;
+   clip_area.height=sheet->sheet_window_height;
+
+   if(!sheet->row_titles_visible) clip_area.x = 0;
+   if(!sheet->column_titles_visible) clip_area.y = 0;
+
+   if(area.x<0) {
+      area.width=area.width+area.x;
+      area.x=0;
+   }
+   if(area.width>clip_area.width) area.width=clip_area.width+10;
+   if(area.y<0) {
+      area.height=area.height+area.y;
+      area.y=0;
+   }
+   if(area.height>clip_area.height) area.height=clip_area.height+10;
+
+   clip_area.x--;
+   clip_area.y--;
+   clip_area.width+=3;
+   clip_area.height+=3;
+
+   gdk_gc_get_values(sheet->xor_gc, &values);
+
+   gdk_gc_set_clip_rectangle(sheet->xor_gc, &clip_area);
+
+   for(i=-1;i<=1;++i)
+     gdk_draw_rectangle(sheet->sheet_window,
+                        sheet->xor_gc,
+                       FALSE,
+                       area.x+i, area.y+i,
+                        area.width-2*i, area.height-2*i);
+
+
+   gdk_gc_set_clip_rectangle(sheet->xor_gc, NULL);
+
+   gdk_gc_set_foreground(sheet->xor_gc, &values.foreground);
+
+}                      
+
+  
+/* this function returns the new width of the column being resized given
+ * the column and x position of the cursor; the x cursor position is passed
+ * in as a pointer and automaticaly corrected if it's beyond min/max limits */
+static guint
+new_column_width (GtkSheet * sheet,
+                 gint column,
+                 gint * x)
+{
+  gint cx, width;
+  guint min_width;
+
+  cx = *x;
+
+  min_width = sheet->column_requisition;
+
+  /* you can't shrink a column to less than its minimum width */
+  if (cx < COLUMN_LEFT_XPIXEL (sheet, column) + min_width)
+    {
+      *x = cx = COLUMN_LEFT_XPIXEL (sheet, column) + min_width;
+    }
+
+  /* don't grow past the end of the window */
+  /*
+  if (cx > sheet->sheet_window_width)
+    {
+      *x = cx = sheet->sheet_window_width;
+    }
+    */
+  /* calculate new column width making sure it doesn't end up
+   * less than the minimum width */
+  width = cx - COLUMN_LEFT_XPIXEL (sheet, column);
+  if (width < min_width)
+    width = min_width;
+
+  xxx_set_column_width(sheet, column, width);
+  sheet->view.coli=COLUMN_FROM_XPIXEL(sheet, sheet->sheet_window_width);
+  size_allocate_column_title_buttons (sheet);
+  
+  return width;
+}
+
+/* this function returns the new height of the row being resized given
+ * the row and y position of the cursor; the y cursor position is passed
+ * in as a pointer and automaticaly corrected if it's beyond min/max limits */
+static guint
+new_row_height (GtkSheet * sheet,
+               gint row,
+               gint * y)
+{
+  gint cy, height;
+  guint min_height;
+
+  cy = *y;
+  min_height = sheet->row_requisition;
+
+  /* you can't shrink a row to less than its minimum height */
+  if (cy < ROW_TOP_YPIXEL (sheet, row) + min_height)
+
+    {
+      *y = cy = ROW_TOP_YPIXEL (sheet, row) + min_height;
+    }
+
+  /* don't grow past the end of the window */
+  /*
+  if (cy > sheet->sheet_window_height)
+    {
+      *y = cy = sheet->sheet_window_height;
+    }
+    */
+  /* calculate new row height making sure it doesn't end up
+   * less than the minimum height */
+  height = (cy - ROW_TOP_YPIXEL (sheet, row));
+  if (height < min_height)
+    height = min_height;
+
+  yyy_set_row_height(sheet, row, height);
+  sheet->view.rowi=ROW_FROM_YPIXEL(sheet, sheet->sheet_window_height-1);
+  size_allocate_row_title_buttons (sheet);
+
+  return height;
+}
+
+static void
+gtk_sheet_set_column_width (GtkSheet * sheet,
+                           gint column,
+                           guint width)
+{
+  guint min_width;
+
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  if (column < 0 || column >= xxx_column_count(sheet))
+    return;
+
+  gtk_sheet_column_size_request(sheet, column, &min_width);
+  if(width < min_width) return;
+
+  xxx_set_column_width(sheet, column, width);
+
+  if(GTK_WIDGET_REALIZED(GTK_WIDGET(sheet)) && !GTK_SHEET_IS_FROZEN(sheet)){
+    size_allocate_column_title_buttons (sheet);
+    adjust_scrollbars (sheet);
+    gtk_sheet_size_allocate_entry(sheet);
+    gtk_sheet_range_draw (sheet, NULL);
+  } else
+
+  gtk_signal_emit(GTK_OBJECT(sheet), sheet_signals[CHANGED], -1, column);
+  gtk_signal_emit(GTK_OBJECT(sheet), sheet_signals[NEW_COL_WIDTH], column, width);
+
+}
+
+
+
+void
+gtk_sheet_set_row_height (GtkSheet * sheet,
+                           gint row,
+                           guint height)
+{
+  guint min_height;
+
+  g_return_if_fail (sheet != NULL);
+  g_return_if_fail (GTK_IS_SHEET (sheet));
+
+  if (row < 0 || row >= yyy_row_count(sheet))
+    return;
+
+  gtk_sheet_row_size_request(sheet, row, &min_height);
+  if(height < min_height) return;
+
+  yyy_set_row_height(sheet, row, height);
+
+  if(GTK_WIDGET_REALIZED(GTK_WIDGET(sheet)) && !GTK_SHEET_IS_FROZEN(sheet)){
+    size_allocate_row_title_buttons (sheet);
+    adjust_scrollbars (sheet);
+    gtk_sheet_size_allocate_entry(sheet);
+    gtk_sheet_range_draw (sheet, NULL);
+  }
+
+  gtk_signal_emit(GTK_OBJECT(sheet), sheet_signals[CHANGED], row, -1);
+  gtk_signal_emit(GTK_OBJECT(sheet), sheet_signals[NEW_ROW_HEIGHT], row, height);
+
+}
+
+
+gboolean
+gtk_sheet_get_attributes(GtkSheet *sheet, gint row, gint col, GtkSheetCellAttr *attributes)
+{
+ g_return_val_if_fail (sheet != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_SHEET (sheet), FALSE);
+
+ if(row < 0 || col < 0) return FALSE;
+
+ init_attributes(sheet, col, attributes);
+
+ if ( !sheet->model) 
+        return FALSE;
+
+ attributes->is_editable = g_sheet_model_is_editable(sheet->model, row, col);
+ attributes->is_visible = g_sheet_model_is_visible(sheet->model, row, col);
+
+ const GdkColor *fg = g_sheet_model_get_foreground(sheet->model, row, col);
+ if ( fg ) 
+   attributes->foreground =  *fg;
+
+ const GdkColor *bg = g_sheet_model_get_background(sheet->model, row, col);
+ if ( bg ) 
+   attributes->background =  *bg;
+
+ const GtkJustification *j = g_sheet_model_get_justification(sheet->model,
+                                                              row, col);
+ if (j)   attributes->justification = *j;
+
+ const PangoFontDescription *font_desc = 
+   g_sheet_model_get_font_desc(sheet->model, row, col);
+ if ( font_desc )  attributes->font_desc = font_desc;
+
+ const GtkSheetCellBorder *border = 
+  g_sheet_model_get_cell_border(sheet->model, row, col);
+
+ if ( border )    attributes->border = *border;
+
+ return TRUE;
+}
+
+static void
+init_attributes(GtkSheet *sheet, gint col, GtkSheetCellAttr *attributes)
+{
+ /* DEFAULT VALUES */    
+ attributes->foreground = GTK_WIDGET(sheet)->style->black;
+ attributes->background = sheet->bg_color;
+ if(!GTK_WIDGET_REALIZED(GTK_WIDGET(sheet))){
+   GdkColormap *colormap;
+   colormap=gdk_colormap_get_system();
+   gdk_color_black(colormap, &attributes->foreground);
+   attributes->background = sheet->bg_color;
+ }
+ attributes->justification = xxx_column_justification(sheet, col);
+ attributes->border.width = 0;
+ attributes->border.line_style = GDK_LINE_SOLID;
+ attributes->border.cap_style = GDK_CAP_NOT_LAST;
+ attributes->border.join_style = GDK_JOIN_MITER;
+ attributes->border.mask = 0;
+ attributes->border.color = GTK_WIDGET(sheet)->style->black;
+ attributes->is_editable = TRUE;
+ attributes->is_visible = TRUE;
+ attributes->font_desc = GTK_WIDGET(sheet)->style->font_desc;
+
+}       
+
+
+/********************************************************************
+ * Container Functions:
+ * gtk_sheet_add
+ * gtk_sheet_put
+ * gtk_sheet_attach
+ * gtk_sheet_remove
+ * gtk_sheet_move_child
+ * gtk_sheet_position_child
+ * gtk_sheet_position_children 
+ * gtk_sheet_realize_child
+ * gtk_sheet_get_child_at
+ ********************************************************************/ 
+
+GtkSheetChild *
+gtk_sheet_put(GtkSheet *sheet, GtkWidget *child, gint x, gint y)
+{
+  GtkRequisition child_requisition;
+  GtkSheetChild *child_info;
+
+  g_return_val_if_fail(sheet != NULL, NULL);
+  g_return_val_if_fail(GTK_IS_SHEET(sheet), NULL);
+  g_return_val_if_fail(child != NULL, NULL);
+  g_return_val_if_fail(child->parent == NULL, NULL);
+
+  child_info = g_new (GtkSheetChild, 1);
+  child_info->widget = child;
+  child_info->x = x;  
+  child_info->y = y;
+  child_info->attached_to_cell = FALSE;
+  child_info->floating = TRUE;
+  child_info->xpadding = child_info->ypadding = 0;
+  child_info->xexpand = child_info->yexpand = FALSE;
+  child_info->xshrink = child_info->yshrink = FALSE;
+  child_info->xfill = child_info->yfill = FALSE;
+
+  sheet->children = g_list_append(sheet->children, child_info);
+
+  gtk_widget_set_parent (child, GTK_WIDGET(sheet));
+
+  gtk_widget_size_request(child, &child_requisition);
+
+  if (GTK_WIDGET_VISIBLE(GTK_WIDGET(sheet)))
+    {
+       if(GTK_WIDGET_REALIZED(GTK_WIDGET(sheet)) && 
+          (!GTK_WIDGET_REALIZED(child) || GTK_WIDGET_NO_WINDOW(child)))
+        gtk_sheet_realize_child(sheet, child_info);
+
+       if(GTK_WIDGET_MAPPED(GTK_WIDGET(sheet)) && 
+          !GTK_WIDGET_MAPPED(child))
+        gtk_widget_map(child);
+    }
+
+  gtk_sheet_position_child(sheet, child_info);
+
+/* This will avoid drawing on the titles */
+
+  if(GTK_WIDGET_REALIZED(GTK_WIDGET(sheet)))
+   {
+      if(sheet->row_titles_visible)
+             gdk_window_show(sheet->row_title_window);
+      if(sheet->column_titles_visible)
+             gdk_window_show(sheet->column_title_window);
+   }
+
+  return (child_info);
+}
+
+void
+gtk_sheet_attach_floating       (GtkSheet *sheet,
+                                 GtkWidget *widget,
+                                 gint row, gint col)
+{
+  GdkRectangle area;
+  GtkSheetChild *child;
+
+  if(row < 0 || col < 0){
+    gtk_sheet_button_attach(sheet, widget, row, col);
+    return;
+  }
+
+  gtk_sheet_get_cell_area(sheet, row, col, &area);
+  child = gtk_sheet_put(sheet, widget, area.x, area.y);
+  child->attached_to_cell = TRUE;
+  child->row = row;
+  child->col = col;
+}
+
+void
+gtk_sheet_attach_default        (GtkSheet *sheet,
+                                 GtkWidget *widget,
+                                 gint row, gint col)
+{
+  if(row < 0 || col < 0){
+    gtk_sheet_button_attach(sheet, widget, row, col);
+    return;
+  }
+
+  gtk_sheet_attach(sheet, widget, row, col, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0);
+}
+
+void
+gtk_sheet_attach        (GtkSheet *sheet,
+                         GtkWidget *widget,
+                         gint row, gint col,
+                         gint xoptions,
+                         gint yoptions,
+                         gint xpadding,
+                         gint ypadding)
+{
+  GdkRectangle area;
+  GtkSheetChild *child = NULL;
+
+  if(row < 0 || col < 0){
+    gtk_sheet_button_attach(sheet, widget, row, col);
+    return;
+  }
+
+  child = g_new0(GtkSheetChild, 1);
+  child->attached_to_cell = TRUE;
+  child->floating = FALSE;
+  child->widget = widget;
+  child->row = row;
+  child->col = col;
+  child->xpadding = xpadding;
+  child->ypadding = ypadding;
+  child->xexpand = (xoptions & GTK_EXPAND) != 0;
+  child->yexpand = (yoptions & GTK_EXPAND) != 0;
+  child->xshrink = (xoptions & GTK_SHRINK) != 0;
+  child->yshrink = (yoptions & GTK_SHRINK) != 0;
+  child->xfill = (xoptions & GTK_FILL) != 0;
+  child->yfill = (yoptions & GTK_FILL) != 0;
+
+  sheet->children = g_list_append(sheet->children, child);
+
+  gtk_sheet_get_cell_area(sheet, row, col, &area);
+
+  child->x = area.x + child->xpadding;
+  child->y = area.y + child->ypadding;
+
+  if (GTK_WIDGET_VISIBLE(GTK_WIDGET(sheet)))
+    {
+       if(GTK_WIDGET_REALIZED(GTK_WIDGET(sheet)) &&
+          (!GTK_WIDGET_REALIZED(widget) || GTK_WIDGET_NO_WINDOW(widget)))
+        gtk_sheet_realize_child(sheet, child);
+
+       if(GTK_WIDGET_MAPPED(GTK_WIDGET(sheet)) &&
+          !GTK_WIDGET_MAPPED(widget))
+        gtk_widget_map(widget);
+    }
+
+  gtk_sheet_position_child(sheet, child);
+
+/* This will avoid drawing on the titles */
+
+  if(GTK_WIDGET_REALIZED(GTK_WIDGET(sheet)))
+   {
+      if(GTK_SHEET_ROW_TITLES_VISIBLE(sheet))
+             gdk_window_show(sheet->row_title_window);
+      if(GTK_SHEET_COL_TITLES_VISIBLE(sheet))
+             gdk_window_show(sheet->column_title_window);
+   }
+
+}
+
+void
+gtk_sheet_button_attach                (GtkSheet *sheet, 
+                                GtkWidget *widget, 
+                                gint row, gint col)
+{
+  GtkSheetButton *button;
+  GtkSheetChild *child;
+  GtkRequisition button_requisition;
+
+  if(row >= 0 && col >= 0) return;
+  if(row < 0 && col < 0) return;
+
+  child = g_new (GtkSheetChild, 1);
+  child->widget = widget;
+  child->x = 0;  
+  child->y = 0;  
+  child->attached_to_cell = TRUE;
+  child->floating = FALSE;
+  child->row = row;
+  child->col = col;
+  child->xpadding = child->ypadding = 0;
+  child->xshrink = child->yshrink = FALSE;
+  child->xfill = child->yfill = FALSE;
+
+
+  sheet->children = g_list_append(sheet->children, child);
+
+  gtk_sheet_button_size_request(sheet, button, &button_requisition);
+
+
+  if (GTK_WIDGET_VISIBLE(GTK_WIDGET(sheet)))
+    {
+       if(GTK_WIDGET_REALIZED(GTK_WIDGET(sheet)) && 
+          (!GTK_WIDGET_REALIZED(widget) || GTK_WIDGET_NO_WINDOW(widget)))
+        gtk_sheet_realize_child(sheet, child);
+
+       if(GTK_WIDGET_MAPPED(GTK_WIDGET(sheet)) && 
+          !GTK_WIDGET_MAPPED(widget))
+        gtk_widget_map(widget);
+    }
+
+  if(row == -1) size_allocate_column_title_buttons(sheet);
+  if(col == -1) size_allocate_row_title_buttons(sheet);
+
+}
+
+static void
+label_size_request(GtkSheet *sheet, gchar *label, GtkRequisition *req)
+{
+  gchar *words;
+  gchar word[1000];
+  gint n = 0;
+  gint row_height = DEFAULT_ROW_HEIGHT(GTK_WIDGET(sheet)) - 2*CELLOFFSET + 2;
+
+  req->height = 0;
+  req->width = 0;
+  words=label;
+
+  while(words && *words != '\0'){
+    if(*words == '\n' || *(words+1) == '\0'){
+      req->height += row_height;
+
+      word[n] = '\0';
+      req->width = MAX(req->width, STRING_WIDTH(GTK_WIDGET(sheet), GTK_WIDGET(sheet)->style->font_desc, word));
+      n = 0;
+    } else {
+      word[n++] = *words;
+    }
+    words++;
+  }
+  if(n > 0) req->height -= 2;
+}
+
+static void
+gtk_sheet_button_size_request  (GtkSheet *sheet,
+                                 const GtkSheetButton *button, 
+                                 GtkRequisition *button_requisition)
+{
+  GtkRequisition requisition;
+  GtkRequisition label_requisition;
+
+  if(gtk_sheet_autoresize(sheet) && button->label && strlen(button->label) > 0){
+     label_size_request(sheet, button->label, &label_requisition);
+     label_requisition.width += 2*CELLOFFSET;
+     label_requisition.height += 2*CELLOFFSET;
+  } else {
+     label_requisition.height = DEFAULT_ROW_HEIGHT(GTK_WIDGET(sheet));
+     label_requisition.width = COLUMN_MIN_WIDTH;
+  } 
+
+  if(button->child)
+  {
+     gtk_widget_size_request(button->child->widget, &requisition);
+     requisition.width += 2*button->child->xpadding;
+     requisition.height += 2*button->child->ypadding;
+     requisition.width += 2*sheet->button->style->xthickness;
+     requisition.height += 2*sheet->button->style->ythickness;
+  }
+  else
+  {
+     requisition.height = DEFAULT_ROW_HEIGHT(GTK_WIDGET(sheet));
+     requisition.width = COLUMN_MIN_WIDTH;
+  }
+
+  *button_requisition = requisition;
+  button_requisition->width = MAX(requisition.width, label_requisition.width);
+  button_requisition->height = MAX(requisition.height, label_requisition.height);
+}
+
+static void
+gtk_sheet_row_size_request      (GtkSheet *sheet,
+                                 gint row,
+                                 guint *requisition)
+{
+  GtkRequisition button_requisition;
+  GList *children;
+
+  gtk_sheet_button_size_request(sheet, 
+                               yyy_row_button(sheet, row),
+                               &button_requisition);
+
+  *requisition = button_requisition.height;
+
+  children = sheet->children;
+  while(children){
+    GtkSheetChild *child = (GtkSheetChild *)children->data;
+    GtkRequisition child_requisition;
+
+    if(child->attached_to_cell && child->row == row && child->col != -1 && !child->floating && !child->yshrink){
+      gtk_widget_get_child_requisition(child->widget, &child_requisition);
+
+      if(child_requisition.height + 2 * child->ypadding > *requisition)
+        *requisition = child_requisition.height + 2 * child->ypadding;
+    }
+    children = children->next;
+  }
+
+  sheet->row_requisition = * requisition;
+}
+
+static void
+gtk_sheet_column_size_request   (GtkSheet *sheet,
+                                 gint col,
+                                 guint *requisition)
+{
+  GtkRequisition button_requisition;
+  GList *children;
+
+  gtk_sheet_button_size_request(sheet, 
+                               xxx_column_button(sheet, col),
+                               &button_requisition);
+
+  *requisition = button_requisition.width;
+
+  children = sheet->children;
+  while(children){
+    GtkSheetChild *child = (GtkSheetChild *)children->data;
+    GtkRequisition child_requisition;
+
+    if(child->attached_to_cell && child->col == col && child->row != -1 && !child->floating && !child->xshrink){
+      gtk_widget_get_child_requisition(child->widget, &child_requisition);
+
+      if(child_requisition.width + 2 * child->xpadding > *requisition)
+        *requisition = child_requisition.width + 2 * child->xpadding;
+    }
+    children = children->next;
+  }
+
+  sheet->column_requisition = *requisition;
+}
+
+void
+gtk_sheet_move_child(GtkSheet *sheet, GtkWidget *widget, gint x, gint y)
+{
+  GtkSheetChild *child;
+  GList *children;
+
+  g_return_if_fail(sheet != NULL);
+  g_return_if_fail(GTK_IS_SHEET(sheet));
+
+  children = sheet->children;
+  while(children)
+    {
+       child = children->data;
+
+       if(child->widget == widget){
+         child->x = x;
+         child->y = y;
+         child->row = ROW_FROM_YPIXEL(sheet, y);
+        child->col = COLUMN_FROM_XPIXEL(sheet, x);
+         gtk_sheet_position_child(sheet, child);
+         return;
+       }
+
+       children = children->next;
+    }
+
+  g_warning("Widget must be a GtkSheet child"); 
+
+}
+
+static void
+gtk_sheet_position_child(GtkSheet *sheet, GtkSheetChild *child)
+{
+   GtkRequisition child_requisition;
+   GtkAllocation child_allocation;
+   gint xoffset = 0; 
+   gint yoffset = 0;
+   gint x = 0, y = 0;
+   GdkRectangle area;
+
+   gtk_widget_get_child_requisition(child->widget, &child_requisition);
+
+   if(sheet->column_titles_visible) 
+             yoffset = sheet->column_title_area.height;
+
+   if(sheet->row_titles_visible)
+             xoffset = sheet->row_title_area.width;
+
+   if(child->attached_to_cell){
+/*
+      child->x = COLUMN_LEFT_XPIXEL(sheet, child->col);
+      child->y = ROW_TOP_YPIXEL(sheet, child->row);
+
+      if(sheet->row_titles_visible) 
+                                    child->x-=sheet->row_title_area.width;
+      if(sheet->column_titles_visible) 
+                                    child->y-=sheet->column_title_area.height;
+
+      width = xxx_column_width(sheet, child->col);
+      height = yyy_row_height(sheet, child->row);
+*/
+      
+      gtk_sheet_get_cell_area(sheet, child->row, child->col, &area);
+      child->x = area.x + child->xpadding;
+      child->y = area.y + child->ypadding;
+
+      if(!child->floating){
+        if(child_requisition.width + 2*child->xpadding <= xxx_column_width(sheet, child->col)){
+          if(child->xfill){
+            child_requisition.width = child_allocation.width = xxx_column_width(sheet, child->col) - 2*child->xpadding;
+          } else {
+            if(child->xexpand){
+              child->x = area.x + xxx_column_width(sheet, child->col) / 2 -
+                                  child_requisition.width / 2;
+            }
+            child_allocation.width = child_requisition.width;
+          }
+        } else {
+          if(!child->xshrink){
+            gtk_sheet_set_column_width(sheet, child->col, child_requisition.width + 2 * child->xpadding);
+          }
+          child_allocation.width = xxx_column_width(sheet, child->col) - 2*child->xpadding;
+        }
+
+        if(child_requisition.height + 2*child->ypadding <= yyy_row_height(sheet, child->row)){
+          if(child->yfill){
+            child_requisition.height = child_allocation.height = yyy_row_height(sheet, child->row) - 2*child->ypadding;
+          } else {
+            if(child->yexpand){
+              child->y = area.y + yyy_row_height(sheet, child->row) / 2 -
+                                  child_requisition.height / 2;
+            }
+            child_allocation.height = child_requisition.height;
+          }
+        } else {
+          if(!child->yshrink){
+            gtk_sheet_set_row_height(sheet, child->row, child_requisition.height + 2 * child->ypadding);
+          }
+          child_allocation.height = yyy_row_height(sheet, child->row) - 2*child->ypadding;
+        }
+      } else {
+        child_allocation.width = child_requisition.width;
+        child_allocation.height = child_requisition.height;
+      }
+
+      x = child_allocation.x = child->x + xoffset;
+      y = child_allocation.y = child->y + yoffset;
+   }
+   else
+   {
+      x = child_allocation.x = child->x + sheet->hoffset + xoffset;   
+      x = child_allocation.x = child->x + xoffset;   
+      y = child_allocation.y = child->y + sheet->voffset + yoffset;
+      y = child_allocation.y = child->y + yoffset;
+      child_allocation.width = child_requisition.width;
+      child_allocation.height = child_requisition.height;
+   }
+
+   gtk_widget_size_allocate(child->widget, &child_allocation);
+   gtk_widget_queue_draw(child->widget);
+}
+
+static void
+gtk_sheet_forall (GtkContainer *container,
+                  gboolean      include_internals,
+                  GtkCallback   callback,
+                  gpointer      callback_data)
+{
+  GtkSheet *sheet;
+  GtkSheetChild *child;
+  GList *children;
+
+  g_return_if_fail (GTK_IS_SHEET (container));
+  g_return_if_fail (callback != NULL);
+
+  sheet = GTK_SHEET (container);
+  children = sheet->children;
+  while (children)
+    {
+      child = children->data;
+      children = children->next;
+
+      (* callback) (child->widget, callback_data);
+    }
+  if(sheet->button)
+     (* callback) (sheet->button, callback_data);
+  if(sheet->sheet_entry)
+     (* callback) (sheet->sheet_entry, callback_data);
+}
+
+
+static void
+gtk_sheet_position_children(GtkSheet *sheet)
+{
+  GList *children;
+  GtkSheetChild *child;
+
+  children = sheet->children;
+
+  while(children)
+   {
+     child = (GtkSheetChild *)children->data;
+
+     if(child->col !=-1 && child->row != -1)
+           gtk_sheet_position_child(sheet, child);
+
+     if(child->row == -1){
+        if(child->col < MIN_VISIBLE_COLUMN(sheet) || 
+           child->col > MAX_VISIBLE_COLUMN(sheet))
+              gtk_sheet_child_hide(child);
+        else
+              gtk_sheet_child_show(child);
+     }
+     if(child->col == -1){
+        if(child->row < MIN_VISIBLE_ROW(sheet) ||
+           child->row > MAX_VISIBLE_ROW(sheet))
+              gtk_sheet_child_hide(child);
+        else
+              gtk_sheet_child_show(child);
+     }
+     children = children->next;
+   }
+    
+}
+
+static void
+gtk_sheet_remove (GtkContainer *container, GtkWidget *widget)
+{
+  GtkSheet *sheet;
+  GList *children;
+  GtkSheetChild *child = 0;
+
+  g_return_if_fail(container != NULL);
+  g_return_if_fail(GTK_IS_SHEET(container));
+
+  sheet = GTK_SHEET(container);
+
+  children = sheet->children;
+
+  while(children)
+   {
+     child = (GtkSheetChild *)children->data;
+
+     if(child->widget == widget) break;
+
+     children = children->next;
+   }
+
+  if (children)
+   {
+     gtk_widget_unparent (widget);
+     child->widget = NULL;
+
+     sheet->children = g_list_remove_link (sheet->children, children);
+     g_list_free_1 (children);
+     g_free(child);
+   }
+
+}
+
+static void
+gtk_sheet_realize_child(GtkSheet *sheet, GtkSheetChild *child)
+{
+  GtkWidget *widget;
+
+  widget = GTK_WIDGET(sheet);
+
+  if(GTK_WIDGET_REALIZED(widget)){
+    if(child->row == -1)
+      gtk_widget_set_parent_window(child->widget, sheet->column_title_window);
+    else if(child->col == -1)
+      gtk_widget_set_parent_window(child->widget, sheet->row_title_window);
+    else
+      gtk_widget_set_parent_window(child->widget, sheet->sheet_window);
+  }
+
+  gtk_widget_set_parent(child->widget, widget);
+}
+
+
+   
+GtkSheetChild *
+gtk_sheet_get_child_at(GtkSheet *sheet, gint row, gint col)
+{
+  GList *children;
+  GtkSheetChild *child = 0;
+
+  g_return_val_if_fail(sheet != NULL, NULL);
+  g_return_val_if_fail(GTK_IS_SHEET(sheet), NULL);
+
+  children = sheet->children;
+
+  while(children)
+   {
+     child = (GtkSheetChild *)children->data;
+
+     if(child->attached_to_cell)
+        if(child->row == row && child->col == col) break; 
+     
+     children = children->next;
+   }
+
+  if(children) return child; 
+
+  return NULL;
+}
+
+static void
+gtk_sheet_child_hide(GtkSheetChild *child) 
+{
+  g_return_if_fail(child != NULL);
+  gtk_widget_hide(child->widget);
+}
+
+static void
+gtk_sheet_child_show(GtkSheetChild *child) 
+{
+  g_return_if_fail(child != NULL);
+
+  gtk_widget_show(child->widget);
+}
+
+GSheetModel *
+gtk_sheet_get_model(const GtkSheet *sheet)
+{
+  g_return_val_if_fail (GTK_IS_SHEET (sheet), NULL);
+
+  return sheet->model;
+}
+
diff --git a/lib/gtksheet/gtksheet.h b/lib/gtksheet/gtksheet.h
new file mode 100644 (file)
index 0000000..9f0a76d
--- /dev/null
@@ -0,0 +1,720 @@
+/* This version of GtkSheet has been heavily modified, for the specific 
+   requirements of PSPPIRE. */
+
+
+/* GtkSheet widget for Gtk+.
+ * Copyright (C) 1999-2001 Adrian E. Feiguin <adrian@ifir.ifir.edu.ar>
+ *
+ * Based on GtkClist widget by Jay Painter, but major changes.
+ * Memory allocation routines inspired on SC (Spreadsheet Calculator)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __GTK_SHEET_H__
+#define __GTK_SHEET_H__
+
+#include <gtk/gtk.h>
+
+#include "gtkextra-sheet.h"
+#include "gsheetmodel.h"
+#include "gsheet-column-iface.h"
+#include "gsheet-row-iface.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+typedef enum
+{
+  GTK_SHEET_FOREGROUND,
+  GTK_SHEET_BACKGROUND,
+  GTK_SHEET_FONT,
+  GTK_SHEET_JUSTIFICATION,
+  GTK_SHEET_BORDER,
+  GTK_SHEET_BORDER_COLOR,
+  GTK_SHEET_IS_EDITABLE,
+  GTK_SHEET_IS_VISIBLE
+} GtkSheetAttrType;
+
+/* sheet->state */
+
+enum 
+{
+  GTK_SHEET_NORMAL,
+  GTK_SHEET_ROW_SELECTED,
+  GTK_SHEET_COLUMN_SELECTED,
+  GTK_SHEET_RANGE_SELECTED
+};
+     
+
+#define GTK_TYPE_SHEET_RANGE (gtk_sheet_range_get_type ())
+#define GTK_TYPE_SHEET (gtk_sheet_get_type ())
+
+#define GTK_SHEET(obj)          GTK_CHECK_CAST (obj, gtk_sheet_get_type (), GtkSheet)
+#define GTK_SHEET_CLASS(klass)  GTK_CHECK_CLASS_CAST (klass, gtk_sheet_get_type (), GtkSheetClass)
+#define GTK_IS_SHEET(obj)       GTK_CHECK_TYPE (obj, gtk_sheet_get_type ())
+
+/* Public flags, for compatibility */
+
+#define GTK_SHEET_IS_LOCKED(sheet)       gtk_sheet_locked(sheet)
+#define GTK_SHEET_ROW_FROZEN(sheet)      !gtk_sheet_rows_resizable(sheet)
+#define GTK_SHEET_COLUMN_FROZEN(sheet)   !gtk_sheet_columns_resizable(sheet)
+#define GTK_SHEET_AUTORESIZE(sheet)      gtk_sheet_autoresize(sheet)
+#define GTK_SHEET_CLIP_TEXT(sheet)       gtk_sheet_clip_text(sheet)
+#define GTK_SHEET_ROW_TITLES_VISIBLE(sheet)   gtk_sheet_row_titles_visible(sheet)
+#define GTK_SHEET_COL_TITLES_VISIBLE(sheet)   gtk_sheet_column_titles_visible(sheet) 
+#define GTK_SHEET_AUTO_SCROLL(sheet)     gtk_sheet_autoscroll(sheet)
+#define GTK_SHEET_JUSTIFY_ENTRY(sheet)   gtk_sheet_justify_entry(sheet)
+
+
+typedef struct _GtkSheetClass GtkSheetClass;
+typedef struct _GtkSheetCellAttr     GtkSheetCellAttr;
+typedef struct _GtkSheetCell GtkSheetCell;
+
+
+struct _GtkSheetCellAttr
+{
+  GtkJustification justification;
+  const PangoFontDescription *font_desc;
+  GdkColor foreground;
+  GdkColor background;
+  GtkSheetCellBorder border;
+  gboolean is_editable;
+  gboolean is_visible;
+};
+
+struct _GtkSheetCell
+{
+  gint row;
+  gint col;
+};
+
+
+struct _GtkSheet{
+  GtkContainer container;
+
+  GSheetColumn *column_geometry;
+  GSheetRow *row_geometry;
+
+  guint16 flags;
+
+  GSheetModel *model;
+
+  GtkSelectionMode selection_mode;
+  gboolean autoresize;
+  gboolean autoscroll;
+  gboolean clip_text;
+  gboolean justify_entry;
+  gboolean locked;
+
+  guint freeze_count;
+
+  /* Background colors */
+  GdkColor bg_color;
+  GdkColor grid_color;
+  gboolean show_grid;
+
+  /* sheet children */
+  GList *children;
+
+  /* allocation rectangle after the container_border_width
+     and the width of the shadow border */
+  GdkRectangle internal_allocation;
+
+  gchar *name;
+
+  gint16 column_requisition;
+  gint16 row_requisition;
+
+  gboolean rows_resizable;
+  gboolean columns_resizable;
+
+  /* Displayed range */
+  GtkSheetRange view; 
+
+  /* active cell */
+  GtkSheetCell active_cell;
+  GtkWidget *sheet_entry;
+
+  GtkType entry_type;
+
+  /* expanding selection */
+  GtkSheetCell selection_cell;
+
+  /* timer for automatic scroll during selection */  
+  gint32 timer;
+  /* timer for flashing clipped range */
+  gint32 clip_timer;
+  gint interval;
+
+  /* global selection button */
+  GtkWidget *button;
+
+  /* sheet state */
+  gint state;
+
+  /* selected range */
+  GtkSheetRange range;
+
+  /*the scrolling window and it's height and width to
+   * make things a little speedier */
+  GdkWindow *sheet_window;
+  guint sheet_window_width;
+  guint sheet_window_height;
+
+  /* sheet backing pixmap */  
+  GdkWindow *pixmap;    
+
+  /* offsets for scrolling */
+  gint hoffset;
+  gint voffset;
+  gfloat old_hadjustment;
+  gfloat old_vadjustment;
+  
+  /* border shadow style */
+  GtkShadowType shadow_type;
+  
+  /* Column Titles */
+  GdkRectangle column_title_area;
+  GdkWindow *column_title_window;
+  gboolean column_titles_visible;
+
+  /* Row Titles */
+  GdkRectangle row_title_area;
+  GdkWindow *row_title_window;
+  gboolean row_titles_visible;
+
+  /*scrollbars*/
+  GtkAdjustment *hadjustment;
+  GtkAdjustment *vadjustment;
+
+  /* xor GC for the verticle drag line */
+  GdkGC *xor_gc;
+
+  /* gc for drawing unselected cells */
+  GdkGC *fg_gc;
+  GdkGC *bg_gc;
+
+  /* cursor used to indicate dragging */
+  GdkCursor *cursor_drag;
+
+  /* the current x-pixel location of the xor-drag vline */
+  gint x_drag;
+
+  /* the current y-pixel location of the xor-drag hline */
+  gint y_drag;
+
+  /* current cell being dragged */
+  GtkSheetCell drag_cell;
+  /* current range being dragged */
+  GtkSheetRange drag_range;
+
+  /* clipped range */
+  GtkSheetRange clip_range;
+};
+
+struct _GtkSheetClass
+{
+ GtkContainerClass parent_class;
+ void (*set_scroll_adjustments) (GtkSheet *sheet,
+                                GtkAdjustment *hadjustment,
+                                GtkAdjustment *vadjustment);
+
+ void (*select_row)            (GtkSheet *sheet, gint row);
+
+ void (*select_column)                 (GtkSheet *sheet, gint column);
+
+ void (*select_range)          (GtkSheet *sheet, GtkSheetRange *range);
+
+ void (*clip_range)            (GtkSheet *sheet, GtkSheetRange *clip_range);
+
+ void (*resize_range)          (GtkSheet *sheet,
+                               GtkSheetRange *old_range,
+                               GtkSheetRange *new_range);
+
+ void (*move_range)                    (GtkSheet *sheet,
+                               GtkSheetRange *old_range,
+                               GtkSheetRange *new_range);
+
+ gboolean (*traverse)          (GtkSheet *sheet,
+                               gint row, gint column,
+                               gint *new_row, gint *new_column);
+
+ gboolean (*deactivate)                (GtkSheet *sheet,
+                               gint row, gint column);
+
+ gboolean (*activate)          (GtkSheet *sheet,
+                               gint row, gint column);
+
+ void (*set_cell)              (GtkSheet *sheet,
+                               gint row, gint column);
+
+ void (*clear_cell)            (GtkSheet *sheet,
+                               gint row, gint column);
+
+ void (*changed)               (GtkSheet *sheet,
+                               gint row, gint column);
+
+ void (*new_column_width)       (GtkSheet *sheet,
+                                 gint col,
+                                 guint width);
+
+ void (*new_row_height)        (GtkSheet *sheet,
+                                 gint row,
+                                 guint height);
+
+};
+  
+GType gtk_sheet_get_type (void);
+GtkType gtk_sheet_range_get_type (void);
+
+
+/* create a new sheet */
+GtkWidget * gtk_sheet_new (GSheetRow *vgeo, GSheetColumn *hgeo,
+                          const gchar *title,
+                          GSheetModel *model);
+
+
+
+
+/* create a new browser sheet. It cells can not be edited */
+GtkWidget *
+gtk_sheet_new_browser                  (guint rows, guint columns, const gchar *title);
+
+void
+gtk_sheet_construct_browser            (GtkSheet *sheet,
+                                                guint rows, guint columns, const gchar *title);
+
+/* create a new sheet with custom entry */
+GtkWidget *
+gtk_sheet_new_with_custom_entry        (GSheetRow *vgeo,
+                                        GSheetColumn *hgeo,
+                                         const gchar *title,
+                                        GtkType entry_type);
+void
+gtk_sheet_construct_with_custom_entry  (GtkSheet *sheet,
+                                        GSheetRow *vgeo,
+                                        GSheetColumn *hgeo,
+                                         const gchar *title,
+                                        GtkType entry_type);
+/* change scroll adjustments */
+void
+gtk_sheet_set_hadjustment              (GtkSheet *sheet,
+                                        GtkAdjustment *adjustment); 
+void
+gtk_sheet_set_vadjustment              (GtkSheet *sheet,
+                                        GtkAdjustment *adjustment); 
+/* Change entry */
+void
+gtk_sheet_change_entry                 (GtkSheet *sheet, GtkType entry_type);
+
+/* Returns sheet's entry widget */
+GtkWidget *
+gtk_sheet_get_entry                    (GtkSheet *sheet);
+GtkWidget *
+gtk_sheet_get_entry_widget             (GtkSheet *sheet);
+
+/* Returns sheet->state 
+ * Added by Steven Rostedt <steven.rostedt@lmco.com> */
+gint
+gtk_sheet_get_state                    (GtkSheet *sheet);
+
+/* Returns sheet's ranges 
+ * Added by Murray Cumming */
+guint
+gtk_sheet_get_columns_count            (GtkSheet *sheet);
+
+guint
+gtk_sheet_get_rows_count               (GtkSheet *sheet);
+
+void
+gtk_sheet_get_visible_range            (GtkSheet *sheet,
+                                        GtkSheetRange *range);
+void
+gtk_sheet_set_selection_mode           (GtkSheet *sheet, gint mode);
+
+void
+gtk_sheet_set_autoresize               (GtkSheet *sheet, gboolean autoresize);
+
+gboolean
+gtk_sheet_autoresize                   (GtkSheet *sheet);
+
+void
+gtk_sheet_set_autoscroll               (GtkSheet *sheet, gboolean autoscroll);
+
+gboolean
+gtk_sheet_autoscroll                   (GtkSheet *sheet);
+
+void
+gtk_sheet_set_clip_text                        (GtkSheet *sheet, gboolean clip_text);
+
+gboolean
+gtk_sheet_clip_text                    (GtkSheet *sheet);
+
+void
+gtk_sheet_set_justify_entry            (GtkSheet *sheet, gboolean justify);
+
+gboolean
+gtk_sheet_justify_entry                        (GtkSheet *sheet);
+
+void
+gtk_sheet_set_locked                   (GtkSheet *sheet, gboolean lock);
+
+gboolean
+gtk_sheet_locked                       (GtkSheet *sheet);
+
+/* set sheet title */
+void
+gtk_sheet_set_title                    (GtkSheet *sheet, const gchar *title);
+
+/* freeze all visual updates of the sheet.
+ * Then thaw the sheet after you have made a number of changes.
+ * The updates will occure in a more efficent way than if
+ * you made them on a unfrozen sheet */
+void
+gtk_sheet_freeze                       (GtkSheet *sheet);
+void
+gtk_sheet_thaw                         (GtkSheet *sheet);
+/* Background colors */
+void
+gtk_sheet_set_background               (GtkSheet *sheet,
+                                        GdkColor *bg_color);
+void
+gtk_sheet_set_grid                     (GtkSheet *sheet,
+                                        GdkColor *grid_color);
+void
+gtk_sheet_show_grid                    (GtkSheet *sheet,
+                                        gboolean show);
+gboolean
+gtk_sheet_grid_visible                 (GtkSheet *sheet);
+
+/* set/get column title */ 
+void
+gtk_sheet_set_column_title             (GtkSheet * sheet,
+                                       gint column,
+                                       const gchar * title);
+
+const gchar *
+gtk_sheet_get_column_title             (GtkSheet * sheet,
+                                       gint column);
+
+/* set/get row title */
+void
+gtk_sheet_set_row_title                (GtkSheet * sheet,
+                                       gint row,
+                                       const gchar * title);
+const gchar *
+gtk_sheet_get_row_title                (GtkSheet * sheet,
+                                       gint row);
+
+
+/* set/get button label */
+void
+gtk_sheet_row_button_add_label         (GtkSheet *sheet, 
+                                       gint row, const gchar *label);
+const gchar *
+gtk_sheet_row_button_get_label         (GtkSheet *sheet, 
+                                       gint row);
+void
+gtk_sheet_row_button_justify           (GtkSheet *sheet, 
+                                       gint row, GtkJustification justification);
+
+
+
+/* scroll the viewing area of the sheet to the given column
+ * and row; row_align and col_align are between 0-1 representing the
+ * location the row should appear on the screnn, 0.0 being top or left,
+ * 1.0 being bottom or right; if row or column is negative then there
+ * is no change */
+void
+gtk_sheet_moveto (GtkSheet * sheet,
+                 gint row,
+                 gint column,
+                 gfloat row_align,
+                  gfloat col_align);
+
+
+void
+gtk_sheet_show_row_titles              (GtkSheet *sheet);
+void
+gtk_sheet_hide_row_titles              (GtkSheet *sheet);
+gboolean
+gtk_sheet_row_titles_visible           (GtkSheet *sheet);
+
+
+/* set row button sensitivity. If sensitivity is TRUE can be toggled, 
+ * otherwise it acts as a title */
+void 
+gtk_sheet_row_set_sensitivity          (GtkSheet *sheet, 
+                                       gint row,  gboolean sensitive);
+
+/* set sensitivity for all row buttons */
+void
+gtk_sheet_rows_set_sensitivity         (GtkSheet *sheet, gboolean sensitive);
+void
+gtk_sheet_rows_set_resizable           (GtkSheet *sheet, gboolean resizable);
+gboolean
+gtk_sheet_rows_resizable               (GtkSheet *sheet);
+
+/* set row visibility. The default value is TRUE. If FALSE, the 
+ * row is hidden */
+void
+gtk_sheet_row_set_visibility           (GtkSheet *sheet, 
+                                        gint row, gboolean visible);
+void
+gtk_sheet_row_label_set_visibility     (GtkSheet *sheet, 
+                                        gint row, gboolean visible);
+void
+gtk_sheet_rows_labels_set_visibility   (GtkSheet *sheet, gboolean visible);
+
+
+/* select the row. The range is then highlighted, and the bounds are stored
+ * in sheet->range  */
+void
+gtk_sheet_select_row                   (GtkSheet * sheet,
+                                       gint row);
+
+/* select the column. The range is then highlighted, and the bounds are stored
+ * in sheet->range  */
+void
+gtk_sheet_select_column                (GtkSheet * sheet,
+                                       gint column);
+
+/* save selected range to "clipboard" */
+void
+gtk_sheet_clip_range                   (GtkSheet *sheet, const GtkSheetRange *range);
+/* free clipboard */
+void
+gtk_sheet_unclip_range                 (GtkSheet *sheet);
+
+gboolean
+gtk_sheet_in_clip                      (GtkSheet *sheet);
+
+/* get scrollbars adjustment */
+GtkAdjustment *
+gtk_sheet_get_vadjustment              (GtkSheet * sheet);
+GtkAdjustment *
+gtk_sheet_get_hadjustment              (GtkSheet * sheet);
+
+/* highlight the selected range and store bounds in sheet->range */
+void gtk_sheet_select_range            (GtkSheet *sheet, 
+                                        const GtkSheetRange *range); 
+
+/* obvious */
+void gtk_sheet_unselect_range          (GtkSheet *sheet); 
+
+/* set active cell where the entry will be displayed 
+ * returns FALSE if current cell can't be deactivated or
+ * requested cell can't be activated */
+gboolean
+gtk_sheet_set_active_cell              (GtkSheet *sheet, 
+                                       gint row, gint column);
+void
+gtk_sheet_get_active_cell              (GtkSheet *sheet, 
+                                       gint *row, gint *column);
+
+/* set cell contents and allocate memory if needed */
+void 
+gtk_sheet_set_cell                     (GtkSheet *sheet, 
+                                       gint row, gint col, 
+                                        GtkJustification justification,
+                                       const gchar *text);
+void 
+gtk_sheet_set_cell_text                        (GtkSheet *sheet, 
+                                       gint row, gint col,
+                                       const gchar *text);
+/* get cell contents */
+const gchar *     
+gtk_sheet_cell_get_text                (GtkSheet *sheet, gint row, gint col);
+
+/* clear cell contents */
+void 
+gtk_sheet_cell_clear                   (GtkSheet *sheet, gint row, gint col);
+/* clear cell contents and remove links */
+void 
+gtk_sheet_cell_delete                  (GtkSheet *sheet, gint row, gint col);
+
+/* clear range contents. If range==NULL the whole sheet will be cleared */
+void 
+gtk_sheet_range_clear                  (GtkSheet *sheet, 
+                                        const GtkSheetRange *range);
+/* clear range contents and remove links */
+void 
+gtk_sheet_range_delete                 (GtkSheet *sheet, 
+                                        const GtkSheetRange *range);
+
+/* get cell state: GTK_STATE_NORMAL, GTK_STATE_SELECTED */
+GtkStateType
+gtk_sheet_cell_get_state               (GtkSheet *sheet, gint row, gint col);
+
+/* get row and column correspondig to the given position in the screen */
+gboolean
+gtk_sheet_get_pixel_info (GtkSheet * sheet,
+                         gint x,
+                         gint y,
+                         gint * row,
+                         gint * column);
+
+/* get area of a given cell */
+gboolean
+gtk_sheet_get_cell_area (GtkSheet *sheet,
+                         gint row,
+                         gint column,
+                         GdkRectangle *area);
+
+/* set row height */
+void
+gtk_sheet_set_row_height (GtkSheet * sheet,
+                         gint row,
+                         guint height);
+
+
+/* delete nrows rows starting in row */
+void
+gtk_sheet_delete_rows                  (GtkSheet *sheet, guint row, guint nrows);
+
+/* append nrows row to the end of the sheet */
+void
+gtk_sheet_add_row                      (GtkSheet *sheet, guint nrows);
+
+/* insert nrows rows before the given row and pull right */
+void
+gtk_sheet_insert_rows                  (GtkSheet *sheet, guint row, guint nrows);
+
+/* set abckground color of the given range */
+void
+gtk_sheet_range_set_background         (GtkSheet *sheet, 
+                                       const GtkSheetRange *range, 
+                                       const GdkColor *color);
+
+/* set foreground color (text color) of the given range */
+void
+gtk_sheet_range_set_foreground         (GtkSheet *sheet, 
+                                       const GtkSheetRange *range, 
+                                       const GdkColor *color);
+
+/* set text justification (GTK_JUSTIFY_LEFT, RIGHT, CENTER) of the given range.
+ * The default value is GTK_JUSTIFY_LEFT. If autoformat is on, the
+ * default justification for numbers is GTK_JUSTIFY_RIGHT */
+void
+gtk_sheet_range_set_justification      (GtkSheet *sheet, 
+                                       const GtkSheetRange *range, 
+                                       GtkJustification justification);
+void
+gtk_sheet_column_set_justification      (GtkSheet *sheet,
+                                        gint column,
+                                        GtkJustification justification);
+/* set if cell contents can be edited or not in the given range:
+ * accepted values are TRUE or FALSE. */
+void
+gtk_sheet_range_set_editable           (GtkSheet *sheet, 
+                                       const GtkSheetRange *range, 
+                                       gint editable);
+
+/* set if cell contents are visible or not in the given range:
+ * accepted values are TRUE or FALSE.*/
+void
+gtk_sheet_range_set_visible            (GtkSheet *sheet, 
+                                       const GtkSheetRange *range, 
+                                       gboolean visible);
+
+/* set cell border style in the given range.
+ * mask values are CELL_LEFT_BORDER, CELL_RIGHT_BORDER, CELL_TOP_BORDER,
+ * CELL_BOTTOM_BORDER
+ * width is the width of the border line in pixels 
+ * line_style is the line_style for the border line */
+void
+gtk_sheet_range_set_border             (GtkSheet *sheet, 
+                                       const GtkSheetRange *range, 
+                                       gint mask, 
+                                       guint width, 
+                                       gint line_style);
+
+/* set border color for the given range */
+void
+gtk_sheet_range_set_border_color       (GtkSheet *sheet, 
+                                       const GtkSheetRange *range, 
+                                       const GdkColor *color);
+
+/* set font for the given range */
+void
+gtk_sheet_range_set_font               (GtkSheet *sheet, 
+                                       const GtkSheetRange *range, 
+                                       PangoFontDescription *font);
+
+/* get cell attributes of the given cell */
+/* TRUE means that the cell is currently allocated */
+gboolean
+gtk_sheet_get_attributes               (GtkSheet *sheet, 
+                                       gint row, gint col, 
+                                       GtkSheetCellAttr *attributes);
+
+
+GtkSheetChild *
+gtk_sheet_put                          (GtkSheet *sheet, 
+                                        GtkWidget *widget, 
+                                        gint x, gint y);
+void
+gtk_sheet_attach_floating               (GtkSheet *sheet,
+                                         GtkWidget *widget,
+                                         gint row, gint col);
+void
+gtk_sheet_attach_default                (GtkSheet *sheet,
+                                         GtkWidget *widget,
+                                         gint row, gint col);
+void
+gtk_sheet_attach                        (GtkSheet *sheet,
+                                         GtkWidget *widget,
+                                         gint row, gint col,
+                                         gint xoptions,
+                                         gint yoptions,
+                                         gint xpadding,
+                                         gint ypadding);
+
+
+void
+gtk_sheet_move_child                   (GtkSheet *sheet, 
+                                        GtkWidget *widget, 
+                                        gint x, gint y);
+
+GtkSheetChild *
+gtk_sheet_get_child_at                 (GtkSheet *sheet, 
+                                        gint row, gint col);
+
+void
+gtk_sheet_button_attach                        (GtkSheet *sheet,
+                                        GtkWidget *widget,
+                                        gint row, gint col);
+                       
+
+
+void           gtk_sheet_set_model(GtkSheet *sheet, 
+                                  GSheetModel *model);
+
+GSheetModel * gtk_sheet_get_model(const GtkSheet *sheet);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __GTK_SHEET_H__ */
+
+
diff --git a/src/data/ChangeLog b/src/data/ChangeLog
new file mode 100644 (file)
index 0000000..c80710c
--- /dev/null
@@ -0,0 +1,5 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+
+       * Numerous renames.  See src/ChangeLog for details.
+       
+       * Moved files from src directory
diff --git a/src/data/any-reader.c b/src/data/any-reader.c
new file mode 100644 (file)
index 0000000..876d7fa
--- /dev/null
@@ -0,0 +1,207 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "any-reader.h"
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "message.h"
+#include "file-handle-def.h"
+#include "filename.h"
+#include "por-file-reader.h"
+#include "sys-file-reader.h"
+#include "str.h"
+#include "scratch-reader.h"
+#include "xalloc.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* Type of file backing an any_reader. */
+enum any_reader_type
+  {
+    SYSTEM_FILE,                /* System file. */
+    PORTABLE_FILE,              /* Portable file. */
+    SCRATCH_FILE                /* Scratch file. */
+  };
+
+/* Reader for any type of case-structured file. */
+struct any_reader 
+  {
+    enum any_reader_type type;  /* Type of file. */
+    void *private;              /* Private data. */
+  };
+
+/* Result of type detection. */
+enum detect_result 
+  {
+    YES,                        /* It is this type. */
+    NO,                         /* It is not this type. */
+    IO_ERROR                    /* File couldn't be opened. */
+  };
+
+/* Tries to detect whether HANDLE represents a given type of
+   file, by opening the file and passing it to DETECT, and
+   returns a detect_result. */
+static enum detect_result
+try_detect (struct file_handle *handle, bool (*detect) (FILE *))
+{
+  FILE *file;
+  bool is_type;
+
+  file = fn_open (fh_get_filename (handle), "rb");
+  if (file == NULL)
+    {
+      msg (ME, _("An error occurred while opening \"%s\": %s."),
+           fh_get_filename (handle), strerror (errno));
+      return IO_ERROR;
+    }
+    
+  is_type = detect (file);
+  
+  fn_close (fh_get_filename (handle), file);
+
+  return is_type ? YES : NO;
+}
+
+/* If PRIVATE is non-null, creates and returns a new any_reader,
+   initializing its fields to TYPE and PRIVATE.  If PRIVATE is a
+   null pointer, just returns a null pointer. */   
+static struct any_reader *
+make_any_reader (enum any_reader_type type, void *private) 
+{
+  if (private != NULL) 
+    {
+      struct any_reader *reader = xmalloc (sizeof *reader);
+      reader->type = type;
+      reader->private = private;
+      return reader;
+    }
+  else
+    return NULL;
+}
+
+/* Creates an any_reader for HANDLE.  On success, returns the new
+   any_reader and stores the file's dictionary into *DICT.  On
+   failure, returns a null pointer. */
+struct any_reader *
+any_reader_open (struct file_handle *handle, struct dictionary **dict)
+{
+  switch (fh_get_referent (handle)) 
+    {
+    case FH_REF_FILE:
+      {
+        enum detect_result result;
+
+        result = try_detect (handle, sfm_detect);
+        if (result == IO_ERROR)
+          return NULL;
+        else if (result == YES)
+          return make_any_reader (SYSTEM_FILE,
+                                  sfm_open_reader (handle, dict, NULL));
+
+        result = try_detect (handle, pfm_detect);
+        if (result == IO_ERROR)
+          return NULL;
+        else if (result == YES)
+          return make_any_reader (PORTABLE_FILE,
+                                  pfm_open_reader (handle, dict, NULL));
+
+        msg (SE, _("\"%s\" is not a system or portable file."),
+             fh_get_filename (handle));
+        return NULL;
+      }
+
+    case FH_REF_INLINE:
+      msg (SE, _("The inline file is not allowed here."));
+      return NULL;
+
+    case FH_REF_SCRATCH:
+      return make_any_reader (SCRATCH_FILE,
+                              scratch_reader_open (handle, dict));
+    }
+  abort ();
+}
+
+/* Reads a single case from READER into C.
+   Returns true if successful, false at end of file or on error. */
+bool
+any_reader_read (struct any_reader *reader, struct ccase *c) 
+{
+  switch (reader->type) 
+    {
+    case SYSTEM_FILE:
+      return sfm_read_case (reader->private, c);
+
+    case PORTABLE_FILE:
+      return pfm_read_case (reader->private, c);
+
+    case SCRATCH_FILE:
+      return scratch_reader_read_case (reader->private, c);
+    }
+  abort ();
+}
+
+/* Returns true if an I/O error has occurred on READER, false
+   otherwise. */
+bool
+any_reader_error (struct any_reader *reader) 
+{
+  switch (reader->type) 
+    {
+    case SYSTEM_FILE:
+      return sfm_read_error (reader->private);
+
+    case PORTABLE_FILE:
+      return pfm_read_error (reader->private);
+
+    case SCRATCH_FILE:
+      return scratch_reader_error (reader->private);
+    }
+  abort ();
+}
+
+/* Closes READER. */
+void
+any_reader_close (struct any_reader *reader) 
+{
+  if (reader == NULL)
+    return;
+
+  switch (reader->type) 
+    {
+    case SYSTEM_FILE:
+      sfm_close_reader (reader->private);
+      break;
+
+    case PORTABLE_FILE:
+      pfm_close_reader (reader->private);
+      break;
+
+    case SCRATCH_FILE:
+      scratch_reader_close (reader->private);
+      break;
+
+    default:
+      abort ();
+    }
+}
diff --git a/src/data/any-reader.h b/src/data/any-reader.h
new file mode 100644 (file)
index 0000000..5ba72fc
--- /dev/null
@@ -0,0 +1,34 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef ANY_READER_H
+#define ANY_READER_H 1
+
+#include <stdbool.h>
+
+struct file_handle;
+struct dictionary;
+struct ccase;
+struct any_reader *any_reader_open (struct file_handle *,
+                                    struct dictionary **);
+bool any_reader_read (struct any_reader *, struct ccase *);
+bool any_reader_error (struct any_reader *);
+void any_reader_close (struct any_reader *);
+
+#endif /* any-reader.h */
diff --git a/src/data/any-writer.c b/src/data/any-writer.c
new file mode 100644 (file)
index 0000000..93d2817
--- /dev/null
@@ -0,0 +1,209 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "any-writer.h"
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "message.h"
+#include "file-handle-def.h"
+#include "filename.h"
+#include "por-file-writer.h"
+#include "sys-file-writer.h"
+#include "str.h"
+#include "scratch-writer.h"
+#include "xalloc.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* Type of file backing an any_writer. */
+enum any_writer_type
+  {
+    SYSTEM_FILE,                /* System file. */
+    PORTABLE_FILE,              /* Portable file. */
+    SCRATCH_FILE                /* Scratch file. */
+  };
+
+/* Writer for any type of case-structured file. */
+struct any_writer 
+  {
+    enum any_writer_type type;  /* Type of file. */
+    void *private;              /* Private data. */
+  };
+
+/* Creates and returns a writer for HANDLE with the given DICT. */
+struct any_writer *
+any_writer_open (struct file_handle *handle, struct dictionary *dict)
+{
+  switch (fh_get_referent (handle)) 
+    {
+    case FH_REF_FILE:
+      {
+        struct any_writer *writer;
+        char *extension;
+
+        extension = fn_extension (fh_get_filename (handle));
+        str_lowercase (extension);
+
+        if (!strcmp (extension, ".por"))
+          writer = any_writer_from_pfm_writer (
+            pfm_open_writer (handle, dict, pfm_writer_default_options ()));
+        else
+          writer = any_writer_from_sfm_writer (
+            sfm_open_writer (handle, dict, sfm_writer_default_options ()));
+        free (extension);
+
+        return writer;
+      }
+
+    case FH_REF_INLINE:
+      msg (ME, _("The inline file is not allowed here."));
+      return NULL;
+
+    case FH_REF_SCRATCH:
+      return any_writer_from_scratch_writer (scratch_writer_open (handle,
+                                                                  dict));
+    }
+
+  abort ();
+}
+
+/* If PRIVATE is non-null, creates and returns a new any_writer,
+   initializing its fields to TYPE and PRIVATE.  If PRIVATE is a
+   null pointer, just returns a null pointer. */   
+static struct any_writer *
+make_any_writer (enum any_writer_type type, void *private) 
+{
+  if (private != NULL) 
+    {
+      struct any_writer *writer = xmalloc (sizeof *writer);
+      writer->type = type;
+      writer->private = private;
+      return writer; 
+    }
+  else
+    return NULL;
+}
+  
+/* If SFM_WRITER is non-null, encapsulates SFM_WRITER in an
+   any_writer and returns it.  If SFM_WRITER is null, just
+   returns a null pointer.
+
+   Useful when you need to pass options to sfm_open_writer().
+   Typical usage:
+        any_writer_from_sfm_writer (sfm_open_writer (fh, dict, opts))
+   If you don't need to pass options, then any_writer_open() by
+   itself is easier and more straightforward. */
+struct any_writer *
+any_writer_from_sfm_writer (struct sfm_writer *sfm_writer) 
+{
+  return make_any_writer (SYSTEM_FILE, sfm_writer);
+}
+
+/* If PFM_WRITER is non-null, encapsulates PFM_WRITER in an
+   any_writer and returns it.  If PFM_WRITER is null, just
+   returns a null pointer.
+
+   Useful when you need to pass options to pfm_open_writer().
+   Typical usage:
+        any_writer_from_pfm_writer (pfm_open_writer (fh, dict, opts))
+   If you don't need to pass options, then any_writer_open() by
+   itself is easier and more straightforward. */
+struct any_writer *
+any_writer_from_pfm_writer (struct pfm_writer *pfm_writer) 
+{
+  return make_any_writer (PORTABLE_FILE, pfm_writer);
+}
+
+/* If SCRATCH_WRITER is non-null, encapsulates SCRATCH_WRITER in
+   an any_writer and returns it.  If SCRATCH_WRITER is null, just
+   returns a null pointer.
+
+   Not particularly useful.  Included just for consistency. */
+struct any_writer *
+any_writer_from_scratch_writer (struct scratch_writer *scratch_writer) 
+{
+  return make_any_writer (SCRATCH_FILE, scratch_writer);
+}
+
+/* Writes cases C to WRITER.
+   Returns true if successful, false on failure. */
+bool
+any_writer_write (struct any_writer *writer, const struct ccase *c) 
+{
+  switch (writer->type) 
+    {
+    case SYSTEM_FILE:
+      return sfm_write_case (writer->private, c);
+
+    case PORTABLE_FILE:
+      return pfm_write_case (writer->private, c);
+
+    case SCRATCH_FILE:
+      return scratch_writer_write_case (writer->private, c);
+    }
+  abort ();
+}
+
+/* Returns true if an I/O error has occurred on WRITER, false
+   otherwise. */
+bool
+any_writer_error (const struct any_writer *writer) 
+{
+  switch (writer->type) 
+    {
+    case SYSTEM_FILE:
+      return sfm_write_error (writer->private);
+
+    case PORTABLE_FILE:
+      return pfm_write_error (writer->private);
+
+    case SCRATCH_FILE:
+      return scratch_writer_error (writer->private);
+    }
+  abort ();
+}
+
+/* Closes WRITER.
+   Returns true if successful, false if an I/O error occurred. */
+bool
+any_writer_close (struct any_writer *writer) 
+{
+  if (writer == NULL)
+    return true;
+
+  switch (writer->type) 
+    {
+    case SYSTEM_FILE:
+      return sfm_close_writer (writer->private);
+
+    case PORTABLE_FILE:
+      return pfm_close_writer (writer->private);
+
+    case SCRATCH_FILE:
+      return scratch_writer_close (writer->private);
+
+    default:
+      abort ();
+    }
+}
diff --git a/src/data/any-writer.h b/src/data/any-writer.h
new file mode 100644 (file)
index 0000000..3b20b65
--- /dev/null
@@ -0,0 +1,41 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef ANY_WRITER_H
+#define ANY_WRITER_H 1
+
+#include <stdbool.h>
+
+struct file_handle;
+struct dictionary;
+struct ccase;
+struct sfm_writer;
+struct pfm_writer;
+struct scratch_writer;
+
+struct any_writer *any_writer_open (struct file_handle *, struct dictionary *);
+struct any_writer *any_writer_from_sfm_writer (struct sfm_writer *);
+struct any_writer *any_writer_from_pfm_writer (struct pfm_writer *);
+struct any_writer *any_writer_from_scratch_writer (struct scratch_writer *);
+
+bool any_writer_write (struct any_writer *, const struct ccase *);
+bool any_writer_error (const struct any_writer *);
+bool any_writer_close (struct any_writer *);
+
+#endif /* any-writer.h */
diff --git a/src/data/calendar.c b/src/data/calendar.c
new file mode 100644 (file)
index 0000000..0a6498d
--- /dev/null
@@ -0,0 +1,211 @@
+#include <config.h>
+#include "calendar.h"
+#include <assert.h>
+#include <stdbool.h>
+#include "settings.h"
+#include "value.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* 14 Oct 1582. */
+#define EPOCH (-577734)
+
+/* Calculates and returns floor(a/b) for integer b > 0. */
+static int
+floor_div (int a, int b) 
+{
+  assert (b > 0);
+  return (a >= 0 ? a : a - b + 1) / b;
+}
+
+/* Calculates floor(a/b) and the corresponding remainder and
+   stores them into *Q and *R. */
+static void
+floor_divmod (int a, int b, int *q, int *r) 
+{
+  *q = floor_div (a, b);
+  *r = a - b * *q;
+}
+
+/* Returns true if Y is a leap year, false otherwise. */
+static bool
+is_leap_year (int y) 
+{
+  return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
+}
+
+static int
+raw_gregorian_to_offset (int y, int m, int d) 
+{
+  return (EPOCH - 1
+          + 365 * (y - 1)
+          + floor_div (y - 1, 4)
+          - floor_div (y - 1, 100)
+          + floor_div (y - 1, 400)
+          + floor_div (367 * m - 362, 12)
+          + (m <= 2 ? 0 : (m >= 2 && is_leap_year (y) ? -1 : -2))
+          + d);
+}
+
+/* Returns the number of days from 14 Oct 1582 to (Y,M,D) in the
+   Gregorian calendar.  Returns SYSMIS for dates before 14 Oct
+   1582. */
+double
+calendar_gregorian_to_offset (int y, int m, int d,
+                              calendar_error_func *error, void *aux)
+{
+  /* Normalize year. */
+  if (y >= 0 && y < 100) 
+    {
+      int epoch = get_epoch ();
+      int century = epoch / 100 + (y < epoch % 100);
+      y += century * 100;
+    }
+
+  /* Normalize month. */
+  if (m < 1 || m > 12) 
+    {
+      if (m == 0) 
+        {
+          y--;
+          m = 12;
+        }
+      else if (m == 13) 
+        {
+          y++;
+          m = 1;
+        }
+      else
+        {
+          error (aux, _("Month %d is not in acceptable range of 0 to 13."), m);
+          return SYSMIS;
+        }
+    }
+
+  /* Normalize day. */
+  if (d < 0 || d > 31) 
+    {
+      error (aux, _("Day %d is not in acceptable range of 0 to 31."), d);
+      return SYSMIS;
+    }
+
+  /* Validate date. */
+  if (y < 1582 || (y == 1582 && (m < 10 || (m == 10 && d < 15)))) 
+    {
+      error (aux, _("Date %04d-%d-%d is before the earliest acceptable "
+                    "date of 1582-10-15."), y, m, d);
+      return SYSMIS;
+    }
+
+  /* Calculate offset. */
+  return raw_gregorian_to_offset (y, m, d);
+}
+
+/* Returns the number of days in the given YEAR from January 1 up
+   to (but not including) the first day of MONTH. */
+static int
+cum_month_days (int year, int month) 
+{
+  static const int cum_month_days[12] = 
+    {
+      0,
+      31, /* Jan */
+      31 + 28, /* Feb */
+      31 + 28 + 31, /* Mar */
+      31 + 28 + 31 + 30, /* Apr */
+      31 + 28 + 31 + 30 + 31, /* May */
+      31 + 28 + 31 + 30 + 31 + 30, /* Jun */
+      31 + 28 + 31 + 30 + 31 + 30 + 31, /* Jul */
+      31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, /* Aug */
+      31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, /* Sep */
+      31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, /* Oct */
+      31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, /* Nov */
+    };
+
+  assert (month >= 1 && month <= 12);
+  return cum_month_days[month - 1] + (month >= 3 && is_leap_year (year));
+}
+
+/* Takes a count of days from 14 Oct 1582 and returns the
+   Gregorian calendar year it is in.  Dates both before and after
+   the epoch are supported. */
+int
+calendar_offset_to_year (int ofs) 
+{
+  int d0;
+  int n400, d1;
+  int n100, d2;
+  int n4, d3;
+  int n1;
+  int y;
+
+  d0 = ofs - EPOCH;
+  floor_divmod (d0, 365 * 400 + 100 - 3, &n400, &d1);
+  floor_divmod (d1, 365 * 100 + 25 - 1, &n100, &d2);
+  floor_divmod (d2, 365 * 4 + 1, &n4, &d3);
+  n1 = floor_div (d3, 365);
+  y = 400 * n400 + 100 * n100 + 4 * n4 + n1;
+  if (n100 != 4 && n1 != 4)
+    y++;
+
+  return y;
+}
+
+/* Takes a count of days from 14 Oct 1582 and translates it into
+   a Gregorian calendar date in (*Y,*M,*D).  Dates both before
+   and after the epoch are supported. */
+void
+calendar_offset_to_gregorian (int ofs, int *y, int *m, int *d)
+{
+  int year = *y = calendar_offset_to_year (ofs);
+  int january1 = raw_gregorian_to_offset (year, 1, 1);
+  int yday = ofs - january1 + 1;
+  int march1 = january1 + cum_month_days (year, 3);
+  int correction = ofs < march1 ? 0 : (is_leap_year (year) ? 1 : 2);
+  int month = *m = (12 * (yday - 1 + correction) + 373) / 367;
+  *d = yday - cum_month_days (year, month);
+}
+
+/* Takes a count of days from 14 Oct 1582 and returns the 1-based
+   year-relative day number, that is, the number of days from the
+   beginning of the year. */
+int
+calendar_offset_to_yday (int ofs)
+{
+  int year = calendar_offset_to_year (ofs);
+  int january1 = raw_gregorian_to_offset (year, 1, 1);
+  int yday = ofs - january1 + 1;
+  return yday;
+}
+
+/* Takes a count of days from 14 Oct 1582 and returns the
+   corresponding weekday 1...7, with 1=Sunday. */
+int
+calendar_offset_to_wday (int ofs)
+{
+  int wday = (ofs - EPOCH + 1) % 7 + 1;
+  if (wday <= 0)
+    wday += 7;
+  return wday;
+}
+
+/* Takes a count of days from 14 Oct 1582 and returns the month
+   it is in. */
+int
+calendar_offset_to_month (int ofs) 
+{
+  int y, m, d;
+  calendar_offset_to_gregorian (ofs, &y, &m, &d);
+  return m;
+}
+
+/* Takes a count of days from 14 Oct 1582 and returns the
+   corresponding day of the month. */
+int
+calendar_offset_to_mday (int ofs) 
+{
+  int y, m, d;
+  calendar_offset_to_gregorian (ofs, &y, &m, &d);
+  return d;
+}
diff --git a/src/data/calendar.h b/src/data/calendar.h
new file mode 100644 (file)
index 0000000..1a70592
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef CALENDAR_H
+#define CALENDAR_H 1
+
+typedef void calendar_error_func (void *aux, const char *, ...);
+
+double calendar_gregorian_to_offset (int y, int m, int d,
+                                     calendar_error_func *, void *aux);
+void calendar_offset_to_gregorian (int ofs, int *y, int *m, int *d);
+int calendar_offset_to_year (int ofs);
+int calendar_offset_to_month (int ofs);
+int calendar_offset_to_mday (int ofs);
+int calendar_offset_to_yday (int ofs);
+int calendar_offset_to_wday (int ofs);
+
+#endif /* calendar.h */
diff --git a/src/data/case.c b/src/data/case.c
new file mode 100644 (file)
index 0000000..6fad874
--- /dev/null
@@ -0,0 +1,431 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "case.h"
+#include <limits.h>
+#include <stdlib.h>
+#include "value.h"
+#include "alloc.h"
+#include "str.h"
+#include "variable.h"
+
+#ifdef GLOBAL_DEBUGGING
+#undef NDEBUG
+#else
+#ifndef NDEBUG
+#define NDEBUG
+#endif
+#endif
+#include <assert.h>
+
+/* Changes C not to share data with any other case.
+   C must be a case with a reference count greater than 1.
+   There should be no reason for external code to call this
+   function explicitly.  It will be called automatically when
+   needed. */
+void
+case_unshare (struct ccase *c) 
+{
+  struct case_data *cd;
+  
+  assert (c != NULL);
+  assert (c->this == c);
+  assert (c->case_data != NULL);
+  assert (c->case_data->ref_cnt > 1);
+
+  cd = c->case_data;
+  cd->ref_cnt--;
+  case_create (c, c->case_data->value_cnt);
+  memcpy (c->case_data->values, cd->values,
+          sizeof *cd->values * cd->value_cnt); 
+}
+
+/* Returns the number of bytes needed by a case with VALUE_CNT
+   values. */
+static inline size_t
+case_size (size_t value_cnt) 
+{
+  return (offsetof (struct case_data, values)
+          + value_cnt * sizeof (union value));
+}
+
+#ifdef GLOBAL_DEBUGGING
+/* Initializes C as a null case. */
+void
+case_nullify (struct ccase *c) 
+{
+  c->case_data = NULL;
+  c->this = c;
+}
+#endif /* GLOBAL_DEBUGGING */
+
+#ifdef GLOBAL_DEBUGGING
+/* Returns true iff C is a null case. */
+int
+case_is_null (const struct ccase *c) 
+{
+  return c->case_data == NULL;
+}
+#endif /* GLOBAL_DEBUGGING */
+
+/* Initializes C as a new case that can store VALUE_CNT values.
+   The values have indeterminate contents until explicitly
+   written. */
+void
+case_create (struct ccase *c, size_t value_cnt) 
+{
+  if (!case_try_create (c, value_cnt))
+    xalloc_die ();
+}
+
+#ifdef GLOBAL_DEBUGGING
+/* Initializes CLONE as a copy of ORIG. */
+void
+case_clone (struct ccase *clone, const struct ccase *orig)
+{
+  assert (orig != NULL);
+  assert (orig->this == orig);
+  assert (orig->case_data != NULL);
+  assert (orig->case_data->ref_cnt > 0);
+  assert (clone != NULL);
+
+  if (clone != orig) 
+    {
+      *clone = *orig;
+      clone->this = clone;
+    }
+  orig->case_data->ref_cnt++;
+}
+#endif /* GLOBAL_DEBUGGING */
+
+#ifdef GLOBAL_DEBUGGING
+/* Replaces DST by SRC and nullifies SRC.
+   DST and SRC must be initialized cases at entry. */
+void
+case_move (struct ccase *dst, struct ccase *src) 
+{
+  assert (src != NULL);
+  assert (src->this == src);
+  assert (src->case_data != NULL);
+  assert (src->case_data->ref_cnt > 0);
+  assert (dst != NULL);
+
+  *dst = *src;
+  dst->this = dst;
+  case_nullify (src);
+}
+#endif /* GLOBAL_DEBUGGING */
+
+#ifdef GLOBAL_DEBUGGING
+/* Destroys case C. */
+void
+case_destroy (struct ccase *c) 
+{
+  struct case_data *cd;
+  
+  assert (c != NULL);
+  assert (c->this == c);
+
+  cd = c->case_data;
+  if (cd != NULL && --cd->ref_cnt == 0) 
+    {
+      memset (cd->values, 0xcc, sizeof *cd->values * cd->value_cnt);
+      cd->value_cnt = 0xdeadbeef;
+      free (cd); 
+    }
+}
+#endif /* GLOBAL_DEBUGGING */
+
+/* Resizes case C from OLD_CNT to NEW_CNT values. */
+void
+case_resize (struct ccase *c, size_t old_cnt, size_t new_cnt) 
+{
+  struct ccase new;
+
+  case_create (&new, new_cnt);
+  case_copy (&new, 0, c, 0, old_cnt < new_cnt ? old_cnt : new_cnt);
+  case_swap (&new, c);
+  case_destroy (&new);
+}
+
+/* Swaps cases A and B. */
+void
+case_swap (struct ccase *a, struct ccase *b) 
+{
+  struct case_data *t = a->case_data;
+  a->case_data = b->case_data;
+  b->case_data = t;
+}
+
+/* Attempts to create C as a new case that holds VALUE_CNT
+   values.  Returns nonzero if successful, zero if memory
+   allocation failed. */
+int
+case_try_create (struct ccase *c, size_t value_cnt) 
+{
+  c->case_data = malloc (case_size (value_cnt));
+  if (c->case_data != NULL) 
+    {
+#ifdef GLOBAL_DEBUGGING
+      c->this = c;
+#endif
+      c->case_data->value_cnt = value_cnt;
+      c->case_data->ref_cnt = 1;
+      return 1;
+    }
+  else 
+    {
+#ifdef GLOBAL_DEBUGGING
+      c->this = c;
+#endif
+      return 0;
+    }
+}
+
+/* Tries to initialize CLONE as a copy of ORIG.
+   Returns nonzero if successful, zero if memory allocation
+   failed. */
+int
+case_try_clone (struct ccase *clone, const struct ccase *orig) 
+{
+  case_clone (clone, orig);
+  return 1;
+}
+
+#ifdef GLOBAL_DEBUGGING
+/* Copies VALUE_CNT values from SRC (starting at SRC_IDX) to DST
+   (starting at DST_IDX). */
+void
+case_copy (struct ccase *dst, size_t dst_idx,
+           const struct ccase *src, size_t src_idx,
+           size_t value_cnt)
+{
+  assert (dst != NULL);
+  assert (dst->this == dst);
+  assert (dst->case_data != NULL);
+  assert (dst->case_data->ref_cnt > 0);
+  assert (dst_idx + value_cnt <= dst->case_data->value_cnt);
+
+  assert (src != NULL);
+  assert (src->this == src);
+  assert (src->case_data != NULL);
+  assert (src->case_data->ref_cnt > 0);
+  assert (src_idx + value_cnt <= dst->case_data->value_cnt);
+
+  if (dst->case_data->ref_cnt > 1)
+    case_unshare (dst);
+  if (dst->case_data != src->case_data || dst_idx != src_idx) 
+    memmove (dst->case_data->values + dst_idx,
+             src->case_data->values + src_idx,
+             sizeof *dst->case_data->values * value_cnt); 
+}
+#endif /* GLOBAL_DEBUGGING */
+
+#ifdef GLOBAL_DEBUGGING
+/* Copies case C to OUTPUT.
+   OUTPUT_SIZE is the number of `union values' in OUTPUT,
+   which must match the number of `union values' in C. */
+void
+case_to_values (const struct ccase *c, union value *output,
+                size_t output_size UNUSED) 
+{
+  assert (c != NULL);
+  assert (c->this == c);
+  assert (c->case_data != NULL);
+  assert (c->case_data->ref_cnt > 0);
+  assert (output_size == c->case_data->value_cnt);
+  assert (output != NULL || output_size == 0);
+
+  memcpy (output, c->case_data->values,
+          c->case_data->value_cnt * sizeof *output);
+}
+#endif /* GLOBAL_DEBUGGING */
+
+#ifdef GLOBAL_DEBUGGING
+/* Copies INPUT into case C.
+   INPUT_SIZE is the number of `union values' in INPUT,
+   which must match the number of `union values' in C. */
+void
+case_from_values (struct ccase *c, const union value *input,
+                  size_t input_size UNUSED) 
+{
+  assert (c != NULL);
+  assert (c->this == c);
+  assert (c->case_data != NULL);
+  assert (c->case_data->ref_cnt > 0);
+  assert (input_size == c->case_data->value_cnt);
+  assert (input != NULL || input_size == 0);
+
+  if (c->case_data->ref_cnt > 1)
+    case_unshare (c);
+  memcpy (c->case_data->values, input,
+          c->case_data->value_cnt * sizeof *input);
+}
+#endif /* GLOBAL_DEBUGGING */
+
+#ifdef GLOBAL_DEBUGGING
+/* Returns a pointer to the `union value' used for the
+   element of C numbered IDX.
+   The caller must not modify the returned data. */
+const union value *
+case_data (const struct ccase *c, size_t idx) 
+{
+  assert (c != NULL);
+  assert (c->this == c);
+  assert (c->case_data != NULL);
+  assert (c->case_data->ref_cnt > 0);
+  assert (idx < c->case_data->value_cnt);
+
+  return &c->case_data->values[idx];
+}
+#endif /* GLOBAL_DEBUGGING */
+
+#ifdef GLOBAL_DEBUGGING
+/* Returns the numeric value of the `union value' in C numbered
+   IDX. */
+double
+case_num (const struct ccase *c, size_t idx) 
+{
+  assert (c != NULL);
+  assert (c->this == c);
+  assert (c->case_data != NULL);
+  assert (c->case_data->ref_cnt > 0);
+  assert (idx < c->case_data->value_cnt);
+
+  return c->case_data->values[idx].f;
+}
+#endif /* GLOBAL_DEBUGGING */
+
+#ifdef GLOBAL_DEBUGGING
+/* Returns the string value of the `union value' in C numbered
+   IDX.
+   (Note that the value is not null-terminated.)
+   The caller must not modify the return value. */
+const char *
+case_str (const struct ccase *c, size_t idx) 
+{
+  assert (c != NULL);
+  assert (c->this == c);
+  assert (c->case_data != NULL);
+  assert (c->case_data->ref_cnt > 0);
+  assert (idx < c->case_data->value_cnt);
+
+  return c->case_data->values[idx].s;
+}
+#endif /* GLOBAL_DEBUGGING */
+
+#ifdef GLOBAL_DEBUGGING
+/* Returns a pointer to the `union value' used for the
+   element of C numbered IDX.
+   The caller is allowed to modify the returned data. */
+union value *
+case_data_rw (struct ccase *c, size_t idx) 
+{
+  assert (c != NULL);
+  assert (c->this == c);
+  assert (c->case_data != NULL);
+  assert (c->case_data->ref_cnt > 0);
+  assert (idx < c->case_data->value_cnt);
+
+  if (c->case_data->ref_cnt > 1)
+    case_unshare (c);
+  return &c->case_data->values[idx];
+}
+#endif /* GLOBAL_DEBUGGING */
+
+/* Compares the values of the VAR_CNT variables in VP
+   in cases A and B and returns a strcmp()-type result. */
+int
+case_compare (const struct ccase *a, const struct ccase *b,
+              struct variable *const *vp, size_t var_cnt)
+{
+  return case_compare_2dict (a, b, vp, vp, var_cnt);
+}
+
+/* Compares the values of the VAR_CNT variables in VAP in case CA
+   to the values of the VAR_CNT variables in VBP in CB
+   and returns a strcmp()-type result. */
+int
+case_compare_2dict (const struct ccase *ca, const struct ccase *cb,
+                    struct variable *const *vap, struct variable *const *vbp,
+                    size_t var_cnt) 
+{
+  for (; var_cnt-- > 0; vap++, vbp++) 
+    {
+      const struct variable *va = *vap;
+      const struct variable *vb = *vbp;
+
+      assert (va->type == vb->type);
+      assert (va->width == vb->width);
+      
+      if (va->width == 0) 
+        {
+          double af = case_num (ca, va->fv);
+          double bf = case_num (cb, vb->fv);
+
+          if (af != bf) 
+            return af > bf ? 1 : -1;
+        }
+      else 
+        {
+          const char *as = case_str (ca, va->fv);
+          const char *bs = case_str (cb, vb->fv);
+          int cmp = memcmp (as, bs, va->width);
+
+          if (cmp != 0)
+            return cmp;
+        }
+    }
+  return 0;
+}
+
+/* Returns a pointer to the array of `union value's used for C.
+   The caller must *not* modify the returned data.
+
+   NOTE: This function breaks the case abstraction.  It should
+   *not* be used often.  Prefer the other case functions. */
+const union value *
+case_data_all (const struct ccase *c) 
+{
+  assert (c != NULL);
+  assert (c->this == c);
+  assert (c->case_data != NULL);
+  assert (c->case_data->ref_cnt > 0);
+
+  return c->case_data->values;
+}
+
+/* Returns a pointer to the array of `union value's used for C.
+   The caller is allowed to modify the returned data.
+
+   NOTE: This function breaks the case abstraction.  It should
+   *not* be used often.  Prefer the other case functions. */
+union value *
+case_data_all_rw (struct ccase *c) 
+{
+  assert (c != NULL);
+  assert (c->this == c);
+  assert (c->case_data != NULL);
+  assert (c->case_data->ref_cnt > 0);
+
+  if (c->case_data->ref_cnt > 1)
+    case_unshare (c);
+  return c->case_data->values;
+}
diff --git a/src/data/case.h b/src/data/case.h
new file mode 100644 (file)
index 0000000..6f6abeb
--- /dev/null
@@ -0,0 +1,188 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef HEADER_CASE
+#define HEADER_CASE
+
+#include <stddef.h>
+#include <stdbool.h>
+#include "value.h"
+
+/* Opaque structure that represents a case.  Use accessor
+   functions instead of accessing any members directly.  Use
+   case_move() or case_clone() instead of copying.  */
+struct ccase 
+  {
+    struct case_data *case_data;        /* Actual data. */
+#if GLOBAL_DEBUGGING
+    struct ccase *this;                 /* Detects unauthorized move/copy. */
+#endif
+  };
+
+/* Invisible to user code. */
+struct case_data
+  {
+    size_t value_cnt;                   /* Number of values. */
+    unsigned ref_cnt;                   /* Reference count. */
+    union value values[1];              /* Values. */
+  };
+
+#ifdef GLOBAL_DEBUGGING
+#define CASE_INLINE
+#else
+#define CASE_INLINE static
+#endif
+
+CASE_INLINE void case_nullify (struct ccase *);
+CASE_INLINE int case_is_null (const struct ccase *);
+
+void case_create (struct ccase *, size_t value_cnt);
+CASE_INLINE void case_clone (struct ccase *, const struct ccase *);
+CASE_INLINE void case_move (struct ccase *, struct ccase *);
+CASE_INLINE void case_destroy (struct ccase *);
+
+void case_resize (struct ccase *, size_t old_cnt, size_t new_cnt);
+void case_swap (struct ccase *, struct ccase *);
+
+int case_try_create (struct ccase *, size_t value_cnt);
+int case_try_clone (struct ccase *, const struct ccase *);
+
+CASE_INLINE void case_copy (struct ccase *dst, size_t dst_idx,
+                            const struct ccase *src, size_t src_idx,
+                            size_t cnt);
+
+CASE_INLINE void case_to_values (const struct ccase *, union value *, size_t);
+CASE_INLINE void case_from_values (struct ccase *,
+                                   const union value *, size_t);
+
+CASE_INLINE const union value *case_data (const struct ccase *, size_t idx);
+CASE_INLINE double case_num (const struct ccase *, size_t idx);
+CASE_INLINE const char *case_str (const struct ccase *, size_t idx);
+
+CASE_INLINE union value *case_data_rw (struct ccase *, size_t idx);
+
+struct variable;
+int case_compare (const struct ccase *, const struct ccase *,
+                  struct variable *const *, size_t var_cnt);
+int case_compare_2dict (const struct ccase *, const struct ccase *,
+                        struct variable *const *, struct variable *const *,
+                        size_t var_cnt);
+
+const union value *case_data_all (const struct ccase *);
+union value *case_data_all_rw (struct ccase *);
+
+void case_unshare (struct ccase *);
+
+#ifndef GLOBAL_DEBUGGING
+#include <stdlib.h>
+#include "str.h"
+
+static inline void
+case_nullify (struct ccase *c) 
+{
+  c->case_data = NULL;
+}
+
+static inline int
+case_is_null (const struct ccase *c) 
+{
+  return c->case_data == NULL;
+}
+
+static inline void
+case_clone (struct ccase *clone, const struct ccase *orig)
+{
+  *clone = *orig;
+  orig->case_data->ref_cnt++;
+}
+
+static inline void
+case_move (struct ccase *dst, struct ccase *src) 
+{
+  *dst = *src;
+  src->case_data = NULL;
+}
+
+static inline void
+case_destroy (struct ccase *c) 
+{
+  struct case_data *cd = c->case_data;
+  if (cd != NULL && --cd->ref_cnt == 0)
+    free (cd);
+}
+
+static inline void
+case_copy (struct ccase *dst, size_t dst_idx,
+           const struct ccase *src, size_t src_idx,
+           size_t value_cnt) 
+{
+  if (dst->case_data->ref_cnt > 1)
+    case_unshare (dst);
+  if (dst->case_data != src->case_data || dst_idx != src_idx) 
+    memmove (dst->case_data->values + dst_idx,
+             src->case_data->values + src_idx,
+             sizeof *dst->case_data->values * value_cnt); 
+}
+
+static inline void
+case_to_values (const struct ccase *c, union value *output,
+                size_t output_size ) 
+{
+  memcpy (output, c->case_data->values,
+          output_size * sizeof *output);
+}
+
+static inline void
+case_from_values (struct ccase *c, const union value *input,
+                  size_t input_size UNUSED) 
+{
+  if (c->case_data->ref_cnt > 1)
+    case_unshare (c);
+  memcpy (c->case_data->values, input,
+          c->case_data->value_cnt * sizeof *input);
+}
+
+static inline const union value *
+case_data (const struct ccase *c, size_t idx) 
+{
+  return &c->case_data->values[idx];
+}
+
+static inline double
+case_num (const struct ccase *c, size_t idx) 
+{
+  return c->case_data->values[idx].f;
+}
+
+static inline const char *
+case_str (const struct ccase *c, size_t idx)
+{
+  return c->case_data->values[idx].s;
+}
+
+static inline union value *
+case_data_rw (struct ccase *c, size_t idx)
+{
+  if (c->case_data->ref_cnt > 1)
+    case_unshare (c);
+  return &c->case_data->values[idx];
+}
+#endif /* !GLOBAL_DEBUGGING */
+
+#endif /* case.h */
diff --git a/src/data/casefile.c b/src/data/casefile.c
new file mode 100644 (file)
index 0000000..63b0fcd
--- /dev/null
@@ -0,0 +1,803 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "casefile.h"
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "alloc.h"
+#include "case.h"
+#include "message.h"
+#include "full-read.h"
+#include "full-write.h"
+#include "misc.h"
+#include "make-file.h"
+#include "settings.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#define IO_BUF_SIZE (8192 / sizeof (union value))
+
+/* A casefile represents a sequentially accessible stream of
+   immutable cases.
+
+   If workspace allows, a casefile is maintained in memory.  If
+   workspace overflows, then the casefile is pushed to disk.  In
+   either case the interface presented to callers is kept the
+   same.
+
+   The life cycle of a casefile consists of up to three phases:
+
+       1. Writing.  The casefile initially contains no cases.  In
+          this phase, any number of cases may be appended to the
+          end of a casefile.  (Cases are never inserted in the
+          middle or before the beginning of a casefile.)
+
+          Use casefile_append() or casefile_append_xfer() to
+          append a case to a casefile.
+
+       2. Reading.  The casefile may be read sequentially,
+          starting from the beginning, by "casereaders".  Any
+          number of casereaders may be created, at any time,
+          during the reading phase.  Each casereader has an
+          independent position in the casefile.
+
+          Casereaders may only move forward.  They cannot move
+          backward to arbitrary records or seek randomly.
+          Cloning casereaders is possible, but it is not yet
+          implemented.
+
+          Use casefile_get_reader() to create a casereader for
+          use in phase 2.  This also transitions from phase 1 to
+          phase 2.  Calling casefile_mode_reader() makes the same
+          transition, without creating a casereader.
+
+          Use casereader_read() or casereader_read_xfer() to read
+          a case from a casereader.  Use casereader_destroy() to
+          discard a casereader when it is no longer needed.
+
+       3. Destruction.  This phase is optional.  The casefile is
+          also read with casereaders in this phase, but the
+          ability to create new casereaders is curtailed.
+
+          In this phase, casereaders could still be cloned (once
+          we eventually implement cloning).
+
+          To transition from phase 1 or 2 to phase 3 and create a
+          casereader, call casefile_get_destructive_reader().
+          The same functions apply to the casereader obtained
+          this way as apply to casereaders obtained in phase 2.
+          
+          After casefile_get_destructive_reader() is called, no
+          more casereaders may be created with
+          casefile_get_reader() or
+          casefile_get_destructive_reader().  (If cloning of
+          casereaders were implemented, it would still be
+          possible.)
+
+          The purpose of the limitations applied to casereaders
+          in phase 3 is to allow in-memory casefiles to fully
+          transfer ownership of cases to the casereaders,
+          avoiding the need for extra copies of case data.  For
+          relatively static data sets with many variables, I
+          suspect (without evidence) that this may be a big
+          performance boost.
+
+   When a casefile is no longer needed, it may be destroyed with
+   casefile_destroy().  This function will also destroy any
+   remaining casereaders. */
+
+/* In-memory cases are arranged in an array of arrays.  The top
+   level is variable size and the size of each bottom level array
+   is fixed at the number of cases defined here.  */
+#define CASES_PER_BLOCK 128             
+
+/* A casefile. */
+struct casefile 
+  {
+    /* Basic data. */
+    struct casefile *next, *prev;       /* Next, prev in global list. */
+    size_t value_cnt;                   /* Case size in `union value's. */
+    size_t case_acct_size;              /* Case size for accounting. */
+    unsigned long case_cnt;             /* Number of cases stored. */
+    enum { MEMORY, DISK } storage;      /* Where cases are stored. */
+    enum { WRITE, READ } mode;          /* Is writing or reading allowed? */
+    struct casereader *readers;         /* List of our readers. */
+    int being_destroyed;                /* Does a destructive reader exist? */
+    bool ok;                            /* False after I/O error. */
+
+    /* Memory storage. */
+    struct ccase **cases;               /* Pointer to array of cases. */
+
+    /* Disk storage. */
+    int fd;                             /* File descriptor, -1 if none. */
+    char *filename;                     /* Filename. */
+    union value *buffer;                /* I/O buffer, NULL if none. */
+    size_t buffer_used;                 /* Number of values used in buffer. */
+    size_t buffer_size;                 /* Buffer size in values. */
+  };
+
+/* For reading out the cases in a casefile. */
+struct casereader 
+  {
+    struct casereader *next, *prev;     /* Next, prev in casefile's list. */
+    struct casefile *cf;                /* Our casefile. */
+    unsigned long case_idx;             /* Case number of current case. */
+    int destructive;                    /* Is this a destructive reader? */
+
+    /* Disk storage. */
+    int fd;                             /* File descriptor. */
+    union value *buffer;                /* I/O buffer. */
+    size_t buffer_pos;                  /* Offset of buffer position. */
+    struct ccase c;                     /* Current case. */
+  };
+
+/* Return the case number of the current case */
+unsigned long
+casereader_cnum(const struct casereader *r)
+{
+  return r->case_idx;
+}
+
+/* Doubly linked list of all casefiles. */
+static struct casefile *casefiles;
+
+/* Number of bytes of case allocated in in-memory casefiles. */
+static size_t case_bytes;
+
+static void register_atexit (void);
+static void exit_handler (void);
+
+static void reader_open_file (struct casereader *reader);
+static void write_case_to_disk (struct casefile *cf, const struct ccase *c);
+static void flush_buffer (struct casefile *cf);
+static bool fill_buffer (struct casereader *reader);
+
+static void io_error (struct casefile *, const char *, ...)
+     PRINTF_FORMAT (2, 3);
+static int safe_open (const char *filename, int flags);
+static int safe_close (int fd);
+
+/* Creates and returns a casefile to store cases of VALUE_CNT
+   `union value's each. */
+struct casefile *
+casefile_create (size_t value_cnt) 
+{
+  struct casefile *cf = xmalloc (sizeof *cf);
+  cf->next = casefiles;
+  cf->prev = NULL;
+  if (cf->next != NULL)
+    cf->next->prev = cf;
+  casefiles = cf;
+  cf->value_cnt = value_cnt;
+  cf->case_acct_size = (cf->value_cnt + 4) * sizeof *cf->buffer;
+  cf->case_cnt = 0;
+  cf->storage = MEMORY;
+  cf->mode = WRITE;
+  cf->readers = NULL;
+  cf->being_destroyed = 0;
+  cf->ok = true;
+  cf->cases = NULL;
+  cf->fd = -1;
+  cf->filename = NULL;
+  cf->buffer = NULL;
+  cf->buffer_size = ROUND_UP (cf->value_cnt, IO_BUF_SIZE);
+  if (cf->value_cnt > 0 && cf->buffer_size % cf->value_cnt > 64)
+    cf->buffer_size = cf->value_cnt;
+  cf->buffer_used = 0;
+  register_atexit ();
+  return cf;
+}
+
+/* Destroys casefile CF. */
+void
+casefile_destroy (struct casefile *cf) 
+{
+  if (cf != NULL) 
+    {
+      if (cf->next != NULL)
+        cf->next->prev = cf->prev;
+      if (cf->prev != NULL)
+        cf->prev->next = cf->next;
+      if (casefiles == cf)
+        casefiles = cf->next;
+
+      while (cf->readers != NULL) 
+        casereader_destroy (cf->readers);
+
+      if (cf->cases != NULL) 
+        {
+          size_t idx, block_cnt;
+
+          case_bytes -= cf->case_cnt * cf->case_acct_size;
+          for (idx = 0; idx < cf->case_cnt; idx++)
+            {
+              size_t block_idx = idx / CASES_PER_BLOCK;
+              size_t case_idx = idx % CASES_PER_BLOCK;
+              struct ccase *c = &cf->cases[block_idx][case_idx];
+              case_destroy (c);
+            }
+
+          block_cnt = DIV_RND_UP (cf->case_cnt, CASES_PER_BLOCK);
+          for (idx = 0; idx < block_cnt; idx++)
+            free (cf->cases[idx]);
+
+          free (cf->cases);
+        }
+
+      if (cf->fd != -1)
+        safe_close (cf->fd);
+          
+      if (cf->filename != NULL && remove (cf->filename) == -1) 
+        io_error (cf, _("%s: Removing temporary file: %s."),
+                  cf->filename, strerror (errno));
+      free (cf->filename);
+
+      free (cf->buffer);
+
+      free (cf);
+    }
+}
+
+/* Returns true if an I/O error has occurred in casefile CF. */
+bool
+casefile_error (const struct casefile *cf) 
+{
+  return !cf->ok;
+}
+
+/* Returns nonzero only if casefile CF is stored in memory (instead of on
+   disk). */
+int
+casefile_in_core (const struct casefile *cf) 
+{
+  assert (cf != NULL);
+
+  return cf->storage == MEMORY;
+}
+
+/* Puts a casefile to "sleep", that is, minimizes the resources
+   needed for it by closing its file descriptor and freeing its
+   buffer.  This is useful if we need so many casefiles that we
+   might not have enough memory and file descriptors to go
+   around.
+
+   For simplicity, this implementation always converts the
+   casefile to reader mode.  If this turns out to be a problem,
+   with a little extra work we could also support sleeping
+   writers.
+
+   Returns true if successful, false if an I/O error occurred. */
+bool
+casefile_sleep (const struct casefile *cf_) 
+{
+  struct casefile *cf = (struct casefile *) cf_;
+  assert (cf != NULL);
+
+  casefile_mode_reader (cf);
+  casefile_to_disk (cf);
+  flush_buffer (cf);
+
+  if (cf->fd != -1) 
+    {
+      safe_close (cf->fd);
+      cf->fd = -1;
+    }
+  if (cf->buffer != NULL) 
+    {
+      free (cf->buffer);
+      cf->buffer = NULL;
+    }
+
+  return cf->ok;
+}
+
+/* Returns the number of `union value's in a case for CF. */
+size_t
+casefile_get_value_cnt (const struct casefile *cf) 
+{
+  assert (cf != NULL);
+
+  return cf->value_cnt;
+}
+
+/* Returns the number of cases in casefile CF. */
+unsigned long
+casefile_get_case_cnt (const struct casefile *cf) 
+{
+  assert (cf != NULL);
+
+  return cf->case_cnt;
+}
+
+/* Appends a copy of case C to casefile CF.  Not valid after any
+   reader for CF has been created.
+   Returns true if successful, false if an I/O error occurred. */
+bool
+casefile_append (struct casefile *cf, const struct ccase *c) 
+{
+  assert (cf != NULL);
+  assert (c != NULL);
+  assert (cf->mode == WRITE);
+
+  /* Try memory first. */
+  if (cf->storage == MEMORY) 
+    {
+      if (case_bytes < get_workspace ())
+        {
+          size_t block_idx = cf->case_cnt / CASES_PER_BLOCK;
+          size_t case_idx = cf->case_cnt % CASES_PER_BLOCK;
+          struct ccase new_case;
+
+          case_bytes += cf->case_acct_size;
+          case_clone (&new_case, c);
+          if (case_idx == 0) 
+            {
+              if ((block_idx & (block_idx - 1)) == 0) 
+                {
+                  size_t block_cap = block_idx == 0 ? 1 : block_idx * 2;
+                  cf->cases = xnrealloc (cf->cases,
+                                         block_cap, sizeof *cf->cases);
+                }
+
+              cf->cases[block_idx] = xnmalloc (CASES_PER_BLOCK,
+                                               sizeof **cf->cases);
+            }
+
+          case_move (&cf->cases[block_idx][case_idx], &new_case);
+        }
+      else
+        {
+          casefile_to_disk (cf);
+          assert (cf->storage == DISK);
+          write_case_to_disk (cf, c);
+        }
+    }
+  else
+    write_case_to_disk (cf, c);
+
+  cf->case_cnt++;
+  return cf->ok;
+}
+
+/* Appends case C to casefile CF, which takes over ownership of
+   C.  Not valid after any reader for CF has been created.
+   Returns true if successful, false if an I/O error occurred. */
+bool
+casefile_append_xfer (struct casefile *cf, struct ccase *c) 
+{
+  casefile_append (cf, c);
+  case_destroy (c);
+  return cf->ok;
+}
+
+/* Writes case C to casefile CF's disk buffer, first flushing the buffer to
+   disk if it would otherwise overflow.
+   Returns true if successful, false if an I/O error occurred. */
+static void
+write_case_to_disk (struct casefile *cf, const struct ccase *c) 
+{
+  if (!cf->ok)
+    return;
+  
+  case_to_values (c, cf->buffer + cf->buffer_used, cf->value_cnt);
+  cf->buffer_used += cf->value_cnt;
+  if (cf->buffer_used + cf->value_cnt > cf->buffer_size)
+    flush_buffer (cf);
+}
+
+/* If any bytes in CF's output buffer are used, flush them to
+   disk. */
+static void
+flush_buffer (struct casefile *cf) 
+{
+  if (cf->ok && cf->buffer_used > 0) 
+    {
+      if (!full_write (cf->fd, cf->buffer,
+                       cf->buffer_size * sizeof *cf->buffer))
+        io_error (cf, _("Error writing temporary file: %s."),
+                  strerror (errno));
+
+
+      cf->buffer_used = 0;
+    }
+}
+
+/* If CF is currently stored in memory, writes it to disk.  Readers, if any,
+   retain their current positions.
+   Returns true if successful, false if an I/O error occurred. */
+bool
+casefile_to_disk (const struct casefile *cf_) 
+{
+  struct casefile *cf = (struct casefile *) cf_;
+  struct casereader *reader;
+  
+  assert (cf != NULL);
+
+  if (cf->storage == MEMORY)
+    {
+      size_t idx, block_cnt;
+      
+      assert (cf->filename == NULL);
+      assert (cf->fd == -1);
+      assert (cf->buffer_used == 0);
+
+      if (!make_temp_file (&cf->fd, &cf->filename))
+        {
+          cf->ok = false;
+          return false;
+        }
+      cf->storage = DISK;
+
+      cf->buffer = xnmalloc (cf->buffer_size, sizeof *cf->buffer);
+      memset (cf->buffer, 0, cf->buffer_size * sizeof *cf->buffer);
+
+      case_bytes -= cf->case_cnt * cf->case_acct_size;
+      for (idx = 0; idx < cf->case_cnt; idx++)
+        {
+          size_t block_idx = idx / CASES_PER_BLOCK;
+          size_t case_idx = idx % CASES_PER_BLOCK;
+          struct ccase *c = &cf->cases[block_idx][case_idx];
+          write_case_to_disk (cf, c);
+          case_destroy (c);
+        }
+
+      block_cnt = DIV_RND_UP (cf->case_cnt, CASES_PER_BLOCK);
+      for (idx = 0; idx < block_cnt; idx++)
+        free (cf->cases[idx]);
+
+      free (cf->cases);
+      cf->cases = NULL;
+
+      if (cf->mode == READ)
+        flush_buffer (cf);
+
+      for (reader = cf->readers; reader != NULL; reader = reader->next)
+        reader_open_file (reader);
+    }
+  return cf->ok;
+}
+
+/* Changes CF to reader mode, ensuring that no more cases may be
+   added.  Creating a casereader for CF has the same effect. */
+void
+casefile_mode_reader (struct casefile *cf) 
+{
+  assert (cf != NULL);
+  cf->mode = READ;
+}
+
+/* Creates and returns a casereader for CF.  A casereader can be used to
+   sequentially read the cases in a casefile. */
+struct casereader *
+casefile_get_reader (const struct casefile *cf_) 
+{
+  struct casefile *cf = (struct casefile *) cf_;
+  struct casereader *reader;
+
+  assert (cf != NULL);
+  assert (!cf->being_destroyed);
+
+  /* Flush the buffer to disk if it's not empty. */
+  if (cf->mode == WRITE && cf->storage == DISK)
+    flush_buffer (cf);
+  
+  cf->mode = READ;
+
+  reader = xmalloc (sizeof *reader);
+  reader->next = cf->readers;
+  if (cf->readers != NULL)
+    reader->next->prev = reader;
+  cf->readers = reader;
+  reader->prev = NULL;
+  reader->cf = cf;
+  reader->case_idx = 0;
+  reader->destructive = 0;
+  reader->fd = -1;
+  reader->buffer = NULL;
+  reader->buffer_pos = 0;
+  case_nullify (&reader->c);
+
+  if (reader->cf->storage == DISK) 
+    reader_open_file (reader);
+
+  return reader;
+}
+
+/* Creates and returns a destructive casereader for CF.  Like a
+   normal casereader, a destructive casereader sequentially reads
+   the cases in a casefile.  Unlike a normal casereader, a
+   destructive reader cannot operate concurrently with any other
+   reader.  (This restriction could be relaxed in a few ways, but
+   it is so far unnecessary for other code.) */
+struct casereader *
+casefile_get_destructive_reader (struct casefile *cf) 
+{
+  struct casereader *reader;
+  
+  assert (cf->readers == NULL);
+  reader = casefile_get_reader (cf);
+  reader->destructive = 1;
+  cf->being_destroyed = 1;
+  return reader;
+}
+
+/* Opens a disk file for READER and seeks to the current position as indicated
+   by case_idx.  Normally the current position is the beginning of the file,
+   but casefile_to_disk may cause the file to be opened at a different
+   position. */
+static void
+reader_open_file (struct casereader *reader) 
+{
+  struct casefile *cf = reader->cf;
+  off_t file_ofs;
+
+  if (!cf->ok || reader->case_idx >= cf->case_cnt)
+    return;
+
+  if (cf->fd != -1) 
+    {
+      reader->fd = cf->fd;
+      cf->fd = -1;
+    }
+  else 
+    {
+      reader->fd = safe_open (cf->filename, O_RDONLY);
+      if (reader->fd < 0)
+        io_error (cf, _("%s: Opening temporary file: %s."),
+                  cf->filename, strerror (errno));
+    }
+
+  if (cf->buffer != NULL) 
+    {
+      reader->buffer = cf->buffer;
+      cf->buffer = NULL; 
+    }
+  else 
+    {
+      reader->buffer = xnmalloc (cf->buffer_size, sizeof *cf->buffer);
+      memset (reader->buffer, 0, cf->buffer_size * sizeof *cf->buffer); 
+    }
+
+  if (cf->value_cnt != 0) 
+    {
+      size_t buffer_case_cnt = cf->buffer_size / cf->value_cnt;
+      file_ofs = ((off_t) reader->case_idx / buffer_case_cnt
+                  * cf->buffer_size * sizeof *cf->buffer);
+      reader->buffer_pos = (reader->case_idx % buffer_case_cnt
+                            * cf->value_cnt);
+    }
+  else 
+    file_ofs = 0;
+  if (lseek (reader->fd, file_ofs, SEEK_SET) != file_ofs)
+    io_error (cf, _("%s: Seeking temporary file: %s."),
+              cf->filename, strerror (errno));
+
+  if (cf->case_cnt > 0 && cf->value_cnt > 0)
+    fill_buffer (reader);
+
+  case_create (&reader->c, cf->value_cnt);
+}
+
+/* Fills READER's buffer by reading a block from disk. */
+static bool
+fill_buffer (struct casereader *reader)
+{
+  if (reader->cf->ok) 
+    {
+      int bytes = full_read (reader->fd, reader->buffer,
+                             reader->cf->buffer_size * sizeof *reader->buffer);
+      if (bytes < 0) 
+        io_error (reader->cf, _("%s: Reading temporary file: %s."),
+                  reader->cf->filename, strerror (errno));
+      else if (bytes != reader->cf->buffer_size * sizeof *reader->buffer) 
+        io_error (reader->cf, _("%s: Temporary file ended unexpectedly."),
+                  reader->cf->filename); 
+    }
+  return reader->cf->ok;
+}
+
+/* Returns the casefile that READER reads. */
+const struct casefile *
+casereader_get_casefile (const struct casereader *reader) 
+{
+  assert (reader != NULL);
+  
+  return reader->cf;
+}
+
+/* Reads a copy of the next case from READER into C.
+   Caller is responsible for destroying C.
+   Returns true if successful, false at end of file. */
+int
+casereader_read (struct casereader *reader, struct ccase *c) 
+{
+  assert (reader != NULL);
+  
+  if (!reader->cf->ok || reader->case_idx >= reader->cf->case_cnt) 
+    return 0;
+
+  if (reader->cf->storage == MEMORY) 
+    {
+      size_t block_idx = reader->case_idx / CASES_PER_BLOCK;
+      size_t case_idx = reader->case_idx % CASES_PER_BLOCK;
+
+      case_clone (c, &reader->cf->cases[block_idx][case_idx]);
+      reader->case_idx++;
+      return 1;
+    }
+  else 
+    {
+      if (reader->buffer_pos + reader->cf->value_cnt > reader->cf->buffer_size)
+        {
+          if (!fill_buffer (reader))
+            return 0;
+          reader->buffer_pos = 0;
+        }
+
+      case_from_values (&reader->c, reader->buffer + reader->buffer_pos,
+                        reader->cf->value_cnt);
+      reader->buffer_pos += reader->cf->value_cnt;
+      reader->case_idx++;
+
+      case_clone (c, &reader->c);
+      return 1;
+    }
+}
+
+/* Reads the next case from READER into C and transfers ownership
+   to the caller.  Caller is responsible for destroying C.
+   Returns true if successful, false at end of file or on I/O
+   error. */
+int
+casereader_read_xfer (struct casereader *reader, struct ccase *c)
+{
+  assert (reader != NULL);
+
+  if (reader->destructive == 0
+      || reader->case_idx >= reader->cf->case_cnt
+      || reader->cf->storage == DISK) 
+    return casereader_read (reader, c);
+  else 
+    {
+      size_t block_idx = reader->case_idx / CASES_PER_BLOCK;
+      size_t case_idx = reader->case_idx % CASES_PER_BLOCK;
+      struct ccase *read_case = &reader->cf->cases[block_idx][case_idx];
+
+      case_move (c, read_case);
+      reader->case_idx++;
+      return 1;
+    }
+}
+
+/* Destroys READER. */
+void
+casereader_destroy (struct casereader *reader)
+{
+  assert (reader != NULL);
+
+  if (reader->next != NULL)
+    reader->next->prev = reader->prev;
+  if (reader->prev != NULL)
+    reader->prev->next = reader->next;
+  if (reader->cf->readers == reader)
+    reader->cf->readers = reader->next;
+
+  if (reader->cf->buffer == NULL)
+    reader->cf->buffer = reader->buffer;
+  else
+    free (reader->buffer);
+
+  if (reader->fd != -1) 
+    {
+      if (reader->cf->fd == -1)
+        reader->cf->fd = reader->fd;
+      else
+        safe_close (reader->fd);
+    }
+  
+  case_destroy (&reader->c);
+
+  free (reader);
+}
+
+/* Marks CF as having encountered an I/O error.
+   If this is the first error on CF, reports FORMAT to the user,
+   doing printf()-style substitutions. */
+static void
+io_error (struct casefile *cf, const char *format, ...)
+{
+  if (cf->ok) 
+    {
+      struct error e;
+      va_list args;
+
+      e.class = ME;
+      e.where.filename = NULL;
+      e.where.line_number = -1;
+      e.title = NULL;
+
+      va_start (args, format);
+      err_vmsg (&e, format, args);
+      va_end (args);
+    }
+  cf->ok = false;
+}
+
+/* Calls open(), passing FILENAME and FLAGS, repeating as necessary
+   to deal with interrupted calls. */
+static int
+safe_open (const char *filename, int flags) 
+{
+  int fd;
+
+  do 
+    {
+      fd = open (filename, flags);
+    }
+  while (fd == -1 && errno == EINTR);
+
+  return fd;
+}
+
+/* Calls close(), passing FD, repeating as necessary to deal with
+   interrupted calls. */
+static int safe_close (int fd) 
+{
+  int retval;
+
+  do 
+    {
+      retval = close (fd);
+    }
+  while (retval == -1 && errno == EINTR);
+
+  return retval;
+}
+
+/* Registers our exit handler with atexit() if it has not already
+   been registered. */
+static void
+register_atexit (void) 
+{
+  static int registered = 0;
+  if (!registered) 
+    {
+      registered = 1;
+      atexit (exit_handler);
+    }
+}
+
+
+
+/* atexit() handler that closes and deletes our temporary
+   files. */
+static void
+exit_handler (void) 
+{
+  while (casefiles != NULL)
+    casefile_destroy (casefiles);
+}
diff --git a/src/data/casefile.h b/src/data/casefile.h
new file mode 100644 (file)
index 0000000..8c56dbb
--- /dev/null
@@ -0,0 +1,55 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef HEADER_CASEFILE
+#define HEADER_CASEFILE
+
+#include <stddef.h>
+#include <stdbool.h>
+
+struct ccase;
+struct casefile;
+struct casereader;
+
+struct casefile *casefile_create (size_t value_cnt);
+void casefile_destroy (struct casefile *);
+
+bool casefile_error (const struct casefile *);
+int casefile_in_core (const struct casefile *);
+bool casefile_to_disk (const struct casefile *);
+bool casefile_sleep (const struct casefile *);
+
+size_t casefile_get_value_cnt (const struct casefile *);
+unsigned long casefile_get_case_cnt (const struct casefile *);
+
+bool casefile_append (struct casefile *, const struct ccase *);
+bool casefile_append_xfer (struct casefile *, struct ccase *);
+
+void casefile_mode_reader (struct casefile *);
+struct casereader *casefile_get_reader (const struct casefile *);
+struct casereader *casefile_get_destructive_reader (struct casefile *);
+
+const struct casefile *casereader_get_casefile (const struct casereader *);
+int casereader_read (struct casereader *, struct ccase *);
+int casereader_read_xfer (struct casereader *, struct ccase *);
+void casereader_destroy (struct casereader *);
+
+unsigned long casereader_cnum(const struct casereader *);
+
+#endif /* casefile.h */
diff --git a/src/data/cat-routines.h b/src/data/cat-routines.h
new file mode 100644 (file)
index 0000000..43cfccc
--- /dev/null
@@ -0,0 +1,53 @@
+/* PSPP - Binary encodings for categorical variables.
+   Copyright (C) 2005 Free Software Foundation, Inc.
+   Written by Jason H Stover <jason@sakla.net>.
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+/*
+  Functions and data structures to recode categorical variables into
+  vectors and sub-rows of matrices.
+  
+  To fit many types of statistical models, it is necessary
+  to change each value of a categorical variable to a vector with binary
+  entries. These vectors are then stored as sub-rows within a matrix
+  during model-fitting. We need functions and data strucutres to,
+  e.g., map a value, say 'a', of a variable named 'cat_var', to a
+  vector, say (0 1 0 0 0), and vice versa.  We also need to be able
+  to map the vector back to the value 'a', and if the vector is a
+  sub-row of a matrix, we need to know which sub-row corresponds to
+  the variable 'cat_var'.
+
+ */
+
+#ifndef CAT_ROUTINES_H
+#define CAT_ROUTINES_H
+#define CAT_VALUE_NOT_FOUND -2
+#include <stdbool.h>
+#include "category.h"
+
+size_t cat_value_find (const struct variable *, const union value *);
+
+union value *cat_subscript_to_value (const size_t, struct variable *);
+
+void cat_stored_values_create (struct variable *);
+
+void cat_value_update (struct variable *, const union value *);
+
+void cat_create_value_matrix (struct variable *);
+
+void cat_stored_values_destroy (struct variable *);
+#endif
diff --git a/src/data/category.c b/src/data/category.c
new file mode 100644 (file)
index 0000000..e48987a
--- /dev/null
@@ -0,0 +1,143 @@
+/* PSPP - binary encodings for categorical variables.
+   Copyright (C) 2005 Free Software Foundation, Inc.
+   Written by Jason H Stover <jason@sakla.net>.
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+/*
+  Functions and data structures to store values of a categorical
+  variable, and to recode those values into binary vectors.
+
+  For some statistical models, it is necessary to change each value
+  of a categorical variable to a vector with binary entries. These
+  vectors are then stored as sub-rows within a matrix during
+  model-fitting. For example, we need functions and data strucutres to map a
+  value, say 'a', of a variable named 'cat_var', to a vector, say (0
+  1 0 0 0), and vice versa.  We also need to be able to map the
+  vector back to the value 'a', and if the vector is a sub-row of a
+  matrix, we need to know which sub-row corresponds to the variable
+  'cat_var'.
+*/
+#include <config.h>
+#include <stdlib.h>
+#include <message.h>
+#include "alloc.h"
+#include "message.h"
+#include "category.h"
+#include "cat-routines.h"
+#include <string.h>
+#include "variable.h"
+
+#define N_INITIAL_CATEGORIES 1
+
+void
+cat_stored_values_create (struct variable *v)
+{
+  if (v->obs_vals == NULL)
+    {
+      v->obs_vals = xmalloc (sizeof (*v->obs_vals));
+      v->obs_vals->n_categories = 0;
+      v->obs_vals->n_allocated_categories = N_INITIAL_CATEGORIES;
+      v->obs_vals->vals =
+       xnmalloc (N_INITIAL_CATEGORIES, sizeof *v->obs_vals->vals);
+    }
+}
+
+void
+cat_stored_values_destroy (struct variable *v)
+{
+  assert (v != NULL);
+  if (v->obs_vals != NULL)
+    {
+      free (v->obs_vals);
+    }
+}
+
+/*
+  Which subscript corresponds to val?
+ */
+size_t
+cat_value_find (const struct variable *v, const union value *val)
+{
+  size_t i;
+  const union value *candidate;
+
+  assert (val != NULL);
+  assert (v != NULL);
+  assert (v->obs_vals != NULL);
+  for (i = 0; i < v->obs_vals->n_categories; i++)
+    {
+      candidate = v->obs_vals->vals + i;
+      assert (candidate != NULL);
+      if (!compare_values (candidate, val, v->width))
+       {
+         return i;
+       }
+    }
+  return CAT_VALUE_NOT_FOUND;
+}
+
+/*
+   Add the new value unless it is already present.
+ */
+void
+cat_value_update (struct variable *v, const union value *val)
+{
+  struct cat_vals *cv;
+
+  if (v->type == ALPHA)
+    {
+      assert (val != NULL);
+      assert (v != NULL);
+      cv = v->obs_vals;
+      if (cat_value_find (v, val) == CAT_VALUE_NOT_FOUND)
+       {
+         if (cv->n_categories >= cv->n_allocated_categories)
+           {
+             cv->n_allocated_categories *= 2;
+             cv->vals = xnrealloc (cv->vals,
+                                   cv->n_allocated_categories,
+                                   sizeof *cv->vals);
+           }
+         cv->vals[cv->n_categories] = *val;
+         cv->n_categories++;
+       }
+    }
+}
+
+union value *
+cat_subscript_to_value (const size_t s, struct variable *v)
+{
+  assert (v->obs_vals != NULL);
+  if (s < v->obs_vals->n_categories)
+    {
+      return (v->obs_vals->vals + s);
+    }
+  else
+    {
+      return NULL;
+    }
+}
+
+/*
+  Return the number of categories of a categorical variable.
+ */
+size_t 
+cat_get_n_categories (const struct variable *v)
+{
+  return v->obs_vals->n_categories;
+}
+
diff --git a/src/data/category.h b/src/data/category.h
new file mode 100644 (file)
index 0000000..30ca272
--- /dev/null
@@ -0,0 +1,66 @@
+/* PSPP - Binary encodings for categorical variables.
+   Copyright (C) 2005 Free Software Foundation, Inc.
+   Written by Jason H Stover <jason@sakla.net>.
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+/*
+  Functions and data structures to recode categorical variables into
+  vectors and sub-rows of matrices.
+  
+  To fit many types of statistical models, it is necessary
+  to change each value of a categorical variable to a vector with binary
+  entries. These vectors are then stored as sub-rows within a matrix
+  during model-fitting. We need functions and data strucutres to,
+  e.g., map a value, say 'a', of a variable named 'cat_var', to a
+  vector, say (0 1 0 0 0), and vice versa.  We also need to be able
+  to map the vector back to the value 'a', and if the vector is a
+  sub-row of a matrix, we need to know which sub-row corresponds to
+  the variable 'cat_var'.
+
+ */
+
+#ifndef CAT_H
+#define CAT_H
+#define CAT_VALUE_NOT_FOUND -2
+#include <stdbool.h>
+
+
+union value;
+struct variable ; 
+
+/*
+  This structure contains the observed values of a 
+  categorical variable.
+ */
+struct cat_vals
+{
+  union value *vals;
+  size_t n_categories;
+  size_t n_allocated_categories;       /* This is used only during
+                                          initialization to keep
+                                          track of the number of
+                                          values stored.
+                                        */
+};
+
+/*
+  Return the number of categories of a categorical variable.
+ */
+size_t  cat_get_n_categories (const struct variable *v);
+
+
+#endif
diff --git a/src/data/data-in.c b/src/data/data-in.c
new file mode 100644 (file)
index 0000000..1502417
--- /dev/null
@@ -0,0 +1,1435 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "data-in.h"
+#include "message.h"
+#include <math.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include "message.h"
+#include "calendar.h"
+#include "identifier.h"
+#include "magic.h"
+#include "misc.h"
+#include "settings.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+\f
+/* Specialized error routine. */
+
+static void dls_error (const struct data_in *, const char *format, ...)
+     PRINTF_FORMAT (2, 3);
+
+static void
+vdls_error (const struct data_in *i, const char *format, va_list args)
+{
+  struct error e;
+  struct string title;
+
+  if (i->flags & DI_IGNORE_ERROR)
+    return;
+
+  ds_init (&title, 64);
+  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_c_str (&title);
+
+  err_vmsg (&e, format, args);
+
+  ds_destroy (&title);
+}
+
+static void
+dls_error (const struct data_in *i, const char *format, ...) 
+{
+  va_list args;
+
+  va_start (args, format);
+  vdls_error (i, format, args);
+  va_end (args);
+}
+\f
+/* Parsing utility functions. */
+
+/* Excludes leading and trailing whitespace from I by adjusting
+   pointers. */
+static void
+trim_whitespace (struct data_in *i)
+{
+  while (i->s < i->e && isspace ((unsigned char) i->s[0])) 
+    i->s++;
+
+  while (i->s < i->e && isspace ((unsigned char) i->e[-1]))
+    i->e--;
+}
+
+/* Returns nonzero if we're not at the end of the string being
+   parsed. */
+static inline bool
+have_char (struct data_in *i)
+{
+  return i->s < i->e;
+}
+
+/* If implied decimal places are enabled, apply them to
+   I->v->f. */
+static void
+apply_implied_decimals (struct data_in *i) 
+{
+  if ((i->flags & DI_IMPLIED_DECIMALS) && i->format.d > 0)
+    i->v->f /= pow (10., i->format.d);
+}
+\f
+/* Format parsers. */ 
+
+static bool parse_int (struct data_in *i, long *result);
+
+/* This function is based on strtod() from the GNU C library. */
+static bool
+parse_numeric (struct data_in *i)
+{
+  int sign;                     /* +1 or -1. */
+  double num;                  /* The number so far.  */
+
+  bool got_dot;                        /* Found a decimal point.  */
+  size_t digit_cnt;            /* 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++;
+    }
+  else
+    sign = 1;
+  
+  if (type != FMT_DOT)
+    {
+      decimal = get_decimal();
+      grouping = get_grouping();
+    }
+  else
+    {
+      decimal = get_grouping();
+      grouping = get_decimal();
+    }
+
+  i->v->f = SYSMIS;
+  num = 0.0;
+  got_dot = false;
+  digit_cnt = 0;
+  exponent = 0;
+  for (; have_char (i); i->s++)
+    {
+      if (isdigit ((unsigned char) *i->s))
+       {
+         digit_cnt++;
+
+         /* 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 = true;
+      else if ((type != FMT_COMMA && type != FMT_DOT) || *i->s != grouping)
+       /* Any other character terminates the number.  */
+       break;
+    }
+
+  if (!digit_cnt)
+    {
+      if (got_dot)
+       {
+         i->v->f = SYSMIS;
+         return true;
+       }
+      dls_error (i, _("Field does not form a valid floating-point constant."));
+      i->v->f = SYSMIS;
+      return false;
+    }
+  
+  if (have_char (i) && strchr ("eEdD-+", *i->s))
+    {
+      /* Get the exponent specified after the `e' or `E'.  */
+      long exp;
+
+      if (isalpha ((unsigned char) *i->s))
+       i->s++;
+      if (!parse_int (i, &exp))
+        {
+          i->v->f = SYSMIS;
+          return false;
+        }
+
+      exponent += exp;
+    }
+  else if (!got_dot && (i->flags & DI_IMPLIED_DECIMALS))
+    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 false;
+    }
+
+  if (num == 0.0)
+    {
+      i->v->f = 0.0;
+      return true;
+    }
+
+  /* Multiply NUM by 10 to the EXPONENT power, checking for overflow
+     and underflow.  */
+  if (exponent < 0)
+    {
+      if (-exponent + digit_cnt > -(DBL_MIN_10_EXP) + 5
+         || num < DBL_MIN * pow (10.0, (double) -exponent)) 
+        {
+          dls_error (i, _("Underflow in floating-point constant."));
+          i->v->f = 0.0;
+          return false;
+        }
+
+      num *= pow (10.0, (double) exponent);
+    }
+  else if (exponent > 0)
+    {
+      if (num > DBL_MAX * pow (10.0, (double) -exponent))
+        {
+          dls_error (i, _("Overflow in floating-point constant."));
+          i->v->f = SYSMIS;
+          return false;
+        }
+      
+      num *= pow (10.0, (double) exponent);
+    }
+
+  i->v->f = sign > 0 ? num : -num;
+  return true;
+}
+
+/* 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 bool
+parse_N (struct data_in *i)
+{
+  const char *cp;
+
+  i->v->f = 0;
+  for (cp = i->s; cp < i->e; cp++)
+    {
+      if (!isdigit ((unsigned char) *cp))
+       {
+         dls_error (i, _("All characters in field must be digits."));
+         return false;
+       }
+
+      i->v->f = i->v->f * 10.0 + (*cp - '0');
+    }
+
+  apply_implied_decimals (i);
+  return true;
+}
+
+static inline bool
+parse_PIBHEX (struct data_in *i)
+{
+  double n;
+  const char *cp;
+
+  trim_whitespace (i);
+
+  n = 0.0;
+  for (cp = i->s; cp < i->e; cp++)
+    {
+      if (!isxdigit ((unsigned char) *cp))
+       {
+         dls_error (i, _("Unrecognized character in field."));
+         return false;
+       }
+
+      n = n * 16.0 + hexit_value (*cp);
+    }
+  
+  i->v->f = n;
+  return true;
+}
+
+static inline bool
+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 false;
+    }
+  
+  {
+    const char *cp;
+    
+    for (cp = i->s; cp < i->e; cp++)
+      if (!isxdigit ((unsigned char) *cp))
+       {
+         dls_error (i, _("Field must contain only hex digits."));
+         return false;
+       }
+  }
+  
+  /* 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 true;
+}
+
+static inline bool
+parse_Z (struct data_in *i)
+{
+  char buf[64];
+  bool got_dot = false;
+
+  /* Warn user that we suck. */
+  {
+    static bool warned;
+
+    if (!warned)
+      {
+       msg (MW, 
+            _("Quality of zoned decimal (Z) input format code is "
+              "suspect.  Check your results three times. Report bugs "
+               "to %s."),PACKAGE_BUGREPORT);
+       warned = true;
+      }
+  }
+
+  /* Validate input. */
+  trim_whitespace (i);
+
+  if (i->e - i->s < 2)
+    {
+      dls_error (i, _("Zoned decimal field contains fewer than 2 "
+                     "characters."));
+      return false;
+    }
+
+  /* Copy sign into buf[0]. */
+  if ((i->e[-1] & 0xc0) != 0xc0)
+    {
+      dls_error (i, _("Bad sign byte in zoned decimal number."));
+      return false;
+    }
+  buf[0] = (i->e[-1] ^ (i->e[-1] >> 1)) & 0x10 ? '-' : '+';
+
+  /* Copy digits into buf[1 ... len - 1] and terminate string. */
+  {
+    const char *sp;
+    char *dp;
+
+    for (sp = i->s, dp = buf + 1; sp < i->e - 1; sp++, dp++)
+      if (*sp == '.') 
+        {
+          *dp = '.';
+          got_dot = true;
+        }
+      else if ((*sp & 0xf0) == 0xf0 && (*sp & 0xf) < 10)
+       *dp = (*sp & 0xf) + '0';
+      else
+       {
+         dls_error (i, _("Format error in zoned decimal number."));
+         return false;
+       }
+
+    *dp = '\0';
+  }
+
+  /* Parse as number. */
+  {
+    char *tail;
+    
+    i->v->f = strtod (buf, &tail);
+    if (tail != i->e)
+      {
+       dls_error (i, _("Error in syntax of zoned decimal number."));
+       return false;
+      }
+  }
+
+  if (!got_dot)
+    apply_implied_decimals (i);
+
+  return true;
+}
+
+static inline bool
+parse_IB (struct data_in *i)
+{
+#ifndef WORDS_BIGENDIAN
+  char buf[64];
+#endif
+  const unsigned 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. */
+#ifdef WORDS_BIGENDIAN
+  p = (const unsigned char *) i->s;
+#else
+  memcpy (buf, i->s, i->e - i->s);
+  buf_reverse (buf, i->e - i->s);
+  p = (const unsigned char *) buf;
+#endif
+
+  /* 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);
+
+  apply_implied_decimals (i);
+
+  return true;
+}
+
+static inline bool
+parse_PIB (struct data_in *i)
+{
+  int j;
+
+  i->v->f = 0.0;
+#if WORDS_BIGENDIAN
+  for (j = 0; j < i->e - i->s; j++)
+    i->v->f = i->v->f * 256.0 + (unsigned char) i->s[j];
+#else
+  for (j = i->e - i->s - 1; j >= 0; j--)
+    i->v->f = i->v->f * 256.0 + (unsigned char) i->s[j];
+#endif
+
+  apply_implied_decimals (i);
+
+  return true;
+}
+
+static inline bool
+parse_P (struct data_in *i)
+{
+  const 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) & 15);
+      i->v->f = i->v->f * 10 + (*cp & 15);
+    }
+  i->v->f = i->v->f * 10 + ((*cp >> 4) & 15);
+  if ((*cp ^ (*cp >> 1)) & 0x10)
+      i->v->f = -i->v->f;
+
+  apply_implied_decimals (i);
+
+  return true;
+}
+
+static inline bool
+parse_PK (struct data_in *i)
+{
+  const char *cp;
+
+  i->v->f = 0.0;
+  for (cp = i->s; cp < i->e; cp++)
+    {
+      i->v->f = i->v->f * 10 + ((*cp >> 4) & 15);
+      i->v->f = i->v->f * 10 + (*cp & 15);
+    }
+
+  apply_implied_decimals (i);
+
+  return true;
+}
+
+static inline bool
+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 (sizeof u.c, (size_t) (i->e - i->s)));
+  i->v->f = u.d;
+
+  return true;
+}
+
+static inline bool
+parse_A (struct data_in *i)
+{
+  buf_copy_rpad (i->v->s, i->format.w, i->s, i->e - i->s);
+  return true;
+}
+
+static inline bool
+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 false;
+    }
+
+  {
+    const char *cp;
+    
+    for (cp = i->s; cp < i->e; cp++)
+      if (!isxdigit ((unsigned char) *cp))
+       {
+         dls_error (i, _("Field must contain only hex digits."));
+         return false;
+       }
+  }
+  
+  {
+    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);
+  }
+  
+  return true;
+}
+\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 bool
+parse_leader (struct data_in *i)
+{
+  skip_whitespace (i);
+  return true;
+}
+
+static inline bool
+force_have_char (struct data_in *i)
+{
+  if (have_char (i))
+    return true;
+
+  dls_error (i, _("Unexpected end of field."));
+  return false;
+}
+
+static bool
+parse_int (struct data_in *i, long *result)
+{
+  bool negative = false;
+  
+  if (!force_have_char (i))
+    return false;
+
+  if (*i->s == '+')
+    {
+      i->s++;
+      force_have_char (i);
+    }
+  else if (*i->s == '-')
+    {
+      negative = true;
+      i->s++;
+      force_have_char (i);
+    }
+  
+  if (!isdigit ((unsigned char) *i->s))
+    {
+      dls_error (i, _("Digit expected in field."));
+      return false;
+    }
+
+  *result = 0;
+  for (;;)
+    {
+      *result = *result * 10 + (*i->s++ - '0');
+      if (!have_char (i) || !isdigit ((unsigned char) *i->s))
+       break;
+    }
+
+  if (negative)
+    *result = -*result;
+  return true;
+}
+
+static bool
+parse_day (struct data_in *i, long *day)
+{
+  if (!parse_int (i, day))
+    return false;
+  if (*day >= 1 && *day <= 31)
+    return true;
+
+  dls_error (i, _("Day (%ld) must be between 1 and 31."), *day);
+  return false;
+}
+
+static bool
+parse_day_count (struct data_in *i, long *day_count)
+{
+  return parse_int (i, day_count);
+}
+
+static bool
+parse_date_delimiter (struct data_in *i)
+{
+  bool delim = false;
+
+  while (have_char (i)
+        && (*i->s == '-' || *i->s == '/' || isspace ((unsigned char) *i->s)
+            || *i->s == '.' || *i->s == ','))
+    {
+      delim = true;
+      i->s++;
+    }
+  if (delim)
+    return true;
+
+  dls_error (i, _("Delimiter expected between fields in date."));
+  return false;
+}
+
+/* Association between a name and a value. */
+struct enum_name
+  {
+    const char *name;           /* Name. */
+    bool can_abbreviate;        /* True if name may be abbreviated. */
+    int value;                  /* Value associated with name. */
+  };
+
+/* Reads a name from I and sets *OUTPUT to the value associated
+   with that name.  Returns true if successful, false otherwise. */
+static bool
+parse_enum (struct data_in *i, const char *what,
+            const struct enum_name *enum_names,
+            long *output) 
+{
+  const char *name;
+  size_t length;
+  const struct enum_name *ep;
+
+  /* Consume alphabetic characters. */
+  name = i->s;
+  length = 0;
+  while (have_char (i) && isalpha ((unsigned char) *i->s)) 
+    {
+      length++;
+      i->s++; 
+    }
+  if (length == 0) 
+    {
+      dls_error (i, _("Parse error at `%c' expecting %s."), *i->s, what);
+      return false;
+    }
+
+  for (ep = enum_names; ep->name != NULL; ep++)
+    if ((ep->can_abbreviate
+         && lex_id_match_len (ep->name, strlen (ep->name), name, length))
+        || (!ep->can_abbreviate && length == strlen (ep->name)
+            && !buf_compare_case (name, ep->name, length)))
+      {
+        *output = ep->value;
+        return true;
+      }
+
+  dls_error (i, _("Unknown %s `%.*s'."), what, (int) length, name);
+  return false;
+}
+
+static bool
+parse_month (struct data_in *i, long *month)
+{
+  static const struct enum_name month_names[] = 
+    {
+      {"january", true, 1},
+      {"february", true, 2},
+      {"march", true, 3},
+      {"april", true, 4},
+      {"may", true, 5},
+      {"june", true, 6},
+      {"july", true, 7},
+      {"august", true, 8},
+      {"september", true, 9},
+      {"october", true, 10},
+      {"november", true, 11},
+      {"december", true, 12},
+
+      {"i", false, 1},
+      {"ii", false, 2},
+      {"iii", false, 3},
+      {"iv", false, 4},
+      {"iiii", false, 4},
+      {"v", false, 5},
+      {"vi", false, 6},
+      {"vii", false, 7},
+      {"viii", false, 8},
+      {"ix", false, 9},
+      {"viiii", false, 9},
+      {"x", false, 10},
+      {"xi", false, 11},
+      {"xii", false, 12},
+
+      {NULL, false, 0},
+    };
+
+  if (!force_have_char (i))
+    return false;
+  
+  if (isdigit ((unsigned char) *i->s))
+    {
+      if (!parse_int (i, month))
+       return false;
+      if (*month >= 1 && *month <= 12)
+       return true;
+      
+      dls_error (i, _("Month (%ld) must be between 1 and 12."), *month);
+      return false;
+    }
+  else 
+    return parse_enum (i, _("month"), month_names, month);
+}
+
+static bool
+parse_year (struct data_in *i, long *year)
+{
+  if (!parse_int (i, year))
+    return false;
+  
+  if (*year >= 0 && *year <= 199)
+    *year += 1900;
+  if (*year >= 1582 || *year <= 19999)
+    return true;
+
+  dls_error (i, _("Year (%ld) must be between 1582 and 19999."), *year);
+  return false;
+}
+
+static bool
+parse_trailer (struct data_in *i)
+{
+  skip_whitespace (i);
+  if (!have_char (i))
+    return true;
+  
+  dls_error (i, _("Trailing garbage \"%s\" following date."), i->s);
+  return false;
+}
+
+static bool
+parse_julian (struct data_in *i, long *julian)
+{
+  if (!parse_int (i, julian))
+    return false;
+   
+  {
+    int day = *julian % 1000;
+
+    if (day < 1 || day > 366)
+      {
+       dls_error (i, _("Julian day (%d) must be between 1 and 366."), day);
+       return false;
+      }
+  }
+  
+  {
+    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 false;
+      }
+  }
+
+  return true;
+}
+
+static bool
+parse_quarter (struct data_in *i, long *quarter)
+{
+  if (!parse_int (i, quarter))
+    return false;
+  if (*quarter >= 1 && *quarter <= 4)
+    return true;
+
+  dls_error (i, _("Quarter (%ld) must be between 1 and 4."), *quarter);
+  return false;
+}
+
+static bool
+parse_q_delimiter (struct data_in *i)
+{
+  skip_whitespace (i);
+  if (!have_char (i) || tolower ((unsigned char) *i->s) != 'q')
+    {
+      dls_error (i, _("`Q' expected between quarter and year."));
+      return false;
+    }
+  i->s++;
+  skip_whitespace (i);
+  return true;
+}
+
+static bool
+parse_week (struct data_in *i, long *week)
+{
+  if (!parse_int (i, week))
+    return false;
+  if (*week >= 1 && *week <= 53)
+    return true;
+
+  dls_error (i, _("Week (%ld) must be between 1 and 53."), *week);
+  return false;
+}
+
+static bool
+parse_wk_delimiter (struct data_in *i)
+{
+  skip_whitespace (i);
+  if (i->s + 1 >= i->e
+      || tolower ((unsigned char) i->s[0]) != 'w'
+      || tolower ((unsigned char) i->s[1]) != 'k')
+    {
+      dls_error (i, _("`WK' expected between week and year."));
+      return false;
+    }
+  i->s += 2;
+  skip_whitespace (i);
+  return true;
+}
+
+static bool
+parse_time_delimiter (struct data_in *i)
+{
+  bool delim = false;
+
+  while (have_char (i) && (*i->s == ':' || *i->s == '.'
+                           || isspace ((unsigned char) *i->s)))
+    {
+      delim = true;
+      i->s++;
+    }
+
+  if (delim)
+    return true;
+  
+  dls_error (i, _("Delimiter expected between fields in time."));
+  return false;
+}
+
+static bool
+parse_hour (struct data_in *i, long *hour)
+{
+  if (!parse_int (i, hour))
+    return false;
+  if (*hour >= 0)
+    return true;
+  
+  dls_error (i, _("Hour (%ld) must be positive."), *hour);
+  return false;
+}
+
+static bool
+parse_minute (struct data_in *i, long *minute)
+{
+  if (!parse_int (i, minute))
+    return false;
+  if (*minute >= 0 && *minute <= 59)
+    return true;
+  
+  dls_error (i, _("Minute (%ld) must be between 0 and 59."), *minute);
+  return false;
+}
+
+static bool
+parse_opt_second (struct data_in *i, double *second)
+{
+  bool delim = false;
+
+  char buf[64];
+  char *cp;
+
+  while (have_char (i)
+        && (*i->s == ':' || *i->s == '.' || isspace ((unsigned char) *i->s)))
+    {
+      delim = true;
+      i->s++;
+    }
+  
+  if (!delim || !isdigit ((unsigned char) *i->s))
+    {
+      *second = 0.0;
+      return true;
+    }
+
+  cp = buf;
+  while (have_char (i) && isdigit ((unsigned char) *i->s))
+    *cp++ = *i->s++;
+  if (have_char (i) && *i->s == '.')
+    *cp++ = *i->s++;
+  while (have_char (i) && isdigit ((unsigned char) *i->s))
+    *cp++ = *i->s++;
+  *cp = '\0';
+  
+  *second = strtod (buf, NULL);
+
+  return true;
+}
+
+static bool
+parse_hour24 (struct data_in *i, long *hour24)
+{
+  if (!parse_int (i, hour24))
+    return false;
+  if (*hour24 >= 0 && *hour24 <= 23)
+    return true;
+  
+  dls_error (i, _("Hour (%ld) must be between 0 and 23."), *hour24);
+  return false;
+}
+
+     
+static bool
+parse_weekday (struct data_in *i, long *weekday)
+{
+  static const struct enum_name weekday_names[] = 
+    {
+      {"sunday", true, 1},
+      {"su", true, 1},
+      {"monday", true, 2},
+      {"mo", true, 2},
+      {"tuesday", true, 3},
+      {"tu", true, 3},
+      {"wednesday", true, 4},
+      {"we", true, 4},
+      {"thursday", true, 5},
+      {"th", true, 5},
+      {"friday", true, 6},
+      {"fr", true, 6},
+      {"saturday", true, 7},
+      {"sa", true, 7},
+      
+      {NULL, false, 0},
+    };
+
+  return parse_enum (i, _("weekday"), weekday_names, weekday);
+}
+
+static bool
+parse_spaces (struct data_in *i)
+{
+  skip_whitespace (i);
+  return true;
+}
+
+static bool
+parse_sign (struct data_in *i, int *sign)
+{
+  if (!force_have_char (i))
+    return false;
+
+  switch (*i->s)
+    {
+    case '-':
+      i->s++;
+      *sign = -1;
+      break;
+
+    case '+':
+      i->s++;
+      /* fall through */
+
+    default:
+      *sign = 1;
+      break;
+    }
+
+  return true;
+}
+\f
+/* Date & time formats. */
+
+static void
+calendar_error (void *i_, const char *format, ...) 
+{
+  struct data_in *i = i_;
+  va_list args;
+
+  va_start (args, format);
+  vdls_error (i, format, args);
+  va_end (args);
+}
+
+static bool
+ymd_to_ofs (struct data_in *i, int year, int month, int day, double *ofs) 
+{
+  *ofs = calendar_gregorian_to_offset (year, month, day, calendar_error, i);
+  return *ofs != SYSMIS;
+}
+
+static bool
+ymd_to_date (struct data_in *i, int year, int month, int day, double *date) 
+{
+  if (ymd_to_ofs (i, year, month, day, date)) 
+    {
+      *date *= 60. * 60. * 24.;
+      return true; 
+    }
+  else
+    return false;
+}
+
+static bool
+parse_DATE (struct data_in *i)
+{
+  long day, month, year;
+
+  return (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)
+          && ymd_to_date (i, year, month, day, &i->v->f));
+}
+
+static bool
+parse_ADATE (struct data_in *i)
+{
+  long month, day, year;
+
+  return (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)
+          && ymd_to_date (i, year, month, day, &i->v->f));
+}
+
+static bool
+parse_EDATE (struct data_in *i)
+{
+  long month, day, year;
+
+  return (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)
+          && ymd_to_date (i, year, month, day, &i->v->f));
+}
+
+static bool
+parse_SDATE (struct data_in *i)
+{
+  long month, day, year;
+
+  return (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)
+          && ymd_to_date (i, year, month, day, &i->v->f));
+}
+
+static bool
+parse_JDATE (struct data_in *i)
+{
+  long julian;
+  double ofs;
+  
+  if (!parse_leader (i)
+      || !parse_julian (i, &julian)
+      || !parse_trailer (i)
+      || !ymd_to_ofs (i, julian / 1000, 1, 1, &ofs))
+    return false;
+
+  i->v->f = (ofs + julian % 1000 - 1) * 60. * 60. * 24.;
+  return true;
+}
+
+static bool
+parse_QYR (struct data_in *i)
+{
+  long quarter, year;
+
+  return (parse_leader (i)
+          && parse_quarter (i, &quarter)
+          && parse_q_delimiter (i)
+          && parse_year (i, &year)
+          && parse_trailer (i)
+          && ymd_to_date (i, year, (quarter - 1) * 3 + 1, 1, &i->v->f));
+}
+
+static bool
+parse_MOYR (struct data_in *i)
+{
+  long month, year;
+
+  return (parse_leader (i)
+          && parse_month (i, &month)
+          && parse_date_delimiter (i)
+          && parse_year (i, &year)
+          && parse_trailer (i)
+          && ymd_to_date (i, year, month, 1, &i->v->f));
+}
+
+static bool
+parse_WKYR (struct data_in *i)
+{
+  long week, year;
+  double ofs;
+
+  if (!parse_leader (i)
+      || !parse_week (i, &week)
+      || !parse_wk_delimiter (i)
+      || !parse_year (i, &year)
+      || !parse_trailer (i))
+    return false;
+
+  if (year != 1582) 
+    {
+      if (!ymd_to_ofs (i, year, 1, 1, &ofs))
+        return false;
+    }
+  else 
+    {
+      if (ymd_to_ofs (i, 1583, 1, 1, &ofs))
+        return false;
+      ofs -= 365;
+    }
+
+  i->v->f = (ofs + (week - 1) * 7) * 60. * 60. * 24.;
+  return true;
+}
+
+static bool
+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 false;
+
+  i->v->f = (hour * 60. * 60. + minute * 60. + second) * sign;
+  return true;
+}
+
+static bool
+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 false;
+
+  i->v->f = (day_count * 60. * 60. * 24.
+            + hour * 60. * 60.
+            + minute * 60.
+            + second) * sign;
+  return true;
+}
+
+static bool
+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)
+      || !ymd_to_date (i, year, month, day, &i->v->f))
+    return false;
+
+  i->v->f += hour24 * 60. * 60. + minute * 60. + second;
+  return true;
+}
+
+static bool
+parse_WKDAY (struct data_in *i)
+{
+  long weekday;
+
+  if (!parse_leader (i)
+      || !parse_weekday (i, &weekday)
+      || !parse_trailer (i))
+    return false;
+
+  i->v->f = weekday;
+  return true;
+}
+
+static bool
+parse_MONTH (struct data_in *i)
+{
+  long month;
+
+  if (!parse_leader (i)
+      || !parse_month (i, &month)
+      || !parse_trailer (i))
+    return false;
+
+  i->v->f = month;
+  return true;
+}
+\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)
+    memset (i->v->s, ' ', i->format.w);
+  else
+    i->v->f = get_blanks();
+}
+
+bool
+data_in (struct data_in *i)
+{
+  const struct fmt_desc *const fmt = &formats[i->format.type];
+
+  assert (check_input_specifier (&i->format, 0));
+
+  /* Check that we've got a string to work with. */
+  if (i->e == i->s || i->format.w <= 0)
+    {
+      default_result (i);
+      return true;
+    }
+
+  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 char *cp;
+
+      cp = i->s;
+      for (;;)
+       {
+         if (!isspace ((unsigned char) *cp))
+           break;
+
+         if (++cp == i->e)
+           {
+             i->v->f = get_blanks();
+             return true;
+           }
+       }
+    }
+  
+  {
+    static bool (*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,
+      };
+
+    bool (*handler)(struct data_in *);
+    bool 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/data-in.h b/src/data/data-in.h
new file mode 100644 (file)
index 0000000..287b2fb
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !data_in_h
+#define data_in_h 1
+
+#include <stddef.h>
+#include <stdbool.h>
+#include "format.h"
+
+/* Flags. */
+enum
+  {
+    DI_IGNORE_ERROR = 01,      /* Don't report errors to the user. */
+    DI_IMPLIED_DECIMALS = 02    /* Insert decimals if no '.' in input. */
+  };
+
+/* Information about parsing one data field. */
+struct data_in
+  {
+    const char *s;              /* Source start. */
+    const 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. */
+  };
+
+bool data_in (struct data_in *);
+
+void data_in_finite_line (struct data_in *di, const char *line, size_t len,
+                         int fc, int lc);
+
+#endif /* data-in.h */
diff --git a/src/data/data-out.c b/src/data/data-out.c
new file mode 100644 (file)
index 0000000..7dfcc78
--- /dev/null
@@ -0,0 +1,1256 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <ctype.h>
+#include <math.h>
+#include <float.h>
+#include <stdlib.h>
+#include <time.h>
+#include "calendar.h"
+#include "message.h"
+#include "format.h"
+#include "magic.h"
+#include "misc.h"
+#include "misc.h"
+#include "settings.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+\f
+/* Public functions. */
+
+typedef int numeric_converter (char *, const struct fmt_spec *, double);
+static numeric_converter convert_F, convert_N, convert_E, convert_F_plus;
+static numeric_converter convert_Z, convert_IB, convert_P, convert_PIB;
+static numeric_converter convert_PIBHEX, convert_PK, convert_RB;
+static numeric_converter convert_RBHEX, convert_CCx, convert_date;
+static numeric_converter convert_time, convert_WKDAY, convert_MONTH;
+
+static numeric_converter try_F, convert_infinite;
+
+typedef int string_converter (char *, const struct fmt_spec *, const char *);
+static string_converter convert_A, convert_AHEX;
+
+/* Converts binary value V into printable form in the exactly
+   FP->W character in buffer S according to format specification
+   FP.  No null terminator is appended to the buffer.  */
+bool
+data_out (char *s, const struct fmt_spec *fp, const union value *v)
+{
+  int cat = formats[fp->type].cat;
+  int ok;
+
+  assert (check_output_specifier (fp, 0));
+  if (!(cat & FCAT_STRING)) 
+    {
+      /* Numeric formatting. */
+      double number = v->f;
+
+      /* Handle SYSMIS turning into blanks. */
+      if ((cat & FCAT_BLANKS_SYSMIS) && number == SYSMIS)
+        {
+          memset (s, ' ', fp->w);
+          s[fp->w - fp->d - 1] = '.';
+          return true;
+        }
+
+      /* Handle decimal shift. */
+      if ((cat & FCAT_SHIFT_DECIMAL) && number != SYSMIS && fp->d)
+        number *= pow (10.0, fp->d);
+
+      switch (fp->type) 
+        {
+        case FMT_F:
+          ok = convert_F (s, fp, number);
+          break;
+
+        case FMT_N:
+          ok = convert_N (s, fp, number);
+          break;
+
+        case FMT_E:
+          ok = convert_E (s, fp, number);
+          break;
+
+        case FMT_COMMA: case FMT_DOT: case FMT_DOLLAR: case FMT_PCT:
+          ok = convert_F_plus (s, fp, number);
+          break;
+
+        case FMT_Z:
+          ok = convert_Z (s, fp, number);
+          break;
+
+        case FMT_A:
+          assert (0);
+          abort ();
+
+        case FMT_AHEX:
+          assert (0);
+          abort ();
+
+        case FMT_IB:
+          ok = convert_IB (s, fp, number);
+          break;
+
+        case FMT_P:
+          ok = convert_P (s, fp, number);
+          break;
+
+        case FMT_PIB:
+          ok = convert_PIB (s, fp, number);
+          break;
+
+        case FMT_PIBHEX:
+          ok = convert_PIBHEX (s, fp, number);
+          break;
+
+        case FMT_PK:
+          ok = convert_PK (s, fp, number);
+          break;
+
+        case FMT_RB:
+          ok = convert_RB (s, fp, number);
+          break;
+
+        case FMT_RBHEX:
+          ok = convert_RBHEX (s, fp, number);
+          break;
+
+        case FMT_CCA: case FMT_CCB: case FMT_CCC: case FMT_CCD: case FMT_CCE:
+          ok = convert_CCx (s, fp, number);
+          break;
+
+        case FMT_DATE: case FMT_EDATE: case FMT_SDATE: case FMT_ADATE:
+        case FMT_JDATE: case FMT_QYR: case FMT_MOYR: case FMT_WKYR:
+        case FMT_DATETIME: 
+          ok = convert_date (s, fp, number);
+          break;
+
+        case FMT_TIME: case FMT_DTIME:
+          ok = convert_time (s, fp, number);
+          break;
+
+        case FMT_WKDAY:
+          ok = convert_WKDAY (s, fp, number);
+          break;
+
+        case FMT_MONTH:
+          ok = convert_MONTH (s, fp, number);
+          break;
+
+        default:
+          assert (0);
+          abort ();
+        }
+    }
+  else 
+    {
+      /* String formatting. */
+      const char *string = v->s;
+
+      switch (fp->type) 
+        {
+        case FMT_A:
+          ok = convert_A (s, fp, string);
+          break;
+
+        case FMT_AHEX:
+          ok = convert_AHEX (s, fp, string);
+          break;
+
+        default:
+          assert (0);
+          abort ();
+        }
+    }
+
+  /* Error handling. */
+  if (!ok)
+    strncpy (s, "ERROR", fp->w);
+  
+  return ok;
+}
+
+/* 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)
+{
+  struct fmt_spec f = make_output_format (FMT_F, w, d);
+  convert_F (s, &f, v);
+}
+\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
+
+/* 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, double number)
+{
+  if (!try_F (dst, fp, number))
+    convert_E (dst, fp, number);
+  return 1;
+}
+
+/* Handles N format. */
+static int
+convert_N (char *dst, const struct fmt_spec *fp, double number)
+{
+  double d = floor (number);
+
+  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, number);
+      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, double number)
+{
+  /* Temporary buffer. */
+  char buf[128];
+  
+  /* Ranged number of decimal places. */
+  int d;
+
+  if (!finite (number))
+    return convert_infinite (dst, fp, number);
+
+  /* Check that the format is wide 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 (number < 0)
+    d--;
+  if (d < 0)
+    d = 0;
+  sprintf (buf, "%*.*E", fp->w, d, number);
+
+  /* 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 (number) > 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 ((get_decimal() == ',' && fp->type != FMT_DOT)
+      || (get_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, double number)
+{
+  char buf[40];
+  
+  if (try_F (buf, fp, number))
+    insert_commas (dst, buf, fp);
+  else
+    convert_E (dst, fp, number);
+
+  return 1;
+}
+
+static int
+convert_Z (char *dst, const struct fmt_spec *fp, double number)
+{
+  static int warned = 0;
+
+  if (!warned)
+    {
+      msg (MW, 
+       _("Quality of zoned decimal (Z) output format code is "
+          "suspect.  Check your results. Report bugs to %s."),
+       PACKAGE_BUGREPORT);
+      warned = 1;
+    }
+
+  if (number == 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 (number));
+    if (d >= power10[fp->w])
+      {
+       msg (ME, _("Number %g too big to fit in field with format Z%d.%d."),
+            number, fp->w, fp->d);
+       return 0;
+      }
+
+    sprintf (buf, "%*.0f", fp->w, number);
+    for (i = 0; i < fp->w; i++)
+      dst[i] = (buf[i] - '0') | 0xf0;
+    if (number < 0)
+      dst[fp->w - 1] &= 0xdf;
+  }
+
+  return 1;
+}
+
+static int
+convert_A (char *dst, const struct fmt_spec *fp, const char *string)
+{
+  memcpy (dst, string, fp->w);
+  return 1;
+}
+
+static int
+convert_AHEX (char *dst, const struct fmt_spec *fp, const char *string)
+{
+  int i;
+
+  for (i = 0; i < fp->w / 2; i++)
+    {
+      *dst++ = MAKE_HEXIT ((string[i]) >> 4);
+      *dst++ = MAKE_HEXIT ((string[i]) & 0xf);
+    }
+
+  return 1;
+}
+
+static int
+convert_IB (char *dst, const struct fmt_spec *fp, double number)
+{
+  /* Strategy: Basically the same as convert_PIBHEX() but with
+     base 256. Then negate the two's-complement result if number
+     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 (number), &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 number is negative. */
+  if (number < 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);
+#ifndef WORDS_BIGENDIAN
+  buf_reverse (dst, fp->w);
+#endif
+
+  return 1;
+}
+
+static int
+convert_P (char *dst, const struct fmt_spec *fp, double number)
+{
+  /* Buffer for fp->w*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 (number)));
+
+  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 (number >= 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, double number)
+{
+  /* 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 (number), &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);
+    }
+#ifndef WORDS_BIGENDIAN
+  buf_reverse (dst, fp->w);
+#endif
+
+  return 1;
+}
+
+static int
+convert_PIBHEX (char *dst, const struct fmt_spec *fp, double number)
+{
+  /* 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 (number), &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, double number)
+{
+  /* Buffer for fp->w*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 (number)));
+
+  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, double number)
+{
+  union
+    {
+      double d;
+      char c[8];
+    }
+  u;
+
+  u.d = number;
+  memcpy (dst, u.c, fp->w);
+
+  return 1;
+}
+
+static int
+convert_RBHEX (char *dst, const struct fmt_spec *fp, double number)
+{
+  union
+  {
+    double d;
+    char c[8];
+  }
+  u;
+
+  int i;
+
+  u.d = number;
+  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, double number)
+{
+  if (try_CCx (dst, fp, number))
+    return 1;
+  else
+    {
+      struct fmt_spec f;
+      
+      f.type = FMT_COMMA;
+      f.w = fp->w;
+      f.d = fp->d;
+  
+      return convert_F_plus (dst, &f, number);
+    }
+}
+
+static int
+convert_date (char *dst, const struct fmt_spec *fp, double number)
+{
+  static const char *months[12] =
+    {
+      "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
+      "JUL", "AUG", "SEP", "OCT", "NOV", "DEC",
+    };
+
+  char buf[64] = {0};
+  int ofs = number / 86400.;
+  int month, day, year;
+
+  if (ofs < 1)
+    return 0;
+
+  calendar_offset_to_gregorian (ofs, &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 = calendar_offset_to_yday (ofs);
+       
+        if (fp->w < 7)
+          sprintf (buf, "%02d%03d", year % 100, yday); 
+        else if (year4 (year))
+          sprintf (buf, "%04d%03d", year, yday);
+        else
+       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 = calendar_offset_to_yday (ofs);
+       
+       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 (number / 60. / 60.), 24.),
+                      (int) fmod (floor (number / 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 (number, 60.));
+         }
+      }
+      break;
+    default:
+      assert (0);
+    }
+
+  if (buf[0] == 0)
+    return 0;
+  buf_copy_str_rpad (dst, fp->w, buf);
+  return 1;
+}
+
+static int
+convert_time (char *dst, const struct fmt_spec *fp, double number)
+{
+  char temp_buf[40];
+  char *cp;
+
+  double time;
+  int width;
+
+  if (fabs (number) > 1e20)
+    {
+      msg (ME, _("Time value %g too large in magnitude to convert to "
+          "alphanumeric time."), number);
+      return 0;
+    }
+
+  time = number;
+  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.));
+    }
+  buf_copy_str_rpad (dst, fp->w, temp_buf);
+
+  return 1;
+}
+
+static int
+convert_WKDAY (char *dst, const struct fmt_spec *fp, double wkday)
+{
+  static const char *weekdays[7] =
+    {
+      "SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY",
+      "THURSDAY", "FRIDAY", "SATURDAY",
+    };
+
+  if (wkday < 1 || wkday > 7)
+    {
+      msg (ME, _("Weekday index %f does not lie between 1 and 7."),
+           (double) wkday);
+      return 0;
+    }
+  buf_copy_str_rpad (dst, fp->w, weekdays[(int) wkday - 1]);
+
+  return 1;
+}
+
+static int
+convert_MONTH (char *dst, const struct fmt_spec *fp, double month)
+{
+  static const char *months[12] =
+    {
+      "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE",
+      "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER",
+    };
+
+  if (month < 1 || month > 12)
+    {
+      msg (ME, _("Month index %f does not lie between 1 and 12."),
+           month);
+      return 0;
+    }
+  
+  buf_copy_str_rpad (dst, fp->w, months[(int) month - 1]);
+
+  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 ? get_grouping() : get_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 number)
+{
+  const struct custom_currency *cc = get_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 == get_decimal () ? FMT_COMMA : FMT_DOT;
+  f.w = fp->w - strlen (cc->prefix) - strlen (cc->suffix);
+  if (number < 0)
+    f.w -= strlen (cc->neg_prefix) + strlen (cc->neg_suffix) - 1;
+  else
+    /* Convert -0 to +0. */
+    number = fabs (number);
+  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, number) || *buf == '*')
+    return 0;
+  insert_commas (buf2, buf, &f);
+
+  /* Postprocess back into buf. */
+  cp = buf;
+  if (number < 0)
+    cp = stpcpy (cp, cc->neg_prefix);
+  cp = stpcpy (cp, cc->prefix);
+  {
+    char *bp = buf2;
+    while (*bp == ' ')
+      bp++;
+
+    assert ((number >= 0) ^ (*bp == '-'));
+    if (number < 0)
+      bp++;
+
+    memcpy (cp, bp, f.w - (bp - buf2));
+    cp += f.w - (bp - buf2);
+  }
+  cp = stpcpy (cp, cc->suffix);
+  if (number < 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;
+}
+
+static int
+format_and_round (char *dst, double number, const struct fmt_spec *fp,
+                  int decimals);
+
+/* Tries to format NUMBER into DST as the F format specified in
+   *FP.  Return true if successful, false on failure. */
+static int
+try_F (char *dst, const struct fmt_spec *fp, double number)
+{
+  assert (fp->w <= 40);
+  if (finite (number)) 
+    {
+      if (fabs (number) < power10[fp->w])
+        {
+          /* The value may fit in the field. */
+          if (fp->d == 0) 
+            {
+              /* There are no decimal places, so there's no way
+                 that the value can be shortened.  Either it fits
+                 or it doesn't. */
+              char buf[41];
+              sprintf (buf, "%*.0f", fp->w, number);
+              if (strlen (buf) <= fp->w) 
+                {
+                  buf_copy_str_lpad (dst, fp->w, buf);
+                  return true; 
+                }
+              else 
+                return false;
+            }
+          else 
+            {
+              /* First try to format it with 2 extra decimal
+                 places.  This gives us a good chance of not
+                 needing even more decimal places, but it also
+                 avoids wasting too much time formatting more
+                 decimal places on the first try. */
+              int result = format_and_round (dst, number, fp, fp->d + 2);
+              if (result >= 0)
+                return result;
+
+              /* 2 extra decimal places weren't enough to
+                 correctly round.  Try again with the maximum
+                 number of places. */
+              return format_and_round (dst, number, fp, LDBL_DIG + 1);
+            }
+        }
+      else 
+        {
+          /* The value is too big to fit in the field. */
+          return false;
+        }
+    }
+  else
+    return convert_infinite (dst, fp, number);
+}
+
+/* Tries to compose NUMBER into DST in format FP by first
+   formatting it with DECIMALS decimal places, then rounding off
+   to as many decimal places will fit or the number specified in
+   FP, whichever is fewer.
+
+   Returns 1 if conversion succeeds, 0 if this try at conversion
+   failed and so will any other tries (because the integer part
+   of the number is too long), or -1 if this try failed but
+   another with higher DECIMALS might succeed (because we'd be
+   able to properly round). */
+static int
+format_and_round (char *dst, double number, const struct fmt_spec *fp,
+                  int decimals)
+{
+  /* Number of characters before the decimal point,
+     which includes digits and possibly a minus sign. */
+  int predot_chars;
+
+  /* Number of digits in the output fraction,
+     which may be smaller than fp->d if there's not enough room. */
+  int fraction_digits;
+
+  /* Points to last digit that will remain in the fraction after
+     rounding. */
+  char *final_frac_dig;
+
+  /* Round up? */
+  bool round_up;
+  
+  char buf[128];
+  
+  assert (decimals > fp->d);
+  if (decimals > LDBL_DIG)
+    decimals = LDBL_DIG + 1;
+
+  sprintf (buf, "%.*f", decimals, number);
+
+  /* Omit integer part if it's 0. */
+  if (!memcmp (buf, "0.", 2))
+    memmove (buf, buf + 1, strlen (buf));
+  else if (!memcmp (buf, "-0.", 3))
+    memmove (buf + 1, buf + 2, strlen (buf + 1));
+
+  predot_chars = strcspn (buf, ".");
+  if (predot_chars > fp->w) 
+    {
+      /* Can't possibly fit. */
+      return 0; 
+    }
+  else if (predot_chars == fp->w)
+    {
+      /* Exact fit for integer part and sign. */
+      memcpy (dst, buf, fp->w);
+      return 1;
+    }
+  else if (predot_chars + 1 == fp->w) 
+    {
+      /* There's room for the decimal point, but not for any
+         digits of the fraction.
+         Right-justify the integer part and sign. */
+      dst[0] = ' ';
+      memcpy (dst + 1, buf, fp->w);
+      return 1;
+    }
+
+  /* It looks like we have room for at least one digit of the
+     fraction.  Figure out how many. */
+  fraction_digits = fp->w - predot_chars - 1;
+  if (fraction_digits > fp->d)
+    fraction_digits = fp->d;
+  final_frac_dig = buf + predot_chars + fraction_digits;
+
+  /* Decide rounding direction and truncate string. */
+  if (final_frac_dig[1] == '5'
+      && strspn (final_frac_dig + 2, "0") == strlen (final_frac_dig + 2)) 
+    {
+      /* Exactly 1/2. */
+      if (decimals <= LDBL_DIG)
+        {
+          /* Don't have enough fractional digits to know which way to
+             round.  We can format with more decimal places, so go
+             around again. */
+          return -1;
+        }
+      else 
+        {
+          /* We used up all our fractional digits and still don't
+             know.  Round to even. */
+          round_up = (final_frac_dig[0] - '0') % 2 != 0;
+        }
+    }
+  else
+    round_up = final_frac_dig[1] >= '5';
+  final_frac_dig[1] = '\0';
+
+  /* Do rounding. */
+  if (round_up) 
+    {
+      char *cp = final_frac_dig;
+      for (;;) 
+        {
+          if (*cp >= '0' && *cp <= '8')
+            {
+              (*cp)++;
+              break; 
+            }
+          else if (*cp == '9') 
+            *cp = '0';
+          else
+            assert (*cp == '.');
+
+          if (cp == buf || *--cp == '-')
+            {
+              size_t length;
+              
+              /* Tried to go past the leftmost digit.  Insert a 1. */
+              memmove (cp + 1, cp, strlen (cp) + 1);
+              *cp = '1';
+
+              length = strlen (buf);
+              if (length > fp->w) 
+                {
+                  /* Inserting the `1' overflowed our space.
+                     Drop a decimal place. */
+                  buf[--length] = '\0';
+
+                  /* If that was the last decimal place, drop the
+                     decimal point too. */
+                  if (buf[length - 1] == '.')
+                    buf[length - 1] = '\0';
+                }
+              
+              break;
+            }
+        }
+    }
+
+  /* Omit `-' if value output is zero. */
+  if (buf[0] == '-' && buf[strspn (buf, "-.0")] == '\0')
+    memmove (buf, buf + 1, strlen (buf));
+
+  buf_copy_str_lpad (dst, fp->w, buf);
+  return 1;
+}
+
+/* Formats non-finite NUMBER into DST according to the width
+   given in FP. */
+static int
+convert_infinite (char *dst, const struct fmt_spec *fp, double number)
+{
+  assert (!finite (number));
+  
+  if (fp->w >= 3)
+    {
+      const char *s;
+
+      if (isnan (number))
+        s = "NaN";
+      else if (isinf (number))
+        s = number > 0 ? "+Infinity" : "-Infinity";
+      else
+        s = "Unknown";
+
+      buf_copy_str_lpad (dst, fp->w, s);
+    }
+  else 
+    memset (dst, '*', fp->w);
+
+  return true;
+}
diff --git a/src/data/dictionary.c b/src/data/dictionary.c
new file mode 100644 (file)
index 0000000..e0afe9b
--- /dev/null
@@ -0,0 +1,1211 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "dictionary.h"
+#include <stdlib.h>
+#include <ctype.h>
+#include "array.h"
+#include "alloc.h"
+#include "case.h"
+#include "category.h"
+#include "cat-routines.h"
+#include "message.h"
+#include "hash.h"
+#include "misc.h"
+#include "settings.h"
+#include "str.h"
+#include "value-labels.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* A dictionary. */
+struct dictionary
+  {
+    struct variable **var;     /* Variables. */
+    size_t var_cnt, var_cap;    /* Number of variables, capacity. */
+    struct hsh_table *name_tab;        /* Variable index by name. */
+    int next_value_idx;         /* Index of next `union value' to allocate. */
+    struct variable **split;    /* SPLIT FILE vars. */
+    size_t split_cnt;           /* SPLIT FILE count. */
+    struct variable *weight;    /* WEIGHT variable. */
+    struct variable *filter;    /* FILTER variable. */
+    int case_limit;             /* Current case limit (N command). */
+    char *label;               /* File label. */
+    char *documents;           /* Documents, as a string. */
+    struct vector **vector;     /* Vectors of variables. */
+    size_t vector_cnt;          /* Number of vectors. */
+  };
+
+/* Active file dictionary. */
+struct dictionary *default_dict;
+
+/* Creates and returns a new dictionary. */
+struct dictionary *
+dict_create (void) 
+{
+  struct dictionary *d = xmalloc (sizeof *d);
+  
+  d->var = NULL;
+  d->var_cnt = d->var_cap = 0;
+  d->name_tab = hsh_create (8, compare_var_names, hash_var_name, NULL, NULL);
+  d->next_value_idx = 0;
+  d->split = NULL;
+  d->split_cnt = 0;
+  d->weight = NULL;
+  d->filter = NULL;
+  d->case_limit = 0;
+  d->label = NULL;
+  d->documents = NULL;
+  d->vector = NULL;
+  d->vector_cnt = 0;
+
+  return d;
+}
+
+/* Creates and returns a (deep) copy of an existing
+   dictionary. */
+struct dictionary *
+dict_clone (const struct dictionary *s) 
+{
+  struct dictionary *d;
+  size_t i;
+
+  assert (s != NULL);
+
+  d = dict_create ();
+
+  for (i = 0; i < s->var_cnt; i++) 
+    {
+      struct variable *sv = s->var[i];
+      struct variable *dv = dict_clone_var_assert (d, sv, sv->name);
+      var_set_short_name (dv, sv->short_name);
+    }
+
+  d->next_value_idx = s->next_value_idx;
+
+  d->split_cnt = s->split_cnt;
+  if (d->split_cnt > 0) 
+    {
+      d->split = xnmalloc (d->split_cnt, sizeof *d->split);
+      for (i = 0; i < d->split_cnt; i++) 
+        d->split[i] = dict_lookup_var_assert (d, s->split[i]->name);
+    }
+
+  if (s->weight != NULL) 
+    d->weight = dict_lookup_var_assert (d, s->weight->name);
+
+  if (s->filter != NULL) 
+    d->filter = dict_lookup_var_assert (d, s->filter->name);
+
+  d->case_limit = s->case_limit;
+  dict_set_label (d, dict_get_label (s));
+  dict_set_documents (d, dict_get_documents (s));
+
+  d->vector_cnt = s->vector_cnt;
+  d->vector = xnmalloc (d->vector_cnt, sizeof *d->vector);
+  for (i = 0; i < s->vector_cnt; i++) 
+    {
+      struct vector *sv = s->vector[i];
+      struct vector *dv = d->vector[i] = xmalloc (sizeof *dv);
+      int j;
+      
+      dv->idx = i;
+      strcpy (dv->name, sv->name);
+      dv->cnt = sv->cnt;
+      dv->var = xnmalloc (dv->cnt, sizeof *dv->var);
+      for (j = 0; j < dv->cnt; j++)
+        dv->var[j] = d->var[sv->var[j]->index];
+    }
+
+  return d;
+}
+
+/* Clears the contents from a dictionary without destroying the
+   dictionary itself. */
+void
+dict_clear (struct dictionary *d) 
+{
+  /* FIXME?  Should we really clear case_limit, label, documents?
+     Others are necessarily cleared by deleting all the variables.*/
+  int i;
+
+  assert (d != NULL);
+
+  for (i = 0; i < d->var_cnt; i++) 
+    {
+      struct variable *v = d->var[i];
+      var_clear_aux (v);
+      val_labs_destroy (v->val_labs);
+      free (v->label);
+      free (v); 
+    }
+  free (d->var);
+  d->var = NULL;
+  d->var_cnt = d->var_cap = 0;
+  hsh_clear (d->name_tab);
+  d->next_value_idx = 0;
+  free (d->split);
+  d->split = NULL;
+  d->split_cnt = 0;
+  d->weight = NULL;
+  d->filter = NULL;
+  d->case_limit = 0;
+  free (d->label);
+  d->label = NULL;
+  free (d->documents);
+  d->documents = NULL;
+  dict_clear_vectors (d);
+}
+
+/* Destroys the aux data for every variable in D, by calling
+   var_clear_aux() for each variable. */
+void
+dict_clear_aux (struct dictionary *d) 
+{
+  int i;
+  
+  assert (d != NULL);
+  
+  for (i = 0; i < d->var_cnt; i++)
+    var_clear_aux (d->var[i]);
+}
+
+/* Clears a dictionary and destroys it. */
+void
+dict_destroy (struct dictionary *d)
+{
+  if (d != NULL) 
+    {
+      dict_clear (d);
+      hsh_destroy (d->name_tab);
+      free (d);
+    }
+}
+
+/* Returns the number of variables in D. */
+size_t
+dict_get_var_cnt (const struct dictionary *d) 
+{
+  assert (d != NULL);
+
+  return d->var_cnt;
+}
+
+/* Returns the variable in D with index IDX, which must be
+   between 0 and the count returned by dict_get_var_cnt(),
+   exclusive. */
+struct variable *
+dict_get_var (const struct dictionary *d, size_t idx) 
+{
+  assert (d != NULL);
+  assert (idx < d->var_cnt);
+
+  return d->var[idx];
+}
+
+/* Sets *VARS to an array of pointers to variables in D and *CNT
+   to the number of variables in *D.  By default all variables
+   are returned, but bits may be set in EXCLUDE_CLASSES to
+   exclude ordinary, system, and/or scratch variables. */
+void
+dict_get_vars (const struct dictionary *d, struct variable ***vars,
+               size_t *cnt, unsigned exclude_classes)
+{
+  size_t count;
+  size_t i;
+  
+  assert (d != NULL);
+  assert (vars != NULL);
+  assert (cnt != NULL);
+  assert ((exclude_classes & ~((1u << DC_ORDINARY)
+                               | (1u << DC_SYSTEM)
+                               | (1u << DC_SCRATCH))) == 0);
+  
+  count = 0;
+  for (i = 0; i < d->var_cnt; i++)
+    if (!(exclude_classes & (1u << dict_class_from_id (d->var[i]->name))))
+      count++;
+
+  *vars = xnmalloc (count, sizeof **vars);
+  *cnt = 0;
+  for (i = 0; i < d->var_cnt; i++)
+    if (!(exclude_classes & (1u << dict_class_from_id (d->var[i]->name))))
+      (*vars)[(*cnt)++] = d->var[i];
+  assert (*cnt == count);
+}
+
+
+/* Creates and returns a new variable in D with the given NAME
+   and WIDTH.  Returns a null pointer if the given NAME would
+   duplicate that of an existing variable in the dictionary. */
+struct variable *
+dict_create_var (struct dictionary *d, const char *name, int width)
+{
+  struct variable *v;
+
+  assert (d != NULL);
+  assert (name != NULL);
+
+  assert (width >= 0 && width < 256);
+
+  assert (var_is_valid_name(name,0));
+    
+  /* Make sure there's not already a variable by that name. */
+  if (dict_lookup_var (d, name) != NULL)
+    return NULL;
+
+  /* Allocate and initialize variable. */
+  v = xmalloc (sizeof *v);
+  str_copy_trunc (v->name, sizeof v->name, name);
+  v->type = width == 0 ? NUMERIC : ALPHA;
+  v->width = width;
+  v->fv = d->next_value_idx;
+  v->nv = width == 0 ? 1 : DIV_RND_UP (width, 8);
+  v->init = 1;
+  v->reinit = dict_class_from_id (v->name) != DC_SCRATCH;
+  v->index = d->var_cnt;
+  mv_init (&v->miss, width);
+  if (v->type == NUMERIC)
+    {
+      v->print = f8_2;
+      v->alignment = ALIGN_RIGHT;
+      v->display_width = 8;
+      v->measure = MEASURE_SCALE;
+    }
+  else
+    {
+      v->print = make_output_format (FMT_A, v->width, 0);
+      v->alignment = ALIGN_LEFT;
+      v->display_width = 8;
+      v->measure = MEASURE_NOMINAL;
+    }
+  v->write = v->print;
+  v->val_labs = val_labs_create (v->width);
+  v->label = NULL;
+  var_clear_short_name (v);
+  v->aux = NULL;
+  v->aux_dtor = NULL;
+  v->obs_vals = NULL;
+
+  /* Update dictionary. */
+  if (d->var_cnt >= d->var_cap) 
+    {
+      d->var_cap = 8 + 2 * d->var_cap; 
+      d->var = xnrealloc (d->var, d->var_cap, sizeof *d->var);
+    }
+  d->var[v->index] = v;
+  d->var_cnt++;
+  hsh_force_insert (d->name_tab, v);
+
+  d->next_value_idx += v->nv;
+
+  return v;
+}
+
+/* Creates and returns a new variable in D with the given NAME
+   and WIDTH.  Assert-fails if the given NAME would duplicate
+   that of an existing variable in the dictionary. */
+struct variable *
+dict_create_var_assert (struct dictionary *d, const char *name, int width)
+{
+  struct variable *v = dict_create_var (d, name, width);
+  assert (v != NULL);
+  return v;
+}
+
+/* Creates and returns a new variable in D with name NAME, as a
+   copy of existing variable OV, which need not be in D or in any
+   dictionary.  Returns a null pointer if the given NAME would
+   duplicate that of an existing variable in the dictionary. */
+struct variable *
+dict_clone_var (struct dictionary *d, const struct variable *ov,
+                const char *name)
+{
+  struct variable *nv;
+
+  assert (d != NULL);
+  assert (ov != NULL);
+  assert (name != NULL);
+
+  assert (strlen (name) >= 1);
+  assert (strlen (name) <= LONG_NAME_LEN);
+
+  nv = dict_create_var (d, name, ov->width);
+  if (nv == NULL)
+    return NULL;
+
+  /* Copy most members not copied via dict_create_var().
+     short_name[] is intentionally not copied, because there is
+     no reason to give a new variable with potentially a new name
+     the same short name. */
+  nv->init = 1;
+  nv->reinit = ov->reinit;
+  mv_copy (&nv->miss, &ov->miss);
+  nv->print = ov->print;
+  nv->write = ov->write;
+  val_labs_destroy (nv->val_labs);
+  nv->val_labs = val_labs_copy (ov->val_labs);
+  if (ov->label != NULL)
+    nv->label = xstrdup (ov->label);
+  nv->measure = ov->measure;
+  nv->display_width = ov->display_width;
+  nv->alignment = ov->alignment;
+
+  return nv;
+}
+
+/* Creates and returns a new variable in D with name NAME, as a
+   copy of existing variable OV, which need not be in D or in any
+   dictionary.  Assert-fails if the given NAME would duplicate
+   that of an existing variable in the dictionary. */
+struct variable *
+dict_clone_var_assert (struct dictionary *d, const struct variable *ov,
+                       const char *name)
+{
+  struct variable *v = dict_clone_var (d, ov, name);
+  assert (v != NULL);
+  return v;
+}
+
+/* Returns the variable named NAME in D, or a null pointer if no
+   variable has that name. */
+struct variable *
+dict_lookup_var (const struct dictionary *d, const char *name)
+{
+  struct variable v;
+  
+  assert (d != NULL);
+  assert (name != NULL);
+
+  str_copy_trunc (v.name, sizeof v.name, name);
+  return hsh_find (d->name_tab, &v);
+}
+
+/* Returns the variable named NAME in D.  Assert-fails if no
+   variable has that name. */
+struct variable *
+dict_lookup_var_assert (const struct dictionary *d, const char *name)
+{
+  struct variable *v = dict_lookup_var (d, name);
+  assert (v != NULL);
+  return v;
+}
+
+/* Returns true if variable V is in dictionary D,
+   false otherwise. */
+bool
+dict_contains_var (const struct dictionary *d, const struct variable *v)
+{
+  assert (d != NULL);
+  assert (v != NULL);
+
+  return v->index >= 0 && v->index < d->var_cnt && d->var[v->index] == v;
+}
+
+/* Compares two double pointers to variables, which should point
+   to elements of a struct dictionary's `var' member array. */
+static int
+compare_var_ptrs (const void *a_, const void *b_, void *aux UNUSED) 
+{
+  struct variable *const *a = a_;
+  struct variable *const *b = b_;
+
+  return *a < *b ? -1 : *a > *b;
+}
+
+/* Deletes variable V from dictionary D and frees V.
+
+   This is a very bad idea if there might be any pointers to V
+   from outside D.  In general, no variable in default_dict
+   should be deleted when any transformations are active, because
+   those transformations might reference the deleted variable.
+   The safest time to delete a variable is just after a procedure
+   has been executed, as done by MODIFY VARS.
+
+   Pointers to V within D are not a problem, because
+   dict_delete_var() knows to remove V from split variables,
+   weights, filters, etc. */
+void
+dict_delete_var (struct dictionary *d, struct variable *v) 
+{
+  size_t i;
+
+  assert (d != NULL);
+  assert (v != NULL);
+  assert (dict_contains_var (d, v));
+
+  /* Delete aux data. */
+  var_clear_aux (v);
+
+  /* Remove V from splits, weight, filter variables. */
+  d->split_cnt = remove_equal (d->split, d->split_cnt, sizeof *d->split,
+                               &v, compare_var_ptrs, NULL);
+  if (d->weight == v)
+    d->weight = NULL;
+  if (d->filter == v)
+    d->filter = NULL;
+  dict_clear_vectors (d);
+
+  /* Remove V from var array. */
+  remove_element (d->var, d->var_cnt, sizeof *d->var, v->index);
+  d->var_cnt--;
+
+  /* Update index. */
+  for (i = v->index; i < d->var_cnt; i++)
+    d->var[i]->index = i;
+
+  /* Update name hash. */
+  hsh_force_delete (d->name_tab, v);
+
+  /* Free memory. */
+  val_labs_destroy (v->val_labs);
+  cat_stored_values_destroy (v);
+  free (v->label);
+  free (v);
+}
+
+/* Deletes the COUNT variables listed in VARS from D.  This is
+   unsafe; see the comment on dict_delete_var() for details. */
+void 
+dict_delete_vars (struct dictionary *d,
+                  struct variable *const *vars, size_t count) 
+{
+  /* FIXME: this can be done in O(count) time, but this algorithm
+     is O(count**2). */
+  assert (d != NULL);
+  assert (count == 0 || vars != NULL);
+
+  while (count-- > 0)
+    dict_delete_var (d, *vars++);
+}
+
+/* Deletes scratch variables from dictionary D. */
+void
+dict_delete_scratch_vars (struct dictionary *d)
+{
+  int i;
+
+  /* FIXME: this can be done in O(count) time, but this algorithm
+     is O(count**2). */
+  assert (d != NULL);
+
+  for (i = 0; i < d->var_cnt; )
+    if (dict_class_from_id (d->var[i]->name) == DC_SCRATCH)
+      dict_delete_var (d, d->var[i]);
+    else
+      i++;
+}
+
+/* Moves V to 0-based position IDX in D.  Other variables in D,
+   if any, retain their relative positions.  Runs in time linear
+   in the distance moved. */
+void
+dict_reorder_var (struct dictionary *d, struct variable *v,
+                  size_t new_index) 
+{
+  size_t min_idx, max_idx;
+  size_t i;
+  
+  assert (d != NULL);
+  assert (v != NULL);
+  assert (dict_contains_var (d, v));
+  assert (new_index < d->var_cnt);
+
+  move_element (d->var, d->var_cnt, sizeof *d->var, v->index, new_index);
+
+  min_idx = min (v->index, new_index);
+  max_idx = max (v->index, new_index);
+  for (i = min_idx; i <= max_idx; i++)
+    d->var[i]->index = i;
+}
+
+/* Reorders the variables in D, placing the COUNT variables
+   listed in ORDER in that order at the beginning of D.  The
+   other variables in D, if any, retain their relative
+   positions. */
+void 
+dict_reorder_vars (struct dictionary *d,
+                   struct variable *const *order, size_t count) 
+{
+  struct variable **new_var;
+  size_t i;
+  
+  assert (d != NULL);
+  assert (count == 0 || order != NULL);
+  assert (count <= d->var_cnt);
+
+  new_var = xnmalloc (d->var_cnt, sizeof *new_var);
+  memcpy (new_var, order, count * sizeof *new_var);
+  for (i = 0; i < count; i++) 
+    {
+      assert (d->var[order[i]->index] != NULL);
+      d->var[order[i]->index] = NULL;
+      order[i]->index = i;
+    }
+  for (i = 0; i < d->var_cnt; i++)
+    if (d->var[i] != NULL)
+      {
+        assert (count < d->var_cnt);
+        new_var[count] = d->var[i];
+        new_var[count]->index = count;
+        count++;
+      }
+  free (d->var);
+  d->var = new_var;
+}
+
+/* Changes the name of V in D to name NEW_NAME.  Assert-fails if
+   a variable named NEW_NAME is already in D, except that
+   NEW_NAME may be the same as V's existing name. */
+void 
+dict_rename_var (struct dictionary *d, struct variable *v,
+                 const char *new_name) 
+{
+  assert (d != NULL);
+  assert (v != NULL);
+  assert (new_name != NULL);
+  assert (var_is_valid_name (new_name, false));
+  assert (dict_contains_var (d, v));
+  assert (!compare_var_names (v->name, new_name, NULL)
+          || dict_lookup_var (d, new_name) == NULL);
+
+  hsh_force_delete (d->name_tab, v);
+  str_copy_trunc (v->name, sizeof v->name, new_name);
+  hsh_force_insert (d->name_tab, v);
+
+  if (get_algorithm () == ENHANCED)
+    var_clear_short_name (v);
+}
+
+/* Renames COUNT variables specified in VARS to the names given
+   in NEW_NAMES within dictionary D.  If the renaming would
+   result in a duplicate variable name, returns false and stores a
+   name that would be duplicated into *ERR_NAME (if ERR_NAME is
+   non-null).  Otherwise, the renaming is successful, and true
+   is returned. */
+bool
+dict_rename_vars (struct dictionary *d,
+                  struct variable **vars, char **new_names,
+                  size_t count, char **err_name) 
+{
+  char **old_names;
+  size_t i;
+  bool success = true;
+
+  assert (d != NULL);
+  assert (count == 0 || vars != NULL);
+  assert (count == 0 || new_names != NULL);
+
+  /* Remove the variables to be renamed from the name hash,
+     save their names, and rename them. */
+  old_names = xnmalloc (count, sizeof *old_names);
+  for (i = 0; i < count; i++) 
+    {
+      assert (d->var[vars[i]->index] == vars[i]);
+      assert (var_is_valid_name (new_names[i], false));
+      hsh_force_delete (d->name_tab, vars[i]);
+      old_names[i] = xstrdup (vars[i]->name);
+      strcpy (vars[i]->name, new_names[i]);
+    }
+
+  /* Add the renamed variables back into the name hash,
+     checking for conflicts. */
+  for (i = 0; i < count; i++)
+    {
+      assert (new_names[i] != NULL);
+      assert (*new_names[i] != '\0');
+      assert (strlen (new_names[i]) >= 1);
+      assert (strlen (new_names[i]) <= LONG_NAME_LEN);
+
+      if (hsh_insert (d->name_tab, vars[i]) != NULL)
+        {
+          /* There is a name conflict.
+             Back out all the name changes that have already
+             taken place, and indicate failure. */
+          size_t fail_idx = i;
+          if (err_name != NULL) 
+            *err_name = new_names[i];
+
+          for (i = 0; i < fail_idx; i++)
+            hsh_force_delete (d->name_tab, vars[i]);
+          
+          for (i = 0; i < count; i++)
+            {
+              strcpy (vars[i]->name, old_names[i]);
+              hsh_force_insert (d->name_tab, vars[i]);
+            }
+
+          success = false;
+          goto done;
+        }
+    }
+
+  /* Clear short names. */
+  if (get_algorithm () == ENHANCED)
+    for (i = 0; i < count; i++)
+      var_clear_short_name (vars[i]);
+
+ done:
+  /* Free the old names we kept around. */
+  for (i = 0; i < count; i++)
+    free (old_names[i]);
+  free (old_names);
+
+  return success;
+}
+
+/* Returns the weighting variable in dictionary D, or a null
+   pointer if the dictionary is unweighted. */
+struct variable *
+dict_get_weight (const struct dictionary *d) 
+{
+  assert (d != NULL);
+  assert (d->weight == NULL || dict_contains_var (d, d->weight));
+  
+  return d->weight;
+}
+
+/* Returns the value of D's weighting variable in case C, except that a
+   negative weight is returned as 0.  Returns 1 if the dictionary is
+   unweighted. Will warn about missing, negative, or zero values if
+   warn_on_invalid is nonzero. The function will set warn_on_invalid to zero
+   if an invalid weight is found. */
+double
+dict_get_case_weight (const struct dictionary *d, const struct ccase *c, 
+                     int *warn_on_invalid)
+{
+  assert (d != NULL);
+  assert (c != NULL);
+
+  if (d->weight == NULL)
+    return 1.0;
+  else 
+    {
+      double w = case_num (c, d->weight->fv);
+      if (w < 0.0 || mv_is_num_missing (&d->weight->miss, w))
+        w = 0.0;
+      if ( w == 0.0 && *warn_on_invalid ) {
+         *warn_on_invalid = 0;
+         msg (SW, _("At least one case in the data file had a weight value "
+                    "that was user-missing, system-missing, zero, or "
+                    "negative.  These case(s) were ignored."));
+      }
+      return w;
+    }
+}
+
+/* Sets the weighting variable of D to V, or turning off
+   weighting if V is a null pointer. */
+void
+dict_set_weight (struct dictionary *d, struct variable *v) 
+{
+  assert (d != NULL);
+  assert (v == NULL || dict_contains_var (d, v));
+  assert (v == NULL || v->type == NUMERIC);
+
+  d->weight = v;
+}
+
+/* Returns the filter variable in dictionary D (see cmd_filter())
+   or a null pointer if the dictionary is unfiltered. */
+struct variable *
+dict_get_filter (const struct dictionary *d) 
+{
+  assert (d != NULL);
+  assert (d->filter == NULL || dict_contains_var (d, d->filter));
+  
+  return d->filter;
+}
+
+/* Sets V as the filter variable for dictionary D.  Passing a
+   null pointer for V turn off filtering. */
+void
+dict_set_filter (struct dictionary *d, struct variable *v)
+{
+  assert (d != NULL);
+  assert (v == NULL || dict_contains_var (d, v));
+
+  d->filter = v;
+}
+
+/* Returns the case limit for dictionary D, or zero if the number
+   of cases is unlimited (see cmd_n()). */
+int
+dict_get_case_limit (const struct dictionary *d) 
+{
+  assert (d != NULL);
+
+  return d->case_limit;
+}
+
+/* Sets CASE_LIMIT as the case limit for dictionary D.  Zero for
+   CASE_LIMIT indicates no limit. */
+void
+dict_set_case_limit (struct dictionary *d, int case_limit) 
+{
+  assert (d != NULL);
+  assert (case_limit >= 0);
+
+  d->case_limit = case_limit;
+}
+
+/* Returns the index of the next value to be added to D.  This
+   value is the number of `union value's that need to be
+   allocated to store a case for dictionary D. */
+int
+dict_get_next_value_idx (const struct dictionary *d) 
+{
+  assert (d != NULL);
+
+  return d->next_value_idx;
+}
+
+/* Returns the number of bytes needed to store a case for
+   dictionary D. */
+size_t
+dict_get_case_size (const struct dictionary *d) 
+{
+  assert (d != NULL);
+
+  return sizeof (union value) * dict_get_next_value_idx (d);
+}
+
+/* Deletes scratch variables in dictionary D and reassigns values
+   so that fragmentation is eliminated. */
+void
+dict_compact_values (struct dictionary *d) 
+{
+  size_t i;
+
+  d->next_value_idx = 0;
+  for (i = 0; i < d->var_cnt; )
+    {
+      struct variable *v = d->var[i];
+
+      if (dict_class_from_id (v->name) != DC_SCRATCH) 
+        {
+          v->fv = d->next_value_idx;
+          d->next_value_idx += v->nv;
+          i++;
+        }
+      else
+        dict_delete_var (d, v);
+    }
+}
+
+/* Returns the number of values that would be used by a case if
+   dict_compact_values() were called. */
+size_t
+dict_get_compacted_value_cnt (const struct dictionary *d) 
+{
+  size_t i;
+  size_t cnt;
+
+  cnt = 0;
+  for (i = 0; i < d->var_cnt; i++)
+    if (dict_class_from_id (d->var[i]->name) != DC_SCRATCH) 
+      cnt += d->var[i]->nv;
+  return cnt;
+}
+
+/* Creates and returns an array mapping from a dictionary index
+   to the `fv' that the corresponding variable will have after
+   calling dict_compact_values().  Scratch variables receive -1
+   for `fv' because dict_compact_values() will delete them. */
+int *
+dict_get_compacted_idx_to_fv (const struct dictionary *d) 
+{
+  size_t i;
+  size_t next_value_idx;
+  int *idx_to_fv;
+  
+  idx_to_fv = xnmalloc (d->var_cnt, sizeof *idx_to_fv);
+  next_value_idx = 0;
+  for (i = 0; i < d->var_cnt; i++)
+    {
+      struct variable *v = d->var[i];
+
+      if (dict_class_from_id (v->name) != DC_SCRATCH) 
+        {
+          idx_to_fv[i] = next_value_idx;
+          next_value_idx += v->nv;
+        }
+      else 
+        idx_to_fv[i] = -1;
+    }
+  return idx_to_fv;
+}
+
+/* Returns true if a case for dictionary D would be smaller after
+   compaction, false otherwise.  Compacting a case eliminates
+   "holes" between values and after the last value.  Holes are
+   created by deleting variables (or by scratch variables).
+
+   The return value may differ from whether compacting a case
+   from dictionary D would *change* the case: compaction could
+   rearrange values even if it didn't reduce space
+   requirements. */
+bool
+dict_needs_compaction (const struct dictionary *d) 
+{
+  return dict_get_compacted_value_cnt (d) < dict_get_next_value_idx (d);
+}
+\f
+/* How to copy a contiguous range of values between cases. */
+struct copy_map
+  {
+    size_t src_idx;             /* Starting value index in source case. */
+    size_t dst_idx;             /* Starting value index in target case. */
+    size_t cnt;                 /* Number of values. */
+  };
+
+/* How to compact a case. */
+struct dict_compactor 
+  {
+    struct copy_map *maps;      /* Array of mappings. */
+    size_t map_cnt;             /* Number of mappings. */
+  };
+
+/* Creates and returns a dict_compactor that can be used to
+   compact cases for dictionary D.
+
+   Compacting a case eliminates "holes" between values and after
+   the last value.  Holes are created by deleting variables (or
+   by scratch variables). */
+struct dict_compactor *
+dict_make_compactor (const struct dictionary *d)
+{
+  struct dict_compactor *compactor;
+  struct copy_map *map;
+  size_t map_allocated;
+  size_t value_idx;
+  size_t i;
+
+  compactor = xmalloc (sizeof *compactor);
+  compactor->maps = NULL;
+  compactor->map_cnt = 0;
+  map_allocated = 0;
+
+  value_idx = 0;
+  map = NULL;
+  for (i = 0; i < d->var_cnt; i++) 
+    {
+      struct variable *v = d->var[i];
+
+      if (dict_class_from_id (v->name) == DC_SCRATCH)
+        continue;
+      if (map != NULL && map->src_idx + map->cnt == v->fv) 
+        map->cnt += v->nv;
+      else 
+        {
+          if (compactor->map_cnt == map_allocated)
+            compactor->maps = x2nrealloc (compactor->maps, &map_allocated,
+                                          sizeof *compactor->maps);
+          map = &compactor->maps[compactor->map_cnt++];
+          map->src_idx = v->fv;
+          map->dst_idx = value_idx;
+          map->cnt = v->nv;
+        }
+      value_idx += v->nv;
+    }
+
+  return compactor;
+}
+
+/* Compacts SRC by copying it to DST according to the scheme in
+   COMPACTOR.
+
+   Compacting a case eliminates "holes" between values and after
+   the last value.  Holes are created by deleting variables (or
+   by scratch variables). */
+void
+dict_compactor_compact (const struct dict_compactor *compactor,
+                        struct ccase *dst, const struct ccase *src) 
+{
+  size_t i;
+
+  for (i = 0; i < compactor->map_cnt; i++) 
+    {
+      const struct copy_map *map = &compactor->maps[i];
+      case_copy (dst, map->dst_idx, src, map->src_idx, map->cnt);
+    }
+}
+
+/* Destroys COMPACTOR. */
+void
+dict_compactor_destroy (struct dict_compactor *compactor) 
+{
+  if (compactor != NULL) 
+    {
+      free (compactor->maps);
+      free (compactor);
+    }
+}
+
+/* Returns the SPLIT FILE vars (see cmd_split_file()).  Call
+   dict_get_split_cnt() to determine how many SPLIT FILE vars
+   there are.  Returns a null pointer if and only if there are no
+   SPLIT FILE vars. */
+struct variable *const *
+dict_get_split_vars (const struct dictionary *d) 
+{
+  assert (d != NULL);
+  
+  return d->split;
+}
+
+/* Returns the number of SPLIT FILE vars. */
+size_t
+dict_get_split_cnt (const struct dictionary *d) 
+{
+  assert (d != NULL);
+
+  return d->split_cnt;
+}
+
+/* Sets CNT split vars SPLIT in dictionary D. */
+void
+dict_set_split_vars (struct dictionary *d,
+                     struct variable *const *split, size_t cnt)
+{
+  assert (d != NULL);
+  assert (cnt == 0 || split != NULL);
+
+  d->split_cnt = cnt;
+  d->split = xnrealloc (d->split, cnt, sizeof *d->split);
+  memcpy (d->split, split, cnt * sizeof *d->split);
+}
+
+/* Returns the file label for D, or a null pointer if D is
+   unlabeled (see cmd_file_label()). */
+const char *
+dict_get_label (const struct dictionary *d) 
+{
+  assert (d != NULL);
+
+  return d->label;
+}
+
+/* Sets D's file label to LABEL, truncating it to a maximum of 60
+   characters. */
+void
+dict_set_label (struct dictionary *d, const char *label) 
+{
+  assert (d != NULL);
+
+  free (d->label);
+  if (label == NULL)
+    d->label = NULL;
+  else if (strlen (label) < 60)
+    d->label = xstrdup (label);
+  else 
+    {
+      d->label = xmalloc (61);
+      memcpy (d->label, label, 60);
+      d->label[60] = '\0';
+    }
+}
+
+/* Returns the documents for D, or a null pointer if D has no
+   documents (see cmd_document()).. */
+const char *
+dict_get_documents (const struct dictionary *d) 
+{
+  assert (d != NULL);
+
+  return d->documents;
+}
+
+/* Sets the documents for D to DOCUMENTS, or removes D's
+   documents if DOCUMENT is a null pointer. */
+void
+dict_set_documents (struct dictionary *d, const char *documents)
+{
+  assert (d != NULL);
+
+  free (d->documents);
+  if (documents == NULL)
+    d->documents = NULL;
+  else
+    d->documents = xstrdup (documents);
+}
+
+/* Creates in D a vector named NAME that contains CNT variables
+   VAR (see cmd_vector()).  Returns true if successful, or
+   false if a vector named NAME already exists in D. */
+bool
+dict_create_vector (struct dictionary *d,
+                    const char *name,
+                    struct variable **var, size_t cnt) 
+{
+  struct vector *vector;
+  size_t i;
+
+  assert (d != NULL);
+  assert (name != NULL);
+  assert (var_is_valid_name (name, false));
+  assert (var != NULL);
+  assert (cnt > 0);
+  
+  if (dict_lookup_vector (d, name) != NULL)
+    return false;
+
+  d->vector = xnrealloc (d->vector, d->vector_cnt + 1, sizeof *d->vector);
+  vector = d->vector[d->vector_cnt] = xmalloc (sizeof *vector);
+  vector->idx = d->vector_cnt++;
+  str_copy_trunc (vector->name, sizeof vector->name, name);
+  vector->var = xnmalloc (cnt, sizeof *var);
+  for (i = 0; i < cnt; i++)
+    {
+      assert (dict_contains_var (d, var[i]));
+      vector->var[i] = var[i];
+    }
+  vector->cnt = cnt;
+  
+  return true;
+}
+
+/* Returns the vector in D with index IDX, which must be less
+   than dict_get_vector_cnt (D). */
+const struct vector *
+dict_get_vector (const struct dictionary *d, size_t idx) 
+{
+  assert (d != NULL);
+  assert (idx < d->vector_cnt);
+
+  return d->vector[idx];
+}
+
+/* Returns the number of vectors in D. */
+size_t
+dict_get_vector_cnt (const struct dictionary *d) 
+{
+  assert (d != NULL);
+
+  return d->vector_cnt;
+}
+
+/* Looks up and returns the vector within D with the given
+   NAME. */
+const struct vector *
+dict_lookup_vector (const struct dictionary *d, const char *name) 
+{
+  size_t i;
+
+  assert (d != NULL);
+  assert (name != NULL);
+
+  for (i = 0; i < d->vector_cnt; i++)
+    if (!strcasecmp (d->vector[i]->name, name))
+      return d->vector[i];
+  return NULL;
+}
+
+/* Deletes all vectors from D. */
+void
+dict_clear_vectors (struct dictionary *d) 
+{
+  size_t i;
+  
+  assert (d != NULL);
+
+  for (i = 0; i < d->vector_cnt; i++) 
+    {
+      free (d->vector[i]->var);
+      free (d->vector[i]);
+    }
+  free (d->vector);
+  d->vector = NULL;
+  d->vector_cnt = 0;
+}
+
+/* Compares two strings. */
+static int
+compare_strings (const void *a, const void *b, void *aux UNUSED) 
+{
+  return strcmp (a, b);
+}
+
+/* Hashes a string. */
+static unsigned
+hash_string (const void *s, void *aux UNUSED) 
+{
+  return hsh_hash_string (s);
+}
+
+/* Assigns a valid, unique short_name[] to each variable in D.
+   Each variable whose actual name is short has highest priority
+   for that short name.  Otherwise, variables with an existing
+   short_name[] have the next highest priority for a given short
+   name; if it is already taken, then the variable is treated as
+   if short_name[] had been empty.  Otherwise, long names are
+   truncated to form short names.  If that causes conflicts,
+   variables are renamed as PREFIX_A, PREFIX_B, and so on. */
+void
+dict_assign_short_names (struct dictionary *d) 
+{
+  struct hsh_table *short_names;
+  size_t i;
+
+  /* Give variables whose names are short the corresponding short
+     names, and clear short_names[] that conflict with a variable
+     name. */
+  for (i = 0; i < d->var_cnt; i++)
+    {
+      struct variable *v = d->var[i];
+      if (strlen (v->name) <= SHORT_NAME_LEN)
+        var_set_short_name (v, v->name);
+      else if (dict_lookup_var (d, v->short_name) != NULL)
+        var_clear_short_name (v);
+    }
+
+  /* Each variable with an assigned short_name[] now gets it
+     unless there is a conflict. */
+  short_names = hsh_create (d->var_cnt, compare_strings, hash_string,
+                            NULL, NULL);
+  for (i = 0; i < d->var_cnt; i++)
+    {
+      struct variable *v = d->var[i];
+      if (v->short_name[0] && hsh_insert (short_names, v->short_name) != NULL)
+        var_clear_short_name (v);
+    }
+  
+  /* Now assign short names to remaining variables. */
+  for (i = 0; i < d->var_cnt; i++)
+    {
+      struct variable *v = d->var[i];
+      if (v->short_name[0] == '\0') 
+        {
+          int sfx;
+
+          /* Form initial short_name. */
+          var_set_short_name (v, v->name);
+
+          /* Try _A, _B, ... _AA, _AB, etc., if needed. */
+          for (sfx = 0; hsh_insert (short_names, v->short_name) != NULL; sfx++)
+            var_set_short_name_suffix (v, v->name, sfx);
+        } 
+    }
+
+  /* Get rid of hash table. */
+  hsh_destroy (short_names);
+}
diff --git a/src/data/dictionary.h b/src/data/dictionary.h
new file mode 100644 (file)
index 0000000..d028771
--- /dev/null
@@ -0,0 +1,116 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef DICTIONARY_H
+#define DICTIONARY_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+/* Dictionary. */ 
+
+struct variable;
+struct dictionary *dict_create (void);
+struct dictionary *dict_clone (const struct dictionary *);
+void dict_clear (struct dictionary *);
+void dict_clear_aux (struct dictionary *);
+void dict_destroy (struct dictionary *);
+
+size_t dict_get_var_cnt (const struct dictionary *);
+struct variable *dict_get_var (const struct dictionary *, size_t idx);
+void dict_get_vars (const struct dictionary *,
+                    struct variable ***vars, size_t *cnt,
+                    unsigned exclude_classes);
+
+struct variable *dict_create_var (struct dictionary *, const char *,
+                                  int width);
+
+struct variable *dict_create_var_assert (struct dictionary *, const char *,
+                                  int width);
+struct variable *dict_clone_var (struct dictionary *, const struct variable *,
+                                 const char *);
+struct variable *dict_clone_var_assert (struct dictionary *,
+                                        const struct variable *, const char *);
+
+struct variable *dict_lookup_var (const struct dictionary *, const char *);
+struct variable *dict_lookup_var_assert (const struct dictionary *,
+                                         const char *);
+bool dict_contains_var (const struct dictionary *, const struct variable *);
+void dict_delete_var (struct dictionary *, struct variable *);
+void dict_delete_vars (struct dictionary *,
+                       struct variable *const *, size_t count);
+void dict_delete_scratch_vars (struct dictionary *);
+void dict_reorder_var (struct dictionary *d, struct variable *v,
+                       size_t new_index);
+void dict_reorder_vars (struct dictionary *,
+                        struct variable *const *, size_t count);
+void dict_rename_var (struct dictionary *, struct variable *, const char *);
+bool dict_rename_vars (struct dictionary *,
+                       struct variable **, char **new_names,
+                       size_t count, char **err_name);
+
+struct ccase;
+struct variable *dict_get_weight (const struct dictionary *);
+double dict_get_case_weight (const struct dictionary *, 
+                            const struct ccase *, int *);
+void dict_set_weight (struct dictionary *, struct variable *);
+
+struct variable *dict_get_filter (const struct dictionary *);
+void dict_set_filter (struct dictionary *, struct variable *);
+
+int dict_get_case_limit (const struct dictionary *);
+void dict_set_case_limit (struct dictionary *, int);
+
+int dict_get_next_value_idx (const struct dictionary *);
+size_t dict_get_case_size (const struct dictionary *);
+
+void dict_compact_values (struct dictionary *);
+size_t dict_get_compacted_value_cnt (const struct dictionary *);
+int *dict_get_compacted_idx_to_fv (const struct dictionary *);
+bool dict_needs_compaction (const struct dictionary *);
+
+struct dict_compactor *dict_make_compactor (const struct dictionary *);
+void dict_compactor_compact (const struct dict_compactor *,
+                             struct ccase *, const struct ccase *);
+void dict_compactor_destroy (struct dict_compactor *);
+
+struct variable *const *dict_get_split_vars (const struct dictionary *);
+size_t dict_get_split_cnt (const struct dictionary *);
+void dict_set_split_vars (struct dictionary *,
+                          struct variable *const *, size_t cnt);
+
+const char *dict_get_label (const struct dictionary *);
+void dict_set_label (struct dictionary *, const char *);
+
+const char *dict_get_documents (const struct dictionary *);
+void dict_set_documents (struct dictionary *, const char *);
+
+bool dict_create_vector (struct dictionary *,
+                         const char *name,
+                         struct variable **, size_t cnt);
+const struct vector *dict_get_vector (const struct dictionary *,
+                                      size_t idx);
+size_t dict_get_vector_cnt (const struct dictionary *);
+const struct vector *dict_lookup_vector (const struct dictionary *,
+                                         const char *name);
+void dict_clear_vectors (struct dictionary *);
+
+void dict_assign_short_names (struct dictionary *);
+
+#endif /* dictionary.h */
diff --git a/src/data/file-handle-def.c b/src/data/file-handle-def.c
new file mode 100644 (file)
index 0000000..25f26c9
--- /dev/null
@@ -0,0 +1,453 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "file-handle-def.h"
+#include "message.h"
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include "alloc.h"
+#include "filename.h"
+#include "message.h"
+#include "magic.h"
+#include "variable.h"
+#include "scratch-handle.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* (headers) */
+
+/* File handle. */
+struct file_handle 
+  {
+    struct file_handle *next;   /* Next in global list. */
+    int open_cnt;               /* 0=not open, otherwise # of openers. */
+    bool deleted;               /* Destroy handle when open_cnt goes to 0? */
+
+    char *name;                 /* File handle identifier. */
+    const char *type;           /* If open, type of file. */
+    char open_mode[3];          /* "[rw][se]". */
+    void *aux;                  /* Aux data pointer for owner if any. */
+    enum fh_referent referent;  /* What the file handle refers to. */
+
+    /* FH_REF_FILE only. */
+    char *filename;            /* Filename as provided by user. */
+    struct file_identity *identity; /* For checking file identity. */
+    enum fh_mode mode;         /* File mode. */
+
+    /* FH_REF_FILE and FH_REF_INLINE only. */
+    size_t record_width;        /* Length of fixed-format records. */
+    size_t tab_width;           /* Tab width, 0=do not expand tabs. */
+
+    /* FH_REF_SCRATCH only. */
+    struct scratch_handle *sh;  /* Scratch file data. */
+  };
+
+/* List of all handles. */
+static struct file_handle *file_handles;
+
+/* Default file handle for DATA LIST, REREAD, REPEATING DATA
+   commands. */
+static struct file_handle *default_handle;
+
+/* The "file" that reads from BEGIN DATA...END DATA. */
+static struct file_handle *inline_file;
+
+static struct file_handle *create_handle (const char *name, enum fh_referent);
+
+/* File handle initialization routine. */
+void 
+fh_init (void)
+{
+  inline_file = create_handle ("INLINE", FH_REF_INLINE);
+  inline_file->record_width = 80;
+  inline_file->tab_width = 8;
+}
+
+/* Free HANDLE and remove it from the global list. */
+static void
+free_handle (struct file_handle *handle) 
+{
+  /* Remove handle from global list. */
+  if (file_handles == handle)
+    file_handles = handle->next;
+  else 
+    {
+      struct file_handle *iter = file_handles;
+      while (iter->next != handle)
+        iter = iter->next;
+      iter->next = handle->next;
+    }
+
+  /* Free data. */
+  free (handle->name);
+  free (handle->filename);
+  fn_free_identity (handle->identity);
+  scratch_handle_destroy (handle->sh);
+  free (handle);
+}
+
+/* Frees all the file handles. */
+void 
+fh_done (void)
+{
+  while (file_handles != NULL) 
+    free_handle (file_handles);
+}
+
+/* Returns the handle named HANDLE_NAME, or a null pointer if
+   there is none. */
+struct file_handle *
+fh_from_name (const char *handle_name) 
+{
+  struct file_handle *iter;
+
+  for (iter = file_handles; iter != NULL; iter = iter->next)
+    if (!iter->deleted && !strcasecmp (handle_name, iter->name))
+      return iter;
+  return NULL;
+}
+
+/* Returns the handle for the file named FILENAME,
+   or a null pointer if none exists.
+   Different names for the same file (e.g. "x" and "./x") are
+   considered equivalent. */
+struct file_handle *
+fh_from_filename (const char *filename)
+{
+  struct file_identity *identity;
+  struct file_handle *iter;
+      
+  /* First check for a file with the same identity. */
+  identity = fn_get_identity (filename);
+  if (identity != NULL) 
+    {
+      for (iter = file_handles; iter != NULL; iter = iter->next)
+        if (!iter->deleted
+            && iter->referent == FH_REF_FILE
+            && iter->identity != NULL
+            && !fn_compare_file_identities (identity, iter->identity))
+          {
+            fn_free_identity (identity);
+            return iter; 
+          }
+      fn_free_identity (identity);
+    }
+
+  /* Then check for a file with the same name. */
+  for (iter = file_handles; iter != NULL; iter = iter->next)
+    if (!iter->deleted
+        && iter->referent == FH_REF_FILE && !strcmp (filename, iter->filename))
+      return iter; 
+
+  return NULL;
+}
+
+/* Creates a new handle with name HANDLE_NAME that refers to
+   REFERENT.  Links the new handle into the global list.  Returns
+   the new handle.
+
+   The new handle is not fully initialized.  The caller is
+   responsible for completing its initialization. */
+static struct file_handle *
+create_handle (const char *handle_name, enum fh_referent referent) 
+{
+  struct file_handle *handle = xzalloc (sizeof *handle);
+  handle->next = file_handles;
+  handle->open_cnt = 0;
+  handle->deleted = false;
+  handle->name = xstrdup (handle_name);
+  handle->type = NULL;
+  handle->aux = NULL;
+  handle->referent = referent;
+  file_handles = handle;
+  return handle;
+}
+
+/* Returns the unique handle of referent type FH_REF_INLINE,
+   which refers to the "inline file" that represents character
+   data in the command file between BEGIN DATA and END DATA. */
+struct file_handle *
+fh_inline_file (void) 
+{
+  return inline_file;
+}
+
+/* Creates a new file handle named HANDLE_NAME, which must not be
+   the name of an existing file handle.  The new handle is
+   associated with file FILENAME and the given PROPERTIES. */
+struct file_handle *
+fh_create_file (const char *handle_name, const char *filename,
+                const struct fh_properties *properties)
+{
+  struct file_handle *handle;
+  assert (fh_from_name (handle_name) == NULL);
+  handle = create_handle (handle_name, FH_REF_FILE);
+  handle->filename = xstrdup (filename);
+  handle->identity = fn_get_identity (filename);
+  handle->mode = properties->mode;
+  handle->record_width = properties->record_width;
+  handle->tab_width = properties->tab_width;
+  return handle;
+}
+
+/* Creates a new file handle named HANDLE_NAME, which must not be
+   the name of an existing file handle.  The new handle is
+   associated with a scratch file (initially empty). */
+struct file_handle *
+fh_create_scratch (const char *handle_name) 
+{
+  struct file_handle *handle = create_handle (handle_name, FH_REF_SCRATCH);
+  handle->sh = NULL;
+  return handle;
+}
+
+/* Returns a set of default properties for a file handle. */
+const struct fh_properties *
+fh_default_properties (void)
+{
+  static const struct fh_properties default_properties
+    = {FH_MODE_TEXT, 1024, 4};
+  return &default_properties;
+}
+
+/* Deletes FH from the global list of file handles.  Afterward,
+   attempts to search for it will fail.  Unless the file handle
+   is currently open, it will be destroyed; otherwise, it will be
+   destroyed later when it is closed.
+   Normally needed only if a file_handle needs to be re-assigned.
+   Otherwise, just let fh_done() destroy the handle. */
+void 
+fh_free (struct file_handle *handle)
+{
+  if (handle == fh_inline_file () || handle == NULL || handle->deleted)
+    return;
+  handle->deleted = true;
+
+  if (handle == default_handle)
+    default_handle = fh_inline_file ();
+
+  if (handle->open_cnt == 0)
+    free_handle (handle);
+}
+
+/* Returns an English description of MODE,
+   which is in the format of the MODE argument to fh_open(). */
+static const char *
+mode_name (const char *mode) 
+{
+  assert (mode != NULL);
+  assert (mode[0] == 'r' || mode[0] == 'w');
+
+  return mode[0] == 'r' ? "reading" : "writing";
+}
+
+/* Tries to open handle H with the given TYPE and MODE.
+
+   H's referent type must be one of the bits in MASK.  The caller
+   must verify this ahead of time; we simply assert it here.
+
+   TYPE is the sort of file, e.g. "system file".  Only one given
+   type of access is allowed on a given file handle at once.
+   If successful, a reference to TYPE is retained, so it should
+   probably be a string literal.
+
+   MODE combines the read or write mode with the sharing mode.
+   The first character is 'r' for read, 'w' for write.  The
+   second character is 's' to permit sharing, 'e' to require
+   exclusive access.
+
+   Returns the address of a void * that the caller can use for
+   data specific to the file handle if successful, or a null
+   pointer on failure.  For exclusive access modes the void *
+   will always be a null pointer at return.  In shared access
+   modes the void * will necessarily be null only if no other
+   sharers are active. */
+void **
+fh_open (struct file_handle *h, enum fh_referent mask UNUSED,
+         const char *type, const char *mode) 
+{
+  assert (h != NULL);
+  assert ((fh_get_referent (h) & mask) != 0);
+  assert (type != NULL);
+  assert (mode != NULL);
+  assert (mode[0] == 'r' || mode[0] == 'w');
+  assert (mode[1] == 's' || mode[1] == 'e');
+  assert (mode[2] == '\0');
+
+  if (h->open_cnt != 0) 
+    {
+      if (strcmp (h->type, type)) 
+        {
+          msg (SE, _("Can't open %s as a %s because it is "
+                     "already open as a %s."),
+               fh_get_name (h), type, h->type);
+          return NULL; 
+        }
+      else if (strcmp (h->open_mode, mode)) 
+        {
+          msg (SE, _("Can't open %s as a %s for %s because it is "
+                     "already open for %s."),
+               fh_get_name (h), type, mode_name (mode),
+               mode_name (h->open_mode));
+          return NULL;
+        }
+      else if (h->open_mode[1] == 'e')
+        {
+          msg (SE, _("Can't re-open %s as a %s for %s."),
+               fh_get_name (h), type, mode_name (mode));
+          return NULL;
+        }
+    }
+  else 
+    {
+      h->type = type;
+      strcpy (h->open_mode, mode);
+      assert (h->aux == NULL);
+    }
+  h->open_cnt++;
+
+  return &h->aux;
+}
+
+/* Closes file handle H, which must have been open for the
+   specified TYPE and MODE of access provided to fh_open().
+   Returns zero if the file is now closed, nonzero if it is still
+   open due to another reference.
+
+   After fh_close() returns zero for a handle, it is unsafe to
+   reference that file handle again in any way, because its
+   storage may have been freed. */
+int
+fh_close (struct file_handle *h, const char *type, const char *mode)
+{
+  assert (h != NULL);
+  assert (h->open_cnt > 0);
+  assert (type != NULL);
+  assert (!strcmp (type, h->type));
+  assert (mode != NULL);
+  assert (!strcmp (mode, h->open_mode));
+
+  if (--h->open_cnt == 0) 
+    {
+      h->type = NULL;
+      h->aux = NULL;
+      if (h->deleted)
+        free_handle (h);
+      return 0;
+    }
+  return 1;
+}
+
+/* Is the file open?  BEGIN DATA...END DATA uses this to detect
+   whether the inline file is actually in use. */
+bool
+fh_is_open (const struct file_handle *handle) 
+{
+  return handle->open_cnt > 0;
+}
+
+/* Returns the identifier of file HANDLE.  If HANDLE was created
+   by referring to a filename instead of a handle name, returns
+   the filename, enclosed in double quotes.  Return value is
+   owned by the file handle. 
+
+   Useful for printing error messages about use of file handles.  */
+const char *
+fh_get_name (const struct file_handle *handle)
+{
+  return handle->name;
+}
+
+/* Returns the type of object that HANDLE refers to. */
+enum fh_referent
+fh_get_referent (const struct file_handle *handle) 
+{
+  return handle->referent;
+}
+
+/* Returns the name of the file associated with HANDLE. */
+const char *
+fh_get_filename (const struct file_handle *handle) 
+{
+  assert (handle->referent == FH_REF_FILE);
+  return handle->filename;
+}
+
+/* Returns the mode of HANDLE. */
+enum fh_mode
+fh_get_mode (const struct file_handle *handle) 
+{
+  assert (handle->referent == FH_REF_FILE);
+  return handle->mode;
+}
+
+/* Returns the width of a logical record on HANDLE. */
+size_t
+fh_get_record_width (const struct file_handle *handle)
+{
+  assert (handle->referent & (FH_REF_FILE | FH_REF_INLINE));
+  return handle->record_width;
+}
+
+/* Returns the number of characters per tab stop for HANDLE, or
+   zero if tabs are not to be expanded.  Applicable only to
+   FH_MODE_TEXT files. */
+size_t
+fh_get_tab_width (const struct file_handle *handle) 
+{
+  assert (handle->referent & (FH_REF_FILE | FH_REF_INLINE));
+  return handle->tab_width;
+}
+
+/* Returns the scratch file handle associated with HANDLE.
+   Applicable to only FH_REF_SCRATCH files. */
+struct scratch_handle *
+fh_get_scratch_handle (struct file_handle *handle) 
+{
+  assert (handle->referent == FH_REF_SCRATCH);
+  return handle->sh;
+}
+
+/* Sets SH to be the scratch file handle associated with HANDLE.
+   Applicable to only FH_REF_SCRATCH files. */
+void
+fh_set_scratch_handle (struct file_handle *handle, struct scratch_handle *sh)
+{
+  assert (handle->referent == FH_REF_SCRATCH);
+  handle->sh = sh;
+}
+
+/* Returns the current default handle. */
+struct file_handle *
+fh_get_default_handle (void) 
+{
+  return default_handle ? default_handle : fh_inline_file ();
+}
+
+/* Sets NEW_DEFAULT_HANDLE as the default handle. */
+void
+fh_set_default_handle (struct file_handle *new_default_handle) 
+{
+  assert (new_default_handle == NULL
+          || (new_default_handle->referent & (FH_REF_INLINE | FH_REF_FILE)));
+  default_handle = new_default_handle;
+}
diff --git a/src/data/file-handle-def.h b/src/data/file-handle-def.h
new file mode 100644 (file)
index 0000000..c5c61ea
--- /dev/null
@@ -0,0 +1,96 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef FILE_HANDLE_DEF_H
+#define FILE_HANDLE_DEF_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+/* What a file handle refers to.
+   (Ordinarily only a single value is allowed, but fh_open()
+   and fh_parse() take a mask.) */
+enum fh_referent
+  {
+    FH_REF_FILE = 001,          /* Ordinary file (the most common case). */
+    FH_REF_INLINE = 002,        /* The inline file. */
+    FH_REF_SCRATCH = 004        /* Temporary dataset. */
+  };
+
+/* File modes. */
+enum fh_mode
+  {
+    FH_MODE_TEXT,               /* New-line delimited lines. */
+    FH_MODE_BINARY              /* Fixed-length records. */
+  };
+
+/* Properties of a file handle. */
+struct fh_properties 
+  {
+    enum fh_mode mode;          /* File mode. */
+    size_t record_width;        /* Length of fixed-format records. */
+    size_t tab_width;           /* Tab width, 0=do not expand tabs. */
+  };
+
+void fh_init (void);
+void fh_done (void);
+
+/* Creating file handles. */
+struct file_handle *fh_create_file (const char *handle_name,
+                                    const char *filename,
+                                    const struct fh_properties *);
+struct file_handle *fh_create_scratch (const char *handle_name);
+const struct fh_properties *fh_default_properties (void);
+
+/* Delete file handle from global list. */
+void fh_free (struct file_handle *);
+
+/* Finding file handles. */
+struct file_handle *fh_from_name (const char *handle_name);
+struct file_handle *fh_from_filename (const char *filename);
+struct file_handle *fh_inline_file (void);
+
+/* Generic properties of file handles. */
+const char *fh_get_name (const struct file_handle *);
+enum fh_referent fh_get_referent (const struct file_handle *);
+
+/* Properties of FH_REF_FILE file handles. */
+const char *fh_get_filename (const struct file_handle *);
+enum fh_mode fh_get_mode (const struct file_handle *) ;
+
+/* Properties of FH_REF_FILE and FH_REF_INLINE file handles. */
+size_t fh_get_record_width (const struct file_handle *);
+size_t fh_get_tab_width (const struct file_handle *);
+
+/* Properties of FH_REF_SCRATCH file handles. */
+struct scratch_handle *fh_get_scratch_handle (struct file_handle *);
+void fh_set_scratch_handle (struct file_handle *, struct scratch_handle *);
+
+/* Opening and closing file handles. */
+void **fh_open (struct file_handle *, enum fh_referent mask,
+                const char *type, const char *mode);
+int fh_close (struct file_handle *, const char *type, const char *mode);
+bool fh_is_open (const struct file_handle *);
+
+/* Default file handle for DATA LIST, REREAD, REPEATING DATA
+   commands. */
+struct file_handle *fh_get_default_handle (void);
+void fh_set_default_handle (struct file_handle *);
+
+#endif
diff --git a/src/data/filename.c b/src/data/filename.c
new file mode 100644 (file)
index 0000000..2865bd3
--- /dev/null
@@ -0,0 +1,954 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include "filename.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include "alloc.h"
+#include "message.h"
+#include "str.h"
+#include "settings.h"
+#include "version.h"
+#include "xreadlink.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+/* PORTME: Everything in this file is system dependent. */
+
+#ifdef unix
+#include <pwd.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <sys/stat.h>
+#include "stat-macros.h"
+#endif
+
+#ifdef __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 (&output, strlen (input));
+
+  for (;;)
+    switch (*input)
+      {
+      case '\0':
+       return ds_c_str (&output);
+       
+      case '$':
+       input++;
+
+       if (*input == '$')
+         {
+           ds_putc (&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_putc (&output, *input++);
+           
+           value = getenv (ds_c_str (&output) + start);
+           ds_truncate (&output, start);
+           ds_puts (&output, value);
+
+           if (stop && *input == stop)
+             input++;
+         }
+
+      default:
+       ds_putc (&output, *input++);
+      }
+}
+
+#ifdef 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 (&output, strlen (input));
+
+  ip = input;
+
+  for (ip = input; *ip; )
+    if (*ip != '~' || (ip != input && ip[-1] != PATH_DELIMITER))
+      ds_putc (&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_putc (&output, *ip++);
+           else
+             ds_puts (&output, pwd->pw_dir);
+         }
+       else
+         {
+           const char *home = fn_getenv ("HOME");
+           if (!home)
+             ds_putc (&output, *ip++);
+           else
+             ds_puts (&output, home);
+         }
+
+       ip = cp;
+      }
+
+  return ds_c_str (&output);
+}
+#else /* !unix */
+char *
+fn_tilde_expand (const 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 defined (unix) || defined (__MSDOS__) || defined (__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 (&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_puts (&filename, prepend);
+         ds_putc (&filename, DIR_SEPARATOR);
+       }
+      ds_concat (&filename, bp, ep - bp);
+      if (ep - bp
+         && ds_c_str (&filename)[ds_length (&filename) - 1] != DIR_SEPARATOR)
+       ds_putc (&filename, DIR_SEPARATOR);
+      ds_puts (&filename, basename);
+      
+      msg (VM (5), " - %s", ds_c_str (&filename));
+      if (fn_exists_p (ds_c_str (&filename)))
+       {
+         msg (VM (4), _("Found `%s'."), ds_c_str (&filename));
+         free (subst_path);
+         return ds_c_str (&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).  */
+#ifdef 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;
+      while (getcwd (dest, maxlen - (dest - fn2)) == NULL && errno == ERANGE)
+       {
+         maxlen *= 2;
+         dest = fn2 = xrealloc (fn2, maxlen + 1);
+         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 defined (__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 = buf_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
+
+/* Returns the extension part of FILENAME as a malloc()'d string.
+   If FILENAME does not have an extension, returns an empty
+   string. */
+char *
+fn_extension (const char *filename) 
+{
+  const char *extension = strrchr (filename, '.');
+  if (extension == NULL)
+    extension = "";
+  return xstrdup (extension);
+}
+\f
+#if unix
+/* 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);
+    }
+}
+#else
+char *
+fn_get_cwd (void)
+{
+  int size = 2;
+  char *buffer = xmalloc (size);
+  if ( buffer) 
+  {
+    buffer[0]='.';
+    buffer[1]='\0';
+  }
+
+  return buffer;
+     
+}
+#endif
+\f
+/* Find out information about files. */
+
+/* Returns nonzero iff NAME specifies an absolute filename. */
+int
+fn_absolute_p (const char *name)
+{
+#ifdef unix
+  if (name[0] == '/'
+      || !strncmp (name, "./", 2)
+      || !strncmp (name, "../", 3)
+      || name[0] == '~')
+    return 1;
+#elif defined (__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")
+#ifdef 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)
+{
+#ifdef unix
+  struct stat temp;
+
+  return stat (name, &temp) == 0;
+#else
+  FILE *f = fopen (name, "r");
+  if (!f)
+    return 0;
+  fclose (f);
+  return 1;
+#endif
+}
+
+/* Returns the symbolic link value for FILENAME as a dynamically
+   allocated buffer, or a null pointer on failure. */
+char *
+fn_readlink (const char *filename)
+{
+  return xreadlink (filename, 32);
+}
+\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;
+  
+#ifdef unix
+  if (fn[0] == '|')
+    {
+      if (get_safer_mode ())
+       return safety_violation (fn);
+
+      return popen (&fn[1], mode);
+    }
+  else if (*fn && fn[strlen (fn) - 1] == '|')
+    {
+      char *s;
+      FILE *f;
+
+      if (get_safer_mode ())
+       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;
+#ifdef 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;
+}
+
+#ifdef unix
+/* A file's identity. */
+struct file_identity 
+  {
+    dev_t device;               /* Device number. */
+    ino_t inode;                /* Inode number. */
+  };
+
+/* Returns a pointer to a dynamically allocated structure whose
+   value can be used to tell whether two files are actually the
+   same file.  Returns a null pointer if no information about the
+   file is available, perhaps because it does not exist.  The
+   caller is responsible for freeing the structure with
+   fn_free_identity() when finished. */  
+struct file_identity *
+fn_get_identity (const char *filename) 
+{
+  struct stat s;
+
+  if (stat (filename, &s) == 0) 
+    {
+      struct file_identity *identity = xmalloc (sizeof *identity);
+      identity->device = s.st_dev;
+      identity->inode = s.st_ino;
+      return identity;
+    }
+  else
+    return NULL;
+}
+
+/* Frees IDENTITY obtained from fn_get_identity(). */
+void
+fn_free_identity (struct file_identity *identity) 
+{
+  free (identity);
+}
+
+/* Compares A and B, returning a strcmp()-type result. */
+int
+fn_compare_file_identities (const struct file_identity *a,
+                            const struct file_identity *b) 
+{
+  assert (a != NULL);
+  assert (b != NULL);
+  if (a->device != b->device)
+    return a->device < b->device ? -1 : 1;
+  else
+    return a->inode < b->inode ? -1 : a->inode > b->inode;
+}
+#else /* not unix */
+/* A file's identity. */
+struct file_identity 
+  {
+    char *normalized_filename;  /* File's normalized name. */
+  };
+
+/* Returns a pointer to a dynamically allocated structure whose
+   value can be used to tell whether two files are actually the
+   same file.  Returns a null pointer if no information about the
+   file is available, perhaps because it does not exist.  The
+   caller is responsible for freeing the structure with
+   fn_free_identity() when finished. */  
+struct file_identity *
+fn_get_identity (const char *filename) 
+{
+  struct file_identity *identity = xmalloc (sizeof *identity);
+  identity->normalized_filename = fn_normalize (filename);
+  return identity;
+}
+
+/* Frees IDENTITY obtained from fn_get_identity(). */
+void
+fn_free_identity (struct file_identity *identity) 
+{
+  if (identity != NULL) 
+    {
+      free (identity->normalized_filename);
+      free (identity);
+    }
+}
+
+/* Compares A and B, returning a strcmp()-type result. */
+int
+fn_compare_file_identities (const struct file_identity *a,
+                            const struct file_identity *b) 
+{
+  return strcmp (a->normalized_filename, b->normalized_filename);
+}
+#endif /* not unix */
diff --git a/src/data/filename.h b/src/data/filename.h
new file mode 100644 (file)
index 0000000..3ad4d64
--- /dev/null
@@ -0,0 +1,88 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !filename_h
+#define filename_h 1
+
+#include <stdio.h>
+
+/* Directory separator and path delimiter for this OS. */
+#ifndef __MSDOS__
+#define DIR_SEPARATOR '/'
+#define PATH_DELIMITER ':'
+#else
+#define DIR_SEPARATOR '\\'
+#define PATH_DELIMITER ';'
+#endif
+
+/* 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_extension (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);
+
+struct file_identity *fn_get_identity (const char *filename);
+void fn_free_identity (struct file_identity *);
+int fn_compare_file_identities (const struct file_identity *,
+                                const struct file_identity *);
+\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/data/format.c b/src/data/format.c
new file mode 100644 (file)
index 0000000..e88df46
--- /dev/null
@@ -0,0 +1,373 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "format.h"
+#include <ctype.h>
+#include "message.h"
+#include <stdlib.h>
+#include "misc.h"
+#include "identifier.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#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},
+};
+
+/* Common formats. */
+const struct fmt_spec f8_2 = {FMT_F, 8, 2};
+
+/* 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;
+}
+
+/* Does checks in common betwen check_input_specifier() and
+   check_output_specifier() and returns true if so.  Otherwise,
+   emits an error message (if EMIT_ERROR is nonzero) and returns
+   false. */
+static bool
+check_common_specifier (const struct fmt_spec *spec, bool emit_error)
+{
+  struct fmt_desc *f ; 
+  char *str;
+
+  if ( spec->type > FMT_NUMBER_OF_FORMATS ) 
+    {
+      if (emit_error)
+        msg (SE, _("Format specifies a bad type (%d)"), spec->type);
+      
+      return false;
+    }
+
+  f = &formats[spec->type];
+  str = fmt_to_string (spec);
+
+  if ((f->cat & FCAT_EVEN_WIDTH) && spec->w % 2)
+    {
+      if (emit_error)
+        msg (SE, _("Format %s specifies an odd width %d, but "
+                   "an even width is required."),
+             str, spec->w);
+      return false;
+    }
+  if (f->n_args > 1 && (spec->d < 0 || spec->d > 16))
+    {
+      if (emit_error)
+        msg (SE, _("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 false;
+    }
+  return true;
+}
+
+/* Checks whether SPEC is valid as an input format and returns
+   nonzero if so.  Otherwise, emits an error message (if
+   EMIT_ERROR is nonzero) and returns zero. */
+int
+check_input_specifier (const struct fmt_spec *spec, int emit_error)
+{
+  struct fmt_desc *f ;
+  char *str ;
+
+  if (!check_common_specifier (spec, emit_error))
+    return false;
+
+  f = &formats[spec->type];
+  str = fmt_to_string (spec);
+
+
+  if (spec->type == FMT_X)
+    return 1;
+  if (f->cat & FCAT_OUTPUT_ONLY)
+    {
+      if (emit_error)
+        msg (SE, _("Format %s may not be used for input."), f->name);
+      return 0;
+    }
+  if (spec->w < f->Imin_w || spec->w > f->Imax_w)
+    {
+      if (emit_error)
+        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 ((spec->type == FMT_F || spec->type == FMT_COMMA
+         || spec->type == FMT_DOLLAR)
+      && spec->d > spec->w)
+    {
+      if (emit_error)
+        msg (SE, _("Input format %s is invalid because it specifies more "
+                   "decimal places than the field width."), str);
+      return 0;
+    }
+  return 1;
+}
+
+/* Checks whether SPEC is valid as an output format and returns
+   nonzero if so.  Otherwise, emits an error message (if
+   EMIT_ERROR is nonzero) and returns zero. */
+int
+check_output_specifier (const struct fmt_spec *spec, int emit_error)
+{
+  struct fmt_desc *f;
+  char *str ; 
+
+  if (!check_common_specifier (spec, emit_error))
+    return false;
+
+  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)
+    {
+      if (emit_error)
+        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->type == FMT_F || spec->type == FMT_COMMA
+         || spec->type == FMT_DOLLAR)
+      && spec->d >= spec->w)
+    {
+      if (emit_error)
+        msg (SE, _("Output format %s is invalid because it specifies as "
+                   "many decimal places as the field width, which "
+                   "fails to allow space for a decimal point.  "
+                   "Try %s%d.%d instead."),
+             str, f->name, spec->d + 1, spec->d);
+      return 0;
+    }
+  return 1;
+}
+
+/* Checks that FORMAT is appropriate for a variable of the given
+   TYPE and returns true if so.  Otherwise returns false and (if
+   EMIT_ERROR is true) emits an error message. */
+bool
+check_specifier_type (const struct fmt_spec *format,
+                      int type, bool emit_error) 
+{
+  const struct fmt_desc *f = &formats[format->type];
+  assert (type == NUMERIC || type == ALPHA);
+  if ((type == ALPHA) != ((f->cat & FCAT_STRING) != 0))
+    {
+      if (emit_error)
+        msg (SE, _("%s variables are not compatible with %s format %s."),
+             type == ALPHA ? _("String") : _("Numeric"),
+             type == ALPHA ? _("numeric") : _("string"),
+             fmt_to_string (format));
+      return false;
+    }
+  return true;
+}
+  
+/* Checks that FORMAT is appropriate for a variable of the given
+   WIDTH and returns true if so.  Otherwise returns false and (if
+   EMIT_ERROR is true) emits an error message. */
+bool
+check_specifier_width (const struct fmt_spec *format,
+                       int width, bool emit_error) 
+{
+  if (!check_specifier_type (format, width != 0 ? ALPHA : NUMERIC, emit_error))
+    return false;
+  if (get_format_var_width (format) != width)
+    {
+      if (emit_error)
+        msg (SE, _("String variable with width %d not compatible with "
+                   "format %s."),
+             width, fmt_to_string (format));
+      return false;
+    }
+  return true;
+}
+
+/* Converts input format specifier INPUT into output format
+   specifier OUTPUT. */
+void
+convert_fmt_ItoO (const struct fmt_spec *input, struct fmt_spec *output)
+{
+  assert (check_input_specifier (input, 0));
+
+  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 > 0)
+       output->w++;
+      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);
+    }
+
+  assert (check_output_specifier (output, 0));
+}
+
+/* Returns the width corresponding to the format specifier.  The
+   return value is the value of the `width' member of a `struct
+   variable' for such an input format. */
+int
+get_format_var_width (const struct fmt_spec *spec) 
+{
+  if (spec->type == FMT_AHEX)
+    return spec->w / 2;
+  else if (spec->type == FMT_A)
+    return spec->w;
+  else
+    return 0;
+}
+
+/* Returns the PSPP format corresponding to the given SPSS
+   format. */
+int
+translate_fmt (int spss) 
+{
+  int type;
+  
+  for (type = 0; type < FMT_NUMBER_OF_FORMATS; type++)
+    if (formats[type].spss == spss)
+      return type;
+  return -1;
+}
+
+/* Returns an input format specification with type TYPE, width W,
+   and D decimals. */
+struct fmt_spec
+make_input_format (int type, int w, int d) 
+{
+  struct fmt_spec f;
+  f.type = type;
+  f.w = w;
+  f.d = d;
+  assert (check_input_specifier (&f, 0));
+  return f;
+}
+
+/* Returns an output format specification with type TYPE, width
+   W, and D decimals. */
+struct fmt_spec
+make_output_format (int type, int w, int d)
+{
+  struct fmt_spec f;
+  f.type = type;
+  f.w = w;
+  f.d = d;
+  assert (check_output_specifier (&f, 0));
+  return f;
+}
diff --git a/src/data/format.def b/src/data/format.def
new file mode 100644 (file)
index 0000000..0dfc21c
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, 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,  255, 0004, FMT_A, 1)
+DEFFMT (FMT_AHEX,        "AHEX",      1,  2, 510,  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, 38)
+DEFFMT (FMT_SDATE,       "SDATE",     1,  8,  40,  8,   40, 0001, FMT_SDATE, 39)
+DEFFMT (FMT_ADATE,       "ADATE",     1,  8,  40,  8,   40, 0001, FMT_ADATE, 23)
+DEFFMT (FMT_JDATE,       "JDATE",     1,  5,  40,  5,   40, 0001, FMT_JDATE, 24)
+DEFFMT (FMT_QYR,         "QYR",       1,  4,  40,  6,   40, 0001, FMT_QYR, 29)
+DEFFMT (FMT_MOYR,        "MOYR",      1,  6,  40,  6,   40, 0001, FMT_MOYR, 28)
+DEFFMT (FMT_WKYR,        "WKYR",      1,  6,  40,  8,   40, 0001, FMT_WKYR, 30)
+DEFFMT (FMT_DATETIME,    "DATETIME",  2, 17,  40, 17,   40, 0001, FMT_DATETIME, 22)
+DEFFMT (FMT_TIME,        "TIME",      2,  5,  40,  5,   40, 0001, FMT_TIME, 21)
+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/data/format.h b/src/data/format.h
new file mode 100644 (file)
index 0000000..edf7f64
--- /dev/null
@@ -0,0 +1,128 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !format_h
+#define format_h 1
+
+/* Display format types. */
+
+#include <stdbool.h>
+
+/* See the definitions of these functions and variables when modifying
+   this list:
+   misc.c:convert_fmt_ItoO()
+   sys-file-reader.c:parse_format_spec()
+   data-in.c:parse_string_as_format() */
+#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. */
+  };
+
+
+enum alignment 
+  {
+    ALIGN_LEFT = 0,
+    ALIGN_RIGHT = 1,
+    ALIGN_CENTRE = 2
+  };
+
+
+enum measure
+  {
+    MEASURE_NOMINAL=1,
+    MEASURE_ORDINAL=2,
+    MEASURE_SCALE=3
+  };
+
+
+
+/* Descriptions of all the display formats above. */
+extern struct fmt_desc formats[];
+
+union value;
+
+/* Maximum length of formatted value, in characters. */
+#define MAX_FORMATTED_LEN 256
+
+/* Flags for parsing formats. */
+enum fmt_parse_flags
+  {
+    FMTP_ALLOW_XT = 001,                /* 1=Allow X and T formats. */
+    FMTP_SUPPRESS_ERRORS = 002          /* 1=Do not emit error messages. */
+  };
+
+/* Common formats. */
+extern const struct fmt_spec f8_2;      /* F8.2. */
+
+int parse_format_specifier (struct fmt_spec *input, enum fmt_parse_flags);
+int parse_format_specifier_name (const char **cp, enum fmt_parse_flags);
+int check_input_specifier (const struct fmt_spec *spec, int emit_error);
+int check_output_specifier (const struct fmt_spec *spec, int emit_error);
+bool check_specifier_type (const struct fmt_spec *, int type, bool emit_error);
+bool check_specifier_width (const struct fmt_spec *,
+                            int width, bool emit_error);
+void convert_fmt_ItoO (const struct fmt_spec *input, struct fmt_spec *output);
+int get_format_var_width (const struct fmt_spec *);
+int parse_string_as_format (const char *s, int len, const struct fmt_spec *fp,
+                           int fc, union value *v);
+int translate_fmt (int spss);
+bool 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);
+struct fmt_spec make_input_format (int type, int w, int d);
+struct fmt_spec make_output_format (int type, int w, int d);
+
+#endif /* !format_h */
diff --git a/src/data/identifier.c b/src/data/identifier.c
new file mode 100644 (file)
index 0000000..f90f4be
--- /dev/null
@@ -0,0 +1,130 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2005 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+/* 
+   This file is concerned with the definition of the PSPP syntax, NOT the 
+   action of scanning/parsing code .
+*/
+
+#include <config.h>
+#include "identifier.h"
+
+
+#include <assert.h>
+#include <string.h>
+
+
+/* Table of keywords. */
+const char *keywords[T_N_KEYWORDS + 1] = 
+  {
+    "AND", "OR", "NOT",
+    "EQ", "GE", "GT", "LE", "LT", "NE",
+    "ALL", "BY", "TO", "WITH",
+    NULL,
+  };
+
+/* Recognizing identifiers. */
+
+/* Returns true if C may be the first character in an
+   identifier. */
+bool
+lex_is_id1 (char c_) 
+{
+  unsigned char c = c_;
+  return isalpha (c) || c == '@' || c == '#' || c == '$';
+}
+
+/* Returns true if C may be a character in an identifier other
+   than the first. */
+bool
+lex_is_idn (char c_)
+{
+  unsigned char c = c_;
+  return lex_is_id1 (c) || isdigit (c) || c == '.' || c == '_';
+}
+
+/* If string S begins with an identifier, returns the first
+   character following it.  Otherwise, returns S unchanged. */
+char *
+lex_skip_identifier (const char *s) 
+{
+  if (lex_is_id1 (*s))
+    {
+      s++;
+      while (lex_is_idn (*s))
+        s++;
+    }
+  return (char *) s;
+}
+\f
+/* Comparing identifiers. */
+
+/* Keywords match if one of the following is true: KW and TOK are
+   identical (except for 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. */
+bool
+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 true;
+      else if (i == tok_len)
+       return i >= 3;
+      else if (i == kw_len)
+       return false;
+      else if (toupper ((unsigned char) kw[i])
+              != toupper ((unsigned char) tok[i]))
+       return false;
+
+      i++;
+    }
+}
+
+/* Same as lex_id_match_len() minus the need to pass in the lengths. */
+bool
+lex_id_match (const char *kw, const char *tok)
+{
+  return lex_id_match_len (kw, strlen (kw), tok, strlen (tok));
+}
+
+
+
+/* Returns the proper token type, either T_ID or a reserved keyword
+   enum, for ID[], which must contain LEN characters. */
+int
+lex_id_to_token (const char *id, size_t len)
+{
+  const char **kwp;
+
+  if (len < 2 || len > 4)
+    return T_ID;
+  
+  for (kwp = keywords; *kwp; kwp++)
+    if (!strcasecmp (*kwp, id))
+      return T_FIRST_KEYWORD + (kwp - keywords);
+
+  return T_ID;
+}
+\f
diff --git a/src/data/identifier.h b/src/data/identifier.h
new file mode 100644 (file)
index 0000000..7f4beb7
--- /dev/null
@@ -0,0 +1,71 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !lex_def_h
+#define lex_def_h 1
+
+#include <ctype.h>
+#include <stdbool.h>
+#include <sys/types.h>
+
+/* Token types. */
+/* The order of the enumerals below is important.  Do not change it. */
+enum
+  {
+    T_ID = 256, /* Identifier. */
+    T_POS_NUM, /* Positive number. */
+    T_NEG_NUM, /* Negative 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
+  };
+
+/* Recognizing identifiers. */
+bool lex_is_id1 (char);
+bool lex_is_idn (char);
+char *lex_skip_identifier (const char *);
+
+/* Comparing identifiers. */
+bool lex_id_match_len (const char *keyword_string, size_t keyword_len,
+                       const char *token_string, size_t token_len);
+bool lex_id_match (const char *keyword_string, const char *token_string);
+int lex_id_to_token (const char *id, size_t len);
+
+#endif /* !lex_def_h */
diff --git a/src/data/make-file.c b/src/data/make-file.c
new file mode 100644 (file)
index 0000000..312f9ff
--- /dev/null
@@ -0,0 +1,108 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include "filename.h"
+#include "make-file.h"
+#include "message.h"
+#include "alloc.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* Creates a temporary file and stores its name in *FILENAME and
+   a file descriptor for it in *FD.  Returns success.  Caller is
+   responsible for freeing *FILENAME. */
+int
+make_temp_file (int *fd, char **filename)
+{
+  const char *parent_dir;
+
+  assert (filename != NULL);
+  assert (fd != NULL);
+
+  if (getenv ("TMPDIR") != NULL)
+    parent_dir = getenv ("TMPDIR");
+  else
+    parent_dir = P_tmpdir;
+
+  *filename = xmalloc (strlen (parent_dir) + 32);
+  sprintf (*filename, "%s%cpsppXXXXXX", parent_dir, DIR_SEPARATOR);
+  *fd = mkstemp (*filename);
+  if (*fd < 0)
+    {
+      msg (ME, _("%s: Creating temporary file: %s."),
+           *filename, strerror (errno));
+      free (*filename);
+      *filename = NULL;
+      return 0;
+    }
+  return 1;
+}
+
+
+/* Creates a temporary file and stores its name in *FILENAME and
+   a file stream for it in *FP.  Returns success.  Caller is
+   responsible for freeing *FILENAME and for closing *FP */
+int
+make_unique_file_stream (FILE **fp, char **filename)
+{
+  static int serial = 0;
+  const char *parent_dir;
+
+
+  /* FIXME: 
+     Need to check for pre-existing file name.
+     Need also to pass in the directory instead of using /tmp 
+  */
+
+  assert (filename != NULL);
+  assert (fp != NULL);
+
+  if (getenv ("TMPDIR") != NULL)
+    parent_dir = getenv ("TMPDIR");
+  else
+    parent_dir = P_tmpdir;
+
+  *filename = xmalloc (strlen (parent_dir) + 32);
+
+
+  sprintf (*filename, "%s%cpspp%d.png", parent_dir, DIR_SEPARATOR, serial++);
+
+  *fp = fopen(*filename, "w");
+
+  if (! *fp )
+    {
+      msg (ME, _("%s: Creating file: %s."), *filename, strerror (errno));
+      free (*filename);
+      *filename = NULL;
+      return 0;
+    }
+
+  return 1;
+}
+
+
+
+
diff --git a/src/data/make-file.h b/src/data/make-file.h
new file mode 100644 (file)
index 0000000..88fa879
--- /dev/null
@@ -0,0 +1,35 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef MKFILE_H
+#define MKFILE_H
+
+
+/* Creates a temporary file and stores its name in *FILENAME and
+   a file descriptor for it in *FD.  Returns success.  Caller is
+   responsible for freeing *FILENAME. */
+int make_temp_file (int *fd, char **filename); 
+
+
+/* Creates a temporary file and stores its name in *FILENAME and
+   a file stream for it in *FP.  Returns success.  Caller is
+   responsible for freeing *FILENAME. */
+int make_unique_file_stream (FILE **fp, char **filename) ;
+
+#endif /* make-file.h */
diff --git a/src/data/missing-values.c b/src/data/missing-values.c
new file mode 100644 (file)
index 0000000..6940c6c
--- /dev/null
@@ -0,0 +1,440 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "missing-values.h"
+#include <assert.h>
+#include <stdlib.h>
+#include "str.h"
+
+
+/* Initializes MV as a set of missing values for a variable of
+   the given WIDTH.  Although only numeric variables and short
+   string variables may have missing values, WIDTH may be any
+   valid variable width. */
+void
+mv_init (struct missing_values *mv, int width) 
+{
+  assert (width >= 0 && width <= MAX_STRING);
+  mv->type = MV_NONE;
+  mv->width = width;
+}
+
+void 
+mv_set_type(struct missing_values *mv, enum mv_type type)
+{
+  mv->type = type;
+}
+
+
+/* Copies SRC to MV. */
+void
+mv_copy (struct missing_values *mv, const struct missing_values *src) 
+{
+  assert(src);
+
+  *mv = *src;
+}
+
+/* Returns true if MV is an empty set of missing values. */
+bool
+mv_is_empty (const struct missing_values *mv) 
+{
+  return mv->type == MV_NONE;
+}
+
+/* Returns the width of the missing values that MV may
+   contain. */
+int
+mv_get_width (const struct missing_values *mv)
+{
+  return mv->width;
+}
+
+/* Attempts to add individual value V to the set of missing
+   values MV.  Returns true if successful, false if MV has no
+   more room for missing values.  (Long string variables never
+   accept missing values.) */
+bool
+mv_add_value (struct missing_values *mv, const union value *v)
+{
+  if (mv->width > MAX_SHORT_STRING)
+    return false;
+  switch (mv->type) 
+    {
+    case MV_NONE:
+    case MV_1:
+    case MV_2:
+    case MV_RANGE:
+      mv->values[mv->type & 3] = *v;
+      mv->type++;
+      return true;
+
+    case MV_3:
+    case MV_RANGE_1:
+      return false;
+    }
+  abort ();
+}
+
+/* Attempts to add S to the set of string missing values MV.  S
+   must contain exactly as many characters as MV's width.
+   Returns true if successful, false if MV has no more room for
+   missing values.  (Long string variables never accept missing
+   values.) */
+bool
+mv_add_str (struct missing_values *mv, const char s[]) 
+{
+  assert (mv->width > 0);
+  return mv_add_value (mv, (union value *) s);
+}
+
+/* Attempts to add D to the set of numeric missing values MV.
+   Returns true if successful, false if MV has no more room for
+   missing values.  */
+bool
+mv_add_num (struct missing_values *mv, double d) 
+{
+  assert (mv->width == 0);
+  return mv_add_value (mv, (union value *) &d);
+}
+
+/* Attempts to add range [LOW, HIGH] to the set of numeric
+   missing values MV.  Returns true if successful, false if MV
+   has no room for a range, or if LOW > HIGH. */
+bool
+mv_add_num_range (struct missing_values *mv, double low, double high) 
+{
+  assert (mv->width == 0);
+  if (low > high)
+    return false;
+  switch (mv->type) 
+    {
+    case MV_NONE:
+    case MV_1:
+      mv->values[1].f = low;
+      mv->values[2].f = high;
+      mv->type |= 4;
+      return true;
+
+    case MV_2:
+    case MV_3:
+    case MV_RANGE:
+    case MV_RANGE_1:
+      return false;
+    }
+  abort ();
+}
+
+/* Returns true if MV contains an individual value,
+   false if MV is empty (or contains only a range). */
+bool
+mv_has_value (const struct missing_values *mv)
+{
+  switch (mv->type) 
+    {
+    case MV_1:
+    case MV_2:
+    case MV_3:
+    case MV_RANGE_1:
+      return true;
+      
+    case MV_NONE:
+    case MV_RANGE:
+      return false;
+    }
+  abort ();
+}
+
+/* Removes one individual value from MV and stores it in *V.
+   MV must contain an individual value (as determined by
+   mv_has_value()). */
+void
+mv_pop_value (struct missing_values *mv, union value *v) 
+{
+  assert (mv_has_value (mv));
+  mv->type--;
+  *v = mv->values[mv->type & 3];
+}
+
+/* Stores  a value  in *V.
+   MV must contain an individual value (as determined by
+   mv_has_value()). 
+   IDX is the zero based index of the value to get
+*/
+void
+mv_peek_value (const struct missing_values *mv, union value *v, int idx) 
+{
+  assert (idx >= 0 ) ;
+  assert (idx < 3);
+
+  assert (mv_has_value (mv));
+  *v = mv->values[idx];
+}
+
+void 
+mv_replace_value (struct missing_values *mv, const union value *v, int idx)
+{
+  assert (idx >= 0) ;
+  assert (idx < mv_n_values(mv));
+
+  mv->values[idx] = *v;
+}
+
+
+
+int  
+mv_n_values (const struct missing_values *mv)
+{
+  assert(mv_has_value(mv));
+  return mv->type & 3;
+}
+
+
+/* Returns true if MV contains a numeric range,
+   false if MV is empty (or contains only individual values). */
+bool
+mv_has_range (const struct missing_values *mv) 
+{
+  switch (mv->type) 
+    {
+    case MV_RANGE:
+    case MV_RANGE_1:
+      return true;
+      
+    case MV_NONE:
+    case MV_1:
+    case MV_2:
+    case MV_3:
+      return false;
+    }
+  abort ();
+}
+
+/* Removes the numeric range from MV and stores it in *LOW and
+   *HIGH.  MV must contain a individual range (as determined by
+   mv_has_range()). */
+void
+mv_pop_range (struct missing_values *mv, double *low, double *high) 
+{
+  assert (mv_has_range (mv));
+  *low = mv->values[1].f;
+  *high = mv->values[2].f;
+  mv->type &= 3;
+}
+
+
+/* Returns the numeric range from MV  into *LOW and
+   *HIGH.  MV must contain a individual range (as determined by
+   mv_has_range()). */
+void
+mv_peek_range (const struct missing_values *mv, double *low, double *high) 
+{
+  assert (mv_has_range (mv));
+  *low = mv->values[1].f;
+  *high = mv->values[2].f;
+}
+
+
+/* Returns true if values[IDX] is in use when the `type' member
+   is set to TYPE (in struct missing_values),
+   false otherwise. */
+static bool
+using_element (unsigned type, int idx) 
+{
+  assert (idx >= 0 && idx < 3);
+  
+  switch (type) 
+    {
+    case MV_NONE:
+      return false;
+    case MV_1:
+      return idx < 1;
+    case MV_2:
+      return idx < 2;
+    case MV_3:
+      return true;
+    case MV_RANGE:
+      return idx > 0;
+    case MV_RANGE_1:
+      return true;
+    }
+  abort ();
+}
+
+/* Returns true if S contains only spaces between indexes
+   NEW_WIDTH (inclusive) and OLD_WIDTH (exclusive),
+   false otherwise. */
+static bool
+can_resize_string (const char *s, int old_width, int new_width) 
+{
+  int i;
+
+  assert (new_width < old_width);
+  for (i = new_width; i < old_width; i++)
+    if (s[i] != ' ')
+      return false;
+  return true;
+}
+
+/* Returns true if MV can be resized to the given WIDTH with
+   mv_resize(), false otherwise.  Resizing to the same width is
+   always possible.  Resizing to a long string WIDTH is only
+   possible if MV is an empty set of missing values; otherwise,
+   resizing to a larger WIDTH is always possible.  Resizing to a
+   shorter width is possible only when each missing value
+   contains only spaces in the characters that will be
+   trimmed. */
+bool
+mv_is_resizable (struct missing_values *mv, int width) 
+{
+  assert ((width == 0) == (mv->width == 0));
+  if (width > MAX_SHORT_STRING && mv->type != MV_NONE)
+    return false;
+  else if (width >= mv->width)
+    return true;
+  else 
+    {
+      int i;
+      
+      for (i = 0; i < 3; i++)
+        if (using_element (mv->type, i)
+            && !can_resize_string (mv->values[i].s, mv->width, width))
+          return false;
+      return true;
+    }
+}
+
+/* Resizes MV to the given WIDTH.  WIDTH must fit the constraints
+   explained for mv_is_resizable(). */
+void
+mv_resize (struct missing_values *mv, int width) 
+{
+  assert (mv_is_resizable (mv, width));
+  if (width > mv->width) 
+    {
+      int i;
+      
+      for (i = 0; i < 3; i++)
+        memset (mv->values[i].s + mv->width, ' ', width - mv->width);
+    }
+  mv->width = width;
+}
+
+/* Returns true if V is system missing or a missing value in MV,
+   false otherwise. */
+bool
+mv_is_value_missing (const struct missing_values *mv, const union value *v)
+{
+  return (mv->width == 0
+          ? mv_is_num_missing (mv, v->f)
+          : mv_is_str_missing (mv, v->s));
+}
+
+/* Returns true if D is system missing or a missing value in MV,
+   false otherwise.
+   MV must be a set of numeric missing values. */
+bool
+mv_is_num_missing (const struct missing_values *mv, double d)
+{
+  assert (mv->width == 0);
+  return d == SYSMIS || mv_is_num_user_missing (mv, d);
+}
+
+/* Returns true if S[] is a missing value in MV, false otherwise.
+   MV must be a set of string missing values. 
+   S[] must contain exactly as many characters as MV's width. */
+bool
+mv_is_str_missing (const struct missing_values *mv, const char s[])
+{
+  return mv_is_str_user_missing (mv, s);
+}
+
+/* Returns true if V is a missing value in MV, false otherwise. */
+bool
+mv_is_value_user_missing (const struct missing_values *mv,
+                          const union value *v)
+{
+  return (mv->width == 0
+          ? mv_is_num_user_missing (mv, v->f)
+          : mv_is_str_user_missing (mv, v->s));
+}
+
+/* Returns true if D is a missing value in MV, false otherwise.
+   MV must be a set of numeric missing values. */
+bool
+mv_is_num_user_missing (const struct missing_values *mv, double d)
+{
+  const union value *v = mv->values;
+  assert (mv->width == 0);
+  switch (mv->type) 
+    {
+    case MV_NONE:
+      return false;
+    case MV_1:
+      return v[0].f == d;
+    case MV_2:
+      return v[0].f == d || v[1].f == d;
+    case MV_3:
+      return v[0].f == d || v[1].f == d || v[2].f == d;
+    case MV_RANGE:
+      return v[1].f <= d && d <= v[2].f;
+    case MV_RANGE_1:
+      return v[0].f == d || (v[1].f <= d && d <= v[2].f);
+    }
+  abort ();
+}
+
+/* Returns true if S[] is a missing value in MV, false otherwise.
+   MV must be a set of string missing values. 
+   S[] must contain exactly as many characters as MV's width. */
+bool
+mv_is_str_user_missing (const struct missing_values *mv,
+                        const char s[])
+{
+  const union value *v = mv->values;
+  assert (mv->width > 0);
+  switch (mv->type) 
+    {
+    case MV_NONE:
+      return false;
+    case MV_1:
+      return !memcmp (v[0].s, s, mv->width);
+    case MV_2:
+      return (!memcmp (v[0].s, s, mv->width)
+              || !memcmp (v[1].s, s, mv->width));
+    case MV_3:
+      return (!memcmp (v[0].s, s, mv->width)
+              || !memcmp (v[1].s, s, mv->width)
+              || !memcmp (v[2].s, s, mv->width));
+    case MV_RANGE:
+    case MV_RANGE_1:
+      abort ();
+    }
+  abort ();
+}
+
+/* Returns true if MV is a set of numeric missing values and V is
+   the system missing value. */
+bool
+mv_is_value_system_missing (const struct missing_values *mv,
+                            const union value *v)
+{
+  return mv->width == 0 ? v->f == SYSMIS : false;
+}
diff --git a/src/data/missing-values.h b/src/data/missing-values.h
new file mode 100644 (file)
index 0000000..43e6290
--- /dev/null
@@ -0,0 +1,93 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !missing_values_h
+#define missing_values_h 1
+
+#include <stdbool.h>
+#include "value.h"
+
+/* Types of user-missing values.
+   Invisible--use access functions defined below instead. */
+enum mv_type 
+  {
+    MV_NONE = 0,                /* No user-missing values. */
+    MV_1 = 1,                   /* One user-missing value. */
+    MV_2 = 2,                   /* Two user-missing values. */
+    MV_3 = 3,                   /* Three user-missing values. */
+    MV_RANGE = 4,               /* A range of user-missing values. */
+    MV_RANGE_1 = 5              /* A range plus an individual value. */
+  };
+
+/* Missing values.
+   Opaque--use access functions defined below. */
+struct missing_values 
+  {
+    unsigned type;              /* Number and type of missing values. */
+    int width;                  /* 0=numeric, otherwise string width. */
+    union value values[3];      /* Missing values.  [y,z] are the range. */
+  };
+
+
+void mv_init (struct missing_values *, int width);
+void mv_set_type(struct missing_values *mv, enum mv_type type);
+
+void mv_copy (struct missing_values *, const struct missing_values *);
+bool mv_is_empty (const struct missing_values *);
+int mv_get_width (const struct missing_values *);
+
+bool mv_add_value (struct missing_values *, const union value *);
+bool mv_add_str (struct missing_values *, const char[]);
+bool mv_add_num (struct missing_values *, double);
+bool mv_add_num_range (struct missing_values *, double low, double high);
+
+bool mv_has_value (const struct missing_values *);
+void mv_pop_value (struct missing_values *, union value *);
+void mv_peek_value (const struct missing_values *mv, union value *v, int idx);
+void mv_replace_value (struct missing_values *mv, const union value *v, int idx);
+
+int  mv_n_values (const struct missing_values *mv);
+
+
+bool mv_has_range (const struct missing_values *);
+void mv_pop_range (struct missing_values *, double *low, double *high);
+void mv_peek_range (const struct missing_values *, double *low, double *high);
+
+bool mv_is_resizable (struct missing_values *, int width);
+void mv_resize (struct missing_values *, int width);
+
+typedef bool is_missing_func (const struct missing_values *,
+                              const union value *);
+
+/* Is a value system or user missing? */
+bool mv_is_value_missing (const struct missing_values *, const union value *);
+bool mv_is_num_missing (const struct missing_values *, double);
+bool mv_is_str_missing (const struct missing_values *, const char[]);
+
+/* Is a value user missing? */
+bool mv_is_value_user_missing (const struct missing_values *,
+                               const union value *);
+bool mv_is_num_user_missing (const struct missing_values *, double);
+bool mv_is_str_user_missing (const struct missing_values *, const char[]);
+
+/* Is a value system missing? */
+bool mv_is_value_system_missing (const struct missing_values *,
+                                 const union value *);
+
+#endif /* missing-values.h */
diff --git a/src/data/por-file-reader.c b/src/data/por-file-reader.c
new file mode 100644 (file)
index 0000000..db33acf
--- /dev/null
@@ -0,0 +1,736 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+   Code for parsing floating-point numbers adapted from GNU C
+   library.
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "por-file-reader.h"
+#include "message.h"
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <math.h>
+#include <setjmp.h>
+#include "alloc.h"
+#include <stdbool.h>
+#include "case.h"
+#include "dictionary.h"
+#include "file-handle-def.h"
+#include "format.h"
+#include "hash.h"
+#include "magic.h"
+#include "misc.h"
+#include "pool.h"
+#include "str.h"
+#include "value-labels.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+/* portable_to_local[PORTABLE] translates the given portable
+   character into the local character set. */
+static const char portable_to_local[256] =
+  {
+    "                                                                "
+    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ."
+    "<(+|&[]!$*);^-/|,%_>?`:$@'=\"      ~-   0123456789   -() {}\\     "
+    "                                                                "
+  };
+
+/* Portable file reader. */
+struct pfm_reader
+  {
+    struct pool *pool;          /* All the portable file state. */
+
+    jmp_buf bail_out;           /* longjmp() target for error handling. */
+
+    struct file_handle *fh;     /* File handle. */
+    FILE *file;                        /* File stream. */
+    char cc;                   /* Current character. */
+    char *trans;                /* 256-byte character set translation table. */
+    int var_cnt;                /* Number of variables. */
+    int weight_index;          /* 0-based index of weight variable, or -1. */
+    int *widths;                /* Variable widths, 0 for numeric. */
+    int value_cnt;             /* Number of `value's per case. */
+    bool ok;                    /* Set false on I/O error. */
+  };
+
+static void
+error (struct pfm_reader *r, const char *msg,...)
+     PRINTF_FORMAT (2, 3);
+
+/* Displays MSG as an error message and aborts reading the
+   portable file via longjmp(). */
+static void
+error (struct pfm_reader *r, const char *msg, ...)
+{
+  struct error e;
+  const char *filename;
+  char *title;
+  va_list args;
+
+  e.class = ME;
+  e.where.filename = NULL;
+  e.where.line_number = 0;
+  filename = fh_get_filename (r->fh);
+  e.title = title = pool_alloc (r->pool, strlen (filename) + 80);
+  sprintf (title, _("portable file %s corrupt at offset %ld: "),
+           filename, ftell (r->file));
+
+  va_start (args, msg);
+  err_vmsg (&e, msg, args);
+  va_end (args);
+
+  r->ok = false;
+
+  longjmp (r->bail_out, 1);
+}
+
+/* Closes portable file reader R, after we're done with it. */
+void
+pfm_close_reader (struct pfm_reader *r)
+{
+  if (r != NULL)
+    pool_destroy (r->pool);
+}
+
+/* Read a single character into cur_char.  */
+static void
+advance (struct pfm_reader *r)
+{
+  int c;
+
+  while ((c = getc (r->file)) == '\r' || c == '\n')
+    continue;
+  if (c == EOF)
+    error (r, _("unexpected end of file")); 
+
+  if (r->trans != NULL)
+    c = r->trans[c]; 
+  r->cc = c;
+}
+
+/* Skip a single character if present, and return whether it was
+   skipped. */
+static inline bool
+match (struct pfm_reader *r, int c)
+{
+  if (r->cc == c)
+    {
+      advance (r);
+      return true;
+    }
+  else
+    return false;
+}
+
+static void read_header (struct pfm_reader *);
+static void read_version_data (struct pfm_reader *, struct pfm_read_info *);
+static void read_variables (struct pfm_reader *, struct dictionary *);
+static void read_value_label (struct pfm_reader *, struct dictionary *);
+void dump_dictionary (struct dictionary *);
+
+/* 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 pfm_reader *
+pfm_open_reader (struct file_handle *fh, struct dictionary **dict,
+                 struct pfm_read_info *info)
+{
+  struct pool *volatile pool = NULL;
+  struct pfm_reader *volatile r = NULL;
+
+  *dict = dict_create ();
+  if (!fh_open (fh, FH_REF_FILE, "portable file", "rs"))
+    goto error;
+
+  /* Create and initialize reader. */
+  pool = pool_create ();
+  r = pool_alloc (pool, sizeof *r);
+  r->pool = pool;
+  if (setjmp (r->bail_out))
+    goto error;
+  r->fh = fh;
+  r->file = pool_fopen (r->pool, fh_get_filename (r->fh), "rb");
+  r->weight_index = -1;
+  r->trans = NULL;
+  r->var_cnt = 0;
+  r->widths = NULL;
+  r->value_cnt = 0;
+  r->ok = true;
+
+  /* Check that file open succeeded, prime reading. */
+  if (r->file == NULL)
+    {
+      msg (ME, _("An error occurred while opening \"%s\" for reading "
+                 "as a portable file: %s."),
+           fh_get_filename (r->fh), strerror (errno));
+      goto error;
+    }
+  
+  /* Read header, version, date info, product id, variables. */
+  read_header (r);
+  read_version_data (r, info);
+  read_variables (r, *dict);
+
+  /* Read value labels. */
+  while (match (r, 'D'))
+    read_value_label (r, *dict);
+
+  /* Check that we've made it to the data. */
+  if (!match (r, 'F'))
+    error (r, _("Data record expected."));
+
+  return r;
+
+ error:
+  pfm_close_reader (r);
+  dict_destroy (*dict);
+  *dict = NULL;
+  return NULL;
+}
+\f
+/* Returns the value of base-30 digit C,
+   or -1 if C is not a base-30 digit. */
+static int
+base_30_value (unsigned char c) 
+{
+  static const char base_30_digits[] = "0123456789ABCDEFGHIJKLMNOPQRST";
+  const char *p = strchr (base_30_digits, c);
+  return p != NULL ? p - base_30_digits : -1;
+}
+
+/* Read a floating point value and return its value. */
+static double
+read_float (struct pfm_reader *r)
+{
+  double num = 0.;
+  int exponent = 0;
+  bool got_dot = false;         /* Seen a decimal point? */
+  bool got_digit = false;       /* Seen any digits? */
+  bool negative = false;        /* Number is negative? */
+
+  /* Skip leading spaces. */
+  while (match (r, ' '))
+    continue;
+
+  /* `*' indicates system-missing. */
+  if (match (r, '*'))
+    {
+      advance (r);     /* Probably a dot (.) but doesn't appear to matter. */
+      return SYSMIS;
+    }
+
+  negative = match (r, '-');
+  for (;;)
+    {
+      int digit = base_30_value (r->cc);
+      if (digit != -1)
+       {
+         got_digit = true;
+
+         /* 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) + digit;
+
+         /* 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 && r->cc == '.')
+       /* Record that we have found the decimal point.  */
+       got_dot = 1;
+      else
+       /* Any other character terminates the number.  */
+       break;
+
+      advance (r);
+    }
+
+  /* Check that we had some digits. */
+  if (!got_digit)
+    error (r, "Number expected.");
+
+  /* Get exponent if any. */
+  if (r->cc == '+' || r->cc == '-')
+    {
+      long int exp = 0;
+      bool negative_exponent = r->cc == '-';
+      int digit;
+
+      for (advance (r); (digit = base_30_value (r->cc)) != -1; advance (r))
+       {
+         if (exp > LONG_MAX / 30)
+            {
+              exp = LONG_MAX;
+              break;
+            }
+         exp = exp * 30 + digit;
+       }
+
+      /* We don't check whether there were actually any digits, but we
+         probably should. */
+      if (negative_exponent)
+       exp = -exp;
+      exponent += exp;
+    }
+
+  /* Numbers must end with `/'. */
+  if (!match (r, '/'))
+    error (r, _("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))
+        num = DBL_MAX;
+      else
+        num *= pow (30.0, (double) exponent);
+    }
+
+  return negative ? -num : num;
+}
+  
+/* Read an integer and return its value. */
+static int
+read_int (struct pfm_reader *r)
+{
+  double f = read_float (r);
+  if (floor (f) != f || f >= INT_MAX || f <= INT_MIN)
+    error (r, _("Invalid integer."));
+  return f;
+}
+
+/* Reads a string into BUF, which must have room for 256
+   characters. */
+static void
+read_string (struct pfm_reader *r, char *buf)
+{
+  int n = read_int (r);
+  if (n < 0 || n > 255)
+    error (r, _("Bad string length %d."), n);
+  
+  while (n-- > 0)
+    {
+      *buf++ = r->cc;
+      advance (r);
+    }
+  *buf = '\0';
+}
+
+/* Reads a string and returns a copy of it allocated from R's
+   pool. */
+static char *
+read_pool_string (struct pfm_reader *r) 
+{
+  char string[256];
+  read_string (r, string);
+  return pool_strdup (r->pool, string);
+}
+\f
+/* Reads the 464-byte file header. */
+static void
+read_header (struct pfm_reader *r)
+{
+  char *trans;
+  int i;
+
+  /* Read and ignore vanity splash strings. */
+  for (i = 0; i < 200; i++)
+    advance (r);
+  
+  /* Skip the first 64 characters of the translation table.
+     We don't care about these.  They are probably all set to
+     '0', marking them as untranslatable, and that would screw
+     up our actual translation of the real '0'. */
+  for (i = 0; i < 64; i++)
+    advance (r);
+
+  /* Read the rest of the translation table. */
+  trans = pool_malloc (r->pool, 256);
+  memset (trans, 0, 256);
+  for (; i < 256; i++) 
+    {
+      unsigned char c;
+
+      advance (r);
+
+      c = r->cc;
+      if (trans[c] == 0)
+        trans[c] = portable_to_local[i];
+    }
+
+  /* Set up the translation table, then read the first
+     translated character. */
+  r->trans = trans;
+  advance (r); 
+
+  /* Skip and verify signature. */
+  for (i = 0; i < 8; i++) 
+    if (!match (r, "SPSSPORT"[i])) 
+      {
+        msg (SE, _("%s: Not a portable file."), fh_get_filename (r->fh));
+        longjmp (r->bail_out, 1);
+      }
+}
+
+/* Reads the version and date info record, as well as product and
+   subproduct identification records if present. */
+static void
+read_version_data (struct pfm_reader *r, struct pfm_read_info *info)
+{
+  static char empty_string[] = "";
+  char *date, *time, *product, *author, *subproduct;
+  int i;
+
+  /* Read file. */
+  if (!match (r, 'A'))
+    error (r, "Unrecognized version code `%c'.", r->cc);
+  date = read_pool_string (r);
+  time = read_pool_string (r);
+  product = match (r, '1') ? read_pool_string (r) : empty_string;
+  author = match (r, '2') ? read_pool_string (r) : empty_string;
+  subproduct = match (r, '3') ? read_pool_string (r) : empty_string;
+
+  /* Validate file. */
+  if (strlen (date) != 8)
+    error (r, _("Bad date string length %d."), strlen (date));
+  if (strlen (time) != 6)
+    error (r, _("Bad time string length %d."), strlen (time));
+
+  /* Save file info. */
+  if (info != NULL) 
+    {
+      /* Date. */
+      for (i = 0; i < 8; i++) 
+        {
+          static const int map[] = {6, 7, 8, 9, 3, 4, 0, 1};
+          info->creation_date[map[i]] = date[i]; 
+        }
+      info->creation_date[2] = info->creation_date[5] = ' ';
+      info->creation_date[10] = 0;
+
+      /* Time. */
+      for (i = 0; i < 6; i++)
+        {
+          static const int map[] = {0, 1, 3, 4, 6, 7};
+          info->creation_time[map[i]] = time[i];
+        }
+      info->creation_time[2] = info->creation_time[5] = ' ';
+      info->creation_time[8] = 0;
+
+      /* Product. */
+      str_copy_trunc (info->product, sizeof info->product, product);
+      str_copy_trunc (info->subproduct, sizeof info->subproduct, subproduct);
+    }
+}
+
+/* Translates a format specification read from portable file R as
+   the three integers INTS into a normal format specifier FORMAT,
+   checking that the format is appropriate for variable V. */
+static void
+convert_format (struct pfm_reader *r, const int portable_format[3],
+                struct fmt_spec *format, struct variable *v)
+{
+  format->type = translate_fmt (portable_format[0]);
+  if (format->type == -1)
+    error (r, _("%s: Bad format specifier byte (%d)."),
+           v->name, portable_format[0]);
+  format->w = portable_format[1];
+  format->d = portable_format[2];
+
+  if (!check_output_specifier (format, false)
+      || !check_specifier_width (format, v->width, false))
+    error (r, _("%s variable %s has invalid format specifier %s."),
+           v->type == NUMERIC ? _("Numeric") : _("String"),
+           v->name, fmt_to_string (format));
+}
+
+static union value parse_value (struct pfm_reader *, struct variable *);
+
+/* Read information on all the variables.  */
+static void
+read_variables (struct pfm_reader *r, struct dictionary *dict)
+{
+  char *weight_name = NULL;
+  int i;
+  
+  if (!match (r, '4'))
+    error (r, _("Expected variable count record."));
+  
+  r->var_cnt = read_int (r);
+  if (r->var_cnt <= 0 || r->var_cnt == NOT_INT)
+    error (r, _("Invalid number of variables %d."), r->var_cnt);
+  r->widths = pool_nalloc (r->pool, r->var_cnt, sizeof *r->widths);
+
+  /* Purpose of this value is unknown.  It is typically 161. */
+  read_int (r);
+
+  if (match (r, '6'))
+    {
+      weight_name = read_pool_string (r);
+      if (strlen (weight_name) > SHORT_NAME_LEN) 
+        error (r, _("Weight variable name (%s) truncated."), weight_name);
+    }
+  
+  for (i = 0; i < r->var_cnt; i++)
+    {
+      int width;
+      char name[256];
+      int fmt[6];
+      struct variable *v;
+      int j;
+
+      if (!match (r, '7'))
+       error (r, _("Expected variable record."));
+
+      width = read_int (r);
+      if (width < 0)
+       error (r, _("Invalid variable width %d."), width);
+      r->widths[i] = width;
+
+      read_string (r, name);
+      for (j = 0; j < 6; j++)
+        fmt[j] = read_int (r);
+
+      if (!var_is_valid_name (name, false) || *name == '#' || *name == '$')
+        error (r, _("position %d: Invalid variable name `%s'."), i, name);
+      str_uppercase (name);
+
+      if (width < 0 || width > 255)
+       error (r, "Bad width %d for variable %s.", width, name);
+
+      v = dict_create_var (dict, name, width);
+      if (v == NULL)
+       error (r, _("Duplicate variable name %s."), name);
+
+      convert_format (r, &fmt[0], &v->print, v);
+      convert_format (r, &fmt[3], &v->write, v);
+
+      /* Range missing values. */
+      if (match (r, 'B')) 
+        {
+          double x = read_float (r);
+          double y = read_float (r);
+          mv_add_num_range (&v->miss, x, y);
+        }
+      else if (match (r, 'A'))
+        mv_add_num_range (&v->miss, read_float (r), HIGHEST);
+      else if (match (r, '9'))
+        mv_add_num_range (&v->miss, LOWEST, read_float (r));
+
+      /* Single missing values. */
+      while (match (r, '8')) 
+        {
+          union value value = parse_value (r, v);
+          mv_add_value (&v->miss, &value); 
+        }
+
+      if (match (r, 'C')) 
+        {
+          char label[256];
+          read_string (r, label);
+          v->label = xstrdup (label); 
+        }
+    }
+
+  if (weight_name != NULL) 
+    {
+      struct variable *weight_var = dict_lookup_var (dict, weight_name);
+      if (weight_var == NULL)
+        error (r, _("Weighting variable %s not present in dictionary."),
+               weight_name);
+
+      dict_set_weight (dict, weight_var);
+    }
+}
+
+/* Parse a value for variable VV into value V. */
+static union value
+parse_value (struct pfm_reader *r, struct variable *vv)
+{
+  union value v;
+  
+  if (vv->type == ALPHA) 
+    {
+      char string[256];
+      read_string (r, string);
+      buf_copy_str_rpad (v.s, 8, string); 
+    }
+  else
+    v.f = read_float (r);
+
+  return v;
+}
+
+/* Parse a value label record and return success. */
+static void
+read_value_label (struct pfm_reader *r, struct dictionary *dict)
+{
+  /* Variables. */
+  int nv;
+  struct variable **v;
+
+  /* Labels. */
+  int n_labels;
+
+  int i;
+
+  nv = read_int (r);
+  v = pool_nalloc (r->pool, nv, sizeof *v);
+  for (i = 0; i < nv; i++)
+    {
+      char name[256];
+      read_string (r, name);
+
+      v[i] = dict_lookup_var (dict, name);
+      if (v[i] == NULL)
+       error (r, _("Unknown variable %s while parsing value labels."), name);
+
+      if (v[0]->width != v[i]->width)
+       error (r, _("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 (r);
+  for (i = 0; i < n_labels; i++)
+    {
+      union value val;
+      char label[256];
+      int j;
+
+      val = parse_value (r, v[0]);
+      read_string (r, label);
+
+      /* Assign the value_label's to each variable. */
+      for (j = 0; j < nv; j++)
+       {
+         struct variable *var = v[j];
+
+         if (!val_labs_replace (var->val_labs, val, label))
+           continue;
+
+         if (var->type == NUMERIC)
+           error (r, _("Duplicate label for value %g for variable %s."),
+                  val.f, var->name);
+         else
+           error (r, _("Duplicate label for value `%.*s' for variable %s."),
+                  var->width, val.s, var->name);
+       }
+    }
+}
+
+/* Reads one case from portable file R into C. */
+bool
+pfm_read_case (struct pfm_reader *r, struct ccase *c)
+{
+  size_t i;
+  size_t idx;
+
+  setjmp (r->bail_out);
+  if (!r->ok)
+    return false;
+  
+  /* Check for end of file. */
+  if (r->cc == 'Z')
+    return false;
+
+  idx = 0;
+  for (i = 0; i < r->var_cnt; i++) 
+    {
+      int width = r->widths[i];
+      
+      if (width == 0)
+        {
+          case_data_rw (c, idx)->f = read_float (r);
+          idx++;
+        }
+      else
+        {
+          char string[256];
+          read_string (r, string);
+          buf_copy_str_rpad (case_data_rw (c, idx)->s, width, string);
+          idx += DIV_RND_UP (width, MAX_SHORT_STRING);
+        }
+    }
+  
+  return true;
+}
+
+/* Returns true if an I/O error has occurred on READER, false
+   otherwise. */
+bool
+pfm_read_error (const struct pfm_reader *reader) 
+{
+  return !reader->ok;
+}
+
+/* Returns true if FILE is an SPSS portable file,
+   false otherwise. */
+bool
+pfm_detect (FILE *file) 
+{
+  unsigned char header[464];
+  char trans[256];
+  int cooked_cnt, raw_cnt;
+  int i;
+
+  cooked_cnt = raw_cnt = 0;
+  while (cooked_cnt < sizeof header)
+    {
+      int c = getc (file);
+      if (c == EOF || raw_cnt++ > 512)
+        return false;
+      else if (c != '\n' && c != '\r') 
+        header[cooked_cnt++] = c;
+    }
+
+  memset (trans, 0, 256);
+  for (i = 64; i < 256; i++) 
+    {
+      unsigned char c = header[i + 200];
+      if (trans[c] == 0)
+        trans[c] = portable_to_local[i];
+    }
+
+  for (i = 0; i < 8; i++) 
+    if (trans[header[i + 456]] != "SPSSPORT"[i]) 
+      return false; 
+
+  return true;
+}
diff --git a/src/data/por-file-reader.h b/src/data/por-file-reader.h
new file mode 100644 (file)
index 0000000..83c3a24
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef PFM_READ_H
+#define PFM_READ_H
+
+/* Portable file reading. */
+
+#include <stdbool.h>
+#include <stdio.h>
+
+/* 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;
+struct ccase;
+struct pfm_reader *pfm_open_reader (struct file_handle *,
+                                    struct dictionary **,
+                                    struct pfm_read_info *);
+bool pfm_read_case (struct pfm_reader *, struct ccase *);
+bool pfm_read_error (const struct pfm_reader *);
+void pfm_close_reader (struct pfm_reader *);
+bool pfm_detect (FILE *);
+
+#endif /* por-file-reader.h */
diff --git a/src/data/por-file-writer.c b/src/data/por-file-writer.c
new file mode 100644 (file)
index 0000000..5d6d1ef
--- /dev/null
@@ -0,0 +1,845 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "por-file-writer.h"
+#include "message.h"
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <float.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include "alloc.h"
+#include "case.h"
+#include "dictionary.h"
+#include "message.h"
+#include "file-handle-def.h"
+#include "hash.h"
+#include "magic.h"
+#include "misc.h"
+#include "stat-macros.h"
+#include "str.h"
+#include "value-labels.h"
+#include "variable.h"
+#include "version.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+/* Portable file writer. */
+struct pfm_writer
+  {
+    struct file_handle *fh;     /* File handle. */
+    FILE *file;                        /* File stream. */
+
+    int lc;                    /* Number of characters on this line so far. */
+
+    size_t var_cnt;             /* Number of variables. */
+    struct pfm_var *vars;       /* Variables. */
+
+    int digits;                 /* Digits of precision. */
+  };
+
+/* A variable to write to the portable file. */
+struct pfm_var 
+  {
+    int width;                  /* 0=numeric, otherwise string var width. */
+    int fv;                     /* Starting case index. */
+  };
+
+static void buf_write (struct pfm_writer *, const void *, size_t);
+static void write_header (struct pfm_writer *);
+static void write_version_data (struct pfm_writer *);
+static void write_variables (struct pfm_writer *, struct dictionary *);
+static void write_value_labels (struct pfm_writer *,
+                                const struct dictionary *);
+
+static void format_trig_double (long double, int base_10_precision, char[]);
+static char *format_trig_int (int, bool force_sign, char[]);
+
+/* Returns default options for writing a portable file. */
+struct pfm_write_options
+pfm_writer_default_options (void) 
+{
+  struct pfm_write_options opts;
+  opts.create_writeable = true;
+  opts.type = PFM_COMM;
+  opts.digits = DBL_DIG;
+  return opts;
+}
+
+/* Writes the dictionary DICT to portable file HANDLE according
+   to the given OPTS.  Returns nonzero only if successful.  DICT
+   will not be modified, except to assign short names. */
+struct pfm_writer *
+pfm_open_writer (struct file_handle *fh, struct dictionary *dict,
+                 struct pfm_write_options opts)
+{
+  struct pfm_writer *w = NULL;
+  mode_t mode;
+  int fd;
+  size_t i;
+
+  /* Create file. */
+  mode = S_IRUSR | S_IRGRP | S_IROTH;
+  if (opts.create_writeable)
+    mode |= S_IWUSR | S_IWGRP | S_IWOTH;
+  fd = open (fh_get_filename (fh), O_WRONLY | O_CREAT | O_TRUNC, mode);
+  if (fd < 0) 
+    goto open_error;
+
+  /* Open file handle. */
+  if (!fh_open (fh, FH_REF_FILE, "portable file", "we"))
+    goto error;
+
+  /* Initialize data structures. */
+  w = xmalloc (sizeof *w);
+  w->fh = fh;
+  w->file = fdopen (fd, "w");
+  if (w->file == NULL) 
+    {
+      close (fd);
+      goto open_error;
+    }
+  
+  w->lc = 0;
+  w->var_cnt = 0;
+  w->vars = NULL;
+  
+  w->var_cnt = dict_get_var_cnt (dict);
+  w->vars = xnmalloc (w->var_cnt, sizeof *w->vars);
+  for (i = 0; i < w->var_cnt; i++) 
+    {
+      const struct variable *dv = dict_get_var (dict, i);
+      struct pfm_var *pv = &w->vars[i];
+      pv->width = dv->width;
+      pv->fv = dv->fv;
+    }
+
+  w->digits = opts.digits;
+  if (w->digits < 1) 
+    {
+      msg (ME, _("Invalid decimal digits count %d.  Treating as %d."),
+           w->digits, DBL_DIG);
+      w->digits = DBL_DIG;
+    }
+
+  /* Write file header. */
+  write_header (w);
+  write_version_data (w);
+  write_variables (w, dict);
+  write_value_labels (w, dict);
+  buf_write (w, "F", 1);
+  if (pfm_write_error (w))
+    goto error;
+  return w;
+
+ error:
+  pfm_close_writer (w);
+  return NULL;
+
+ open_error:
+  msg (ME, _("An error occurred while opening \"%s\" for writing "
+             "as a portable file: %s."),
+       fh_get_filename (fh), strerror (errno));
+  goto error;
+}
+\f  
+/* Write NBYTES starting at BUF to the portable file represented by
+   H.  Break lines properly every 80 characters.  */
+static void
+buf_write (struct pfm_writer *w, const void *buf_, size_t nbytes)
+{
+  const char *buf = buf_;
+
+  if (ferror (w->file))
+    return;
+
+  assert (buf != NULL);
+  while (nbytes + w->lc >= 80)
+    {
+      size_t n = 80 - w->lc;
+      
+      if (n)
+        fwrite (buf, n, 1, w->file);
+      fwrite ("\r\n", 2, 1, w->file);
+
+      nbytes -= n;
+      buf += n;
+      w->lc = 0;
+    }
+  fwrite (buf, nbytes, 1, w->file);
+
+  w->lc += nbytes;
+}
+
+/* Write D to the portable file as a floating-point field. */
+static void
+write_float (struct pfm_writer *w, double d)
+{
+  char buffer[64];
+  format_trig_double (d, floor (d) == d ? DBL_DIG : w->digits, buffer);
+  buf_write (w, buffer, strlen (buffer));
+  buf_write (w, "/", 1);
+}
+
+/* Write N to the portable file as an integer field. */
+static void
+write_int (struct pfm_writer *w, int n)
+{
+  char buffer[64];
+  format_trig_int (n, false, buffer);
+  buf_write (w, buffer, strlen (buffer));
+  buf_write (w, "/", 1);
+}
+
+/* Write S to the portable file as a string field. */
+static void
+write_string (struct pfm_writer *w, const char *s)
+{
+  size_t n = strlen (s);
+  write_int (w, (int) n);
+  buf_write (w, s, n);
+}
+\f
+/* Write file header. */
+static void
+write_header (struct pfm_writer *w)
+{
+  static const char spss2ascii[256] =
+    {
+      "0000000000000000000000000000000000000000000000000000000000000000"
+      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ."
+      "<(+|&[]!$*);^-/|,%_>?`:$@'=\"000000~-0000123456789000-()0{}\\00000"
+      "0000000000000000000000000000000000000000000000000000000000000000"
+    };
+  int i;
+
+  for (i = 0; i < 5; i++)
+    buf_write (w, "ASCII SPSS PORT FILE                    ", 40);
+  
+  buf_write (w, spss2ascii, 256);
+  buf_write (w, "SPSSPORT", 8);
+}
+
+/* Writes version, date, and identification records. */
+static void
+write_version_data (struct pfm_writer *w)
+{
+  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);
+  buf_write (w, "A", 1);
+  write_string (w, date_str);
+  write_string (w, time_str);
+
+  /* Product identification. */
+  buf_write (w, "1", 1);
+  write_string (w, version);
+
+  /* Subproduct identification. */
+  buf_write (w, "3", 1);
+  write_string (w, host_system);
+}
+
+/* Write format F to file H. */
+static void
+write_format (struct pfm_writer *w, struct fmt_spec *f)
+{
+  write_int (w, formats[f->type].spss);
+  write_int (w, f->w);
+  write_int (w, f->d);
+}
+
+/* Write value V for variable VV to file H. */
+static void
+write_value (struct pfm_writer *w, union value *v, struct variable *vv)
+{
+  if (vv->type == NUMERIC)
+    write_float (w, v->f);
+  else 
+    {
+      write_int (w, vv->width);
+      buf_write (w, v->s, vv->width); 
+    }
+}
+
+/* Write variable records. */
+static void
+write_variables (struct pfm_writer *w, struct dictionary *dict)
+{
+  int i;
+
+  dict_assign_short_names (dict);
+  
+  buf_write (w, "4", 1);
+  write_int (w, dict_get_var_cnt (dict));
+  write_int (w, 161);
+
+  for (i = 0; i < dict_get_var_cnt (dict); i++)
+    {
+      struct variable *v = dict_get_var (dict, i);
+      struct missing_values mv;
+      
+      buf_write (w, "7", 1);
+      write_int (w, v->width);
+      write_string (w, v->short_name);
+      write_format (w, &v->print);
+      write_format (w, &v->write);
+
+      /* Write missing values. */
+      mv_copy (&mv, &v->miss);
+      while (mv_has_range (&mv))
+        {
+          double x, y;
+          mv_pop_range (&mv, &x, &y);
+          if (x == LOWEST)
+            {
+              buf_write (w, "9", 1);
+              write_float (w, y);
+            }
+          else if (y == HIGHEST)
+            {
+              buf_write (w, "A", 1);
+              write_float (w, y);
+            }
+          else
+            {
+              buf_write (w, "B", 1);
+              write_float (w, x);
+              write_float (w, y);
+            }
+        }
+      while (mv_has_value (&mv)) 
+        {
+          union value value;
+          mv_pop_value (&mv, &value);
+          buf_write (w, "8", 1);
+          write_value (w, &value, v);
+        }
+
+      if (v->label)
+        { 
+          buf_write (w, "C", 1);
+          write_string (w, v->label);
+        }
+    }
+}
+
+/* Write value labels to disk.  FIXME: Inefficient. */
+static void
+write_value_labels (struct pfm_writer *w, const struct dictionary *dict)
+{
+  int i;
+
+  for (i = 0; i < dict_get_var_cnt (dict); i++)
+    {
+      struct val_labs_iterator *j;
+      struct variable *v = dict_get_var (dict, i);
+      struct val_lab *vl;
+
+      if (!val_labs_count (v->val_labs))
+       continue;
+
+      buf_write (w, "D", 1);
+      write_int (w, 1);
+      write_string (w, v->short_name);
+      write_int (w, val_labs_count (v->val_labs));
+
+      for (vl = val_labs_first_sorted (v->val_labs, &j); vl != NULL;
+           vl = val_labs_next (v->val_labs, &j)) 
+        {
+          write_value (w, &vl->value, v);
+          write_string (w, vl->label);
+        }
+    }
+}
+
+/* Writes case ELEM to the portable file represented by H. */
+int 
+pfm_write_case (struct pfm_writer *w, const struct ccase *c)
+{
+  int i;
+
+  if (ferror (w->file))
+    return 0;
+  
+  for (i = 0; i < w->var_cnt; i++)
+    {
+      struct pfm_var *v = &w->vars[i];
+      
+      if (v->width == 0)
+        write_float (w, case_num (c, v->fv));
+      else
+       {
+         write_int (w, v->width);
+          buf_write (w, case_str (c, v->fv), v->width);
+       }
+    }
+
+  return !pfm_write_error (w);
+}
+
+bool
+pfm_write_error (const struct pfm_writer *w) 
+{
+  return ferror (w->file);
+}
+
+/* Closes a portable file after we're done with it.
+   Returns true if successful, false if an I/O error occurred. */
+bool
+pfm_close_writer (struct pfm_writer *w)
+{
+  bool ok;
+
+  if (w == NULL)
+    return true;
+
+  ok = true;
+  if (w->file != NULL)
+    {
+      char buf[80];
+      memset (buf, 'Z', sizeof buf);
+      buf_write (w, buf, w->lc >= 80 ? 80 : 80 - w->lc);
+
+      ok = !pfm_write_error (w);
+      if (fclose (w->file) == EOF) 
+        ok = false; 
+
+      if (!ok) 
+        msg (ME, _("An I/O error occurred writing portable file \"%s\"."),
+             fh_get_filename (w->fh));
+    }
+
+  fh_close (w->fh, "portable file", "we");
+  
+  free (w->vars);
+  free (w);
+
+  return ok;
+}
+\f
+/* Base-30 conversion.
+
+   Portable files represent numbers in base-30 format, so we need
+   to be able to convert real and integer number to that base.
+   Older versions of PSPP used libgmp to do so, but this added a
+   big library dependency to do just one thing.  Now we do it
+   ourselves internally.
+
+   Important fact: base 30 is called "trigesimal". */
+
+/* Conversion base. */
+#define BASE 30                         /* As an integer. */
+#define LDBASE ((long double) BASE)     /* As a long double. */
+
+/* This is floor(log30(2**31)), the minimum number of trigesimal
+   digits that a `long int' can hold. */
+#define CHUNK_SIZE 6                    
+
+/* pow_tab[i] = pow (30, pow (2, i)) */
+static long double pow_tab[16];
+
+/* Initializes pow_tab[]. */
+static void
+init_pow_tab (void) 
+{
+  static bool did_init = false;
+  long double power;
+  size_t i;
+
+  /* Only initialize once. */
+  if (did_init)
+    return;
+  did_init = true;
+
+  /* Set each element of pow_tab[] until we run out of numerical
+     range. */
+  i = 0;
+  for (power = 30.0L; power < DBL_MAX; power *= power)
+    {
+      assert (i < sizeof pow_tab / sizeof *pow_tab);
+      pow_tab[i++] = power;
+    }
+}
+
+/* Returns 30**EXPONENT, for 0 <= EXPONENT <= log30(DBL_MAX). */
+static long double
+pow30_nonnegative (int exponent)
+{
+  long double power;
+  int i;
+
+  assert (exponent >= 0);
+  assert (exponent < 1L << (sizeof pow_tab / sizeof *pow_tab));
+
+  power = 1.L;
+  for (i = 0; exponent > 0; exponent >>= 1, i++)
+    if (exponent & 1)
+      power *= pow_tab[i];
+
+  return power;
+}
+
+/* Returns 30**EXPONENT, for log30(DBL_MIN) <= EXPONENT <=
+   log30(DBL_MAX). */
+static long double
+pow30 (int exponent)
+{
+  if (exponent >= 0)
+    return pow30_nonnegative (exponent);
+  else
+    return 1.L / pow30_nonnegative (-exponent);
+}
+
+/* Returns the character corresponding to TRIG. */
+static int
+trig_to_char (int trig)
+{
+  assert (trig >= 0 && trig < 30);
+  return "0123456789ABCDEFGHIJKLMNOPQRST"[trig];
+}
+
+/* Formats the TRIG_CNT trigs in TRIGS[], writing them as
+   null-terminated STRING.  The trigesimal point is inserted
+   after TRIG_PLACES characters have been printed, if necessary
+   adding extra zeros at either end for correctness.  Returns the
+   character after the formatted number. */
+static char *
+format_trig_digits (char *string,
+                    const char trigs[], int trig_cnt, int trig_places)
+{
+  if (trig_places < 0)
+    {
+      *string++ = '.';
+      while (trig_places++ < 0)
+        *string++ = '0';
+      trig_places = -1;
+    }
+  while (trig_cnt-- > 0)
+    {
+      if (trig_places-- == 0)
+        *string++ = '.';
+      *string++ = trig_to_char (*trigs++);
+    }
+  while (trig_places-- > 0)
+    *string++ = '0';
+  *string = '\0';
+  return string;
+}
+
+/* Helper function for format_trig_int() that formats VALUE as a
+   trigesimal integer at CP.  VALUE must be nonnegative.
+   Returns the character following the formatted integer. */
+static char *
+recurse_format_trig_int (char *cp, int value)
+{
+  int trig = value % BASE;
+  value /= BASE;
+  if (value > 0)
+    cp = recurse_format_trig_int (cp, value);
+  *cp++ = trig_to_char (trig);
+  return cp;
+}
+
+/* Formats VALUE as a trigesimal integer in null-terminated
+   STRING[].  VALUE must be in the range -DBL_MAX...DBL_MAX.  If
+   FORCE_SIGN is true, a sign is always inserted; otherwise, a
+   sign is only inserted if VALUE is negative. */
+static char *
+format_trig_int (int value, bool force_sign, char string[])
+{
+  /* Insert sign. */
+  if (value < 0)
+    {
+      *string++ = '-';
+      value = -value;
+    }
+  else if (force_sign)
+    *string++ = '+';
+
+  /* Format integer. */
+  string = recurse_format_trig_int (string, value);
+  *string = '\0';
+  return string;
+}
+
+/* Determines whether the TRIG_CNT trigesimals in TRIGS[] warrant
+   rounding up or down.  Returns true if TRIGS[] represents a
+   value greater than half, false if less than half.  If TRIGS[]
+   is exactly half, examines TRIGS[-1] and returns true if odd,
+   false if even ("round to even"). */
+static bool
+should_round_up (const char trigs[], int trig_cnt)
+{
+  assert (trig_cnt > 0);
+
+  if (*trigs < BASE / 2)
+    {
+      /* Less than half: round down. */
+      return false;
+    }
+  else if (*trigs > BASE / 2)
+    {
+      /* Greater than half: round up. */
+      return true;
+    }
+  else
+    {
+      /* Approximately half: look more closely. */
+      int i;
+      for (i = 1; i < trig_cnt; i++)
+        if (trigs[i] > 0)
+          {
+            /* Slightly greater than half: round up. */
+            return true;
+          }
+
+      /* Exactly half: round to even. */
+      return trigs[-1] % 2;
+    }
+}
+
+/* Rounds up the rightmost trig in the TRIG_CNT trigs in TRIGS[],
+   carrying to the left as necessary.  Returns true if
+   successful, false on failure (due to a carry out of the
+   leftmost position). */
+static bool
+try_round_up (char *trigs, int trig_cnt)
+{
+  while (trig_cnt > 0)
+    {
+      char *round_trig = trigs + --trig_cnt;
+      if (*round_trig != BASE - 1)
+        {
+          /* Round this trig up to the next value. */
+          ++*round_trig;
+          return true;
+        }
+
+      /* Carry over to the next trig to the left. */
+      *round_trig = 0;
+    }
+
+  /* Ran out of trigs to carry. */
+  return false;
+}
+
+/* Converts VALUE to trigesimal format in string OUTPUT[] with the
+   equivalent of at least BASE_10_PRECISION decimal digits of
+   precision.  The output format may use conventional or
+   scientific notation.  Missing, infinite, and extreme values
+   are represented with "*.". */
+static void
+format_trig_double (long double value, int base_10_precision, char output[])
+{
+  /* Original VALUE was negative? */
+  bool negative;
+
+  /* Number of significant trigesimals. */
+  int base_30_precision;
+
+  /* Base-2 significand and exponent for original VALUE. */
+  double base_2_sig;
+  int base_2_exp;
+
+  /* VALUE as a set of trigesimals. */
+  char buffer[DBL_DIG + 16];
+  char *trigs;
+  int trig_cnt;
+
+  /* Number of trigesimal places for trigs.
+     trigs[0] has coefficient 30**(trig_places - 1),
+     trigs[1] has coefficient 30**(trig_places - 2),
+     and so on.
+     In other words, the trigesimal point is just before trigs[0].
+   */
+  int trig_places;
+
+  /* Number of trigesimal places left to write into BUFFER. */
+  int trigs_to_output;
+
+  init_pow_tab ();
+
+  /* Handle special cases. */
+  if (value == SYSMIS)
+    goto missing_value;
+  if (value == 0.)
+    goto zero;
+
+  /* Make VALUE positive. */
+  if (value < 0)
+    {
+      value = -value;
+      negative = true;
+    }
+  else
+    negative = false;
+
+  /* Adjust VALUE to roughly 30**3, by shifting the trigesimal
+     point left or right as necessary.  We approximate the
+     base-30 exponent by obtaining the base-2 exponent, then
+     multiplying by log30(2).  This approximation is sufficient
+     to ensure that the adjusted VALUE is always in the range
+     0...30**6, an invariant of the loop below. */
+  errno = 0;
+  base_2_sig = frexp (value, &base_2_exp);
+  if (errno != 0 || !finite (base_2_sig))
+    goto missing_value;
+  if (base_2_exp == 0 && base_2_sig == 0.)
+    goto zero;
+  if (base_2_exp <= INT_MIN / 20379L || base_2_exp >= INT_MAX / 20379L)
+    goto missing_value;
+  trig_places = (base_2_exp * 20379L / 100000L) + CHUNK_SIZE / 2;
+  value *= pow30 (CHUNK_SIZE - trig_places);
+
+  /* Dump all the trigs to buffer[], CHUNK_SIZE at a time. */
+  trigs = buffer;
+  trig_cnt = 0;
+  for (trigs_to_output = DIV_RND_UP (DBL_DIG * 2, 3) + 1 + (CHUNK_SIZE / 2);
+       trigs_to_output > 0;
+       trigs_to_output -= CHUNK_SIZE)
+    {
+      long chunk;
+      int trigs_left;
+
+      /* The current chunk is just the integer part of VALUE,
+         truncated to the nearest integer.  The chunk fits in a
+         long. */
+      chunk = value;
+      assert (pow30 (CHUNK_SIZE) <= LONG_MAX);
+      assert (chunk >= 0 && chunk < pow30 (CHUNK_SIZE));
+
+      value -= chunk;
+
+      /* Append the chunk, in base 30, to trigs[]. */
+      for (trigs_left = CHUNK_SIZE; chunk > 0 && trigs_left > 0; )
+        {
+          trigs[trig_cnt + --trigs_left] = chunk % 30;
+          chunk /= 30;
+        }
+      while (trigs_left > 0)
+        trigs[trig_cnt + --trigs_left] = 0;
+      trig_cnt += CHUNK_SIZE;
+
+      /* Proceed to the next chunk. */
+      if (value == 0.)
+        break;
+      value *= pow (LDBASE, CHUNK_SIZE);
+    }
+
+  /* Strip leading zeros. */
+  while (trig_cnt > 1 && *trigs == 0)
+    {
+      trigs++;
+      trig_cnt--;
+      trig_places--;
+    }
+
+  /* Round to requested precision, conservatively estimating the
+     required base-30 precision as 2/3 of the base-10 precision
+     (log30(10) = .68). */
+  assert (base_10_precision > 0);
+  if (base_10_precision > LDBL_DIG)
+    base_10_precision = LDBL_DIG;
+  base_30_precision = DIV_RND_UP (base_10_precision * 2, 3);
+  if (trig_cnt > base_30_precision)
+    {
+      if (should_round_up (trigs + base_30_precision,
+                           trig_cnt - base_30_precision))
+        {
+          /* Try to round up. */
+          if (try_round_up (trigs, base_30_precision))
+            {
+              /* Rounding up worked. */
+              trig_cnt = base_30_precision;
+            }
+          else
+            {
+              /* Couldn't round up because we ran out of trigs to
+                 carry into.  Do the carry here instead. */
+              *trigs = 1;
+              trig_cnt = 1;
+              trig_places++;
+            }
+        }
+      else
+        {
+          /* Round down. */
+          trig_cnt = base_30_precision;
+        }
+    }
+  else
+    {
+      /* No rounding required: fewer digits available than
+         requested. */
+    }
+
+  /* Strip trailing zeros. */
+  while (trig_cnt > 1 && trigs[trig_cnt - 1] == 0)
+    trig_cnt--;
+
+  /* Write output. */
+  if (negative)
+    *output++ = '-';
+  if (trig_places >= -1 && trig_places < trig_cnt + 3)
+    {
+      /* Use conventional notation. */
+      format_trig_digits (output, trigs, trig_cnt, trig_places);
+    }
+  else
+    {
+      /* Use scientific notation. */
+      char *op;
+      op = format_trig_digits (output, trigs, trig_cnt, trig_cnt);
+      op = format_trig_int (trig_places - trig_cnt, true, op);
+    }
+  return;
+
+ zero:
+  strcpy (output, "0");
+  return;
+
+ missing_value:
+  strcpy (output, "*.");
+  return;
+}
diff --git a/src/data/por-file-writer.h b/src/data/por-file-writer.h
new file mode 100644 (file)
index 0000000..cf9066e
--- /dev/null
@@ -0,0 +1,53 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef PFM_WRITE_H
+#define PFM_WRITE_H
+
+#include <stdbool.h>
+
+/* Portable file writing. */
+
+/* Portable file types. */
+enum pfm_type
+  {
+    PFM_COMM,   /* Formatted for communication. */
+    PFM_TAPE    /* Formatted for tape. */
+  };
+
+/* Portable file writing options. */
+struct pfm_write_options 
+  {
+    bool create_writeable;      /* File perms: writeable or read/only? */
+    enum pfm_type type;         /* Type of portable file (TODO). */
+    int digits;                 /* Digits of precision. */
+  };
+
+struct file_handle;
+struct dictionary;
+struct ccase;
+struct pfm_writer *pfm_open_writer (struct file_handle *, struct dictionary *,
+                                    struct pfm_write_options);
+struct pfm_write_options pfm_writer_default_options (void);
+
+int pfm_write_case (struct pfm_writer *, const struct ccase *);
+bool pfm_write_error (const struct pfm_writer *);
+bool pfm_close_writer (struct pfm_writer *);
+
+#endif /* por-file-writer.h */
diff --git a/src/data/scratch-handle.c b/src/data/scratch-handle.c
new file mode 100644 (file)
index 0000000..5e3b74a
--- /dev/null
@@ -0,0 +1,36 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include "scratch-handle.h"
+#include "casefile.h"
+#include "dictionary.h"
+
+/* Destroys HANDLE. */
+void
+scratch_handle_destroy (struct scratch_handle *handle) 
+{
+  if (handle != NULL) 
+    {
+      dict_destroy (handle->dictionary);
+      casefile_destroy (handle->casefile);
+      free (handle);
+    }
+}
diff --git a/src/data/scratch-handle.h b/src/data/scratch-handle.h
new file mode 100644 (file)
index 0000000..34739cf
--- /dev/null
@@ -0,0 +1,34 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef SCRATCH_HANDLE_H
+#define SCRATCH_HANDLE_H 1
+
+#include <stdbool.h>
+
+/* A scratch file. */
+struct scratch_handle 
+  {
+    struct dictionary *dictionary;      /* Dictionary. */
+    struct casefile *casefile;          /* Cases. */
+  };
+
+void scratch_handle_destroy (struct scratch_handle *);
+
+#endif /* scratch-handle.h */
diff --git a/src/data/scratch-reader.c b/src/data/scratch-reader.c
new file mode 100644 (file)
index 0000000..2067897
--- /dev/null
@@ -0,0 +1,95 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "scratch-reader.h"
+#include <stdlib.h>
+#include "casefile.h"
+#include "dictionary.h"
+#include "message.h"
+#include "file-handle-def.h"
+#include "scratch-handle.h"
+#include "xalloc.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* A reader for a scratch file. */
+struct scratch_reader 
+  {
+    struct file_handle *fh;             /* Underlying file handle. */
+    struct casereader *casereader;      /* Case reader. */
+  };
+
+/* Opens FH, which must have referent type FH_REF_SCRATCH, and
+   returns a scratch_reader for it, or a null pointer on
+   failure.  Stores the dictionary for the scratch file into
+   *DICT.
+
+   If you use an any_reader instead, then your code can be more
+   flexible without being any harder to write. */
+struct scratch_reader *
+scratch_reader_open (struct file_handle *fh, struct dictionary **dict)
+{
+  struct scratch_handle *sh;
+  struct scratch_reader *reader;
+  
+  if (!fh_open (fh, FH_REF_SCRATCH, "scratch file", "rs"))
+    return NULL;
+  
+  sh = fh_get_scratch_handle (fh);
+  if (sh == NULL) 
+    {
+      msg (SE, _("Scratch file handle %s has not yet been written, "
+                 "using SAVE or another procedure, so it cannot yet "
+                 "be used for reading."),
+           fh_get_name (fh));
+      return NULL;
+    }
+
+  *dict = dict_clone (sh->dictionary);
+  reader = xmalloc (sizeof *reader);
+  reader->fh = fh;
+  reader->casereader = casefile_get_reader (sh->casefile);
+  return reader;
+}
+
+/* Reads a case from READER into C.
+   Returns true if successful, false on error or at end of file. */
+bool
+scratch_reader_read_case (struct scratch_reader *reader, struct ccase *c)
+{
+  return casereader_read (reader->casereader, c);
+}
+
+/* Returns true if an I/O error occurred on READER, false otherwise. */
+bool
+scratch_reader_error (const struct scratch_reader *reader) 
+{
+  return casefile_error (casereader_get_casefile (reader->casereader));
+}
+
+/* Closes READER. */
+void
+scratch_reader_close (struct scratch_reader *reader) 
+{
+  fh_close (reader->fh, "scratch file", "rs");
+  casereader_destroy (reader->casereader);
+  free (reader);
+}
diff --git a/src/data/scratch-reader.h b/src/data/scratch-reader.h
new file mode 100644 (file)
index 0000000..52fc2d0
--- /dev/null
@@ -0,0 +1,34 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef SCRATCH_READER_H
+#define SCRATCH_READER_H 1
+
+#include <stdbool.h>
+
+struct dictionary;
+struct file_handle;
+struct ccase;
+struct scratch_reader *scratch_reader_open (struct file_handle *,
+                                            struct dictionary **);
+bool scratch_reader_read_case (struct scratch_reader *, struct ccase *);
+bool scratch_reader_error (const struct scratch_reader *);
+void scratch_reader_close (struct scratch_reader *);
+
+#endif /* scratch-reader.h */
diff --git a/src/data/scratch-writer.c b/src/data/scratch-writer.c
new file mode 100644 (file)
index 0000000..8bedef6
--- /dev/null
@@ -0,0 +1,122 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "scratch-writer.h"
+#include <stdlib.h>
+#include "case.h"
+#include "casefile.h"
+#include "dictionary.h"
+#include "file-handle-def.h"
+#include "scratch-handle.h"
+#include "xalloc.h"
+
+/* A scratch file writer. */
+struct scratch_writer 
+  {
+    struct scratch_handle *handle;      /* Underlying scratch handle. */
+    struct file_handle *fh;             /* Underlying file handle. */
+    struct dict_compactor *compactor;   /* Compacts into handle->dictionary. */
+  };
+
+/* Opens FH, which must have referent type FH_REF_SCRATCH, and
+   returns a scratch_writer for it, or a null pointer on
+   failure.  Cases stored in the scratch_writer will be expected
+   to be drawn from DICTIONARY.
+
+   If you use an any_writer instead, then your code can be more
+   flexible without being any harder to write. */
+struct scratch_writer *
+scratch_writer_open (struct file_handle *fh,
+                     const struct dictionary *dictionary) 
+{
+  struct scratch_handle *sh;
+  struct scratch_writer *writer;
+  struct dictionary *scratch_dict;
+  struct dict_compactor *compactor;
+
+  if (!fh_open (fh, FH_REF_SCRATCH, "scratch file", "we"))
+    return NULL;
+
+  /* Destroy previous contents of handle. */
+  sh = fh_get_scratch_handle (fh);
+  if (sh != NULL) 
+    scratch_handle_destroy (sh);
+
+  /* Copy the dictionary and compact if needed. */
+  scratch_dict = dict_clone (dictionary);
+  if (dict_needs_compaction (scratch_dict)) 
+    {
+      compactor = dict_make_compactor (scratch_dict);
+      dict_compact_values (scratch_dict);
+    }
+  else
+    compactor = NULL;
+
+  /* Create new contents. */
+  sh = xmalloc (sizeof *sh);
+  sh->dictionary = scratch_dict;
+  sh->casefile = casefile_create (dict_get_next_value_idx (sh->dictionary));
+
+  /* Create writer. */
+  writer = xmalloc (sizeof *writer);
+  writer->handle = sh;
+  writer->fh = fh;
+  writer->compactor = compactor;
+
+  fh_set_scratch_handle (fh, sh);
+  return writer;
+}
+
+/* Writes case C to WRITER. */
+bool
+scratch_writer_write_case (struct scratch_writer *writer,
+                           const struct ccase *c) 
+{
+  struct scratch_handle *handle = writer->handle;
+  if (writer->compactor) 
+    {
+      struct ccase tmp_case;
+      case_create (&tmp_case, dict_get_next_value_idx (handle->dictionary));
+      dict_compactor_compact (writer->compactor, &tmp_case, c);
+      return casefile_append_xfer (handle->casefile, &tmp_case);
+    }
+  else 
+    return casefile_append (handle->casefile, c);
+}
+
+/* Returns true if an I/O error occurred on WRITER, false otherwise. */
+bool
+scratch_writer_error (const struct scratch_writer *writer) 
+{
+  return casefile_error (writer->handle->casefile);
+}
+
+/* Closes WRITER.
+   Returns true if successful, false if an I/O error occurred on WRITER. */
+bool
+scratch_writer_close (struct scratch_writer *writer) 
+{
+  struct casefile *cf = writer->handle->casefile;
+  bool ok = casefile_error (cf);
+  casefile_mode_reader (cf);
+  fh_close (writer->fh, "scratch file", "we");
+  free (writer);
+  return ok;
+}
diff --git a/src/data/scratch-writer.h b/src/data/scratch-writer.h
new file mode 100644 (file)
index 0000000..3b4e295
--- /dev/null
@@ -0,0 +1,34 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef SCRATCH_WRITER_H
+#define SCRATCH_WRITER_H 1
+
+#include <stdbool.h>
+
+struct dictionary;
+struct file_handle;
+struct ccase;
+struct scratch_writer *scratch_writer_open (struct file_handle *,
+                                            const struct dictionary *);
+bool scratch_writer_write_case (struct scratch_writer *, const struct ccase *);
+bool scratch_writer_error (const struct scratch_writer *);
+bool scratch_writer_close (struct scratch_writer *);
+
+#endif /* scratch-writer.h */
diff --git a/src/data/settings.c b/src/data/settings.c
new file mode 100644 (file)
index 0000000..81b6789
--- /dev/null
@@ -0,0 +1,538 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "settings.h"
+#include <assert.h>
+#include <stdlib.h>
+#include <time.h>
+#include "format.h"
+#include "value.h"
+#include "xalloc.h"
+
+static int viewlength = 24;
+static int viewwidth = 79;
+static bool long_view = false;
+
+static bool safer_mode = false;
+
+static char decimal = '.';
+static char grouping = ',';
+
+static bool echo = false;
+static bool include = true;
+
+static int epoch = -1;
+
+static bool errorbreak = false;
+
+static bool scompress = false;
+
+static bool undefined = true;
+static double blanks = SYSMIS;
+
+static int mxwarns = 100;
+static int mxerrs = 100;
+
+static bool printback = true;
+static bool mprint = true;
+
+static int mxloops = 1;
+
+static bool nulline = true;
+
+static char endcmd = '.';
+
+static size_t workspace = 4L * 1024 * 1024;
+
+static struct fmt_spec default_format = {FMT_F, 8, 2};
+
+#define CC_INITIALIZER {"-", "", "", "", '.', ','}
+static struct custom_currency cc[CC_CNT] = 
+  {
+    CC_INITIALIZER,
+    CC_INITIALIZER,
+    CC_INITIALIZER,
+    CC_INITIALIZER,
+    CC_INITIALIZER,
+  };
+
+static bool testing_mode = false;
+
+static int global_algorithm = ENHANCED;
+static int cmd_algorithm = ENHANCED;
+static int *algorithm = &global_algorithm;
+
+static int syntax = ENHANCED;
+
+static void init_viewport (void);
+
+void
+settings_init (void)
+{
+  init_viewport ();
+}
+
+void
+settings_done (void)
+{
+}
+
+/* Screen length in lines. */
+int
+get_viewlength (void)
+{
+  return viewlength;
+}
+
+/* Sets the view length. */
+void
+set_viewlength (int viewlength_) 
+{
+  viewlength = viewlength_;
+}
+
+/* Set view width to a very long value, and prevent it from ever
+   changing. */
+void
+force_long_view (void)
+{
+  long_view = true;
+  viewwidth = 9999;
+}
+
+/* Screen width. */
+int
+get_viewwidth(void)
+{
+  return viewwidth;
+}
+
+/* Sets the screen width. */
+void
+set_viewwidth (int viewwidth_) 
+{
+  viewwidth = viewwidth_;
+}
+
+#if HAVE_LIBTERMCAP
+static void
+get_termcap_viewport (void)
+{
+  char term_buffer[16384];
+  if (getenv ("TERM") == NULL)
+    return;
+  else if (tgetent (term_buffer, getenv ("TERM")) <= 0)
+    {
+      msg (IE, _("Could not access definition for terminal `%s'."), termtype);
+      return;
+    }
+
+  if (tgetnum ("li") > 0)
+    viewlength = tgetnum ("li");
+
+  if (tgetnum ("co") > 1)
+    viewwidth = tgetnum ("co") - 1;
+}
+#endif /* HAVE_LIBTERMCAP */
+
+static void 
+init_viewport (void)
+{
+  if (long_view)
+    return;
+  
+  viewwidth = viewlength = -1;
+
+#if HAVE_LIBTERMCAP
+  get_termcap_viewport ();
+#endif /* HAVE_LIBTERMCAP */
+
+  if (viewwidth < 0 && getenv ("COLUMNS") != NULL)
+    viewwidth = atoi (getenv ("COLUMNS"));
+  if (viewlength < 0 && getenv ("LINES") != NULL)
+    viewlength = atoi (getenv ("LINES"));
+
+  if (viewwidth < 0)
+    viewwidth = 79;
+  if (viewlength < 0)
+    viewlength = 24;
+}
+
+/* Whether PSPP can erase and overwrite files. */
+bool
+get_safer_mode (void)
+{
+  return safer_mode;
+}
+
+/* Set safer mode. */
+void
+set_safer_mode (void)
+{
+  safer_mode = true;
+}
+
+/* The character used for a decimal point: ',' or '.'.  Only
+   respected for data input and output. */
+char 
+get_decimal (void)
+{
+  return decimal;
+}
+
+/* Sets the character used for a decimal point, which must be
+   either ',' or '.'. */
+void
+set_decimal (char decimal_) 
+{
+  assert (decimal_ == '.' || decimal_ == ',');
+  decimal = decimal_;
+}
+
+/* The character used for grouping in numbers: '.' or ','; the
+   opposite of set_decimal.  Only used in COMMA data input and
+   output. */
+char
+get_grouping (void)
+{
+  return grouping;
+}
+
+/* Sets the character used for grouping, which must be either ','
+   or '.'. */
+void
+set_grouping (char grouping_) 
+{
+  assert (grouping_ == '.' || grouping_ == ',');
+  grouping = grouping_;
+}
+/* Echo commands to the listing file/printer? */
+bool
+get_echo (void)
+{
+  return echo;
+}
+
+/* Set echo. */
+void
+set_echo (bool echo_) 
+{
+  echo = echo_;
+}
+
+/* If echo is on, whether commands from include files are echoed. */
+bool
+get_include (void)
+{
+  return include;
+}
+
+/* Set include file echo. */
+void
+set_include (bool include_) 
+{
+  include = include_;
+}
+
+/* What year to use as the start of the epoch. */
+int
+get_epoch (void) 
+{
+  if (epoch < 0) 
+    {
+      time_t t = time (0);
+      struct tm *tm = localtime (&t);
+      epoch = (tm != NULL ? tm->tm_year + 1900 : 2000) - 69;
+    }
+
+  return epoch;
+}
+
+/* Sets the year that starts the epoch. */
+void
+set_epoch (int epoch_) 
+{
+  epoch = epoch_;
+}
+
+/* Does an error stop execution? */
+bool
+get_errorbreak (void)
+{
+  return errorbreak;
+}
+
+/* Sets whether an error stops execution. */
+void
+set_errorbreak (bool errorbreak_) 
+{
+  errorbreak = errorbreak_;
+}
+
+/* Compress system files by default? */
+bool 
+get_scompression (void)
+{
+  return scompress;
+}
+
+/* Set system file default compression. */
+void
+set_scompression (bool scompress_) 
+{
+  scompress = scompress_;
+}
+
+/* Whether to warn on undefined values in numeric data. */
+bool
+get_undefined (void)
+{
+  return undefined;
+}
+
+/* Set whether to warn on undefined values. */
+void
+set_undefined (bool undefined_) 
+{
+  undefined = undefined_;
+}
+
+/* The value that blank numeric fields are set to when read in. */
+double
+get_blanks (void)
+{
+  return blanks;
+}
+
+/* Set the value that blank numeric fields are set to when read
+   in. */
+void
+set_blanks (double blanks_) 
+{
+  blanks = blanks_;
+}
+
+/* Maximum number of warnings + errors. */
+int
+get_mxwarns (void)
+{  
+  return mxwarns;
+}
+
+/* Sets maximum number of warnings + errors. */
+void
+set_mxwarns (int mxwarns_) 
+{
+  mxwarns = mxwarns_;
+}
+
+/* Maximum number of errors. */
+int
+get_mxerrs (void)
+{
+  return mxerrs;
+}
+
+/* Sets maximum number of errors. */
+void
+set_mxerrs (int mxerrs_) 
+{
+  mxerrs = mxerrs_;
+}
+
+/* Whether commands are written to the display. */
+bool
+get_printback (void)
+{
+  return printback;
+}
+
+/* Sets whether commands are written to the display. */
+void
+set_printback (bool printback_) 
+{
+  printback = printback_;
+}
+
+/* Independent of get_printback, controls whether the commands
+   generated by macro invocations are displayed. */
+bool
+get_mprint (void)
+{
+  return mprint;
+}
+
+/* Sets whether the commands generated by macro invocations are
+   displayed. */
+void
+set_mprint (bool mprint_) 
+{
+  mprint = mprint_;
+}
+
+/* Implied limit of unbounded loop. */
+int
+get_mxloops (void)
+{
+  return mxloops;
+}
+
+/* Set implied limit of unbounded loop. */
+void
+set_mxloops (int mxloops_) 
+{
+  mxloops = mxloops_;
+}
+
+/* Whether a blank line is a command terminator. */
+bool
+get_nulline (void)
+{
+  return nulline;
+}
+
+/* Set whether a blank line is a command terminator. */
+void
+set_nulline (bool nulline_)
+{
+  nulline = nulline_;
+}
+
+/* The character used to terminate commands. */
+char
+get_endcmd (void)
+{
+  return endcmd;
+}
+
+/* Set the character used to terminate commands. */
+void
+set_endcmd (char endcmd_) 
+{
+  endcmd = endcmd_;
+}
+
+/* Approximate maximum amount of memory to use for cases, in
+   bytes. */
+size_t
+get_workspace (void)
+{
+  return workspace;
+}
+
+/* Set approximate maximum amount of memory to use for cases, in
+   bytes. */
+
+void
+set_workspace (size_t workspace_) 
+{
+  workspace = workspace_;
+}
+
+/* Default format for variables created by transformations and by
+   DATA LIST {FREE,LIST}. */
+const struct fmt_spec *
+get_format (void)
+{ 
+  return &default_format;
+}
+
+/* Set default format for variables created by transformations
+   and by DATA LIST {FREE,LIST}. */
+void
+set_format (const struct fmt_spec *default_format_) 
+{
+  default_format = *default_format_;
+}
+
+/* Gets the custom currency specification with the given IDX. */
+const struct custom_currency *
+get_cc (int idx)
+{
+  assert (idx >= 0 && idx < CC_CNT);
+  return &cc[idx];
+}
+
+/* Gets custom currency specification IDX to CC. */
+void
+set_cc (int idx, const struct custom_currency *cc_) 
+{
+  assert (idx >= 0 && idx < CC_CNT);
+  cc[idx] = *cc_;
+}
+
+/* Are we in testing mode?  (e.g. --testing-mode command line
+   option) */
+bool
+get_testing_mode (void) 
+{
+  return testing_mode;
+}
+
+/* Set testing mode. */
+void
+set_testing_mode (bool testing_mode_) 
+{
+  testing_mode = testing_mode_;
+}
+
+/* Return the current algorithm setting */
+enum behavior_mode
+get_algorithm (void)
+{
+  return *algorithm;
+}
+
+/* Set the algorithm option globally. */
+void 
+set_algorithm (enum behavior_mode mode)
+{
+  global_algorithm = mode;
+}
+
+/* Set the algorithm option for this command only */
+void 
+set_cmd_algorithm (enum behavior_mode mode)
+{
+  cmd_algorithm = mode; 
+  algorithm = &cmd_algorithm;
+}
+
+/* Unset the algorithm option for this command */
+void
+unset_cmd_algorithm (void)
+{
+  algorithm = &global_algorithm;
+}
+
+/* Get the current syntax setting */
+enum behavior_mode
+get_syntax (void)
+{
+  return syntax;
+}
+
+/* Set the syntax option */
+void 
+set_syntax (enum behavior_mode mode)
+{
+  syntax = mode;
+}
diff --git a/src/data/settings.h b/src/data/settings.h
new file mode 100644 (file)
index 0000000..4bdc65d
--- /dev/null
@@ -0,0 +1,130 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !settings_h
+#define settings_h 1
+
+#include <stdbool.h>
+#include <stddef.h>
+
+/* 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. */
+  };
+
+void settings_init (void);
+void settings_done (void);
+
+void force_long_view (void);
+int get_viewlength (void);
+void set_viewlength (int);
+
+int get_viewwidth (void);
+void set_viewwidth (int);
+
+bool get_safer_mode (void);
+void set_safer_mode (void);
+
+char get_decimal (void);
+void set_decimal (char);
+char get_grouping (void);
+void set_grouping (char);
+
+bool get_echo (void);
+void set_echo (bool);
+bool get_include (void);
+void set_include (bool);
+
+int get_epoch (void);
+void set_epoch (int);
+
+bool get_errorbreak (void);
+void set_errorbreak (bool);
+
+bool get_scompression (void);
+void set_scompression (bool);
+
+bool get_undefined (void);
+void set_undefined (bool);
+double get_blanks (void);
+void set_blanks (double);
+
+int get_mxwarns (void);
+void set_mxwarns (int);
+int get_mxerrs (void);
+void set_mxerrs (int);
+
+bool get_printback (void);
+void set_printback (bool);
+bool get_mprint (void);
+void set_mprint (bool);
+
+int get_mxloops (void);
+void set_mxloops (int);
+
+bool get_nulline (void);
+void set_nulline (bool);
+
+char get_endcmd (void);
+void set_endcmd (char);
+
+size_t get_workspace (void);
+void set_workspace (size_t);
+
+const struct fmt_spec *get_format (void);
+void set_format (const struct fmt_spec *);
+
+/* Maximum number of custom currency specifications */
+#define CC_CNT 5
+
+/* One custom currency specification. */
+#define CC_WIDTH 16
+struct custom_currency
+  {
+    char neg_prefix[CC_WIDTH]; /* Negative prefix. */
+    char prefix[CC_WIDTH];     /* Prefix. */
+    char suffix[CC_WIDTH];     /* Suffix. */
+    char neg_suffix[CC_WIDTH]; /* Negative suffix. */
+    char decimal;              /* Decimal point. */
+    char grouping;             /* Grouping character. */
+  };
+
+const struct custom_currency *get_cc (int idx);
+void set_cc (int idx, const struct custom_currency *);
+
+bool get_testing_mode (void);
+void set_testing_mode (bool);
+
+enum behavior_mode {
+  ENHANCED,             /* Use improved PSPP behavior. */
+  COMPATIBLE            /* Be as compatible as possible. */
+};
+
+enum behavior_mode get_algorithm (void);
+void set_algorithm (enum behavior_mode);
+enum behavior_mode get_syntax (void);
+void set_syntax(enum behavior_mode);
+void set_cmd_algorithm (enum behavior_mode);
+void unset_cmd_algorithm (void);
+
+#endif /* !settings_h */
diff --git a/src/data/sfm-private.h b/src/data/sfm-private.h
new file mode 100644 (file)
index 0000000..c127b85
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, 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
+
+/* Find 32-bit signed integer type. */
+#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
+
+/* Find 64-bit floating-point type. */
+#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
+
+/* Figure out SYSMIS value for flt64. */
+#include "magic.h"
+#if SIZEOF_DOUBLE == 8
+#define second_lowest_flt64 second_lowest_value
+#else
+#error Must define second_lowest_flt64 for your architecture.
+#endif
+
+/* Record Type 1: General Information. */
+struct sysfile_header
+  {
+    char rec_type[4] P;                /* 00: Record-type code, "$FL2". */
+    char prod_name[60] P;      /* 04: Product identification. */
+    int32 layout_code P;       /* 40: 2. */
+    int32 case_size P;         /* 44: Number of `value's per case. 
+                                  Note: some systems set this to -1 */
+    int32 compress P;          /* 48: 1=compressed, 0=not compressed. */
+    int32 weight_idx P;         /* 4c: 1-based index of weighting var, or 0. */
+    int32 case_cnt P;          /* 50: Number of cases, -1 if unknown. */
+    flt64 bias P;              /* 54: Compression bias (100.0). */
+    char creation_date[9] P;   /* 5c: `dd mmm yy' creation date of file. */
+    char creation_time[8] P;   /* 65: `hh:mm:ss' 24-hour creation time. */
+    char file_label[64] P;     /* 6d: File label. */
+    char padding[3] P;         /* ad: 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[SHORT_NAME_LEN] P; /* Variable name. */
+    /* The rest of the structure varies. */
+  };
+
+#if __BORLANDC__
+#pragma -a4
+#endif
diff --git a/src/data/sys-file-reader.c b/src/data/sys-file-reader.c
new file mode 100644 (file)
index 0000000..c8c7034
--- /dev/null
@@ -0,0 +1,1555 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "sys-file-reader.h"
+#include "sfm-private.h"
+#include "message.h"
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <setjmp.h>
+#include "alloc.h"
+#include "case.h"
+#include "dictionary.h"
+#include "message.h"
+#include "file-handle-def.h"
+#include "filename.h"
+#include "format.h"
+#include "magic.h"
+#include "misc.h"
+#include "value-labels.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+/* System file reader. */
+struct sfm_reader
+  {
+    struct file_handle *fh;     /* File handle. */
+    FILE *file;                        /* File stream. */
+
+    int reverse_endian;                /* 1=file has endianness opposite us. */
+    int fix_specials;           /* 1=SYSMIS/HIGHEST/LOWEST differs from us. */
+    int value_cnt;             /* Number of `union values's per case. */
+    long case_cnt;             /* Number of cases, -1 if unknown. */
+    int compressed;            /* 1=compressed, 0=not compressed. */
+    double bias;               /* Compression bias, usually 100.0. */
+    int weight_idx;            /* 0-based index of weighting variable, or -1. */
+    bool ok;                    /* False after an I/O error or corrupt data. */
+
+    /* Variables. */
+    struct sfm_var *vars;       /* Variables. */
+
+    /* File's special constants. */
+    flt64 sysmis;
+    flt64 highest;
+    flt64 lowest;
+
+    /* Decompression buffer. */
+    flt64 *buf;                        /* Buffer data. */
+    flt64 *ptr;                        /* Current location in buffer. */
+    flt64 *end;                        /* End of buffer data. */
+
+    /* Compression instruction octet. */
+    unsigned char x[8];         /* Current instruction octet. */
+    unsigned char *y;          /* Location in current instruction octet. */
+  };
+
+/* A variable in a system file. */
+struct sfm_var 
+  {
+    int width;                  /* 0=numeric, otherwise string width. */
+    int fv;                     /* Index into case. */
+  };
+\f
+/* Utilities. */
+
+/* Swap bytes *A and *B. */
+static inline void
+bswap (char *a, char *b) 
+{
+  char t = *a;
+  *a = *b;
+  *b = t;
+}
+
+/* Reverse the byte order of 32-bit integer *X. */
+static inline void
+bswap_int32 (int32 *x_)
+{
+  char *x = (char *) x_;
+  bswap (x + 0, x + 3);
+  bswap (x + 1, x + 2);
+}
+
+/* Reverse the byte order of 64-bit floating point *X. */
+static inline void
+bswap_flt64 (flt64 *x_)
+{
+  char *x = (char *) x_;
+  bswap (x + 0, x + 7);
+  bswap (x + 1, x + 6);
+  bswap (x + 2, x + 5);
+  bswap (x + 3, x + 4);
+}
+
+static void
+corrupt_msg (int class, const char *format,...)
+     PRINTF_FORMAT (2, 3);
+
+/* Displays a corrupt sysfile error. */
+static void
+corrupt_msg (int class, const char *format,...)
+{
+  struct error e;
+  va_list args;
+
+  e.class = class;
+  e.where.filename = NULL;
+  e.where.line_number = 0;
+  e.title = _("corrupt system file: ");
+
+  va_start (args, format);
+  err_vmsg (&e, format, args);
+  va_end (args);
+}
+
+/* Closes a system file after we're done with it. */
+void
+sfm_close_reader (struct sfm_reader *r)
+{
+  if (r == NULL)
+    return;
+
+  if (r->file)
+    {
+      if (fn_close (fh_get_filename (r->fh), r->file) == EOF)
+        msg (ME, _("%s: Closing system file: %s."),
+             fh_get_filename (r->fh), strerror (errno));
+      r->file = NULL;
+    }
+
+  if (r->fh != NULL)
+    fh_close (r->fh, "system file", "rs");
+  
+  free (r->vars);
+  free (r->buf);
+  free (r);
+}
+\f
+/* Dictionary reader. */
+
+static void buf_unread(struct sfm_reader *r, size_t byte_cnt);
+
+static void *buf_read (struct sfm_reader *, void *buf, size_t byte_cnt,
+                       size_t min_alloc);
+
+static int read_header (struct sfm_reader *,
+                        struct dictionary *, struct sfm_read_info *);
+static int parse_format_spec (struct sfm_reader *, int32,
+                             struct fmt_spec *, const struct variable *);
+static int read_value_labels (struct sfm_reader *, struct dictionary *,
+                              struct variable **var_by_idx);
+static int read_variables (struct sfm_reader *,
+                           struct dictionary *, struct variable ***var_by_idx);
+static int read_machine_int32_info (struct sfm_reader *, int size, int count);
+static int read_machine_flt64_info (struct sfm_reader *, int size, int count);
+static int read_documents (struct sfm_reader *, struct dictionary *);
+
+static int fread_ok (struct sfm_reader *, void *, size_t);
+
+/* Displays the message X with corrupt_msg, then jumps to the error
+   label. */
+#define lose(X)                                 \
+       do {                                    \
+           corrupt_msg X;                      \
+           goto error;                         \
+       } while (0)
+
+/* Calls buf_read with the specified arguments, and jumps to
+   error if the read fails. */
+#define assertive_buf_read(a,b,c,d)             \
+       do {                                    \
+           if (!buf_read (a,b,c,d))            \
+             goto error;                       \
+       } while (0)
+
+/* Opens the system file designated by file handle FH for
+   reading.  Reads the system file's dictionary into *DICT.
+   If INFO is non-null, then it receives additional info about the
+   system file. */
+struct sfm_reader *
+sfm_open_reader (struct file_handle *fh, struct dictionary **dict,
+                 struct sfm_read_info *info)
+{
+  struct sfm_reader *r = NULL;
+  struct variable **var_by_idx = NULL;
+
+  *dict = dict_create ();
+  if (!fh_open (fh, FH_REF_FILE, "system file", "rs"))
+    goto error;
+
+  /* Create and initialize reader. */
+  r = xmalloc (sizeof *r);
+  r->fh = fh;
+  r->file = fn_open (fh_get_filename (fh), "rb");
+
+  r->reverse_endian = 0;
+  r->fix_specials = 0;
+  r->value_cnt = 0;
+  r->case_cnt = 0;
+  r->compressed = 0;
+  r->bias = 100.0;
+  r->weight_idx = -1;
+  r->ok = true;
+
+  r->vars = NULL;
+
+  r->sysmis = -FLT64_MAX;
+  r->highest = FLT64_MAX;
+  r->lowest = second_lowest_flt64;
+
+  r->buf = r->ptr = r->end = NULL;
+  r->y = r->x + sizeof r->x;
+
+  /* Check that file open succeeded. */
+  if (r->file == NULL)
+    {
+      msg (ME, _("An error occurred while opening \"%s\" for reading "
+                 "as a system file: %s."),
+           fh_get_filename (r->fh), strerror (errno));
+      goto error;
+    }
+
+  /* Read header and variables. */
+  if (!read_header (r, *dict, info) || !read_variables (r, *dict, &var_by_idx))
+    goto error;
+
+
+  /* Handle weighting. */
+  if (r->weight_idx != -1)
+    {
+      struct variable *weight_var;
+
+      if (r->weight_idx < 0 || r->weight_idx >= r->value_cnt)
+       lose ((ME, _("%s: Index of weighting variable (%d) is not between 0 "
+                    "and number of elements per case (%d)."),
+              fh_get_filename (r->fh), r->weight_idx, r->value_cnt));
+
+
+      weight_var = var_by_idx[r->weight_idx];
+
+      if (weight_var == NULL)
+       lose ((ME,
+               _("%s: Weighting variable may not be a continuation of "
+              "a long string variable."), fh_get_filename (fh)));
+      else if (weight_var->type == ALPHA)
+       lose ((ME, _("%s: Weighting variable may not be a string variable."),
+              fh_get_filename (fh)));
+
+      dict_set_weight (*dict, weight_var);
+    }
+  else
+    dict_set_weight (*dict, NULL);
+
+  /* Read records of types 3, 4, 6, and 7. */
+  for (;;)
+    {
+      int32 rec_type;
+
+      assertive_buf_read (r, &rec_type, sizeof rec_type, 0);
+      if (r->reverse_endian)
+       bswap_int32 (&rec_type);
+
+      switch (rec_type)
+       {
+       case 3:
+         if (!read_value_labels (r, *dict, var_by_idx))
+           goto error;
+         break;
+
+       case 4:
+         lose ((ME, _("%s: Orphaned variable index record (type 4).  Type 4 "
+                       "records must always immediately follow type 3 "
+                       "records."),
+                fh_get_filename (r->fh)));
+
+       case 6:
+         if (!read_documents (r, *dict))
+           goto error;
+         break;
+
+       case 7:
+         {
+           struct
+             {
+               int32 subtype P;
+               int32 size P;
+               int32 count P;
+             }
+           data;
+            unsigned long bytes;
+
+           int skip = 0;
+
+           assertive_buf_read (r, &data, sizeof data, 0);
+           if (r->reverse_endian)
+             {
+               bswap_int32 (&data.subtype);
+               bswap_int32 (&data.size);
+               bswap_int32 (&data.count);
+             }
+            bytes = data.size * data.count;
+            if (bytes < data.size || bytes < data.count)
+              lose ((ME, "%s: Record type %d subtype %d too large.",
+                     fh_get_filename (r->fh), rec_type, data.subtype));
+
+           switch (data.subtype)
+             {
+             case 3:
+               if (!read_machine_int32_info (r, data.size, data.count))
+                 goto error;
+               break;
+
+             case 4:
+               if (!read_machine_flt64_info (r, data.size, data.count))
+                 goto error;
+               break;
+
+             case 5:
+             case 6:  /* ?? Used by SPSS 8.0. */
+               skip = 1;
+               break;
+               
+             case 11: /* Variable display parameters */
+               {
+                 const int  n_vars = data.count / 3 ;
+                 int i;
+                 if ( data.count % 3 || n_vars > dict_get_var_cnt(*dict) ) 
+                   {
+                     msg (MW, _("%s: Invalid subrecord length. "
+                                "Record: 7; Subrecord: 11"), 
+                          fh_get_filename (r->fh));
+                     skip = 1;
+                   }
+
+                 for ( i = 0 ; i < min(n_vars, dict_get_var_cnt(*dict)) ; ++i ) 
+                   {
+                     struct
+                     {
+                       int32 measure P;
+                       int32 width P;
+                       int32 align P;
+                     }
+                     params;
+
+                     struct variable *v;
+
+                     assertive_buf_read (r, &params, sizeof(params), 0);
+
+                     v = dict_get_var(*dict, i);
+
+                     v->measure = params.measure;
+                     v->display_width = params.width;
+                     v->alignment = params.align;
+                   }
+               }
+               break;
+
+             case 13: /* SPSS 12.0 Long variable name map */
+               {
+                 char *buf, *short_name, *save_ptr;
+                  int idx;
+
+                  /* Read data. */
+                  buf = xmalloc (bytes + 1);
+                 if (!buf_read (r, buf, bytes, 0)) 
+                    {
+                      free (buf);
+                      goto error;
+                    }
+                 buf[bytes] = '\0';
+
+                  /* Parse data. */
+                 for (short_name = strtok_r (buf, "=", &save_ptr), idx = 0;
+                       short_name != NULL;
+                       short_name = strtok_r (NULL, "=", &save_ptr), idx++)
+                   {
+                      char *long_name = strtok_r (NULL, "\t", &save_ptr);
+                      struct variable *v;
+
+                      /* Validate long name. */
+                      if (long_name == NULL)
+                        {
+                          msg (MW, _("%s: Trailing garbage in long variable "
+                                     "name map."),
+                               fh_get_filename (r->fh));
+                          break;
+                        }
+                      if (!var_is_valid_name (long_name, false))
+                        {
+                          msg (MW, _("%s: Long variable mapping to invalid "
+                                     "variable name `%s'."),
+                               fh_get_filename (r->fh), long_name);
+                          break;
+                        }
+                      
+                      /* Find variable using short name. */
+                      v = dict_lookup_var (*dict, short_name);
+                      if (v == NULL)
+                        {
+                          msg (MW, _("%s: Long variable mapping for "
+                                     "nonexistent variable %s."),
+                               fh_get_filename (r->fh), short_name);
+                          break;
+                        }
+
+                      /* Identify any duplicates. */
+                     if ( compare_var_names(short_name, long_name, 0) &&
+                         NULL != dict_lookup_var (*dict, long_name))
+                        lose ((ME, _("%s: Duplicate long variable name `%s' "
+                                     "within system file."),
+                               fh_get_filename (r->fh), long_name));
+
+
+                      /* Set long name.
+                         Renaming a variable may clear the short
+                         name, but we want to retain it, so
+                         re-set it explicitly. */
+                      dict_rename_var (*dict, v, long_name);
+                      var_set_short_name (v, short_name);
+
+                      /* For compatability, make sure dictionary
+                         is in long variable name map order.  In
+                         the common case, this has no effect,
+                         because the dictionary and the long
+                         variable name map are already in the
+                         same order. */
+                      dict_reorder_var (*dict, v, idx);
+                   }
+
+                 /* Free data. */
+                 free (buf);
+               }
+               break;
+
+             default:
+               msg (MW, _("%s: Unrecognized record type 7, subtype %d "
+                           "encountered in system file."),
+                     fh_get_filename (r->fh), data.subtype);
+               skip = 1;
+             }
+
+           if (skip)
+             {
+               void *x = buf_read (r, NULL, data.size * data.count, 0);
+               if (x == NULL)
+                 goto error;
+               free (x);
+             }
+         }
+         break;
+
+       case 999:
+         {
+           int32 filler;
+
+           assertive_buf_read (r, &filler, sizeof filler, 0);
+           goto success;
+         }
+
+       default:
+         corrupt_msg(MW, _("%s: Unrecognized record type %d."),
+                 fh_get_filename (r->fh), rec_type);
+       }
+    }
+
+success:
+  /* Come here on successful completion. */
+  free (var_by_idx);
+  return r;
+
+error:
+  /* Come here on unsuccessful completion. */
+  sfm_close_reader (r);
+  free (var_by_idx);
+  if (*dict != NULL) 
+    {
+      dict_destroy (*dict);
+      *dict = NULL; 
+    }
+  return NULL;
+}
+
+/* Read record type 7, subtype 3. */
+static int
+read_machine_int32_info (struct sfm_reader *r, int size, int count)
+{
+  int32 data[8];
+  int file_bigendian;
+
+  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."),
+          fh_get_filename (r->fh), size, count, sizeof (int32)));
+
+  assertive_buf_read (r, data, sizeof data, 0);
+  if (r->reverse_endian)
+    for (i = 0; i < 8; i++)
+      bswap_int32 (&data[i]);
+
+#ifdef 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."),
+           fh_get_filename (r->fh)));
+#else
+#error Add support for your floating-point format.
+#endif
+
+#ifdef WORDS_BIGENDIAN
+  file_bigendian = 1;
+#else
+  file_bigendian = 0;
+#endif
+  if (r->reverse_endian)
+    file_bigendian ^= 1;
+  if (file_bigendian ^ (data[6] == 1))
+    lose ((ME, _("%s: File-indicated endianness (%s) does not match "
+                 "endianness intuited from file header (%s)."),
+          fh_get_filename (r->fh),
+           file_bigendian ? _("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."),
+           fh_get_filename (r->fh),
+           (data[7] == 1 ? "EBCDIC"
+            : (data[7] == 4 ? _("DEC Kanji") : _("Unknown")))));
+
+  return 1;
+
+error:
+  return 0;
+}
+
+/* Read record type 7, subtype 4. */
+static int
+read_machine_flt64_info (struct sfm_reader *r, int size, int count)
+{
+  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."),
+          fh_get_filename (r->fh), size, count, sizeof (flt64)));
+
+  assertive_buf_read (r, data, sizeof data, 0);
+  if (r->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)
+    {
+      r->sysmis = data[0];
+      r->highest = data[1];
+      r->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."),
+          fh_get_filename (r->fh), (double) data[0], (double) SYSMIS,
+          (double) data[1], (double) FLT64_MAX,
+          (double) data[2], (double) second_lowest_flt64);
+    }
+  
+  return 1;
+
+error:
+  return 0;
+}
+
+static int
+read_header (struct sfm_reader *r,
+             struct dictionary *dict, struct sfm_read_info *info)
+{
+  struct sysfile_header hdr;           /* Disk buffer. */
+  char prod_name[sizeof hdr.prod_name + 1];    /* Buffer for product name. */
+  int skip_amt = 0;                    /* Amount of product name to omit. */
+  int i;
+
+  /* Read header, check magic. */
+  assertive_buf_read (r, &hdr, sizeof hdr, 0);
+  if (strncmp ("$FL2", hdr.rec_type, 4) != 0)
+    lose ((ME, _("%s: Bad magic.  Proper system files begin with "
+                "the four characters `$FL2'. This file will not be read."),
+          fh_get_filename (r->fh)));
+
+  /* Check eye-category.her 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. */
+  if (hdr.layout_code == 2)
+    r->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."),
+              fh_get_filename (r->fh), hdr.layout_code));
+
+      r->reverse_endian = 1;
+      bswap_int32 (&hdr.case_size);
+      bswap_int32 (&hdr.compress);
+      bswap_int32 (&hdr.weight_idx);
+      bswap_int32 (&hdr.case_cnt);
+      bswap_flt64 (&hdr.bias);
+    }
+
+
+  /* Copy basic info and verify correctness. */
+  r->value_cnt = hdr.case_size;
+
+  /* If value count is rediculous, then force it to -1 (a sentinel value) */
+  if ( r->value_cnt < 0 || 
+       r->value_cnt > (INT_MAX / (int) sizeof (union value) / 2))
+    r->value_cnt = -1;
+
+  r->compressed = hdr.compress;
+
+  r->weight_idx = hdr.weight_idx - 1;
+
+  r->case_cnt = hdr.case_cnt;
+  if (r->case_cnt < -1 || r->case_cnt > INT_MAX / 2)
+    lose ((ME,
+           _("%s: Number of cases in file (%ld) is not between -1 and %d."),
+           fh_get_filename (r->fh), (long) r->case_cnt, INT_MAX / 2));
+
+  r->bias = hdr.bias;
+  if (r->bias != 100.0)
+    corrupt_msg (MW, _("%s: Compression bias (%g) is not the usual "
+                       "value of 100."),
+                 fh_get_filename (r->fh), r->bias);
+
+  /* Make a file label only on the condition that the given label is
+     not all spaces or nulls. */
+  {
+    int i;
+
+    for (i = sizeof hdr.file_label - 1; i >= 0; i--)
+      if (!isspace ((unsigned char) hdr.file_label[i])
+         && hdr.file_label[i] != 0)
+       {
+          char *label = xmalloc (i + 2);
+         memcpy (label, hdr.file_label, i + 1);
+         label[i + 1] = 0;
+          dict_set_label (dict, label);
+          free (label);
+         break;
+       }
+  }
+
+  if (info)
+    {
+      char *cp;
+
+      memcpy (info->creation_date, hdr.creation_date, 9);
+      info->creation_date[9] = 0;
+
+      memcpy (info->creation_time, hdr.creation_time, 8);
+      info->creation_time[8] = 0;
+
+#ifdef WORDS_BIGENDIAN
+      info->big_endian = !r->reverse_endian;
+#else
+      info->big_endian = r->reverse_endian;
+#endif
+
+      info->compressed = hdr.compress;
+
+      info->case_cnt = hdr.case_cnt;
+
+      for (cp = &prod_name[skip_amt]; cp < &prod_name[60]; cp++)
+       if (isgraph ((unsigned char) *cp))
+         break;
+      strcpy (info->product, cp);
+    }
+
+  return 1;
+
+error:
+  return 0;
+}
+
+/* Reads most of the dictionary from file H; also fills in the
+   associated VAR_BY_IDX array. */
+static int
+read_variables (struct sfm_reader *r,
+                struct dictionary *dict, struct variable ***var_by_idx)
+{
+  int i;
+
+  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. */
+
+  assert(r);
+
+  *var_by_idx = 0;
+
+  /* Pre-allocate variables. */
+  if (r->value_cnt != -1) 
+    {
+      *var_by_idx = xnmalloc (r->value_cnt, sizeof **var_by_idx);
+      r->vars = xnmalloc (r->value_cnt, sizeof *r->vars);
+    }
+
+
+  /* Read in the entry for each variable and use the info to
+     initialize the dictionary. */
+  for (i = 0; ; ++i)
+    {
+      struct variable *vv;
+      char name[SHORT_NAME_LEN + 1];
+      int nv;
+      int j;
+
+      if ( r->value_cnt != -1  && i >= r->value_cnt ) 
+       break;
+
+      assertive_buf_read (r, &sv, sizeof sv, 0);
+
+      if (r->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);
+       }
+
+      /* We've come to the end of the variable entries */
+      if (sv.rec_type != 2)
+       {
+         buf_unread(r, sizeof sv);
+         r->value_cnt = i;
+         break;
+       }
+
+      if ( -1 == r->value_cnt ) 
+       {
+         *var_by_idx = xnrealloc (*var_by_idx, i + 1, sizeof **var_by_idx);
+         r->vars = xnrealloc (r->vars, i + 1, sizeof *r->vars);
+       }
+
+      /* 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."),
+                   fh_get_filename (r->fh), i));
+
+
+         r->vars[i].width = -1;
+         (*var_by_idx)[i] = NULL;
+         long_string_count--;
+         continue;
+       }
+      else if (sv.type == -1)
+       lose ((ME, _("%s: position %d: Superfluous long string continuation "
+                     "record."),
+               fh_get_filename (r->fh), i));
+
+      /* Check fields for validity. */
+      if (sv.type < 0 || sv.type > 255)
+       lose ((ME, _("%s: position %d: Bad variable type code %d."),
+              fh_get_filename (r->fh), 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."), fh_get_filename (r->fh), 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."), fh_get_filename (r->fh), i));
+
+      /* 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."),
+               fh_get_filename (r->fh), i));
+      if (islower ((unsigned char) sv.name[0]))
+       msg (MW, _("%s: position %d: Variable name begins with lowercase letter "
+                   "%c."),
+             fh_get_filename (r->fh), 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."),
+             fh_get_filename (r->fh), i);
+      name[0] = toupper ((unsigned char) (sv.name[0]));
+
+      /* Copy remaining characters of variable name. */
+      for (j = 1; j < SHORT_NAME_LEN; 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."),
+                   fh_get_filename (r->fh), i, j + 1, sv.name[j]);
+             name[j] = toupper ((unsigned char) (c));
+           }
+         else if (isalnum (c) || c == '.' || c == '@'
+                  || c == '#' || c == '$' || c == '_')
+           name[j] = c;
+         else
+           lose ((ME, _("%s: position %d: character `\\%03o' (%c) is not valid in a "
+                         "variable name."),
+                   fh_get_filename (r->fh), i, c, c));
+       }
+      name[j] = 0;
+
+      if ( ! var_is_valid_name(name, false) ) 
+        lose ((ME, _("%s: Invalid variable name `%s' within system file."),
+               fh_get_filename (r->fh), name));
+
+      /* Create variable. */
+
+      vv = (*var_by_idx)[i] = dict_create_var (dict, name, sv.type);
+      if (vv == NULL) 
+        lose ((ME, _("%s: Duplicate variable name `%s' within system file."),
+               fh_get_filename (r->fh), name));
+
+      var_set_short_name (vv, vv->name);
+
+      /* Case reading data. */
+      nv = sv.type == 0 ? 1 : DIV_RND_UP (sv.type, sizeof (flt64));
+      long_string_count = nv - 1;
+      next_value += nv;
+
+      /* Get variable label, if any. */
+      if (sv.has_var_label == 1)
+       {
+         /* Disk buffer. */
+         int32 len;
+
+         /* Read length of label. */
+         assertive_buf_read (r, &len, sizeof len, 0);
+         if (r->reverse_endian)
+           bswap_int32 (&len);
+
+         /* Check len. */
+         if (len < 0 || len > 255)
+           lose ((ME, _("%s: Variable %s indicates variable label of invalid "
+                         "length %d."),
+                   fh_get_filename (r->fh), vv->name, len));
+
+         if ( len != 0 ) 
+           {
+             /* Read label into variable structure. */
+             vv->label = buf_read (r, NULL, ROUND_UP (len, sizeof (int32)), len + 1);
+             if (vv->label == NULL)
+               goto error;
+             vv->label[len] = '\0';
+           }
+       }
+
+      /* Set missing values. */
+      if (sv.n_missing_values != 0)
+       {
+         flt64 mv[3];
+          int mv_cnt = abs (sv.n_missing_values);
+
+         if (vv->width > MAX_SHORT_STRING)
+           lose ((ME, _("%s: Long string variable %s may not have missing "
+                         "values."),
+                   fh_get_filename (r->fh), vv->name));
+
+         assertive_buf_read (r, mv, sizeof *mv * mv_cnt, 0);
+
+         if (r->reverse_endian && vv->type == NUMERIC)
+           for (j = 0; j < mv_cnt; j++)
+             bswap_flt64 (&mv[j]);
+
+         if (sv.n_missing_values > 0)
+           {
+              for (j = 0; j < sv.n_missing_values; j++)
+                if (vv->type == NUMERIC)
+                  mv_add_num (&vv->miss, mv[j]);
+                else
+                  mv_add_str (&vv->miss, (char *) &mv[j]);
+           }
+         else
+           {
+             if (vv->type == ALPHA)
+               lose ((ME, _("%s: String variable %s may not have missing "
+                             "values specified as a range."),
+                       fh_get_filename (r->fh), vv->name));
+
+             if (mv[0] == r->lowest)
+                mv_add_num_range (&vv->miss, LOWEST, mv[1]);
+             else if (mv[1] == r->highest)
+                mv_add_num_range (&vv->miss, mv[0], HIGHEST);
+             else
+                mv_add_num_range (&vv->miss, mv[0], mv[1]);
+
+             if (sv.n_missing_values == -3)
+                mv_add_num (&vv->miss, mv[2]);
+           }
+       }
+
+      if (!parse_format_spec (r, sv.print, &vv->print, vv)
+         || !parse_format_spec (r, sv.write, &vv->write, vv))
+       goto error;
+
+      r->vars[i].width = vv->width;
+      r->vars[i].fv = vv->fv;
+
+    }
+
+  /* Some consistency checks. */
+  if (long_string_count != 0)
+    lose ((ME, _("%s: Long string continuation records omitted at end of "
+                 "dictionary."),
+           fh_get_filename (r->fh)));
+
+  if (next_value != r->value_cnt)
+    corrupt_msg(MW, _("%s: System file header indicates %d variable positions but "
+                 "%d were read from file."),
+           fh_get_filename (r->fh), r->value_cnt, next_value);
+
+
+  return 1;
+
+error:
+  return 0;
+}
+
+/* Translates the format spec from sysfile format to internal
+   format. */
+static int
+parse_format_spec (struct sfm_reader *r, int32 s,
+                   struct fmt_spec *f, const struct variable *v)
+{
+  f->type = translate_fmt ((s >> 16) & 0xff);
+  if (f->type == -1)
+    lose ((ME, _("%s: Bad format specifier byte (%d)."),
+          fh_get_filename (r->fh), (s >> 16) & 0xff));
+  f->w = (s >> 8) & 0xff;
+  f->d = s & 0xff;
+
+  if ((v->type == ALPHA) ^ ((formats[f->type].cat & FCAT_STRING) != 0))
+    lose ((ME, _("%s: %s variable %s has %s format specifier %s."),
+          fh_get_filename (r->fh),
+           v->type == ALPHA ? _("String") : _("Numeric"),
+          v->name,
+          formats[f->type].cat & FCAT_STRING ? _("string") : _("numeric"),
+          formats[f->type].name));
+
+  if (!check_output_specifier (f, false)
+      || !check_specifier_width (f, v->width, false)) 
+    {
+      msg (ME, _("%s variable %s has invalid format specifier %s."),
+           v->type == NUMERIC ? _("Numeric") : _("String"),
+           v->name, fmt_to_string (f));
+      *f = v->type == NUMERIC ? f8_2 : make_output_format (FMT_A, v->width, 0);
+    }
+  return 1;
+
+error:
+  return 0;
+}
+
+/* Reads value labels from sysfile H and inserts them into the
+   associated dictionary. */
+int
+read_value_labels (struct sfm_reader *r,
+                   struct dictionary *dict, struct variable **var_by_idx)
+{
+  struct label 
+    {
+      char raw_value[8];        /* Value as uninterpreted bytes. */
+      union value value;        /* Value. */
+      char *label;              /* Null-terminated label string. */
+    };
+
+  struct label *labels = NULL;
+  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_buf_read (r, &n_labels, sizeof n_labels, 0);
+  if (r->reverse_endian)
+    bswap_int32 (&n_labels);
+
+  if ( n_labels >= ((int32) ~0) / sizeof *labels)
+    {    
+      corrupt_msg(MW, _("%s: Invalid number of labels: %d.  Ignoring labels."),
+                 fh_get_filename (r->fh), n_labels);
+      n_labels = 0;
+    }
+
+  /* Allocate memory. */
+  labels = xcalloc (n_labels, sizeof *labels);
+  for (i = 0; i < n_labels; i++)
+    labels[i].label = NULL;
+
+  /* Read each value/label tuple into labels[]. */
+  for (i = 0; i < n_labels; i++)
+    {
+      struct label *label = labels + i;
+      unsigned char label_len;
+      size_t padded_len;
+
+      /* Read value. */
+      assertive_buf_read (r, label->raw_value, sizeof label->raw_value, 0);
+
+      /* Read label length. */
+      assertive_buf_read (r, &label_len, sizeof label_len, 0);
+      padded_len = ROUND_UP (label_len + 1, sizeof (flt64));
+
+      /* Read label, padding. */
+      label->label = xmalloc (padded_len + 1);
+      assertive_buf_read (r, label->label, padded_len - 1, 0);
+      label->label[label_len] = 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_buf_read (r, &rec_type, sizeof rec_type, 0);
+    if (r->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 should."),
+             fh_get_filename (r->fh)));
+  }
+
+  /* Read number of variables associated with value label from type 4
+     record. */
+  assertive_buf_read (r, &n_vars, sizeof n_vars, 0);
+  if (r->reverse_endian)
+    bswap_int32 (&n_vars);
+  if (n_vars < 1 || n_vars > dict_get_var_cnt (dict))
+    lose ((ME, _("%s: Number of variables associated with a value label (%d) "
+                 "is not between 1 and the number of variables (%d)."),
+          fh_get_filename (r->fh), n_vars, dict_get_var_cnt (dict)));
+
+  /* Read the list of variables. */
+  var = xnmalloc (n_vars, sizeof *var);
+  for (i = 0; i < n_vars; i++)
+    {
+      int32 var_idx;
+      struct variable *v;
+
+      /* Read variable index, check range. */
+      assertive_buf_read (r, &var_idx, sizeof var_idx, 0);
+      if (r->reverse_endian)
+       bswap_int32 (&var_idx);
+      if (var_idx < 1 || var_idx > r->value_cnt)
+       lose ((ME, _("%s: Variable index associated with value label (%d) is "
+                     "not between 1 and the number of values (%d)."),
+              fh_get_filename (r->fh), var_idx, r->value_cnt));
+
+      /* Make sure it's a real variable. */
+      v = var_by_idx[var_idx - 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."),
+               fh_get_filename (r->fh), var_idx));
+      if (v->type == ALPHA && v->width > MAX_SHORT_STRING)
+       lose ((ME, _("%s: Value labels are not allowed on long string "
+                     "variables (%s)."),
+               fh_get_filename (r->fh), 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."),
+             fh_get_filename (r->fh),
+            var[0]->name, var[0]->type == ALPHA ? _("string") : _("numeric"),
+            var[i]->name, var[i]->type == ALPHA ? _("string") : _("numeric")));
+
+  /* Fill in labels[].value, now that we know the desired type. */
+  for (i = 0; i < n_labels; i++) 
+    {
+      struct label *label = labels + i;
+      
+      if (var[0]->type == ALPHA)
+        {
+          const int copy_len = min (sizeof label->raw_value,
+                                    sizeof label->label);
+          memcpy (label->value.s, label->raw_value, copy_len);
+        } else {
+          flt64 f;
+          assert (sizeof f == sizeof label->raw_value);
+          memcpy (&f, label->raw_value, sizeof f);
+          if (r->reverse_endian)
+            bswap_flt64 (&f);
+          label->value.f = f;
+        }
+    }
+  
+  /* Assign the value_label's to each variable. */
+  for (i = 0; i < n_vars; i++)
+    {
+      struct variable *v = var[i];
+      int j;
+
+      /* Add each label to the variable. */
+      for (j = 0; j < n_labels; j++)
+       {
+          struct label *label = labels + j;
+         if (!val_labs_replace (v->val_labs, label->value, label->label))
+           continue;
+
+         if (var[0]->type == NUMERIC)
+           msg (MW, _("%s: File contains duplicate label for value %g for "
+                       "variable %s."),
+                 fh_get_filename (r->fh), label->value.f, v->name);
+         else
+           msg (MW, _("%s: File contains duplicate label for value `%.*s' "
+                       "for variable %s."),
+                 fh_get_filename (r->fh), v->width, label->value.s, v->name);
+       }
+    }
+
+  for (i = 0; i < n_labels; i++)
+    free (labels[i].label);
+  free (labels);
+  free (var);
+  return 1;
+
+error:
+  if (labels) 
+    {
+      for (i = 0; i < n_labels; i++)
+        free (labels[i].label);
+      free (labels); 
+    }
+  free (var);
+  return 0;
+}
+
+/* Reads BYTE_CNT bytes from the file represented by H.  If BUF is
+   non-NULL, uses that as the buffer; otherwise allocates at least
+   MIN_ALLOC bytes.  Returns a pointer to the buffer on success, NULL
+   on failure. */
+static void *
+buf_read (struct sfm_reader *r, void *buf, size_t byte_cnt, size_t min_alloc)
+{
+  assert (r);
+
+  if (buf == NULL && byte_cnt > 0 )
+    buf = xmalloc (max (byte_cnt, min_alloc));
+
+  if ( byte_cnt == 0 )
+    return buf;
+
+  
+  if (1 != fread (buf, byte_cnt, 1, r->file))
+    {
+      if (ferror (r->file))
+       msg (ME, _("%s: Reading system file: %s."),
+             fh_get_filename (r->fh), strerror (errno));
+      else
+       corrupt_msg (ME, _("%s: Unexpected end of file."),
+                     fh_get_filename (r->fh));
+      r->ok = false;
+      return NULL;
+    }
+  return buf;
+}
+
+/* Winds the reader BYTE_CNT bytes back in the reader stream.   */
+void
+buf_unread(struct sfm_reader *r, size_t byte_cnt)
+{
+  assert(byte_cnt > 0);
+
+  if ( 0 != fseek(r->file, -byte_cnt, SEEK_CUR))
+    {
+      msg (ME, _("%s: Seeking system file: %s."),
+          fh_get_filename (r->fh), strerror (errno));
+    }
+}
+
+/* Reads a document record, type 6, from system file R, and sets up
+   the documents and n_documents fields in the associated
+   dictionary. */
+static int
+read_documents (struct sfm_reader *r, struct dictionary *dict)
+{
+  int32 line_cnt;
+  char *documents;
+
+  if (dict_get_documents (dict) != NULL)
+    lose ((ME, _("%s: System file contains multiple "
+                 "type 6 (document) records."),
+          fh_get_filename (r->fh)));
+
+  assertive_buf_read (r, &line_cnt, sizeof line_cnt, 0);
+  if (line_cnt <= 0)
+    lose ((ME, _("%s: Number of document lines (%ld) "
+                 "must be greater than 0."),
+          fh_get_filename (r->fh), (long) line_cnt));
+
+  documents = buf_read (r, NULL, 80 * line_cnt, line_cnt * 80 + 1);
+  /* FIXME?  Run through asciify. */
+  if (documents == NULL)
+    return 0;
+  documents[80 * line_cnt] = '\0';
+  dict_set_documents (dict, documents);
+  free (documents);
+  return 1;
+
+error:
+  return 0;
+}
+\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 sfm_reader *r)
+{
+  size_t amt;
+
+  if (!r->ok)
+    return false;
+  if (r->buf == NULL)
+    r->buf = xnmalloc (128, sizeof *r->buf);
+  amt = fread (r->buf, sizeof *r->buf, 128, r->file);
+  if (ferror (r->file))
+    {
+      msg (ME, _("%s: Error reading file: %s."),
+           fh_get_filename (r->fh), strerror (errno));
+      r->ok = false;
+      return 0;
+    }
+  r->ptr = r->buf;
+  r->end = &r->buf[amt];
+  return amt;
+}
+
+/* Reads a single case consisting of compressed data from system
+   file H into the array BUF[] according to reader R, and
+   returns nonzero only if successful. */
+/* Data in system files is compressed in this manner.  Data
+   values are grouped into sets of eight ("octets").  Each value
+   in an octet has one instruction byte that are output together.
+   Each instruction byte gives a value for that byte or indicates
+   that the value can be found following the instructions. */
+static int
+read_compressed_data (struct sfm_reader *r, flt64 *buf)
+{
+  const unsigned char *p_end = r->x + sizeof (flt64);
+  unsigned char *p = r->y;
+
+  const flt64 *buf_beg = buf;
+  const flt64 *buf_end = &buf[r->value_cnt];
+
+  for (;;)
+    {
+      for (; p < p_end; p++){
+       switch (*p)
+         {
+         case 0:
+           /* Code 0 is ignored. */
+           continue;
+         case 252:
+           /* Code 252 is end of file. */
+           if (buf_beg == buf)
+              return 0;
+            lose ((ME, _("%s: Compressed data is corrupted.  Data ends "
+                         "in partial case."),
+                   fh_get_filename (r->fh)));
+         case 253:
+           /* Code 253 indicates that the value is stored explicitly
+              following the instruction bytes. */
+           if (r->ptr == NULL || r->ptr >= r->end)
+             if (!buffer_input (r))
+                lose ((ME, _("%s: Unexpected end of file."),
+                       fh_get_filename (r->fh)));
+           memcpy (buf++, r->ptr++, sizeof *buf);
+           if (buf >= buf_end)
+             goto success;
+           break;
+         case 254:
+           /* Code 254 indicates a string that is all blanks. */
+           memset (buf++, ' ', sizeof *buf);
+           if (buf >= buf_end)
+             goto success;
+           break;
+         case 255:
+           /* Code 255 indicates the system-missing value. */
+           *buf = r->sysmis;
+           if (r->reverse_endian)
+             bswap_flt64 (buf);
+           buf++;
+           if (buf >= buf_end)
+             goto success;
+           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). */
+           *buf = *p - r->bias;
+           if (r->reverse_endian)
+             bswap_flt64 (buf);
+           buf++;
+           if (buf >= buf_end)
+             goto success;
+           break;
+         }
+      }
+      /* We have reached the end of this instruction octet.  Read
+        another. */
+      if (r->ptr == NULL || r->ptr >= r->end) 
+        {
+          if (!buffer_input (r))
+            if (buf_beg != buf)
+              lose ((ME, _("%s: Unexpected end of file."),
+                     fh_get_filename (r->fh))); 
+        }
+      memcpy (r->x, r->ptr++, sizeof *buf);
+      p = r->x;
+    }
+
+  abort ();
+
+success:
+  /* We have filled up an entire record.  Update state and return
+     successfully. */
+  r->y = ++p;
+  return 1;
+
+error:
+  /* I/O error. */
+  r->ok = false;
+  return 0;
+}
+
+/* Reads one case from READER's file into C.  Returns nonzero
+   only if successful. */
+int
+sfm_read_case (struct sfm_reader *r, struct ccase *c)
+{
+  if (!r->ok)
+    return 0;
+  
+  if (!r->compressed && sizeof (flt64) == sizeof (double)) 
+    {
+      /* Fast path: external and internal representations are the
+         same, except possibly for endianness or SYSMIS.  Read
+         directly into the case's buffer, then fix up any minor
+         details as needed. */
+      if (!fread_ok (r, case_data_all_rw (c),
+                     sizeof (union value) * r->value_cnt))
+        return 0;
+
+      /* Fix up endianness if needed. */
+      if (r->reverse_endian) 
+        {
+          int i;
+          
+          for (i = 0; i < r->value_cnt; i++) 
+            if (r->vars[i].width == 0)
+              bswap_flt64 (&case_data_rw (c, r->vars[i].fv)->f);
+        }
+
+      /* Fix up SYSMIS values if needed.
+         I don't think this will ever actually kick in, but it
+         can't hurt. */
+      if (r->sysmis != SYSMIS) 
+        {
+          int i;
+          
+          for (i = 0; i < r->value_cnt; i++) 
+            if (r->vars[i].width == 0 && case_num (c, i) == r->sysmis)
+              case_data_rw (c, r->vars[i].fv)->f = SYSMIS;
+        }
+    }
+  else 
+    {
+      /* Slow path: internal and external representations differ.
+         Read into a bounce buffer, then copy to C. */
+      flt64 *bounce;
+      flt64 *bounce_cur;
+      size_t bounce_size;
+      int read_ok;
+      int i;
+
+      bounce_size = sizeof *bounce * r->value_cnt;
+      bounce = bounce_cur = local_alloc (bounce_size);
+
+      if (!r->compressed)
+        read_ok = fread_ok (r, bounce, bounce_size);
+      else
+        read_ok = read_compressed_data (r, bounce);
+      if (!read_ok) 
+        {
+          local_free (bounce);
+          return 0;
+        }
+
+      for (i = 0; i < r->value_cnt; i++)
+        {
+          struct sfm_var *v = &r->vars[i];
+
+          if (v->width == 0)
+            {
+              flt64 f = *bounce_cur++;
+              if (r->reverse_endian)
+                bswap_flt64 (&f);
+              case_data_rw (c, v->fv)->f = f == r->sysmis ? SYSMIS : f;
+            }
+          else if (v->width != -1)
+            {
+              memcpy (case_data_rw (c, v->fv)->s, bounce_cur, v->width);
+              bounce_cur += DIV_RND_UP (v->width, sizeof (flt64));
+            }
+        }
+
+      local_free (bounce);
+    }
+  return 1; 
+}
+
+static int
+fread_ok (struct sfm_reader *r, void *buffer, size_t byte_cnt)
+{
+  size_t read_bytes = fread (buffer, 1, byte_cnt, r->file);
+
+  if (read_bytes == byte_cnt)
+    return 1;
+  else
+    {
+      if (ferror (r->file)) 
+        {
+          msg (ME, _("%s: Reading system file: %s."),
+               fh_get_filename (r->fh), strerror (errno));
+          r->ok = false; 
+        }
+      else if (read_bytes != 0) 
+        {
+          msg (ME, _("%s: Partial record at end of system file."),
+               fh_get_filename (r->fh));
+          r->ok = false; 
+        }
+      return 0;
+    }
+}
+\f
+/* Returns true if an I/O error has occurred on READER, false
+   otherwise. */
+bool
+sfm_read_error (const struct sfm_reader *reader) 
+{
+  return !reader->ok;
+}
+
+/* Returns true if FILE is an SPSS system file,
+   false otherwise. */
+bool
+sfm_detect (FILE *file) 
+{
+  struct sysfile_header hdr;
+
+  if (fread (&hdr, sizeof hdr, 1, file) != 1)
+    return false;
+  if (strncmp ("$FL2", hdr.rec_type, 4))
+    return false;
+  return true; 
+}
diff --git a/src/data/sys-file-reader.h b/src/data/sys-file-reader.h
new file mode 100644 (file)
index 0000000..68e6e84
--- /dev/null
@@ -0,0 +1,50 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef SFM_READ_H
+#define SFM_READ_H 1
+
+#include <stdbool.h>
+#include <stdio.h>
+
+/* Reading system files. */
+
+/* System file info that doesn't fit in struct dictionary. */
+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 big_endian;            /* 1=big-endian, 0=little-endian. */
+    int compressed;            /* 0=no, 1=yes. */
+    int case_cnt;               /* -1 if unknown. */
+    char product[61];          /* Product name plus a null. */
+  };
+
+struct dictionary;
+struct file_handle;
+struct ccase;
+struct sfm_reader *sfm_open_reader (struct file_handle *,
+                                    struct dictionary **,
+                                    struct sfm_read_info *);
+int sfm_read_case (struct sfm_reader *, struct ccase *);
+bool sfm_read_error (const struct sfm_reader *);
+void sfm_close_reader (struct sfm_reader *);
+bool sfm_detect (FILE *);
+
+#endif /* sys-file-reader.h */
diff --git a/src/data/sys-file-writer.c b/src/data/sys-file-writer.c
new file mode 100644 (file)
index 0000000..578bd98
--- /dev/null
@@ -0,0 +1,910 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "sys-file-writer.h"
+#include "sfm-private.h"
+#include "message.h"
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <time.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>    /* Required by SunOS4. */
+#endif
+#include "alloc.h"
+#include "case.h"
+#include "dictionary.h"
+#include "message.h"
+#include "file-handle-def.h"
+#include "hash.h"
+#include "magic.h"
+#include "misc.h"
+#include "settings.h"
+#include "stat-macros.h"
+#include "str.h"
+#include "value-labels.h"
+#include "variable.h"
+#include "version.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+/* Compression bias used by PSPP.  Values between (1 -
+   COMPRESSION_BIAS) and (251 - COMPRESSION_BIAS) inclusive can be
+   compressed. */
+#define COMPRESSION_BIAS 100
+
+/* System file writer. */
+struct sfm_writer
+  {
+    struct file_handle *fh;     /* File handle. */
+    FILE *file;                        /* File stream. */
+
+    int needs_translation;      /* 0=use fast path, 1=translation needed. */
+    int compress;              /* 1=compressed, 0=not compressed. */
+    int case_cnt;              /* Number of cases written so far. */
+    size_t flt64_cnt;           /* Number of flt64 elements in case. */
+
+    /* Compression buffering. */
+    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. */
+
+    /* Variables. */
+    struct sfm_var *vars;       /* Variables. */
+    size_t var_cnt;             /* Number of variables. */
+  };
+
+/* A variable in a system file. */
+struct sfm_var 
+  {
+    int width;                  /* 0=numeric, otherwise string width. */
+    int fv;                     /* Index into case. */
+    size_t flt64_cnt;           /* Number of flt64 elements. */
+  };
+
+static char *append_string_max (char *, const char *, const char *);
+static void write_header (struct sfm_writer *, const struct dictionary *);
+static void buf_write (struct sfm_writer *, const void *, size_t);
+static void write_variable (struct sfm_writer *, struct variable *);
+static void write_value_labels (struct sfm_writer *,
+                                struct variable *, int idx);
+static void write_rec_7_34 (struct sfm_writer *);
+
+static void write_longvar_table (struct sfm_writer *w, 
+                                 const struct dictionary *dict);
+
+static void write_variable_display_parameters (struct sfm_writer *w, 
+                                               const struct dictionary *dict);
+
+static void write_documents (struct sfm_writer *, const struct dictionary *);
+static int does_dict_need_translation (const struct dictionary *);
+
+static inline int
+var_flt64_cnt (const struct variable *v) 
+{
+  return v->type == NUMERIC ? 1 : DIV_RND_UP (v->width, sizeof (flt64));
+}
+
+/* Returns default options for writing a system file. */
+struct sfm_write_options
+sfm_writer_default_options (void) 
+{
+  struct sfm_write_options opts;
+  opts.create_writeable = true;
+  opts.compress = get_scompression ();
+  opts.version = 3;
+  return opts;
+}
+
+/* Opens the system file designated by file handle FH for writing
+   cases from dictionary D according to the given OPTS.  If
+   COMPRESS is nonzero, the system file will be compressed.
+
+   No reference to D is retained, so it may be modified or
+   destroyed at will after this function returns.  D is not
+   modified by this function, except to assign short names. */
+struct sfm_writer *
+sfm_open_writer (struct file_handle *fh, struct dictionary *d,
+                 struct sfm_write_options opts)
+{
+  struct sfm_writer *w = NULL;
+  mode_t mode;
+  int fd;
+  int idx;
+  int i;
+
+  /* Check version. */
+  if (opts.version != 2 && opts.version != 3) 
+    {
+      msg (ME, _("Unknown system file version %d. Treating as version %d."),
+           opts.version, 3);
+      opts.version = 3;
+    }
+
+  /* Create file. */
+  mode = S_IRUSR | S_IRGRP | S_IROTH;
+  if (opts.create_writeable)
+    mode |= S_IWUSR | S_IWGRP | S_IWOTH;
+  fd = open (fh_get_filename (fh), O_WRONLY | O_CREAT | O_TRUNC, mode);
+  if (fd < 0) 
+    goto open_error;
+
+  /* Open file handle. */
+  if (!fh_open (fh, FH_REF_FILE, "system file", "we"))
+    goto error;
+
+  /* Create and initialize writer. */
+  w = xmalloc (sizeof *w);
+  w->fh = fh;
+  w->file = fdopen (fd, "w");
+
+  w->needs_translation = does_dict_need_translation (d);
+  w->compress = opts.compress;
+  w->case_cnt = 0;
+  w->flt64_cnt = 0;
+
+  w->buf = w->end = w->ptr = NULL;
+  w->x = w->y = NULL;
+
+  w->var_cnt = dict_get_var_cnt (d);
+  w->vars = xnmalloc (w->var_cnt, sizeof *w->vars);
+  for (i = 0; i < w->var_cnt; i++) 
+    {
+      const struct variable *dv = dict_get_var (d, i);
+      struct sfm_var *sv = &w->vars[i];
+      sv->width = dv->width;
+      sv->fv = dv->fv;
+      sv->flt64_cnt = var_flt64_cnt (dv);
+    }
+
+  /* Check that file create succeeded. */
+  if (w->file == NULL) 
+    {
+      close (fd);
+      goto open_error;
+    }
+
+  /* Write the file header. */
+  write_header (w, d);
+
+  /* Write basic variable info. */
+  dict_assign_short_names (d);
+  for (i = 0; i < dict_get_var_cnt (d); i++)
+    write_variable (w, dict_get_var (d, i));
+
+  /* Write out value labels. */
+  for (idx = i = 0; i < dict_get_var_cnt (d); i++)
+    {
+      struct variable *v = dict_get_var (d, i);
+
+      write_value_labels (w, v, idx);
+      idx += var_flt64_cnt (v);
+    }
+
+  if (dict_get_documents (d) != NULL)
+    write_documents (w, d);
+
+  write_rec_7_34 (w);
+
+  write_variable_display_parameters (w, d);
+
+  if (opts.version >= 3) 
+    write_longvar_table (w, d);
+
+  /* Write end-of-headers record. */
+  {
+    struct
+      {
+       int32 rec_type P;
+       int32 filler P;
+      }
+    rec_999;
+
+    rec_999.rec_type = 999;
+    rec_999.filler = 0;
+
+    buf_write (w, &rec_999, sizeof rec_999);
+  }
+
+  if (w->compress) 
+    {
+      w->buf = xnmalloc (128, sizeof *w->buf);
+      w->ptr = w->buf;
+      w->end = &w->buf[128];
+      w->x = (unsigned char *) w->ptr++;
+      w->y = (unsigned char *) w->ptr;
+    }
+
+  if (sfm_write_error (w))
+    goto error;
+  
+  return w;
+
+ error:
+  sfm_close_writer (w);
+  return NULL;
+
+ open_error:
+  msg (ME, _("Error opening \"%s\" for writing as a system file: %s."),
+       fh_get_filename (fh), strerror (errno));
+  goto error;
+}
+
+static int
+does_dict_need_translation (const struct dictionary *d)
+{
+  size_t case_idx;
+  size_t i;
+
+  case_idx = 0;
+  for (i = 0; i < dict_get_var_cnt (d); i++) 
+    {
+      struct variable *v = dict_get_var (d, i);
+      if (v->fv != case_idx)
+        return 0;
+      case_idx += v->nv;
+    }
+  return 1;
+}
+
+/* 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 system file W. */
+static void
+write_header (struct sfm_writer *w, const struct dictionary *d)
+{
+  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;
+
+  w->flt64_cnt = 0;
+  for (i = 0; i < dict_get_var_cnt (d); i++)
+    w->flt64_cnt += var_flt64_cnt (dict_get_var (d, i));
+  hdr.case_size = w->flt64_cnt;
+
+  hdr.compress = w->compress;
+
+  if (dict_get_weight (d) != NULL)
+    {
+      struct variable *weight_var;
+      int recalc_weight_idx = 1;
+      int i;
+
+      weight_var = dict_get_weight (d);
+      for (i = 0; ; i++) 
+        {
+         struct variable *v = dict_get_var (d, i);
+          if (v == weight_var)
+            break;
+         recalc_weight_idx += var_flt64_cnt (v);
+       }
+      hdr.weight_idx = recalc_weight_idx;
+    }
+  else
+    hdr.weight_idx = 0;
+
+  hdr.case_cnt = -1;
+  hdr.bias = COMPRESSION_BIAS;
+
+  if (time (&t) == (time_t) -1)
+    {
+      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);
+    }
+  
+  {
+    const char *label = dict_get_label (d);
+    if (label == NULL)
+      label = "";
+
+    buf_copy_str_rpad (hdr.file_label, sizeof hdr.file_label, label); 
+  }
+  
+  memset (hdr.padding, 0, sizeof hdr.padding);
+
+  buf_write (w, &hdr, sizeof hdr);
+}
+
+/* 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 system file W. */
+static void
+write_variable (struct sfm_writer *w, struct variable *v)
+{
+  struct sysfile_variable sv;
+
+  /* Missing values. */
+  struct missing_values mv;
+  flt64 m[3];           /* Missing value values. */
+  int nm;               /* Number of missing values, possibly negative. */
+
+  sv.rec_type = 2;
+  sv.type = v->width;
+  sv.has_var_label = (v->label != NULL);
+
+  mv_copy (&mv, &v->miss);
+  nm = 0;
+  if (mv_has_range (&mv)) 
+    {
+      double x, y;
+      mv_pop_range (&mv, &x, &y);
+      m[nm++] = x == LOWEST ? second_lowest_flt64 : x;
+      m[nm++] = y == HIGHEST ? FLT64_MAX : y;
+    }
+  while (mv_has_value (&mv))
+    {
+      union value value;
+      mv_pop_value (&mv, &value);
+      if (v->type == NUMERIC)
+        m[nm] = value.f;
+      else
+        buf_copy_rpad ((char *) &m[nm], sizeof m[nm], value.s, v->width);
+      nm++;
+    }
+  if (mv_has_range (&v->miss))
+    nm = -nm;
+
+  sv.n_missing_values = nm;
+  write_format_spec (&v->print, &sv.print);
+  write_format_spec (&v->write, &sv.write);
+  buf_copy_str_rpad (sv.name, sizeof sv.name, v->short_name);
+  buf_write (w, &sv, sizeof sv);
+
+  if (v->label)
+    {
+      struct label
+       {
+         int32 label_len P;
+         char label[255] P;
+       }
+      l;
+
+      int ext_len;
+
+      l.label_len = min (strlen (v->label), 255);
+      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);
+
+      buf_write (w, &l, offsetof (struct label, label) + ext_len);
+    }
+
+  if (nm)
+    buf_write (w, m, sizeof *m * abs (nm));
+
+  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++)
+       buf_write (w, &sv, sizeof sv);
+    }
+}
+
+/* Writes the value labels for variable V having system file
+   variable index IDX to system file W. */
+static void
+write_value_labels (struct sfm_writer *w, struct variable *v, int idx)
+{
+  struct value_label_rec
+    {
+      int32 rec_type P;
+      int32 n_labels P;
+      flt64 labels[1] P;
+    };
+
+  struct var_idx_rec
+    {
+      int32 rec_type P;
+      int32 n_vars P;
+      int32 vars[1] P;
+    };
+
+  struct val_labs_iterator *i;
+  struct value_label_rec *vlr;
+  struct var_idx_rec vir;
+  struct val_lab *vl;
+  size_t vlr_size;
+  flt64 *loc;
+
+  if (!val_labs_count (v->val_labs))
+    return;
+
+  /* Pass 1: Count bytes. */
+  vlr_size = (sizeof (struct value_label_rec)
+             + sizeof (flt64) * (val_labs_count (v->val_labs) - 1));
+  for (vl = val_labs_first (v->val_labs, &i); vl != NULL;
+       vl = val_labs_next (v->val_labs, &i))
+    vlr_size += ROUND_UP (strlen (vl->label) + 1, sizeof (flt64));
+
+  /* Pass 2: Copy bytes. */
+  vlr = xmalloc (vlr_size);
+  vlr->rec_type = 3;
+  vlr->n_labels = val_labs_count (v->val_labs);
+  loc = vlr->labels;
+  for (vl = val_labs_first_sorted (v->val_labs, &i); vl != NULL;
+       vl = val_labs_next (v->val_labs, &i))
+    {
+      size_t len = strlen (vl->label);
+
+      *loc++ = vl->value.f;
+      *(unsigned char *) loc = len;
+      memcpy (&((char *) loc)[1], vl->label, len);
+      memset (&((char *) loc)[1 + len], ' ',
+             REM_RND_UP (len + 1, sizeof (flt64)));
+      loc += DIV_RND_UP (len + 1, sizeof (flt64));
+    }
+  
+  buf_write (w, vlr, vlr_size);
+  free (vlr);
+
+  vir.rec_type = 4;
+  vir.n_vars = 1;
+  vir.vars[0] = idx + 1;
+  buf_write (w, &vir, sizeof vir);
+}
+
+/* Writes record type 6, document record. */
+static void
+write_documents (struct sfm_writer *w, const struct dictionary *d)
+{
+  struct
+    {
+      int32 rec_type P;                /* Always 6. */
+      int32 n_lines P;         /* Number of lines of documents. */
+    }
+  rec_6;
+
+  const char *documents;
+  size_t n_lines;
+
+  documents = dict_get_documents (d);
+  n_lines = strlen (documents) / 80;
+
+  rec_6.rec_type = 6;
+  rec_6.n_lines = n_lines;
+  buf_write (w, &rec_6, sizeof rec_6);
+  buf_write (w, documents, 80 * n_lines);
+}
+
+/* Write the alignment, width and scale values */
+static void
+write_variable_display_parameters (struct sfm_writer *w, 
+                                  const struct dictionary *dict)
+{
+  int i;
+
+  struct
+  {
+    int32 rec_type P;
+    int32 subtype P;
+    int32 elem_size P;
+    int32 n_elem P;
+  } vdp_hdr;
+
+  vdp_hdr.rec_type = 7;
+  vdp_hdr.subtype = 11;
+  vdp_hdr.elem_size = 4;
+  vdp_hdr.n_elem = w->var_cnt * 3;
+
+  buf_write (w, &vdp_hdr, sizeof vdp_hdr);
+
+  for ( i = 0 ; i < w->var_cnt ; ++i ) 
+    {
+      struct variable *v;
+      struct
+      {
+       int32 measure P;
+       int32 width P;
+       int32 align P;
+      }
+      params;
+
+      v = dict_get_var(dict, i);
+
+      params.measure = v->measure;
+      params.width = v->display_width;
+      params.align = v->alignment;
+      
+      buf_write (w, &params, sizeof(params));
+    }
+}
+
+/* Writes the long variable name table */
+static void
+write_longvar_table (struct sfm_writer *w, const struct dictionary *dict)
+{
+  struct
+    {
+      int32 rec_type P;
+      int32 subtype P;
+      int32 elem_size P;
+      int32 n_elem P;
+    }
+  lv_hdr;
+
+  struct string long_name_map;
+  size_t i;
+
+  ds_init (&long_name_map, 10 * dict_get_var_cnt (dict));
+  for (i = 0; i < dict_get_var_cnt (dict); i++)
+    {
+      struct variable *v = dict_get_var (dict, i);
+      
+      if (i)
+        ds_putc (&long_name_map, '\t');
+      ds_printf (&long_name_map, "%s=%s", v->short_name, v->name);
+    }
+
+  lv_hdr.rec_type = 7;
+  lv_hdr.subtype = 13;
+  lv_hdr.elem_size = 1;
+  lv_hdr.n_elem = ds_length (&long_name_map);
+
+  buf_write (w, &lv_hdr, sizeof lv_hdr);
+  buf_write (w, ds_data (&long_name_map), ds_length (&long_name_map));
+
+  ds_destroy (&long_name_map);
+}
+
+/* Writes record type 7, subtypes 3 and 4. */
+static void
+write_rec_7_34 (struct sfm_writer *w)
+{
+  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. */
+#ifdef FPREP_IEEE754
+  rec_7.elem_3[4] = 1;
+#endif
+
+  rec_7.elem_3[5] = 1;
+
+  /* PORTME: 1=big-endian, 2=little-endian. */
+#if WORDS_BIGENDIAN
+  rec_7.elem_3[6] = 1;
+#else
+  rec_7.elem_3[6] = 2;
+#endif
+
+  /* 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;
+
+  buf_write (w, &rec_7, sizeof rec_7);
+}
+
+/* Write NBYTES starting at BUF to the system file represented by
+   H. */
+static void
+buf_write (struct sfm_writer *w, const void *buf, size_t nbytes)
+{
+  assert (buf != NULL);
+  fwrite (buf, nbytes, 1, w->file);
+}
+
+/* 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 void
+ensure_buf_space (struct sfm_writer *w)
+{
+  if (w->ptr >= w->end)
+    {
+      memset (w->x, 0, w->y - w->x);
+      w->x = w->y;
+      w->ptr = w->buf;
+      buf_write (w, w->buf, sizeof *w->buf * 128);
+    }
+}
+
+static void write_compressed_data (struct sfm_writer *w, const flt64 *elem);
+
+/* Writes case C to system file W.
+   Returns 1 if successful, 0 if an I/O error occurred. */
+int
+sfm_write_case (struct sfm_writer *w, const struct ccase *c)
+{
+  if (ferror (w->file))
+    return 0;
+  
+  w->case_cnt++;
+
+  if (!w->needs_translation && !w->compress
+      && sizeof (flt64) == sizeof (union value)) 
+    {
+      /* Fast path: external and internal representations are the
+         same and the dictionary is properly ordered.  Write
+         directly to file. */
+      buf_write (w, case_data_all (c), sizeof (union value) * w->flt64_cnt);
+    }
+  else 
+    {
+      /* Slow path: internal and external representations differ.
+         Write into a bounce buffer, then write to W. */
+      flt64 *bounce;
+      flt64 *bounce_cur;
+      size_t bounce_size;
+      size_t i;
+
+      bounce_size = sizeof *bounce * w->flt64_cnt;
+      bounce = bounce_cur = local_alloc (bounce_size);
+
+      for (i = 0; i < w->var_cnt; i++) 
+        {
+          struct sfm_var *v = &w->vars[i];
+
+          if (v->width == 0) 
+            *bounce_cur = case_num (c, v->fv);
+          else 
+            memcpy (bounce_cur, case_data (c, v->fv)->s, v->width);
+          bounce_cur += v->flt64_cnt;
+        }
+
+      if (!w->compress)
+        buf_write (w, bounce, bounce_size);
+      else
+        write_compressed_data (w, bounce);
+
+      local_free (bounce); 
+    }
+  
+  return !sfm_write_error (w);
+}
+
+static void
+put_instruction (struct sfm_writer *w, unsigned char instruction) 
+{
+  if (w->x >= w->y)
+    {
+      ensure_buf_space (w);
+      w->x = (unsigned char *) w->ptr++;
+      w->y = (unsigned char *) w->ptr;
+    }
+  *w->x++ = instruction;
+}
+
+static void
+put_element (struct sfm_writer *w, const flt64 *elem) 
+{
+  ensure_buf_space (w);
+  memcpy (w->ptr++, elem, sizeof *elem);
+}
+
+static void
+write_compressed_data (struct sfm_writer *w, const flt64 *elem) 
+{
+  size_t i;
+
+  for (i = 0; i < w->var_cnt; i++)
+    {
+      struct sfm_var *v = &w->vars[i];
+
+      if (v->width == 0) 
+        {
+          if (*elem == -FLT64_MAX)
+            put_instruction (w, 255);
+          else if (*elem >= 1 - COMPRESSION_BIAS
+                   && *elem <= 251 - COMPRESSION_BIAS
+                   && *elem == (int) *elem) 
+            put_instruction (w, (int) *elem + COMPRESSION_BIAS);
+          else
+            {
+              put_instruction (w, 253);
+              put_element (w, elem);
+            }
+          elem++;
+        }
+      else 
+        {
+          size_t j;
+          
+          for (j = 0; j < v->flt64_cnt; j++, elem++) 
+            {
+              if (!memcmp (elem, "        ", sizeof (flt64)))
+                put_instruction (w, 254);
+              else 
+                {
+                  put_instruction (w, 253);
+                  put_element (w, elem);
+                }
+            }
+        }
+    }
+}
+
+/* Returns true if an I/O error has occurred on WRITER, false otherwise. */
+bool
+sfm_write_error (const struct sfm_writer *writer)
+{
+  return ferror (writer->file);
+}
+
+/* Closes a system file after we're done with it.
+   Returns true if successful, false if an I/O error occurred. */
+bool
+sfm_close_writer (struct sfm_writer *w)
+{
+  bool ok;
+  
+  if (w == NULL)
+    return true;
+
+  ok = true;
+  if (w->file != NULL) 
+    {
+      /* Flush buffer. */
+      if (w->buf != NULL && w->ptr > w->buf)
+        {
+          memset (w->x, 0, w->y - w->x);
+          buf_write (w, w->buf, (w->ptr - w->buf) * sizeof *w->buf);
+        }
+      fflush (w->file);
+
+      ok = !sfm_write_error (w);
+
+      /* Seek back to the beginning and update the number of cases.
+         This is just a courtesy to later readers, so there's no need
+         to check return values or report errors. */
+      if (ok && !fseek (w->file, offsetof (struct sysfile_header, case_cnt),
+                        SEEK_SET))
+        {
+          int32 case_cnt = w->case_cnt;
+          fwrite (&case_cnt, sizeof case_cnt, 1, w->file);
+          clearerr (w->file);
+        }
+
+      if (fclose (w->file) == EOF)
+        ok = false;
+
+      if (!ok)
+        msg (ME, _("An I/O error occurred writing system file \"%s\"."),
+             fh_get_filename (w->fh));
+    }
+
+  fh_close (w->fh, "system file", "we");
+  
+  free (w->buf);
+  free (w->vars);
+  free (w);
+
+  return ok;
+}
diff --git a/src/data/sys-file-writer.h b/src/data/sys-file-writer.h
new file mode 100644 (file)
index 0000000..537f21c
--- /dev/null
@@ -0,0 +1,46 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef SFM_WRITE_H
+#define SFM_WRITE_H 1
+
+#include <stdbool.h>
+
+/* Writing system files. */
+
+/* Options for creating a system file. */
+struct sfm_write_options 
+  {
+    bool create_writeable;      /* File perms: writeable or read/only? */
+    bool compress;              /* Compress file? */
+    int version;                /* System file version (currently 2 or 3). */
+  };
+
+struct file_handle;
+struct dictionary;
+struct ccase;
+struct sfm_writer *sfm_open_writer (struct file_handle *, struct dictionary *,
+                                    struct sfm_write_options);
+struct sfm_write_options sfm_writer_default_options (void);
+
+int sfm_write_case (struct sfm_writer *, const struct ccase *);
+bool sfm_write_error (const struct sfm_writer *);
+bool sfm_close_writer (struct sfm_writer *);
+
+#endif /* sys-file-writer.h */
diff --git a/src/data/value-labels.c b/src/data/value-labels.c
new file mode 100644 (file)
index 0000000..4638bd3
--- /dev/null
@@ -0,0 +1,518 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "value-labels.h"
+#include "message.h"
+#include <stdlib.h>
+#include "alloc.h"
+#include "hash.h"
+#include "str.h"
+
+static hsh_compare_func compare_int_val_lab;
+static hsh_hash_func hash_int_val_lab;
+static hsh_free_func free_int_val_lab;
+
+struct atom;
+static struct atom *atom_create (const char *string);
+static void atom_destroy (struct atom *);
+static char *atom_to_string (const struct atom *);
+
+/* A set of value labels. */
+struct val_labs 
+  {
+    int width;                  /* 0=numeric, otherwise string width. */
+    struct hsh_table *labels;   /* Hash table of `struct int_val_lab's. */
+  };
+
+/* Creates and returns a new, empty set of value labels with the
+   given WIDTH, which must designate a numeric (0) or short
+   string (1...MAX_SHORT_STRING inclusive) width. */
+struct val_labs *
+val_labs_create (int width) 
+{
+  struct val_labs *vls;
+
+  assert (width >= 0);
+
+  vls = xmalloc (sizeof *vls);
+  vls->width = width;
+  vls->labels = NULL;
+  return vls;
+}
+
+/* Creates and returns a new set of value labels identical to
+   VLS. */
+struct val_labs *
+val_labs_copy (const struct val_labs *vls) 
+{
+  struct val_labs *copy;
+  struct val_labs_iterator *i;
+  struct val_lab *vl;
+
+  assert (vls != NULL);
+
+  copy = val_labs_create (vls->width);
+  for (vl = val_labs_first (vls, &i); vl != NULL;
+       vl = val_labs_next (vls, &i)) 
+    val_labs_add (copy, vl->value, vl->label);
+  return copy;
+}
+
+/* Changes the width of VLS to NEW_WIDTH.  If VLS is numeric,
+   NEW_WIDTH must be 0, otherwise it must be within the range
+   1...MAX_SHORT_STRING inclusive. */
+void
+val_labs_set_width (struct val_labs *vls, int new_width) 
+{
+  assert (vls != NULL);
+  assert ((vls->width == 0) == (new_width == 0));
+
+  vls->width = new_width;
+}
+
+/* Destroys VLS. */
+void
+val_labs_destroy (struct val_labs *vls) 
+{
+  if (vls != NULL) 
+    {
+      if (vls->labels != NULL)
+        hsh_destroy (vls->labels);
+      free (vls);
+    }
+}
+
+/* Removes all the value labels from VLS. */
+void
+val_labs_clear (struct val_labs *vls) 
+{
+  assert (vls != NULL);
+
+  hsh_destroy (vls->labels);
+  vls->labels = NULL;
+}
+
+/* Returns the number of value labels in VLS. */
+size_t
+val_labs_count (const struct val_labs *vls) 
+{
+  assert (vls != NULL);
+
+  if (vls->labels == NULL)
+    return 0;
+  else
+    return hsh_count (vls->labels);
+}
+\f
+/* One value label in internal format. */
+struct int_val_lab
+  {
+    union value value;          /* The value being labeled. */
+    struct atom *label;         /* A ref-counted string. */
+  };
+
+/* Creates and returns an int_val_lab based on VALUE and
+   LABEL. */
+static struct int_val_lab *
+create_int_val_lab (struct val_labs *vls, union value value, const char *label) 
+{
+  struct int_val_lab *ivl;
+
+  assert (label != NULL);
+  assert (vls->width <= MAX_SHORT_STRING);
+  
+  ivl = xmalloc (sizeof *ivl);
+  ivl->value = value;
+  if (vls->width > 0)
+    memset (ivl->value.s + vls->width, ' ', MAX_SHORT_STRING - vls->width);
+  ivl->label = atom_create (label);
+
+  return ivl;
+}
+
+/* If VLS does not already contain a value label for VALUE, adds
+   LABEL for it and returns nonzero.  Otherwise, returns zero.
+   Behavior is undefined if VLS's width is greater than
+   MAX_SHORT_STRING. */
+int
+val_labs_add (struct val_labs *vls, union value value, const char *label) 
+{
+  struct int_val_lab *ivl;
+  void **vlpp;
+
+  assert (vls != NULL);
+  assert (vls->width <= MAX_SHORT_STRING);
+  assert (label != NULL);
+
+  if (vls->labels == NULL) 
+    vls->labels = hsh_create (8, compare_int_val_lab, hash_int_val_lab,
+                              free_int_val_lab, vls);
+
+  ivl = create_int_val_lab (vls, value, label);
+  vlpp = hsh_probe (vls->labels, ivl);
+  if (*vlpp == NULL) 
+    {
+      *vlpp = ivl;
+      return 1; 
+    }
+  else 
+    {
+      free_int_val_lab (ivl, vls);
+      return 0;
+    }
+}
+
+/* Sets LABEL as the value label for VALUE in VLS.  Returns zero
+   if there wasn't already a value label for VALUE, or nonzero if
+   there was.  Behavior is undefined if VLS's width is greater
+   than MAX_SHORT_STRING. */
+int
+val_labs_replace (struct val_labs *vls, union value value, const char *label) 
+{
+  struct int_val_lab *ivl;
+
+  assert (vls != NULL);
+  assert (vls->width <= MAX_SHORT_STRING);
+  assert (label != NULL);
+
+  if (vls->labels == NULL)
+    {
+      val_labs_add (vls, value, label);
+      return 0;
+    }
+
+  ivl = hsh_replace (vls->labels, create_int_val_lab (vls, value, label));
+  if (ivl == NULL) 
+    return 0;
+  else 
+    {
+      free_int_val_lab (ivl, vls);
+      return 1;
+    }
+}
+
+/* Removes any value label for VALUE within VLS.  Returns nonzero
+   if a value label was removed. Behavior is undefined if VLS's
+   width is greater than MAX_SHORT_STRING. */
+int 
+val_labs_remove (struct val_labs *vls, union value value) 
+{
+  assert (vls != NULL);
+  assert (vls->width <= MAX_SHORT_STRING);
+
+  if (vls->labels != NULL) 
+    {
+      struct int_val_lab *ivl = create_int_val_lab (vls, value, "");
+      int deleted = hsh_delete (vls->labels, ivl);
+      free (ivl);
+      return deleted;
+    }
+  else
+    return 0;
+}
+
+/* Searches VLS for a value label for VALUE.  If successful,
+   returns the label; otherwise, returns a null pointer.  If
+   VLS's width is greater than MAX_SHORT_STRING, always returns a
+   null pointer. */
+char *
+val_labs_find (const struct val_labs *vls, union value value) 
+{
+  assert (vls != NULL);
+
+  if (vls->width > MAX_SHORT_STRING)
+    return NULL;
+
+  if (vls->labels != NULL) 
+    {
+      struct int_val_lab ivl, *vlp;
+
+      ivl.value = value;
+      vlp = hsh_find (vls->labels, &ivl);
+      if (vlp != NULL)
+        return atom_to_string (vlp->label);
+    }
+  return NULL;
+}
+\f
+/* A value labels iterator. */
+struct val_labs_iterator 
+  {
+    void **labels;              /* The labels, in order. */
+    void **lp;                  /* Current label. */
+    struct val_lab vl;          /* Structure presented to caller. */
+  };
+
+/* Sets up *IP for iterating through the value labels in VLS in
+   no particular order.  Returns the first value label or a null
+   pointer if VLS is empty.  If the return value is non-null,
+   then val_labs_next() may be used to continue iterating or
+   val_labs_done() to free up the iterator.  Otherwise, neither
+   function may be called for *IP. */
+struct val_lab *
+val_labs_first (const struct val_labs *vls, struct val_labs_iterator **ip) 
+{
+  struct val_labs_iterator *i;
+
+  assert (vls != NULL);
+  assert (ip != NULL);
+
+  if (vls->labels == NULL || vls->width > MAX_SHORT_STRING)
+    return NULL;
+
+  i = *ip = xmalloc (sizeof *i);
+  i->labels = hsh_data_copy (vls->labels);
+  i->lp = i->labels;
+  return val_labs_next (vls, ip);
+}
+
+/* Sets up *IP for iterating through the value labels in VLS in
+   sorted order of values.  Returns the first value label or a
+   null pointer if VLS is empty.  If the return value is
+   non-null, then val_labs_next() may be used to continue
+   iterating or val_labs_done() to free up the iterator.
+   Otherwise, neither function may be called for *IP. */
+struct val_lab *
+val_labs_first_sorted (const struct val_labs *vls,
+                       struct val_labs_iterator **ip)
+{
+  struct val_labs_iterator *i;
+
+  assert (vls != NULL);
+  assert (ip != NULL);
+
+  if (vls->labels == NULL || vls->width > MAX_SHORT_STRING)
+    return NULL;
+
+  i = *ip = xmalloc (sizeof *i);
+  i->lp = i->labels = hsh_sort_copy (vls->labels);
+  return val_labs_next (vls, ip);
+}
+
+/* Returns the next value label in an iteration begun by
+   val_labs_first() or val_labs_first_sorted().  If the return
+   value is non-null, then val_labs_next() may be used to
+   continue iterating or val_labs_done() to free up the iterator.
+   Otherwise, neither function may be called for *IP. */
+struct val_lab *
+val_labs_next (const struct val_labs *vls, struct val_labs_iterator **ip)
+{
+  struct val_labs_iterator *i;
+  struct int_val_lab *ivl;
+  
+  assert (vls != NULL);
+  assert (vls->width <= MAX_SHORT_STRING);
+  assert (ip != NULL);
+  assert (*ip != NULL);
+
+  i = *ip;
+  ivl = *i->lp++;
+  if (ivl != NULL) 
+    {
+      i->vl.value = ivl->value;
+      i->vl.label = atom_to_string (ivl->label);
+      return &i->vl;
+    }
+  else 
+    {
+      free (i->labels);
+      free (i);
+      *ip = NULL;
+      return NULL;
+    }
+}
+
+/* Discards the state for an incomplete iteration begun by
+   val_labs_first() or val_labs_first_sorted(). */
+void 
+val_labs_done (struct val_labs_iterator **ip) 
+{
+  struct val_labs_iterator *i;
+
+  assert (ip != NULL);
+  assert (*ip != NULL);
+  
+  i = *ip;
+  free (i->labels);
+  free (i);
+  *ip = NULL;
+}
+\f
+/* Compares two value labels and returns a strcmp()-type result. */
+int
+compare_int_val_lab (const void *a_, const void *b_, void *vls_)
+{
+  const struct int_val_lab *a = a_;
+  const struct int_val_lab *b = b_;
+  const struct val_labs *vls = vls_;
+
+  if (vls->width == 0) 
+    return a->value.f < b->value.f ? -1 : a->value.f > b->value.f;
+  else
+    return memcmp (a->value.s, b->value.s, vls->width);
+}
+
+/* Hash a value label. */
+unsigned
+hash_int_val_lab (const void *vl_, void *vls_)
+{
+  const struct int_val_lab *vl = vl_;
+  const struct val_labs *vls = vls_;
+
+  if (vls->width == 0)
+    return hsh_hash_double (vl->value.f);
+  else
+    return hsh_hash_bytes (vl->value.s, sizeof vl->value.s);
+}
+
+/* Free a value label. */
+void
+free_int_val_lab (void *vl_, void *vls_ UNUSED) 
+{
+  struct int_val_lab *vl = vl_;
+
+  atom_destroy (vl->label);
+  free (vl);
+}
+\f
+/* Atoms. */
+
+/* An atom. */
+struct atom 
+  {
+    char *string;               /* String value. */
+    unsigned ref_count;         /* Number of references. */
+  };
+
+static hsh_compare_func compare_atoms;
+static hsh_hash_func hash_atom;
+static hsh_free_func free_atom;
+
+/* Hash table of atoms. */
+static struct hsh_table *atoms;
+
+/* Creates and returns an atom for STRING. */
+static struct atom *
+atom_create (const char *string) 
+{
+  struct atom a;
+  void **app;
+  
+  assert (string != NULL);
+          
+  if (atoms == NULL) 
+    atoms = hsh_create (8, compare_atoms, hash_atom, free_atom, NULL);
+
+  a.string = (char *) string;
+  app = hsh_probe (atoms, &a);
+  if (*app != NULL) 
+    {
+      struct atom *ap = *app;
+      ap->ref_count++;
+      return ap;
+    }
+  else
+    {
+      struct atom *ap = xmalloc (sizeof *ap);
+      ap->string = xstrdup (string);
+      ap->ref_count = 1;
+      *app = ap;
+      return ap;
+    }
+}
+
+/* Destroys ATOM. */
+static void 
+atom_destroy (struct atom *atom)
+{
+  if (atom != NULL) 
+    {
+      assert (atom->ref_count > 0);
+      atom->ref_count--;
+      if (atom->ref_count == 0) 
+        hsh_force_delete (atoms, atom);
+    }
+}
+
+/* Returns the string associated with ATOM. */
+static  char *
+atom_to_string (const struct atom *atom) 
+{
+  assert (atom != NULL);
+  
+  return atom->string;
+}
+
+/* A hsh_compare_func that compares A and B. */
+static int
+compare_atoms (const void *a_, const void *b_, void *aux UNUSED) 
+{
+  const struct atom *a = a_;
+  const struct atom *b = b_;
+
+  return strcmp (a->string, b->string);
+}
+
+/* A hsh_hash_func that hashes ATOM. */
+static unsigned
+hash_atom (const void *atom_, void *aux UNUSED) 
+{
+  const struct atom *atom = atom_;
+
+  return hsh_hash_string (atom->string);
+}
+
+/* A hsh_free_func that destroys ATOM. */
+static void
+free_atom (void *atom_, void *aux UNUSED) 
+{
+  struct atom *atom = atom_;
+
+  free (atom->string);
+  free (atom);
+}
+
+
+/* Get a string representing the value.
+   That is, if it has a label, then return that label,
+   otherwise, if the value is alpha, then return the string for it,
+   else format it and return the formatted string
+*/
+const char *
+value_to_string (const union value *val, const struct variable *var)
+{
+  char *s;
+  
+  assert (val != NULL);
+  assert (var != NULL);
+
+  s = val_labs_find (var->val_labs, *val);
+  if (s == NULL) 
+    {
+      static char buf[256];
+      if (var->width != 0) 
+        str_copy_buf_trunc (buf, sizeof buf, val->s, var->width);
+      else
+        snprintf(buf, 100, "%g", val->f);
+      s = buf;
+    }
+  
+  return s;
+}
diff --git a/src/data/value-labels.h b/src/data/value-labels.h
new file mode 100644 (file)
index 0000000..ad21a6d
--- /dev/null
@@ -0,0 +1,62 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef VAL_LABS_H
+#define VAL_LABS_H 1
+
+#include <stddef.h>
+#include "variable.h"
+
+struct val_labs;
+
+struct val_lab 
+  {
+    union value value;
+    const char *label;
+  };
+
+struct val_labs *val_labs_create (int width);
+struct val_labs *val_labs_copy (const struct val_labs *);
+void val_labs_set_width (struct val_labs *, int new_width);
+void val_labs_destroy (struct val_labs *);
+void val_labs_clear (struct val_labs *);
+size_t val_labs_count (const struct val_labs *);
+
+int val_labs_add (struct val_labs *, union value, const char *);
+int val_labs_replace (struct val_labs *, union value, const char *);
+int val_labs_remove (struct val_labs *, union value);
+char *val_labs_find (const struct val_labs *, union value);
+
+struct val_labs_iterator;
+
+struct val_lab *val_labs_first (const struct val_labs *,
+                                struct val_labs_iterator **);
+struct val_lab *val_labs_first_sorted (const struct val_labs *,
+                                       struct val_labs_iterator **);
+struct val_lab *val_labs_next (const struct val_labs *,
+                               struct val_labs_iterator **);
+void val_labs_done (struct val_labs_iterator **);
+
+/* Return a string representing this value, in the form most 
+   appropriate from a human factors perspective.
+   (IE: the label if it has one, otherwise the alpha/numeric )
+*/
+const char *value_to_string(const union value *, const struct variable *);
+
+#endif /* value-labels.h */
diff --git a/src/data/value.h b/src/data/value.h
new file mode 100644 (file)
index 0000000..1c2a19c
--- /dev/null
@@ -0,0 +1,78 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !value_h
+#define value_h 1
+
+#include <float.h>
+
+#include <config.h>
+
+/* Values. */
+
+/* Max length of a short string value, generally 8 chars. */
+#define MAX_SHORT_STRING ((SIZEOF_DOUBLE)>=8 ? ((SIZEOF_DOUBLE)+1)/2*2 : 8)
+#define MIN_LONG_STRING (MAX_SHORT_STRING+1)
+
+/* Max string length. */
+#define MAX_STRING 255
+
+/* 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 > SHORT_NAME_LEN
+#error MAX_SHORT_STRING must be less than or equal to SHORT_NAME_LEN.
+#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. */
+    char s[MAX_SHORT_STRING];
+
+    /* 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. */
+    char *c;
+  };
+
+/* Maximum number of `union value's in a single number or string
+   value. */
+#define MAX_ELEMS_PER_VALUE (MAX_STRING / sizeof (union value) + 1)
+
+int compare_values (const union value *a, const union value *b, int width);
+
+unsigned  hash_value(const union value  *v, int width);
+
+
+
+#endif /* !value.h */
diff --git a/src/data/variable.c b/src/data/variable.c
new file mode 100644 (file)
index 0000000..2436399
--- /dev/null
@@ -0,0 +1,319 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "variable.h"
+#include "message.h"
+#include <stdlib.h>
+#include "alloc.h"
+#include "dictionary.h"
+#include "hash.h"
+#include "identifier.h"
+#include "misc.h"
+#include "str.h"
+#include "value-labels.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* Returns an adjective describing the given variable TYPE,
+   suitable for use in phrases like "numeric variable". */
+const char *
+var_type_adj (enum var_type type) 
+{
+  return type == NUMERIC ? _("numeric") : _("string");
+}
+
+/* Returns a noun describing a value of the given variable TYPE,
+   suitable for use in phrases like "a number". */
+const char *
+var_type_noun (enum var_type type) 
+{
+  return type == NUMERIC ? _("number") : _("string");
+}
+
+/* Assign auxiliary data AUX to variable V, which must not
+   already have auxiliary data.  Before V's auxiliary data is
+   cleared, AUX_DTOR(V) will be called. */
+void *
+var_attach_aux (struct variable *v,
+                void *aux, void (*aux_dtor) (struct variable *)) 
+{
+  assert (v->aux == NULL);
+  assert (aux != NULL);
+  v->aux = aux;
+  v->aux_dtor = aux_dtor;
+  return aux;
+}
+
+/* Remove auxiliary data, if any, from V, and returns it, without
+   calling any associated destructor. */
+void *
+var_detach_aux (struct variable *v) 
+{
+  void *aux = v->aux;
+  assert (aux != NULL);
+  v->aux = NULL;
+  return aux;
+}
+
+/* Clears auxiliary data, if any, from V, and calls any
+   associated destructor. */
+void
+var_clear_aux (struct variable *v) 
+{
+  assert (v != NULL);
+  if (v->aux != NULL) 
+    {
+      if (v->aux_dtor != NULL)
+        v->aux_dtor (v);
+      v->aux = NULL;
+    }
+}
+
+/* This function is appropriate for use an auxiliary data
+   destructor (passed as AUX_DTOR to var_attach_aux()) for the
+   case where the auxiliary data should be passed to free(). */
+void
+var_dtor_free (struct variable *v) 
+{
+  free (v->aux);
+}
+
+/* Compares A and B, which both have the given WIDTH, and returns
+   a strcmp()-type result. */
+int
+compare_values (const union value *a, const union value *b, int width) 
+{
+  if (width == 0) 
+    return a->f < b->f ? -1 : a->f > b->f;
+  else
+    return memcmp (a->s, b->s, min(MAX_SHORT_STRING, width));
+}
+
+/* Create a hash of v */
+unsigned 
+hash_value(const union value  *v, int width)
+{
+  unsigned id_hash;
+
+  if ( 0 == width ) 
+    id_hash = hsh_hash_double (v->f);
+  else
+    id_hash = hsh_hash_bytes (v->s, min(MAX_SHORT_STRING, width));
+
+  return id_hash;
+}
+
+
+
+\f
+/* Returns true if NAME is an acceptable name for a variable,
+   false otherwise.  If ISSUE_ERROR is true, issues an
+   explanatory error message on failure. */
+bool
+var_is_valid_name (const char *name, bool issue_error) 
+{
+  size_t length, i;
+  
+  assert (name != NULL);
+
+  length = strlen (name);
+  if (length < 1) 
+    {
+      if (issue_error)
+        msg (SE, _("Variable name cannot be empty string."));
+      return false;
+    }
+  else if (length > LONG_NAME_LEN) 
+    {
+      if (issue_error)
+        msg (SE, _("Variable name %s exceeds %d-character limit."),
+             name, (int) LONG_NAME_LEN);
+      return false;
+    }
+
+  for (i = 0; i < length; i++)
+    if (!lex_is_idn (name[i])) 
+      {
+        if (issue_error)
+          msg (SE, _("Character `%c' (in %s) may not appear in "
+                     "a variable name."),
+               name[i], name);
+        return false;
+      }
+        
+  if (!lex_is_id1 (name[0]))
+    {
+      if (issue_error)
+        msg (SE, _("Character `%c' (in %s), may not appear "
+                   "as the first character in a variable name."),
+             name[0], name);
+      return false;
+    }
+
+  if (lex_id_to_token (name, strlen (name)) != T_ID) 
+    {
+      if (issue_error)
+        msg (SE, _("`%s' may not be used as a variable name because it "
+                   "is a reserved word."), name);
+      return false;
+    }
+
+  return true;
+}
+
+/* A hsh_compare_func that orders variables A and B by their
+   names. */
+int
+compare_var_names (const void *a_, const void *b_, void *foo UNUSED) 
+{
+  const struct variable *a = a_;
+  const struct variable *b = b_;
+
+  return strcasecmp (a->name, b->name);
+}
+
+/* A hsh_hash_func that hashes variable V based on its name. */
+unsigned
+hash_var_name (const void *v_, void *foo UNUSED) 
+{
+  const struct variable *v = v_;
+
+  return hsh_hash_case_string (v->name);
+}
+
+/* A hsh_compare_func that orders pointers to variables A and B
+   by their names. */
+int
+compare_var_ptr_names (const void *a_, const void *b_, void *foo UNUSED) 
+{
+  struct variable *const *a = a_;
+  struct variable *const *b = b_;
+
+  return strcasecmp ((*a)->name, (*b)->name);
+}
+
+/* A hsh_hash_func that hashes pointer to variable V based on its
+   name. */
+unsigned
+hash_var_ptr_name (const void *v_, void *foo UNUSED) 
+{
+  struct variable *const *v = v_;
+
+  return hsh_hash_case_string ((*v)->name);
+}
+\f
+/* Sets V's short_name to SHORT_NAME, truncating it to
+   SHORT_NAME_LEN characters and converting it to uppercase in
+   the process. */
+void
+var_set_short_name (struct variable *v, const char *short_name) 
+{
+  assert (v != NULL);
+  assert (short_name[0] == '\0' || var_is_valid_name (short_name, false));
+  
+  str_copy_trunc (v->short_name, sizeof v->short_name, short_name);
+  str_uppercase (v->short_name);
+}
+
+/* Clears V's short name. */
+void
+var_clear_short_name (struct variable *v) 
+{
+  assert (v != NULL);
+
+  v->short_name[0] = '\0';
+}
+
+/* Sets V's short name to BASE, followed by a suffix of the form
+   _A, _B, _C, ..., _AA, _AB, etc. according to the value of
+   SUFFIX.  Truncates BASE as necessary to fit. */
+void
+var_set_short_name_suffix (struct variable *v, const char *base, int suffix)
+{
+  char string[SHORT_NAME_LEN + 1];
+  char *start, *end;
+  int len, ofs;
+
+  assert (v != NULL);
+  assert (suffix >= 0);
+  assert (strlen (v->short_name) > 0);
+
+  /* Set base name. */
+  var_set_short_name (v, base);
+
+  /* Compose suffix_string. */
+  start = end = string + sizeof string - 1;
+  *end = '\0';
+  do 
+    {
+      *--start = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[suffix % 26];
+      if (start <= string + 1)
+        msg (SE, _("Variable suffix too large."));
+      suffix /= 26;
+    }
+  while (suffix > 0);
+  *--start = '_';
+
+  /* Append suffix_string to V's short name. */
+  len = end - start;
+  if (len + strlen (v->short_name) > SHORT_NAME_LEN)
+    ofs = SHORT_NAME_LEN - len;
+  else
+    ofs = strlen (v->short_name);
+  strcpy (v->short_name + ofs, start);
+}
+
+
+/* Returns the dictionary class corresponding to a variable named
+   NAME. */
+enum dict_class
+dict_class_from_id (const char *name) 
+{
+  assert (name != NULL);
+
+  switch (name[0]) 
+    {
+    default:
+      return DC_ORDINARY;
+    case '$':
+      return DC_SYSTEM;
+    case '#':
+      return DC_SCRATCH;
+    }
+}
+
+/* Returns the name of dictionary class DICT_CLASS. */
+const char *
+dict_class_to_name (enum dict_class dict_class) 
+{
+  switch (dict_class) 
+    {
+    case DC_ORDINARY:
+      return _("ordinary");
+    case DC_SYSTEM:
+      return _("system");
+    case DC_SCRATCH:
+      return _("scratch");
+    default:
+      assert (0);
+      abort ();
+    }
+}
diff --git a/src/data/variable.h b/src/data/variable.h
new file mode 100644 (file)
index 0000000..4a22d24
--- /dev/null
@@ -0,0 +1,237 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !variable_h
+#define variable_h 1
+
+
+#include <stddef.h>
+#include "config.h"
+#include <stdbool.h>
+#include "category.h"
+#include "format.h"
+#include "missing-values.h"
+
+/* Script variables. */
+
+/* Variable type. */
+enum var_type
+  {
+    NUMERIC,                   /* A numeric variable. */
+    ALPHA                      /* A string variable. */
+  };
+
+const char *var_type_adj (enum var_type);
+const char *var_type_noun (enum var_type);
+
+/* A variable's dictionary entry.  */
+struct variable
+  {
+    /* Dictionary information. */
+    char name[LONG_NAME_LEN + 1]; /* Variable name.  Mixed case. */
+    enum var_type type;         /* NUMERIC or ALPHA. */
+    int width;                 /* Size of string variables in chars. */
+    struct missing_values miss; /* Missing values. */
+    struct fmt_spec print;     /* Default format for PRINT. */
+    struct fmt_spec write;     /* Default format for WRITE. */
+    struct val_labs *val_labs;  /* Value labels. */
+    char *label;               /* Variable label. */
+    enum measure measure;       /* Nominal, ordinal, or continuous. */
+    int display_width;          /* Width of data editor column. */
+    enum alignment alignment;   /* Alignment of data in GUI. */
+
+    /* Case information. */
+    int fv, nv;                        /* Index into `value's, number of values. */
+    bool init;                  /* True if needs init and possibly reinit. */
+    bool reinit;                /* True: reinitialize; false: leave. */
+
+    /* Data for use by containing dictionary. */
+    int index;                 /* Dictionary index. */
+
+    /* Short name, used only for system and portable file input
+       and output.  Upper case only.  There is no index for short
+       names.  Short names are not necessarily unique.  Any
+       variable may have no short name, indicated by an empty
+       string. */
+    char short_name[SHORT_NAME_LEN + 1];
+
+    /* Each command may use these fields as needed. */
+    void *aux;
+    void (*aux_dtor) (struct variable *);
+
+    /* Values of a categorical variable.  Procedures need
+       vectors with binary entries, so any variable of type ALPHA will
+       have its values stored here. */
+    struct cat_vals *obs_vals;
+  };
+
+/* Variable names. */
+bool var_is_valid_name (const char *, bool issue_error);
+int compare_var_names (const void *, const void *, void *);
+unsigned hash_var_name (const void *, void *);
+
+/* Short names. */
+void var_set_short_name (struct variable *, const char *);
+void var_set_short_name_suffix (struct variable *, const char *, int suffix);
+void var_clear_short_name (struct variable *);
+
+/* Pointers to `struct variable', by name. */
+int compare_var_ptr_names (const void *, const void *, void *);
+unsigned hash_var_ptr_name (const void *, void *);
+
+/* Variable auxiliary data. */
+void *var_attach_aux (struct variable *,
+                      void *aux, void (*aux_dtor) (struct variable *));
+void var_clear_aux (struct variable *);
+void *var_detach_aux (struct variable *);
+void var_dtor_free (struct variable *);
+
+/* Classes of variables. */
+enum dict_class 
+  {
+    DC_ORDINARY,                /* Ordinary identifier. */
+    DC_SYSTEM,                  /* System variable. */
+    DC_SCRATCH                  /* Scratch variable. */
+  };
+
+enum dict_class dict_class_from_id (const char *name);
+const char *dict_class_to_name (enum dict_class dict_class);
+\f
+/* Vector of variables. */
+struct vector
+  {
+    int idx;                    /* Index for dict_get_vector(). */
+    char name[LONG_NAME_LEN + 1]; /* Name. */
+    struct variable **var;     /* Vector of variables. */
+    int cnt;                   /* Number of variables. */
+  };
+\f
+void discard_variables (void);
+
+/* This is the active file dictionary. */
+extern struct dictionary *default_dict;
+\f
+/* Transformation state. */
+
+/* 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 size_t temp_trns;
+
+void cancel_temporary (void);
+\f
+struct ccase;
+void dump_split_vars (const struct ccase *);
+\f
+/* Transformations. */
+
+/* trns_proc_func return values. */
+#define TRNS_CONTINUE   -1 /* Continue to next transformation. */
+#define TRNS_DROP_CASE  -2 /* Drop this case. */
+#define TRNS_ERROR      -3 /* A serious error, so stop the procedure. */
+#define TRNS_NEXT_CASE  -4 /* Skip to next case.  INPUT PROGRAM only. */
+#define TRNS_END_FILE   -5 /* End of input.  INPUT PROGRAM only. */
+
+typedef int trns_proc_func (void *, struct ccase *, int);
+typedef bool trns_free_func (void *);
+
+/* A transformation. */
+struct transformation
+  {
+    trns_proc_func *proc;       /* Transformation proc. */
+    trns_free_func *free;       /* Garbage collector proc. */
+    void *private;              /* Private data. */
+  };
+
+/* Array of transformations */
+extern struct transformation *t_trns;
+
+/* Number of transformations, maximum number in array currently. */
+extern size_t n_trns, m_trns;
+
+/* Index of first transformation that is really a transformation.  Any
+   transformations before this belong to INPUT PROGRAM. */
+extern size_t f_trns;
+
+void add_transformation (trns_proc_func *, trns_free_func *, void *);
+size_t next_transformation (void);
+bool cancel_transformations (void);
+\f
+struct var_set;
+
+struct var_set *var_set_create_from_dict (const struct dictionary *d);
+struct var_set *var_set_create_from_array (struct variable *const *var,
+                                           size_t);
+
+size_t var_set_get_cnt (const struct var_set *vs);
+struct variable *var_set_get_var (const struct var_set *vs, size_t idx);
+struct variable *var_set_lookup_var (const struct var_set *vs,
+                                     const char *name);
+bool var_set_lookup_var_idx (const struct var_set *vs, const char *name,
+                             size_t *idx);
+void var_set_destroy (struct var_set *vs);
+\f
+/* Variable parsers. */
+
+enum
+  {
+    PV_NONE = 0,               /* No options. */
+    PV_SINGLE = 0001,          /* Restrict to a single name 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 pool;
+struct variable *parse_variable (void);
+struct variable *parse_dict_variable (const struct dictionary *);
+int parse_variables (const struct dictionary *, struct variable ***, size_t *,
+                     int opts);
+int parse_var_set_vars (const struct var_set *, struct variable ***, size_t *,
+                        int opts);
+int parse_DATA_LIST_vars (char ***names, size_t *cnt, int opts);
+int parse_mixed_vars (char ***names, size_t *cnt, int opts);
+int parse_mixed_vars_pool (struct pool *,
+                           char ***names, size_t *cnt, int opts);
+
+
+/* Return a string representing this variable, in the form most 
+   appropriate from a human factors perspective.
+   (IE: the label if it has one, otherwise the name )
+*/
+const char * var_to_string(const struct variable *var);
+
+
+#endif /* !variable.h */
diff --git a/src/language/ChangeLog b/src/language/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/language/command.c b/src/language/command.c
new file mode 100644 (file)
index 0000000..950d801
--- /dev/null
@@ -0,0 +1,825 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include "command.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include "alloc.h"
+#include "dictionary.h"
+#include "message.h"
+#include "lexer.h"
+#include "settings.h"
+#include "manager.h"
+#include "str.h"
+#include "table.h"
+#include "variable.h"
+#include "procedure.h"
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+\f
+/* Global variables. */
+
+/* A STATE_* constant giving the current program state. */
+int pgm_state;
+\f
+/* Static variables. */
+
+/* A single command. */
+struct command
+  {
+    const char *name;          /* Command name. */
+    int transition[4];         /* Transitions to make from each state. */
+    int (*func) (void);                /* Function to call. */
+    int skip_entire_name;       /* If zero, we don't skip the
+                                   final token in the command name. */
+    short debug;                /* Set if this cmd available only in test mode*/
+  };
+
+/* Define the command array. */
+#define DEFCMD(NAME, T1, T2, T3, T4, FUNC)             \
+       {NAME, {T1, T2, T3, T4}, FUNC, 1, 0},
+#define DBGCMD(NAME, T1, T2, T3, T4, FUNC)             \
+       {NAME, {T1, T2, T3, T4}, FUNC, 1, 1},
+#define SPCCMD(NAME, T1, T2, T3, T4, FUNC)             \
+       {NAME, {T1, T2, T3, T4}, FUNC, 0, 0},
+#define UNIMPL(NAME, T1, T2, T3, T4, DESC)             \
+       {NAME, {T1, T2, T3, T4}, NULL, 1, 0},
+static const struct command commands[] = 
+  {
+#include "command.def"
+  };
+#undef DEFCMD
+#undef DBGCMD
+#undef UNIMPL
+
+
+/* Complete the line using the name of a command, 
+ * based upon the current prg_state
+ */
+char * 
+pspp_completion_function (const char *text,   int state)
+{
+  static int skip=0;
+  const struct command *cmd = 0;
+  
+  for(;;)
+    {
+      if ( state + skip >= sizeof(commands)/ sizeof(struct command))
+       {
+         skip = 0;
+         return 0;
+       }
+
+      cmd = &commands[state + skip];
+  
+      if ( cmd->transition[pgm_state] == STATE_ERROR || ( cmd->debug  &&  ! get_testing_mode () ) ) 
+       {
+         skip++; 
+         continue;
+       }
+      
+      if ( text == 0 || 0 == strncasecmp (cmd->name, text, strlen(text)))
+       {
+         break;
+       }
+
+      skip++;
+    }
+  
+
+  return xstrdup(cmd->name);
+}
+
+
+
+#define COMMAND_CNT (sizeof commands / sizeof *commands)
+\f
+/* Command parser. */
+
+static const struct command *parse_command_name (void);
+
+/* Determines whether command C is appropriate to call in this
+   part of a FILE TYPE structure. */
+static int
+FILE_TYPE_okay (const struct command *c UNUSED)
+#if 0
+{
+  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->name);
+  /* 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->name);
+  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."));
+  else
+    okay = 1;
+
+  if (c->func == cmd_record_type)
+    fty.had_rec_type = 1;
+
+  return okay;
+}
+#else
+{
+  return 1;
+}
+#endif
+
+/* 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)
+{
+  const 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 && !strcasecmp (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)
+    {
+      lex_error (_("expecting command name"));
+      return CMD_FAILURE;
+    }
+
+  /* Parse the command name. */
+  cp = parse_command_name ();
+  if (cp == NULL)
+    return CMD_FAILURE;
+  if (cp->func == NULL)
+    {
+      msg (SE, _("%s is not yet implemented."), cp->name);
+      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
+      && case_source_is_class (vfm_source, &file_type_source_class)
+      && !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->name);
+      return CMD_FAILURE;
+    }
+
+  /* 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. */
+    err_set_command_name (cp->name);
+    tab_set_command_name (cp->name);
+    result = cp->func ();
+    err_set_command_name (NULL);
+    tab_set_command_name (NULL);
+    
+    /* Perform the state transition if the command completed
+       successfully (at least in part). */
+    if (result != CMD_FAILURE && result != CMD_CASCADING_FAILURE)
+      {
+       pgm_state = cp->transition[pgm_state];
+
+       if (pgm_state == STATE_ERROR)
+         {
+           discard_variables ();
+           pgm_state = STATE_INIT;
+         }
+      }
+
+    /* Pass the command's success value up to the caller. */
+    return result;
+  }
+}
+
+static size_t
+match_strings (const char *a, size_t a_len,
+               const char *b, size_t b_len) 
+{
+  size_t match_len = 0;
+  
+  while (a_len > 0 && b_len > 0) 
+    {
+      /* Mismatch always returns zero. */
+      if (toupper ((unsigned char) *a++) != toupper ((unsigned char) *b++))
+        return 0;
+
+      /* Advance. */
+      a_len--;
+      b_len--;
+      match_len++;
+    }
+
+  return match_len;
+}
+
+/* Returns the first character in the first word in STRING,
+   storing the word's length in *WORD_LEN.  If no words remain,
+   returns a null pointer and stores 0 in *WORD_LEN.  Words are
+   sequences of alphanumeric characters or single
+   non-alphanumeric characters.  Words are delimited by
+   spaces. */
+static const char *
+find_word (const char *string, size_t *word_len) 
+{
+  /* Skip whitespace and asterisks. */
+  while (isspace ((unsigned char) *string))
+    string++;
+
+  /* End of string? */
+  if (*string == '\0') 
+    {
+      *word_len = 0;
+      return NULL;
+    }
+
+  /* Special one-character word? */
+  if (!isalnum ((unsigned char) *string)) 
+    {
+      *word_len = 1;
+      return string;
+    }
+
+  /* Alphanumeric word. */
+  *word_len = 1;
+  while (isalnum ((unsigned char) string[*word_len]))
+    (*word_len)++;
+
+  return string;
+}
+
+/* Returns nonzero if strings A and B can be confused based on
+   their first three letters. */
+static int
+conflicting_3char_prefixes (const char *a, const char *b) 
+{
+  size_t aw_len, bw_len;
+  const char *aw, *bw;
+
+  aw = find_word (a, &aw_len);
+  bw = find_word (b, &bw_len);
+  assert (aw != NULL && bw != NULL);
+
+  /* Words that are the same don't conflict. */
+  if (aw_len == bw_len && !buf_compare_case (aw, bw, aw_len))
+    return 0;
+  
+  /* Words that are otherwise the same in the first three letters
+     do conflict. */
+  return ((aw_len > 3 && bw_len > 3)
+          || (aw_len == 3 && bw_len > 3)
+          || (bw_len == 3 && aw_len > 3)) && !buf_compare_case (aw, bw, 3);
+}
+
+/* Returns nonzero if CMD can be confused with another command
+   based on the first three letters of its first word. */
+static int
+conflicting_3char_prefix_command (const struct command *cmd) 
+{
+  assert (cmd >= commands && cmd < commands + COMMAND_CNT);
+
+  return ((cmd > commands
+           && conflicting_3char_prefixes (cmd[-1].name, cmd[0].name))
+          || (cmd < commands + COMMAND_CNT
+              && conflicting_3char_prefixes (cmd[0].name, cmd[1].name)));
+}
+
+/* Ways that a set of words can match a command name. */
+enum command_match
+  {
+    MISMATCH,           /* Not a match. */
+    PARTIAL_MATCH,      /* The words begin the command name. */
+    COMPLETE_MATCH      /* The words are the command name. */
+  };
+
+/* Figures out how well the WORD_CNT words in WORDS match CMD,
+   and returns the appropriate enum value.  If WORDS are a
+   partial match for CMD and the next word in CMD is a dash, then
+   *DASH_POSSIBLE is set to 1 if DASH_POSSIBLE is non-null;
+   otherwise, *DASH_POSSIBLE is unchanged. */
+static enum command_match
+cmd_match_words (const struct command *cmd,
+                 char *const words[], size_t word_cnt,
+                 int *dash_possible)
+{
+  const char *word;
+  size_t word_len;
+  size_t word_idx;
+
+  for (word = find_word (cmd->name, &word_len), word_idx = 0;
+       word != NULL && word_idx < word_cnt;
+       word = find_word (word + word_len, &word_len), word_idx++)
+    if (word_len != strlen (words[word_idx])
+        || buf_compare_case (word, words[word_idx], word_len))
+      {
+        size_t match_chars = match_strings (word, word_len,
+                                            words[word_idx],
+                                            strlen (words[word_idx]));
+        if (match_chars == 0) 
+          {
+            /* Mismatch. */
+            return MISMATCH;
+          }
+        else if (match_chars == 1 || match_chars == 2) 
+          {
+            /* One- and two-character abbreviations are not
+               acceptable. */
+            return MISMATCH; 
+          }
+        else if (match_chars == 3) 
+          {
+            /* Three-character abbreviations are acceptable
+               in the first word of a command if there are
+               no name conflicts.  They are always
+               acceptable after the first word. */
+            if (word_idx == 0 && conflicting_3char_prefix_command (cmd))
+              return MISMATCH;
+          }
+        else /* match_chars > 3 */ 
+          {
+            /* Four-character and longer abbreviations are
+               always acceptable.  */
+          }
+      }
+
+  if (word == NULL && word_idx == word_cnt) 
+    {
+      /* cmd->name = "FOO BAR", words[] = {"FOO", "BAR"}. */
+      return COMPLETE_MATCH;
+    }
+  else if (word == NULL) 
+    {
+      /* cmd->name = "FOO BAR", words[] = {"FOO", "BAR", "BAZ"}. */
+      return MISMATCH; 
+    }
+  else 
+    {
+      /* cmd->name = "FOO BAR BAZ", words[] = {"FOO", "BAR"}. */
+      if (word[0] == '-' && dash_possible != NULL)
+        *dash_possible = 1;
+      return PARTIAL_MATCH; 
+    }
+}
+
+/* Returns the number of commands for which the WORD_CNT words in
+   WORDS are a partial or complete match.  If some partial match
+   has a dash as the next word, then *DASH_POSSIBLE is set to 1,
+   otherwise it is set to 0. */
+static int
+count_matching_commands (char *const words[], size_t word_cnt,
+                         int *dash_possible) 
+{
+  const struct command *cmd;
+  int cmd_match_count;
+
+  cmd_match_count = 0;
+  *dash_possible = 0;
+  for (cmd = commands; cmd < commands + COMMAND_CNT; cmd++) 
+    if (cmd_match_words (cmd, words, word_cnt, dash_possible) != MISMATCH) 
+      cmd_match_count++; 
+
+  return cmd_match_count;
+}
+
+/* Returns the command for which the WORD_CNT words in WORDS are
+   a complete match.  Returns a null pointer if no such command
+   exists. */
+static const struct command *
+get_complete_match (char *const words[], size_t word_cnt) 
+{
+  const struct command *cmd;
+  
+  for (cmd = commands; cmd < commands + COMMAND_CNT; cmd++) 
+    if (cmd_match_words (cmd, words, word_cnt, NULL) == COMPLETE_MATCH) 
+      return cmd; 
+  
+  return NULL;
+}
+
+/* Frees the WORD_CNT words in WORDS. */
+static void
+free_words (char *words[], size_t word_cnt) 
+{
+  size_t idx;
+  
+  for (idx = 0; idx < word_cnt; idx++)
+    free (words[idx]);
+}
+
+/* Flags an error that the command whose name is given by the
+   WORD_CNT words in WORDS is unknown. */
+static void
+unknown_command_error (char *const words[], size_t word_cnt) 
+{
+  size_t idx;
+  size_t words_len;
+  char *name, *cp;
+
+  words_len = 0;
+  for (idx = 0; idx < word_cnt; idx++)
+    words_len += strlen (words[idx]);
+
+  cp = name = xmalloc (words_len + word_cnt + 16);
+  for (idx = 0; idx < word_cnt; idx++) 
+    {
+      if (idx != 0)
+        *cp++ = ' ';
+      cp = stpcpy (cp, words[idx]);
+    }
+  *cp = '\0';
+
+  msg (SE, _("Unknown command %s."), name);
+
+  free (name);
+}
+
+
+/* Parse the command name and return a pointer to the corresponding
+   struct command if successful.
+   If not successful, return a null pointer. */
+static const struct command *
+parse_command_name (void)
+{
+  char *words[16];
+  int word_cnt;
+  int complete_word_cnt;
+  int dash_possible;
+
+  dash_possible = 0;
+  word_cnt = complete_word_cnt = 0;
+  while (token == T_ID || (dash_possible && token == '-')) 
+    {
+      int cmd_match_cnt;
+      
+      assert (word_cnt < sizeof words / sizeof *words);
+      if (token == T_ID)
+        words[word_cnt] = xstrdup (ds_c_str (&tokstr));
+      else
+        words[word_cnt] = xstrdup ("-");
+      str_uppercase (words[word_cnt]);
+      word_cnt++;
+
+      cmd_match_cnt = count_matching_commands (words, word_cnt,
+                                               &dash_possible);
+      if (cmd_match_cnt == 0) 
+        break;
+      else if (cmd_match_cnt == 1) 
+        {
+          const struct command *command = get_complete_match (words, word_cnt);
+          if (command != NULL) 
+            {
+              if (command->skip_entire_name)
+                lex_get ();
+             if ( command->debug & !get_testing_mode () ) 
+               goto error;
+              free_words (words, word_cnt);
+              return command;
+            }
+        }
+      else /* cmd_match_cnt > 1 */
+        {
+          /* Do we have a complete command name so far? */
+          if (get_complete_match (words, word_cnt) != NULL)
+            complete_word_cnt = word_cnt;
+        }
+      lex_get ();
+    }
+
+  /* If we saw a complete command name earlier, drop back to
+     it. */
+  if (complete_word_cnt) 
+    {
+      int pushback_word_cnt;
+      const struct command *command;
+
+      /* Get the command. */
+      command = get_complete_match (words, complete_word_cnt);
+      assert (command != NULL);
+
+      /* Figure out how many words we want to keep.
+         We normally want to swallow the entire command. */
+      pushback_word_cnt = complete_word_cnt + 1;
+      if (!command->skip_entire_name)
+        pushback_word_cnt--;
+      
+      /* FIXME: We only support one-token pushback. */
+      assert (pushback_word_cnt + 1 >= word_cnt);
+
+      while (word_cnt > pushback_word_cnt) 
+        {
+          word_cnt--;
+          if (strcmp (words[word_cnt], "-")) 
+            lex_put_back_id (words[word_cnt]);
+          else
+            lex_put_back ('-');
+          free (words[word_cnt]);
+        }
+
+      if ( command->debug && !get_testing_mode () ) 
+       goto error;
+
+      free_words (words, word_cnt);
+      return command;
+    }
+
+error:
+  unknown_command_error (words, word_cnt);
+  free_words (words, word_cnt);
+  return NULL;
+}
+\f
+/* Simple commands. */
+
+/* Parse and execute FINISH command. */
+int
+cmd_finish (void)
+{
+  return CMD_EOF;
+}
+
+/* Parses the N command. */
+int
+cmd_n_of_cases (void)
+{
+  /* Value for N. */
+  int x;
+
+  if (!lex_force_int ())
+    return CMD_FAILURE;
+  x = lex_integer ();
+  lex_get ();
+  if (!lex_match_id ("ESTIMATED"))
+    dict_set_case_limit (default_dict, x);
+
+  return lex_end_of_command ();
+}
+
+/* Parses, performs the EXECUTE procedure. */
+int
+cmd_execute (void)
+{
+  if (!procedure (NULL, NULL))
+    return CMD_CASCADING_FAILURE;
+  return lex_end_of_command ();
+}
+
+/* Parses, performs the ERASE command. */
+int
+cmd_erase (void)
+{
+  if (get_safer_mode ()) 
+    { 
+      msg (SE, _("This command not allowed when the SAFER option is set.")); 
+      return CMD_FAILURE; 
+    } 
+  
+  if (!lex_force_match_id ("FILE"))
+    return CMD_FAILURE;
+  lex_match ('=');
+  if (!lex_force_string ())
+    return CMD_FAILURE;
+
+  if (remove (ds_c_str (&tokstr)) == -1)
+    {
+      msg (SW, _("Error removing `%s': %s."),
+          ds_c_str (&tokstr), strerror (errno));
+      return CMD_FAILURE;
+    }
+
+  return CMD_SUCCESS;
+}
+
+#ifdef 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);
+
+       _exit (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)
+{
+  const 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_c_str (&tokstr);
+       string = 1;
+      }
+    else
+      {
+       cmd = lex_rest_of_line (NULL);
+        lex_discard_line ();
+       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 (get_safer_mode ()) 
+    { 
+      msg (SE, _("This command not allowed when the SAFER option is set.")); 
+      return CMD_FAILURE; 
+    } 
+
+#ifdef 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)
+    code = run_command ();
+  else
+    {
+      msg (SE, _("No operating system support for this command."));
+      code = CMD_FAILURE;
+    }
+#endif /* !unix */
+
+  return code;
+}
+
+/* Parses, performs the NEW FILE command. */
+int
+cmd_new_file (void)
+{
+  discard_variables ();
+
+  return lex_end_of_command ();
+}
+
+/* Parses, performs the CLEAR TRANSFORMATIONS command. */
+int
+cmd_clear_transformations (void)
+{
+  cancel_transformations ();
+  /* FIXME: what about variables created by transformations?
+     They need to be properly initialized. */
+
+  return CMD_SUCCESS;
+}
diff --git a/src/language/command.def b/src/language/command.def
new file mode 100644 (file)
index 0000000..35440c0
--- /dev/null
@@ -0,0 +1,193 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+/* State abbreviations. */
+#define INIT STATE_INIT
+#define INPU STATE_INPUT
+#define TRAN STATE_TRANS
+#define PROC STATE_PROC
+#define ERRO STATE_ERROR
+
+UNIMPL ("ACF",                   ERRO, ERRO, ERRO, ERRO, "Autocorrelation function")
+UNIMPL ("ADD FILES",             ERRO, ERRO, ERRO, ERRO, "Add files to dictionary")
+DEFCMD ("ADD VALUE LABELS",       ERRO, INPU, TRAN, TRAN, cmd_add_value_labels)
+DEFCMD ("AGGREGATE",              ERRO, ERRO, PROC, TRAN, cmd_aggregate)
+UNIMPL ("ALSCAL",                ERRO, ERRO, ERRO, ERRO, "Multidimensional scaling")
+UNIMPL ("ANOVA",                 ERRO, ERRO, ERRO, ERRO, "Factorial analysis of variance")
+DEFCMD ("APPLY DICTIONARY",       ERRO, ERRO, TRAN, TRAN, cmd_apply_dictionary)
+DEFCMD ("AUTORECODE",             ERRO, ERRO, PROC, PROC, cmd_autorecode)
+SPCCMD ("BEGIN DATA",             ERRO, ERRO, PROC, PROC, cmd_begin_data)
+DEFCMD ("BREAK",                  ERRO, INPU, TRAN, TRAN, cmd_break)
+UNIMPL ("CASEPLOT",              ERRO, ERRO, ERRO, ERRO, "Plot time series")
+UNIMPL ("CASESTOVARS",           ERRO, ERRO, ERRO, ERRO, "Restructure complex data")
+UNIMPL ("CCF",                   ERRO, ERRO, ERRO, ERRO, "Time series cross correlation")
+DEFCMD ("CLEAR TRANSFORMATIONS",  ERRO, INPU, TRAN, TRAN, cmd_clear_transformations)
+DEFCMD ("CLOSE FILE HANDLE",      INIT, INPU, TRAN, PROC, cmd_close_file_handle)
+UNIMPL ("CLUSTER",               ERRO, ERRO, ERRO, ERRO, "Hierachial clustering")
+DEFCMD ("COMPUTE",                ERRO, INPU, TRAN, TRAN, cmd_compute)
+UNIMPL ("CONJOINT",              ERRO, ERRO, ERRO, ERRO, "Analyse full concept data")
+DEFCMD ("CORRELATIONS",                  ERRO, ERRO, PROC, PROC, cmd_correlations)
+DEFCMD ("COUNT",                  ERRO, INPU, TRAN, TRAN, cmd_count)
+UNIMPL ("COXREG",                 ERRO, ERRO, ERRO, ERRO, "Cox proportional hazards regression")
+UNIMPL ("CREATE",                 ERRO, ERRO, ERRO, ERRO, "Create time series data")
+DEFCMD ("CROSSTABS",              ERRO, ERRO, PROC, PROC, cmd_crosstabs)
+UNIMPL ("CURVEFIT",               ERRO, ERRO, ERRO, ERRO, "Fit curve to line plot")
+DEFCMD ("DATA LIST",              TRAN, INPU, TRAN, TRAN, cmd_data_list)
+UNIMPL ("DATE",                   ERRO, ERRO, ERRO, ERRO, "Create time series data")
+DBGCMD ("DEBUG CASEFILE",        INIT, INPU, TRAN, PROC, cmd_debug_casefile)
+DBGCMD ("DEBUG EVALUATE",        INIT, INPU, TRAN, PROC, cmd_debug_evaluate)
+DBGCMD ("DEBUG MOMENTS",         INIT, INPU, TRAN, PROC, cmd_debug_moments)
+DBGCMD ("DEBUG POOL",            INIT, INPU, TRAN, PROC, cmd_debug_pool)
+DEFCMD ("DESCRIPTIVES",           ERRO, ERRO, PROC, PROC, cmd_descriptives)
+UNIMPL ("DISCRIMINANT",           ERRO, ERRO, ERRO, ERRO, "Linear discriminant analysis")
+DEFCMD ("DISPLAY",                ERRO, INPU, TRAN, PROC, cmd_display)
+SPCCMD ("DOCUMENT",               ERRO, INPU, TRAN, TRAN, cmd_document)
+DEFCMD ("DO IF",                  ERRO, INPU, TRAN, TRAN, cmd_do_if)
+DEFCMD ("DO REPEAT",              ERRO, INPU, TRAN, TRAN, cmd_do_repeat)
+DEFCMD ("DROP DOCUMENTS",         INIT, INPU, TRAN, PROC, cmd_drop_documents)
+DEFCMD ("ECHO",                   INIT, INPU, TRAN, PROC, cmd_echo)
+UNIMPL ("EDIT",                   ERRO, ERRO, ERRO, ERRO, "obsolete")
+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)
+UNIMPL ("END FILE TYPE",          ERRO, TRAN, ERRO, ERRO, "Ends complex data input")
+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)
+DEFCMD ("EXAMINE",                ERRO, ERRO, PROC, PROC, cmd_examine)
+DEFCMD ("EXECUTE",                ERRO, ERRO, PROC, PROC, cmd_execute)
+DEFCMD ("EXIT",                   INIT, INPU, TRAN, PROC, cmd_finish)
+DEFCMD ("EXPORT",                 ERRO, ERRO, PROC, PROC, cmd_export)
+UNIMPL ("FACTOR",                 ERRO, ERRO, ERRO, ERRO, "Factor analysis")
+DEFCMD ("FILE HANDLE",            INIT, INPU, TRAN, PROC, cmd_file_handle)
+SPCCMD ("FILE LABEL",             INIT, INPU, TRAN, PROC, cmd_file_label)
+UNIMPL ("FILE TYPE",              INPU, ERRO, INPU, INPU, "Complex data input")
+DEFCMD ("FILTER",                 ERRO, ERRO, TRAN, TRAN, cmd_filter)
+DEFCMD ("FINISH",                 INIT, INPU, TRAN, PROC, cmd_finish)
+UNIMPL ("FIT",                    ERRO, ERRO, ERRO, ERRO, "Goodness of Fit")
+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)
+UNIMPL ("GET TRANSLATE",          ERRO, ERRO, ERRO, ERRO, "Read other file formats")
+UNIMPL ("GLM",                    ERRO, ERRO, ERRO, ERRO, "General Linear Model")
+UNIMPL ("GRAPH",                  ERRO, ERRO, ERRO, ERRO, "Draw graphs")
+DEFCMD ("HOST",                   INIT, INPU, TRAN, PROC, cmd_host)
+DEFCMD ("IF",                     ERRO, INPU, TRAN, TRAN, cmd_if)
+UNIMPL ("IGRAPH",                 ERRO, ERRO, ERRO, ERRO, "Interactive graphs")
+DEFCMD ("IMPORT",                 TRAN, ERRO, TRAN, TRAN, cmd_import)
+DEFCMD ("INCLUDE",                INIT, INPU, TRAN, PROC, cmd_include)
+UNIMPL ("INFO",                   ERRO, ERRO, ERRO, ERRO, "Local Documentation")
+DEFCMD ("INPUT PROGRAM",          INPU, ERRO, INPU, INPU, cmd_input_program)
+UNIMPL ("KEYED DATA LIST",        ERRO, ERRO, ERRO, ERRO, "Read nonsequential data")
+UNIMPL ("KM",                     ERRO, ERRO, ERRO, ERRO, "Kaplan-Meier")
+DEFCMD ("LEAVE",                  ERRO, INPU, TRAN, TRAN, cmd_leave)
+DEFCMD ("LIST",                   ERRO, ERRO, PROC, PROC, cmd_list)
+UNIMPL ("LOGISTIC REGRESSION",    ERRO, ERRO, ERRO, ERRO, "Regression Analysis")
+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)
+UNIMPL ("MCONVERT",               ERRO, ERRO, ERRO, ERRO, "Convert covariance/correlation matrices")
+DEFCMD ("MEANS",                  ERRO, ERRO, PROC, PROC, cmd_means)
+DEFCMD ("MISSING VALUES",         ERRO, INPU, TRAN, TRAN, cmd_missing_values)
+DEFCMD ("MODIFY VARS",            ERRO, ERRO, TRAN, PROC, cmd_modify_vars)
+UNIMPL ("MULT RESPONSE",          ERRO, ERRO, ERRO, ERRO, "Multiple reponse analysis")
+UNIMPL ("MVA",                    ERRO, ERRO, ERRO, ERRO, "Missing value analysis")
+DEFCMD ("NEW FILE",               INIT, ERRO, INIT, INIT, cmd_new_file)
+DEFCMD ("N",                      INIT, INPU, TRAN, TRAN, cmd_n_of_cases)
+DEFCMD ("N OF CASES",             INIT, INPU, TRAN, TRAN, cmd_n_of_cases)
+UNIMPL ("NLR",                    ERRO, ERRO, ERRO, ERRO, "Non Linear Regression")
+UNIMPL ("NONPAR CORR",            ERRO, ERRO, ERRO, ERRO, "Nonparametric correlation")
+UNIMPL ("NPAR TESTS",             ERRO, ERRO, ERRO, ERRO, "Nonparametric tests")
+UNIMPL ("NUMBERED",               ERRO, ERRO, ERRO, ERRO, "")
+DEFCMD ("NUMERIC",                ERRO, INPU, TRAN, TRAN, cmd_numeric)
+DEFCMD ("ONEWAY",                 ERRO, ERRO, PROC, PROC, cmd_oneway)
+UNIMPL ("PACF",                   ERRO, ERRO, ERRO, ERRO, "Partial autocorrelation")
+UNIMPL ("PARTIAL CORR",           ERRO, ERRO, ERRO, ERRO, "Partial correlation")
+DEFCMD ("PEARSON CORRELATIONS",          ERRO, ERRO, PROC, PROC, cmd_correlations)
+DEFCMD ("PERMISSIONS",            INIT, INPU, TRAN, PROC, cmd_permissions)
+UNIMPL ("POINT",                  ERRO, ERRO, ERRO, ERRO, "Marker in keyed file")
+UNIMPL ("PPLOT",                  ERRO, ERRO, ERRO, ERRO, "Plot time series variables")
+UNIMPL ("PREDICT",                ERRO, ERRO, ERRO, ERRO, "Specify forecast period")
+UNIMPL ("PRESERVE",              ERRO, ERRO, ERRO, ERRO, "Push settings")
+DEFCMD ("PRINT EJECT",            ERRO, INPU, TRAN, TRAN, cmd_print_eject)
+DEFCMD ("PRINT",                  ERRO, INPU, TRAN, TRAN, cmd_print)
+DEFCMD ("PRINT FORMATS",          ERRO, INPU, TRAN, TRAN, cmd_print_formats)
+DEFCMD ("PRINT SPACE",            ERRO, INPU, TRAN, TRAN, cmd_print_space)
+UNIMPL ("PROCEDURE OUTPUT",       ERRO, ERRO, ERRO, ERRO, "Specify output file")
+UNIMPL ("PROBIT",                 ERRO, ERRO, ERRO, ERRO, "Probit analysis")
+DEFCMD ("PROCESS IF",             ERRO, ERRO, TRAN, TRAN, cmd_process_if)
+UNIMPL ("PROXIMITIES",            ERRO, ERRO, ERRO, ERRO, "Pairwise similarity")
+DEFCMD ("Q",                      INIT, INPU, TRAN, PROC, cmd_finish)
+UNIMPL ("QUICK CLUSTER",          ERRO, ERRO, ERRO, ERRO, "Fast clustering")
+DEFCMD ("QUIT",                   INIT, INPU, TRAN, PROC, cmd_finish)
+UNIMPL ("RANK",                   ERRO, ERRO, ERRO, ERRO, "Create rank scores")
+DEFCMD ("RECODE",                 ERRO, INPU, TRAN, TRAN, cmd_recode)
+DEFCMD ("RECORD TYPE",            ERRO, INPU, ERRO, ERRO, cmd_record_type)
+UNIMPL ("REFORMAT",               ERRO, ERRO, ERRO, ERRO, "Read obsolete files")
+DEFCMD ("REGRESSION",             ERRO, ERRO, PROC, PROC, cmd_regression)
+DEFCMD ("RENAME VARIABLES",       ERRO, INPU, TRAN, PROC, cmd_rename_variables)
+UNIMPL ("REPEATING DATA",         ERRO, INPU, ERRO, ERRO, "Specify multiple cases per input record")
+UNIMPL ("REPORT",                 ERRO, ERRO, ERRO, ERRO, "Pretty print working file")
+DEFCMD ("REREAD",                 ERRO, INPU, ERRO, ERRO, cmd_reread)
+UNIMPL ("RESTORE",               ERRO, ERRO, ERRO, ERRO, "Restore settings")
+UNIMPL ("ROC",                   ERRO, ERRO, ERRO, ERRO, "Receiver operating characteristic")
+UNIMPL ("RMV",                    ERRO, ERRO, ERRO, ERRO, "Replace missing values")
+DEFCMD ("SAMPLE",                 ERRO, ERRO, TRAN, TRAN, cmd_sample)
+DEFCMD ("SAVE",                   ERRO, ERRO, PROC, PROC, cmd_save)
+UNIMPL ("SAVE TRANSLATE",         ERRO, ERRO, ERRO, ERRO, "Save to foriegn format")
+UNIMPL ("SCRIPT",                 ERRO, ERRO, ERRO, ERRO, "Run script file")
+DEFCMD ("SELECT IF",              ERRO, ERRO, TRAN, TRAN, cmd_select_if)
+DEFCMD ("SET",                    INIT, INPU, TRAN, PROC, cmd_set)
+DEFCMD ("SHOW",                   INIT, INPU, TRAN, PROC, cmd_show)
+DEFCMD ("SORT CASES",             ERRO, ERRO, PROC, PROC, cmd_sort_cases)
+DEFCMD ("SORT",                   ERRO, ERRO, PROC, PROC, cmd_sort_cases)
+UNIMPL ("SPCHART",                ERRO, ERRO, ERRO, ERRO, "Plot control charts")
+DEFCMD ("SPLIT FILE",             ERRO, INPU, TRAN, TRAN, cmd_split_file)
+DEFCMD ("STRING",                 ERRO, INPU, TRAN, TRAN, cmd_string)
+SPCCMD ("SUBTITLE",               INIT, INPU, TRAN, PROC, cmd_subtitle)
+UNIMPL ("SUMMARIZE",              ERRO, ERRO, ERRO, ERRO, "Univariate statistics")
+UNIMPL ("SURVIVAL",               ERRO, ERRO, ERRO, ERRO, "Survival analysis")
+DEFCMD ("SYSFILE INFO",           INIT, INPU, TRAN, PROC, cmd_sysfile_info)
+DEFCMD ("TEMPORARY",              ERRO, ERRO, TRAN, TRAN, cmd_temporary)
+SPCCMD ("TITLE",                  INIT, INPU, TRAN, PROC, cmd_title)
+UNIMPL ("TSET",                   ERRO, ERRO, ERRO, ERRO, "Set time sequence variables")
+UNIMPL ("TSHOW",                  ERRO, ERRO, ERRO, ERRO, "Show time sequence variables")
+UNIMPL ("TSPLOT",                 ERRO, ERRO, ERRO, ERRO, "Plot time sequence variables")
+DEFCMD ("T-TEST",                 ERRO, ERRO, PROC, PROC, cmd_t_test)
+UNIMPL ("UNIANOVA",               ERRO, ERRO, ERRO, ERRO, "Univariate analysis")
+UNIMPL ("UNNUMBERED",             ERRO, ERRO, ERRO, ERRO, "obsolete")
+UNIMPL ("UPDATE",                 ERRO, ERRO, ERRO, ERRO, "Update working file")
+DEFCMD ("USE",                   ERRO, ERRO, TRAN, TRAN, cmd_use)
+DEFCMD ("VALUE LABELS",           ERRO, INPU, TRAN, TRAN, cmd_value_labels)
+DEFCMD ("VARIABLE LABELS",        ERRO, INPU, TRAN, TRAN, cmd_variable_labels)
+DEFCMD ("VARIABLE ALIGNMENT",     ERRO, INPU, TRAN, TRAN, cmd_variable_alignment)
+DEFCMD ("VARIABLE LEVEL",         ERRO, INPU, TRAN, TRAN, cmd_variable_level)
+DEFCMD ("VARIABLE WIDTH",         ERRO, INPU, TRAN, TRAN, cmd_variable_width)
+UNIMPL ("VARSTOCASES",           ERRO, ERRO, ERRO, ERRO, "Restructure complex data")
+DEFCMD ("VECTOR",                 ERRO, INPU, TRAN, TRAN, cmd_vector)
+UNIMPL ("VERIFY",                 ERRO, ERRO, ERRO, ERRO, "Report time series")
+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 ("XEXPORT",                ERRO, INPU, TRAN, TRAN, cmd_xexport)
+DEFCMD ("XSAVE",                  ERRO, INPU, TRAN, TRAN, cmd_xsave)
diff --git a/src/language/command.h b/src/language/command.h
new file mode 100644 (file)
index 0000000..ba14c0b
--- /dev/null
@@ -0,0 +1,68 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, 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
+  {
+    /* Successful return values. */
+    CMD_SUCCESS = 0x1000,       /* Successfully parsed and executed. */
+    CMD_EOF,                    /* Requested exit. */
+
+    /* Various kinds of failures, in increasing order of severity. */
+    CMD_TRAILING_GARBAGE,      /* Followed by garbage. */
+    CMD_PART_SUCCESS,          /* Fully executed up to error. */
+    CMD_PART_SUCCESS_MAYBE,    /* May have been partially executed. */
+    CMD_FAILURE,                /* Not executed at all. */
+    CMD_CASCADING_FAILURE       /* Serious error: don't continue. */
+  };
+
+extern int pgm_state;
+
+char *pspp_completion_function (const char *text,   int state);
+
+int cmd_parse (void);
+
+/* Prototype all the command functions. */
+#define DEFCMD(NAME, T1, T2, T3, T4, FUNC)     \
+       int FUNC (void);
+#define SPCCMD(NAME, T1, T2, T3, T4, FUNC)     \
+       int FUNC (void);
+#define DBGCMD(NAME, T1, T2, T3, T4, FUNC)     \
+       int FUNC (void);
+#define UNIMPL(NAME, T1, T2, T3, T4, DESC)
+#include "command.def"
+#undef DEFCMD
+#undef SPCCMD
+#undef UNIMPL
+#undef DBGCMD
+
+#endif /* !command_h */
diff --git a/src/language/control/ChangeLog b/src/language/control/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/language/control/control-stack.c b/src/language/control/control-stack.c
new file mode 100644 (file)
index 0000000..9f1502c
--- /dev/null
@@ -0,0 +1,93 @@
+#include <config.h>
+#include "control-stack.h"
+#include <assert.h>
+#include <stdlib.h>
+#include "message.h"
+#include "xalloc.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+struct ctl_struct
+  {
+    struct ctl_class *class;    /* Class of control structure. */
+    struct ctl_struct *down;   /* Points toward the bottom of ctl_stack. */
+    void *private;              /* Private data. */
+  };
+
+struct ctl_struct *ctl_stack;
+
+void
+ctl_stack_clear (void) 
+{
+  while (ctl_stack != NULL) 
+    {
+      struct ctl_struct *top = ctl_stack;
+      msg (SE, _("%s without %s."),
+           top->class->start_name, top->class->end_name);
+      ctl_stack_pop (top->private);
+    }
+}
+
+void
+ctl_stack_push (struct ctl_class *class, void *private) 
+{
+  struct ctl_struct *ctl;
+
+  assert (private != NULL);
+  ctl = xmalloc (sizeof *ctl);
+  ctl->class = class;
+  ctl->down = ctl_stack;
+  ctl->private = private;
+  ctl_stack = ctl;
+}
+
+void *
+ctl_stack_top (struct ctl_class *class) 
+{
+  struct ctl_struct *top = ctl_stack;
+  if (top != NULL && top->class == class)
+    return top->private;
+  else 
+    {
+      if (ctl_stack_search (class) != NULL)
+        msg (SE, _("This command must appear inside %s...%s, "
+                   "without intermediate %s...%s."),
+             class->start_name, class->end_name,
+             top->class->start_name, top->class->end_name);
+      return NULL; 
+    }
+}
+
+void *
+ctl_stack_search (struct ctl_class *class) 
+{
+  struct ctl_struct *ctl;
+  
+  for (ctl = ctl_stack; ctl != NULL; ctl = ctl->down)
+    if (ctl->class == class)
+      return ctl->private;
+
+  msg (SE, _("This command cannot appear outside %s...%s."),
+       class->start_name, class->end_name);
+  return NULL;
+}
+
+void
+ctl_stack_pop (void *private UNUSED) 
+{
+  struct ctl_struct *top = ctl_stack;
+  
+  assert (top != NULL);
+  assert (top->private == private);
+
+  top->class->close (top->private);
+  ctl_stack = top->down;
+  free (top);
+}
+
+bool
+ctl_stack_is_empty (void) 
+{
+  return ctl_stack == NULL;
+}
diff --git a/src/language/control/control-stack.h b/src/language/control/control-stack.h
new file mode 100644 (file)
index 0000000..87ef4be
--- /dev/null
@@ -0,0 +1,39 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef CTL_STACK_H
+#define CTL_STACK_H 1
+
+#include <stdbool.h>
+
+struct ctl_class 
+  {
+    const char *start_name;     /* e.g. LOOP. */
+    const char *end_name;       /* e.g. END LOOP. */
+    void (*close) (void *);     /* Closes the control structure. */
+  };
+
+void ctl_stack_clear (void);
+void ctl_stack_push (struct ctl_class *, void *private);
+void *ctl_stack_top (struct ctl_class *);
+void *ctl_stack_search (struct ctl_class *);
+void ctl_stack_pop (void *);
+bool ctl_stack_is_empty (void);
+
+#endif /* ctl_stack.h */
diff --git a/src/language/control/do-if.c b/src/language/control/do-if.c
new file mode 100644 (file)
index 0000000..35e5959
--- /dev/null
@@ -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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "control-stack.h"
+#include "message.h"
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "message.h"
+#include "expressions/public.h"
+#include "lexer.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* DO IF, ELSE IF, and ELSE are translated as a single
+   transformation that evaluates each condition and jumps to the
+   start of the appropriate block of transformations.  Each block
+   of transformations (except for the last) ends with a
+   transformation that jumps past the remaining blocks.
+
+   So, the following code:
+
+       DO IF a.             
+       ...block 1...
+       ELSE IF b.
+       ...block 2...
+       ELSE.
+       ...block 3...
+       END IF.
+
+   is effectively translated like this:
+
+       IF a GOTO 1, IF b GOTO 2, ELSE GOTO 3.
+       1: ...block 1...
+          GOTO 4
+       2: ...block 2...
+          GOTO 4
+       3: ...block 3...
+       4:
+
+*/
+
+/* A conditional clause. */
+struct clause 
+  {
+    struct expression *condition; /* Test expression; NULL for ELSE clause. */
+    int target_index;           /* Transformation to jump to if true. */
+  };
+
+/* DO IF transformation. */
+struct do_if_trns
+  {
+    struct clause *clauses;     /* Clauses. */
+    size_t clause_cnt;          /* Number of clauses. */
+    int past_END_IF_index;      /* Transformation just past last clause. */
+  };
+
+static struct ctl_class do_if_class;
+
+static int parse_clause (struct do_if_trns *);
+static void add_clause (struct do_if_trns *,
+                        struct expression *condition, int target_index);
+static void add_else (struct do_if_trns *);
+
+static bool has_else (struct do_if_trns *);
+static bool must_not_have_else (struct do_if_trns *);
+static void close_do_if (void *do_if);
+
+static trns_proc_func do_if_trns_proc, break_trns_proc;
+static trns_free_func do_if_trns_free;
+
+/* Parse DO IF. */
+int
+cmd_do_if (void)
+{
+  struct do_if_trns *do_if = xmalloc (sizeof *do_if);
+  do_if->clauses = NULL;
+  do_if->clause_cnt = 0;
+
+  ctl_stack_push (&do_if_class, do_if);
+  add_transformation (do_if_trns_proc, do_if_trns_free, do_if);
+
+  return parse_clause (do_if);
+}
+
+/* Parse ELSE IF. */
+int
+cmd_else_if (void)
+{
+  struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
+  if (do_if == NULL || !must_not_have_else (do_if))
+    return CMD_CASCADING_FAILURE;
+  return parse_clause (do_if);
+}
+
+/* Parse ELSE. */
+int
+cmd_else (void)
+{
+  struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
+  if (do_if == NULL || !must_not_have_else (do_if))
+    return CMD_CASCADING_FAILURE;
+  add_else (do_if);
+  return lex_end_of_command ();
+}
+
+/* Parse END IF. */
+int
+cmd_end_if (void)
+{
+  struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
+  if (do_if == NULL)
+    return CMD_CASCADING_FAILURE;
+
+  ctl_stack_pop (do_if);
+
+  return lex_end_of_command ();
+}
+
+/* Closes out DO_IF, by adding a sentinel ELSE clause if
+   necessary and setting past_END_IF_index. */
+static void
+close_do_if (void *do_if_) 
+{
+  struct do_if_trns *do_if = do_if_;
+  
+  if (!has_else (do_if)) 
+    add_else (do_if);
+  do_if->past_END_IF_index = next_transformation ();
+}
+
+/* Adds an ELSE clause to DO_IF pointing to the next
+   transformation. */
+static void
+add_else (struct do_if_trns *do_if) 
+{
+  assert (!has_else (do_if));
+  add_clause (do_if, NULL, next_transformation ());
+}
+
+/* Returns true if DO_IF does not yet have an ELSE clause.
+   Reports an error and returns false if it does already. */
+static bool
+must_not_have_else (struct do_if_trns *do_if) 
+{
+  if (has_else (do_if))
+    {
+      msg (SE, _("This command may not follow ELSE in DO IF...END IF."));
+      return false;
+    }
+  else
+    return true;
+}
+
+/* Returns true if DO_IF already has an ELSE clause,
+   false otherwise. */
+static bool
+has_else (struct do_if_trns *do_if) 
+{
+  return (do_if->clause_cnt != 0
+          && do_if->clauses[do_if->clause_cnt - 1].condition == NULL);
+}
+
+/* Parses a DO IF or ELSE IF expression and appends the
+   corresponding clause to DO_IF.  Checks for end of command and
+   returns a command return code. */
+static int
+parse_clause (struct do_if_trns *do_if)
+{
+  struct expression *condition;
+
+  condition = expr_parse (default_dict, EXPR_BOOLEAN);
+  if (condition == NULL)
+    return CMD_CASCADING_FAILURE;
+
+  add_clause (do_if, condition, next_transformation ());
+
+  return lex_end_of_command ();
+}
+
+/* Adds a clause to DO_IF that tests for the given CONDITION and,
+   if true, jumps to TARGET_INDEX. */
+static void
+add_clause (struct do_if_trns *do_if,
+            struct expression *condition, int target_index) 
+{
+  struct clause *clause;
+
+  if (do_if->clause_cnt > 0)
+    add_transformation (break_trns_proc, NULL, do_if);
+
+  do_if->clauses = xnrealloc (do_if->clauses,
+                              do_if->clause_cnt + 1, sizeof *do_if->clauses);
+  clause = &do_if->clauses[do_if->clause_cnt++];
+  clause->condition = condition;
+  clause->target_index = target_index;
+}
+
+/* DO IF transformation procedure.
+   Checks each clause and jumps to the appropriate
+   transformation. */
+static int 
+do_if_trns_proc (void *do_if_, struct ccase *c, int case_num UNUSED)
+{
+  struct do_if_trns *do_if = do_if_;
+  struct clause *clause;
+
+  for (clause = do_if->clauses; clause < do_if->clauses + do_if->clause_cnt;
+       clause++) 
+    {
+      if (clause->condition != NULL)
+        {
+          double boolean = expr_evaluate_num (clause->condition, c, case_num);
+          if (boolean == 1.0)
+            return clause->target_index;
+          else if (boolean == SYSMIS)
+            return do_if->past_END_IF_index;
+        }
+      else 
+        return clause->target_index;
+    }
+  return do_if->past_END_IF_index;
+}
+
+/* Frees a DO IF transformation. */
+static bool
+do_if_trns_free (void *do_if_)
+{
+  struct do_if_trns *do_if = do_if_;
+  struct clause *clause;
+
+  for (clause = do_if->clauses; clause < do_if->clauses + do_if->clause_cnt;
+       clause++)
+    expr_free (clause->condition);
+  free (do_if->clauses);
+  free (do_if);
+  return true;
+}
+
+/* Breaks out of a DO IF construct. */
+static int 
+break_trns_proc (void *do_if_, struct ccase *c UNUSED, int case_num UNUSED)
+{
+  struct do_if_trns *do_if = do_if_;
+
+  return do_if->past_END_IF_index;
+}
+
+/* DO IF control structure class definition. */
+static struct ctl_class do_if_class = 
+  {
+    "DO IF",
+    "END IF",
+    close_do_if,
+  };
diff --git a/src/language/control/loop.c b/src/language/control/loop.c
new file mode 100644 (file)
index 0000000..a4d0e49
--- /dev/null
@@ -0,0 +1,363 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include "alloc.h"
+#include "case.h"
+#include "command.h"
+#include "dictionary.h"
+#include "control-stack.h"
+#include "message.h"
+#include "expressions/public.h"
+#include "lexer.h"
+#include "misc.h"
+#include "pool.h"
+#include "settings.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* LOOP outputs a transformation that is executed only on the
+   first pass through the loop.  On this trip, it initializes for
+   the first pass by resetting the pass number, setting up the
+   indexing clause, and testing the LOOP IF clause.  If the loop
+   is not to be entered at all, it jumps forward just past the
+   END LOOP transformation; otherwise, it continues to the
+   transformation following LOOP.
+
+   END LOOP outputs a transformation that executes at the end of
+   each trip through the loop.  It checks the END LOOP IF clause,
+   then updates the pass number, increments the indexing clause,
+   and tests the LOOP IF clause.  If another pass through the
+   loop is due, it jumps backward to just after the LOOP
+   transformation; otherwise, it continues to the transformation
+   following END LOOP. */
+
+struct loop_trns
+  {
+    struct pool *pool;
+
+    /* Iteration limit. */
+    int max_pass_count;         /* Maximum number of passes (-1=unlimited). */
+    int pass;                  /* Number of passes thru the loop so far. */
+
+    /* a=a TO b [BY c]. */
+    struct variable *index_var; /* Index variable. */
+    struct expression *first_expr; /* Starting index. */
+    struct expression *by_expr;        /* Index increment (default 1.0 if null). */
+    struct expression *last_expr; /* Terminal index. */
+    double cur, by, last;       /* Current value, increment, last value. */
+
+    /* IF condition for LOOP or END LOOP. */
+    struct expression *loop_condition;
+    struct expression *end_loop_condition;
+
+    /* Transformation indexes. */
+    int past_LOOP_index;        /* Just past LOOP transformation. */
+    int past_END_LOOP_index;    /* Just past END LOOP transformation. */
+  };
+
+static struct ctl_class loop_class;
+
+static trns_proc_func loop_trns_proc, end_loop_trns_proc, break_trns_proc;
+static trns_free_func loop_trns_free;
+
+static struct loop_trns *create_loop_trns (void);
+static bool parse_if_clause (struct loop_trns *, struct expression **);
+static bool parse_index_clause (struct loop_trns *, char index_var_name[]);
+static void close_loop (void *);
+\f
+/* LOOP. */
+
+/* Parses LOOP. */
+int
+cmd_loop (void)
+{
+  struct loop_trns *loop;
+  char index_var_name[LONG_NAME_LEN + 1];
+  bool ok = true;
+
+  loop = create_loop_trns ();
+  while (token != '.' && ok) 
+    {
+      if (lex_match_id ("IF")) 
+        ok = parse_if_clause (loop, &loop->loop_condition);
+      else
+        ok = parse_index_clause (loop, index_var_name);
+    }
+
+  /* Find index variable and create if necessary. */
+  if (ok && index_var_name[0] != '\0')
+    {
+      loop->index_var = dict_lookup_var (default_dict, index_var_name);
+      if (loop->index_var == NULL)
+        loop->index_var = dict_create_var (default_dict, index_var_name, 0);
+    }
+  
+  if (!ok)
+    loop->max_pass_count = 0;
+  return ok ? CMD_SUCCESS : CMD_PART_SUCCESS;
+}
+
+/* Parses END LOOP. */
+int
+cmd_end_loop (void)
+{
+  struct loop_trns *loop;
+  bool ok = true;
+
+  loop = ctl_stack_top (&loop_class);
+  if (loop == NULL)
+    return CMD_CASCADING_FAILURE;
+  
+  /* Parse syntax. */
+  if (lex_match_id ("IF"))
+    ok = parse_if_clause (loop, &loop->end_loop_condition);
+  if (ok)
+    ok = lex_end_of_command () == CMD_SUCCESS;
+
+  if (!ok)
+    loop->max_pass_count = 0;
+
+  ctl_stack_pop (loop);
+  
+  return ok ? CMD_SUCCESS : CMD_PART_SUCCESS;
+}
+
+/* Parses BREAK. */
+int
+cmd_break (void)
+{
+  struct ctl_stmt *loop = ctl_stack_search (&loop_class);
+  if (loop == NULL)
+    return CMD_CASCADING_FAILURE;
+
+  add_transformation (break_trns_proc, NULL, loop);
+
+  return lex_end_of_command ();
+}
+
+/* Closes a LOOP construct by emitting the END LOOP
+   transformation and finalizing its members appropriately. */
+static void
+close_loop (void *loop_)
+{
+  struct loop_trns *loop = loop_;
+  
+  add_transformation (end_loop_trns_proc, NULL, loop);
+  loop->past_END_LOOP_index = next_transformation ();
+
+  /* If there's nothing else limiting the number of loops, use
+     MXLOOPS as a limit. */
+  if (loop->max_pass_count == -1
+      && loop->index_var == NULL
+      && loop->loop_condition == NULL
+      && loop->end_loop_condition == NULL)
+    loop->max_pass_count = get_mxloops ();
+}
+
+/* Parses an IF clause for LOOP or END LOOP and stores the
+   resulting expression to *CONDITION.
+   Returns true if successful, false on failure. */
+static bool
+parse_if_clause (struct loop_trns *loop, struct expression **condition) 
+{
+  *condition = expr_parse_pool (loop->pool, default_dict, EXPR_BOOLEAN);
+  return *condition != NULL;
+}
+
+/* Parses an indexing clause into LOOP.
+   Stores the index variable's name in INDEX_VAR_NAME[].
+   Returns true if successful, false on failure. */
+static bool
+parse_index_clause (struct loop_trns *loop, char index_var_name[]) 
+{
+  if (token != T_ID) 
+    {
+      lex_error (NULL);
+      return false;
+    }
+  strcpy (index_var_name, tokid);
+  lex_get ();
+
+  if (!lex_force_match ('='))
+    return false;
+
+  loop->first_expr = expr_parse_pool (loop->pool, default_dict, EXPR_NUMBER);
+  if (loop->first_expr == NULL)
+    return false;
+
+  for (;;)
+    {
+      struct expression **e;
+      if (lex_match (T_TO)) 
+        e = &loop->last_expr;
+      else if (lex_match (T_BY)) 
+        e = &loop->by_expr;
+      else
+        break;
+
+      if (*e != NULL) 
+        {
+          lex_sbc_only_once (e == &loop->last_expr ? "TO" : "BY");
+          return false;
+        }
+      *e = expr_parse_pool (loop->pool, default_dict, EXPR_NUMBER);
+      if (*e == NULL)
+        return false;
+    }
+  if (loop->last_expr == NULL) 
+    {
+      lex_sbc_missing ("TO");
+      return false;
+    }
+  if (loop->by_expr == NULL)
+    loop->by = 1.0;
+
+  return true;
+}
+
+/* Creates, initializes, and returns a new loop_trns. */
+static struct loop_trns *
+create_loop_trns (void) 
+{
+  struct loop_trns *loop = pool_create_container (struct loop_trns, pool);
+  loop->max_pass_count = -1;
+  loop->pass = 0;
+  loop->index_var = NULL;
+  loop->first_expr = loop->by_expr = loop->last_expr = NULL;
+  loop->loop_condition = loop->end_loop_condition = NULL;
+
+  add_transformation (loop_trns_proc, loop_trns_free, loop);
+  loop->past_LOOP_index = next_transformation ();
+
+  ctl_stack_push (&loop_class, loop);
+
+  return loop;
+}
+
+/* Sets up LOOP for the first pass. */
+static int
+loop_trns_proc (void *loop_, struct ccase *c, int case_num)
+{
+  struct loop_trns *loop = loop_;
+
+  if (loop->index_var != NULL)
+    {
+      /* Evaluate loop index expressions. */
+      loop->cur = expr_evaluate_num (loop->first_expr, c, case_num);
+      if (loop->by_expr != NULL)
+       loop->by = expr_evaluate_num (loop->by_expr, c, case_num);
+      loop->last = expr_evaluate_num (loop->last_expr, c, case_num);
+
+      /* Even if the loop is never entered, set the index
+         variable to the initial value. */
+      case_data_rw (c, loop->index_var->fv)->f = loop->cur;
+
+      /* Throw out pathological cases. */
+      if (!finite (loop->cur) || !finite (loop->by) || !finite (loop->last)
+          || loop->by == 0.0
+          || (loop->by > 0.0 && loop->cur > loop->last)
+          || (loop->by < 0.0 && loop->cur < loop->last))
+        goto zero_pass;
+    }
+
+  /* Initialize pass count. */
+  loop->pass = 0;
+  if (loop->max_pass_count >= 0 && loop->pass >= loop->max_pass_count)
+    goto zero_pass;
+
+  /* Check condition. */
+  if (loop->loop_condition != NULL
+      && expr_evaluate_num (loop->loop_condition, c, case_num) != 1.0)
+    goto zero_pass;
+
+  return loop->past_LOOP_index;
+
+ zero_pass:
+  return loop->past_END_LOOP_index;
+}
+
+/* Frees LOOP. */
+static bool
+loop_trns_free (void *loop_)
+{
+  struct loop_trns *loop = loop_;
+
+  pool_destroy (loop->pool);
+  return true;
+}
+
+/* Finishes a pass through the loop and starts the next. */
+static int
+end_loop_trns_proc (void *loop_, struct ccase *c, int case_num UNUSED)
+{
+  struct loop_trns *loop = loop_;
+
+  if (loop->end_loop_condition != NULL
+      && expr_evaluate_num (loop->end_loop_condition, c, case_num) != 1.0)
+    goto break_out;
+
+  /* MXLOOPS limiter. */
+  if (loop->max_pass_count >= 0)
+    {
+      if (loop->pass >= loop->max_pass_count)
+        goto break_out;
+      loop->pass++;
+    }
+
+  /* Indexing clause limiter: counting downward. */
+  if (loop->index_var != NULL) 
+    {
+      loop->cur += loop->by;
+      if ((loop->by > 0.0 && loop->cur > loop->last)
+          || (loop->by < 0.0 && loop->cur < loop->last))
+        goto break_out;
+      case_data_rw (c, loop->index_var->fv)->f = loop->cur;
+    }
+
+  if (loop->loop_condition != NULL
+      && expr_evaluate_num (loop->loop_condition, c, case_num) != 1.0)
+    goto break_out;
+
+  return loop->past_LOOP_index;
+
+ break_out:
+  return loop->past_END_LOOP_index;
+}
+
+/* Executes BREAK. */
+static int
+break_trns_proc (void *loop_, struct ccase *c UNUSED, int case_num UNUSED)
+{
+  struct loop_trns *loop = loop_;
+
+  return loop->past_END_LOOP_index;
+}
+
+/* LOOP control structure class definition. */
+static struct ctl_class loop_class =
+  {
+    "LOOP",
+    "END LOOP",
+    close_loop,
+  };
diff --git a/src/language/control/repeat.c b/src/language/control/repeat.c
new file mode 100644 (file)
index 0000000..bba14c6
--- /dev/null
@@ -0,0 +1,578 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "repeat.h"
+#include "message.h"
+#include <ctype.h>
+#include <math.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "line-buffer.h"
+#include "lexer.h"
+#include "misc.h"
+#include "pool.h"
+#include "settings.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+/* Defines a list of lines used by DO REPEAT. */
+struct line_list
+  {
+    struct line_list *next;    /* Next line. */
+    char *file_name;            /* File name. */
+    int line_number;            /* Line number. */
+    char *line;                        /* Contents. */
+  };
+
+/* The type of substitution made for a DO REPEAT macro. */
+enum repeat_entry_type 
+  {
+    VAR_NAMES,
+    OTHER
+  };
+
+/* Describes one DO REPEAT macro. */
+struct repeat_entry
+  {
+    struct repeat_entry *next;          /* Next entry. */
+    enum repeat_entry_type type;        /* Types of replacements. */
+    char id[LONG_NAME_LEN + 1];         /* Macro identifier. */
+    char **replacement;                 /* Macro replacement. */
+  };
+
+/* A DO REPEAT...END REPEAT block. */
+struct repeat_block 
+  {
+    struct pool *pool;                  /* Pool used for storage. */
+    struct line_list *first_line;       /* First line in line buffer. */
+    struct line_list *cur_line;         /* Current line in line buffer. */
+    int loop_cnt;                       /* Number of loops. */
+    int loop_idx;                       /* Number of loops so far. */
+    struct repeat_entry *macros;        /* Pointer to macro table. */
+    bool print;                         /* Print lines as executed? */
+  };
+
+static bool parse_specification (struct repeat_block *);
+static bool parse_lines (struct repeat_block *);
+static void create_vars (struct repeat_block *);
+
+static int parse_ids (struct repeat_entry *);
+static int parse_numbers (struct repeat_entry *);
+static int parse_strings (struct repeat_entry *);
+
+static void do_repeat_filter (struct string *line, void *block);
+static bool do_repeat_read (struct string *line, char **file_name,
+                            int *line_number, void *block);
+static void do_repeat_close (void *block);
+
+int
+cmd_do_repeat (void)
+{
+  struct repeat_block *block;
+
+  block = pool_create_container (struct repeat_block, pool);
+
+  if (!parse_specification (block) || !parse_lines (block))
+    goto error;
+  
+  create_vars (block);
+  
+  block->cur_line = NULL;
+  block->loop_idx = -1;
+  getl_include_filter (do_repeat_filter, do_repeat_close, block);
+  getl_include_function (do_repeat_read, NULL, block);
+
+  return CMD_SUCCESS;
+
+ error:
+  pool_destroy (block->pool);
+  return CMD_CASCADING_FAILURE;
+}
+
+/* Parses the whole DO REPEAT command specification.
+   Returns success. */
+static bool
+parse_specification (struct repeat_block *block) 
+{
+  char first_name[LONG_NAME_LEN + 1];
+
+  block->loop_cnt = 0;
+  block->macros = NULL;
+  do
+    {
+      struct repeat_entry *e;
+      struct repeat_entry *iter;
+      int count;
+
+      /* Get a stand-in variable name and make sure it's unique. */
+      if (!lex_force_id ())
+       return false;
+      if (dict_lookup_var (default_dict, tokid))
+        msg (SW, _("Dummy variable name \"%s\" hides dictionary "
+                   "variable \"%s\"."),
+             tokid, tokid);
+      for (iter = block->macros; iter != NULL; iter = iter->next)
+       if (!strcasecmp (iter->id, tokid))
+         {
+           msg (SE, _("Dummy variable name \"%s\" is given twice."), tokid);
+           return false;
+         }
+
+      /* Make a new stand-in variable entry and link it into the
+         list. */
+      e = pool_alloc (block->pool, sizeof *e);
+      e->next = block->macros;
+      strcpy (e->id, tokid);
+      block->macros = e;
+
+      /* Skip equals sign. */
+      lex_get ();
+      if (!lex_force_match ('='))
+       return false;
+
+      /* Get the details of the variable's possible values. */
+      if (token == T_ID)
+       count = parse_ids (e);
+      else if (lex_is_number ())
+       count = parse_numbers (e);
+      else if (token == T_STRING)
+       count = parse_strings (e);
+      else
+       {
+         lex_error (NULL);
+         return false;
+       }
+      if (count == 0)
+       return false;
+
+      /* If this is the first variable then it defines how many
+        replacements there must be; otherwise enforce this number of
+        replacements. */
+      if (block->loop_cnt == 0)
+       {
+         block->loop_cnt = count;
+         strcpy (first_name, e->id);
+       }
+      else if (block->loop_cnt != count)
+       {
+         msg (SE, _("Dummy variable \"%s\" had %d "
+                     "substitutions, so \"%s\" must also, but %d "
+                     "were specified."),
+              first_name, block->loop_cnt, e->id, count);
+         return false;
+       }
+
+      lex_match ('/');
+    }
+  while (token != '.');
+
+  return true;
+}
+
+/* If KEYWORD appears beginning at CP, possibly preceded by white
+   space, returns a pointer to the character just after the
+   keyword.  Otherwise, returns a null pointer. */
+static const char *
+recognize_keyword (const char *cp, const char *keyword)
+{
+  const char *end;
+
+  while (isspace ((unsigned char) *cp))
+    cp++;
+
+  end = lex_skip_identifier (cp);
+  if (end != cp
+      && lex_id_match_len (keyword, strlen (keyword), cp, end - cp))
+    return end;
+  else
+    return NULL;
+}
+
+/* Returns CP, advanced past a '+' or '-' if present. */
+static const char *
+skip_indentor (const char *cp) 
+{
+  if (*cp == '+' || *cp == '-')
+    cp++;
+  return cp;
+}
+
+/* Returns true if LINE contains a DO REPEAT command, false
+   otherwise. */
+static bool
+recognize_do_repeat (const char *line) 
+{
+  const char *cp = recognize_keyword (skip_indentor (line), "do");
+  return cp != NULL && recognize_keyword (cp, "repeat") != NULL;
+}
+
+/* Returns true if LINE contains an END REPEAT command, false
+   otherwise.  Sets *PRINT to true for END REPEAT PRINT, false
+   otherwise. */
+static bool
+recognize_end_repeat (const char *line, bool *print)
+{
+  const char *cp = recognize_keyword (skip_indentor (line), "end");
+  if (cp == NULL)
+    return false;
+
+  cp = recognize_keyword (cp, "repeat");
+  if (cp == NULL) 
+    return false; 
+
+  *print = recognize_keyword (cp, "print");
+  return true; 
+}
+
+/* Read all the lines we are going to substitute, inside the DO
+   REPEAT...END REPEAT block. */
+static bool
+parse_lines (struct repeat_block *block) 
+{
+  char *previous_file_name;
+  struct line_list **last_line;
+  int nesting_level;
+
+  previous_file_name = NULL;
+  block->first_line = NULL;
+  last_line = &block->first_line;
+  nesting_level = 0;
+
+  for (;;)
+    {
+      const char *cur_file_name;
+      int cur_line_number;
+      struct line_list *line;
+      bool dot;
+
+      if (!getl_read_line (NULL))
+        return false;
+
+      /* If the current file has changed then record the fact. */
+      getl_location (&cur_file_name, &cur_line_number);
+      if (previous_file_name == NULL 
+          || !strcmp (cur_file_name, previous_file_name))
+        previous_file_name = pool_strdup (block->pool, cur_file_name);
+
+      ds_rtrim_spaces (&getl_buf);
+      dot = ds_chomp (&getl_buf, get_endcmd ());
+      if (recognize_do_repeat (ds_c_str (&getl_buf))) 
+        nesting_level++; 
+      else if (recognize_end_repeat (ds_c_str (&getl_buf), &block->print)) 
+        {
+        if (nesting_level-- == 0)
+          {
+            lex_discard_line ();
+            return true;
+          } 
+        }
+      if (dot)
+        ds_putc (&getl_buf, get_endcmd ());
+      
+      line = *last_line = pool_alloc (block->pool, sizeof *line);
+      line->next = NULL;
+      line->file_name = previous_file_name;
+      line->line_number = cur_line_number;
+      line->line = pool_strdup (block->pool, ds_c_str (&getl_buf));
+      last_line = &line->next;
+    }
+
+  lex_discard_line ();
+  return true;
+}
+
+/* Creates variables for the given DO REPEAT. */
+static void
+create_vars (struct repeat_block *block)
+{
+  struct repeat_entry *iter;
+  for (iter = block->macros; iter; iter = iter->next)
+    if (iter->type == VAR_NAMES)
+      {
+        int i;
+
+        for (i = 0; i < block->loop_cnt; i++)
+          {
+            /* Ignore return value: if the variable already
+               exists there is no harm done. */
+            dict_create_var (default_dict, iter->replacement[i], 0);
+          }
+      }
+}
+
+/* Parses a set of ids for DO REPEAT. */
+static int
+parse_ids (struct repeat_entry *e)
+{
+  size_t i;
+  size_t n = 0;
+
+  e->type = VAR_NAMES;
+  e->replacement = NULL;
+
+  do
+    {
+      char **names;
+      size_t nnames;
+
+      if (!parse_mixed_vars (&names, &nnames, PV_NONE))
+       return 0;
+
+      e->replacement = xnrealloc (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 = OTHER;
+  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 = xnrealloc (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 = OTHER;
+  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 = xnrealloc (string,
+                                               m, sizeof *e->replacement);
+       }
+      string[n++] = lex_token_representation ();
+      lex_get ();
+
+      lex_match (',');
+    }
+  while (token != '/' && token != '.');
+  e->replacement = xnrealloc (string, n, sizeof *e->replacement);
+
+  return n;
+}
+\f
+int
+cmd_end_repeat (void)
+{
+  msg (SE, _("No matching DO REPEAT."));
+  return CMD_CASCADING_FAILURE;
+}
+\f
+/* Finds a DO REPEAT macro with name MACRO_NAME and returns the
+   appropriate subsitution if found, or NULL if not. */
+static char *
+find_substitution (struct repeat_block *block, const char *name, size_t length)
+{
+  struct repeat_entry *e;
+
+  for (e = block->macros; e; e = e->next)
+    if (!memcasecmp (e->id, name, length) && strlen (e->id) == length)
+      return e->replacement[block->loop_idx];
+  
+  return NULL;
+}
+
+/* Makes appropriate DO REPEAT macro substitutions within getl_buf. */
+static void
+do_repeat_filter (struct string *line, void *block_)
+{
+  struct repeat_block *block = block_;
+  bool in_apos, in_quote;
+  char *cp;
+  struct string output;
+  bool dot;
+
+  ds_init (&output, ds_capacity (line));
+
+  /* Strip trailing whitespace, check for & remove terminal dot. */
+  while (isspace (ds_last (line)))
+    ds_truncate (line, ds_length (line) - 1);
+  dot = ds_chomp (line, get_endcmd ());
+
+  in_apos = in_quote = false;
+  for (cp = ds_c_str (line); cp < ds_end (line); )
+    {
+      if (*cp == '\'' && !in_quote)
+       in_apos = !in_apos;
+      else if (*cp == '"' && !in_apos)
+       in_quote = !in_quote;
+      
+      if (in_quote || in_apos || !lex_is_id1 (*cp))
+        ds_putc (&output, *cp++);
+      else 
+        {
+          const char *start = cp;
+          char *end = lex_skip_identifier (start);
+          const char *substitution = find_substitution (block,
+                                                        start, end - start);
+          if (substitution != NULL) 
+            ds_puts (&output, substitution);
+          else
+            ds_concat (&output, start, end - start);
+          cp = end;
+        }
+    }
+  if (dot)
+    ds_putc (&output, get_endcmd ());
+
+  ds_swap (line, &output);
+  ds_destroy (&output);
+}
+
+/* Function called by getl to read a line.
+   Puts the line in OUTPUT, sets the file name in *FILE_NAME and
+   line number in *LINE_NUMBER.  Returns true if a line was
+   obtained, false if the source is exhausted. */
+static bool
+do_repeat_read (struct string *output, char **file_name, int *line_number,
+                void *block_) 
+{
+  struct repeat_block *block = block_;
+  struct line_list *line;
+
+  if (block->cur_line == NULL) 
+    {
+      block->loop_idx++;
+      if (block->loop_idx >= block->loop_cnt)
+        return false;
+      block->cur_line = block->first_line;
+    }
+  line = block->cur_line;
+
+  ds_replace (output, line->line);
+  *file_name = line->file_name;
+  *line_number = -line->line_number;
+  block->cur_line = line->next;
+  return true;
+}
+
+/* Frees a DO REPEAT block.
+   Called by getl to close out the DO REPEAT block. */
+static void
+do_repeat_close (void *block_)
+{
+  struct repeat_block *block = block_;
+  pool_destroy (block->pool);
+}
diff --git a/src/language/control/repeat.h b/src/language/control/repeat.h
new file mode 100644 (file)
index 0000000..33c0218
--- /dev/null
@@ -0,0 +1,25 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !INCLUDED_REPEAT_H
+#define INCLUDED_REPEAT_H 1
+
+void perform_DO_REPEAT_substitutions (void);
+
+#endif /* repeat.h */
diff --git a/src/language/control/temporary.c b/src/language/control/temporary.c
new file mode 100644 (file)
index 0000000..e5b8ae4
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <stddef.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "dictionary.h"
+#include "control-stack.h"
+#include "message.h"
+#include "hash.h"
+#include "lexer.h"
+#include "str.h"
+#include "value-labels.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+int temporary;
+struct dictionary *temp_dict;
+size_t temp_trns;
+
+/* Parses the TEMPORARY command. */
+int
+cmd_temporary (void)
+{
+  /* TEMPORARY is not allowed inside DO IF or LOOP. */
+  if (!ctl_stack_is_empty ())
+    {
+      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;
+    }
+
+  /* Make a copy of the current dictionary. */
+  temporary = 1;
+  temp_dict = dict_clone (default_dict);
+  temp_trns = n_trns;
+
+  return lex_end_of_command ();
+}
+
+/* Cancels the temporary transformation, if any. */
+void
+cancel_temporary (void)
+{
+  if (temporary)
+    {
+      if (temp_dict) 
+        {
+          dict_destroy (temp_dict);
+          temp_dict = NULL; 
+        }
+      temporary = 0;
+      temp_trns = 0;
+    }
+}
diff --git a/src/language/data-io/ChangeLog b/src/language/data-io/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/language/data-io/data-list.c b/src/language/data-io/data-list.c
new file mode 100644 (file)
index 0000000..5983034
--- /dev/null
@@ -0,0 +1,2058 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "data-list.h"
+#include "message.h"
+#include <ctype.h>
+#include <float.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "case.h"
+#include "command.h"
+#include "data-in.h"
+#include "debug-print.h"
+#include "data-reader.h"
+#include "dictionary.h"
+#include "message.h"
+#include "file-handle.h"
+#include "format.h"
+#include "lexer.h"
+#include "misc.h"
+#include "settings.h"
+#include "str.h"
+#include "table.h"
+#include "variable.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+\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;  /* Next specification in list. */
+
+    /* Both free and fixed formats. */
+    struct fmt_spec input;     /* Input format of this field. */
+    struct variable *v;                /* Associated variable.  Used only in
+                                  parsing.  Not safe later. */
+    int fv;                    /* First value in case. */
+
+    /* Fixed format only. */
+    int rec;                   /* Record number (1-based). */
+    int fc, lc;                        /* Column numbers in record. */
+
+    /* Free format only. */
+    char name[LONG_NAME_LEN + 1]; /* Name of variable. */
+  };
+
+/* 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 dls_var_spec *first, *last; /* Variable parsing specifications. */
+    struct dfm_reader *reader;  /* Data file reader. */
+
+    int type;                  /* A DLS_* constant. */
+    struct variable *end;      /* Variable specified on END subcommand. */
+    int rec_cnt;                /* Number of records. */
+    size_t case_size;           /* Case size in bytes. */
+    char *delims;               /* Delimiters if any; not null-terminated. */
+    size_t delim_cnt;           /* Number of delimiter, or 0 for spaces. */
+  };
+
+static const struct case_source_class data_list_source_class;
+
+static int parse_fixed (struct data_list_pgm *);
+static int parse_free (struct dls_var_spec **, struct dls_var_spec **);
+static void dump_fixed_table (const struct dls_var_spec *,
+                              const struct file_handle *, int rec_cnt);
+static void dump_free_table (const struct data_list_pgm *,
+                             const struct file_handle *);
+static void destroy_dls_var_spec (struct dls_var_spec *);
+static trns_free_func data_list_trns_free;
+static trns_proc_func data_list_trns_proc;
+
+/* Message title for REPEATING DATA. */
+#define RPD_ERR "REPEATING DATA: "
+
+int
+cmd_data_list (void)
+{
+  struct data_list_pgm *dls;
+  int table = -1;                /* Print table if nonzero, -1=undecided. */
+  struct file_handle *fh = fh_inline_file ();
+
+  if (!case_source_is_complex (vfm_source))
+    discard_variables ();
+
+  dls = xmalloc (sizeof *dls);
+  dls->reader = NULL;
+  dls->type = -1;
+  dls->end = NULL;
+  dls->rec_cnt = 0;
+  dls->delims = NULL;
+  dls->delim_cnt = 0;
+  dls->first = dls->last = NULL;
+
+  while (token != '/')
+    {
+      if (lex_match_id ("FILE"))
+       {
+         lex_match ('=');
+         fh = fh_parse (FH_REF_FILE | FH_REF_INLINE);
+         if (fh == NULL)
+           goto error;
+         if (case_source_is_class (vfm_source, &file_type_source_class)
+              && fh != fh_get_default_handle ())
+           {
+             msg (SE, _("DATA LIST must use the same file "
+                        "as the enclosing FILE TYPE."));
+             goto error;
+           }
+       }
+      else if (lex_match_id ("RECORDS"))
+       {
+         lex_match ('=');
+         lex_match ('(');
+         if (!lex_force_int ())
+           goto error;
+         dls->rec_cnt = 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."));
+             goto error;
+           }
+         
+         lex_match ('=');
+         if (!lex_force_id ())
+           goto error;
+         dls->end = dict_lookup_var (default_dict, tokid);
+         if (!dls->end) 
+            dls->end = dict_create_var_assert (default_dict, tokid, 0);
+         lex_get ();
+       }
+      else if (token == T_ID)
+       {
+          if (lex_match_id ("NOTABLE"))
+            table = 0;
+          else if (lex_match_id ("TABLE"))
+            table = 1;
+          else 
+            {
+              int type;
+              if (lex_match_id ("FIXED"))
+                type = DLS_FIXED;
+              else if (lex_match_id ("FREE"))
+                type = DLS_FREE;
+              else if (lex_match_id ("LIST"))
+                type = DLS_LIST;
+              else 
+                {
+                  lex_error (NULL);
+                  goto error;
+                }
+
+             if (dls->type != -1)
+               {
+                 msg (SE, _("Only one of FIXED, FREE, or LIST may "
+                             "be specified."));
+                 goto error;
+               }
+             dls->type = type;
+
+              if ((dls->type == DLS_FREE || dls->type == DLS_LIST)
+                  && lex_match ('(')) 
+                {
+                  while (!lex_match (')'))
+                    {
+                      int delim;
+
+                      if (lex_match_id ("TAB"))
+                        delim = '\t';
+                      else if (token == T_STRING && tokstr.length == 1)
+                       {
+                         delim = tokstr.string[0];
+                         lex_get();
+                       }
+                      else 
+                        {
+                          lex_error (NULL);
+                          goto error;
+                        }
+
+                      dls->delims = xrealloc (dls->delims, dls->delim_cnt + 1);
+                      dls->delims[dls->delim_cnt++] = delim;
+
+                      lex_match (',');
+                    }
+                }
+            }
+        }
+      else
+       {
+         lex_error (NULL);
+         goto error;
+       }
+    }
+
+  dls->case_size = dict_get_case_size (default_dict);
+  fh_set_default_handle (fh);
+
+  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 (dls))
+       goto error;
+      if (table)
+       dump_fixed_table (dls->first, fh, dls->rec_cnt);
+    }
+  else
+    {
+      if (!parse_free (&dls->first, &dls->last))
+       goto error;
+      if (table)
+       dump_free_table (dls, fh);
+    }
+
+  dls->reader = dfm_open_reader (fh);
+  if (dls->reader == NULL)
+    goto error;
+
+  if (vfm_source != NULL)
+    add_transformation (data_list_trns_proc, data_list_trns_free, dls);
+  else 
+    vfm_source = create_case_source (&data_list_source_class, dls);
+
+  return CMD_SUCCESS;
+
+ error:
+  data_list_trns_free (dls);
+  return CMD_CASCADING_FAILURE;
+}
+
+/* Adds SPEC to the linked list with head at FIRST and tail at
+   LAST. */
+static void
+append_var_spec (struct dls_var_spec **first, struct dls_var_spec **last,
+                 struct dls_var_spec *spec)
+{
+  spec->next = NULL;
+
+  if (*first == NULL)
+    *first = spec;
+  else 
+    (*last)->next = spec;
+  *last = spec;
+}
+\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;
+  };
+
+/* State of parsing DATA LIST. */
+struct fixed_parsing_state
+  {
+    char **name;               /* Variable names. */
+    size_t name_cnt;           /* Number of names. */
+
+    int recno;                 /* Index of current record. */
+    int sc;                    /* 1-based column number of starting column for
+                                  next field to output. */
+  };
+
+static int fixed_parse_compatible (struct fixed_parsing_state *,
+                                   struct dls_var_spec **,
+                                   struct dls_var_spec **);
+static int fixed_parse_fortran (struct fixed_parsing_state *,
+                                struct dls_var_spec **,
+                                struct dls_var_spec **);
+
+/* Parses all the variable specifications for DATA LIST FIXED,
+   storing them into DLS.  Returns nonzero if successful. */
+static int
+parse_fixed (struct data_list_pgm *dls)
+{
+  struct fixed_parsing_state fx;
+  size_t i;
+
+  fx.recno = 0;
+  fx.sc = 1;
+
+  while (token != '.')
+    {
+      while (lex_match ('/'))
+       {
+         fx.recno++;
+         if (lex_is_integer ())
+           {
+             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;
+       }
+
+      if (!parse_DATA_LIST_vars (&fx.name, &fx.name_cnt, PV_NONE))
+       return 0;
+
+      if (lex_is_number ())
+       {
+         if (!fixed_parse_compatible (&fx, &dls->first, &dls->last))
+           goto fail;
+       }
+      else if (token == '(')
+       {
+         if (!fixed_parse_fortran (&fx, &dls->first, &dls->last))
+           goto fail;
+       }
+      else
+       {
+         msg (SE, _("SPSS-like or FORTRAN-like format "
+                     "specification expected after variable names."));
+         goto fail;
+       }
+
+      for (i = 0; i < fx.name_cnt; i++)
+       free (fx.name[i]);
+      free (fx.name);
+    }
+  if (dls->first == NULL) 
+    {
+      msg (SE, _("At least one variable must be specified."));
+      return 0;
+    }
+  if (dls->rec_cnt && dls->last->rec > dls->rec_cnt)
+    {
+      msg (SE, _("Variables are specified on records that "
+                "should not exist according to RECORDS subcommand."));
+      return 0;
+    }
+  else if (!dls->rec_cnt)
+    dls->rec_cnt = dls->last->rec;
+  return lex_end_of_command () == CMD_SUCCESS;
+
+fail:
+  for (i = 0; i < fx.name_cnt; i++)
+    free (fx.name[i]);
+  free (fx.name);
+  return 0;
+}
+
+/* Parses a variable specification in the form 1-10 (A) based on
+   FX and adds specifications to the linked list with head at
+   FIRST and tail at LAST. */
+static int
+fixed_parse_compatible (struct fixed_parsing_state *fx,
+                        struct dls_var_spec **first, struct dls_var_spec **last)
+{
+  struct fmt_spec input;
+  int fc, lc;
+  int width;
+  int i;
+
+  /* First column. */
+  if (!lex_force_int ())
+    return 0;
+  fc = lex_integer ();
+  if (fc < 1)
+    {
+      msg (SE, _("Column positions for fields must be positive."));
+      return 0;
+    }
+  lex_get ();
+
+  /* Last column. */
+  lex_negative_to_dash ();
+  if (lex_match ('-'))
+    {
+      if (!lex_force_int ())
+       return 0;
+      lc = lex_integer ();
+      if (lc < 1)
+       {
+         msg (SE, _("Column positions for fields must be positive."));
+         return 0;
+       }
+      else if (lc < fc)
+       {
+         msg (SE, _("The ending column for a field must be "
+                    "greater than the starting column."));
+         return 0;
+       }
+      
+      lex_get ();
+    }
+  else
+    lc = fc;
+
+  /* Divide columns evenly. */
+  input.w = (lc - fc + 1) / fx->name_cnt;
+  if ((lc - fc + 1) % fx->name_cnt)
+    {
+      msg (SE, _("The %d columns %d-%d "
+                "can't be evenly divided into %d fields."),
+          lc - fc + 1, fc, lc, fx->name_cnt);
+      return 0;
+    }
+
+  /* Format specifier. */
+  if (lex_match ('('))
+    {
+      struct fmt_desc *fdp;
+
+      if (token == T_ID)
+       {
+         const char *cp;
+
+         input.type = parse_format_specifier_name (&cp, 0);
+         if (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
+       input.type = FMT_F;
+
+      if (lex_is_integer ())
+       {
+         if (lex_integer () < 1)
+           {
+             msg (SE, _("The value for number of decimal places "
+                        "must be at least 1."));
+             return 0;
+           }
+         
+         input.d = lex_integer ();
+         lex_get ();
+       }
+      else
+       input.d = 0;
+
+      fdp = &formats[input.type];
+      if (fdp->n_args < 2 && input.d)
+       {
+         msg (SE, _("Input format %s doesn't accept decimal places."),
+              fdp->name);
+         return 0;
+       }
+      
+      if (input.d > 16)
+       input.d = 16;
+
+      if (!lex_force_match (')'))
+       return 0;
+    }
+  else
+    {
+      input.type = FMT_F;
+      input.d = 0;
+    }
+  if (!check_input_specifier (&input, 1))
+    return 0;
+
+  /* Start column for next specification. */
+  fx->sc = lc + 1;
+
+  /* Width of variables to create. */
+  if (input.type == FMT_A || input.type == FMT_AHEX) 
+    width = input.w;
+  else
+    width = 0;
+
+  /* Create variables and var specs. */
+  for (i = 0; i < fx->name_cnt; i++)
+    {
+      struct dls_var_spec *spec;
+      struct variable *v;
+
+      v = dict_create_var (default_dict, fx->name[i], width);
+      if (v != NULL)
+       {
+         convert_fmt_ItoO (&input, &v->print);
+         v->write = v->print;
+          if (!case_source_is_complex (vfm_source))
+            v->init = 0;
+       }
+      else
+       {
+         v = dict_lookup_var_assert (default_dict, fx->name[i]);
+         if (vfm_source == NULL)
+           {
+             msg (SE, _("%s is a duplicate variable name."), fx->name[i]);
+             return 0;
+           }
+         if ((width != 0) != (v->width != 0))
+           {
+             msg (SE, _("There is already a variable %s of a "
+                        "different type."),
+                  fx->name[i]);
+             return 0;
+           }
+         if (width != 0 && width != v->width)
+           {
+             msg (SE, _("There is already a string variable %s of a "
+                        "different width."), fx->name[i]);
+             return 0;
+           }
+       }
+
+      spec = xmalloc (sizeof *spec);
+      spec->input = input;
+      spec->v = v;
+      spec->fv = v->fv;
+      spec->rec = fx->recno;
+      spec->fc = fc + input.w * i;
+      spec->lc = spec->fc + input.w - 1;
+      append_var_spec (first, last, spec);
+    }
+  return 1;
+}
+
+/* Destroy format list F and, if RECURSE is nonzero, 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, adding the variable
+   specifications to the linked list with head FIRST and tail
+   LAST.  NAME_IDX is used to take values from the list of names
+   in FX; it should initially point to a value of 0. */
+static int
+dump_fmt_list (struct fixed_parsing_state *fx, struct fmt_list *f,
+               struct dls_var_spec **first, struct dls_var_spec **last,
+               int *name_idx)
+{
+  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 (fx, f->down, first, last, name_idx))
+             return 0;
+         }
+       else
+         {
+            struct dls_var_spec *spec;
+            int width;
+           struct variable *v;
+
+            if (formats[f->f.type].cat & FCAT_STRING) 
+              width = f->f.w;
+            else
+              width = 0;
+           if (*name_idx >= fx->name_cnt)
+             {
+               msg (SE, _("The number of format "
+                          "specifications exceeds the given number of "
+                          "variable names."));
+               return 0;
+             }
+           
+           v = dict_create_var (default_dict, fx->name[(*name_idx)++], width);
+           if (!v)
+             {
+               msg (SE, _("%s is a duplicate variable name."), fx->name[i]);
+               return 0;
+             }
+           
+            if (!case_source_is_complex (vfm_source))
+              v->init = 0;
+
+            spec = xmalloc (sizeof *spec);
+            spec->v = v;
+           spec->input = f->f;
+           spec->fv = v->fv;
+           spec->rec = fx->recno;
+           spec->fc = fx->sc;
+           spec->lc = fx->sc + f->f.w - 1;
+           append_var_spec (first, last, spec);
+
+           convert_fmt_ItoO (&spec->input, &v->print);
+           v->write = v->print;
+
+           fx->sc += f->f.w;
+         }
+  return 1;
+}
+
+/* Recursively parses a FORTRAN-like format specification into
+   the linked list with head FIRST and tail TAIL.  LEVEL is the
+   level of recursion, starting from 0.  Returns the parsed
+   specification if successful, or a null pointer on failure.  */
+static struct fmt_list *
+fixed_parse_fortran_internal (struct fixed_parsing_state *fx,
+                              struct dls_var_spec **first,
+                              struct dls_var_spec **last)
+{
+  struct fmt_list *head = NULL;
+  struct fmt_list *tail = NULL;
+
+  lex_force_match ('(');
+  while (token != ')')
+    {
+      /* New fmt_list. */
+      struct fmt_list *new = xmalloc (sizeof *new);
+      new->next = NULL;
+
+      /* Append new to list. */
+      if (head != NULL)
+       tail->next = new;
+      else
+       head = new;
+      tail = new;
+
+      /* Parse count. */
+      if (lex_is_integer ())
+       {
+         new->count = lex_integer ();
+         lex_get ();
+       }
+      else
+       new->count = 1;
+
+      /* Parse format specifier. */
+      if (token == '(')
+       {
+         new->f.type = FMT_DESCEND;
+         new->down = fixed_parse_fortran_internal (fx, first, last);
+         if (new->down == NULL)
+           goto fail;
+       }
+      else if (lex_match ('/'))
+       new->f.type = FMT_NEWREC;
+      else if (!parse_format_specifier (&new->f, FMTP_ALLOW_XT)
+              || !check_input_specifier (&new->f, 1))
+       goto fail;
+
+      lex_match (',');
+    }
+  lex_force_match (')');
+
+  return head;
+
+fail:
+  destroy_fmt_list (head, 0);
+
+  return NULL;
+}
+
+/* Parses a FORTRAN-like format specification into the linked
+   list with head FIRST and tail LAST.  Returns nonzero if
+   successful. */
+static int
+fixed_parse_fortran (struct fixed_parsing_state *fx,
+                     struct dls_var_spec **first, struct dls_var_spec **last)
+{
+  struct fmt_list *list;
+  int name_idx;
+
+  list = fixed_parse_fortran_internal (fx, first, last);
+  if (list == NULL)
+    return 0;
+  
+  name_idx = 0;
+  dump_fmt_list (fx, list, first, last, &name_idx);
+  destroy_fmt_list (list, 1);
+  if (name_idx < fx->name_cnt)
+    {
+      msg (SE, _("There aren't enough format specifications "
+                 "to match the number of variable names given."));
+      return 0; 
+    }
+
+  return 1;
+}
+
+/* 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 (const struct dls_var_spec *specs,
+                  const struct file_handle *fh, int rec_cnt)
+{
+  const struct dls_var_spec *spec;
+  struct tab_table *t;
+  int i;
+
+  for (i = 0, spec = specs; 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 = specs; 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));
+    }
+
+  tab_title (t, 1, ngettext ("Reading %d record from %s.",
+                             "Reading %d records from %s.", rec_cnt),
+             rec_cnt, fh_get_name (fh));
+  tab_submit (t);
+}
+\f
+/* Free-format parsing. */
+
+/* Parses variable specifications for DATA LIST FREE and adds
+   them to the linked list with head FIRST and tail LAST.
+   Returns nonzero only if successful. */
+static int
+parse_free (struct dls_var_spec **first, struct dls_var_spec **last)
+{
+  lex_get ();
+  while (token != '.')
+    {
+      struct fmt_spec input, output;
+      char **name;
+      size_t name_cnt;
+      int width;
+      size_t i;
+
+      if (!parse_DATA_LIST_vars (&name, &name_cnt, PV_NONE))
+       return 0;
+
+      if (lex_match ('('))
+       {
+         if (!parse_format_specifier (&input, 0)
+              || !check_input_specifier (&input, 1)
+              || !lex_force_match (')')) 
+            {
+              for (i = 0; i < name_cnt; i++)
+                free (name[i]);
+              free (name);
+              return 0; 
+            }
+         convert_fmt_ItoO (&input, &output);
+       }
+      else
+       {
+         lex_match ('*');
+          input = make_input_format (FMT_F, 8, 0);
+         output = *get_format ();
+       }
+
+      if (input.type == FMT_A || input.type == FMT_AHEX)
+       width = input.w;
+      else
+       width = 0;
+      for (i = 0; i < name_cnt; i++)
+       {
+          struct dls_var_spec *spec;
+         struct variable *v;
+
+         v = dict_create_var (default_dict, name[i], width);
+         
+         if (!v)
+           {
+             msg (SE, _("%s is a duplicate variable name."), name[i]);
+             return 0;
+           }
+         v->print = v->write = output;
+
+          if (!case_source_is_complex (vfm_source))
+            v->init = 0;
+
+          spec = xmalloc (sizeof *spec);
+          spec->input = input;
+          spec->v = v;
+         spec->fv = v->fv;
+         str_copy_trunc (spec->name, sizeof spec->name, v->name);
+         append_var_spec (first, last, spec);
+       }
+      for (i = 0; i < name_cnt; i++)
+       free (name[i]);
+      free (name);
+    }
+
+  return lex_end_of_command () == CMD_SUCCESS;
+}
+
+/* Displays a table giving information on free-format variable parsing
+   on DATA LIST. */
+static void
+dump_free_table (const struct data_list_pgm *dls,
+                 const struct file_handle *fh)
+{
+  struct tab_table *t;
+  int i;
+  
+  {
+    struct dls_var_spec *spec;
+    for (i = 0, spec = dls->first; 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->first; 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));
+      }
+  }
+
+  tab_title (t, 1, _("Reading free-form data from %s."), fh_get_name (fh));
+  
+  tab_submit (t);
+}
+\f
+/* Input procedure. */ 
+
+/* Extracts a field from the current position in the current
+   record.  Fields can be unquoted or quoted with single- or
+   double-quote characters.  *FIELD is set to the field content.
+   After parsing the field, sets the current position in the
+   record to just past the field and any trailing delimiter.
+   END_BLANK is used internally; it should be initialized by the
+   caller to 0 and left alone afterward.  Returns 0 on failure or
+   a 1-based column number indicating the beginning of the field
+   on success. */
+static int
+cut_field (const struct data_list_pgm *dls, struct fixed_string *field,
+           int *end_blank)
+{
+  struct fixed_string line;
+  char *cp;
+  size_t column_start;
+
+  if (dfm_eof (dls->reader))
+    return 0;
+  if (dls->delim_cnt == 0)
+    dfm_expand_tabs (dls->reader);
+  dfm_get_record (dls->reader, &line);
+
+  cp = ls_c_str (&line);
+  if (dls->delim_cnt == 0) 
+    {
+      /* Skip leading whitespace. */
+      while (cp < ls_end (&line) && isspace ((unsigned char) *cp))
+        cp++;
+      if (cp >= ls_end (&line))
+        return 0;
+      
+      /* Handle actual data, whether quoted or unquoted. */
+      if (*cp == '\'' || *cp == '"')
+        {
+          int quote = *cp;
+
+          field->string = ++cp;
+          while (cp < ls_end (&line) && *cp != quote)
+            cp++;
+          field->length = cp - field->string;
+          if (cp < ls_end (&line))
+            cp++;
+          else
+            msg (SW, _("Quoted string missing terminating `%c'."), quote);
+        }
+      else
+        {
+          field->string = cp;
+          while (cp < ls_end (&line)
+                 && !isspace ((unsigned char) *cp) && *cp != ',')
+            cp++;
+          field->length = cp - field->string;
+        }
+
+      /* Skip trailing whitespace and a single comma if present. */
+      while (cp < ls_end (&line) && isspace ((unsigned char) *cp))
+        cp++;
+      if (cp < ls_end (&line) && *cp == ',')
+        cp++;
+    }
+  else 
+    {
+      if (cp >= ls_end (&line)) 
+        {
+          int column = dfm_column_start (dls->reader);
+               /* A blank line or a line that ends in \t has a
+             trailing blank field. */
+          if (column == 1 || (column > 1 && cp[-1] == '\t'))
+            {
+              if (*end_blank == 0)
+                {
+                  *end_blank = 1;
+                  field->string = ls_end (&line);
+                  field->length = 0;
+                  dfm_forward_record (dls->reader);
+                  return column;
+                }
+              else 
+                {
+                  *end_blank = 0;
+                  return 0;
+                }
+            }
+          else 
+            return 0;
+        }
+      else 
+        {
+          field->string = cp;
+          while (cp < ls_end (&line)
+                 && memchr (dls->delims, *cp, dls->delim_cnt) == NULL)
+            cp++; 
+          field->length = cp - field->string;
+          if (cp < ls_end (&line)) 
+            cp++;
+        }
+    }
+  
+  dfm_forward_columns (dls->reader, field->string - line.string);
+  column_start = dfm_column_start (dls->reader);
+    
+  dfm_forward_columns (dls->reader, cp - field->string);
+    
+  return column_start;
+}
+
+static bool read_from_data_list_fixed (const struct data_list_pgm *,
+                                       struct ccase *);
+static bool read_from_data_list_free (const struct data_list_pgm *,
+                                      struct ccase *);
+static bool read_from_data_list_list (const struct data_list_pgm *,
+                                      struct ccase *);
+
+/* Reads a case from DLS into C.
+   Returns true if successful, false at end of file or on I/O error. */
+static bool
+read_from_data_list (const struct data_list_pgm *dls, struct ccase *c) 
+{
+  bool retval;
+
+  dfm_push (dls->reader);
+  switch (dls->type)
+    {
+    case DLS_FIXED:
+      retval = read_from_data_list_fixed (dls, c);
+      break;
+    case DLS_FREE:
+      retval = read_from_data_list_free (dls, c);
+      break;
+    case DLS_LIST:
+      retval = read_from_data_list_list (dls, c);
+      break;
+    default:
+      abort ();
+    }
+  dfm_pop (dls->reader);
+
+  return retval;
+}
+
+/* Reads a case from the data file into C, parsing it according
+   to fixed-format syntax rules in DLS.  
+   Returns true if successful, false at end of file or on I/O error. */
+static bool
+read_from_data_list_fixed (const struct data_list_pgm *dls, struct ccase *c)
+{
+  struct dls_var_spec *var_spec = dls->first;
+  int i;
+
+  if (dfm_eof (dls->reader))
+    return false;
+  for (i = 1; i <= dls->rec_cnt; i++)
+    {
+      struct fixed_string line;
+      
+      if (dfm_eof (dls->reader))
+       {
+         /* Note that this can't occur on the first record. */
+         msg (SW, _("Partial case of %d of %d records discarded."),
+              i - 1, dls->rec_cnt);
+         return false;
+       }
+      dfm_expand_tabs (dls->reader);
+      dfm_get_record (dls->reader, &line);
+
+      for (; var_spec && i == var_spec->rec; var_spec = var_spec->next)
+       {
+         struct data_in di;
+
+         data_in_finite_line (&di, ls_c_str (&line), ls_length (&line),
+                               var_spec->fc, var_spec->lc);
+         di.v = case_data_rw (c, var_spec->fv);
+         di.flags = DI_IMPLIED_DECIMALS;
+         di.f1 = var_spec->fc;
+         di.format = var_spec->input;
+
+         data_in (&di);
+       }
+
+      dfm_forward_record (dls->reader);
+    }
+
+  return true;
+}
+
+/* Reads a case from the data file into C, parsing it according
+   to free-format syntax rules in DLS.  
+   Returns true if successful, false at end of file or on I/O error. */
+static bool
+read_from_data_list_free (const struct data_list_pgm *dls, struct ccase *c)
+{
+  struct dls_var_spec *var_spec;
+  int end_blank = 0;
+
+  for (var_spec = dls->first; var_spec; var_spec = var_spec->next)
+    {
+      struct fixed_string field;
+      int column;
+      
+      /* Cut out a field and read in a new record if necessary. */
+      for (;;)
+       {
+         column = cut_field (dls, &field, &end_blank);
+         if (column != 0)
+           break;
+
+         if (!dfm_eof (dls->reader)) 
+            dfm_forward_record (dls->reader);
+         if (dfm_eof (dls->reader))
+           {
+             if (var_spec != dls->first)
+               msg (SW, _("Partial case discarded.  The first variable "
+                           "missing was %s."), var_spec->name);
+             return false;
+           }
+       }
+      
+      {
+       struct data_in di;
+
+       di.s = ls_c_str (&field);
+       di.e = ls_end (&field);
+       di.v = case_data_rw (c, var_spec->fv);
+       di.flags = 0;
+       di.f1 = column;
+       di.format = var_spec->input;
+       data_in (&di);
+      }
+    }
+  return true;
+}
+
+/* Reads a case from the data file and parses it according to
+   list-format syntax rules.  
+   Returns true if successful, false at end of file or on I/O error. */
+static bool
+read_from_data_list_list (const struct data_list_pgm *dls, struct ccase *c)
+{
+  struct dls_var_spec *var_spec;
+  int end_blank = 0;
+
+  if (dfm_eof (dls->reader))
+    return false;
+
+  for (var_spec = dls->first; var_spec; var_spec = var_spec->next)
+    {
+      struct fixed_string field;
+      int column;
+
+      /* Cut out a field and check for end-of-line. */
+      column = cut_field (dls, &field, &end_blank);
+      if (column == 0)
+       {
+         if (get_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)
+            {
+              int width = get_format_var_width (&var_spec->input);
+              if (width == 0)
+                case_data_rw (c, var_spec->fv)->f = SYSMIS;
+              else
+                memset (case_data_rw (c, var_spec->fv)->s, ' ', width); 
+            }
+         break;
+       }
+      
+      {
+       struct data_in di;
+
+       di.s = ls_c_str (&field);
+       di.e = ls_end (&field);
+       di.v = case_data_rw (c, var_spec->fv);
+       di.flags = 0;
+       di.f1 = column;
+       di.format = var_spec->input;
+       data_in (&di);
+      }
+    }
+
+  dfm_forward_record (dls->reader);
+  return true;
+}
+
+/* Destroys SPEC. */
+static void
+destroy_dls_var_spec (struct dls_var_spec *spec) 
+{
+  struct dls_var_spec *next;
+
+  while (spec != NULL)
+    {
+      next = spec->next;
+      free (spec);
+      spec = next;
+    }
+}
+
+/* Destroys DATA LIST transformation DLS.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+data_list_trns_free (void *dls_)
+{
+  struct data_list_pgm *dls = dls_;
+  free (dls->delims);
+  destroy_dls_var_spec (dls->first);
+  dfm_close_reader (dls->reader);
+  free (dls);
+  return true;
+}
+
+/* Handle DATA LIST transformation DLS, parsing data into C. */
+static int
+data_list_trns_proc (void *dls_, struct ccase *c, int case_num UNUSED)
+{
+  struct data_list_pgm *dls = dls_;
+  int retval;
+
+  if (read_from_data_list (dls, c))
+    retval = TRNS_CONTINUE;
+  else if (dfm_reader_error (dls->reader) || dfm_eof (dls->reader) > 1) 
+    {
+      /* An I/O error, or encountering end of file for a second
+         time, should be escalated into a more serious error. */
+      retval = TRNS_ERROR;
+    }
+  else
+    retval = TRNS_DROP_CASE;
+  
+  /* If there was an END subcommand handle it. */
+  if (dls->end != NULL) 
+    {
+      double *end = &case_data_rw (c, dls->end->fv)->f;
+      if (retval == TRNS_DROP_CASE)
+        {
+          *end = 1.0;
+          retval = TRNS_CONTINUE;
+        }
+      else
+        *end = 0.0;
+    }
+
+  return retval;
+}
+\f
+/* Reads all the records from the data file and passes them to
+   write_case().
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+data_list_source_read (struct case_source *source,
+                       struct ccase *c,
+                       write_case_func *write_case, write_case_data wc_data)
+{
+  struct data_list_pgm *dls = source->aux;
+
+  for (;;) 
+    {
+      bool ok;
+
+      if (!read_from_data_list (dls, c)) 
+        return !dfm_reader_error (dls->reader);
+
+      dfm_push (dls->reader);
+      ok = write_case (wc_data);
+      dfm_pop (dls->reader);
+      if (!ok)
+        return false;
+    }
+}
+
+/* Destroys the source's internal data. */
+static void
+data_list_source_destroy (struct case_source *source)
+{
+  data_list_trns_free (source->aux);
+}
+
+static const struct case_source_class data_list_source_class = 
+  {
+    "DATA LIST",
+    NULL,
+    data_list_source_read,
+    data_list_source_destroy,
+  };
+\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 dls_var_spec *first, *last; /* Variable parsing specifications. */
+    struct dfm_reader *reader;         /* Input file, never NULL. */
+
+    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. */
+
+    /* ID subcommand. */
+    int id_beg, id_end;                        /* Beginning & end columns. */
+    struct variable *id_var;           /* DATA LIST variable. */
+    struct fmt_spec id_spec;           /* Input format spec. */
+    union value *id_value;              /* ID value. */
+
+    write_case_func *write_case;
+    write_case_data wc_data;
+  };
+
+static trns_free_func repeating_data_trns_free;
+static int parse_num_or_var (struct rpd_num_or_var *, const char *);
+static int parse_repeating_data (struct dls_var_spec **,
+                                 struct dls_var_spec **);
+static void find_variable_input_spec (struct variable *v,
+                                     struct fmt_spec *spec);
+
+/* Parses the REPEATING DATA command. */
+int
+cmd_repeating_data (void)
+{
+  struct repeating_data_trns *rpd;
+  int table = 1;                /* Print table? */
+  bool saw_starts = false;      /* Saw STARTS subcommand? */
+  bool saw_occurs = false;      /* Saw OCCURS subcommand? */
+  bool saw_length = false;      /* Saw LENGTH subcommand? */
+  bool saw_continued = false;   /* Saw CONTINUED subcommand? */
+  bool saw_id = false;          /* Saw ID subcommand? */
+  struct file_handle *const fh = fh_get_default_handle ();
+  
+  assert (case_source_is_complex (vfm_source));
+
+  rpd = xmalloc (sizeof *rpd);
+  rpd->reader = dfm_open_reader (fh);
+  rpd->first = rpd->last = NULL;
+  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->id_value = NULL;
+
+  lex_match ('/');
+  
+  for (;;)
+    {
+      if (lex_match_id ("FILE"))
+       {
+          struct file_handle *file;
+         lex_match ('=');
+         file = fh_parse (FH_REF_FILE | FH_REF_INLINE);
+         if (file == NULL)
+           goto error;
+         if (file != fh)
+           {
+             msg (SE, _("REPEATING DATA must use the same file as its "
+                        "corresponding DATA LIST or FILE TYPE."));
+              goto error;
+           }
+       }
+      else if (lex_match_id ("STARTS"))
+       {
+         lex_match ('=');
+         if (saw_starts)
+           {
+             msg (SE, _("%s subcommand given multiple times."),"STARTS");
+             goto error;
+           }
+          saw_starts = true;
+          
+         if (!parse_num_or_var (&rpd->starts_beg, "STARTS beginning column"))
+           goto error;
+
+         lex_negative_to_dash ();
+         if (lex_match ('-'))
+           {
+             if (!parse_num_or_var (&rpd->starts_end, "STARTS ending column"))
+               goto error;
+           } else {
+             /* Otherwise, rpd->starts_end is uninitialized.  We
+                will initialize it later from the record length
+                of the file.  We can't do so now because the
+                file handle may not be specified 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);
+             goto error;
+           }
+       }
+      else if (lex_match_id ("OCCURS"))
+       {
+         lex_match ('=');
+         if (saw_occurs)
+           {
+             msg (SE, _("%s subcommand given multiple times."),"OCCURS");
+             goto error;
+           }
+         saw_occurs = true;
+
+         if (!parse_num_or_var (&rpd->occurs, "OCCURS"))
+           goto error;
+       }
+      else if (lex_match_id ("LENGTH"))
+       {
+         lex_match ('=');
+         if (saw_length)
+           {
+             msg (SE, _("%s subcommand given multiple times."),"LENGTH");
+             goto error;
+           }
+         saw_length = true;
+
+         if (!parse_num_or_var (&rpd->length, "LENGTH"))
+           goto error;
+       }
+      else if (lex_match_id ("CONTINUED"))
+       {
+         lex_match ('=');
+         if (saw_continued)
+           {
+             msg (SE, _("%s subcommand given multiple times."),"CONTINUED");
+             goto error;
+           }
+         saw_continued = true;
+
+         if (!lex_match ('/'))
+           {
+             if (!parse_num_or_var (&rpd->cont_beg,
+                                     "CONTINUED beginning column"))
+               goto error;
+
+             lex_negative_to_dash ();
+             if (lex_match ('-')
+                 && !parse_num_or_var (&rpd->cont_end,
+                                       "CONTINUED ending column"))
+               goto error;
+         
+             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);
+                 goto error;
+               }
+           }
+         else
+           rpd->cont_beg.num = 1;
+       }
+      else if (lex_match_id ("ID"))
+       {
+         lex_match ('=');
+         if (saw_id)
+           {
+             msg (SE, _("%s subcommand given multiple times."),"ID");
+             goto error;
+           }
+         saw_id = true;
+         
+         if (!lex_force_int ())
+           goto error;
+         if (lex_integer () < 1)
+           {
+             msg (SE, _("ID beginning column (%ld) must be positive."),
+                  lex_integer ());
+             goto error;
+           }
+         rpd->id_beg = lex_integer ();
+         
+         lex_get ();
+         lex_negative_to_dash ();
+         
+         if (lex_match ('-'))
+           {
+             if (!lex_force_int ())
+               goto error;
+             if (lex_integer () < 1)
+               {
+                 msg (SE, _("ID ending column (%ld) must be positive."),
+                      lex_integer ());
+                 goto error;
+               }
+             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);
+                 goto error;
+               }
+             
+             rpd->id_end = lex_integer ();
+             lex_get ();
+           }
+         else rpd->id_end = rpd->id_beg;
+
+         if (!lex_force_match ('='))
+           goto error;
+         rpd->id_var = parse_variable ();
+         if (rpd->id_var == NULL)
+           goto error;
+
+         find_variable_input_spec (rpd->id_var, &rpd->id_spec);
+          rpd->id_value = xnmalloc (rpd->id_var->nv, sizeof *rpd->id_value);
+       }
+      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);
+         goto error;
+       }
+
+      if (!lex_force_match ('/'))
+       goto error;
+    }
+
+  /* Comes here when DATA specification encountered. */
+  if (!saw_starts || !saw_occurs)
+    {
+      if (!saw_starts)
+       msg (SE, _("Missing required specification STARTS."));
+      if (!saw_occurs)
+       msg (SE, _("Missing required specification OCCURS."));
+      goto error;
+    }
+
+  /* Enforce ID restriction. */
+  if (saw_id && !saw_continued)
+    {
+      msg (SE, _("ID specified without CONTINUED."));
+      goto error;
+    }
+
+  /* Calculate and check starts_end, cont_end if necessary. */
+  if (rpd->starts_end.num == 0 && rpd->starts_end.var == NULL) 
+    {
+      rpd->starts_end.num = fh_get_record_width (fh);
+      if (rpd->starts_beg.num != 0 
+          && rpd->starts_beg.num > rpd->starts_end.num)
+        {
+          msg (SE, _("STARTS beginning column (%d) exceeds "
+                     "default STARTS ending column taken from file's "
+                     "record width (%d)."),
+               rpd->starts_beg.num, rpd->starts_end.num);
+          goto error;
+        } 
+    }
+  if (rpd->cont_end.num == 0 && rpd->cont_end.var == NULL) 
+    {
+      rpd->cont_end.num = fh_get_record_width (fh);
+      if (rpd->cont_beg.num != 0
+          && rpd->cont_beg.num > rpd->cont_end.num)
+        {
+          msg (SE, _("CONTINUED beginning column (%d) exceeds "
+                     "default CONTINUED ending column taken from file's "
+                     "record width (%d)."),
+               rpd->cont_beg.num, rpd->cont_end.num);
+          goto error;
+        } 
+    }
+  
+  lex_match ('=');
+  if (!parse_repeating_data (&rpd->first, &rpd->last))
+    goto error;
+
+  /* Calculate length if necessary. */
+  if (!saw_length)
+    {
+      struct dls_var_spec *iter;
+      
+      for (iter = rpd->first; iter; iter = iter->next)
+        if (iter->lc > rpd->length.num)
+          rpd->length.num = iter->lc;
+      assert (rpd->length.num != 0);
+    }
+  
+  if (table)
+    dump_fixed_table (rpd->first, fh, rpd->last->rec);
+
+  add_transformation (repeating_data_trns_proc, repeating_data_trns_free, rpd);
+
+  return lex_end_of_command ();
+
+ error:
+  repeating_data_trns_free (rpd);
+  return CMD_CASCADING_FAILURE;
+}
+
+/* Finds the input format specification for variable V and puts
+   it in SPEC.  Because of the way that DATA LIST is structured,
+   this is nontrivial. */
+static void 
+find_variable_input_spec (struct variable *v, struct fmt_spec *spec)
+{
+  size_t i;
+  
+  for (i = 0; i < n_trns; i++)
+    {
+      struct transformation *trns = &t_trns[i];
+      
+      if (trns->proc == data_list_trns_proc)
+       {
+          struct data_list_pgm *pgm = trns->private;
+         struct dls_var_spec *iter;
+
+         for (iter = pgm->first; 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_is_integer ())
+    {
+      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, adding
+   them to the linked list with head FIRST and tail LAST.
+   Returns nonzero only if successful.  */
+static int
+parse_repeating_data (struct dls_var_spec **first, struct dls_var_spec **last)
+{
+  struct fixed_parsing_state fx;
+  size_t i;
+
+  fx.recno = 0;
+  fx.sc = 1;
+
+  while (token != '.')
+    {
+      if (!parse_DATA_LIST_vars (&fx.name, &fx.name_cnt, PV_NONE))
+       return 0;
+
+      if (lex_is_number ())
+       {
+         if (!fixed_parse_compatible (&fx, first, last))
+           goto fail;
+       }
+      else if (token == '(')
+       {
+         if (!fixed_parse_fortran (&fx, first, last))
+           goto fail;
+       }
+      else
+       {
+         msg (SE, _("SPSS-like or FORTRAN-like format "
+                     "specification expected after variable names."));
+         goto fail;
+       }
+
+      for (i = 0; i < fx.name_cnt; i++)
+       free (fx.name[i]);
+      free (fx.name);
+    }
+  
+  return 1;
+
+ fail:
+  for (i = 0; i < fx.name_cnt; 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->var != NULL)
+    {
+      double v = case_num (c, n->var->fv);
+      return v != SYSMIS && v >= INT_MIN && v <= INT_MAX ? v : -1;
+    }
+  else
+    return n->num;
+}
+
+/* Parameter record passed to rpd_parse_record(). */
+struct rpd_parse_info 
+  {
+    struct repeating_data_trns *trns;  /* REPEATING DATA transformation. */
+    const char *line;   /* Line being parsed. */
+    size_t len;         /* Line length. */
+    int beg, end;       /* First and last column of first occurrence. */
+    int ofs;            /* Column offset between repeated occurrences. */
+    struct ccase *c;    /* Case to fill in. */
+    int verify_id;      /* Zero to initialize ID, nonzero to verify it. */
+    int max_occurs;     /* Max number of occurrences to parse. */
+  };
+
+/* Parses one record of repeated data and outputs corresponding
+   cases.  Returns number of occurrences parsed up to the
+   maximum specified in INFO. */
+static int
+rpd_parse_record (const struct rpd_parse_info *info)
+{
+  struct repeating_data_trns *t = info->trns;
+  int cur = info->beg;
+  int occurrences;
+
+  /* Handle record ID values. */
+  if (t->id_beg != 0)
+    {
+      union value id_temp[MAX_ELEMS_PER_VALUE];
+      
+      /* Parse record ID into V. */
+      {
+       struct data_in di;
+
+       data_in_finite_line (&di, info->line, info->len, t->id_beg, t->id_end);
+       di.v = info->verify_id ? id_temp : t->id_value;
+       di.flags = 0;
+       di.f1 = t->id_beg;
+       di.format = t->id_spec;
+
+       if (!data_in (&di))
+         return 0;
+      }
+
+      if (info->verify_id
+          && compare_values (id_temp, t->id_value, t->id_var->width) != 0)
+       {
+         char expected_str [MAX_FORMATTED_LEN + 1];
+         char actual_str [MAX_FORMATTED_LEN + 1];
+
+         data_out (expected_str, &t->id_var->print, t->id_value);
+          expected_str[t->id_var->print.w] = '\0';
+
+         data_out (actual_str, &t->id_var->print, id_temp);
+          actual_str[t->id_var->print.w] = '\0';
+           
+         tmsg (SE, RPD_ERR, 
+               _("Encountered mismatched record ID \"%s\" expecting \"%s\"."),
+               actual_str, expected_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 < info->max_occurs; )
+      {
+       if (cur + info->ofs > info->end + 1)
+         break;
+       occurrences++;
+
+       {
+         struct dls_var_spec *var_spec = t->first;
+       
+         for (; var_spec; var_spec = var_spec->next)
+           {
+             int fc = var_spec->fc - 1 + cur;
+             int lc = var_spec->lc - 1 + cur;
+
+             if (fc > info->len && !warned && var_spec->input.type != FMT_A)
+               {
+                 warned = 1;
+
+                 tmsg (SW, RPD_ERR,
+                       _("Variable %s starting in column %d extends "
+                         "beyond physical record length of %d."),
+                       var_spec->v->name, fc, info->len);
+               }
+             
+             {
+               struct data_in di;
+
+               data_in_finite_line (&di, info->line, info->len, fc, lc);
+               di.v = case_data_rw (info->c, var_spec->fv);
+               di.flags = 0;
+               di.f1 = fc + 1;
+               di.format = var_spec->input;
+
+               if (!data_in (&di))
+                 return 0;
+             }
+           }
+       }
+
+       cur += info->ofs;
+
+       if (!t->write_case (t->wc_data))
+         return 0;
+      }
+  }
+
+  return occurrences;
+}
+
+/* Reads one set of repetitions of the elements in the REPEATING
+   DATA structure.  Returns TRNS_CONTINUE on success,
+   TRNS_DROP_CASE on end of file or on failure, or TRNS_NEXT_CASE. */
+int
+repeating_data_trns_proc (void *trns_, struct ccase *c, int case_num UNUSED)
+{
+  struct repeating_data_trns *t = trns_;
+    
+  struct fixed_string line;       /* 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;
+    
+  dfm_push (t->reader);
+  
+  /* Read the current record. */
+  dfm_reread_record (t->reader, 1);
+  dfm_expand_tabs (t->reader);
+  if (dfm_eof (t->reader))
+    return TRNS_DROP_CASE;
+  dfm_get_record (t->reader, &line);
+  dfm_forward_record (t->reader);
+
+  /* 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 TRNS_NEXT_CASE;
+    }
+  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 TRNS_NEXT_CASE;
+    }
+  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 TRNS_DROP_CASE;
+    }
+  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 TRNS_DROP_CASE;
+    }
+
+  /* Parse the first record. */
+  if (!skip_first_record)
+    {
+      struct rpd_parse_info info;
+      info.trns = t;
+      info.line = ls_c_str (&line);
+      info.len = ls_length (&line);
+      info.beg = starts_beg;
+      info.end = starts_end;
+      info.ofs = length;
+      info.c = c;
+      info.verify_id = 0;
+      info.max_occurs = occurs_left;
+      code = rpd_parse_record (&info);
+      if (!code)
+        return TRNS_DROP_CASE;
+      occurs_left -= code;
+    }
+  else if (cont_beg == 0)
+    return TRNS_NEXT_CASE;
+
+  /* Make sure, if some occurrences are left, that we have
+     continuation records. */
+  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, (starts_end - starts_beg + 1) / length);
+      return TRNS_DROP_CASE;
+    }
+
+  /* Go on to additional records. */
+  while (occurs_left != 0)
+    {
+      struct rpd_parse_info info;
+
+      assert (occurs_left >= 0);
+
+      /* Read in another record. */
+      if (dfm_eof (t->reader))
+        {
+          tmsg (SE, RPD_ERR,
+                _("Unexpected end of file with %d repetitions "
+                  "remaining out of %d."),
+                occurs_left, occurs);
+          return TRNS_DROP_CASE;
+        }
+      dfm_expand_tabs (t->reader);
+      dfm_get_record (t->reader, &line);
+      dfm_forward_record (t->reader);
+
+      /* Parse this record. */
+      info.trns = t;
+      info.line = ls_c_str (&line);
+      info.len = ls_length (&line);
+      info.beg = cont_beg;
+      info.end = cont_end;
+      info.ofs = length;
+      info.c = c;
+      info.verify_id = 1;
+      info.max_occurs = occurs_left;
+      code = rpd_parse_record (&info);;
+      if (!code)
+        return TRNS_DROP_CASE;
+      occurs_left -= code;
+    }
+    
+  dfm_pop (t->reader);
+
+  /* FIXME: This is a kluge until we've implemented multiplexing of
+     transformations. */
+  return TRNS_NEXT_CASE;
+}
+
+/* Frees a REPEATING DATA transformation.
+   Returns true if successful, false if an I/O error occurred. */
+bool
+repeating_data_trns_free (void *rpd_) 
+{
+  struct repeating_data_trns *rpd = rpd_;
+  
+  destroy_dls_var_spec (rpd->first);
+  dfm_close_reader (rpd->reader);
+  free (rpd->id_value);
+  free (rpd);
+  return true;
+}
+
+/* Lets repeating_data_trns_proc() know how to write the cases
+   that it composes.  Not elegant. */
+void
+repeating_data_set_write_case (struct transformation *trns_,
+                               write_case_func *write_case,
+                               write_case_data wc_data) 
+{
+  struct repeating_data_trns *t = trns_->private;
+
+  assert (trns_->proc == repeating_data_trns_proc);
+  t->write_case = write_case;
+  t->wc_data = wc_data;
+}
diff --git a/src/language/data-io/data-list.h b/src/language/data-io/data-list.h
new file mode 100644 (file)
index 0000000..9b78ccd
--- /dev/null
@@ -0,0 +1,33 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef INCLUDED_DATA_LIST_H
+#define INCLUDED_DATA_LIST_H
+
+/* FIXME: This header is a kluge and should go away when we come
+   up with a less-klugy solution. */
+
+#include "variable.h"
+#include "procedure.h"
+
+trns_proc_func repeating_data_trns_proc;
+void repeating_data_set_write_case (struct transformation *,
+                                    write_case_func *, write_case_data);
+
+#endif /* data-list.h */
diff --git a/src/language/data-io/data-reader.c b/src/language/data-io/data-reader.c
new file mode 100644 (file)
index 0000000..24b27b4
--- /dev/null
@@ -0,0 +1,441 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-2004, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "data-reader.h"
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "message.h"
+#include "file-handle.h"
+#include "file-handle-def.h"
+#include "filename.h"
+#include "line-buffer.h"
+#include "lexer.h"
+#include "str.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+/* Flags for DFM readers. */
+enum dfm_reader_flags
+  {
+    DFM_ADVANCE = 002,          /* Read next line on dfm_get_record() call? */
+    DFM_SAW_BEGIN_DATA = 004,   /* For inline_file only, whether we've 
+                                   already read a BEGIN DATA line. */
+    DFM_TABS_EXPANDED = 010,    /* Tabs have been expanded. */
+  };
+
+/* Data file reader. */
+struct dfm_reader
+  {
+    struct file_handle *fh;     /* File handle. */
+    struct file_locator where;  /* Current location in data file. */
+    struct string line;         /* Current line. */
+    struct string scratch;      /* Extra line buffer. */
+    enum dfm_reader_flags flags; /* Zero or more of DFM_*. */
+    struct file_ext file;      /* Associated file. */
+    size_t pos;                 /* Offset in line of current character. */
+    unsigned eof_cnt;           /* # of attempts to advance past EOF. */
+  };
+
+/* Closes reader R opened by dfm_open_reader(). */
+void
+dfm_close_reader (struct dfm_reader *r)
+{
+  int still_open;
+  bool is_inline;
+
+  if (r == NULL)
+    return;
+
+  is_inline = r->fh == fh_inline_file ();
+  still_open = fh_close (r->fh, "data file", "rs");
+  if (still_open)
+    return;
+
+  if (!is_inline)
+    {
+      fn_close_ext (&r->file);
+      free (r->file.filename);
+      r->file.filename = NULL;
+    }
+  else
+    {
+      /* Skip any remaining data on the inline file. */
+      if (r->flags & DFM_SAW_BEGIN_DATA) 
+        {
+          dfm_reread_record (r, 0);
+          while (!dfm_eof (r))
+            dfm_forward_record (r); 
+        }
+    }
+
+  ds_destroy (&r->line);
+  ds_destroy (&r->scratch);
+  free (r);
+}
+
+/* Opens the file designated by file handle FH for reading as a
+   data file.  Providing fh_inline_file() for FH designates the
+   "inline file", that is, data included inline in the command
+   file between BEGIN FILE and END FILE.  Returns a reader if
+   successful, or a null pointer otherwise. */
+struct dfm_reader *
+dfm_open_reader (struct file_handle *fh)
+{
+  struct dfm_reader *r;
+  void **rp;
+
+  rp = fh_open (fh, FH_REF_FILE | FH_REF_INLINE, "data file", "rs");
+  if (rp == NULL)
+    return NULL;
+  if (*rp != NULL)
+    return *rp; 
+  
+  r = xmalloc (sizeof *r);
+  r->fh = fh;
+  ds_init (&r->line, 64);
+  ds_init (&r->scratch, 0);
+  r->flags = DFM_ADVANCE;
+  r->eof_cnt = 0;
+  if (fh != fh_inline_file ()) 
+    {
+      r->where.filename = fh_get_filename (fh);
+      r->where.line_number = 0; 
+      r->file.file = NULL;
+      r->file.filename = xstrdup (fh_get_filename (r->fh));
+      r->file.mode = "rb";
+      r->file.file = NULL;
+      r->file.sequence_no = NULL;
+      r->file.param = NULL;
+      r->file.postopen = NULL;
+      r->file.preclose = NULL;
+      if (!fn_open_ext (&r->file))
+        {
+          msg (ME, _("Could not open \"%s\" for reading as a data file: %s."),
+               fh_get_filename (r->fh), strerror (errno));
+          fh_close (fh,"data file", "rs");
+          free (r);
+          return NULL;
+        }
+    }
+  *rp = r;
+
+  return r;
+}
+
+/* Returns true if an I/O error occurred on READER, false otherwise. */
+bool
+dfm_reader_error (const struct dfm_reader *r) 
+{
+  return fh_get_referent (r->fh) == FH_REF_FILE && ferror (r->file.file);
+}
+
+/* Reads a record from the inline file into R.
+   Returns true if successful, false on failure. */
+static bool
+read_inline_record (struct dfm_reader *r)
+{
+  if ((r->flags & DFM_SAW_BEGIN_DATA) == 0)
+    {
+      r->flags |= DFM_SAW_BEGIN_DATA;
+
+      while (token == '.')
+        lex_get ();
+      if (!lex_force_match_id ("BEGIN") || !lex_force_match_id ("DATA"))
+        return false;
+      getl_set_prompt_style (GETL_PROMPT_DATA);
+    }
+      
+  if (!getl_read_line (NULL))
+    {
+      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."));
+      return false;
+    }
+
+  if (ds_length (&getl_buf) >= 8
+      && !strncasecmp (ds_c_str (&getl_buf), "end data", 8))
+    {
+      lex_set_prog (ds_c_str (&getl_buf) + ds_length (&getl_buf));
+      return false;
+    }
+
+  ds_replace (&r->line, ds_c_str (&getl_buf));
+  return true;
+}
+
+/* Reads a record from a disk file into R.
+   Returns true if successful, false on failure. */
+static bool
+read_file_record (struct dfm_reader *r)
+{
+  assert (r->fh != fh_inline_file ());
+  if (fh_get_mode (r->fh) == FH_MODE_TEXT)
+    {
+      ds_clear (&r->line);
+      if (!ds_gets (&r->line, r->file.file)) 
+        {
+          if (ferror (r->file.file))
+            msg (ME, _("Error reading file %s: %s."),
+                 fh_get_name (r->fh), strerror (errno));
+          return false;
+        }
+    }
+  else if (fh_get_mode (r->fh) == FH_MODE_BINARY)
+    {
+      size_t record_width = fh_get_record_width (r->fh);
+      size_t amt;
+
+      if (ds_length (&r->line) < record_width) 
+        ds_rpad (&r->line, record_width, 0);
+          
+      amt = fread (ds_c_str (&r->line), 1, record_width,
+                   r->file.file);
+      if (record_width != amt)
+        {
+          if (ferror (r->file.file))
+            msg (ME, _("Error reading file %s: %s."),
+                 fh_get_name (r->fh), strerror (errno));
+          else if (amt != 0)
+            msg (ME, _("%s: Partial record at end of file."),
+                 fh_get_name (r->fh));
+
+          return false;
+        }
+    }
+  else
+    abort ();
+
+  r->where.line_number++;
+
+  return true;
+}
+
+/* Reads a record from R, setting the current position to the
+   start of the line.  If an error occurs or end-of-file is
+   encountered, the current line is set to null. */
+static bool
+read_record (struct dfm_reader *r)
+{
+  return (fh_get_referent (r->fh) == FH_REF_FILE
+          ? read_file_record (r)
+          : read_inline_record (r));
+}
+
+/* Returns the number of attempts, thus far, to advance past
+   end-of-file in reader R.  Reads forward in HANDLE's file, if
+   necessary, to find out.
+
+   Normally, the user stops attempting to read from the file the
+   first time EOF is reached (a return value of 1).  If the user
+   tries to read past EOF again (a return value of 2 or more),
+   an error message is issued, and the caller should more
+   forcibly abort to avoid an infinite loop. */
+unsigned
+dfm_eof (struct dfm_reader *r) 
+{
+  if (r->flags & DFM_ADVANCE)
+    {
+      r->flags &= ~DFM_ADVANCE;
+
+      if (r->eof_cnt == 0 && read_record (r)) 
+        {
+          r->pos = 0;
+          return 0; 
+        }
+
+      r->eof_cnt++;
+      if (r->eof_cnt == 2)
+        {
+          if (r->fh != fh_inline_file ())
+            msg (ME, _("Attempt to read beyond end-of-file on file %s."),
+                 fh_get_name (r->fh));
+          else
+            msg (ME, _("Attempt to read beyond END DATA."));
+        }
+    }
+
+  return r->eof_cnt;
+}
+
+/* Returns the current record in the file corresponding to
+   HANDLE.  Aborts if reading from the file is necessary or at
+   end of file, so call dfm_eof() first.  Sets *LINE to the line,
+   which is not null-terminated.  The caller must not free or
+   modify the returned string.  */
+void
+dfm_get_record (struct dfm_reader *r, struct fixed_string *line)
+{
+  assert ((r->flags & DFM_ADVANCE) == 0);
+  assert (r->eof_cnt == 0);
+  assert (r->pos <= ds_length (&r->line));
+
+  line->string = ds_data (&r->line) + r->pos;
+  line->length = ds_length (&r->line) - r->pos;
+}
+
+/* Expands tabs in the current line into the equivalent number of
+   spaces, if appropriate for this kind of file.  Aborts if
+   reading from the file is necessary or at end of file, so call
+   dfm_eof() first.*/
+void
+dfm_expand_tabs (struct dfm_reader *r) 
+{
+  struct string temp;
+  size_t ofs, new_pos, tab_width;
+
+  assert ((r->flags & DFM_ADVANCE) == 0);
+  assert (r->eof_cnt == 0);
+  assert (r->pos <= ds_length (&r->line));
+
+  if (r->flags & DFM_TABS_EXPANDED)
+    return;
+  r->flags |= DFM_TABS_EXPANDED;
+
+  if (r->fh != fh_inline_file ()
+      && (fh_get_mode (r->fh) == FH_MODE_BINARY
+          || fh_get_tab_width (r->fh) == 0
+          || memchr (ds_c_str (&r->line), '\t', ds_length (&r->line)) == NULL))
+    return;
+
+  /* Expand tabs from r->line into r->scratch, and figure out
+     new value for r->pos. */
+  tab_width = fh_get_tab_width (r->fh);
+  ds_clear (&r->scratch);
+  new_pos = 0;
+  for (ofs = 0; ofs < ds_length (&r->line); ofs++)
+    {
+      unsigned char c;
+      
+      if (ofs == r->pos)
+        new_pos = ds_length (&r->scratch);
+
+      c = ds_c_str (&r->line)[ofs];
+      if (c != '\t')
+        ds_putc (&r->scratch, c);
+      else 
+        {
+          do
+            ds_putc (&r->scratch, ' ');
+          while (ds_length (&r->scratch) % tab_width != 0);
+        }
+    }
+
+  /* Swap r->line and r->scratch and set new r->pos. */
+  temp = r->line;
+  r->line = r->scratch;
+  r->scratch = temp;
+  r->pos = new_pos;
+}
+
+/* Causes dfm_get_record() to read in the next record the next time it
+   is executed on file HANDLE. */
+void
+dfm_forward_record (struct dfm_reader *r)
+{
+  r->flags |= DFM_ADVANCE;
+}
+
+/* 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.  */
+void
+dfm_reread_record (struct dfm_reader *r, size_t column)
+{
+  r->flags &= ~DFM_ADVANCE;
+  if (column < 1)
+    r->pos = 0;
+  else if (column > ds_length (&r->line))
+    r->pos = ds_length (&r->line);
+  else
+    r->pos = column - 1;
+}
+
+/* Sets the current line to begin COLUMNS characters following
+   the current start. */
+void
+dfm_forward_columns (struct dfm_reader *r, size_t columns)
+{
+  dfm_reread_record (r, (r->pos + 1) + columns);
+}
+
+/* Returns the 1-based column to which the line pointer in HANDLE
+   is set.  Unless dfm_reread_record() or dfm_forward_columns()
+   have been called, this is 1. */
+size_t
+dfm_column_start (struct dfm_reader *r)
+{
+  return r->pos + 1;
+}
+
+/* Pushes the filename and line number on the fn/ln stack. */
+void
+dfm_push (struct dfm_reader *r)
+{
+  if (r->fh != fh_inline_file ())
+    err_push_file_locator (&r->where);
+}
+
+/* Pops the filename and line number from the fn/ln stack. */
+void
+dfm_pop (struct dfm_reader *r)
+{
+  if (r->fh != fh_inline_file ())
+    err_pop_file_locator (&r->where);
+}
+\f
+/* BEGIN DATA...END DATA procedure. */
+
+/* Perform BEGIN DATA...END DATA as a procedure in itself. */
+int
+cmd_begin_data (void)
+{
+  struct dfm_reader *r;
+  bool ok;
+
+  if (!fh_is_open (fh_inline_file ()))
+    {
+      msg (SE, _("This command is not valid here since the current "
+                 "input program does not access the inline file."));
+      return CMD_CASCADING_FAILURE;
+    }
+
+  /* Open inline file. */
+  r = dfm_open_reader (fh_inline_file ());
+  r->flags |= DFM_SAW_BEGIN_DATA;
+
+  /* Input procedure reads from inline file. */
+  getl_set_prompt_style (GETL_PROMPT_DATA);
+  ok = procedure (NULL, NULL);
+
+  dfm_close_reader (r);
+
+  return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+}
diff --git a/src/language/data-io/data-reader.h b/src/language/data-io/data-reader.h
new file mode 100644 (file)
index 0000000..bbf5c52
--- /dev/null
@@ -0,0 +1,53 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef DFM_READ_H
+#define DFM_READ_H
+
+/* 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. */
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct file_handle;
+struct fixed_string;
+
+/* Input. */
+struct dfm_reader *dfm_open_reader (struct file_handle *);
+void dfm_close_reader (struct dfm_reader *);
+bool dfm_reader_error (const struct dfm_reader *);
+unsigned dfm_eof (struct dfm_reader *);
+void dfm_get_record (struct dfm_reader *, struct fixed_string *);
+void dfm_expand_tabs (struct dfm_reader *);
+
+/* Line control. */
+void dfm_forward_record (struct dfm_reader *);
+void dfm_reread_record (struct dfm_reader *, size_t column);
+void dfm_forward_columns (struct dfm_reader *, size_t columns);
+size_t dfm_column_start (struct dfm_reader *);
+
+/* File stack. */
+void dfm_push (struct dfm_reader *);
+void dfm_pop (struct dfm_reader *);
+
+#endif /* data-reader.h */
diff --git a/src/language/data-io/data-writer.c b/src/language/data-io/data-writer.c
new file mode 100644 (file)
index 0000000..993b81c
--- /dev/null
@@ -0,0 +1,146 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "data-writer.h"
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "message.h"
+#include "file-handle.h"
+#include "filename.h"
+#include "str.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* Data file writer. */
+struct dfm_writer
+  {
+    struct file_handle *fh;     /* File handle. */
+    struct file_ext file;      /* Associated file. */
+    char *bounce;               /* Bounce buffer for fixed-size fields. */
+  };
+
+/* Opens a file handle for writing as a data file. */
+struct dfm_writer *
+dfm_open_writer (struct file_handle *fh)
+{
+  struct dfm_writer *w;
+  void **aux;
+  
+  aux = fh_open (fh, FH_REF_FILE, "data file", "ws");
+  if (aux == NULL)
+    return NULL;
+  if (*aux != NULL)
+    return *aux;
+
+  w = *aux = xmalloc (sizeof *w);
+  w->fh = fh;
+  w->file.file = NULL;
+  w->bounce = NULL;
+
+  w->file.filename = xstrdup (fh_get_filename (w->fh));
+  w->file.mode = "wb";
+  w->file.file = NULL;
+  w->file.sequence_no = NULL;
+  w->file.param = NULL;
+  w->file.postopen = NULL;
+  w->file.preclose = NULL;
+      
+  if (!fn_open_ext (&w->file))
+    {
+      msg (ME, _("An error occurred while opening \"%s\" for writing "
+                 "as a data file: %s."),
+           fh_get_filename (w->fh), strerror (errno));
+      goto error;
+    }
+
+  return w;
+
+ error:
+  dfm_close_writer (w);
+  return NULL;
+}
+
+/* Returns false if an I/O error occurred on WRITER, true otherwise. */
+bool
+dfm_write_error (const struct dfm_writer *writer) 
+{
+  return ferror (writer->file.file);
+}
+
+/* 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 dfm_writer *w, const char *rec, size_t len)
+{
+  assert (w != NULL);
+
+  if (dfm_write_error (w))
+    return 0;
+  
+  if (fh_get_mode (w->fh) == FH_MODE_BINARY
+      && len < fh_get_record_width (w->fh))
+    {
+      size_t rec_width = fh_get_record_width (w->fh);
+      if (w->bounce == NULL)
+        w->bounce = xmalloc (rec_width);
+      memcpy (w->bounce, rec, len);
+      memset (&w->bounce[len], 0, rec_width - len);
+      rec = w->bounce;
+      len = rec_width;
+    }
+
+  fwrite (rec, len, 1, w->file.file);
+  return !dfm_write_error (w);
+}
+
+/* Closes data file writer W. */
+bool
+dfm_close_writer (struct dfm_writer *w)
+{
+  bool ok;
+
+  if (w == NULL)
+    return true;
+  if (fh_close (w->fh, "data file", "ws"))
+    return true;
+
+  ok = true;
+  if (w->file.file != NULL)
+    {
+      ok = !dfm_write_error (w);
+      if (!fn_close_ext (&w->file))
+        ok = false;
+
+      if (!ok)
+        msg (ME, _("I/O error occurred writing data file \"%s\"."),
+             fh_get_filename (w->fh));
+
+      free (w->file.filename);
+      w->file.filename = NULL;
+    }
+  free (w->bounce);
+  free (w);
+
+  return ok;
+}
diff --git a/src/language/data-io/data-writer.h b/src/language/data-io/data-writer.h
new file mode 100644 (file)
index 0000000..0eba2ab
--- /dev/null
@@ -0,0 +1,34 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef DFM_WRITE_H
+#define DFM_WRITE_H
+
+/* Writing data files. */
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct file_handle;
+struct dfm_writer *dfm_open_writer (struct file_handle *);
+bool dfm_close_writer (struct dfm_writer *);
+bool dfm_write_error (const struct dfm_writer *);
+int dfm_put_record (struct dfm_writer *, const char *rec, size_t len);
+
+#endif /* data-writer.h */
diff --git a/src/language/data-io/file-handle.h b/src/language/data-io/file-handle.h
new file mode 100644 (file)
index 0000000..2e8de05
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !file_handle_h
+#define file_handle_h 1
+
+/* File handles. */
+
+#include <stdbool.h>
+#include <stddef.h>
+#include "file-handle-def.h"
+
+struct file_handle *fh_parse (enum fh_referent);
+
+#endif /* !file_handle.h */
diff --git a/src/language/data-io/file-handle.q b/src/language/data-io/file-handle.q
new file mode 100644 (file)
index 0000000..65c7b25
--- /dev/null
@@ -0,0 +1,215 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "file-handle.h"
+#include "message.h"
+#include <errno.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "filename.h"
+#include "command.h"
+#include "lexer.h"
+#include "line-buffer.h"
+#include "message.h"
+#include "magic.h"
+#include "str.h"
+#include "variable.h"
+#include "file-handle-def.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* (headers) */
+
+
+/* (specification)
+   "FILE HANDLE" (fh_):
+     name=string;
+     lrecl=integer;
+     tabwidth=integer "x>=0" "%s must be nonnegative";
+     mode=mode:!character/image/scratch.
+*/
+/* (declarations) */
+/* (functions) */
+
+int
+cmd_file_handle (void)
+{
+  char handle_name[LONG_NAME_LEN + 1];
+  struct fh_properties properties = *fh_default_properties ();
+
+  struct cmd_file_handle cmd;
+  struct file_handle *handle;
+
+  if (!lex_force_id ())
+    return CMD_CASCADING_FAILURE;
+  str_copy_trunc (handle_name, sizeof handle_name, tokid);
+
+  handle = fh_from_name (handle_name);
+  if (handle != NULL)
+    {
+      msg (SE, _("File handle %s is already defined.  "
+                 "Use CLOSE FILE HANDLE before redefining a file handle."),
+          handle_name);
+      return CMD_CASCADING_FAILURE;
+    }
+
+  lex_get ();
+  if (!lex_force_match ('/'))
+    return CMD_CASCADING_FAILURE;
+
+  if (!parse_file_handle (&cmd))
+    return CMD_CASCADING_FAILURE;
+
+  if (lex_end_of_command () != CMD_SUCCESS)
+    goto lossage;
+
+  if (cmd.s_name == NULL && cmd.mode != FH_SCRATCH)
+    {
+      lex_sbc_missing ("NAME");
+      goto lossage;
+    }
+
+  switch (cmd.mode)
+    {
+    case FH_CHARACTER:
+      properties.mode = FH_MODE_TEXT;
+      if (cmd.sbc_tabwidth)
+        properties.tab_width = cmd.n_tabwidth[0];
+      break;
+    case FH_IMAGE:
+      properties.mode = FH_MODE_BINARY;
+      if (cmd.n_lrecl[0] == NOT_LONG)
+        msg (SE, _("Fixed-length records were specified on /RECFORM, but "
+                   "record length was not specified on /LRECL.  "
+                   "Assuming %d-character records."),
+             properties.record_width);
+      else if (cmd.n_lrecl[0] < 1)
+        msg (SE, _("Record length (%ld) must be at least one byte.  "
+                   "Assuming %d-character records."),
+             cmd.n_lrecl[0], properties.record_width);
+      else
+        properties.record_width = cmd.n_lrecl[0];
+      break;
+    default:
+      assert (0);
+    }
+
+  if (cmd.mode != FH_SCRATCH)
+    fh_create_file (handle_name, cmd.s_name, &properties);
+  else
+    fh_create_scratch (handle_name);
+
+  free_file_handle (&cmd);
+  return CMD_SUCCESS;
+
+ lossage:
+  free_file_handle (&cmd);
+  return CMD_CASCADING_FAILURE;
+}
+
+int
+cmd_close_file_handle (void) 
+{
+  struct file_handle *handle;
+
+  if (!lex_force_id ())
+    return CMD_CASCADING_FAILURE;
+  handle = fh_from_name (tokid);
+  if (handle == NULL)
+    return CMD_CASCADING_FAILURE;
+
+  fh_free (handle);
+
+  return CMD_SUCCESS;
+}
+
+/* Returns the name for REFERENT. */
+static const char *
+referent_name (enum fh_referent referent) 
+{
+  switch (referent) 
+    {
+    case FH_REF_FILE:
+      return _("file");
+    case FH_REF_INLINE:
+      return _("inline file");
+    case FH_REF_SCRATCH:
+      return _("scratch file");
+    default:
+      abort ();
+    }
+}
+
+/* Parses a file handle name, which may be a filename as a string
+   or a file handle name as an identifier.  The allowed types of
+   file handle are restricted to those in REFERENT_MASK.  Returns
+   the file handle when successful, a null pointer on failure. */
+struct file_handle *
+fh_parse (enum fh_referent referent_mask)
+{
+  struct file_handle *handle;
+
+  if (lex_match_id ("INLINE")) 
+    handle = fh_inline_file ();
+  else 
+    {
+      if (token != T_ID && token != T_STRING)
+        {
+          lex_error (_("expecting a file name or handle name"));
+          return NULL;
+        }
+
+      handle = NULL;
+      if (token == T_ID) 
+        handle = fh_from_name (tokid);
+      if (handle == NULL) 
+        handle = fh_from_filename (ds_c_str (&tokstr)); 
+      if (handle == NULL)
+        {
+          if (token != T_ID || tokid[0] != '#' || get_syntax () != ENHANCED) 
+            {
+              char *filename = ds_c_str (&tokstr);
+              char *handle_name = xasprintf ("\"%s\"", filename);
+              handle = fh_create_file (handle_name, filename,
+                                       fh_default_properties ());
+              free (handle_name);
+            }
+          else
+            handle = fh_create_scratch (tokid);
+        }
+      lex_get ();
+    }
+
+  if (!(fh_get_referent (handle) & referent_mask)) 
+    {
+      msg (SE, _("Handle for %s not allowed here."),
+           referent_name (fh_get_referent (handle)));
+      return NULL;
+    }
+
+  return handle;
+}
+
+/*
+   Local variables:
+   mode: c
+   End:
+*/
diff --git a/src/language/data-io/file-type.c b/src/language/data-io/file-type.c
new file mode 100644 (file)
index 0000000..a021dd5
--- /dev/null
@@ -0,0 +1,743 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "case.h"
+#include "command.h"
+#include "data-in.h"
+#include "data-reader.h"
+#include "dictionary.h"
+#include "message.h"
+#include "file-handle.h"
+#include "format.h"
+#include "lexer.h"
+#include "str.h"
+#include "variable.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* 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[LONG_NAME_LEN + 1]; /* 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. */
+struct file_type_pgm
+  {
+    int type;                  /* One of the FTY_* constants. */
+    struct dfm_reader *reader;  /* Data file to read. */
+    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. */
+    size_t case_size;           /* Case size in bytes. */
+  };
+
+static int parse_col_spec (struct col_spec *, const char *);
+static void create_col_var (struct col_spec *c);
+
+int cmd_file_type (void);
+
+/* Parses FILE TYPE command. */
+int
+cmd_file_type (void)
+{
+  static struct file_type_pgm *fty;     /* FIXME: static? WTF? */
+  struct file_handle *fh = fh_inline_file ();
+
+  /* Initialize. */
+  discard_variables ();
+
+  fty = xmalloc (sizeof *fty);
+  fty->reader = NULL;
+  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;
+
+  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."));
+      goto error;
+    }
+
+  while (token != '.')
+    {
+      if (lex_match_id ("FILE"))
+       {
+         lex_match ('=');
+         fh = fh_parse (FH_REF_FILE | FH_REF_INLINE);
+         if (fh == NULL)
+           goto error;
+       }
+      else if (lex_match_id ("RECORD"))
+       {
+         lex_match ('=');
+         if (!parse_col_spec (&fty->record, "####RECD"))
+           goto error;
+       }
+      else if (lex_match_id ("CASE"))
+       {
+         if (fty->type == FTY_MIXED)
+           {
+             msg (SE, _("The CASE subcommand is not valid on FILE TYPE "
+                        "MIXED."));
+             goto error;
+           }
+         
+         lex_match ('=');
+         if (!parse_col_spec (&fty->case_sbc, "####CASE"))
+           goto error;
+       }
+      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."));
+             goto error;
+           }
+       }
+      else if (lex_match_id ("DUPLICATE"))
+       {
+         if (fty->type == FTY_MIXED)
+           {
+             msg (SE, _("The DUPLICATE subcommand is not valid on "
+                        "FILE TYPE MIXED."));
+             goto error;
+           }
+
+         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."));
+                 goto error;
+               }
+             
+             fty->duplicate = 2;
+           }
+         else
+           {
+             msg (SE, _("WARN%s expected after DUPLICATE."),
+                  (fty->type == FTY_NESTED ? _(", NOWARN, or CASE")
+                   : _(" or NOWARN")));
+             goto error;
+           }
+       }
+      else if (lex_match_id ("MISSING"))
+       {
+         if (fty->type == FTY_MIXED)
+           {
+             msg (SE, _("The MISSING subcommand is not valid on "
+                        "FILE TYPE MIXED."));
+             goto error;
+           }
+         
+         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."));
+             goto error;
+           }
+       }
+      else if (lex_match_id ("ORDERED"))
+       {
+         if (fty->type != FTY_GROUPED)
+           {
+             msg (SE, _("ORDERED is only valid on FILE TYPE GROUPED."));
+             goto error;
+           }
+         
+         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."));
+             goto error;
+           }
+       }
+      else
+       {
+         lex_error (_("while expecting a valid subcommand"));
+         goto error;
+       }
+    }
+
+  if (fty->record.name[0] == 0)
+    {
+      msg (SE, _("The required RECORD subcommand was not present."));
+      goto error;
+    }
+
+  if (fty->type == FTY_GROUPED)
+    {
+      if (fty->case_sbc.name[0] == 0)
+       {
+         msg (SE, _("The required CASE subcommand was not present."));
+         goto error;
+       }
+      
+      if (!strcasecmp (fty->case_sbc.name, fty->record.name))
+       {
+         msg (SE, _("CASE and RECORD must specify different variable "
+                    "names."));
+         goto error;
+       }
+    }
+
+  fty->reader = dfm_open_reader (fh);
+  if (fty->reader == NULL)
+    goto error;
+  fh_set_default_handle (fh);
+
+  create_col_var (&fty->record);
+  if (fty->case_sbc.name[0])
+    create_col_var (&fty->case_sbc);
+  vfm_source = create_case_source (&file_type_source_class, fty);
+
+  return CMD_SUCCESS;
+
+ error:
+  free (fty);
+  return CMD_CASCADING_FAILURE;
+}
+
+/* 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 width;
+
+  if (formats[c->fmt].cat & FCAT_STRING)
+    width = c->nc;
+  else
+    width = 0;
+  c->v = dict_create_var (default_dict, c->name, 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;
+
+  /* Name. */
+  if (token == T_ID)
+    {
+      strcpy (c->name, tokid);
+      lex_get ();
+    }
+  else
+    strcpy (c->name, def_name);
+
+  /* First column. */
+  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 ();
+
+  /* Last column. */
+  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;
+
+  /* Format specifier. */
+  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, 1);
+}
+\f
+/* RECORD TYPE. */
+
+/* Parse the RECORD TYPE command. */
+int
+cmd_record_type (void)
+{
+  struct file_type_pgm *fty;
+  struct record_type *rct;
+
+  /* Make sure we're inside a FILE TYPE structure. */
+  if (pgm_state != STATE_INPUT
+      || !case_source_is_class (vfm_source, &file_type_source_class))
+    {
+      msg (SE, _("This command may only appear within a "
+                "FILE TYPE/END FILE TYPE structure."));
+      return CMD_CASCADING_FAILURE;
+    }
+
+  fty = vfm_source->aux;
+
+  /* Initialize the record_type structure. */
+  rct = xmalloc (sizeof *rct);
+  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 (fty->recs_tail && (fty->recs_tail->flags & RCT_OTHER))
+    {
+      msg (SE, _("OTHER may appear only on the last RECORD TYPE command."));
+      goto error;
+    }
+      
+  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."));
+         goto error;
+       }
+    }
+
+  /* Parse record type values. */
+  if (lex_match_id ("OTHER"))
+    rct->flags |= RCT_OTHER;
+  else
+    {
+      int mv = 0;
+
+      while (lex_is_number () || token == T_STRING)
+       {
+         if (rct->nv >= mv)
+           {
+             mv += 16;
+             rct->v = xnrealloc (rct->v, mv, sizeof *rct->v);
+           }
+
+         if (formats[fty->record.fmt].cat & FCAT_STRING)
+           {
+             if (!lex_force_string ())
+               goto error;
+             rct->v[rct->nv].c = xmalloc (fty->record.nc + 1);
+             buf_copy_str_rpad (rct->v[rct->nv].c, fty->record.nc + 1,
+                                 ds_c_str (&tokstr));
+           }
+         else
+           {
+             if (!lex_force_num ())
+               goto error;
+             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."));
+             goto error;
+           }
+
+         lex_match ('=');
+         if (!parse_col_spec (&rct->case_sbc, ""))
+           goto error;
+         if (rct->case_sbc.name[0])
+           {
+             msg (SE, _("No variable name may be specified for the "
+                        "CASE subcommand on RECORD TYPE."));
+             goto error;
+           }
+         
+         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."));
+             goto error;
+           }
+       }
+      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."));
+             goto error;
+           }
+       }
+      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."));
+             goto error;
+           }
+       }
+      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."));
+             goto error;
+           }
+       }
+      else
+       {
+         lex_error (_("while expecting a valid subcommand"));
+         goto error;
+       }
+    }
+
+  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;
+
+ error:
+  if (formats[fty->record.fmt].cat & FCAT_STRING) 
+    {
+      int i;
+      
+      for (i = 0; i < rct->nv; i++)
+        free (rct->v[i].c); 
+    }
+  free (rct->v);
+  free (rct);
+
+  return CMD_CASCADING_FAILURE;
+}
+\f
+/* END FILE TYPE. */
+
+int cmd_end_file_type (void);
+int
+cmd_end_file_type (void)
+{
+  struct file_type_pgm *fty;
+
+  if (pgm_state != STATE_INPUT
+      || case_source_is_class (vfm_source, &file_type_source_class))
+    {
+      msg (SE, _("This command may only appear within a "
+                "FILE TYPE/END FILE TYPE structure."));
+      return CMD_CASCADING_FAILURE;
+    }
+  fty = vfm_source->aux;
+  fty->case_size = dict_get_case_size (default_dict);
+
+  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 I/O error. */
+  discard_variables ();
+  return CMD_CASCADING_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 case C and calls write_case()
+   for each one.  Compare data-list.c:read_from_data_list.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+file_type_source_read (struct case_source *source,
+                       struct ccase *c,
+                       write_case_func *write_case UNUSED,
+                       write_case_data wc_data UNUSED)
+{
+  struct file_type_pgm *fty = source->aux;
+  struct fmt_spec format;
+
+  dfm_push (fty->reader);
+
+  format.type = fty->record.fmt;
+  format.w = fty->record.nc;
+  format.d = 0;
+  while (!dfm_eof (fty->reader))
+    {
+      struct fixed_string line;
+      struct record_type *iter;
+      union value v;
+      int i;
+
+      dfm_expand_tabs (fty->reader);
+      dfm_get_record (fty->reader, &line);
+      if (formats[fty->record.fmt].cat & FCAT_STRING)
+       {
+         struct data_in di;
+         
+         v.c = case_data_rw (c, fty->record.v->fv)->s;
+
+         data_in_finite_line (&di, ls_c_str (&line), ls_length (&line),
+                              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, ls_c_str (&line), ls_length (&line),
+                              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);
+
+          case_data_rw (c, fty->record.v->fv)->f = 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 (iter->v[i].f == v.f)
+                 goto found;
+           }
+         if (fty->wild)
+           msg (SW, _("Unknown record type %g."), v.f);
+       }
+      dfm_forward_record (fty->reader);
+      continue;
+
+    found:
+      /* Arrive here if there is a matching record_type, which is in
+         iter. */
+      dfm_forward_record (fty->reader);
+    }
+
+/*  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->reader);
+
+  return !dfm_reader_error (fty->reader);
+}
+
+static void
+file_type_source_destroy (struct case_source *source)
+{
+  struct file_type_pgm *fty = source->aux;
+  struct record_type *iter, *next;
+
+  cancel_transformations ();
+  dfm_close_reader (fty->reader);
+  for (iter = fty->recs_head; iter; iter = next)
+    {
+      next = iter->next;
+      free (iter);
+    }
+}
+
+const struct case_source_class file_type_source_class =
+  {
+    "FILE TYPE",
+    NULL,
+    file_type_source_read,
+    file_type_source_destroy,
+  };
diff --git a/src/language/data-io/get.c b/src/language/data-io/get.c
new file mode 100644 (file)
index 0000000..c6ae3ed
--- /dev/null
@@ -0,0 +1,1699 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <stdlib.h>
+#include "alloc.h"
+#include "any-reader.h"
+#include "any-writer.h"
+#include "case.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "file-handle.h"
+#include "hash.h"
+#include "lexer.h"
+#include "misc.h"
+#include "por-file-writer.h"
+#include "settings.h"
+#include "sys-file-writer.h"
+#include "str.h"
+#include "value-labels.h"
+#include "variable.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+/* Rearranging and reducing a dictionary. */
+static void start_case_map (struct dictionary *);
+static struct case_map *finish_case_map (struct dictionary *);
+static void map_case (const struct case_map *,
+                      const struct ccase *, struct ccase *);
+static void destroy_case_map (struct case_map *);
+
+static bool parse_dict_trim (struct dictionary *);
+\f
+/* Reading system and portable files. */
+
+/* Type of command. */
+enum reader_command 
+  {
+    GET_CMD,
+    IMPORT_CMD
+  };
+
+/* Case reader input program. */
+struct case_reader_pgm 
+  {
+    struct any_reader *reader;  /* File reader. */
+    struct case_map *map;       /* Map from file dict to active file dict. */
+    struct ccase bounce;        /* Bounce buffer. */
+  };
+
+static const struct case_source_class case_reader_source_class;
+
+static void case_reader_pgm_free (struct case_reader_pgm *);
+
+/* Parses a GET or IMPORT command. */
+static int
+parse_read_command (enum reader_command type)
+{
+  struct case_reader_pgm *pgm = NULL;
+  struct file_handle *fh = NULL;
+  struct dictionary *dict = NULL;
+
+  for (;;)
+    {
+      lex_match ('/');
+
+      if (lex_match_id ("FILE") || token == T_STRING)
+       {
+         lex_match ('=');
+
+         fh = fh_parse (FH_REF_FILE | FH_REF_SCRATCH);
+         if (fh == NULL)
+            goto error;
+       }
+      else if (type == IMPORT_CMD && 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"));
+              goto error;
+           }
+       }
+      else
+        break; 
+    }
+  
+  if (fh == NULL) 
+    {
+      lex_sbc_missing ("FILE");
+      goto error;
+    }
+              
+  discard_variables ();
+
+  pgm = xmalloc (sizeof *pgm);
+  pgm->reader = any_reader_open (fh, &dict);
+  pgm->map = NULL;
+  case_nullify (&pgm->bounce);
+  if (pgm->reader == NULL)
+    goto error;
+
+  case_create (&pgm->bounce, dict_get_next_value_idx (dict));
+  
+  start_case_map (dict);
+
+  while (token != '.')
+    {
+      lex_match ('/');
+      if (!parse_dict_trim (dict))
+        goto error;
+    }
+
+  pgm->map = finish_case_map (dict);
+  
+  dict_destroy (default_dict);
+  default_dict = dict;
+
+  vfm_source = create_case_source (&case_reader_source_class, pgm);
+
+  return CMD_SUCCESS;
+
+ error:
+  case_reader_pgm_free (pgm);
+  if (dict != NULL)
+    dict_destroy (dict);
+  return CMD_CASCADING_FAILURE;
+}
+
+/* Frees a struct case_reader_pgm. */
+static void
+case_reader_pgm_free (struct case_reader_pgm *pgm) 
+{
+  if (pgm != NULL) 
+    {
+      any_reader_close (pgm->reader);
+      destroy_case_map (pgm->map);
+      case_destroy (&pgm->bounce);
+      free (pgm);
+    }
+}
+
+/* Clears internal state related to case reader input procedure. */
+static void
+case_reader_source_destroy (struct case_source *source)
+{
+  struct case_reader_pgm *pgm = source->aux;
+  case_reader_pgm_free (pgm);
+}
+
+/* Reads all the cases from the data file into C and passes them
+   to WRITE_CASE one by one, passing WC_DATA.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+case_reader_source_read (struct case_source *source,
+                         struct ccase *c,
+                         write_case_func *write_case, write_case_data wc_data)
+{
+  struct case_reader_pgm *pgm = source->aux;
+  bool ok = true;
+
+  do
+    {
+      bool got_case;
+      if (pgm->map == NULL)
+        got_case = any_reader_read (pgm->reader, c);
+      else
+        {
+          got_case = any_reader_read (pgm->reader, &pgm->bounce);
+          if (got_case)
+            map_case (pgm->map, &pgm->bounce, c);
+        }
+      if (!got_case)
+        break;
+
+      ok = write_case (wc_data);
+    }
+  while (ok);
+
+  return ok && !any_reader_error (pgm->reader);
+}
+
+static const struct case_source_class case_reader_source_class =
+  {
+    "case reader",
+    NULL,
+    case_reader_source_read,
+    case_reader_source_destroy,
+  };
+\f
+/* GET. */
+int
+cmd_get (void) 
+{
+  return parse_read_command (GET_CMD);
+}
+
+/* IMPORT. */
+int
+cmd_import (void) 
+{
+  return parse_read_command (IMPORT_CMD);
+}
+\f
+/* Writing system and portable files. */ 
+
+/* Type of output file. */
+enum writer_type
+  {
+    SYSFILE_WRITER,     /* System file. */
+    PORFILE_WRITER      /* Portable file. */
+  };
+
+/* Type of a command. */
+enum command_type 
+  {
+    XFORM_CMD,          /* Transformation. */
+    PROC_CMD            /* Procedure. */
+  };
+
+/* File writer plus a case map. */
+struct case_writer
+  {
+    struct any_writer *writer;  /* File writer. */
+    struct case_map *map;       /* Map to output file dictionary
+                                   (null pointer for identity mapping). */
+    struct ccase bounce;        /* Bounce buffer for mapping (if needed). */
+  };
+
+/* Destroys AW. */
+static bool
+case_writer_destroy (struct case_writer *aw)
+{
+  bool ok = true;
+  if (aw != NULL) 
+    {
+      ok = any_writer_close (aw->writer);
+      destroy_case_map (aw->map);
+      case_destroy (&aw->bounce);
+      free (aw);
+    }
+  return ok;
+}
+
+/* Parses SAVE or XSAVE or EXPORT or XEXPORT command.
+   WRITER_TYPE identifies the type of file to write,
+   and COMMAND_TYPE identifies the type of command.
+
+   On success, returns a writer.
+   For procedures only, sets *RETAIN_UNSELECTED to true if cases
+   that would otherwise be excluded by FILTER or USE should be
+   included.
+
+   On failure, returns a null pointer. */
+static struct case_writer *
+parse_write_command (enum writer_type writer_type,
+                     enum command_type command_type,
+                     bool *retain_unselected)
+{
+  /* Common data. */
+  struct file_handle *handle; /* Output file. */
+  struct dictionary *dict;    /* Dictionary for output file. */
+  struct case_writer *aw;      /* Writer. */  
+
+  /* Common options. */
+  bool print_map;             /* Print map?  TODO. */
+  bool print_short_names;     /* Print long-to-short name map.  TODO. */
+  struct sfm_write_options sysfile_opts;
+  struct pfm_write_options porfile_opts;
+
+  assert (writer_type == SYSFILE_WRITER || writer_type == PORFILE_WRITER);
+  assert (command_type == XFORM_CMD || command_type == PROC_CMD);
+  assert ((retain_unselected != NULL) == (command_type == PROC_CMD));
+
+  if (command_type == PROC_CMD)
+    *retain_unselected = true;
+
+  handle = NULL;
+  dict = dict_clone (default_dict);
+  aw = xmalloc (sizeof *aw);
+  aw->writer = NULL;
+  aw->map = NULL;
+  case_nullify (&aw->bounce);
+  print_map = false;
+  print_short_names = false;
+  sysfile_opts = sfm_writer_default_options ();
+  porfile_opts = pfm_writer_default_options ();
+
+  start_case_map (dict);
+  dict_delete_scratch_vars (dict);
+
+  lex_match ('/');
+  for (;;)
+    {
+      if (lex_match_id ("OUTFILE"))
+       {
+          if (handle != NULL) 
+            {
+              lex_sbc_only_once ("OUTFILE");
+              goto error; 
+            }
+          
+         lex_match ('=');
+      
+         handle = fh_parse (FH_REF_FILE | FH_REF_SCRATCH);
+         if (handle == NULL)
+           goto error;
+       }
+      else if (lex_match_id ("NAMES"))
+        print_short_names = true;
+      else if (lex_match_id ("PERMISSIONS")) 
+        {
+          bool cw;
+          
+          lex_match ('=');
+          if (lex_match_id ("READONLY"))
+            cw = false;
+          else if (lex_match_id ("WRITEABLE"))
+            cw = true;
+          else
+            {
+              lex_error (_("expecting %s or %s"), "READONLY", "WRITEABLE");
+              goto error;
+            }
+          sysfile_opts.create_writeable = porfile_opts.create_writeable = cw;
+        }
+      else if (command_type == PROC_CMD && lex_match_id ("UNSELECTED")) 
+        {
+          lex_match ('=');
+          if (lex_match_id ("RETAIN"))
+            *retain_unselected = true;
+          else if (lex_match_id ("DELETE"))
+            *retain_unselected = false;
+          else
+            {
+              lex_error (_("expecting %s or %s"), "RETAIN", "DELETE");
+              goto error;
+            }
+        }
+      else if (writer_type == SYSFILE_WRITER && lex_match_id ("COMPRESSED"))
+       sysfile_opts.compress = true;
+      else if (writer_type == SYSFILE_WRITER && lex_match_id ("UNCOMPRESSED"))
+       sysfile_opts.compress = false;
+      else if (writer_type == SYSFILE_WRITER && lex_match_id ("VERSION"))
+       {
+         lex_match ('=');
+         if (!lex_force_int ())
+            goto error;
+          sysfile_opts.version = lex_integer ();
+          lex_get ();
+       }
+      else if (writer_type == PORFILE_WRITER && lex_match_id ("TYPE")) 
+        {
+          lex_match ('=');
+          if (lex_match_id ("COMMUNICATIONS"))
+            porfile_opts.type = PFM_COMM;
+          else if (lex_match_id ("TAPE"))
+            porfile_opts.type = PFM_TAPE;
+          else
+            {
+              lex_error (_("expecting %s or %s"), "COMM", "TAPE");
+              goto error;
+            }
+        }
+      else if (writer_type == PORFILE_WRITER && lex_match_id ("DIGITS")) 
+        {
+          lex_match ('=');
+          if (!lex_force_int ())
+            goto error;
+          porfile_opts.digits = lex_integer ();
+          lex_get ();
+        }
+      else if (!parse_dict_trim (dict))
+        goto error;
+      
+      if (!lex_match ('/'))
+       break;
+    }
+  if (lex_end_of_command () != CMD_SUCCESS)
+    goto error;
+
+  if (handle == NULL) 
+    {
+      lex_sbc_missing ("OUTFILE");
+      goto error;
+    }
+
+  dict_compact_values (dict);
+  aw->map = finish_case_map (dict);
+  if (aw->map != NULL)
+    case_create (&aw->bounce, dict_get_next_value_idx (dict));
+
+  if (fh_get_referent (handle) == FH_REF_FILE) 
+    {
+      switch (writer_type) 
+        {
+        case SYSFILE_WRITER:
+          aw->writer = any_writer_from_sfm_writer (
+            sfm_open_writer (handle, dict, sysfile_opts));
+          break;
+        case PORFILE_WRITER:
+          aw->writer = any_writer_from_pfm_writer (
+            pfm_open_writer (handle, dict, porfile_opts));
+          break;
+        }
+    }
+  else
+    aw->writer = any_writer_open (handle, dict);
+  dict_destroy (dict);
+  
+  return aw;
+
+ error:
+  case_writer_destroy (aw);
+  dict_destroy (dict);
+  return NULL;
+}
+
+/* Writes case C to writer AW. */
+static bool
+case_writer_write_case (struct case_writer *aw, struct ccase *c) 
+{
+  if (aw->map != NULL) 
+    {
+      map_case (aw->map, c, &aw->bounce);
+      c = &aw->bounce; 
+    }
+  return any_writer_write (aw->writer, c);
+}
+\f
+/* SAVE and EXPORT. */
+
+static bool output_proc (struct ccase *, void *);
+
+/* Parses and performs the SAVE or EXPORT procedure. */
+static int
+parse_output_proc (enum writer_type writer_type)
+{
+  bool retain_unselected;
+  struct variable *saved_filter_variable;
+  struct case_writer *aw;
+  bool ok;
+
+  aw = parse_write_command (writer_type, PROC_CMD, &retain_unselected);
+  if (aw == NULL) 
+    return CMD_CASCADING_FAILURE;
+
+  saved_filter_variable = dict_get_filter (default_dict);
+  if (retain_unselected) 
+    dict_set_filter (default_dict, NULL);
+  ok = procedure (output_proc, aw);
+  dict_set_filter (default_dict, saved_filter_variable);
+
+  case_writer_destroy (aw);
+  return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+}
+
+/* Writes case C to file. */
+static bool
+output_proc (struct ccase *c, void *aw_) 
+{
+  struct case_writer *aw = aw_;
+  return case_writer_write_case (aw, c);
+}
+
+int
+cmd_save (void) 
+{
+  return parse_output_proc (SYSFILE_WRITER);
+}
+
+int
+cmd_export (void) 
+{
+  return parse_output_proc (PORFILE_WRITER);
+}
+\f
+/* XSAVE and XEXPORT. */
+
+/* Transformation. */
+struct output_trns 
+  {
+    struct case_writer *aw;      /* Writer. */
+  };
+
+static trns_proc_func output_trns_proc;
+static trns_free_func output_trns_free;
+
+/* Parses the XSAVE or XEXPORT transformation command. */
+static int
+parse_output_trns (enum writer_type writer_type) 
+{
+  struct output_trns *t = xmalloc (sizeof *t);
+  t->aw = parse_write_command (writer_type, XFORM_CMD, NULL);
+  if (t->aw == NULL) 
+    {
+      free (t);
+      return CMD_CASCADING_FAILURE;
+    }
+
+  add_transformation (output_trns_proc, output_trns_free, t);
+  return CMD_SUCCESS;
+}
+
+/* Writes case C to the system file specified on XSAVE or XEXPORT. */
+static int
+output_trns_proc (void *trns_, struct ccase *c, int case_num UNUSED)
+{
+  struct output_trns *t = trns_;
+  case_writer_write_case (t->aw, c);
+  return TRNS_CONTINUE;
+}
+
+/* Frees an XSAVE or XEXPORT transformation.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+output_trns_free (void *trns_)
+{
+  struct output_trns *t = trns_;
+  bool ok = true;
+
+  if (t != NULL)
+    {
+      ok = case_writer_destroy (t->aw);
+      free (t);
+    }
+  return ok;
+}
+
+/* XSAVE command. */
+int
+cmd_xsave (void) 
+{
+  return parse_output_trns (SYSFILE_WRITER);
+}
+
+/* XEXPORT command. */
+int
+cmd_xexport (void) 
+{
+  return parse_output_trns (PORFILE_WRITER);
+}
+\f
+static bool rename_variables (struct dictionary *dict);
+static bool drop_variables (struct dictionary *dict);
+static bool keep_variables (struct dictionary *dict);
+
+/* Commands that read and write system files share a great deal
+   of common syntactic structure for rearranging and dropping
+   variables.  This function parses this syntax and modifies DICT
+   appropriately.  Returns true on success, false on failure. */
+static bool
+parse_dict_trim (struct dictionary *dict)
+{
+  if (lex_match_id ("MAP")) 
+    {
+      /* FIXME. */
+      return true;
+    }
+  else if (lex_match_id ("DROP"))
+    return drop_variables (dict);
+  else if (lex_match_id ("KEEP"))
+    return keep_variables (dict);
+  else if (lex_match_id ("RENAME"))
+    return rename_variables (dict);
+  else
+    {
+      lex_error (_("expecting a valid subcommand"));
+      return false;
+    }
+}
+
+/* Parses and performs the RENAME subcommand of GET and SAVE. */
+static bool
+rename_variables (struct dictionary *dict)
+{
+  size_t i;
+
+  int success = 0;
+
+  struct variable **v;
+  char **new_names;
+  size_t nv, nn;
+  char *err_name;
+
+  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 (dict_lookup_var (dict, tokid) != NULL)
+       {
+         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;
+       }
+      
+      dict_rename_var (dict, v, tokid);
+      lex_get ();
+      return 1;
+    }
+
+  nv = nn = 0;
+  v = NULL;
+  new_names = 0;
+  group = 1;
+  while (lex_match ('('))
+    {
+      size_t old_nv = nv;
+
+      if (!parse_variables (dict, &v, &nv, PV_NO_DUPLICATE | PV_APPEND))
+       goto done;
+      if (!lex_match ('='))
+       {
+         msg (SE, _("`=' expected after variable list."));
+         goto done;
+       }
+      if (!parse_DATA_LIST_vars (&new_names, &nn, PV_APPEND | PV_NO_SCRATCH))
+       goto done;
+      if (nn != nv)
+       {
+         msg (SE, _("Number of variables on left side of `=' (%d) does not "
+                     "match number of variables on right side (%d), in "
+                     "parenthesized group %d of RENAME subcommand."),
+              (unsigned) (nv - old_nv), (unsigned) (nn - old_nv), group);
+         goto done;
+       }
+      if (!lex_force_match (')'))
+       goto done;
+      group++;
+    }
+
+  if (!dict_rename_vars (dict, v, new_names, nv, &err_name)) 
+    {
+      msg (SE, _("Requested renaming duplicates variable name %s."), err_name);
+      goto done;
+    }
+  success = 1;
+
+ done:
+  for (i = 0; i < nn; i++)
+    free (new_names[i]);
+  free (new_names);
+  free (v);
+
+  return success;
+}
+
+/* Parses and performs the DROP subcommand of GET and SAVE.
+   Returns true if successful, false on failure.*/
+static bool
+drop_variables (struct dictionary *dict)
+{
+  struct variable **v;
+  size_t nv;
+
+  lex_match ('=');
+  if (!parse_variables (dict, &v, &nv, PV_NONE))
+    return false;
+  dict_delete_vars (dict, v, nv);
+  free (v);
+
+  if (dict_get_var_cnt (dict) == 0)
+    {
+      msg (SE, _("Cannot DROP all variables from dictionary."));
+      return false;
+    }
+  return true;
+}
+
+/* Parses and performs the KEEP subcommand of GET and SAVE.
+   Returns true if successful, false on failure.*/
+static bool
+keep_variables (struct dictionary *dict)
+{
+  struct variable **v;
+  size_t nv;
+  size_t i;
+
+  lex_match ('=');
+  if (!parse_variables (dict, &v, &nv, PV_NONE))
+    return false;
+
+  /* Move the specified variables to the beginning. */
+  dict_reorder_vars (dict, v, nv);
+          
+  /* Delete the remaining variables. */
+  v = xnrealloc (v, dict_get_var_cnt (dict) - nv, sizeof *v);
+  for (i = nv; i < dict_get_var_cnt (dict); i++)
+    v[i - nv] = dict_get_var (dict, i);
+  dict_delete_vars (dict, v, dict_get_var_cnt (dict) - nv);
+  free (v);
+
+  return true;
+}
+\f
+/* MATCH FILES. */
+
+#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. */
+    struct any_reader *reader;  /* File reader. */
+    struct dictionary *dict;   /* Dictionary from system file. */
+
+    /* IN subcommand. */
+    char *in_name;              /* Variable name. */
+    struct variable *in_var;    /* Variable (in master dictionary). */
+
+    struct ccase input;         /* Input record. */
+  };
+
+/* MATCH FILES procedure. */
+struct mtf_proc 
+  {
+    struct mtf_file *head;      /* First file mentioned on FILE or TABLE. */
+    struct mtf_file *tail;      /* Last file mentioned on FILE or TABLE. */
+
+    bool ok;                    /* False if I/O error occurs. */
+
+    size_t by_cnt;              /* Number of variables on BY subcommand. */
+
+    /* Names of FIRST, LAST variables. */
+    char first[LONG_NAME_LEN + 1], last[LONG_NAME_LEN + 1];
+    
+    struct dictionary *dict;    /* Dictionary of output file. */
+    struct case_sink *sink;     /* Sink to receive output. */
+    struct ccase mtf_case;      /* Case used for output. */
+
+    unsigned seq_num;           /* Have we initialized this variable? */
+    unsigned *seq_nums;         /* Sequence numbers for each var in dict. */
+  };
+
+static bool mtf_free (struct mtf_proc *);
+static bool mtf_close_file (struct mtf_file *);
+static int mtf_merge_dictionary (struct dictionary *const, struct mtf_file *);
+static bool mtf_delete_file_in_place (struct mtf_proc *, struct mtf_file **);
+
+static bool mtf_read_nonactive_records (void *);
+static bool mtf_processing_finish (void *);
+static bool mtf_processing (struct ccase *, void *);
+
+static char *var_type_description (struct variable *);
+
+static void set_master (struct variable *, struct variable *master);
+static struct variable *get_master (struct variable *);
+
+/* Parse and execute the MATCH FILES command. */
+int
+cmd_match_files (void)
+{
+  struct mtf_proc mtf;
+  struct mtf_file *first_table = NULL;
+  struct mtf_file *iter;
+  
+  bool used_active_file = false;
+  bool saw_table = false;
+  bool saw_in = false;
+
+  bool ok;
+  
+  mtf.head = mtf.tail = NULL;
+  mtf.by_cnt = 0;
+  mtf.first[0] = '\0';
+  mtf.last[0] = '\0';
+  mtf.dict = dict_create ();
+  mtf.sink = NULL;
+  case_nullify (&mtf.mtf_case);
+  mtf.seq_num = 0;
+  mtf.seq_nums = NULL;
+  dict_set_case_limit (mtf.dict, dict_get_case_limit (default_dict));
+
+  lex_match ('/');
+  while (token == T_ID
+         && (lex_id_match ("FILE", tokid) || lex_id_match ("TABLE", tokid)))
+    {
+      struct mtf_file *file = xmalloc (sizeof *file);
+
+      if (lex_match_id ("FILE"))
+        file->type = MTF_FILE;
+      else if (lex_match_id ("TABLE"))
+        {
+          file->type = MTF_TABLE;
+          saw_table = true;
+        }
+      else
+        assert (0);
+      lex_match ('=');
+
+      file->by = NULL;
+      file->handle = NULL;
+      file->reader = NULL;
+      file->dict = NULL;
+      file->in_name = NULL;
+      file->in_var = NULL;
+      case_nullify (&file->input);
+
+      /* 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;
+        }
+
+      if (lex_match ('*'))
+        {
+          file->handle = NULL;
+          file->reader = NULL;
+              
+          if (used_active_file)
+            {
+              msg (SE, _("The active file may not be specified more "
+                         "than once."));
+              goto error;
+            }
+          used_active_file = true;
+
+          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 error;
+            }
+
+          if (temporary != 0)
+            {
+              msg (SE,
+                   _("MATCH FILES may not be used after TEMPORARY when "
+                     "the active file is an input source.  "
+                     "Temporary transformations will be made permanent."));
+              cancel_temporary (); 
+            }
+
+          file->dict = default_dict;
+        }
+      else
+        {
+          file->handle = fh_parse (FH_REF_FILE | FH_REF_SCRATCH);
+          if (file->handle == NULL)
+            goto error;
+
+          file->reader = any_reader_open (file->handle, &file->dict);
+          if (file->reader == NULL)
+            goto error;
+
+          case_create (&file->input, dict_get_next_value_idx (file->dict));
+        }
+
+      while (lex_match ('/'))
+        if (lex_match_id ("RENAME")) 
+          {
+            if (!rename_variables (file->dict))
+              goto error; 
+          }
+        else if (lex_match_id ("IN"))
+          {
+            lex_match ('=');
+            if (token != T_ID)
+              {
+                lex_error (NULL);
+                goto error;
+              }
+
+            if (file->in_name != NULL)
+              {
+                msg (SE, _("Multiple IN subcommands for a single FILE or "
+                           "TABLE."));
+                goto error;
+              }
+            file->in_name = xstrdup (tokid);
+            lex_get ();
+            saw_in = true;
+          }
+
+      mtf_merge_dictionary (mtf.dict, file);
+    }
+  
+  while (token != '.')
+    {
+      if (lex_match (T_BY))
+       {
+          struct variable **by;
+          
+         if (mtf.by_cnt)
+           {
+             msg (SE, _("BY may appear at most once."));
+             goto error;
+           }
+             
+         lex_match ('=');
+         if (!parse_variables (mtf.dict, &by, &mtf.by_cnt,
+                               PV_NO_DUPLICATE | PV_NO_SCRATCH))
+           goto error;
+
+          for (iter = mtf.head; iter != NULL; iter = iter->next)
+            {
+              size_t i;
+         
+              iter->by = xnmalloc (mtf.by_cnt, sizeof *iter->by);
+
+              for (i = 0; i < mtf.by_cnt; i++)
+                {
+                  iter->by[i] = dict_lookup_var (iter->dict, by[i]->name);
+                  if (iter->by[i] == NULL)
+                    {
+                      msg (SE, _("File %s lacks BY variable %s."),
+                           iter->handle ? fh_get_name (iter->handle) : "*",
+                           by[i]->name);
+                      free (by);
+                      goto error;
+                    }
+                }
+            }
+          free (by);
+       }
+      else if (lex_match_id ("FIRST")) 
+        {
+          if (mtf.first[0] != '\0')
+            {
+              msg (SE, _("FIRST may appear at most once."));
+              goto error;
+            }
+             
+         lex_match ('=');
+          if (!lex_force_id ())
+            goto error;
+          strcpy (mtf.first, tokid);
+          lex_get ();
+        }
+      else if (lex_match_id ("LAST")) 
+        {
+          if (mtf.last[0] != '\0')
+            {
+              msg (SE, _("LAST may appear at most once."));
+              goto error;
+            }
+             
+         lex_match ('=');
+          if (!lex_force_id ())
+            goto error;
+          strcpy (mtf.last, tokid);
+          lex_get ();
+        }
+      else if (lex_match_id ("MAP"))
+       {
+         /* FIXME. */
+       }
+      else if (lex_match_id ("DROP")) 
+        {
+          if (!drop_variables (mtf.dict))
+            goto error;
+        }
+      else if (lex_match_id ("KEEP")) 
+        {
+          if (!keep_variables (mtf.dict))
+            goto error;
+        }
+      else
+       {
+         lex_error (NULL);
+         goto error;
+       }
+
+      if (!lex_match ('/') && token != '.') 
+        {
+          lex_end_of_command ();
+          goto error;
+        }
+    }
+
+  if (mtf.by_cnt == 0)
+    {
+      if (saw_table)
+        {
+          msg (SE, _("BY is required when TABLE is specified."));
+          goto error;
+        }
+      if (saw_in)
+        {
+          msg (SE, _("BY is required when IN is specified."));
+          goto error;
+        }
+    }
+
+  /* Set up mapping from each file's variables to master
+     variables. */
+  for (iter = mtf.head; iter != NULL; iter = iter->next)
+    {
+      struct dictionary *d = iter->dict;
+      int i;
+
+      for (i = 0; i < dict_get_var_cnt (d); i++)
+        {
+          struct variable *v = dict_get_var (d, i);
+          struct variable *mv = dict_lookup_var (mtf.dict, v->name);
+          if (mv != NULL)
+            set_master (v, mv);
+        }
+    }
+
+  /* Add IN variables to master dictionary. */
+  for (iter = mtf.head; iter != NULL; iter = iter->next) 
+    if (iter->in_name != NULL)
+      {
+        iter->in_var = dict_create_var (mtf.dict, iter->in_name, 0);
+        if (iter->in_var == NULL)
+          {
+            msg (SE, _("IN variable name %s duplicates an "
+                       "existing variable name."),
+                 iter->in_var->name);
+            goto error;
+          }
+        iter->in_var->print = iter->in_var->write
+          = make_output_format (FMT_F, 1, 0);
+      }
+    
+  /* 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(s) that have minimum BY
+     values.  Store all the values from these input records into
+     the output record.
+
+     4. 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.
+
+     5. Write the output record.
+
+     6. 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.
+
+     7. Repeat from step 2.
+
+     Unfortunately, this algorithm can't be implemented in a
+     straightforward way because there's no function to read a
+     record from the active file.  Instead, it has to be written
+     as a state machine.
+
+     FIXME: For merging large numbers of files (more than 10?) a
+     better algorithm would use a heap for finding minimum
+     values. */
+
+  if (!used_active_file)
+    discard_variables ();
+
+  dict_compact_values (mtf.dict);
+  mtf.sink = create_case_sink (&storage_sink_class, mtf.dict, NULL);
+  if (mtf.sink->class->open != NULL)
+    mtf.sink->class->open (mtf.sink);
+
+  mtf.seq_nums = xcalloc (dict_get_var_cnt (mtf.dict), sizeof *mtf.seq_nums);
+  case_create (&mtf.mtf_case, dict_get_next_value_idx (mtf.dict));
+
+  if (!mtf_read_nonactive_records (&mtf))
+    goto error;
+
+  if (used_active_file) 
+    ok = procedure (mtf_processing, &mtf) && mtf_processing_finish (&mtf);
+  else
+    ok = mtf_processing_finish (&mtf);
+
+  free_case_source (vfm_source);
+  vfm_source = NULL;
+
+  dict_destroy (default_dict);
+  default_dict = mtf.dict;
+  mtf.dict = NULL;
+  vfm_source = mtf.sink->class->make_source (mtf.sink);
+  free_case_sink (mtf.sink);
+  
+  if (!mtf_free (&mtf))
+    ok = false;
+  return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+  
+ error:
+  mtf_free (&mtf);
+  return CMD_CASCADING_FAILURE;
+}
+
+/* Repeats 2...7 an arbitrary number of times. */
+static bool
+mtf_processing_finish (void *mtf_)
+{
+  struct mtf_proc *mtf = mtf_;
+  struct mtf_file *iter;
+
+  /* Find the active file and delete it. */
+  for (iter = mtf->head; iter; iter = iter->next)
+    if (iter->handle == NULL)
+      {
+        if (!mtf_delete_file_in_place (mtf, &iter))
+          abort ();
+        break;
+      }
+  
+  while (mtf->head && mtf->head->type == MTF_FILE)
+    if (!mtf_processing (NULL, mtf))
+      return false;
+
+  return true;
+}
+
+/* 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;
+}
+
+/* Closes FILE and frees its associated data.
+   Returns true if successful, false if an I/O error
+   occurred on FILE. */
+static bool
+mtf_close_file (struct mtf_file *file)
+{
+  bool ok = file->reader == NULL || !any_reader_error (file->reader);
+  free (file->by);
+  any_reader_close (file->reader);
+  if (file->handle != NULL)
+    dict_destroy (file->dict);
+  case_destroy (&file->input);
+  free (file->in_name);
+  free (file);
+  return ok;
+}
+
+/* Free all the data for the MATCH FILES procedure.
+   Returns true if successful, false if an I/O error
+   occurred. */
+static bool
+mtf_free (struct mtf_proc *mtf)
+{
+  struct mtf_file *iter, *next;
+  bool ok = true;
+
+  for (iter = mtf->head; iter; iter = next)
+    {
+      next = iter->next;
+      assert (iter->dict != mtf->dict);
+      if (!mtf_close_file (iter))
+        ok = false;
+    }
+  
+  if (mtf->dict)
+    dict_destroy (mtf->dict);
+  case_destroy (&mtf->mtf_case);
+  free (mtf->seq_nums);
+
+  return ok;
+}
+
+/* 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.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+mtf_delete_file_in_place (struct mtf_proc *mtf, struct mtf_file **file)
+{
+  struct mtf_file *f = *file;
+  int i;
+
+  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;
+
+  if (f->in_var != NULL)
+    case_data_rw (&mtf->mtf_case, f->in_var->fv)->f = 0.;
+  for (i = 0; i < dict_get_var_cnt (f->dict); i++)
+    {
+      struct variable *v = dict_get_var (f->dict, i);
+      struct variable *mv = get_master (v);
+      if (mv != NULL) 
+        {
+          union value *out = case_data_rw (&mtf->mtf_case, mv->fv);
+         
+          if (v->type == NUMERIC)
+            out->f = SYSMIS;
+          else
+            memset (out->s, ' ', v->width);
+        } 
+    }
+
+  return mtf_close_file (f);
+}
+
+/* Read a record from every input file except the active file.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+mtf_read_nonactive_records (void *mtf_)
+{
+  struct mtf_proc *mtf = mtf_;
+  struct mtf_file *iter, *next;
+  bool ok = true;
+
+  for (iter = mtf->head; ok && iter != NULL; iter = next)
+    {
+      next = iter->next;
+      if (iter->handle && !any_reader_read (iter->reader, &iter->input)) 
+        if (!mtf_delete_file_in_place (mtf, &iter))
+          ok = false;
+    }
+  return ok;
+}
+
+/* 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_proc *mtf,
+                       struct mtf_file *a, struct mtf_file *b,
+                       struct ccase *c)
+{
+  struct ccase *ca = case_is_null (&a->input) ? c : &a->input;
+  struct ccase *cb = case_is_null (&b->input) ? c : &b->input;
+  assert ((a == NULL) + (b == NULL) + (c == NULL) <= 1);
+  return case_compare_2dict (ca, cb, a->by, b->by, mtf->by_cnt);
+}
+
+/* Perform one iteration of steps 3...7 above.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+mtf_processing (struct ccase *c, void *mtf_)
+{
+  struct mtf_proc *mtf = mtf_;
+
+  /* Do we need another record from the active file? */
+  bool read_active_file;
+
+  assert (mtf->head != NULL);
+  if (mtf->head->type == MTF_TABLE)
+    return true;
+  
+  do
+    {
+      struct mtf_file *min_head, *min_tail; /* Files with minimum BY values. */
+      struct mtf_file *max_head, *max_tail; /* Files with non-minimum BYs. */
+      struct mtf_file *iter, *next;
+
+      read_active_file = false;
+      
+      /* 3. Find the FILE input record(s) that have minimum BY
+         values.  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) 
+        {
+          int cmp = mtf_compare_BY_values (mtf, min_head, iter, c);
+          if (cmp < 0) 
+            {
+              if (max_head)
+                max_tail = max_tail->next_min = iter;
+              else
+                max_head = max_tail = iter;
+            }
+          else if (cmp == 0) 
+           min_tail = min_tail->next_min = iter;
+          else /* cmp > 0 */
+            {
+              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;
+            }
+        }
+      
+      /* 4. 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. */
+      for (; iter != NULL; iter = next)
+       {
+         assert (iter->type == MTF_TABLE);
+      
+         next = iter->next;
+          for (;;) 
+            {
+              int cmp = mtf_compare_BY_values (mtf, min_head, iter, c);
+              if (cmp < 0) 
+                {
+                  if (max_head)
+                    max_tail = max_tail->next_min = iter;
+                  else
+                    max_head = max_tail = iter;
+                }
+              else if (cmp == 0)
+                min_tail = min_tail->next_min = iter;
+              else /* cmp > 0 */
+                {
+                  if (iter->handle == NULL)
+                    return true;
+                  if (any_reader_read (iter->reader, &iter->input))
+                    continue;
+                  if (!mtf_delete_file_in_place (mtf, &iter))
+                    return false;
+                }
+              break;
+            }
+       }
+
+      /* Next sequence number. */
+      mtf->seq_num++;
+
+      /* 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 < dict_get_var_cnt (iter->dict); i++)
+           {
+             struct variable *v = dict_get_var (iter->dict, i);
+              struct variable *mv = get_master (v);
+         
+             if (mv != NULL && mtf->seq_nums[mv->index] != mtf->seq_num) 
+                {
+                  struct ccase *record
+                    = case_is_null (&iter->input) ? c : &iter->input;
+                  union value *out = case_data_rw (&mtf->mtf_case, mv->fv);
+
+                  mtf->seq_nums[mv->index] = mtf->seq_num;
+                  if (v->type == NUMERIC)
+                    out->f = case_num (record, v->fv);
+                  else
+                    memcpy (out->s, case_str (record, v->fv), v->width);
+                } 
+            }
+          if (iter->in_var != NULL)
+            case_data_rw (&mtf->mtf_case, iter->in_var->fv)->f = 1.;
+
+          if (iter->type == MTF_FILE && iter->handle == NULL)
+            read_active_file = true;
+       }
+
+      /* 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 < dict_get_var_cnt (iter->dict); i++)
+           {
+             struct variable *v = dict_get_var (iter->dict, i);
+              struct variable *mv = get_master (v);
+
+             if (mv != NULL && mtf->seq_nums[mv->index] != mtf->seq_num) 
+                {
+                  union value *out = case_data_rw (&mtf->mtf_case, mv->fv);
+                  mtf->seq_nums[mv->index] = mtf->seq_num;
+
+                  if (v->type == NUMERIC)
+                    out->f = SYSMIS;
+                  else
+                    memset (out->s, ' ', v->width);
+                }
+            }
+          if (iter->in_var != NULL)
+            case_data_rw (&mtf->mtf_case, iter->in_var->fv)->f = 0.;
+       }
+
+      /* 5. Write the output record. */
+      mtf->sink->class->write (mtf->sink, &mtf->mtf_case);
+
+      /* 6. 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; iter = next)
+       {
+         next = iter->next_min;
+         if (iter->reader != NULL
+              && !any_reader_read (iter->reader, &iter->input))
+            if (!mtf_delete_file_in_place (mtf, &iter))
+              return false;
+       }
+    }
+  while (!read_active_file
+         && mtf->head != NULL && mtf->head->type == MTF_FILE);
+
+  return true;
+}
+
+/* Merge the dictionary for file F into master dictionary M. */
+static int
+mtf_merge_dictionary (struct dictionary *const m, struct mtf_file *f)
+{
+  struct dictionary *d = f->dict;
+  const char *d_docs, *m_docs;
+  int i;
+
+  if (dict_get_label (m) == NULL)
+    dict_set_label (m, dict_get_label (d));
+
+  d_docs = dict_get_documents (d);
+  m_docs = dict_get_documents (m);
+  if (d_docs != NULL) 
+    {
+      if (m_docs == NULL)
+        dict_set_documents (m, d_docs);
+      else
+        {
+          char *new_docs;
+          size_t new_len;
+
+          new_len = strlen (m_docs) + strlen (d_docs);
+          new_docs = xmalloc (new_len + 1);
+          strcpy (new_docs, m_docs);
+          strcat (new_docs, d_docs);
+          dict_set_documents (m, new_docs);
+          free (new_docs);
+        }
+    }
+  
+  for (i = 0; i < dict_get_var_cnt (d); i++)
+    {
+      struct variable *dv = dict_get_var (d, i);
+      struct variable *mv = dict_lookup_var (m, dv->name);
+
+      if (dict_class_from_id (dv->name) == DC_SCRATCH)
+        continue;
+
+      if (mv != NULL)
+        {
+          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_get_name (f->handle),
+                   var_type_description (dv), var_type_description (mv));
+              return 0;
+            }
+        
+          if (dv->width == mv->width)
+            {
+              if (val_labs_count (dv->val_labs)
+                  && !val_labs_count (mv->val_labs))
+                mv->val_labs = val_labs_copy (dv->val_labs);
+              if (!mv_is_empty (&dv->miss) && mv_is_empty (&mv->miss))
+                mv_copy (&mv->miss, &dv->miss);
+            }
+
+          if (dv->label && !mv->label)
+            mv->label = xstrdup (dv->label);
+        }
+      else
+        mv = dict_clone_var_assert (m, dv, dv->name);
+    }
+
+  return 1;
+}
+
+/* Marks V's master variable as MASTER. */
+static void
+set_master (struct variable *v, struct variable *master) 
+{
+  var_attach_aux (v, master, NULL);
+}
+
+/* Returns the master variable corresponding to V,
+   as set with set_master(). */
+static struct variable *
+get_master (struct variable *v) 
+{
+  return v->aux;
+}
+\f
+
+\f
+/* Case map.
+
+   A case map copies data from a case that corresponds for one
+   dictionary to a case that corresponds to a second dictionary
+   derived from the first by, optionally, deleting, reordering,
+   or renaming variables.  (No new variables may be created.)
+   */
+
+/* A case map. */
+struct case_map
+  {
+    size_t value_cnt;   /* Number of values in map. */
+    int *map;           /* For each destination index, the
+                           corresponding source index. */
+  };
+
+/* Prepares dictionary D for producing a case map.  Afterward,
+   the caller may delete, reorder, or rename variables within D
+   at will before using finish_case_map() to produce the case
+   map.
+
+   Uses D's aux members, which must otherwise not be in use. */
+static void
+start_case_map (struct dictionary *d) 
+{
+  size_t var_cnt = dict_get_var_cnt (d);
+  size_t i;
+  
+  for (i = 0; i < var_cnt; i++)
+    {
+      struct variable *v = dict_get_var (d, i);
+      int *src_fv = xmalloc (sizeof *src_fv);
+      *src_fv = v->fv;
+      var_attach_aux (v, src_fv, var_dtor_free);
+    }
+}
+
+/* Produces a case map from dictionary D, which must have been
+   previously prepared with start_case_map().
+
+   Does not retain any reference to D, and clears the aux members
+   set up by start_case_map().
+
+   Returns the new case map, or a null pointer if no mapping is
+   required (that is, no data has changed position). */
+static struct case_map *
+finish_case_map (struct dictionary *d) 
+{
+  struct case_map *map;
+  size_t var_cnt = dict_get_var_cnt (d);
+  size_t i;
+  int identity_map;
+
+  map = xmalloc (sizeof *map);
+  map->value_cnt = dict_get_next_value_idx (d);
+  map->map = xnmalloc (map->value_cnt, sizeof *map->map);
+  for (i = 0; i < map->value_cnt; i++)
+    map->map[i] = -1;
+
+  identity_map = 1;
+  for (i = 0; i < var_cnt; i++) 
+    {
+      struct variable *v = dict_get_var (d, i);
+      int *src_fv = (int *) var_detach_aux (v);
+      size_t idx;
+
+      if (v->fv != *src_fv)
+        identity_map = 0;
+      
+      for (idx = 0; idx < v->nv; idx++)
+        {
+          int src_idx = *src_fv + idx;
+          int dst_idx = v->fv + idx;
+          
+          assert (map->map[dst_idx] == -1);
+          map->map[dst_idx] = src_idx;
+        }
+      free (src_fv);
+    }
+
+  if (identity_map) 
+    {
+      destroy_case_map (map);
+      return NULL;
+    }
+
+  while (map->value_cnt > 0 && map->map[map->value_cnt - 1] == -1)
+    map->value_cnt--;
+
+  return map;
+}
+
+/* Maps from SRC to DST, applying case map MAP. */
+static void
+map_case (const struct case_map *map,
+          const struct ccase *src, struct ccase *dst) 
+{
+  size_t dst_idx;
+
+  assert (map != NULL);
+  assert (src != NULL);
+  assert (dst != NULL);
+  assert (src != dst);
+
+  for (dst_idx = 0; dst_idx < map->value_cnt; dst_idx++)
+    {
+      int src_idx = map->map[dst_idx];
+      if (src_idx != -1)
+        *case_data_rw (dst, dst_idx) = *case_data (src, src_idx);
+    }
+}
+
+/* Destroys case map MAP. */
+static void
+destroy_case_map (struct case_map *map) 
+{
+  if (map != NULL) 
+    {
+      free (map->map);
+      free (map);
+    }
+}
diff --git a/src/language/data-io/inpt-pgm.c b/src/language/data-io/inpt-pgm.c
new file mode 100644 (file)
index 0000000..99bfb23
--- /dev/null
@@ -0,0 +1,426 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <float.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "case.h"
+#include "command.h"
+#include "data-list.h"
+#include "data-reader.h"
+#include "dictionary.h"
+#include "message.h"
+#include "expressions/public.h"
+#include "file-handle.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+#include "variable.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+/* Indicates how a `union value' should be initialized. */
+enum value_init_type
+  {
+    INP_NUMERIC = 01,          /* Numeric. */
+    INP_STRING = 0,            /* String. */
+    
+    INP_INIT_ONCE = 02,                /* Initialize only once. */
+    INP_REINIT = 0,            /* Reinitialize for each iteration. */
+  };
+
+struct input_program_pgm 
+  {
+    enum value_init_type *init; /* How to initialize each `union value'. */
+    size_t init_cnt;            /* Number of elements in inp_init. */
+    size_t case_size;           /* Size of case in bytes. */
+  };
+
+static trns_proc_func end_case_trns_proc, reread_trns_proc, end_file_trns_proc;
+static trns_free_func reread_trns_free;
+
+int
+cmd_input_program (void)
+{
+  discard_variables ();
+
+  /* FIXME: we shouldn't do this here, but I'm afraid that other
+     code will check the class of vfm_source. */
+  vfm_source = create_case_source (&input_program_source_class, NULL);
+
+  return lex_end_of_command ();
+}
+
+int
+cmd_end_input_program (void)
+{
+  struct input_program_pgm *inp;
+  size_t i;
+
+  if (!case_source_is_class (vfm_source, &input_program_source_class))
+    {
+      msg (SE, _("No matching INPUT PROGRAM command."));
+      return CMD_CASCADING_FAILURE;
+    }
+  
+  if (dict_get_next_value_idx (default_dict) == 0)
+    msg (SW, _("No data-input or transformation commands specified "
+        "between INPUT PROGRAM and END INPUT PROGRAM."));
+
+  /* Mark the boundary between INPUT PROGRAM transformations and
+     ordinary transformations. */
+  f_trns = n_trns;
+
+  /* Figure out how to initialize each input case. */
+  inp = xmalloc (sizeof *inp);
+  inp->init_cnt = dict_get_next_value_idx (default_dict);
+  inp->init = xnmalloc (inp->init_cnt, sizeof *inp->init);
+  for (i = 0; i < inp->init_cnt; i++)
+    inp->init[i] = -1;
+  for (i = 0; i < dict_get_var_cnt (default_dict); i++)
+    {
+      struct variable *var = dict_get_var (default_dict, i);
+      enum value_init_type value_init;
+      size_t j;
+      
+      value_init = var->type == NUMERIC ? INP_NUMERIC : INP_STRING;
+      value_init |= var->reinit ? INP_REINIT : INP_INIT_ONCE;
+
+      for (j = 0; j < var->nv; j++)
+        inp->init[j + var->fv] = value_init;
+    }
+  for (i = 0; i < inp->init_cnt; i++)
+    assert (inp->init[i] != -1);
+  inp->case_size = dict_get_case_size (default_dict);
+
+  /* Put inp into vfm_source for later use. */
+  vfm_source->aux = inp;
+
+  return lex_end_of_command ();
+}
+
+/* Initializes case C.  Called before the first case is read. */
+static void
+init_case (const struct input_program_pgm *inp, struct ccase *c)
+{
+  size_t i;
+
+  for (i = 0; i < inp->init_cnt; i++)
+    switch (inp->init[i]) 
+      {
+      case INP_NUMERIC | INP_INIT_ONCE:
+        case_data_rw (c, i)->f = 0.0;
+        break;
+      case INP_NUMERIC | INP_REINIT:
+        case_data_rw (c, i)->f = SYSMIS;
+        break;
+      case INP_STRING | INP_INIT_ONCE:
+      case INP_STRING | INP_REINIT:
+        memset (case_data_rw (c, i)->s, ' ', sizeof case_data_rw (c, i)->s);
+        break;
+      default:
+        assert (0);
+      }
+}
+
+/* Clears case C.  Called between reading successive records. */
+static void
+clear_case (const struct input_program_pgm *inp, struct ccase *c)
+{
+  size_t i;
+
+  for (i = 0; i < inp->init_cnt; i++)
+    switch (inp->init[i]) 
+      {
+      case INP_NUMERIC | INP_INIT_ONCE:
+        break;
+      case INP_NUMERIC | INP_REINIT:
+        case_data_rw (c, i)->f = SYSMIS;
+        break;
+      case INP_STRING | INP_INIT_ONCE:
+        break;
+      case INP_STRING | INP_REINIT:
+        memset (case_data_rw (c, i)->s, ' ', sizeof case_data_rw (c, i)->s);
+        break;
+      default:
+        assert (0);
+      }
+}
+
+/* Executes each transformation in turn on a `blank' case.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+input_program_source_read (struct case_source *source,
+                           struct ccase *c,
+                           write_case_func *write_case,
+                           write_case_data wc_data)
+{
+  struct input_program_pgm *inp = source->aux;
+  size_t i;
+
+  /* Nonzero if there were any END CASE commands in the set of
+     transformations.  If so, we don't automatically write out
+     cases. */
+  int end_case = 0;
+
+  /* FIXME?  This is the number of cases sent out of the input
+     program, not the number of cases written to the procedure.
+     The difference should only show up in $CASENUM in COMPUTE.
+     We should check behavior against SPSS. */
+  int cases_written = 0;
+
+  assert (inp != NULL);
+
+  /* Figure end_case. */
+  for (i = 0; i < f_trns; i++)
+    if (t_trns[i].proc == end_case_trns_proc)
+      end_case = 1;
+
+  /* FIXME: This is an ugly kluge. */
+  for (i = 0; i < f_trns; i++)
+    if (t_trns[i].proc == repeating_data_trns_proc)
+      repeating_data_set_write_case (t_trns[i].private, write_case, wc_data);
+
+  init_case (inp, c);
+  for (;;)
+    {
+      /* Perform transformations on `blank' case. */
+      for (i = 0; i < f_trns; )
+       {
+          int code;
+
+          if (t_trns[i].proc == end_case_trns_proc) 
+            {
+              cases_written++;
+              if (!write_case (wc_data))
+                return false;
+              clear_case (inp, c);
+              i++;
+              continue;
+            }
+
+         code = t_trns[i].proc (t_trns[i].private, c, cases_written + 1);
+         switch (code)
+           {
+           case TRNS_CONTINUE:
+             i++;
+             break;
+
+            case TRNS_DROP_CASE:
+              abort ();
+
+            case TRNS_ERROR:
+              return false;
+
+           case TRNS_NEXT_CASE:
+             goto next_case;
+
+           case TRNS_END_FILE:
+              return true;
+
+           default:
+             i = code;
+             break;
+           }
+       }
+
+      /* Write the case if appropriate. */
+      if (!end_case) 
+        {
+          cases_written++;
+          if (!write_case (wc_data))
+            return false;
+        }
+
+      /* Blank out the case for the next iteration. */
+    next_case:
+      clear_case (inp, c);
+    }
+}
+
+/* Destroys an INPUT PROGRAM source. */
+static void
+input_program_source_destroy (struct case_source *source)
+{
+  struct input_program_pgm *inp = source->aux;
+
+  cancel_transformations ();
+
+  if (inp != NULL) 
+    {
+      free (inp->init);
+      free (inp);
+    }
+}
+
+const struct case_source_class input_program_source_class =
+  {
+    "INPUT PROGRAM",
+    NULL,
+    input_program_source_read,
+    input_program_source_destroy,
+  };
+\f
+int
+cmd_end_case (void)
+{
+  if (!case_source_is_class (vfm_source, &input_program_source_class))
+    {
+      msg (SE, _("This command may only be executed between INPUT PROGRAM "
+                "and END INPUT PROGRAM."));
+      return CMD_CASCADING_FAILURE;
+    }
+
+  add_transformation (end_case_trns_proc, NULL, NULL);
+
+  return lex_end_of_command ();
+}
+
+/* Should never be called, because this is handled in
+   input_program_source_read(). */
+int
+end_case_trns_proc (void *trns_ UNUSED, struct ccase *c UNUSED,
+                    int case_num UNUSED)
+{
+  abort ();
+}
+
+/* REREAD transformation. */
+struct reread_trns
+  {
+    struct dfm_reader *reader; /* File to move file pointer back on. */
+    struct expression *column; /* Column to reset file pointer to. */
+  };
+
+/* Parses REREAD command. */
+int
+cmd_reread (void)
+{
+  struct file_handle *fh;       /* File to be re-read. */
+  struct expression *e;         /* Expression for column to set. */
+  struct reread_trns *t;        /* Created transformation. */
+
+  fh = fh_get_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_CASCADING_FAILURE;
+           }
+         
+         e = expr_parse (default_dict, EXPR_NUMBER);
+         if (!e)
+           return CMD_CASCADING_FAILURE;
+       }
+      else if (lex_match_id ("FILE"))
+       {
+         lex_match ('=');
+          fh = fh_parse (FH_REF_FILE | FH_REF_INLINE);
+         if (fh == NULL)
+           {
+             expr_free (e);
+             return CMD_CASCADING_FAILURE;
+           }
+         lex_get ();
+       }
+      else
+       {
+         lex_error (NULL);
+         expr_free (e);
+       }
+    }
+
+  t = xmalloc (sizeof *t);
+  t->reader = dfm_open_reader (fh);
+  t->column = e;
+  add_transformation (reread_trns_proc, reread_trns_free, t);
+
+  return CMD_SUCCESS;
+}
+
+/* Executes a REREAD transformation. */
+static int
+reread_trns_proc (void *t_, struct ccase *c, int case_num)
+{
+  struct reread_trns *t = t_;
+
+  if (t->column == NULL)
+    dfm_reread_record (t->reader, 1);
+  else
+    {
+      double column = expr_evaluate_num (t->column, c, case_num);
+      if (!finite (column) || column < 1)
+       {
+         msg (SE, _("REREAD: Column numbers must be positive finite "
+              "numbers.  Column set to 1."));
+         dfm_reread_record (t->reader, 1);
+       }
+      else
+       dfm_reread_record (t->reader, column);
+    }
+  return TRNS_CONTINUE;
+}
+
+/* Frees a REREAD transformation.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+reread_trns_free (void *t_)
+{
+  struct reread_trns *t = t_;
+  expr_free (t->column);
+  dfm_close_reader (t->reader);
+  return true;
+}
+
+/* Parses END FILE command. */
+int
+cmd_end_file (void)
+{
+  if (!case_source_is_class (vfm_source, &input_program_source_class))
+    {
+      msg (SE, _("This command may only be executed between INPUT PROGRAM "
+                "and END INPUT PROGRAM."));
+      return CMD_CASCADING_FAILURE;
+    }
+
+  add_transformation (end_file_trns_proc, NULL, NULL);
+
+  return lex_end_of_command ();
+}
+
+/* Executes an END FILE transformation. */
+static int
+end_file_trns_proc (void *trns_ UNUSED, struct ccase *c UNUSED,
+                    int case_num UNUSED)
+{
+  return TRNS_END_FILE;
+}
diff --git a/src/language/data-io/list.q b/src/language/data-io/list.q
new file mode 100644 (file)
index 0000000..0834e2a
--- /dev/null
@@ -0,0 +1,725 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "case.h"
+#include "command.h"
+#include "dictionary.h"
+#include "lexer.h"
+#include "message.h"
+#include "magic.h"
+#include "misc.h"
+#include "htmlP.h"
+#include "output.h"
+#include "size_max.h"
+#include "manager.h"
+#include "table.h"
+#include "variable.h"
+#include "procedure.h"
+#include "format.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* (headers) */
+
+#include "debug-print.h"
+
+/* (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. */
+    size_t n_vertical; /* Number of labels to list vertically. */
+    size_t header_rows;        /* Number of header rows. */
+    char **header;     /* The header itself. */
+  };
+
+/* Parsed command. */
+static struct cmd_list cmd;
+
+/* Current case number. */
+static int case_idx;
+
+/* Line buffer. */
+static char *line_buf;
+
+/* TTY-style output functions. */
+static unsigned n_lines_remaining (struct outp_driver *d);
+static unsigned n_chars_width (struct outp_driver *d);
+static void write_line (struct outp_driver *d, char *s);
+
+/* Other functions. */
+static bool list_cases (struct ccase *, void *);
+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 unsigned
+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 unsigned
+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;
+  bool ok;
+
+  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)
+    dict_get_vars (default_dict, &cmd.v_variables, &cmd.n_variables,
+                  (1u << DC_SYSTEM) | (1u << DC_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)
+    {
+      if (dict_get_weight (default_dict) != NULL)
+       {
+         size_t i;
+
+         for (i = 0; i < cmd.n_variables; i++)
+           if (cmd.v_variables[i] == dict_get_weight (default_dict))
+             break;
+         if (i >= cmd.n_variables)
+           {
+             /* Add the weight variable to the end of the variable list. */
+             cmd.n_variables++;
+             cmd.v_variables = xnrealloc (cmd.v_variables, cmd.n_variables,
+                                           sizeof *cmd.v_variables);
+             cmd.v_variables[cmd.n_variables - 1]
+                = dict_get_weight (default_dict);
+           }
+       }
+      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 = make_output_format (FMT_F,
+                                              (cmd.last == LONG_MAX
+                                               ? 5 : intlog10 (cmd.last)), 0);
+
+      /* Add the weight variable at the beginning of the variable list. */
+      cmd.n_variables++;
+      cmd.v_variables = xnrealloc (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;
+    }
+
+  determine_layout ();
+
+  case_idx = 0;
+  ok = procedure_with_splits (write_all_headers, list_cases, NULL, NULL);
+  free (line_buf);
+
+  clean_up ();
+
+  return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+}
+
+/* Writes headers to all devices.  This is done at the beginning of
+   each SPLIT FILE group. */
+static void
+write_all_headers (void *aux UNUSED)
+{
+  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);
+         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);
+         
+         {
+           size_t 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)
+    {
+      size_t i;
+      size_t x;
+      
+      /* Allocate, initialize header. */
+      prc->header = xnmalloc (prc->header_rows, sizeof *prc->header);
+      {
+       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];
+         size_t j;
+
+         memset (&prc->header[prc->header_rows - 1][x], '-', v->print.w);
+         x += v->print.w - 1;
+         for (j = 0; j < 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. */
+  if (prc->header_rows >= 2) 
+    {
+      size_t i;
+        
+      for (i = prc->header_rows - 1; i-- != 0; )
+        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;
+       size_t 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))
+    {
+      size_t column;   /* Current column. */
+      int width;       /* Accumulated width. */
+      int height;       /* Height of vertical names. */
+      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, height = 0, column = 0;
+          column < cmd.n_variables && width <= max_width;
+          column++) 
+        {
+          struct variable *v = cmd.v_variables[column];
+          width += v->print.w;
+          if (strlen (v->name) > height)
+            height = strlen (v->name);
+        }
+      
+      /* If it fit then we need to determine how many labels can be
+         written horizontally. */
+      if (width <= max_width && height <= SHORT_NAME_LEN)
+       {
+#ifndef NDEBUG
+         prc->n_vertical = SIZE_MAX;
+#endif
+         for (column = cmd.n_variables; column-- != 0; )
+           {
+             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 != SIZE_MAX);
+
+         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,
+                                   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);
+}
+
+/* Writes case C to output. */
+static bool
+list_cases (struct ccase *c, void *aux UNUSED)
+{
+  struct outp_driver *d;
+  
+  case_idx++;
+  if (case_idx < cmd.first || case_idx > cmd.last
+      || (cmd.step != 1 && (case_idx - cmd.first) % cmd.step))
+    return true;
+
+  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;
+             }
+
+            if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
+             data_out (&line_buf[x], &v->print, case_data (c, v->fv));
+            else 
+              {
+                union value case_idx_value;
+                case_idx_value.f = case_idx;
+                data_out (&line_buf[x], &v->print, &case_idx_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];
+           char buf[41];
+           
+            if ((formats[v->print.type].cat & FCAT_STRING) || v->fv != -1)
+             data_out (buf, &v->print, case_data (c, v->fv));
+            else 
+              {
+                union value case_idx_value;
+                case_idx_value.f = case_idx;
+                data_out (buf, &v->print, &case_idx_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 true;
+}
+
+/* 
+   Local Variables:
+   mode: c
+   End:
+*/
diff --git a/src/language/data-io/matrix-data.c b/src/language/data-io/matrix-data.c
new file mode 100644 (file)
index 0000000..343e96d
--- /dev/null
@@ -0,0 +1,2023 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <stdlib.h>
+#include <ctype.h>
+#include <float.h>
+#include "array.h"
+#include "alloc.h"
+#include "case.h"
+#include "command.h"
+#include "data-in.h"
+#include "data-reader.h"
+#include "dictionary.h"
+#include "message.h"
+#include "file-handle.h"
+#include "lexer.h"
+#include "misc.h"
+#include "pool.h"
+#include "str.h"
+#include "variable.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+/* FIXME: /N subcommand not implemented.  It should be pretty simple,
+   too. */
+
+/* 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
+  };
+
+/* Format type enums. */
+enum format_type
+  {
+    LIST,
+    FREE
+  };
+
+/* Matrix section enums. */
+enum matrix_section
+  {
+    LOWER,
+    UPPER,
+    FULL
+  };
+
+/* Diagonal inclusion enums. */
+enum include_diagonal
+  {
+    DIAGONAL,
+    NODIAGONAL
+  };
+
+/* CONTENTS types. */
+enum content_type
+  {
+    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 const 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",
+  };
+
+/* A MATRIX DATA input program. */
+struct matrix_data_pgm 
+  {
+    struct pool *container;     /* Arena used for all allocations. */
+    struct dfm_reader *reader;  /* Data file to read. */
+
+    /* Format. */
+    enum format_type fmt;      /* LIST or FREE. */
+    enum matrix_section section;/* LOWER or UPPER or FULL. */
+    enum include_diagonal diag; /* DIAGONAL or NODIAGONAL. */
+
+    int explicit_rowtype;       /* ROWTYPE_ specified explicitly in data? */
+    struct variable *rowtype_, *varname_; /* ROWTYPE_, VARNAME_ variables. */
+    
+    struct variable *single_split; /* Single SPLIT FILE variable. */
+
+    /* Factor variables.  */
+    size_t n_factors;           /* Number of factor variables. */
+    struct variable **factors;  /* Factor variables. */
+    int is_per_factor[PROX + 1]; /* Is there per-factor data? */
+
+    int cells;                  /* Number of cells, or -1 if none. */
+
+    int pop_n;                  /* Population N specified by user. */
+
+    /* CONTENTS subcommand. */
+    int contents[EOC * 3 + 1];  /* Contents. */
+    int n_contents;             /* Number of entries. */
+
+    /* Continuous variables. */
+    int n_continuous;           /* Number of continuous variables. */
+    int first_continuous;       /* Index into default_dict.var of
+                                   first continuous variable. */
+  };
+
+/* Auxiliary data attached to MATRIX DATA variables. */
+struct mxd_var 
+  {
+    int var_type;              /* Variable type. */
+    int sub_type;              /* Subtype. */
+  };
+
+static const struct case_source_class matrix_data_with_rowtype_source_class;
+static const struct case_source_class matrix_data_without_rowtype_source_class;
+
+static int compare_variables_by_mxd_var_type (const void *pa,
+                                            const void *pb);
+static bool read_matrices_without_rowtype (struct matrix_data_pgm *);
+static bool read_matrices_with_rowtype (struct matrix_data_pgm *);
+static int string_to_content_type (char *, int *);
+static void attach_mxd_aux (struct variable *, int var_type, int sub_type);
+
+int
+cmd_matrix_data (void)
+{
+  struct pool *pool;
+  struct matrix_data_pgm *mx;
+  struct file_handle *fh = fh_inline_file ();
+  bool ok;
+    
+  unsigned seen = 0;
+  
+  discard_variables ();
+
+  pool = pool_create ();
+  mx = pool_alloc (pool, sizeof *mx);
+  mx->container = pool;
+  mx->reader = NULL;
+  mx->fmt = LIST;
+  mx->section = LOWER;
+  mx->diag = DIAGONAL;
+  mx->explicit_rowtype = 0;
+  mx->rowtype_ = NULL;
+  mx->varname_ = NULL;
+  mx->single_split = NULL;
+  mx->n_factors = 0;
+  mx->factors = NULL;
+  memset (mx->is_per_factor, 0, sizeof mx->is_per_factor);
+  mx->cells = -1;
+  mx->pop_n = -1;
+  mx->n_contents = 0;
+  mx->n_continuous = 0;
+  mx->first_continuous = 0;
+  while (token != '.')
+    {
+      lex_match ('/');
+
+      if (lex_match_id ("VARIABLES"))
+       {
+         char **v;
+         size_t 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;
+         
+         {
+           size_t i;
+
+           for (i = 0; i < nv; i++)
+             if (!strcasecmp (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;
+               }
+         }
+         
+         {
+           size_t i;
+
+           for (i = 0; i < nv; i++)
+             {
+               struct variable *new_var;
+               
+               if (strcasecmp (v[i], "ROWTYPE_"))
+                 {
+                   new_var = dict_create_var_assert (default_dict, v[i], 0);
+                    attach_mxd_aux (new_var, MXD_CONTINUOUS, i);
+                  }
+               else
+                 mx->explicit_rowtype = 1;
+               free (v[i]);
+             }
+           free (v);
+         }
+         
+          mx->rowtype_ = dict_create_var_assert (default_dict,
+                                                 "ROWTYPE_", 8);
+          attach_mxd_aux (mx->rowtype_, MXD_ROWTYPE, 0);
+       }
+      else if (lex_match_id ("FILE"))
+       {
+         lex_match ('=');
+         fh = fh_parse (FH_REF_FILE | FH_REF_INLINE);
+         if (fh == NULL)
+           goto lossage;
+       }
+      else if (lex_match_id ("FORMAT"))
+       {
+         lex_match ('=');
+
+         while (token == T_ID)
+           {
+             if (lex_match_id ("LIST"))
+               mx->fmt = LIST;
+             else if (lex_match_id ("FREE"))
+               mx->fmt = FREE;
+             else if (lex_match_id ("LOWER"))
+               mx->section = LOWER;
+             else if (lex_match_id ("UPPER"))
+               mx->section = UPPER;
+             else if (lex_match_id ("FULL"))
+               mx->section = FULL;
+             else if (lex_match_id ("DIAGONAL"))
+               mx->diag = DIAGONAL;
+             else if (lex_match_id ("NODIAGONAL"))
+               mx->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 (dict_lookup_var (default_dict, tokid) == NULL
+             && (lex_look_ahead () == '.' || lex_look_ahead () == '/'))
+           {
+             if (!strcasecmp (tokid, "ROWTYPE_")
+                  || !strcasecmp (tokid, "VARNAME_"))
+               {
+                 msg (SE, _("Split variable may not be named ROWTYPE_ "
+                            "or VARNAME_."));
+                 goto lossage;
+               }
+
+             mx->single_split = dict_create_var_assert (default_dict,
+                                                         tokid, 0);
+              attach_mxd_aux (mx->single_split, MXD_CONTINUOUS, 0);
+             lex_get ();
+
+              dict_set_split_vars (default_dict, &mx->single_split, 1);
+           }
+         else
+           {
+             struct variable **split;
+             size_t n;
+
+             if (!parse_variables (default_dict, &split, &n, PV_NO_DUPLICATE))
+               goto lossage;
+
+              dict_set_split_vars (default_dict, split, n);
+           }
+         
+         {
+            struct variable *const *split = dict_get_split_vars (default_dict);
+            size_t split_cnt = dict_get_split_cnt (default_dict);
+            int i;
+
+            for (i = 0; i < split_cnt; i++)
+              {
+                struct mxd_var *mv = split[i]->aux;
+                assert (mv != NULL);
+               if (mv->var_type != MXD_CONTINUOUS)
+                 {
+                   msg (SE, _("Split variable %s is already another type."),
+                        tokid);
+                   goto lossage;
+                 }
+                var_clear_aux (split[i]);
+                attach_mxd_aux (split[i], MXD_SPLIT, 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 (default_dict, &mx->factors, &mx->n_factors,
+                                PV_NONE))
+           goto lossage;
+         
+         {
+           size_t i;
+           
+           for (i = 0; i < mx->n_factors; i++)
+             {
+                struct variable *v = mx->factors[i];
+                struct mxd_var *mv = v->aux;
+                assert (mv != NULL);
+               if (mv->var_type != MXD_CONTINUOUS)
+                 {
+                   msg (SE, _("Factor variable %s is already another type."),
+                        tokid);
+                   goto lossage;
+                 }
+                var_clear_aux (v);
+                attach_mxd_aux (v, MXD_FACTOR, i);
+             }
+         }
+       }
+      else if (lex_match_id ("CELLS"))
+       {
+         lex_match ('=');
+         
+         if (mx->cells != -1)
+           {
+             msg (SE, _("CELLS subcommand multiply specified."));
+             goto lossage;
+           }
+
+         if (!lex_is_integer () || lex_integer () < 1)
+           {
+             lex_error (_("expecting positive integer"));
+             goto lossage;
+           }
+
+         mx->cells = lex_integer ();
+         lex_get ();
+       }
+      else if (lex_match_id ("N"))
+       {
+         lex_match ('=');
+
+         if (mx->pop_n != -1)
+           {
+             msg (SE, _("N subcommand multiply specified."));
+             goto lossage;
+           }
+
+         if (!lex_is_integer () || lex_integer () < 1)
+           {
+             lex_error (_("expecting positive integer"));
+             goto lossage;
+           }
+
+         mx->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++)
+             mx->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 (mx->contents[mx->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;
+                 mx->is_per_factor[item] = inside_parens;
+               }
+             mx->contents[mx->n_contents++] = item;
+
+             if (token == '/' || token == '.')
+               break;
+           }
+
+         if (inside_parens)
+           {
+             msg (SE, _("Missing right parenthesis."));
+             goto lossage;
+           }
+         mx->contents[mx->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 (!mx->n_contents && !mx->explicit_rowtype)
+    {
+      msg (SW, _("CONTENTS subcommand not specified: assuming file "
+                "contains only CORR matrix."));
+
+      mx->contents[0] = CORR;
+      mx->contents[1] = EOC;
+      mx->n_contents = 0;
+    }
+
+  if (mx->n_factors && !mx->explicit_rowtype && mx->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 (mx->explicit_rowtype && mx->single_split)
+    {
+      msg (SE, _("Split file values must be present in the data when "
+                "ROWTYPE_ is present."));
+      goto lossage;
+    }
+      
+  /* Create VARNAME_. */
+  mx->varname_ = dict_create_var_assert (default_dict, "VARNAME_", 8);
+  attach_mxd_aux (mx->varname_, MXD_VARNAME, 0);
+  
+  /* Sort the dictionary variables into the desired order for the
+     system file output. */
+  {
+    struct variable **v;
+    size_t nv;
+
+    dict_get_vars (default_dict, &v, &nv, 0);
+    qsort (v, nv, sizeof *v, compare_variables_by_mxd_var_type);
+    dict_reorder_vars (default_dict, v, nv);
+    free (v);
+  }
+
+  /* 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;
+
+    mx->first_continuous = -1;
+    for (i = 0; i < dict_get_var_cnt (default_dict); i++)
+      {
+       struct variable *v = dict_get_var (default_dict, i);
+        struct mxd_var *mv = v->aux;
+       int type = mv->var_type;
+       
+       assert (type >= 0 && type < MXD_COUNT);
+       v->print = v->write = fmt_tab[type];
+
+       if (type == MXD_CONTINUOUS)
+         mx->n_continuous++;
+       if (mx->first_continuous == -1 && type == MXD_CONTINUOUS)
+         mx->first_continuous = i;
+      }
+  }
+
+  if (mx->n_continuous == 0)
+    {
+      msg (SE, _("No continuous variables specified."));
+      goto lossage;
+    }
+
+  mx->reader = dfm_open_reader (fh);
+  if (mx->reader == NULL)
+    goto lossage;
+
+  if (mx->explicit_rowtype)
+    ok = read_matrices_with_rowtype (mx);
+  else
+    ok = read_matrices_without_rowtype (mx);
+
+  dfm_close_reader (mx->reader);
+
+  pool_destroy (mx->container);
+
+  return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+
+lossage:
+  discard_variables ();
+  free (mx->factors);
+  pool_destroy (mx->container);
+  return CMD_CASCADING_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 (!strcasecmp (s, tp->string))
+      {
+       if (collide)
+         *collide = tp->collide;
+       
+       return tp->value;
+      }
+  return -1;
+}
+
+/* Compare two variables using p.mxd.var_type and p.mxd.sub_type
+   fields. */
+static int
+compare_variables_by_mxd_var_type (const void *a_, const void *b_)
+{
+  struct variable *const *pa = a_;
+  struct variable *const *pb = b_;
+  const struct mxd_var *a = (*pa)->aux;
+  const struct mxd_var *b = (*pb)->aux;
+  
+  if (a->var_type != b->var_type)
+    return a->var_type > b->var_type ? 1 : -1;
+  else
+    return a->sub_type < b->sub_type ? -1 : a->sub_type > b->sub_type;
+}
+
+/* Attaches a struct mxd_var with the specific member values to
+   V. */
+static void
+attach_mxd_aux (struct variable *v, int var_type, int sub_type) 
+{
+  struct mxd_var *mv;
+  
+  assert (v->aux == NULL);
+  mv = xmalloc (sizeof *mv);
+  mv->var_type = var_type;
+  mv->sub_type = sub_type;
+  var_attach_aux (v, mv, var_dtor_free);
+}
+\f
+/* Matrix tokenizer. */
+
+/* Matrix token types. */
+enum matrix_token_type
+  {
+    MNUM,              /* Number. */
+    MSTR               /* String. */
+  };
+
+/* A MATRIX DATA parsing token. */
+struct matrix_token
+  {
+    enum matrix_token_type type; 
+    double number;       /* MNUM: token value. */
+    char *string;        /* MSTR: token string; not null-terminated. */
+    int length;          /* MSTR: tokstr length. */
+  };
+
+static int mget_token (struct matrix_token *, struct dfm_reader *);
+
+#if DEBUGGING
+#define mget_token(TOKEN, READER) mget_token_dump(TOKEN, READER)
+
+static void
+mdump_token (const struct matrix_token *token)
+{
+  switch (token->type)
+    {
+    case MNUM:
+      printf (" #%g", token->number);
+      break;
+    case MSTR:
+      printf (" '%.*s'", token->length, token->string);
+      break;
+    default:
+      assert (0);
+    }
+  fflush (stdout);
+}
+
+static int
+mget_token_dump (struct matrix_token *token, struct dfm_reader *reader)
+{
+  int result = (mget_token) (token, reader);
+  mdump_token (token);
+  return result;
+}
+#endif
+
+/* Return the current position in READER. */
+static const char *
+context (struct dfm_reader *reader)
+{
+  static char buf[32];
+
+  if (dfm_eof (reader))
+    strcpy (buf, "at end of file");
+  else 
+    {
+      struct fixed_string line;
+      const char *sp;
+      
+      dfm_get_record (reader, &line);
+      sp = ls_c_str (&line);
+      while (sp < ls_end (&line) && isspace ((unsigned char) *sp))
+        sp++;
+      if (sp >= ls_end (&line))
+        strcpy (buf, "at end of line");
+      else
+        {
+          char *dp;
+          size_t copy_cnt = 0;
+
+          dp = stpcpy (buf, "before `");
+          while (sp < ls_end (&line) && !isspace ((unsigned char) *sp)
+                 && copy_cnt < 10) 
+            {
+              *dp++ = *sp++;
+              copy_cnt++; 
+            }
+          strcpy (dp, "'");
+        }
+    }
+  
+  return buf;
+}
+
+/* Is there at least one token left in the data file? */
+static int
+another_token (struct dfm_reader *reader)
+{
+  for (;;)
+    {
+      struct fixed_string line;
+      const char *cp;
+      
+      if (dfm_eof (reader))
+        return 0;
+      dfm_get_record (reader, &line);
+
+      cp = ls_c_str (&line);
+      while (isspace ((unsigned char) *cp) && cp < ls_end (&line))
+       cp++;
+
+      if (cp < ls_end (&line)) 
+        {
+          dfm_forward_columns (reader, cp - ls_c_str (&line));
+          return 1;
+        }
+
+      dfm_forward_record (reader);
+    }
+}
+
+/* Parse a MATRIX DATA token from READER into TOKEN. */
+static int
+(mget_token) (struct matrix_token *token, struct dfm_reader *reader)
+{
+  struct fixed_string line;
+  int first_column;
+  char *cp;
+
+  if (!another_token (reader))
+    return 0;
+
+  dfm_get_record (reader, &line);
+  first_column = dfm_column_start (reader);
+
+  /* Three types of fields: quoted with ', quoted with ", unquoted. */
+  cp = ls_c_str (&line);
+  if (*cp == '\'' || *cp == '"')
+    {
+      int quote = *cp;
+
+      token->type = MSTR;
+      token->string = ++cp;
+      while (cp < ls_end (&line) && *cp != quote)
+       cp++;
+      token->length = cp - token->string;
+      if (cp < ls_end (&line))
+       cp++;
+      else
+       msg (SW, _("Scope of string exceeds line."));
+    }
+  else
+    {
+      int is_num = isdigit ((unsigned char) *cp) || *cp == '.';
+
+      token->string = cp++;
+      while (cp < ls_end (&line)
+             && !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++;
+       }
+      
+      token->length = cp - token->string;
+      assert (token->length);
+
+      if (is_num)
+       {
+         struct data_in di;
+
+         di.s = token->string;
+         di.e = token->string + token->length;
+         di.v = (union value *) &token->number;
+         di.f1 = first_column;
+         di.format = make_output_format (FMT_F, token->length, 0);
+
+         if (!data_in (&di))
+           return 0;
+       }
+      else
+       token->type = MSTR;
+    }
+
+  dfm_forward_columns (reader, cp - ls_c_str (&line));
+    
+  return 1;
+}
+
+/* Forcibly skip the end of a line for content type CONTENT in
+   READER. */
+static int
+force_eol (struct dfm_reader *reader, const char *content)
+{
+  struct fixed_string line;
+  const char *cp;
+
+  if (dfm_eof (reader))
+    return 0;
+  dfm_get_record (reader, &line);
+
+  cp = ls_c_str (&line);
+  while (isspace ((unsigned char) *cp) && cp < ls_end (&line))
+    cp++;
+  
+  if (cp < ls_end (&line))
+    {
+      msg (SE, _("End of line expected %s while reading %s."),
+          context (reader), content);
+      return 0;
+    }
+  
+  dfm_forward_record (reader);
+  return 1;
+}
+\f
+/* Back end, omitting ROWTYPE_. */
+
+struct nr_aux_data 
+  {
+    struct matrix_data_pgm *mx; /* MATRIX DATA program. */
+    double ***data;             /* MATRIX DATA data. */
+    double *factor_values;      /* Factor values. */
+    int max_cell_idx;           /* Max-numbered cell that we have
+                                   read so far, plus one. */
+    double *split_values;       /* SPLIT FILE variable values. */
+  };
+
+static int nr_read_splits (struct nr_aux_data *, int compare);
+static int nr_read_factors (struct nr_aux_data *, int cell);
+static bool nr_output_data (struct nr_aux_data *, struct ccase *,
+                            write_case_func *, write_case_data);
+static bool matrix_data_read_without_rowtype (struct case_source *source,
+                                              struct ccase *,
+                                              write_case_func *,
+                                              write_case_data);
+
+/* Read from the data file and write it to the active file.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+read_matrices_without_rowtype (struct matrix_data_pgm *mx)
+{
+  struct nr_aux_data nr;
+  bool ok;
+  
+  if (mx->cells == -1)
+    mx->cells = 1;
+
+  nr.mx = mx;
+  nr.data = NULL;
+  nr.factor_values = xnmalloc (mx->n_factors * mx->cells,
+                               sizeof *nr.factor_values);
+  nr.max_cell_idx = 0;
+  nr.split_values = xnmalloc (dict_get_split_cnt (default_dict),
+                              sizeof *nr.split_values);
+
+  vfm_source = create_case_source (&matrix_data_without_rowtype_source_class, &nr);
+  
+  ok = procedure (NULL, NULL);
+
+  free (nr.split_values);
+  free (nr.factor_values);
+
+  return ok;
+}
+
+/* Mirror data across the diagonal of matrix CP which contains
+   CONTENT type data. */
+static void
+fill_matrix (struct matrix_data_pgm *mx, int content, double *cp)
+{
+  int type = content_type[content];
+
+  if (type == 1 && mx->section != FULL)
+    {
+      if (mx->diag == NODIAGONAL)
+       {
+         const double fill = content == CORR ? 1.0 : SYSMIS;
+         int i;
+
+         for (i = 0; i < mx->n_continuous; i++)
+           cp[i * (1 + mx->n_continuous)] = fill;
+       }
+      
+      {
+       int c, r;
+       
+       if (mx->section == LOWER)
+         {
+           int n_lines = mx->n_continuous;
+           if (mx->section != FULL && mx->diag == NODIAGONAL)
+             n_lines--;
+           
+           for (r = 1; r < n_lines; r++)
+             for (c = 0; c < r; c++)
+               cp[r + c * mx->n_continuous] = cp[c + r * mx->n_continuous];
+         }
+       else 
+         {
+           assert (mx->section == UPPER);
+           for (r = 1; r < mx->n_continuous; r++)
+             for (c = 0; c < r; c++)
+               cp[c + r * mx->n_continuous] = cp[r + c * mx->n_continuous];
+         }
+      }
+    }
+  else if (type == 2)
+    {
+      int c;
+
+      for (c = 1; c < mx->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 (struct nr_aux_data *nr,
+                    int per_factor, int cell, int content, int compare)
+{
+  struct matrix_data_pgm *mx = nr->mx;
+  const int type = content_type[content];               /* Content type. */
+  int n_lines; /* Number of lines to parse from data file for this type. */
+  double *cp;                   /* Current position in vector or matrix. */
+  int i;
+
+  if (type != 1)
+    n_lines = 1;
+  else
+    {
+      n_lines = mx->n_continuous;
+      if (mx->section != FULL && mx->diag == NODIAGONAL)
+       n_lines--;
+    }
+
+  cp = nr->data[content][cell];
+  if (type == 1 && mx->section == LOWER && mx->diag == NODIAGONAL)
+    cp += mx->n_continuous;
+
+  for (i = 0; i < n_lines; i++)
+    {
+      int n_cols;
+      
+      if (!nr_read_splits (nr, 1))
+       return 0;
+      if (per_factor && !nr_read_factors (nr, cell))
+       return 0;
+      compare = 1;
+
+      switch (type)
+       {
+       case 0:
+         n_cols = mx->n_continuous;
+         break;
+       case 1:
+         switch (mx->section)
+           {
+           case LOWER:
+             n_cols = i + 1;
+             break;
+           case UPPER:
+             cp += i;
+             n_cols = mx->n_continuous - i;
+             if (mx->diag == NODIAGONAL)
+               {
+                 n_cols--;
+                 cp++;
+               }
+             break;
+           case FULL:
+             n_cols = mx->n_continuous;
+             break;
+           default:
+             assert (0);
+              abort ();
+           }
+         break;
+       case 2:
+         n_cols = 1;
+         break;
+       default:
+         assert (0);
+          abort ();
+       }
+
+      {
+       int j;
+       
+       for (j = 0; j < n_cols; j++)
+         {
+            struct matrix_token token;
+           if (!mget_token (&token, mx->reader))
+             return 0;
+           if (token.type != MNUM)
+             {
+               msg (SE, _("expecting value for %s %s"),
+                    dict_get_var (default_dict, j)->name,
+                     context (mx->reader));
+               return 0;
+             }
+
+           *cp++ = token.number;
+         }
+       if (mx->fmt != FREE
+            && !force_eol (mx->reader, content_names[content]))
+         return 0;
+       debug_printf (("\n"));
+      }
+
+      if (mx->section == LOWER)
+       cp += mx->n_continuous - n_cols;
+    }
+
+  fill_matrix (mx, 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 true if successful, false if an I/O error occurred. */
+static bool
+matrix_data_read_without_rowtype (struct case_source *source,
+                                  struct ccase *c,
+                                  write_case_func *write_case,
+                                  write_case_data wc_data)
+{
+  struct nr_aux_data *nr = source->aux;
+  struct matrix_data_pgm *mx = nr->mx;
+
+  {
+    int *cp;
+
+    nr->data = pool_nalloc (mx->container, PROX + 1, sizeof *nr->data);
+    
+    {
+      int i;
+
+      for (i = 0; i <= PROX; i++)
+       nr->data[i] = NULL;
+    }
+    
+    for (cp = mx->contents; *cp != EOC; cp++)
+      if (*cp != LPAREN && *cp != RPAREN)
+       {
+         int per_factor = mx->is_per_factor[*cp];
+         int n_entries;
+         
+         n_entries = mx->n_continuous;
+         if (content_type[*cp] == 1)
+           n_entries *= mx->n_continuous;
+         
+         {
+           int n_vectors = per_factor ? mx->cells : 1;
+           int i;
+           
+           nr->data[*cp] = pool_nalloc (mx->container,
+                                         n_vectors, sizeof **nr->data);
+           
+           for (i = 0; i < n_vectors; i++)
+             nr->data[*cp][i] = pool_nalloc (mx->container,
+                                              n_entries, sizeof ***nr->data);
+         }
+       }
+  }
+  
+  for (;;)
+    {
+      int *bp, *ep, *np;
+      
+      if (!nr_read_splits (nr, 0))
+       return true;
+      
+      for (bp = mx->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 ? mx->cells : 1); i++)
+             {
+               int *cp;
+
+               for (cp = bp; cp < ep; cp++) 
+                 if (!nr_read_data_lines (nr, per_factor, i, *cp, cp != bp))
+                   return true;
+             }
+         }
+       }
+
+      if (!nr_output_data (nr, c, write_case, wc_data))
+        return false;
+
+      if (dict_get_split_cnt (default_dict) == 0
+          || !another_token (mx->reader))
+       return true;
+    }
+}
+
+/* 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 (struct nr_aux_data *nr, int compare)
+{
+  struct matrix_data_pgm *mx = nr->mx;
+  static int just_read = 0; /* FIXME: WTF? */
+  size_t split_cnt;
+  size_t i;
+
+  if (compare && just_read)
+    {
+      just_read = 0;
+      return 1;
+    }
+  
+  if (dict_get_split_vars (default_dict) == NULL)
+    return 1;
+
+  if (mx->single_split)
+    {
+      if (!compare) 
+        {
+          struct mxd_var *mv = dict_get_split_vars (default_dict)[0]->aux;
+          nr->split_values[0] = ++mv->sub_type; 
+        }
+      return 1;
+    }
+
+  if (!compare)
+    just_read = 1;
+
+  split_cnt = dict_get_split_cnt (default_dict);
+  for (i = 0; i < split_cnt; i++) 
+    {
+      struct matrix_token token;
+      if (!mget_token (&token, mx->reader))
+        return 0;
+      if (token.type != MNUM)
+        {
+          msg (SE, _("Syntax error expecting SPLIT FILE value %s."),
+               context (mx->reader));
+          return 0;
+        }
+
+      if (!compare)
+        nr->split_values[i] = token.number;
+      else if (nr->split_values[i] != token.number)
+        {
+          msg (SE, _("Expecting value %g for %s."),
+               nr->split_values[i],
+               dict_get_split_vars (default_dict)[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 (struct nr_aux_data *nr, int cell)
+{
+  struct matrix_data_pgm *mx = nr->mx;
+  int compare;
+  
+  if (mx->n_factors == 0)
+    return 1;
+
+  assert (nr->max_cell_idx >= cell);
+  if (cell != nr->max_cell_idx)
+    compare = 1;
+  else
+    {
+      compare = 0;
+      nr->max_cell_idx++;
+    }
+      
+  {
+    size_t i;
+    
+    for (i = 0; i < mx->n_factors; i++)
+      {
+        struct matrix_token token;
+       if (!mget_token (&token, mx->reader))
+         return 0;
+       if (token.type != MNUM)
+         {
+           msg (SE, _("Syntax error expecting factor value %s."),
+                context (mx->reader));
+           return 0;
+         }
+       
+       if (!compare)
+         nr->factor_values[i + mx->n_factors * cell] = token.number;
+       else if (nr->factor_values[i + mx->n_factors * cell] != token.number)
+         {
+           msg (SE, _("Syntax error expecting value %g for %s %s."),
+                nr->factor_values[i + mx->n_factors * cell],
+                mx->factors[i]->name, context (mx->reader));
+           return 0;
+         }
+      }
+  }
+
+  return 1;
+}
+
+/* Write the contents of a cell having content type CONTENT and data
+   CP to the active file.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+dump_cell_content (struct matrix_data_pgm *mx, int content, double *cp,
+                   struct ccase *c,
+                   write_case_func *write_case, write_case_data wc_data)
+{
+  int type = content_type[content];
+
+  {
+    buf_copy_str_rpad (case_data_rw (c, mx->rowtype_->fv)->s, 8,
+                       content_names[content]);
+    
+    if (type != 1)
+      memset (case_data_rw (c, mx->varname_->fv)->s, ' ', 8);
+  }
+
+  {
+    int n_lines = (type == 1) ? mx->n_continuous : 1;
+    int i;
+               
+    for (i = 0; i < n_lines; i++)
+      {
+       int j;
+
+       for (j = 0; j < mx->n_continuous; j++)
+         {
+            int fv = dict_get_var (default_dict, mx->first_continuous + j)->fv;
+            case_data_rw (c, fv)->f = *cp;
+           cp++;
+         }
+       if (type == 1)
+         buf_copy_str_rpad (case_data_rw (c, mx->varname_->fv)->s, 8,
+                             dict_get_var (default_dict,
+                                           mx->first_continuous + i)->name);
+       if (!write_case (wc_data))
+          return false;
+      }
+  }
+  return true;
+}
+
+/* Finally dump out everything from nr_data[] to the output file. */
+static bool
+nr_output_data (struct nr_aux_data *nr, struct ccase *c,
+                write_case_func *write_case, write_case_data wc_data)
+{
+  struct matrix_data_pgm *mx = nr->mx;
+  
+  {
+    struct variable *const *split;
+    size_t split_cnt;
+    size_t i;
+
+    split_cnt = dict_get_split_cnt (default_dict);
+    split = dict_get_split_vars (default_dict);
+    for (i = 0; i < split_cnt; i++)
+      case_data_rw (c, split[i]->fv)->f = nr->split_values[i];
+  }
+
+  if (mx->n_factors)
+    {
+      int cell;
+
+      for (cell = 0; cell < mx->cells; cell++)
+       {
+         {
+           size_t factor;
+
+           for (factor = 0; factor < mx->n_factors; factor++)
+             {
+               case_data_rw (c, mx->factors[factor]->fv)->f
+                 = nr->factor_values[factor + cell * mx->n_factors];
+               debug_printf (("f:%s ", mx->factors[factor]->name));
+             }
+         }
+         
+         {
+           int content;
+           
+           for (content = 0; content <= PROX; content++)
+             if (mx->is_per_factor[content])
+               {
+                 assert (nr->data[content] != NULL
+                         && nr->data[content][cell] != NULL);
+
+                 if (!dump_cell_content (mx, content, nr->data[content][cell],
+                                          c, write_case, wc_data))
+                    return false;
+               }
+         }
+       }
+    }
+
+  {
+    int content;
+    
+    {
+      size_t factor;
+
+      for (factor = 0; factor < mx->n_factors; factor++)
+       case_data_rw (c, mx->factors[factor]->fv)->f = SYSMIS;
+    }
+    
+    for (content = 0; content <= PROX; content++)
+      if (!mx->is_per_factor[content] && nr->data[content] != NULL) 
+        {
+          if (!dump_cell_content (mx, content, nr->data[content][0],
+                                  c, write_case, wc_data))
+            return false; 
+        }
+  }
+
+  return true;
+}
+\f
+/* Back end, with ROWTYPE_. */
+
+/* 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;
+  };
+
+/* With ROWTYPE_ auxiliary data. */
+struct wr_aux_data 
+  {
+    struct matrix_data_pgm *mx;         /* MATRIX DATA program. */
+    int content;                        /* Type of current row. */
+    double *split_values;               /* SPLIT FILE variable values. */
+    struct factor_data *data;           /* All the data. */
+    struct factor_data *current;        /* Current factor. */
+  };
+
+static int wr_read_splits (struct wr_aux_data *, struct ccase *,
+                           write_case_func *, write_case_data);
+static bool wr_output_data (struct wr_aux_data *, struct ccase *,
+                           write_case_func *, write_case_data);
+static int wr_read_rowtype (struct wr_aux_data *, 
+                            const struct matrix_token *, struct dfm_reader *);
+static int wr_read_factors (struct wr_aux_data *);
+static int wr_read_indeps (struct wr_aux_data *);
+static bool matrix_data_read_with_rowtype (struct case_source *,
+                                           struct ccase *,
+                                           write_case_func *,
+                                           write_case_data);
+
+/* When ROWTYPE_ appears in the data, reads the matrices and writes
+   them to the output file.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+read_matrices_with_rowtype (struct matrix_data_pgm *mx)
+{
+  struct wr_aux_data wr;
+  bool ok;
+
+  wr.mx = mx;
+  wr.content = -1;
+  wr.split_values = NULL;
+  wr.data = NULL;
+  wr.current = NULL;
+  mx->cells = 0;
+
+  vfm_source = create_case_source (&matrix_data_with_rowtype_source_class,
+                                   &wr);
+  ok = procedure (NULL, NULL);
+
+  free (wr.split_values);
+  return ok;
+}
+
+/* Read from the data file and write it to the active file.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+matrix_data_read_with_rowtype (struct case_source *source,
+                               struct ccase *c,
+                               write_case_func *write_case,
+                               write_case_data wc_data)
+{
+  struct wr_aux_data *wr = source->aux;
+  struct matrix_data_pgm *mx = wr->mx;
+
+  do
+    {
+      if (!wr_read_splits (wr, c, write_case, wc_data))
+       return true;
+
+      if (!wr_read_factors (wr))
+       return true;
+
+      if (!wr_read_indeps (wr))
+       return true;
+    }
+  while (another_token (mx->reader));
+
+  return wr_output_data (wr, c, write_case, wc_data);
+}
+
+/* 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 (struct wr_aux_data *wr,
+                struct ccase *c,
+                write_case_func *write_case, write_case_data wc_data)
+{
+  struct matrix_data_pgm *mx = wr->mx;
+  int compare;
+  size_t split_cnt;
+
+  split_cnt = dict_get_split_cnt (default_dict);
+  if (split_cnt == 0)
+    return 1;
+
+  if (wr->split_values)
+    compare = 1;
+  else
+    {
+      compare = 0;
+      wr->split_values = xnmalloc (split_cnt, sizeof *wr->split_values);
+    }
+  
+  {
+    int different = 0;
+    int i;
+
+    for (i = 0; i < split_cnt; i++)
+      {
+        struct matrix_token token;
+       if (!mget_token (&token, mx->reader))
+         return 0;
+       if (token.type != MNUM)
+         {
+           msg (SE, _("Syntax error %s expecting SPLIT FILE value."),
+                context (mx->reader));
+           return 0;
+         }
+
+       if (compare && wr->split_values[i] != token.number && !different)
+         {
+           if (!wr_output_data (wr, c, write_case, wc_data))
+             return 0;
+           different = 1;
+           mx->cells = 0;
+         }
+       wr->split_values[i] = token.number;
+      }
+  }
+
+  return 1;
+}
+
+/* Compares doubles A and B, treating SYSMIS as greatest. */
+static int
+compare_doubles (const void *a_, const void *b_, void *aux UNUSED)
+{
+  const double *a = a_;
+  const double *b = b_;
+
+  if (*a == *b)
+    return 0;
+  else if (*a == SYSMIS)
+    return 1;
+  else if (*b == SYSMIS)
+    return -1;
+  else if (*a > *b)
+    return 1;
+  else
+    return -1;
+}
+
+/* Return strcmp()-type comparison of the MX->n_factors factors at _A and
+   _B.  Sort missing values toward the end. */
+static int
+compare_factors (const void *a_, const void *b_, void *mx_)
+{
+  struct matrix_data_pgm *mx = mx_;
+  struct factor_data *const *pa = a_;
+  struct factor_data *const *pb = b_;
+  const double *a = (*pa)->factors;
+  const double *b = (*pb)->factors;
+
+  return lexicographical_compare_3way (a, mx->n_factors,
+                                       b, mx->n_factors,
+                                       sizeof *a,
+                                       compare_doubles, NULL);
+}
+
+/* Write out the data for the current split file to the active
+   file.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+wr_output_data (struct wr_aux_data *wr,
+                struct ccase *c,
+                write_case_func *write_case, write_case_data wc_data)
+{
+  struct matrix_data_pgm *mx = wr->mx;
+  bool ok = true;
+
+  {
+    struct variable *const *split;
+    size_t split_cnt;
+    size_t i;
+
+    split_cnt = dict_get_split_cnt (default_dict);
+    split = dict_get_split_vars (default_dict);
+    for (i = 0; i < split_cnt; i++)
+      case_data_rw (c, split[i]->fv)->f = wr->split_values[i];
+  }
+
+  /* Sort the wr->data list. */
+  {
+    struct factor_data **factors;
+    struct factor_data *iter;
+    int i;
+
+    factors = xnmalloc (mx->cells, sizeof *factors);
+
+    for (i = 0, iter = wr->data; iter; iter = iter->next, i++)
+      factors[i] = iter;
+
+    sort (factors, mx->cells, sizeof *factors, compare_factors, mx);
+
+    wr->data = factors[0];
+    for (i = 0; i < mx->cells - 1; i++)
+      factors[i]->next = factors[i + 1];
+    factors[mx->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)
+      {
+       {
+         size_t factor;
+
+         for (factor = 0; factor < mx->n_factors; factor++)
+            case_data_rw (c, mx->factors[factor]->fv)->f
+              = iter->factors[factor];
+       }
+       
+       {
+         int content;
+
+         for (content = 0; content <= PROX; content++)
+           {
+             if (!iter->n_rows[content])
+               continue;
+             
+             {
+               int type = content_type[content];
+               int n_lines = (type == 1
+                              ? (mx->n_continuous
+                                 - (mx->section != FULL && mx->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 (mx, content, iter->data[content]);
+
+             ok = dump_cell_content (mx, content, iter->data[content],
+                                      c, write_case, wc_data);
+              if (!ok)
+                break;
+           }
+       }
+      }
+  }
+  
+  pool_destroy (mx->container);
+  mx->container = pool_create ();
+  
+  wr->data = wr->current = NULL;
+  
+  return ok;
+}
+
+/* Sets ROWTYPE_ based on the given TOKEN read from READER.
+   Return success. */
+static int 
+wr_read_rowtype (struct wr_aux_data *wr,
+                 const struct matrix_token *token,
+                 struct dfm_reader *reader)
+{
+  if (wr->content != -1)
+    {
+      msg (SE, _("Multiply specified ROWTYPE_ %s."), context (reader));
+      return 0;
+    }
+  if (token->type != MSTR)
+    {
+      msg (SE, _("Syntax error %s expecting ROWTYPE_ string."),
+           context (reader));
+      return 0;
+    }
+  
+  {
+    char s[16];
+    char *cp;
+    
+    memcpy (s, token->string, min (15, token->length));
+    s[min (15, token->length)] = 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 (reader));
+      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 (struct wr_aux_data *wr)
+{
+  struct matrix_data_pgm *mx = wr->mx;
+  double *factor_values = local_alloc (sizeof *factor_values * mx->n_factors);
+
+  wr->content = -1;
+  {
+    size_t i;
+  
+    for (i = 0; i < mx->n_factors; i++)
+      {
+        struct matrix_token token;
+       if (!mget_token (&token, mx->reader))
+         goto lossage;
+       if (token.type == MSTR)
+         {
+           if (!wr_read_rowtype (wr, &token, mx->reader))
+             goto lossage;
+           if (!mget_token (&token, mx->reader))
+             goto lossage;
+         }
+       if (token.type != MNUM)
+         {
+           msg (SE, _("Syntax error expecting factor value %s."),
+                context (mx->reader));
+           goto lossage;
+         }
+       
+       factor_values[i] = token.number;
+      }
+  }
+  if (wr->content == -1)
+    {
+      struct matrix_token token;
+      if (!mget_token (&token, mx->reader))
+       goto lossage;
+      if (!wr_read_rowtype (wr, &token, mx->reader))
+       goto lossage;
+    }
+  
+  /* Try the most recent factor first as a simple caching
+     mechanism. */
+  if (wr->current)
+    {
+      size_t i;
+      
+      for (i = 0; i < mx->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)
+      {
+       size_t i;
+
+       for (i = 0; i < mx->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 (mx->container, sizeof *new);
+
+    new->factors = pool_nalloc (mx->container,
+                                mx->n_factors, sizeof *new->factors);
+    
+    {
+      size_t i;
+
+      for (i = 0; i < mx->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;
+    mx->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 (struct wr_aux_data *wr)
+{
+  struct matrix_data_pgm *mx = wr->mx;
+  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 = mx->n_continuous;
+      if (type == 1)
+       n_items *= mx->n_continuous;
+      
+      c->data[wr->content] = pool_nalloc (mx->container,
+                                          n_items, sizeof **c->data);
+    }
+
+  cp = &c->data[wr->content][n_rows * mx->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 = mx->n_continuous;
+      else
+       n_cols = 1;
+      break;
+    case 1:
+      if (n_rows >= mx->n_continuous - (mx->section != FULL && mx->diag == NODIAGONAL))
+       {
+         msg (SE, _("Too many rows of matrix data for %s."),
+              content_names[wr->content]);
+         return 0;
+       }
+      
+      switch (mx->section)
+       {
+       case LOWER:
+         n_cols = n_rows + 1;
+         if (mx->diag == NODIAGONAL)
+           cp += mx->n_continuous;
+         break;
+       case UPPER:
+         cp += n_rows;
+         n_cols = mx->n_continuous - n_rows;
+         if (mx->diag == NODIAGONAL)
+           {
+             n_cols--;
+             cp++;
+           }
+         break;
+       case FULL:
+         n_cols = mx->n_continuous;
+         break;
+       default:
+         assert (0);
+          abort ();
+       }
+      break;
+    default:
+      assert (0);
+      abort ();
+    }
+  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++)
+      {
+        struct matrix_token token;
+       if (!mget_token (&token, mx->reader))
+         return 0;
+       if (token.type != MNUM)
+         {
+           msg (SE, _("Syntax error expecting value for %s %s."),
+                 dict_get_var (default_dict, mx->first_continuous + j)->name,
+                 context (mx->reader));
+           return 0;
+         }
+
+       *cp++ = token.number;
+      }
+    if (mx->fmt != FREE
+        && !force_eol (mx->reader, content_names[wr->content]))
+      return 0;
+    debug_printf (("\n"));
+  }
+
+  return 1;
+}
+\f
+/* Matrix source. */
+
+static const struct case_source_class matrix_data_with_rowtype_source_class = 
+  {
+    "MATRIX DATA",
+    NULL,
+    matrix_data_read_with_rowtype,
+    NULL,
+  };
+
+static const struct case_source_class 
+matrix_data_without_rowtype_source_class =
+  {
+    "MATRIX DATA",
+    NULL,
+    matrix_data_read_without_rowtype,
+    NULL,
+  };
+
diff --git a/src/language/data-io/print.c b/src/language/data-io/print.c
new file mode 100644 (file)
index 0000000..f5e857e
--- /dev/null
@@ -0,0 +1,1119 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+/* FIXME: seems like a lot of code duplication with data-list.c. */
+
+#include <config.h>
+#include "message.h"
+#include <stdlib.h>
+#include "alloc.h"
+#include "case.h"
+#include "command.h"
+#include "data-writer.h"
+#include "message.h"
+#include "expressions/public.h"
+#include "file-handle.h"
+#include "lexer.h"
+#include "misc.h"
+#include "manager.h"
+#include "table.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* 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. */
+    PRT_BINARY = 004            /* File is binary, omit newlines. */
+  };
+
+/* PRINT, PRINT EJECT, WRITE private data structure. */
+struct print_trns
+  {
+    struct dfm_writer *writer; /* 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. */
+    char *line;                        /* Buffer for sticking lines in. */
+  };
+
+/* 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 trns_proc_func print_trns_proc;
+static trns_free_func print_trns_free;
+static int parse_specs (void);
+static void dump_table (const struct file_handle *);
+static void append_var_spec (struct prt_out_spec *);
+static void alloc_line (void);
+\f
+/* Basic parsing. */
+
+/* Parses PRINT command. */
+int
+cmd_print (void)
+{
+  return internal_cmd_print (PRT_PRINT);
+}
+
+/* Parses PRINT EJECT command. */
+int
+cmd_print_eject (void)
+{
+  return internal_cmd_print (PRT_PRINT | PRT_EJECT);
+}
+
+/* Parses WRITE command. */
+int
+cmd_write (void)
+{
+  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)
+{
+  int table = 0;                /* Print table? */
+  struct print_trns *trns;      /* malloc()'d transformation. */
+  struct file_handle *fh = NULL;
+
+  /* Fill in prt to facilitate error-handling. */
+  prt.writer = NULL;
+  prt.options = f;
+  prt.spec = NULL;
+  prt.line = NULL;
+  next = NULL;
+  nrec = 0;
+
+  which_cmd = f & PRT_CMD_MASK;
+
+  /* Parse the command options. */
+  while (!lex_match ('/'))
+    {
+      if (lex_match_id ("OUTFILE"))
+       {
+         lex_match ('=');
+
+         fh = fh_parse (FH_REF_FILE);
+         if (fh == NULL)
+           goto error;
+       }
+      else if (lex_match_id ("RECORDS"))
+       {
+         lex_match ('=');
+         lex_match ('(');
+         if (!lex_force_int ())
+           goto error;
+         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 error;
+       }
+    }
+
+  /* Parse variables and strings. */
+  if (!parse_specs ())
+    goto error;
+
+  if (fh != NULL)
+    {
+      prt.writer = dfm_open_writer (fh);
+      if (prt.writer == NULL)
+        goto error;
+
+      if (fh_get_mode (fh) == FH_MODE_BINARY)
+        prt.options |= PRT_BINARY;
+    }
+
+  /* Output the variable table if requested. */
+  if (table)
+    dump_table (fh);
+
+  /* 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 (print_trns_proc, print_trns_free, trns);
+
+  return CMD_SUCCESS;
+
+ error:
+  print_trns_free (&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 */
+    size_t nv;                 /* number of variables in list */
+    size_t 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 (lex_is_number ())
+           {
+             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_c_str (&tokstr));
+  lex_get ();
+
+  /* Parse the included column range. */
+  if (lex_is_number ())
+    {
+      /* 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_is_integer () || 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_is_integer ())
+           {
+             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 (default_dict, &fx.v, &fx.nv, PV_DUPLICATE))
+    return 0;
+
+  if (lex_is_number ())
+    {
+      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. */
+      size_t 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;
+}
+
+/* Verifies that FORMAT doesn't need a variable wider than WIDTH.
+   Returns true iff that is the case. */
+static bool
+check_string_width (const struct fmt_spec *format, const struct variable *v) 
+{
+  if (get_format_var_width (format) > v->width)
+    {
+      msg (SE, _("Variable %s has width %d so it cannot be output "
+                 "as format %s."),
+           v->name, v->width, fmt_to_string (format));
+      return false;
+    }
+  return true;
+}
+
+/* Parses a column specification for parse_specs(). */
+static int
+fixed_parse_compatible (void)
+{
+  int individual_var_width;
+  int type;
+  size_t 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 (lex_is_number ())
+       {
+         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 %u "
+                "fields."),
+           fx.lc - fx.fc + 1, fx.fc + 1, fx.lc + 1, (unsigned) fx.nv);
+      return 0;
+    }
+
+  individual_var_width = (fx.lc - fx.fc + 1) / fx.nv;
+  fx.spec.u.v.f.w = individual_var_width;
+  if (!check_output_specifier (&fx.spec.u.v.f, true)
+      || !check_specifier_type (&fx.spec.u.v.f, type, true))
+    return 0;
+  if (type == ALPHA)
+    {
+      for (i = 0; i < fx.nv; i++)
+        if (!check_string_width (&fx.spec.u.v.f, fx.v[i]))
+          return false;
+    }
+
+  fx.spec.type = PRT_VAR;
+  for (i = 0; i < fx.nv; i++)
+    {
+      fx.spec.fc = fx.fc + individual_var_width * 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 (!check_output_specifier (&f->f, true)
+                || !check_specifier_type (&f->f, v->type, true)
+                || !check_string_width (&f->f, v))
+              return false;
+
+           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 = NULL;
+  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_is_number ())
+       {
+         if (!lex_is_integer ())
+           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, FMTP_ALLOW_XT)
+              || !check_output_specifier (&fl->f, 1))
+       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 (const struct file_handle *fh)
+{
+  struct prt_out_spec *spec;
+  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);
+      }
+
+  if (fh != NULL)
+    tab_title (t, 1, ngettext ("Writing %d record to %s.",
+                               "Writing %d records to %s.", recno),
+               recno, fh_get_name (fh));
+  else
+    tab_title (t, 1, ngettext ("Writing %d record.",
+                               "Writing %d records.", recno), recno);
+  tab_submit (t);
+}
+
+/* PORTME: The number of characters in a line terminator. */
+#ifdef __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 */
+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:
+        default:
+         assert (0);
+          abort ();
+       }
+      if (pot_w > w)
+       w = pot_w;
+    }
+  prt.max_width = w + LINE_END_WIDTH + 1;
+  prt.line = xmalloc (prt.max_width);
+}
+\f
+/* Transformation. */
+
+/* Performs the transformation inside print_trns T on case C. */
+static int
+print_trns_proc (void *trns_, struct ccase *c, int case_num UNUSED)
+{
+  /* Transformation. */
+  struct print_trns *t = trns_;
+
+  /* Iterator. */
+  struct prt_out_spec *i;
+
+  /* Line buffer. */
+  char *buf = t->line;
+
+  /* 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.  */
+  for (i = t->spec; i; i = i->next)
+    switch (i->type)
+      {
+      case PRT_NEWLINE:
+       if (t->writer == NULL)
+         {
+           buf[len] = 0;
+           tab_output_text (TAT_FIX | TAT_NOWRAP, buf);
+         }
+       else
+         {
+           if ((t->options & PRT_CMD_MASK) == PRT_PRINT
+                || !(t->options & PRT_BINARY))
+             {
+               /* PORTME: Line ends. */
+#ifdef __MSDOS__
+               buf[len++] = '\r';
+#endif
+               buf[len++] = '\n';
+             }
+
+           dfm_put_record (t->writer, 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:
+        data_out (&buf[i->fc], &i->u.v.f, case_data (c, i->u.v.v->fv));
+       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;
+      }
+
+  if (t->writer != NULL && dfm_write_error (t->writer))
+    return TRNS_ERROR;
+  return TRNS_CONTINUE;
+}
+
+/* Frees all the data inside print_trns T.  Does not free T. */
+static bool
+print_trns_free (void *prt_)
+{
+  struct print_trns *prt = prt_;
+  struct prt_out_spec *i, *n;
+  bool ok = true;
+
+  for (i = prt->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 (prt->writer != NULL)
+    ok = dfm_close_writer (prt->writer);
+  free (prt->line);
+  free (prt);
+  return ok;
+}
+\f
+/* PRINT SPACE. */
+
+/* PRINT SPACE transformation. */
+struct print_space_trns
+{
+  struct dfm_writer *writer;    /* Output data file. */
+  struct expression *e;                /* Number of lines; NULL=1. */
+}
+print_space_trns;
+
+static trns_proc_func print_space_trns_proc;
+static trns_free_func print_space_trns_free;
+
+int
+cmd_print_space (void)
+{
+  struct print_space_trns *t;
+  struct file_handle *fh;
+  struct expression *e;
+  struct dfm_writer *writer;
+
+  if (lex_match_id ("OUTFILE"))
+    {
+      lex_match ('=');
+
+      fh = fh_parse (FH_REF_FILE);
+      if (fh == NULL)
+       return CMD_FAILURE;
+      lex_get ();
+    }
+  else
+    fh = NULL;
+
+  if (token != '.')
+    {
+      e = expr_parse (default_dict, EXPR_NUMBER);
+      if (token != '.')
+       {
+         expr_free (e);
+         lex_error (_("expecting end of command"));
+         return CMD_FAILURE;
+       }
+    }
+  else
+    e = NULL;
+
+  if (fh != NULL)
+    {
+      writer = dfm_open_writer (fh);
+      if (writer == NULL) 
+        {
+          expr_free (e);
+          return CMD_FAILURE;
+        } 
+    }
+  else
+    writer = NULL;
+  
+  t = xmalloc (sizeof *t);
+  t->writer = writer;
+  t->e = e;
+
+  add_transformation (print_space_trns_proc, print_space_trns_free, t);
+  return CMD_SUCCESS;
+}
+
+/* Executes a PRINT SPACE transformation. */
+static int
+print_space_trns_proc (void *t_, struct ccase *c,
+                       int case_num UNUSED)
+{
+  struct print_space_trns *t = t_;
+  int n;
+
+  n = 1;
+  if (t->e)
+    {
+      double f = expr_evaluate_num (t->e, c, case_num);
+      if (f == SYSMIS) 
+        msg (SW, _("The expression on PRINT SPACE evaluated to the "
+                   "system-missing value."));
+      else if (f < 0 || f > INT_MAX)
+        msg (SW, _("The expression on PRINT SPACE evaluated to %g."), f);
+      else
+        n = f;
+    }
+
+  while (n--)
+    if (t->writer == NULL)
+      som_blank_line ();
+    else
+      dfm_put_record (t->writer, "\n", 1);
+
+  if (t->writer != NULL && dfm_write_error (t->writer))
+    return TRNS_ERROR;
+  return TRNS_CONTINUE;
+}
+
+/* Frees a PRINT SPACE transformation.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+print_space_trns_free (void *trns_)
+{
+  struct print_space_trns *trns = trns_;
+  bool ok = dfm_close_writer (trns->writer);
+  expr_free (trns->e);
+  free (trns);
+  return ok;
+}
diff --git a/src/language/dictionary/ChangeLog b/src/language/dictionary/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/language/dictionary/apply-dictionary.c b/src/language/dictionary/apply-dictionary.c
new file mode 100644 (file)
index 0000000..0cf66e2
--- /dev/null
@@ -0,0 +1,169 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include "any-reader.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "file-handle-def.h"
+#include "hash.h"
+#include "lexer.h"
+#include "str.h"
+#include "value-labels.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+/* Parses and executes APPLY DICTIONARY. */
+int
+cmd_apply_dictionary (void)
+{
+  struct file_handle *handle;
+  struct any_reader *reader;
+  struct dictionary *dict;
+
+  int n_matched = 0;
+
+  int i;
+  
+  lex_match_id ("FROM");
+  lex_match ('=');
+  handle = fh_parse (FH_REF_FILE | FH_REF_SCRATCH);
+  if (!handle)
+    return CMD_FAILURE;
+
+  reader = any_reader_open (handle, &dict);
+  if (dict == NULL)
+    return CMD_FAILURE;
+  any_reader_close (reader);
+
+  for (i = 0; i < dict_get_var_cnt (dict); i++)
+    {
+      struct variable *s = dict_get_var (dict, i);
+      struct variable *t = dict_lookup_var (default_dict, 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 (val_labs_count (s->val_labs) && t->width > MAX_SHORT_STRING)
+       msg (SW, _("Cannot add value labels from source file to "
+                  "long string variable %s."),
+            s->name);
+      else if (val_labs_count (s->val_labs))
+       {
+          /* Whether to apply the value labels. */
+          int apply = 1;
+          
+         if (t->width < s->width)
+           {
+             struct val_labs_iterator *i;
+             struct val_lab *lab;
+
+              for (lab = val_labs_first (s->val_labs, &i); lab != NULL;
+                   lab = val_labs_next (s->val_labs, &i))
+               {
+                 int j;
+
+                 /* We will apply the value labels only if all
+                     the truncated characters are blanks. */
+                 for (j = t->width; j < s->width; j++)
+                   if (lab->value.s[j] != ' ') 
+                      {
+                        val_labs_done (&i);
+                        apply = 0;
+                        break; 
+                      }
+               }
+           }
+         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. */
+           }
+
+         if (apply) 
+            {
+              val_labs_destroy (t->val_labs);
+              t->val_labs = s->val_labs;
+              val_labs_set_width (t->val_labs, t->width);
+              s->val_labs = val_labs_create (s->width);
+            }
+       }
+
+      if (!mv_is_empty (&s->miss) && t->width > MAX_SHORT_STRING)
+       msg (SW, _("Cannot apply missing values from source file to "
+                  "long string variable %s."),
+            s->name);
+      else if (!mv_is_empty (&s->miss))
+       {
+          if (mv_is_resizable (&s->miss, t->width)) 
+            {
+              mv_copy (&t->miss, &s->miss);
+              mv_resize (&t->miss, t->width); 
+            }
+       }
+
+      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. */
+  if (dict_get_weight (dict) != NULL) 
+    {
+      struct variable *new_weight
+        = dict_lookup_var (default_dict, dict_get_weight (dict)->name);
+
+      if (new_weight != NULL)
+        dict_set_weight (default_dict, new_weight);
+    }
+  
+  any_reader_close (reader);
+
+  return lex_end_of_command ();
+}
diff --git a/src/language/dictionary/formats.c b/src/language/dictionary/formats.c
new file mode 100644 (file)
index 0000000..9e62d99
--- /dev/null
@@ -0,0 +1,118 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "command.h"
+#include "message.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+enum
+  {
+    FORMATS_PRINT = 001,
+    FORMATS_WRITE = 002
+  };
+
+static int internal_cmd_formats (int);
+
+int
+cmd_print_formats (void)
+{
+  return internal_cmd_formats (FORMATS_PRINT);
+}
+
+int
+cmd_write_formats (void)
+{
+  return internal_cmd_formats (FORMATS_WRITE);
+}
+
+int
+cmd_formats (void)
+{
+  return internal_cmd_formats (FORMATS_PRINT | FORMATS_WRITE);
+}
+
+int
+internal_cmd_formats (int which)
+{
+  /* Variables. */
+  struct variable **v;
+  size_t cv;
+
+  /* Format to set the variables to. */
+  struct fmt_spec f;
+
+  /* Numeric or string. */
+  int type;
+
+  /* Counter. */
+  size_t i;
+
+  for (;;)
+    {
+      if (token == '.')
+       break;
+
+      if (!parse_variables (default_dict, &v, &cv, PV_NUMERIC))
+       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, true)
+          || !check_specifier_type (&f, NUMERIC, true))
+       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;
+    }
+  return CMD_SUCCESS;
+
+fail:
+  free (v);
+  return CMD_PART_SUCCESS_MAYBE;
+}
diff --git a/src/language/dictionary/missing-values.c b/src/language/dictionary/missing-values.c
new file mode 100644 (file)
index 0000000..352284b
--- /dev/null
@@ -0,0 +1,155 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <stdlib.h>
+#include "command.h"
+#include "data-in.h"
+#include "message.h"
+#include "lexer.h"
+#include "magic.h"
+#include "range-parser.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+int
+cmd_missing_values (void)
+{
+  struct variable **v;
+  size_t nv;
+
+  int retval = CMD_PART_SUCCESS_MAYBE;
+  bool deferred_errors = false;
+
+  while (token != '.')
+    {
+      size_t i;
+
+      if (!parse_variables (default_dict, &v, &nv, PV_NONE)) 
+        goto done;
+
+      if (!lex_match ('('))
+        {
+          lex_error (_("expecting `('"));
+          goto done;
+        }
+
+      for (i = 0; i < nv; i++)
+        mv_init (&v[i]->miss, v[i]->width);
+
+      if (!lex_match (')')) 
+        {
+          struct missing_values mv;
+
+          for (i = 0; i < nv; i++)
+            if (v[i]->type != v[0]->type)
+              {
+                const struct variable *n = v[0]->type == NUMERIC ? v[0] : v[i];
+                const struct variable *s = v[0]->type == NUMERIC ? v[i] : v[0];
+                msg (SE, _("Cannot mix numeric variables (e.g. %s) and "
+                           "string variables (e.g. %s) within a single list."),
+                     n->name, s->name);
+                goto done;
+              }
+
+          if (v[0]->type == NUMERIC) 
+            {
+              mv_init (&mv, 0);
+              while (!lex_match (')'))
+                {
+                  double x, y;
+                  bool ok;
+
+                  if (!parse_num_range (&x, &y, &v[0]->print))
+                    goto done;
+                  
+                  ok = (x == y
+                        ? mv_add_num (&mv, x)
+                        : mv_add_num_range (&mv, x, y));
+                  if (!ok)
+                    deferred_errors = true;
+
+                  lex_match (',');
+                }
+            }
+          else 
+            {
+              mv_init (&mv, MAX_SHORT_STRING);
+              while (!lex_match (')')) 
+                {
+                  if (!lex_force_string ())
+                    {
+                      deferred_errors = true;
+                      break;
+                    }
+
+                  if (ds_length (&tokstr) > MAX_SHORT_STRING) 
+                    {
+                      ds_truncate (&tokstr, MAX_SHORT_STRING);
+                      msg (SE, _("Truncating missing value to short string "
+                                 "length (%d characters)."),
+                           MAX_SHORT_STRING);
+                    }
+                  else
+                    ds_rpad (&tokstr, MAX_SHORT_STRING, ' ');
+
+                  if (!mv_add_str (&mv, ds_data (&tokstr)))
+                    deferred_errors = true;
+
+                  lex_get ();
+                  lex_match (',');
+                }
+            }
+          
+          for (i = 0; i < nv; i++) 
+            {
+              if (!mv_is_resizable (&mv, v[i]->width)) 
+                {
+                  msg (SE, _("Missing values provided are too long to assign "
+                             "to variable of width %d."),
+                       v[i]->width);
+                  deferred_errors = true;
+                }
+              else 
+                {
+                  mv_copy (&v[i]->miss, &mv);
+                  mv_resize (&v[i]->miss, v[i]->width);
+                }
+            }
+        }
+
+      lex_match ('/');
+      free (v);
+      v = NULL;
+    }
+  retval = lex_end_of_command ();
+  
+ done:
+  free (v);
+  if (deferred_errors)
+    retval = CMD_PART_SUCCESS_MAYBE;
+  return retval;
+}
+
diff --git a/src/language/dictionary/modify-variables.c b/src/language/dictionary/modify-variables.c
new file mode 100644 (file)
index 0000000..fc91894
--- /dev/null
@@ -0,0 +1,526 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include "message.h"
+#include "array.h"
+#include "alloc.h"
+#include "bit-vector.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "hash.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+#include "variable.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* FIXME: should change weighting variable, etc. */
+/* These control the ordering produced by
+   compare_variables_given_ordering(). */
+struct ordering
+  {
+    int forward;               /* 1=FORWARD, 0=BACKWARD. */
+    int positional;            /* 1=POSITIONAL, 0=ALPHA. */
+  };
+
+/* Increasing order of variable index. */
+static struct ordering forward_positional_ordering = {1, 1};
+
+static int compare_variables_given_ordering (const void *, const void *,
+                                             void *ordering);
+
+/* Explains how to modify the variables in a dictionary. */
+struct var_modification
+  {
+    /* New variable ordering. */
+    struct variable **reorder_vars;
+    size_t reorder_cnt;
+
+    /* DROP/KEEP information. */
+    struct variable **drop_vars;
+    size_t drop_cnt;
+
+    /* New variable names. */
+    struct variable **rename_vars;
+    char **new_names;
+    size_t rename_cnt;
+  };
+
+static int rearrange_dict (struct dictionary *d,
+                           const struct var_modification *vm);
+
+/* 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;
+
+  /* Return code. */
+  int ret_code = CMD_CASCADING_FAILURE;
+
+  size_t i;
+
+  if (temporary != 0)
+    {
+      msg (SE, _("MODIFY VARS may not be used after TEMPORARY.  "
+                 "Temporary transformations will be made permanent."));
+      cancel_temporary (); 
+    }
+
+  vm.reorder_vars = NULL;
+  vm.reorder_cnt = 0;
+  vm.rename_vars = NULL;
+  vm.new_names = NULL;
+  vm.rename_cnt = 0;
+  vm.drop_vars = NULL;
+  vm.drop_cnt = 0;
+
+  /* Parse each subcommand. */
+  lex_match ('/');
+  for (;;)
+    {
+      if (lex_match_id ("REORDER"))
+       {
+         struct variable **v = NULL;
+         size_t nv = 0;
+
+         if (already_encountered & 1)
+           {
+             msg (SE, _("REORDER subcommand may be given at most once."));
+             goto done;
+           }
+         already_encountered |= 1;
+
+         lex_match ('=');
+         do
+           {
+              struct ordering ordering;
+             size_t prev_nv = nv;
+
+             ordering.forward = ordering.positional = 1;
+             if (lex_match_id ("FORWARD"));
+             else if (lex_match_id ("BACKWARD"))
+               ordering.forward = 0;
+             if (lex_match_id ("POSITIONAL"));
+             else if (lex_match_id ("ALPHA"))
+               ordering.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 done;
+                   }
+                 dict_get_vars (default_dict, &v, &nv, 1u << DC_SYSTEM);
+               }
+             else
+               {
+                 if (!lex_match ('('))
+                   {
+                     msg (SE, _("`(' expected on REORDER subcommand."));
+                     free (v);
+                     goto done;
+                   }
+                 if (!parse_variables (default_dict, &v, &nv,
+                                       PV_APPEND | PV_NO_DUPLICATE))
+                   {
+                     free (v);
+                     goto done;
+                   }
+                 if (!lex_match (')'))
+                   {
+                     msg (SE, _("`)' expected following variable names on "
+                          "REORDER subcommand."));
+                     free (v);
+                     goto done;
+                   }
+               }
+             sort (&v[prev_nv], nv - prev_nv, sizeof *v,
+                    compare_variables_given_ordering, &ordering);
+           }
+         while (token != '/' && token != '.');
+
+         vm.reorder_vars = v;
+          vm.reorder_cnt = nv;
+       }
+      else if (lex_match_id ("RENAME"))
+       {
+         if (already_encountered & 2)
+           {
+             msg (SE, _("RENAME subcommand may be given at most once."));
+             goto done;
+           }
+         already_encountered |= 2;
+
+         lex_match ('=');
+         do
+           {
+             size_t prev_nv_1 = vm.rename_cnt;
+             size_t prev_nv_2 = vm.rename_cnt;
+
+             if (!lex_match ('('))
+               {
+                 msg (SE, _("`(' expected on RENAME subcommand."));
+                 goto done;
+               }
+             if (!parse_variables (default_dict, &vm.rename_vars, &vm.rename_cnt,
+                                   PV_APPEND | PV_NO_DUPLICATE))
+               goto done;
+             if (!lex_match ('='))
+               {
+                 msg (SE, _("`=' expected between lists of new and old variable "
+                      "names on RENAME subcommand."));
+                 goto done;
+               }
+             if (!parse_DATA_LIST_vars (&vm.new_names, &prev_nv_1, PV_APPEND))
+               goto done;
+             if (prev_nv_1 != vm.rename_cnt)
+               {
+                 msg (SE, _("Differing number of variables in old name list "
+                      "(%d) and in new name list (%d)."),
+                      vm.rename_cnt - 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 done;
+               }
+             if (!lex_match (')'))
+               {
+                 msg (SE, _("`)' expected after variable lists on RENAME "
+                      "subcommand."));
+                 goto done;
+               }
+           }
+         while (token != '.' && token != '/');
+       }
+      else if (lex_match_id ("KEEP"))
+       {
+         struct variable **keep_vars, **all_vars, **drop_vars;
+         size_t keep_cnt, all_cnt, drop_cnt;
+
+         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 done;
+           }
+         already_encountered |= 4;
+
+         lex_match ('=');
+         if (!parse_variables (default_dict, &keep_vars, &keep_cnt, PV_NONE))
+           goto done;
+
+         /* 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. */
+         sort (keep_vars, keep_cnt, sizeof *keep_vars,
+                compare_variables_given_ordering, &forward_positional_ordering);
+
+          dict_get_vars (default_dict, &all_vars, &all_cnt, 0);
+          assert (all_cnt >= keep_cnt);
+
+          drop_cnt = all_cnt - keep_cnt;
+          drop_vars = xnmalloc (drop_cnt, sizeof *keep_vars);
+          if (set_difference (all_vars, all_cnt,
+                              keep_vars, keep_cnt,
+                              sizeof *all_vars,
+                              drop_vars,
+                              compare_variables_given_ordering,
+                              &forward_positional_ordering)
+              != drop_cnt)
+            assert (0);
+
+          free (keep_vars);
+          free (all_vars);
+
+          vm.drop_vars = drop_vars;
+          vm.drop_cnt = drop_cnt;
+       }
+      else if (lex_match_id ("DROP"))
+       {
+         struct variable **drop_vars;
+         size_t drop_cnt;
+
+         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 done;
+           }
+         already_encountered |= 4;
+
+         lex_match ('=');
+         if (!parse_variables (default_dict, &drop_vars, &drop_cnt, PV_NONE))
+           goto done;
+          vm.drop_vars = drop_vars;
+          vm.drop_cnt = drop_cnt;
+       }
+      else if (lex_match_id ("MAP"))
+       {
+          struct dictionary *temp = dict_clone (default_dict);
+          int success = rearrange_dict (temp, &vm);
+          if (success) 
+            {
+              /* FIXME: display new dictionary. */ 
+            }
+          dict_destroy (temp);
+       }
+      else
+       {
+         if (token == T_ID)
+           msg (SE, _("Unrecognized subcommand name `%s'."), tokid);
+         else
+           msg (SE, _("Subcommand name expected."));
+         goto done;
+       }
+
+      if (token == '.')
+       break;
+      if (token != '/')
+       {
+         msg (SE, _("`/' or `.' expected."));
+         goto done;
+       }
+      lex_get ();
+    }
+
+  if (already_encountered & (1 | 4))
+    {
+      /* Read the data. */
+      if (!procedure (NULL, NULL)) 
+        goto done; 
+    }
+
+  if (!rearrange_dict (default_dict, &vm))
+    goto done; 
+
+  ret_code = CMD_SUCCESS;
+
+done:
+  free (vm.reorder_vars);
+  free (vm.rename_vars);
+  for (i = 0; i < vm.rename_cnt; i++)
+    free (vm.new_names[i]);
+  free (vm.new_names);
+  free (vm.drop_vars);
+  return ret_code;
+}
+
+/* Compares A and B according to the settings in
+   ORDERING, returning a strcmp()-type result. */
+static int
+compare_variables_given_ordering (const void *a_, const void *b_,
+                                  void *ordering_)
+{
+  struct variable *const *pa = a_;
+  struct variable *const *pb = b_;
+  const struct variable *a = *pa;
+  const struct variable *b = *pb;
+  const struct ordering *ordering = ordering_;
+
+  int result;
+  if (ordering->positional)
+    result = a->index < b->index ? -1 : a->index > b->index;
+  else
+    result = strcasecmp (a->name, b->name);
+  if (!ordering->forward)
+    result = -result;
+  return result;
+}
+
+/* Pairs a variable with a new name. */
+struct var_renaming
+  {
+    struct variable *var;
+    char new_name[LONG_NAME_LEN + 1];
+  };
+
+/* A algo_compare_func that compares new_name members in struct
+   var_renaming structures A and B. */
+static int
+compare_var_renaming_by_new_name (const void *a_, const void *b_,
+                                  void *foo UNUSED) 
+{
+  const struct var_renaming *a = a_;
+  const struct var_renaming *b = b_;
+
+  return strcasecmp (a->new_name, b->new_name);
+}
+
+/* Returns true if performing VM on dictionary D would not cause
+   problems such as duplicate variable names.  Returns false
+   otherwise, and issues an error message. */
+static int
+validate_var_modification (const struct dictionary *d,
+                           const struct var_modification *vm) 
+{
+  /* Variable reordering can't be a problem, so we don't simulate
+     it.  Variable renaming can cause duplicate names, but
+     dropping variables can eliminate them, so we simulate both
+     of those. */
+  struct variable **all_vars;
+  struct variable **keep_vars;
+  struct variable **drop_vars;
+  size_t keep_cnt, drop_cnt;
+  size_t all_cnt;
+
+  struct var_renaming *var_renaming;
+  int valid;
+  size_t i;
+
+  /* All variables, in index order. */
+  dict_get_vars (d, &all_vars, &all_cnt, 0);
+
+  /* Drop variables, in index order. */
+  drop_cnt = vm->drop_cnt;
+  drop_vars = xnmalloc (drop_cnt, sizeof *drop_vars);
+  memcpy (drop_vars, vm->drop_vars, drop_cnt * sizeof *drop_vars);
+  sort (drop_vars, drop_cnt, sizeof *drop_vars,
+        compare_variables_given_ordering, &forward_positional_ordering);
+
+  /* Keep variables, in index order. */
+  assert (all_cnt >= drop_cnt);
+  keep_cnt = all_cnt - drop_cnt;
+  keep_vars = xnmalloc (keep_cnt, sizeof *keep_vars);
+  if (set_difference (all_vars, all_cnt,
+                      drop_vars, drop_cnt,
+                      sizeof *all_vars,
+                      keep_vars,
+                      compare_variables_given_ordering,
+                      &forward_positional_ordering) != keep_cnt)
+    assert (0);
+
+  /* Copy variables into var_renaming array. */
+  var_renaming = xnmalloc (keep_cnt, sizeof *var_renaming);
+  for (i = 0; i < keep_cnt; i++) 
+    {
+      var_renaming[i].var = keep_vars[i];
+      strcpy (var_renaming[i].new_name, keep_vars[i]->name);
+    }
+  
+  /* Rename variables in var_renaming array. */
+  for (i = 0; i < vm->rename_cnt; i++) 
+    {
+      struct variable *const *kv;
+      struct var_renaming *vr;
+
+      /* Get the var_renaming element. */
+      kv = binary_search (keep_vars, keep_cnt, sizeof *keep_vars,
+                          &vm->rename_vars[i],
+                          compare_variables_given_ordering,
+                          &forward_positional_ordering);
+      if (kv == NULL)
+        continue;
+      vr = var_renaming + (kv - keep_vars);
+
+      strcpy (vr->new_name, vm->new_names[i]);
+    }
+
+  /* Sort var_renaming array by new names and check for
+     duplicates. */
+  sort (var_renaming, keep_cnt, sizeof *var_renaming,
+        compare_var_renaming_by_new_name, NULL);
+  valid = adjacent_find_equal (var_renaming, keep_cnt, sizeof *var_renaming,
+                               compare_var_renaming_by_new_name, NULL) == NULL;
+
+  /* Clean up. */
+  free (all_vars);
+  free (keep_vars);
+  free (drop_vars);
+  free (var_renaming);
+
+  return valid;
+}
+
+/* Reoders, removes, and renames variables in dictionary D
+   according to VM.  Returns nonzero if successful, zero if there
+   would have been duplicate variable names if the modifications
+   had been carried out.  In the latter case, the dictionary is
+   not modified. */
+static int
+rearrange_dict (struct dictionary *d, const struct var_modification *vm)
+{
+  char **rename_old_names;
+
+  struct variable **rename_vars;
+  char **rename_new_names;
+  size_t rename_cnt;
+
+  size_t i;
+
+  /* Check whether the modifications will cause duplicate
+     names. */
+  if (!validate_var_modification (d, vm))
+    return 0;
+
+  /* Record the old names of variables to rename.  After
+     variables are deleted, we can't depend on the variables to
+     still exist, but we can still look them up by name. */
+  rename_old_names = xnmalloc (vm->rename_cnt, sizeof *rename_old_names);
+  for (i = 0; i < vm->rename_cnt; i++)
+    rename_old_names[i] = xstrdup (vm->rename_vars[i]->name);
+
+  /* Reorder and delete variables. */
+  dict_reorder_vars (d, vm->reorder_vars, vm->reorder_cnt);
+  dict_delete_vars (d, vm->drop_vars, vm->drop_cnt);
+
+  /* Compose lists of variables to rename and their new names. */
+  rename_vars = xnmalloc (vm->rename_cnt, sizeof *rename_vars);
+  rename_new_names = xnmalloc (vm->rename_cnt, sizeof *rename_new_names);
+  rename_cnt = 0;
+  for (i = 0; i < vm->rename_cnt; i++)
+    {
+      struct variable *var = dict_lookup_var (d, rename_old_names[i]);
+      if (var == NULL)
+        continue;
+      
+      rename_vars[rename_cnt] = var;
+      rename_new_names[rename_cnt] = vm->new_names[i];
+      rename_cnt++;
+    }
+
+  /* Do renaming. */
+  if (dict_rename_vars (d, rename_vars, rename_new_names, rename_cnt,
+                        NULL) == 0)
+    assert (0);
+
+  /* Clean up. */
+  for (i = 0; i < vm->rename_cnt; i++)
+    free (rename_old_names[i]);
+  free (rename_old_names);
+  free (rename_vars);
+  free (rename_new_names);
+
+  return 1;
+}
diff --git a/src/language/dictionary/numeric.c b/src/language/dictionary/numeric.c
new file mode 100644 (file)
index 0000000..03eb06b
--- /dev/null
@@ -0,0 +1,206 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <stdlib.h>
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "lexer.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+/* Parses the NUMERIC command. */
+int
+cmd_numeric (void)
+{
+  size_t i;
+
+  /* Names of variables to create. */
+  char **v;
+  size_t nv;
+
+  /* Format spec for variables to create.  f.type==-1 if default is to
+     be used. */
+  struct fmt_spec f;
+
+  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 = dict_create_var (default_dict, v[i], 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;
+           }
+       }
+
+      /* 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)
+{
+  size_t i;
+
+  /* Names of variables to create. */
+  char **v;
+  size_t nv;
+
+  /* Format spec for variables to create. */
+  struct fmt_spec f;
+
+  /* Width of variables to create. */
+  int width;
+
+  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);
+          abort ();
+       }
+
+      /* Create each variable. */
+      for (i = 0; i < nv; i++)
+       {
+         struct variable *new_var = dict_create_var (default_dict, v[i],
+                                                      width);
+         if (!new_var)
+           msg (SE, _("There is already a variable named %s."), v[i]);
+         else
+            new_var->print = new_var->write = f;
+       }
+
+      /* 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;
+  size_t nv;
+
+  size_t i;
+
+  if (!parse_variables (default_dict, &v, &nv, PV_NONE))
+    return CMD_CASCADING_FAILURE;
+  for (i = 0; i < nv; i++)
+    {
+      if (!v[i]->reinit)
+       continue;
+      v[i]->reinit = 0;
+      v[i]->init = 1;
+    }
+  free (v);
+
+  return lex_end_of_command ();
+}
diff --git a/src/language/dictionary/rename-variables.c b/src/language/dictionary/rename-variables.c
new file mode 100644 (file)
index 0000000..1c20c6a
--- /dev/null
@@ -0,0 +1,116 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include "message.h"
+#include "alloc.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "hash.h"
+#include "lexer.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* The code for this function is very similar to the code for the
+   RENAME subcommand of MODIFY VARS. */
+int
+cmd_rename_variables (void)
+{
+  struct variable **rename_vars = NULL;
+  char **rename_new_names = NULL;
+  size_t rename_cnt = 0;
+  char *err_name;
+
+  int status = CMD_CASCADING_FAILURE;
+
+  if (temporary != 0)
+    {
+      msg (SE, _("RENAME VARS may not be used after TEMPORARY.  "
+                 "Temporary transformations will be made permanent."));
+      cancel_temporary (); 
+    }
+
+  do
+    {
+      size_t prev_nv_1 = rename_cnt;
+      size_t prev_nv_2 = rename_cnt;
+
+      if (!lex_match ('('))
+       {
+         msg (SE, _("`(' expected."));
+         goto lossage;
+       }
+      if (!parse_variables (default_dict, &rename_vars, &rename_cnt,
+                           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 (&rename_new_names, &prev_nv_1, PV_APPEND))
+       goto lossage;
+      if (prev_nv_1 != rename_cnt)
+       {
+          size_t i;
+
+         msg (SE, _("Differing number of variables in old name list "
+                     "(%u) and in new name list (%u)."),
+              (unsigned) rename_cnt - prev_nv_2,
+               (unsigned) prev_nv_1 - prev_nv_2);
+         for (i = 0; i < prev_nv_1; i++)
+           free (rename_new_names[i]);
+         free (rename_new_names);
+         rename_new_names = NULL;
+         goto lossage;
+       }
+      if (!lex_match (')'))
+       {
+         msg (SE, _("`)' expected after variable names."));
+         goto lossage;
+       }
+    }
+  while (token != '.');
+
+  if (!dict_rename_vars (default_dict,
+                         rename_vars, rename_new_names, rename_cnt,
+                         &err_name)) 
+    {
+      msg (SE, _("Renaming would duplicate variable name %s."), err_name);
+      goto lossage;
+    }
+
+  status = CMD_SUCCESS;
+
+ lossage:
+  free (rename_vars);
+  if (rename_new_names != NULL) 
+    {
+      size_t i;
+      for (i = 0; i < rename_cnt; i++)
+        free (rename_new_names[i]);
+      free (rename_new_names); 
+    }
+  return status;
+}
diff --git a/src/language/dictionary/split-file.c b/src/language/dictionary/split-file.c
new file mode 100644 (file)
index 0000000..38eabc4
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "lexer.h"
+#include "str.h"
+#include "variable.h"
+
+int
+cmd_split_file (void)
+{
+  if (lex_match_id ("OFF"))
+    dict_set_split_vars (default_dict, NULL, 0);
+  else
+    {
+      struct variable **v;
+      size_t n;
+
+      /* For now, ignore SEPARATE and LAYERED. */
+      lex_match_id ("SEPARATE") || lex_match_id ("LAYERED");
+      
+      lex_match (T_BY);
+      if (!parse_variables (default_dict, &v, &n, PV_NO_DUPLICATE))
+       return CMD_CASCADING_FAILURE;
+
+      dict_set_split_vars (default_dict, v, n);
+      free (v);
+    }
+
+  return lex_end_of_command ();
+}
diff --git a/src/language/dictionary/sys-file-info.c b/src/language/dictionary/sys-file-info.c
new file mode 100644 (file)
index 0000000..0234b83
--- /dev/null
@@ -0,0 +1,608 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <ctype.h>
+#include <stdlib.h>
+#include "array.h"
+#include "alloc.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "file-handle-def.h"
+#include "hash.h"
+#include "lexer.h"
+#include "magic.h"
+#include "misc.h"
+#include "output.h"
+#include "sys-file-reader.h"
+#include "manager.h"
+#include "table.h"
+#include "value-labels.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* Constants for DISPLAY utility. */
+enum
+  {
+    AS_NAMES = 0,
+    AS_INDEX,
+    AS_VARIABLES,
+    AS_LABELS,
+    AS_DICTIONARY,
+    AS_SCRATCH,
+    AS_VECTOR
+  };
+
+static 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_reader *reader;
+  struct sfm_read_info info;
+  int r, nr;
+  int i;
+
+  lex_match_id ("FILE");
+  lex_match ('=');
+
+  h = fh_parse (FH_REF_FILE);
+  if (!h)
+    return CMD_FAILURE;
+
+  reader = sfm_open_reader (h, &d, &info);
+  if (!reader)
+    return CMD_FAILURE;
+  sfm_close_reader (reader);
+
+  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_get_filename (h));
+  tab_text (t, 0, 1, TAB_LEFT, _("Label:"));
+  {
+    const char *label = dict_get_label (d);
+    if (label == NULL)
+      label = _("No label.");
+    tab_text (t, 1, 1, TAB_LEFT, label);
+  }
+  tab_text (t, 0, 2, TAB_LEFT, _("Created:"));
+  tab_text (t, 1, 2, TAB_LEFT | TAT_PRINTF, "%s %s by %s",
+               info.creation_date, info.creation_time, info.product);
+  tab_text (t, 0, 3, TAB_LEFT, _("Endian:"));
+  tab_text (t, 1, 3, TAB_LEFT, info.big_endian ? _("Big.") : _("Little."));
+  tab_text (t, 0, 4, TAB_LEFT, _("Variables:"));
+  tab_text (t, 1, 4, TAB_LEFT | TAT_PRINTF, "%d",
+               dict_get_var_cnt (d));
+  tab_text (t, 0, 5, TAB_LEFT, _("Cases:"));
+  tab_text (t, 1, 5, TAB_LEFT | TAT_PRINTF,
+               info.case_cnt == -1 ? _("Unknown") : "%d", info.case_cnt);
+  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:"));
+  {
+    struct variable *weight_var = dict_get_weight (d);
+    tab_text (t, 1, 7, TAB_LEFT,
+              weight_var != NULL ? weight_var->name : _("Not weighted.")); 
+  }
+  tab_text (t, 0, 8, TAB_LEFT, _("Mode:"));
+  tab_text (t, 1, 8, TAB_LEFT | TAT_PRINTF,
+               _("Compression %s."), info.compressed ? _("on") : _("off"));
+  tab_dim (t, tab_natural_dimensions);
+  tab_submit (t);
+
+  nr = 1 + 2 * dict_get_var_cnt (d);
+
+  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 < dict_get_var_cnt (d); i++)
+    {
+      struct variable *v = dict_get_var (d, i);
+      const int nvl = val_labs_count (v->val_labs);
+      
+      if (r + 10 + nvl > nr)
+       {
+         nr = max (nr * dict_get_var_cnt (d) / (i + 1), nr);
+         nr += 10 + nvl;
+         tab_realloc (t, 4, nr);
+       }
+
+      r = describe_variable (v, t, r, AS_DICTIONARY);
+    }
+
+  tab_box (t, TAL_1, TAL_1, -1, -1, 0, 0, 3, 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);
+
+  dict_destroy (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 **, size_t, int);
+static void display_vectors (int sorted);
+
+int
+cmd_display (void)
+{
+  /* Whether to sort the list of variables alphabetically. */
+  int sorted;
+
+  /* Variables to display. */
+  size_t n;
+  struct variable **vl;
+
+  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 (dict_get_label (default_dict) == 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, dict_get_label (default_dict));
+       }
+    }
+  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 (default_dict, &vl, &n, PV_NONE))
+           {
+             free (vl);
+             return CMD_FAILURE;
+           }
+         as = AS_DICTIONARY;
+       }
+      else
+       dict_get_vars (default_dict, &vl, &n, 0);
+
+      if (as == AS_SCRATCH)
+       {
+         size_t i, m;
+         for (i = 0, m = n; i < n; i++)
+           if (dict_class_from_id (vl[i]->name) != DC_SCRATCH)
+             {
+               vl[i] = NULL;
+               m--;
+             }
+         as = AS_NAMES;
+         n = m;
+       }
+
+      if (n == 0)
+       {
+         msg (SW, _("No variables to display."));
+         return CMD_FAILURE;
+       }
+
+      if (sorted)
+       sort (vl, n, sizeof *vl, compare_var_names, NULL);
+
+      display_variables (vl, n, as);
+
+      free (vl);
+    }
+
+  return lex_end_of_command ();
+}
+
+static void
+display_macros (void)
+{
+  som_blank_line ();
+  tab_output_text (TAB_LEFT, _("Macros not supported."));
+}
+
+static void
+display_documents (void)
+{
+  const char *documents = dict_get_documents (default_dict);
+
+  som_blank_line ();
+  if (documents == NULL)
+    tab_output_text (TAB_LEFT, _("The active file dictionary does not "
+                                 "contain any documents."));
+  else
+    {
+      size_t n_lines = strlen (documents) / 80;
+      char buf[81];
+      size_t i;
+
+      tab_output_text (TAB_LEFT | TAT_TITLE,
+                      _("Documents in the active file:"));
+      som_blank_line ();
+      buf[80] = 0;
+      for (i = 0; i < n_lines; i++)
+       {
+         int len = 79;
+
+         memcpy (buf, &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, size_t 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. */
+  size_t 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"));
+  pc = (as == AS_INDEX ? 1 : 3);
+  if (as != AS_NAMES)
+    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 = val_labs_count (v->val_labs);
+      
+         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. */
+static 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 (!mv_is_empty (&v->miss))
+    {
+      char buf[128];
+      char *cp;
+      struct missing_values mv;
+      int cnt = 0;
+      
+      cp = stpcpy (buf, _("Missing Values: "));
+      mv_copy (&mv, &v->miss);
+      if (mv_has_range (&mv)) 
+        {
+          double x, y;
+          mv_pop_range (&mv, &x, &y);
+          if (x == LOWEST)
+            cp += nsprintf (cp, "LOWEST THRU %g", y);
+          else if (y == HIGHEST)
+            cp += nsprintf (cp, "%g THRU HIGHEST", x);
+          else
+            cp += nsprintf (cp, "%g THRU %g", x, y);
+          cnt++;
+        }
+      while (mv_has_value (&mv)) 
+        {
+          union value value;
+          mv_pop_value (&mv, &value);
+          if (cnt++ > 0)
+            cp += nsprintf (cp, "; ");
+          if (v->type == NUMERIC)
+            cp += nsprintf (cp, "%g", value.f);
+          else 
+            {
+              *cp++ = '"';
+             memcpy (cp, value.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 && val_labs_count (v->val_labs))
+    {
+      struct val_labs_iterator *i;
+      struct val_lab *vl;
+      int orig_r = r;
+
+#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);
+      for (vl = val_labs_first_sorted (v->val_labs, &i); vl != NULL;
+           vl = val_labs_next (v->val_labs, &i))
+        {
+         char buf[128];
+
+         if (v->type == ALPHA)
+           {
+             memcpy (buf, vl->value.s, v->width);
+             buf[v->width] = 0;
+           }
+         else
+           sprintf (buf, "%g", vl->value.f);
+
+         tab_text (t, 1, r, TAB_NONE, buf);
+         tab_text (t, 2, r, TAB_LEFT, vl->label);
+         r++;
+       }
+
+      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_)
+{
+  struct vector *const *pa = a_;
+  struct vector *const *pb = b_;
+  struct vector *a = *pa;
+  struct vector *b = *pb;
+  
+  return strcasecmp (a->name, b->name);
+}
+
+/* Display a list of vectors.  If SORTED is nonzero then they are
+   sorted alphabetically. */
+static void
+display_vectors (int sorted)
+{
+  const struct vector **vl;
+  int i;
+  struct tab_table *t;
+  size_t nvec;
+  
+  nvec = dict_get_vector_cnt (default_dict);
+  if (nvec == 0)
+    {
+      msg (SW, _("No vectors defined."));
+      return;
+    }
+
+  vl = xnmalloc (nvec, sizeof *vl);
+  for (i = 0; i < nvec; i++)
+    vl[i] = dict_get_vector (default_dict, 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/language/dictionary/value-labels.c b/src/language/dictionary/value-labels.c
new file mode 100644 (file)
index 0000000..96b80ba
--- /dev/null
@@ -0,0 +1,193 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "message.h"
+#include "hash.h"
+#include "lexer.h"
+#include "str.h"
+#include "value-labels.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+\f
+/* Declarations. */
+
+static int do_value_labels (int);
+static int verify_val_labs (struct variable **vars, size_t var_cnt);
+static void erase_labels (struct variable **vars, size_t var_cnt);
+static int get_label (struct variable **vars, size_t var_cnt);
+\f
+/* Stubs. */
+
+int
+cmd_value_labels (void)
+{
+  return do_value_labels (1);
+}
+
+int
+cmd_add_value_labels (void)
+{
+  return do_value_labels (0);
+}
+\f
+/* Do it. */
+
+static int
+do_value_labels (int erase)
+{
+  struct variable **vars; /* Variable list. */
+  size_t var_cnt;         /* Number of variables. */
+  int parse_err=0;        /* true if error parsing variables */
+
+  lex_match ('/');
+  
+  while (token != '.')
+    {
+      parse_err = !parse_variables (default_dict, &vars, &var_cnt, 
+                                   PV_SAME_TYPE) ;
+      if (var_cnt < 1)
+       {
+         free(vars);
+         return CMD_FAILURE;
+       }
+      if (!verify_val_labs (vars, var_cnt))
+        goto lossage;
+      if (erase)
+        erase_labels (vars, var_cnt);
+      while (token != '/' && token != '.')
+       if (!get_label (vars, var_cnt))
+          goto lossage;
+
+      if (token != '/')
+       {
+       free (vars);
+       break;
+       }
+
+      lex_get ();
+
+      free (vars);
+    }
+
+  if (token != '.')
+    {
+      lex_error (NULL);
+      return CMD_TRAILING_GARBAGE;
+    }
+
+  return parse_err ? CMD_PART_SUCCESS_MAYBE : CMD_SUCCESS;
+
+ lossage:
+  free (vars);
+  return CMD_PART_SUCCESS_MAYBE;
+}
+
+/* Verifies that none of the VAR_CNT variables in VARS are long
+   string variables. */
+static int
+verify_val_labs (struct variable **vars, size_t var_cnt)
+{
+  size_t i;
+
+  for (i = 0; i < var_cnt; i++)
+    {
+      struct variable *vp = vars[i];
+
+      if (vp->type == ALPHA && vp->width > MAX_SHORT_STRING)
+       {
+         msg (SE, _("It is not possible to assign value labels to long "
+                    "string variables such as %s."), vp->name);
+         return 0;
+       }
+    }
+  return 1;
+}
+
+/* Erases all the labels for the VAR_CNT variables in VARS. */
+static void
+erase_labels (struct variable **vars, size_t var_cnt) 
+{
+  size_t i;
+
+  /* Erase old value labels if desired. */
+  for (i = 0; i < var_cnt; i++)
+    val_labs_clear (vars[i]->val_labs);
+}
+
+/* Parse all the labels for the VAR_CNT variables in VARS and add
+   the specified labels to those variables.  */
+static int
+get_label (struct variable **vars, size_t var_cnt)
+{
+  /* Parse all the labels and add them to the variables. */
+  do
+    {
+      union value value;
+      char *label;
+      size_t i;
+
+      /* Set value. */
+      if (vars[0]->type == ALPHA)
+       {
+         if (token != T_STRING)
+           {
+              lex_error (_("expecting string"));
+             return 0;
+           }
+         buf_copy_str_rpad (value.s, MAX_SHORT_STRING, ds_c_str (&tokstr));
+       }
+      else
+       {
+         if (!lex_is_number ())
+           {
+             lex_error (_("expecting integer"));
+             return 0;
+           }
+         if (!lex_is_integer ())
+           msg (SW, _("Value label `%g' is not integer."), tokval);
+         value.f = tokval;
+       }
+      lex_get ();
+
+      /* Set label. */
+      if (!lex_force_string ())
+       return 0;
+      if (ds_length (&tokstr) > 60)
+       {
+         msg (SW, _("Truncating value label to 60 characters."));
+         ds_truncate (&tokstr, 60);
+       }
+      label = ds_c_str (&tokstr);
+
+      for (i = 0; i < var_cnt; i++)
+        val_labs_replace (vars[i]->val_labs, value, label);
+
+      lex_get ();
+    }
+  while (token != '/' && token != '.');
+
+  return 1;
+}
diff --git a/src/language/dictionary/variable-display.c b/src/language/dictionary/variable-display.c
new file mode 100644 (file)
index 0000000..b7dd812
--- /dev/null
@@ -0,0 +1,170 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "message.h"
+#include "lexer.h"
+#include "str.h"
+#include "variable.h"
+
+#include "debug-print.h"
+
+/* Set variables' alignment
+   This is the alignment for GUI display only.
+   It affects nothing but GUIs
+*/
+int
+cmd_variable_alignment (void)
+{
+  do
+    {
+      struct variable **v;
+      size_t nv;
+
+      size_t i;
+      enum alignment align;
+
+
+      if (!parse_variables (default_dict, &v, &nv, PV_NONE))
+        return CMD_PART_SUCCESS_MAYBE;
+
+      if ( lex_force_match('(') ) 
+       {
+         if ( lex_match_id("LEFT"))
+           align = ALIGN_LEFT;
+         else if ( lex_match_id("RIGHT"))
+           align = ALIGN_RIGHT;
+         else if ( lex_match_id("CENTER"))
+           align = ALIGN_CENTRE;
+         else 
+            {
+              free (v);
+              return CMD_FAILURE; 
+            }
+
+         lex_force_match(')');
+       }
+      else 
+        {
+          free (v);
+          return CMD_FAILURE; 
+        }
+
+      for( i = 0 ; i < nv ; ++i ) 
+       v[i]->alignment = align;
+
+
+      while (token == '/')
+       lex_get ();
+      free (v);
+
+    }
+  while (token != '.');
+  return CMD_SUCCESS;
+}
+
+/* Set variables' display width.
+   This is the width for GUI display only.
+   It affects nothing but GUIs
+*/
+int
+cmd_variable_width (void)
+{
+  do
+    {
+      struct variable **v;
+      size_t nv;
+      size_t i;
+
+      if (!parse_variables (default_dict, &v, &nv, PV_NONE))
+        return CMD_PART_SUCCESS_MAYBE;
+
+      if ( lex_force_match('(') ) 
+       {
+         if ( lex_force_int()) 
+           lex_get();
+         else
+           return CMD_FAILURE;
+         lex_force_match(')');
+       }
+
+      for( i = 0 ; i < nv ; ++i ) 
+         v[i]->display_width = tokval;
+
+      while (token == '/')
+       lex_get ();
+      free (v);
+
+    }
+  while (token != '.');
+  return CMD_SUCCESS;
+}
+
+/* Set variables' measurement level */
+int
+cmd_variable_level (void)
+{
+  do
+    {
+      struct variable **v;
+      size_t nv;
+      enum measure level;
+      size_t i;
+
+      if (!parse_variables (default_dict, &v, &nv, PV_NONE))
+        return CMD_PART_SUCCESS_MAYBE;
+
+      if ( lex_force_match('(') ) 
+       {
+         if ( lex_match_id("SCALE"))
+           level = MEASURE_SCALE;
+         else if ( lex_match_id("ORDINAL"))
+           level = MEASURE_ORDINAL;
+         else if ( lex_match_id("NOMINAL"))
+           level = MEASURE_NOMINAL;
+         else 
+            {
+              free (v);
+              return CMD_FAILURE; 
+            }
+
+         lex_force_match(')');
+       }
+      else
+        {
+          free (v);
+          return CMD_FAILURE; 
+        }
+      
+      for( i = 0 ; i < nv ; ++i ) 
+       v[i]->measure = level ;
+
+
+      while (token == '/')
+       lex_get ();
+      free (v);
+
+    }
+  while (token != '.');
+  return CMD_SUCCESS;
+}
diff --git a/src/language/dictionary/variable-label.c b/src/language/dictionary/variable-label.c
new file mode 100644 (file)
index 0000000..6ddb00a
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "message.h"
+#include "lexer.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+int
+cmd_variable_labels (void)
+{
+  do
+    {
+      struct variable **v;
+      size_t nv;
+
+      size_t i;
+
+      if (!parse_variables (default_dict, &v, &nv, PV_NONE))
+        return CMD_PART_SUCCESS_MAYBE;
+
+      if (token != T_STRING)
+       {
+         msg (SE, _("String expected for variable label."));
+         free (v);
+         return CMD_PART_SUCCESS_MAYBE;
+       }
+      if (ds_length (&tokstr) > 255)
+       {
+         msg (SW, _("Truncating variable label to 255 characters."));
+         ds_truncate (&tokstr, 255);
+       }
+      for (i = 0; i < nv; i++)
+       {
+         if (v[i]->label)
+           free (v[i]->label);
+         v[i]->label = xstrdup (ds_c_str (&tokstr));
+       }
+
+      lex_get ();
+      while (token == '/')
+       lex_get ();
+      free (v);
+    }
+  while (token != '.');
+  return CMD_SUCCESS;
+}
+
+
+
+const char *
+var_to_string(const struct variable *var)
+{
+  if ( !var ) 
+    return 0;
+
+  return ( var->label ? var->label : var->name);
+}
diff --git a/src/language/dictionary/vector.c b/src/language/dictionary/vector.c
new file mode 100644 (file)
index 0000000..9b1eb2c
--- /dev/null
@@ -0,0 +1,205 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+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;
+
+  cp = vecnames = xmalloc (256);
+  endp = &vecnames[256];
+  do
+    {
+      /* Get the name(s) of the new vector(s). */
+      if (!lex_force_id ())
+       return CMD_CASCADING_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 (!strcasecmp (cp2, tokid))
+             {
+               msg (SE, _("Vector name %s is given twice."), tokid);
+               goto fail;
+             }
+
+         if (dict_lookup_vector (default_dict, 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. */
+          struct variable **v;
+          size_t nv;
+
+         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 (default_dict, &v, &nv,
+                                PV_SAME_TYPE | PV_DUPLICATE))
+           goto fail;
+
+          dict_create_vector (default_dict, vecnames, v, nv);
+          free (v);
+       }
+      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[SHORT_NAME_LEN + 1];
+
+          /* Vector variables. */
+          struct variable **v;
+          int nv;
+
+         if (!lex_force_int ())
+           return CMD_CASCADING_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 LONG_NAME_LEN characters or shorter. */
+         ndig = intlog10 (nv);
+         for (cp = vecnames; *cp;)
+           {
+             int len = strlen (cp);
+             if (len + ndig > LONG_NAME_LEN)
+               {
+                 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 (dict_lookup_var (default_dict, name))
+                   {
+                     msg (SE, _("There is already a variable named %s."),
+                           name);
+                     goto fail;
+                   }
+               }
+             cp += strlen (cp) + 1;
+           }
+
+         /* Finally create the variables and vectors. */
+          v = xmalloc (nv * sizeof *v);
+         for (cp = vecnames; *cp;)
+           {
+             for (i = 0; i < nv; i++)
+               {
+                 sprintf (name, "%s%d", cp, i + 1);
+                 v[i] = dict_create_var_assert (default_dict, name, 0);
+               }
+              if (!dict_create_vector (default_dict, cp, v, nv))
+                assert (0);
+             cp += strlen (cp) + 1;
+           }
+          free (v);
+       }
+      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);
+  return CMD_PART_SUCCESS_MAYBE;
+}
diff --git a/src/language/dictionary/weight.c b/src/language/dictionary/weight.c
new file mode 100644 (file)
index 0000000..b6d63c9
--- /dev/null
@@ -0,0 +1,61 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <stdio.h>
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "lexer.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+int
+cmd_weight (void)
+{
+  if (lex_match_id ("OFF"))
+    dict_set_weight (default_dict, NULL);
+  else
+    {
+      struct variable *v;
+
+      lex_match (T_BY);
+      v = parse_variable ();
+      if (!v)
+       return CMD_CASCADING_FAILURE;
+      if (v->type == ALPHA)
+       {
+         msg (SE, _("The weighting variable must be numeric."));
+         return CMD_CASCADING_FAILURE;
+       }
+      if (dict_class_from_id (v->name) == DC_SCRATCH)
+       {
+         msg (SE, _("The weighting variable may not be scratch."));
+         return CMD_CASCADING_FAILURE;
+       }
+
+      dict_set_weight (default_dict, v);
+    }
+
+  return lex_end_of_command ();
+}
diff --git a/src/language/expressions/.cvsignore b/src/language/expressions/.cvsignore
new file mode 100644 (file)
index 0000000..d84d124
--- /dev/null
@@ -0,0 +1,7 @@
+Makefile
+Makefile.in
+evaluate.h
+evaluate.inc
+operations.h
+optimize.inc
+parse.inc
diff --git a/src/language/expressions/ChangeLog b/src/language/expressions/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/language/expressions/TODO b/src/language/expressions/TODO
new file mode 100644 (file)
index 0000000..2bcbb8c
--- /dev/null
@@ -0,0 +1,20 @@
+Needed:
+
+    - Warnings on domain errors (see "Domain Errors" in SPSS manual)
+      and documentation of such.
+
+    - Finish polishing code.  Many functions need comments.
+
+    - Test the remaining statistical distributions.
+
+    - Implement unimplemented functions.
+
+    - Check treatment of 0 bytes in expressions is correct.
+
+Extension ideas:
+
+    - Short-circuit evaluation of logical ops
+
+    - Conditional operator with ? :
+
+    - User-defined functions.
diff --git a/src/language/expressions/evaluate.c b/src/language/expressions/evaluate.c
new file mode 100644 (file)
index 0000000..e5f605b
--- /dev/null
@@ -0,0 +1,303 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "private.h"
+
+#include <ctype.h>
+#include "alloc.h"
+#include "message.h"
+#include "helpers.h"
+#include "evaluate.h"
+#include "pool.h"
+
+static void
+expr_evaluate (struct expression *e, const struct ccase *c, int case_idx,
+               void *result)
+{
+  union operation_data *op = e->ops;
+
+  double *ns = e->number_stack;
+  struct fixed_string *ss = e->string_stack;
+
+  assert ((c != NULL) == (e->dict != NULL));
+  pool_clear (e->eval_pool);
+
+  for (;;)
+    {
+      assert (op < e->ops + e->op_cnt);
+      switch (op++->operation)
+       {
+        case OP_number:
+        case OP_boolean:
+          *ns++ = op++->number;
+          break;
+
+        case OP_string:
+          {
+            const struct fixed_string *s = &op++->string;
+            *ss++ = copy_string (e, s->string, s->length);
+          }
+          break;
+
+        case OP_return_number:
+          *(double *) result = finite (ns[-1]) ? ns[-1] : SYSMIS;
+          return;
+
+        case OP_return_string:
+          *(struct fixed_string *) result = ss[-1];
+          return;
+
+#include "evaluate.inc"
+          
+       default:
+         abort ();
+       }
+    }
+}
+
+double
+expr_evaluate_num (struct expression *e, const struct ccase *c, int case_idx)
+{
+  double d;
+
+  assert (e->type == OP_number || e->type == OP_boolean);
+  expr_evaluate (e, c, case_idx, &d);
+  return d;
+}
+
+void
+expr_evaluate_str (struct expression *e, const struct ccase *c, int case_idx,
+                   char *dst, size_t dst_size) 
+{
+  struct fixed_string s;
+
+  assert (e->type == OP_string);
+  assert ((dst == NULL) == (dst_size == 0));
+  expr_evaluate (e, c, case_idx, &s);
+  buf_copy_rpad (dst, dst_size, s.string, s.length);
+}
+\f
+#include "lexer.h"
+#include "command.h"
+
+int
+cmd_debug_evaluate (void)
+{
+  bool optimize = true;
+  int retval = CMD_FAILURE;
+  bool dump_postfix = false;
+  struct dictionary *d = NULL;
+  struct ccase *c = NULL;
+
+  struct expression *expr;
+
+  for (;;) 
+    {
+      if (lex_match_id ("NOOPTIMIZE"))
+        optimize = 0;
+      else if (lex_match_id ("POSTFIX"))
+        dump_postfix = 1;
+      else if (lex_match ('('))
+        {
+          char name[LONG_NAME_LEN + 1];
+          struct variable *v;
+          size_t old_value_cnt;
+          int width;
+
+          if (!lex_force_id ())
+            goto done;
+          strcpy (name, tokid);
+
+          lex_get ();
+          if (!lex_force_match ('='))
+            goto done;
+
+          if (lex_is_number ())
+            {
+              width = 0;
+              fprintf (stderr, "(%s = %.2f)", name, tokval); 
+            }
+          else if (token == T_STRING) 
+            {
+              width = ds_length (&tokstr);
+              fprintf (stderr, "(%s = \"%.2s\")", name, ds_c_str (&tokstr)); 
+            }
+          else
+            {
+              lex_error (_("expecting number or string"));
+              goto done;
+            }
+
+          if (d == NULL)
+            d = dict_create ();
+          
+          old_value_cnt = dict_get_next_value_idx (d);
+          v = dict_create_var (d, name, width);
+          if (v == NULL)
+            {
+              msg (SE, _("Duplicate variable name %s."), name);
+              goto done;
+            }
+
+          if (c == NULL) 
+            {
+              c = xmalloc (sizeof *c);
+              case_nullify (c);
+            }
+          case_resize (c, old_value_cnt, dict_get_next_value_idx (d));
+
+          if (lex_is_number ())
+            case_data_rw (c, v->fv)->f = tokval;
+          else
+            memcpy (case_data_rw (c, v->fv)->s, ds_data (&tokstr),
+                    v->width);
+          lex_get ();
+
+          if (!lex_force_match (')'))
+            goto done;
+        }
+      else 
+        break;
+    }
+  if (token != '/') 
+    {
+      lex_force_match ('/');
+      goto done;
+    }
+  if (d != NULL)
+    fprintf (stderr, "; ");
+  fprintf (stderr, "%s => ", lex_rest_of_line (NULL));
+  lex_get ();
+
+  expr = expr_parse_any (d, optimize);
+  if (!expr || lex_end_of_command () != CMD_SUCCESS)
+    {
+      if (expr != NULL)
+        expr_free (expr);
+      fprintf (stderr, "error\n");
+      goto done;
+    }
+
+  if (dump_postfix) 
+    expr_debug_print_postfix (expr);
+  else 
+    switch (expr->type) 
+      {
+      case OP_number: 
+        {
+          double d = expr_evaluate_num (expr, c, 0);
+          if (d == SYSMIS)
+            fprintf (stderr, "sysmis\n");
+          else
+            fprintf (stderr, "%.2f\n", d); 
+        }
+        break;
+      
+      case OP_boolean: 
+        {
+          double b = expr_evaluate_num (expr, c, 0);
+          fprintf (stderr, "%s\n",
+                   b == SYSMIS ? "sysmis" : b == 0.0 ? "false" : "true"); 
+        }
+        break;
+
+      case OP_string: 
+        {
+          struct fixed_string s;
+          expr_evaluate (expr, c, 0, &s);
+
+          fputc ('"', stderr);
+          fwrite (s.string, s.length, 1, stderr);
+          fputs ("\"\n", stderr);
+          break; 
+        }
+
+      default:
+        assert (0);
+      }
+
+  expr_free (expr);
+  retval = CMD_SUCCESS;
+
+ done:
+  if (c != NULL) 
+    {
+      case_destroy (c);
+      free (c); 
+    }
+  dict_destroy (d);
+  return retval;
+}
+
+void
+expr_debug_print_postfix (const struct expression *e) 
+{
+  size_t i;
+
+  for (i = 0; i < e->op_cnt; i++) 
+    {
+      union operation_data *op = &e->ops[i];
+      if (i > 0)
+        putc (' ', stderr);
+      switch (e->op_types[i]) 
+        {
+        case OP_operation:
+          if (op->operation == OP_return_number)
+            fprintf (stderr, "return_number");
+          else if (op->operation == OP_return_string)
+            fprintf (stderr, "return_string");
+          else if (is_function (op->operation)) 
+            fprintf (stderr, "%s", operations[op->operation].prototype);
+          else if (is_composite (op->operation)) 
+            fprintf (stderr, "%s", operations[op->operation].name);
+          else
+            fprintf (stderr, "%s:", operations[op->operation].name);
+          break;
+        case OP_number:
+          if (op->number != SYSMIS)
+            fprintf (stderr, "n<%g>", op->number);
+          else
+            fprintf (stderr, "n<SYSMIS>");
+          break;
+        case OP_string:
+          fprintf (stderr, "s<%.*s>",
+                   (int) op->string.length,
+                   op->string.string != NULL ? op->string.string : "");
+          break;
+        case OP_format:
+          fprintf (stderr, "f<%s%d.%d>",
+                  formats[op->format->type].name,
+                  op->format->w, op->format->d);
+          break;
+        case OP_variable:
+          fprintf (stderr, "v<%s>", op->variable->name);
+          break;
+        case OP_vector:
+          fprintf (stderr, "vec<%s>", op->vector->name);
+          break;
+        case OP_integer:
+          fprintf (stderr, "i<%d>", op->integer);
+          break;
+        default:
+          abort ();
+        } 
+    }
+  fprintf (stderr, "\n");
+}
diff --git a/src/language/expressions/evaluate.h.pl b/src/language/expressions/evaluate.h.pl
new file mode 100644 (file)
index 0000000..e1a762f
--- /dev/null
@@ -0,0 +1,32 @@
+do 'generate.pl';
+
+sub generate_output {
+    print "#include \"helpers.h\"\n\n";
+
+    for my $opname (@order) {
+       my ($op) = $ops{$opname};
+       next if $op->{UNIMPLEMENTED};
+
+       my (@args);
+       for my $arg (@{$op->{ARGS}}) {
+           if (!defined $arg->{IDX}) {
+               push (@args, c_type ($arg->{TYPE}) . $arg->{NAME});
+           } else {
+               push (@args, c_type ($arg->{TYPE}) . "$arg->{NAME}" . "[]");
+               push (@args, "size_t $arg->{IDX}");
+           }
+       }
+       for my $aux (@{$op->{AUX}}) {
+           push (@args, c_type ($aux->{TYPE}) . $aux->{NAME});
+       }
+       push (@args, "void") if !@args;
+
+       my ($statements) = $op->{BLOCK} || "  return $op->{EXPRESSION};\n";
+
+       print "static inline ", c_type ($op->{RETURNS}), "\n";
+       print "eval_$opname (", join (', ', @args), ")\n";
+       print "{\n";
+       print "$statements";
+       print "}\n\n";
+    }
+}
diff --git a/src/language/expressions/evaluate.inc.pl b/src/language/expressions/evaluate.inc.pl
new file mode 100644 (file)
index 0000000..85112f9
--- /dev/null
@@ -0,0 +1,79 @@
+do 'generate.pl';
+
+sub generate_output {
+    for my $opname (@order) {
+       my ($op) = $ops{$opname};
+
+       if ($op->{UNIMPLEMENTED}) {
+           print "case $opname:\n";
+           print "  abort ();\n\n";
+           next;
+       }
+
+       my (@decls);
+       my (@args);
+       for my $arg (@{$op->{ARGS}}) {
+           my ($name) = $arg->{NAME};
+           my ($type) = $arg->{TYPE};
+           my ($c_type) = c_type ($type);
+           my ($idx) = $arg->{IDX};
+           push (@args, "arg_$arg->{NAME}");
+           if (!defined ($idx)) {
+               my ($decl) = "${c_type}arg_$name";
+               if ($type->{ROLE} eq 'any') {
+                   unshift (@decls, "$decl = *--$type->{STACK}");
+               } elsif ($type->{ROLE} eq 'leaf') {
+                   push (@decls, "$decl = op++->$type->{ATOM}");
+               } else {
+                   die;
+               }
+           } else {
+               my ($stack) = $type->{STACK};
+               defined $stack or die;
+               unshift (@decls,
+                        "$c_type*arg_$arg->{NAME} = $stack -= arg_$idx");
+               unshift (@decls, "size_t arg_$arg->{IDX} = op++->integer");
+
+               my ($idx) = "arg_$idx";
+               if ($arg->{TIMES} != 1) {
+                   $idx .= " / $arg->{TIMES}";
+               }
+               push (@args, $idx);
+           }
+       }
+       for my $aux (@{$op->{AUX}}) {
+           my ($type) = $aux->{TYPE};
+           my ($name) = $aux->{NAME};
+           if ($type->{ROLE} eq 'leaf') {
+               my ($c_type) = c_type ($type);
+               push (@decls, "${c_type}aux_$name = op++->$type->{ATOM}");
+               push (@args, "aux_$name");
+           } elsif ($type->{ROLE} eq 'fixed') {
+               push (@args, $type->{FIXED_VALUE});
+           }
+       }
+
+       my ($sysmis_cond) = make_sysmis_decl ($op, "op++->integer");
+       push (@decls, $sysmis_cond) if defined $sysmis_cond;
+
+       my ($result) = "eval_$op->{OPNAME} (" . join (', ', @args) . ")";
+
+       my ($stack) = $op->{RETURNS}{STACK};
+
+       print "case $opname:\n";
+       if (@decls) {
+           print "  {\n";
+           print "    $_;\n" foreach @decls;
+           if (defined $sysmis_cond) {
+               my ($miss_ret) = $op->{RETURNS}{MISSING_VALUE};
+               print "    *$stack++ = force_sysmis ? $miss_ret : $result;\n";
+           } else {
+               print "    *$stack++ = $result;\n";
+           }
+           print "  }\n";
+       } else {
+           print "  *$stack++ = $result;\n";
+       }
+       print "  break;\n\n";
+    }
+}
diff --git a/src/language/expressions/generate.pl b/src/language/expressions/generate.pl
new file mode 100644 (file)
index 0000000..9d75386
--- /dev/null
@@ -0,0 +1,632 @@
+use strict;
+use warnings 'all';
+
+use Getopt::Long;
+
+# Parse command line.
+our ($default_output_file) = $0;
+$default_output_file =~ s/\.pl//;
+our ($input_file);
+our ($output_file);
+parse_cmd_line ();
+
+# Initialize type system.
+our (%type, @types);
+init_all_types ();
+
+# Parse input file.
+our (%ops);
+our (@funcs, @opers);
+parse_input ();
+
+# Produce output.
+print_header ();
+generate_output ();
+print_trailer ();
+\f
+# Command line.
+
+# Parses the command line.
+#
+# Initializes $input_file, $output_file.
+sub parse_cmd_line {
+    GetOptions ("i|input=s" => \$input_file,
+               "o|output=s" => \$output_file,
+               "h|help" => sub { usage (); })
+      or exit 1;
+
+    $input_file = "operations.def" if !defined $input_file;
+    $output_file = $default_output_file if !defined $output_file;
+
+    open (INPUT, "<$input_file") or die "$input_file: open: $!\n";
+    open (OUTPUT, ">$output_file") or die "$output_file: create: $!\n";
+
+    select (OUTPUT);
+}
+
+sub usage {
+    print <<EOF;
+$0, for generating $default_output_file from definitions
+usage: generate.pl [-i INPUT] [-o OUTPUT] [-h]
+  -i INPUT    input file containing definitions (default: operations.def)
+  -o OUTPUT   output file (default: $default_output_file)
+  -h          display this help message
+EOF
+    exit (0);
+}
+
+our ($token);
+our ($toktype);
+\f
+# Types.
+
+# Defines all our types.
+#
+# Initializes %type, @types.
+sub init_all_types {
+    # Common user-visible types used throughout evaluation trees.
+    init_type ('number', 'any', C_TYPE => 'double',
+              ATOM => 'number', MANGLE => 'n', HUMAN_NAME => 'num',
+              STACK => 'ns', MISSING_VALUE => 'SYSMIS');
+    init_type ('string', 'any', C_TYPE => 'struct fixed_string',
+              ATOM => 'string', MANGLE => 's', HUMAN_NAME => 'string',
+              STACK => 'ss', MISSING_VALUE => 'empty_string');
+    init_type ('boolean', 'any', C_TYPE => 'double',
+              ATOM => 'number', MANGLE => 'n', HUMAN_NAME => 'boolean',
+              STACK => 'ns', MISSING_VALUE => 'SYSMIS');
+
+    # Format types.
+    init_type ('format', 'atom');
+    init_type ('ni_format', 'leaf', C_TYPE => 'const struct fmt_spec *',
+              ATOM => 'format', MANGLE => 'f',
+              HUMAN_NAME => 'num_input_format');
+    init_type ('no_format', 'leaf', C_TYPE => 'const struct fmt_spec *',
+              ATOM => 'format', MANGLE => 'f',
+              HUMAN_NAME => 'num_output_format');
+
+    # Integer types.
+    init_type ('integer', 'leaf', C_TYPE => 'int',
+              ATOM => 'integer', MANGLE => 'n', HUMAN_NAME => 'integer');
+    init_type ('pos_int', 'leaf', C_TYPE => 'int',
+              ATOM => 'integer', MANGLE => 'n',
+              HUMAN_NAME => 'positive_integer_constant');
+
+    # Variable names.
+    init_type ('variable', 'atom');
+    init_type ('num_var', 'leaf', C_TYPE => 'const struct variable *',
+              ATOM => 'variable', MANGLE => 'Vn',
+              HUMAN_NAME => 'num_variable');
+    init_type ('str_var', 'leaf', C_TYPE => 'const struct variable *',
+              ATOM => 'variable', MANGLE => 'Vs',
+              HUMAN_NAME => 'string_variable');
+
+    # Vectors.
+    init_type ('vector', 'leaf', C_TYPE => 'const struct vector *',
+              ATOM => 'vector', MANGLE => 'v', HUMAN_NAME => 'vector');
+
+    # Fixed types.
+    init_type ('expression', 'fixed', C_TYPE => 'struct expression *',
+              FIXED_VALUE => 'e');
+    init_type ('case', 'fixed', C_TYPE => 'const struct ccase *',
+              FIXED_VALUE => 'c');
+    init_type ('case_idx', 'fixed', C_TYPE => 'size_t',
+              FIXED_VALUE => 'case_idx');
+
+    # One of these is emitted at the end of each expression as a sentinel
+    # that tells expr_evaluate() to return the value on the stack.
+    init_type ('return_number', 'atom');
+    init_type ('return_string', 'atom');
+
+    # Used only for debugging purposes.
+    init_type ('operation', 'atom');
+}
+
+# init_type has 2 required arguments:
+#
+#   NAME: Type name.
+#
+#           `$name' is the type's name in operations.def.
+#
+#           `OP_$name' is the terminal's type in operations.h.
+#
+#           `expr_allocate_$name()' allocates a node of the given type.
+#
+#   ROLE: How the type may be used:
+#
+#           "any": Usable as operands and function arguments, and
+#           function and operator results.
+#
+#           "leaf": Usable as operands and function arguments, but
+#           not function arguments or results.  (Thus, they appear
+#           only in leaf nodes in the parse type.)
+#
+#           "fixed": Not allowed either as an operand or argument
+#           type or a result type.  Used only as auxiliary data.
+#
+#           "atom": Not allowed anywhere; just adds the name to
+#           the list of atoms.
+#
+# All types except those with "atom" as their role also require:
+#
+#   C_TYPE: The C type that represents this abstract type.
+#
+# Types with "any" or "leaf" role require:
+#
+#   ATOM:
+#
+#           `$atom' is the `struct operation_data' member name.
+#
+#           get_$atom_name() obtains the corresponding data from a
+#           node.
+#
+#   MANGLE: Short string for name mangling.  Use identical strings
+#   if two types should not be overloaded.
+#
+#   HUMAN_NAME: Name for a type when we describe it to the user.
+#
+# Types with role "any" require:
+#
+#   STACK: Name of the local variable in expr_evaluate(), used for
+#   maintaining the stack for this type.
+#
+#   MISSING_VALUE: Expression used for the missing value of this
+#   type.
+#
+# Types with role "fixed" require:
+#
+#   FIXED_VALUE: Expression used for the value of this type.
+sub init_type {
+    my ($name, $role, %rest) = @_;
+    my ($type) = $type{"\U$name"} = {NAME => $name, ROLE => $role, %rest};
+
+    my (@need_keys) = qw (NAME ROLE);
+    if ($role eq 'any') {
+       push (@need_keys, qw (C_TYPE ATOM MANGLE HUMAN_NAME STACK MISSING_VALUE));
+    } elsif ($role eq 'leaf') {
+       push (@need_keys, qw (C_TYPE ATOM MANGLE HUMAN_NAME));
+    } elsif ($role eq 'fixed') {
+       push (@need_keys, qw (C_TYPE FIXED_VALUE));
+    } elsif ($role eq 'atom') {
+    } else {
+       die "no role `$role'";
+    }
+
+    my (%have_keys);
+    $have_keys{$_} = 1 foreach keys %$type;
+    for my $key (@need_keys) {
+       defined $type->{$key} or die "$name lacks $key";
+       delete $have_keys{$key};
+    }
+    scalar (keys (%have_keys)) == 0
+      or die "$name has superfluous key(s) " . join (', ', keys (%have_keys));
+
+    push (@types, $type);
+}
+
+# c_type(type).
+#
+# Returns the C type of the given type as a string designed to be
+# prepended to a variable name to produce a declaration.  (That won't
+# work in general but it works well enough for our types.)
+sub c_type {
+    my ($type) = @_;
+    my ($c_type) = $type->{C_TYPE};
+    defined $c_type or die;
+
+    # Append a space unless (typically) $c_type ends in `*'.
+    $c_type .= ' ' if $c_type =~ /\w$/;
+
+    return $c_type;
+}
+\f
+# Input parsing.
+
+# Parses the entire input.
+#
+# Initializes %ops, @funcs, @opers.
+sub parse_input {
+    get_line ();
+    get_token ();
+    while ($toktype ne 'eof') {
+       my (%op);
+
+       $op{OPTIMIZABLE} = 1;
+       $op{UNIMPLEMENTED} = 0;
+       $op{EXTENSION} = 0;
+       for (;;) {
+           if (match ('extension')) {
+               $op{EXTENSION} = 1;
+           } elsif (match ('no_opt')) {
+               $op{OPTIMIZABLE} = 0;
+           } elsif (match ('absorb_miss')) {
+               $op{ABSORB_MISS} = 1;
+           } else {
+               last;
+           }
+       }
+
+       $op{RETURNS} = parse_type () || $type{NUMBER};
+       die "$op{RETURNS} is not a valid return type"
+         if !any ($op{RETURNS}, @type{qw (NUMBER STRING BOOLEAN)});
+
+       $op{CATEGORY} = $token;
+       if (!any ($op{CATEGORY}, qw (operator function))) {
+           die "`operator' or `function' expected at `$token'";
+       }
+       get_token ();
+
+       my ($name) = force ("id");
+
+       die "function name may not contain underscore"
+         if $op{CATEGORY} eq 'function' && $name =~ /_/;
+       die "operator name may not contain period"
+         if $op{CATEGORY} eq 'operator' && $name =~ /\./;
+
+       if (my ($prefix, $suffix) = $name =~ /^(.*)\.(\d+)$/) {
+           $name = $prefix;
+           $op{MIN_VALID} = $suffix;
+           $op{ABSORB_MISS} = 1;
+       }
+       $op{NAME} = $name;
+
+       force_match ('(');
+       @{$op{ARGS}} = ();
+       while (!match (')')) {
+           my ($arg) = parse_arg ();
+           push (@{$op{ARGS}}, $arg);
+           if (defined ($arg->{IDX})) {
+               last if match (')');
+               die "array must be last argument";
+           }
+           if (!match (',')) {
+               force_match (')');
+               last;
+           }
+       }
+
+       for my $arg (@{$op{ARGS}}) {
+           next if !defined $arg->{CONDITION};
+           my ($any_arg) = join ('|', map ($_->{NAME}, @{$op{ARGS}}));
+           $arg->{CONDITION} =~ s/\b($any_arg)\b/arg_$1/g;
+       }
+
+       my ($opname) = "OP_$op{NAME}";
+       $opname =~ tr/./_/;
+       if ($op{CATEGORY} eq 'function') {
+           my ($mangle) = join ('', map ($_->{TYPE}{MANGLE}, @{$op{ARGS}}));
+           $op{MANGLE} = $mangle;
+           $opname .= "_$mangle";
+       }
+       $op{OPNAME} = $opname;
+
+       if ($op{MIN_VALID}) {
+           my ($array_arg) = array_arg (\%op);
+           die "can't have minimum valid count without array arg"
+             if !defined $array_arg;
+           die "minimum valid count allowed only with double array"
+             if $array_arg->{TYPE} ne $type{NUMBER};
+           die "can't have minimum valid count if array has multiplication factor"
+             if $array_arg->{TIMES} != 1;
+       }
+
+       while ($toktype eq 'id') {
+           my ($type) = parse_type () or die "parse error";
+           die "`$type->{NAME}' is not allowed as auxiliary data"
+             unless $type->{ROLE} eq 'leaf' || $type->{ROLE} eq 'fixed';
+           my ($name) = force ("id");
+           push (@{$op{AUX}}, {TYPE => $type, NAME => $name});
+           force_match (';');
+       }
+
+       if ($op{OPTIMIZABLE}) {
+           die "random variate functions must be marked `no_opt'"
+             if $op{NAME} =~ /^RV\./;
+           for my $aux (@{$op{AUX}}) {
+               if (any ($aux->{TYPE}, @type{qw (CASE CASE_IDX)})) {
+                   die "operators with $aux->{TYPE} aux data must be "
+                     . "marked `no_opt'";
+               }
+           }
+       }
+
+       if ($op{RETURNS} eq $type{STRING} && !defined ($op{ABSORB_MISS})) {
+           my (@args);
+           for my $arg (@{$op{ARGS}}) {
+               if (any ($arg->{TYPE}, @type{qw (NUMBER BOOLEAN)})) {
+                   die "$op{NAME} returns string and has double or bool "
+                     . "argument, but is not marked ABSORB_MISS";
+               }
+               if (defined $arg->{CONDITION}) {
+                   die "$op{NAME} returns string but has argument with condition";
+               }
+           }
+       }
+
+       if ($toktype eq 'block') {
+           $op{BLOCK} = force ('block');
+       } elsif ($toktype eq 'expression') {
+           if ($token eq 'unimplemented') {
+               $op{UNIMPLEMENTED} = 1;
+           } else {
+               $op{EXPRESSION} = $token;
+           }
+           get_token ();
+       } else {
+           die "block or expression expected";
+       }
+
+       die "duplicate operation name $opname" if defined $ops{$opname};
+       $ops{$opname} = \%op;
+       if ($op{CATEGORY} eq 'function') {
+           push (@funcs, $opname);
+       } else {
+           push (@opers, $opname);
+       }
+    }
+    close(INPUT);
+
+    @funcs = sort {$ops{$a}->{NAME} cmp $ops{$b}->{NAME}
+                    ||
+                      $ops{$a}->{OPNAME} cmp $ops{$b}->{OPNAME}}
+      @funcs;
+    @opers = sort {$ops{$a}->{NAME} cmp $ops{$b}->{NAME}} @opers;
+    our (@order) = (@funcs, @opers);
+}
+
+# Reads the next token into $token, $toktype.
+sub get_token {
+    our ($line);
+    lookahead ();
+    return if defined ($toktype) && $toktype eq 'eof';
+    $toktype = 'id', $token = $1, return
+       if $line =~ /\G([a-zA-Z_][a-zA-Z_.0-9]*)/gc;
+    $toktype = 'int', $token = $1, return if $line =~ /\G([0-9]+)/gc;
+    $toktype = 'punct', $token = $1, return if $line =~ /\G([][(),*;.])/gc;
+    if ($line =~ /\G=/gc) {
+       $toktype = "expression";
+       $line =~ /\G\s+/gc;
+       $token = accumulate_balanced (';');
+    } elsif ($line =~ /\G\{/gc) {
+       $toktype = "block";
+       $token = accumulate_balanced ('}');
+       $token =~ s/^\n+//;
+    } else {
+       die "bad character `" . substr ($line, pos $line, 1) . "' in input";
+    }
+}
+
+# Skip whitespace, then return the remainder of the line.
+sub lookahead {
+    our ($line);
+    die "unexpected end of file" if !defined ($line);
+    for (;;) {
+       $line =~ /\G\s+/gc;
+       last if pos ($line) < length ($line);
+       get_line ();
+       $token = $toktype = 'eof', return if !defined ($line);
+    }
+    return substr ($line, pos ($line));
+}
+
+# accumulate_balanced($chars)
+#
+# Accumulates input until a character in $chars is encountered, except
+# that balanced pairs of (), [], or {} cause $chars to be ignored.
+#
+# Returns the input read.
+sub accumulate_balanced {
+    my ($end) = @_;
+    my ($s) = "";
+    my ($nest) = 0;
+    our ($line);
+    for (;;) {
+       my ($start) = pos ($line);
+       if ($line =~ /\G([^][(){};,]*)([][(){};,])/gc) {
+           $s .= substr ($line, $start, pos ($line) - $start - 1)
+               if pos ($line) > $start;
+           my ($last) = substr ($line, pos ($line) - 1, 1);
+           if ($last =~ /[[({]/) {
+               $nest++;
+               $s .= $last;
+           } elsif ($last =~ /[])}]/) {
+               if ($nest > 0) {
+                   $nest--;
+                   $s .= $last;
+               } elsif (index ($end, $last) >= 0) {
+                   return $s;
+               } else {
+                   die "unbalanced parentheses";
+               }
+           } elsif (index ($end, $last) >= 0) {
+               return $s if !$nest;
+               $s .= $last;
+           } else {
+               $s .= $last;
+           }
+       } else {
+           $s .= substr ($line, pos ($line)) . "\n";
+           get_line ();
+       }
+    }
+}
+
+# Reads the next line from INPUT into $line.
+sub get_line {
+    our ($line);
+    $line = <INPUT>;
+    if (defined ($line)) {
+       chomp $line;
+       $line =~ s%//.*%%;
+       pos ($line) = 0;
+    }
+}
+
+# If the current token is an identifier that names a type,
+# returns the type and skips to the next token.
+# Otherwise, returns undef.
+sub parse_type {
+    if ($toktype eq 'id') {
+       foreach my $type (values (%type)) {
+           get_token (), return $type
+             if defined ($type->{NAME}) && $type->{NAME} eq $token;
+       }
+    }
+    return;
+}
+
+# force($type).
+#
+# Makes sure that $toktype equals $type, reads the next token, and
+# returns the previous $token.
+sub force {
+    my ($type) = @_;
+    die "parse error at `$token' expecting $type"
+       if $type ne $toktype;
+    my ($tok) = $token;
+    get_token ();
+    return $tok;
+}
+
+# force($tok).
+#
+# If $token equals $tok, reads the next token and returns true.
+# Otherwise, returns false.
+sub match {
+    my ($tok) = @_;
+    if ($token eq $tok) {
+       get_token ();
+       return 1;
+    } else {
+       return 0;
+    }
+}
+
+# force_match($tok).
+#
+# If $token equals $tok, reads the next token.
+# Otherwise, flags an error in the input.
+sub force_match {
+    my ($tok) = @_;
+    die "parse error at `$token' expecting `$tok'" if !match ($tok);
+}
+
+# Parses and returns a function argument.
+sub parse_arg {
+    my (%arg);
+    $arg{TYPE} = parse_type () || $type{NUMBER};
+    die "argument name expected at `$token'" if $toktype ne 'id';
+    $arg{NAME} = $token;
+
+    if (lookahead () =~ /^[[,)]/) {
+       get_token ();
+       if (match ('[')) {
+           die "only double and string arrays supported"
+             if !any ($arg{TYPE}, @type{qw (NUMBER STRING)});
+           $arg{IDX} = force ('id');
+           if (match ('*')) {
+               $arg{TIMES} = force ('int');
+               die "multiplication factor must be positive"
+                 if $arg{TIMES} < 1;
+           } else {
+               $arg{TIMES} = 1;
+           }
+           force_match (']');
+       }
+    } else {
+       $arg{CONDITION} = $arg{NAME} . ' ' . accumulate_balanced (',)');
+       our ($line);
+       pos ($line) -= 1;
+       get_token ();
+    }
+    return \%arg;
+}
+\f
+# Output.
+
+# Prints the output file header.
+sub print_header {
+    print <<EOF;
+/* $output_file
+   Generated from $input_file by generate.pl.  
+   Do not modify! */
+
+EOF
+}
+
+# Prints the output file trailer.
+sub print_trailer {
+    print <<EOF;
+
+/*
+   Local Variables:
+   mode: c
+   buffer-read-only: t
+   End:
+*/
+EOF
+}
+\f
+# Utilities.
+
+# any($target, @list)
+#
+# Returns true if $target appears in @list,
+# false otherwise.
+sub any {
+    $_ eq $_[0] and return 1 foreach @_[1...$#_];
+    return 0;
+}
+
+# make_sysmis_decl($op, $min_valid_src)
+#
+# Returns a declaration for a boolean variable called `force_sysmis',
+# which will be true when operation $op should be system-missing.
+# Returns undef if there are no such circumstances.
+#
+# If $op has a minimum number of valid arguments, $min_valid_src
+# should be an an expression that evaluates to the minimum number of
+# valid arguments for $op.
+sub make_sysmis_decl {
+    my ($op, $min_valid_src) = @_;
+    my (@sysmis_cond); 
+    if (!$op->{ABSORB_MISS}) {
+       for my $arg (@{$op->{ARGS}}) {
+           my ($arg_name) = "arg_$arg->{NAME}";
+           if (!defined $arg->{IDX}) {
+               if (any ($arg->{TYPE}, @type{qw (NUMBER BOOLEAN)})) {
+                   push (@sysmis_cond, "!is_valid ($arg_name)");
+               }
+           } elsif ($arg->{TYPE} eq $type{NUMBER}) {
+               my ($a) = "$arg_name";
+               my ($n) = "arg_$arg->{IDX}";
+               push (@sysmis_cond, "count_valid ($a, $n) < $n");
+           }
+       }
+    } elsif (defined $op->{MIN_VALID}) {
+       my ($args) = $op->{ARGS};
+       my ($arg) = ${$args}[$#{$args}];
+       my ($a) = "arg_$arg->{NAME}";
+       my ($n) = "arg_$arg->{IDX}";
+       push (@sysmis_cond, "count_valid ($a, $n) < $min_valid_src");
+    }
+    for my $arg (@{$op->{ARGS}}) {
+       push (@sysmis_cond, "!($arg->{CONDITION})")
+         if defined $arg->{CONDITION};
+    }
+    return "bool force_sysmis = " . join (' || ', @sysmis_cond)
+      if @sysmis_cond;
+    return;
+}
+
+# array_arg($op)
+#
+# If $op has an array argument, return it.
+# Otherwise, returns undef.
+sub array_arg {
+    my ($op) = @_;
+    my ($args) = $op->{ARGS};
+    return if !@$args;
+    my ($last_arg) = $args->[@$args - 1];
+    return $last_arg if defined $last_arg->{IDX};
+    return;
+}
diff --git a/src/language/expressions/helpers.c b/src/language/expressions/helpers.c
new file mode 100644 (file)
index 0000000..b4534c8
--- /dev/null
@@ -0,0 +1,374 @@
+#include <config.h>
+#include "helpers.h"
+#include <gsl/gsl_roots.h>
+#include <gsl/gsl_sf.h>
+#include "pool.h"
+#include "private.h"
+
+const struct fixed_string empty_string = {NULL, 0};
+
+static void
+expr_error (void *aux UNUSED, const char *format, ...) 
+{
+  struct error e;
+  va_list args;
+
+  /* FIXME: we can do better about saying where the error
+     occurred. */
+  e.class = SE;
+  err_location (&e.where);
+  e.title = NULL;
+
+  va_start (args, format);
+  err_vmsg (&e, format, args);
+  va_end (args);
+}
+
+double
+expr_ymd_to_ofs (double year, double month, double day)
+{
+  int y = year;
+  int m = month;
+  int d = day;
+
+  if (y != year || m != month || d != day) 
+    { 
+      msg (SE, _("One of the arguments to a DATE function is not an integer.  "
+                 "The result will be system-missing."));
+      return SYSMIS;
+    }
+
+  return calendar_gregorian_to_offset (y, m, d, expr_error, NULL);
+}
+
+double
+expr_ymd_to_date (double year, double month, double day)
+{
+  double ofs = expr_ymd_to_ofs (year, month, day);
+  return ofs != SYSMIS ? ofs * DAY_S : SYSMIS;
+}
+
+double
+expr_wkyr_to_date (double week, double year) 
+{
+  int w = week;
+
+  if (w != week) 
+    {
+      msg (SE, _("The week argument to DATE.WKYR is not an integer.  "
+                 "The result will be system-missing."));
+      return SYSMIS;
+    }
+  else if (w < 1 || w > 53) 
+    {
+      msg (SE, _("The week argument to DATE.WKYR is outside the acceptable "
+                 "range of 1 to 53.  "
+                 "The result will be system-missing."));
+      return SYSMIS;
+    }
+  else 
+    {
+      double yr_1_1 = expr_ymd_to_ofs (year, 1, 1);
+      if (yr_1_1 != SYSMIS)
+        return DAY_S * (yr_1_1 + WEEK_DAY * (w - 1));
+      else
+        return SYSMIS;
+    }
+}
+
+double
+expr_yrday_to_date (double year, double yday) 
+{
+  int yd = yday;
+
+  if (yd != yday) 
+    {
+      msg (SE, _("The day argument to DATE.YRDAY is not an integer.  "
+                 "The result will be system-missing."));
+      return SYSMIS;
+    }
+  else if (yd < 1 || yd > 366) 
+    {
+      msg (SE, _("The day argument to DATE.YRDAY is outside the acceptable "
+                 "range of 1 to 366.  "
+                 "The result will be system-missing."));
+      return SYSMIS;
+    }
+  else 
+    {
+      double yr_1_1 = expr_ymd_to_ofs (year, 1, 1);
+      if (yr_1_1 != SYSMIS)
+        return DAY_S * (yr_1_1 + yd - 1.);
+      else
+        return SYSMIS;
+    }
+}
+
+double
+expr_yrmoda (double year, double month, double day)
+{ 
+  if (year >= 0 && year <= 99)
+    year += 1900;
+  else if (year != (int) year && year > 47516) 
+    {
+      msg (SE, _("The year argument to YRMODA is greater than 47516.  "
+                 "The result will be system-missing."));
+      return SYSMIS;
+    }
+
+  return expr_ymd_to_ofs (year, month, day);
+}
+
+int
+compare_string (const struct fixed_string *a, const struct fixed_string *b) 
+{
+  size_t i;
+
+  for (i = 0; i < a->length && i < b->length; i++)
+    if (a->string[i] != b->string[i]) 
+      return a->string[i] < b->string[i] ? -1 : 1;
+  for (; i < a->length; i++)
+    if (a->string[i] != ' ')
+      return 1;
+  for (; i < b->length; i++)
+    if (b->string[i] != ' ')
+      return -1;
+  return 0;
+}
+
+size_t
+count_valid (double *d, size_t d_cnt) 
+{
+  size_t valid_cnt;
+  size_t i;
+
+  valid_cnt = 0;
+  for (i = 0; i < d_cnt; i++)
+    valid_cnt += is_valid (d[i]);
+  return valid_cnt;
+}
+
+struct fixed_string
+alloc_string (struct expression *e, size_t length) 
+{
+  struct fixed_string s;
+  s.length = length;
+  s.string = pool_alloc (e->eval_pool, length);
+  return s;
+}
+
+struct fixed_string
+copy_string (struct expression *e, const char *old, size_t length) 
+{
+  struct fixed_string s = alloc_string (e, length);
+  memcpy (s.string, old, length);
+  return s;
+}
+
+/* Returns the noncentral beta cumulative distribution function
+   value for the given arguments.
+
+   FIXME: The accuracy of this function is not entirely
+   satisfactory.  We only match the example values given in AS
+   310 to the first 5 significant digits. */
+double
+ncdf_beta (double x, double a, double b, double lambda) 
+{
+  double c;
+  
+  if (x <= 0. || x >= 1. || a <= 0. || b <= 0. || lambda <= 0.)
+    return SYSMIS;
+
+  c = lambda / 2.;
+  if (lambda < 54.)
+    {
+      /* Algorithm AS 226. */
+      double x0, a0, beta, temp, gx, q, ax, sumq, sum;
+      double err_max = 2 * DBL_EPSILON;
+      double err_bound;
+      int iter_max = 100;
+      int iter;
+
+      x0 = floor (c - 5.0 * sqrt (c));
+      if (x0 < 0.)
+        x0 = 0.;
+      a0 = a + x0;
+      beta = (gsl_sf_lngamma (a0)
+              + gsl_sf_lngamma (b)
+              - gsl_sf_lngamma (a0 + b));
+      temp = gsl_sf_beta_inc (a0, b, x);
+      gx = exp (a0 * log (x) + b * log (1. - x) - beta - log (a0));
+      if (a0 >= a)
+        q = exp (-c + x0 * log (c)) - gsl_sf_lngamma (x0 + 1.);
+      else
+        q = exp (-c);
+      ax = q * temp;
+      sumq = 1. - q;
+      sum = ax;
+
+      iter = 0;
+      do 
+        {
+          iter++;
+          temp -= gx;
+          gx = x * (a + b + iter - 1.) * gx / (a + iter);
+          q *= c / iter;
+          sumq -= q;
+          ax = temp * q;
+          sum += ax;
+
+          err_bound = (temp - gx) * sumq;
+        }
+      while (iter < iter_max && err_bound > err_max);
+      
+      return sum;
+    }
+  else 
+    {
+      /* Algorithm AS 310. */
+      double m, m_sqrt;
+      int iter, iter_lower, iter_upper, iter1, iter2, j;
+      double t, q, r, psum, beta, s1, gx, fx, temp, ftemp, t0, s0, sum, s;
+      double err_bound;
+      double err_max = 2 * DBL_EPSILON;
+      
+      iter = 0;
+      
+      m = floor (c + .5);
+      m_sqrt = sqrt (m);
+      iter_lower = m - 5. * m_sqrt;
+      iter_upper = m + 5. * m_sqrt;
+      
+      t = -c + m * log (c) - gsl_sf_lngamma (m + 1.);
+      q = exp (t);
+      r = q;
+      psum = q;
+      beta = (gsl_sf_lngamma (a + m)
+              + gsl_sf_lngamma (b)
+              - gsl_sf_lngamma (a + m + b));
+      s1 = (a + m) * log (x) + b * log (1. - x) - log (a + m) - beta;
+      fx = gx = exp (s1);
+      ftemp = temp = gsl_sf_beta_inc (a + m, b, x);
+      iter++;
+      sum = q * temp;
+      iter1 = m;
+
+      while (iter1 >= iter_lower && q >= err_max) 
+        {
+          q = q * iter1 / c;
+          iter++;
+          gx = (a + iter1) / (x * (a + b + iter1 - 1.)) * gx;
+          iter1--;
+          temp += gx;
+          psum += q;
+          sum += q * temp;
+        }
+
+      t0 = (gsl_sf_lngamma (a + b)
+            - gsl_sf_lngamma (a + 1.)
+            - gsl_sf_lngamma (b));
+      s0 = a * log (x) + b * log (1. - x);
+
+      s = 0.;
+      for (j = 0; j < iter1; j++) 
+        {
+          double t1;
+          s += exp (t0 + s0 + j * log (x));
+          t1 = log (a + b + j) - log (a + 1. + j) + t0;
+          t0 = t1;
+        }
+
+      err_bound = (1. - gsl_sf_gamma_inc_P (iter1, c)) * (temp + s);
+      q = r;
+      temp = ftemp;
+      gx = fx;
+      iter2 = m;
+      for (;;) 
+        {
+          double ebd = err_bound + (1. - psum) * temp;
+          if (ebd < err_max || iter >= iter_upper)
+            break;
+
+          iter2++;
+          iter++;
+          q = q * c / iter2;
+          psum += q;
+          temp -= gx;
+          gx = x * (a + b + iter2 - 1.) / (a + iter2) * gx;
+          sum += q * temp;
+        }
+
+      return sum;
+    }
+}
+
+double
+cdf_bvnor (double x0, double x1, double r) 
+{
+  double z = x0 * x0 - 2. * r * x0 * x1 + x1 * x1;
+  return exp (-z / (2. * (1 - r * r))) * (2. * M_PI * sqrt (1 - r * r));
+}
+
+double
+idf_fdist (double P, double df1, double df2) 
+{
+  double temp = gslextras_cdf_beta_Pinv (P, df1 / 2, df2 / 2);
+  return temp * df2 / ((1. - temp) * df1);
+}
+
+/*
+ *  Mathlib : A C Library of Special Functions
+ *  Copyright (C) 1998 Ross Ihaka
+ *  Copyright (C) 2000 The R Development Core Team
+ *
+ *  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., 51 Franklin Street, Fifth Floor, Boston, MA
+ *  02110-1301 USA.
+ */
+
+/* Returns the density of the noncentral beta distribution with
+   noncentrality parameter LAMBDA. */
+double
+npdf_beta (double x, double a, double b, double lambda) 
+{
+  if (lambda < 0. || a <= 0. || b <= 0.)
+    return SYSMIS;
+  else if (lambda == 0.)
+    return gsl_ran_beta_pdf (x, a, b);
+  else 
+    {
+      double max_error = 2 * DBL_EPSILON;
+      int max_iter = 200;
+      double term = gsl_ran_beta_pdf (x, a, b);
+      double lambda2 = 0.5 * lambda;
+      double weight = exp (-lambda2);
+      double sum = weight * term;
+      double psum = weight;
+      int k;
+      for (k = 1; k <= max_iter && 1 - psum < max_error; k++) { 
+        weight *= lambda2 / k;
+        term *= x * (a + b) / a;
+        sum += weight * term;
+        psum += weight;
+        a += 1;
+      } 
+      return sum;
+    }
+}
diff --git a/src/language/expressions/helpers.h b/src/language/expressions/helpers.h
new file mode 100644 (file)
index 0000000..e3813fd
--- /dev/null
@@ -0,0 +1,74 @@
+#ifndef EXPRESSIONS_HELPERS_H 
+#define EXPRESSIONS_HELPERS_H
+
+#include <ctype.h>
+#include <float.h>
+#include <gsl/gsl_cdf.h>
+#include <gsl/gsl_randist.h>
+#include <gsl/gsl_sf.h>
+#include <limits.h>
+#include <math.h>
+#include <stdbool.h>
+#include "case.h"
+#include "data-in.h"
+#include "dictionary.h"
+#include "message.h"
+#include "calendar.h"
+#include "gsl-extras/gsl-extras.h"
+#include "misc.h"
+#include "moments.h"
+#include "random.h"
+#include "settings.h"
+#include "str.h"
+#include "value.h"
+#include "variable.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+static inline double check_errno (double x) 
+{
+  return errno == 0 ? x : SYSMIS;
+}
+
+#define check_errno(EXPRESSION) (errno = 0, check_errno (EXPRESSION))
+
+#define DAY_S (60. * 60. * 24.)         /* Seconds per day. */
+#define DAY_H 24.                       /* Hours per day. */
+#define H_S (60 * 60.)                  /* Seconds per hour. */
+#define H_MIN 60.                       /* Minutes per hour. */
+#define MIN_S 60.                       /* Seconds per minute. */
+#define WEEK_DAY 7.                     /* Days per week. */
+
+extern const struct fixed_string empty_string;
+
+int compare_string (const struct fixed_string *, const struct fixed_string *);
+
+double expr_ymd_to_date (double year, double month, double day);
+double expr_ymd_to_ofs (double year, double month, double day);
+double expr_wkyr_to_date (double wk, double yr);
+double expr_yrday_to_date (double yr, double day);
+double expr_yrmoda (double year, double month, double day);
+
+struct fixed_string alloc_string (struct expression *, size_t length);
+struct fixed_string copy_string (struct expression *,
+                                 const char *, size_t length);
+
+static inline bool
+is_valid (double d) 
+{
+  return finite (d) && d != SYSMIS;
+}
+
+size_t count_valid (double *, size_t);
+
+double idf_beta (double P, double a, double b);
+double ncdf_beta (double x, double a, double b, double lambda);
+double npdf_beta (double x, double a, double b, double lambda);
+
+double cdf_bvnor (double x0, double x1, double r);
+
+double idf_fdist (double P, double a, double b);
+
+#endif /* expressions/helpers.h */
diff --git a/src/language/expressions/operations.def b/src/language/expressions/operations.def
new file mode 100644 (file)
index 0000000..bd5af76
--- /dev/null
@@ -0,0 +1,1021 @@
+// -*- c -*-
+
+operator NEG (x) = -x;
+
+operator ADD (a, b) = a + b;
+operator SUB (a, b) = a - b;
+
+absorb_miss operator MUL (a, b)
+= (a == 0. || b == 0. ? 0.
+   : a == SYSMIS || b == SYSMIS ? SYSMIS
+   : a * b);
+
+absorb_miss operator DIV (a, b)
+= (a == 0. ? 0.
+   : a == SYSMIS || b == SYSMIS ? SYSMIS
+   : a / b);
+
+absorb_miss operator POW (a, b)
+= (a == SYSMIS ? (b == 0. ? 1. : a)
+   : b == SYSMIS ? (a == 0. ? 0. : SYSMIS)
+   : a == 0. && b <= 0. ? SYSMIS
+   : pow (a, b));
+
+absorb_miss boolean operator AND (boolean a, boolean b)
+= (a == 0. ? 0.
+   : b == 0. ? 0.
+   : b == SYSMIS ? SYSMIS
+   : a);
+
+absorb_miss boolean operator OR (boolean a, boolean b)
+= (a == 1. ? 1.
+   : b == 1. ? 1.
+   : b == SYSMIS ? SYSMIS
+   : a);
+
+boolean operator NOT (boolean a)
+= (a == 0. ? 1.
+   : a == 1. ? 0.
+   : SYSMIS);
+
+// Numeric relational operators.
+boolean operator EQ (a, b) = a == b;
+boolean operator GE (a, b) = a >= b;
+boolean operator GT (a, b) = a > b;
+boolean operator LE (a, b) = a <= b;
+boolean operator LT (a, b) = a < b;
+boolean operator NE (a, b) = a != b;
+
+// String relational operators.
+boolean operator EQ_STRING (string a, string b) = compare_string (&a, &b) == 0;
+boolean operator GE_STRING (string a, string b) = compare_string (&a, &b) >= 0;
+boolean operator GT_STRING (string a, string b) = compare_string (&a, &b) > 0;
+boolean operator LE_STRING (string a, string b) = compare_string (&a, &b) <= 0;
+boolean operator LT_STRING (string a, string b) = compare_string (&a, &b) < 0;
+boolean operator NE_STRING (string a, string b) = compare_string (&a, &b) != 0;
+
+// Unary functions.
+function ABS (x) = fabs (x);
+extension function ACOS (x >= -1 && x <= 1) = acos (x);
+function ASIN (x >= -1 && x <= 1) = asin (x);
+function ATAN (x) = atan (x);
+extension function ARCOS (x >= -1 && x <= 1) = acos (x);
+function ARSIN (x >= -1 && x <= 1) = asin (x);
+function ARTAN (x) = atan (x);
+function COS (x) = cos (x);
+function EXP (x) = check_errno (exp (x));
+function LG10(x) = check_errno (log10 (x));
+function LN (x) = check_errno (log (x));
+function LNGAMMA (x >= 0) = gsl_sf_lngamma (x);
+function MOD10 (x) = fmod (x, 10);
+function RND (x) = x >= 0. ? floor (x + .5) : -floor (-x + .5);
+function SIN (x) = sin (x);
+function SQRT (x >= 0) = sqrt (x);
+function TAN (x) = check_errno (tan (x));
+function TRUNC (x) = x >= 0. ? floor (x) : -floor (-x);
+
+absorb_miss function MOD (n, d)
+{
+  if (d != SYSMIS)
+    return n != SYSMIS ? fmod (n, d) : SYSMIS;
+  else
+    return n != 0. ? SYSMIS : 0.;
+}
+
+// N-ary numeric functions.
+absorb_miss boolean function ANY (x != SYSMIS, a[n])
+{
+  int sysmis = 0;
+  size_t i;
+
+  for (i = 0; i < n; i++)
+    if (a[i] == x)
+      return 1.;
+    else if (a[i] == SYSMIS)
+      sysmis = 1;
+
+  return sysmis ? SYSMIS : 0.;
+}
+
+boolean function ANY (string x, string a[n])
+{
+  size_t i;
+
+  for (i = 0; i < n; i++)
+    if (!compare_string (&x, &a[i]))
+      return 1.;
+  return 0.;
+}
+
+function CFVAR.2 (a[n])
+{
+  double mean, variance;
+
+  moments_of_doubles (a, n, NULL, &mean, &variance, NULL, NULL);
+
+  if (mean == SYSMIS || mean == 0 || variance == SYSMIS)
+    return SYSMIS;
+  else
+    return sqrt (variance) / mean;
+}
+
+function MAX.1 (a[n])
+{
+  double max;
+  size_t i;
+
+  max = -DBL_MAX;
+  for (i = 0; i < n; i++)
+    if (a[i] != SYSMIS && a[i] > max)
+      max = a[i];
+  return max;
+}
+
+string function MAX (string a[n])
+{
+  struct fixed_string *max;
+  size_t i;
+
+  max = &a[0];
+  for (i = 1; i < n; i++)
+    if (compare_string (&a[i], max) > 0)
+      max = &a[i];
+  return *max;
+}
+
+function MEAN.1 (a[n])
+{
+  double mean;
+  moments_of_doubles (a, n, NULL, &mean, NULL, NULL, NULL);
+  return mean;
+}
+
+function MIN.1 (a[n])
+{
+  double min;
+  size_t i;
+
+  min = DBL_MAX;
+  for (i = 0; i < n; i++)
+    if (a[i] != SYSMIS && a[i] < min)
+      min = a[i];
+  return min;
+}
+
+string function MIN (string a[n])
+{
+  struct fixed_string *min;
+  size_t i;
+
+  min = &a[0];
+  for (i = 1; i < n; i++)
+    if (compare_string (&a[i], min) < 0)
+      min = &a[i];
+  return *min;
+}
+
+absorb_miss function NMISS (a[n])
+{
+  size_t i;
+  size_t missing_cnt = 0;
+
+  for (i = 0; i < n; i++)
+    missing_cnt += a[i] == SYSMIS;
+  return missing_cnt;
+}
+
+absorb_miss function NVALID (a[n])
+{
+  size_t i;
+  size_t valid_cnt = 0;
+
+  for (i = 0; i < n; i++)
+    valid_cnt += a[i] != SYSMIS;
+  return valid_cnt;
+}
+
+absorb_miss boolean function RANGE (x != SYSMIS, a[n*2])
+{
+  size_t i;
+  int sysmis = 0;
+
+  for (i = 0; i < n; i++)
+    {
+      double w = a[2 * i];
+      double y = a[2 * i + 1];
+      if (w != SYSMIS && y != SYSMIS)
+        {
+          if (w <= x && x <= y)
+            return 1.0;
+        }
+      else
+        sysmis = 1;
+    }
+  return sysmis ? SYSMIS : 0.;
+}
+
+boolean function RANGE (string x, string a[n*2])
+{
+  int i;
+
+  for (i = 0; i < n; i++)
+    {
+      struct fixed_string *w = &a[2 * i];
+      struct fixed_string *y = &a[2 * i + 1];
+      if (compare_string (w, &x) <= 0 && compare_string (&x, y) <= 0)
+        return 1.;
+    }
+  return 0.;
+}
+
+function SD.2 (a[n])
+{
+  double variance;
+  moments_of_doubles (a, n, NULL, NULL, &variance, NULL, NULL);
+  return sqrt (variance);
+}
+
+function SUM.1 (a[n])
+{
+  double sum;
+  size_t i;
+
+  sum = 0.;
+  for (i = 0; i < n; i++)
+    if (a[i] != SYSMIS)
+      sum += a[i];
+  return sum;
+}
+
+function VARIANCE.2 (a[n])
+{
+  double variance;
+  moments_of_doubles (a, n, NULL, NULL, &variance, NULL, NULL);
+  return variance;
+}
+
+// Time construction & extraction functions.
+function TIME.HMS (h, m, s)
+{
+  if ((h > 0. || m > 0. || s > 0.) && (h < 0. || m < 0. || s < 0.))
+    {
+      msg (SW, _("TIME.HMS cannot mix positive and negative arguments."));
+      return SYSMIS;
+    }
+  else
+    return H_S * h + MIN_S * m + s;
+}
+function TIME.DAYS (days) = days * DAY_S;
+function CTIME.DAYS (time) = time / DAY_S;
+function CTIME.HOURS (time) = time / H_S;
+function CTIME.MINUTES (time) = time / MIN_S;
+function CTIME.SECONDS (time) = time;
+
+// Date construction functions.
+function DATE.DMY (d, m, y) = expr_ymd_to_date (y, m, d);
+function DATE.MDY (m, d, y) = expr_ymd_to_date (y, m, d);
+function DATE.MOYR (m, y) = expr_ymd_to_date (y, m, 1);
+function DATE.QYR (q, y) = expr_ymd_to_date (y, q * 3 - 2, 1);
+function DATE.WKYR (w, y) = expr_wkyr_to_date (w, y);
+function DATE.YRDAY (y, yday) = expr_yrday_to_date (y, yday);
+function YRMODA (y, m, d) = expr_yrmoda (y, m, d);
+
+// Date extraction functions.
+function XDATE.TDAY (date) = floor (date / DAY_S);
+function XDATE.HOUR (date) = fmod (floor (date / H_S), DAY_H);
+function XDATE.MINUTE (date) = fmod (floor (date / H_MIN), H_MIN);
+function XDATE.SECOND (date) = fmod (date, MIN_S);
+function XDATE.DATE (date) = floor (date / DAY_S) * DAY_S;
+function XDATE.TIME (date) = fmod (date, DAY_S);
+
+function XDATE.JDAY (date >= DAY_S) = calendar_offset_to_yday (date / DAY_S);
+function XDATE.MDAY (date >= DAY_S) = calendar_offset_to_mday (date / DAY_S);
+function XDATE.MONTH (date >= DAY_S)
+     = calendar_offset_to_month (date / DAY_S);
+function XDATE.QUARTER (date >= DAY_S)
+    = (calendar_offset_to_month (date / DAY_S) - 1) / 3 + 1;
+function XDATE.WEEK (date >= DAY_S)
+    = (calendar_offset_to_yday (date / DAY_S) - 1) / 7 + 1;
+function XDATE.WKDAY (date >= DAY_S) = calendar_offset_to_wday (date / DAY_S);
+function XDATE.YEAR (date >= DAY_S) = calendar_offset_to_year (date / DAY_S);
+
+// String functions.
+string function CONCAT (string a[n])
+     expression e;
+{
+  struct fixed_string dst;
+  size_t i;
+
+  dst = alloc_string (e, 255);
+  dst.length = 0;
+  for (i = 0; i < n; i++)
+    {
+      struct fixed_string *src = &a[i];
+      size_t copy_len;
+
+      copy_len = src->length;
+      if (dst.length + copy_len > 255)
+        copy_len = 255 - dst.length;
+      memcpy (&dst.string[dst.length], src->string, copy_len);
+      dst.length += copy_len;
+    }
+
+  return dst;
+}
+
+function INDEX (string haystack, string needle)
+{
+  if (needle.length == 0)
+    return SYSMIS;
+  else
+    {
+      int limit = haystack.length - needle.length + 1;
+      int i;
+      for (i = 1; i <= limit; i++)
+        if (!memcmp (&haystack.string[i - 1], needle.string, needle.length))
+          return i;
+      return 0;
+    }
+}
+
+function INDEX (string haystack, string needles, needle_len_d)
+{
+  if (needle_len_d <= INT_MIN || needle_len_d >= INT_MAX
+      || (int) needle_len_d != needle_len_d
+      || needles.length == 0)
+    return SYSMIS;
+  else
+    {
+      int needle_len = needle_len_d;
+      if (needle_len < 0 || needle_len > needles.length
+          || needles.length % needle_len != 0)
+        return SYSMIS;
+      else
+        {
+          int limit = haystack.length - needle_len + 1;
+          int i, j;
+          for (i = 1; i <= limit; i++)
+            for (j = 0; j < needles.length; j += needle_len)
+              if (!memcmp (&haystack.string[i - 1], &needles.string[j],
+                           needle_len))
+                return i;
+          return 0;
+        }
+    }
+}
+
+
+function RINDEX (string haystack, string needle)
+{
+  if (needle.length == 0)
+    return SYSMIS;
+  else
+    {
+      int limit = haystack.length - needle.length + 1;
+      int i;
+      for (i = limit; i >= 1; i--)
+        if (!memcmp (&haystack.string[i - 1], needle.string, needle.length))
+          return i;
+      return 0;
+    }
+}
+
+function RINDEX (string haystack, string needles, needle_len_d)
+{
+  if (needle_len_d <= INT_MIN || needle_len_d >= INT_MAX
+      || (int) needle_len_d != needle_len_d
+      || needles.length == 0)
+    return SYSMIS;
+  else
+    {
+      int needle_len = needle_len_d;
+      if (needle_len < 0 || needle_len > needles.length
+          || needles.length % needle_len != 0)
+        return SYSMIS;
+      else
+        {
+          int limit = haystack.length - needle_len + 1;
+          int i, j;
+          for (i = limit; i >= 1; i--)
+            for (j = 0; j < needles.length; j += needle_len)
+              if (!memcmp (&haystack.string[i - 1],
+                           &needles.string[j], needle_len))
+                return i;
+          return 0;
+        }
+    }
+}
+
+function LENGTH (string s)
+{
+  return s.length;
+}
+
+string function LOWER (string s)
+{
+  int i;
+
+  for (i = 0; i < s.length; i++)
+    s.string[i] = tolower ((unsigned char) s.string[i]);
+  return s;
+}
+
+function MBLEN.BYTE (string s, idx)
+{
+  if (idx < 0 || idx >= s.length || (int) idx != idx)
+    return SYSMIS;
+  else
+    return 1;
+}
+
+string function UPCASE (string s)
+{
+  int i;
+
+  for (i = 0; i < s.length; i++)
+    s.string[i] = toupper ((unsigned char) s.string[i]);
+  return s;
+}
+
+absorb_miss string function LPAD (string s, n)
+     expression e;
+{
+  if (n < 0 || n > 255 || (int) n != n)
+    return empty_string;
+  else if (s.length >= n)
+    return s;
+  else
+    {
+      struct fixed_string t = alloc_string (e, n);
+      memset (t.string, ' ', n - s.length);
+      memcpy (&t.string[(int) n - s.length], s.string, s.length);
+      return t;
+    }
+}
+
+absorb_miss string function LPAD (string s, n, string c)
+     expression e;
+{
+  if (n < 0 || n > 255 || (int) n != n || c.length != 1)
+    return empty_string;
+  else if (s.length >= n)
+    return s;
+  else
+    {
+      struct fixed_string t = alloc_string (e, n);
+      memset (t.string, c.string[0], n - s.length);
+      memcpy (&t.string[(int) n - s.length], s.string, s.length);
+      return t;
+    }
+}
+
+absorb_miss string function RPAD (string s, n)
+     expression e;
+{
+  if (n < 0 || n > 255 || (int) n != n)
+    return empty_string;
+  else if (s.length >= n)
+    return s;
+  else
+    {
+      struct fixed_string t = alloc_string (e, n);
+      memcpy (t.string, s.string, s.length);
+      memset (&t.string[s.length], ' ', n - s.length);
+      return t;
+    }
+}
+
+absorb_miss string function RPAD (string s, n, string c)
+     expression e;
+{
+  if (n < 0 || n > 255 || (int) n != n || c.length != 1)
+    return empty_string;
+  else if (s.length >= n)
+    return s;
+  else
+    {
+      struct fixed_string t = alloc_string (e, n);
+      memcpy (t.string, s.string, s.length);
+      memset (&t.string[s.length], c.string[0], n - s.length);
+      return t;
+    }
+}
+
+string function LTRIM (string s)
+{
+  while (s.length > 0 && s.string[0] == ' ') 
+    {
+      s.length--;
+      s.string++;
+    }
+  return s;
+}
+
+string function LTRIM (string s, string c)
+{
+  if (c.length == 1)
+    {
+      while (s.length > 0 && s.string[0] == c.string[0]) 
+        {
+          s.length--;
+          s.string++;
+        }
+      return s;
+    }
+  else
+    return empty_string;
+}
+
+string function RTRIM (string s)
+{
+  while (s.length > 0 && s.string[s.length - 1] == ' ')
+    s.length--;
+  return s;
+}
+
+string function RTRIM (string s, string c)
+{
+  if (c.length == 1)
+    {
+      while (s.length > 0 && s.string[s.length - 1] == c.string[0])
+        s.length--;
+      return s;
+    }
+  else
+    return empty_string;
+}
+
+function NUMBER (string s, ni_format f)
+{
+  struct data_in di;
+  union value out;
+  di.s = s.string;
+  di.v = &out;
+  di.flags = DI_IMPLIED_DECIMALS;
+  di.f1 = 1;
+  di.format = *f;
+  di.e = s.string + min (s.length, di.format.w);
+  data_in (&di);
+  return out.f;
+}
+
+absorb_miss string function STRING (x, no_format f)
+     expression e;
+{
+  union value v;
+  struct fixed_string dst;
+
+  v.f = x;
+  dst = alloc_string (e, f->w);
+  assert ((formats[f->type].cat & FCAT_STRING) == 0);
+  data_out (dst.string, f, &v);
+  return dst;
+}
+
+absorb_miss string function SUBSTR (string s, ofs)
+     expression e;
+{
+  if (ofs >= 1 && ofs <= s.length && (int) ofs == ofs)
+    return copy_string (e, &s.string[(int) ofs - 1], s.length - ofs + 1);
+  else
+    return empty_string;
+}
+
+absorb_miss string function SUBSTR (string s, ofs, cnt)
+     expression e;
+{
+  if (ofs >= 1 && ofs <= s.length && (int) ofs == ofs
+      && cnt >= 1 && cnt <= INT_MAX && (int) cnt == cnt)
+    {
+      int cnt_max = s.length - (int) ofs + 1;
+      return copy_string (e, &s.string[(int) ofs - 1],
+                          cnt <= cnt_max ? cnt : cnt_max);
+    }
+  else
+    return empty_string;
+}
+
+// Artificial.
+operator SQUARE (x) = x * x;
+boolean operator NUM_TO_BOOLEAN (x)
+{
+  if (x == 0. || x == 1. || x == SYSMIS)
+    return x;
+  else
+    {
+      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."));
+      return 0.;
+    }
+}
+
+operator BOOLEAN_TO_NUM (boolean x) = x;
+
+// Beta distribution.
+function PDF.BETA (x >= 0 && x <= 1, a > 0, b > 0)
+     = gsl_ran_beta_pdf (x, a, b);
+function CDF.BETA (x >= 0 && x <= 1, a > 0, b > 0) = gsl_cdf_beta_P (x, a, b);
+function IDF.BETA (P >= 0 && P <= 1, a > 0, b > 0)
+     = gslextras_cdf_beta_Pinv (P, a, b);
+no_opt function RV.BETA (a > 0, b > 0) = gsl_ran_beta (get_rng (), a, b);
+function NCDF.BETA (x >= 0, a > 0, b > 0, lambda > 0)
+     = ncdf_beta (x, a, b, lambda);
+function NPDF.BETA (x >= 0, a > 0, b > 0, lambda > 0)
+     = npdf_beta (x, a, b, lambda);
+
+// Bivariate normal distribution.
+function CDF.BVNOR (x0, x1, r >= -1 && r <= 1) = cdf_bvnor (x0, x1, r);
+function PDF.BVNOR (x0, x1, r >= -1 && r <= 1)
+     = gsl_ran_bivariate_gaussian_pdf (x0, x1, 1, 1, r);
+
+// Cauchy distribution.
+function CDF.CAUCHY (x, a, b > 0) = gsl_cdf_cauchy_P ((x - a) / b, 1);
+function IDF.CAUCHY (P > 0 && P < 1, a, b > 0)
+     = a + b * gsl_cdf_cauchy_Pinv (P, 1);
+function PDF.CAUCHY (x, a, b > 0) = gsl_ran_cauchy_pdf ((x - a) / b, 1) / b;
+no_opt function RV.CAUCHY (a, b > 0) = a + b * gsl_ran_cauchy (get_rng (), 1);
+
+// Chi-square distribution.
+function CDF.CHISQ (x >= 0, df > 0) = gsl_cdf_chisq_P (x, df);
+function IDF.CHISQ (P >= 0 && P < 1, df > 0) = gsl_cdf_chisq_Pinv (P, df);
+function PDF.CHISQ (x >= 0, df > 0) = gsl_ran_chisq_pdf (x, df);
+no_opt function RV.CHISQ (df > 0) = gsl_ran_chisq (get_rng (), df);
+function NCDF.CHISQ (x >= 0, df > 0, c) = unimplemented;
+function NPDF.CHISQ (x >= 0, df > 0, c) = unimplemented;
+function SIG.CHISQ (x >= 0, df > 0) = gsl_cdf_chisq_Q (x, df);
+
+// Exponential distribution.
+function CDF.EXP (x >= 0, a > 0) = gsl_cdf_exponential_P (x, 1. / a);
+function IDF.EXP (P >= 0 && P < 1, a > 0)
+     = gsl_cdf_exponential_Pinv (P, 1. / a);
+function PDF.EXP (x >= 0, a > 0) = gsl_ran_exponential_pdf (x, 1. / a);
+no_opt function RV.EXP (a > 0) = gsl_ran_exponential (get_rng (), 1. / a);
+
+// Exponential power distribution.
+extension function PDF.XPOWER (x, a > 0, b >= 0)
+     = gsl_ran_exppow_pdf (x, a, b);
+no_opt extension function RV.XPOWER (a > 0, b >= 0)
+     = gsl_ran_exppow (get_rng (), a, b);
+
+// F distribution.
+function CDF.F (x >= 0, df1 > 0, df2 > 0) = gsl_cdf_fdist_P (x, df1, df2);
+function IDF.F (P >= 0 && P < 1, df1 > 0, df2 > 0) = idf_fdist (P, df1, df2);
+function PDF.F (x >= 0, df1 > 0, df2 > 0) = gsl_ran_fdist_pdf (x, df1, df2);
+no_opt function RV.F (df1 > 0, df2 > 0) = gsl_ran_fdist (get_rng (), df1, df2);
+function NCDF.F (x >= 0, df1 > 0, df2 > 0, lambda >= 0) = unimplemented;
+function NPDF.F (x >= 0, df1 > 0, df2 > 0, lmabda >= 0) = unimplemented;
+function SIG.F (x >= 0, df1 > 0, df2 > 0) = gsl_cdf_fdist_Q (x, df1, df2);
+
+// Gamma distribution.
+function CDF.GAMMA (x >= 0, a > 0, b > 0) = gsl_cdf_gamma_P (x, a, 1. / b);
+function IDF.GAMMA (P >= 0 && P <= 1, a > 0, b > 0)
+     = gsl_cdf_gamma_Pinv (P, a, 1. / b);
+function PDF.GAMMA (x >= 0, a > 0, b > 0) = gsl_ran_gamma_pdf (x, a, 1. / b);
+no_opt function RV.GAMMA (a > 0, b > 0) 
+     = gsl_ran_gamma (get_rng (), a, 1. / b);
+
+// Half-normal distribution.
+function CDF.HALFNRM (x, a, b > 0) = unimplemented;
+function IDF.HALFNRM (P > 0 && P < 1, a, b > 0) = unimplemented;
+function PDF.HALFNRM (x, a, b > 0) = unimplemented;
+no_opt function RV.HALFNRM (a, b > 0) = unimplemented;
+
+// Inverse Gaussian distribution.
+function CDF.IGAUSS (x > 0, a > 0, b > 0) = unimplemented;
+function IDF.IGAUSS (P >= 0 && P < 1, a > 0, b > 0) = unimplemented;
+function PDF.IGAUSS (x > 0, a > 0, b > 0) = unimplemented;
+no_opt function RV.IGAUSS (a > 0, b > 0) = unimplemented;
+
+// Landau distribution.
+extension function PDF.LANDAU (x) = gsl_ran_landau_pdf (x);
+no_opt extension function RV.LANDAU () = gsl_ran_landau (get_rng ());
+
+// Laplace distribution.
+function CDF.LAPLACE (x, a, b > 0) = gsl_cdf_laplace_P ((x - a) / b, 1);
+function IDF.LAPLACE (P > 0 && P < 1, a, b > 0)
+     = a + b * gsl_cdf_laplace_Pinv (P, 1);
+function PDF.LAPLACE (x, a, b > 0) = gsl_ran_laplace_pdf ((x - a) / b, 1) / b;
+no_opt function RV.LAPLACE (a, b > 0) 
+     = a + b * gsl_ran_laplace (get_rng (), 1);
+
+// Levy alpha-stable distribution.
+no_opt extension function RV.LEVY (c, alpha > 0 && alpha <= 2) 
+     = gsl_ran_levy (get_rng (), c, alpha);
+
+// Levy skew alpha-stable distribution.
+no_opt extension function RV.LVSKEW (c, alpha > 0 && alpha <= 2,
+                                     beta >= -1 && beta <= 1) 
+     = gsl_ran_levy_skew (get_rng (), c, alpha, beta);
+
+// Logistic distribution.
+function CDF.LOGISTIC (x, a, b > 0) = gsl_cdf_logistic_P ((x - a) / b, 1);
+function IDF.LOGISTIC (P > 0 && P < 1, a, b > 0)
+     = a + b * gsl_cdf_logistic_Pinv (P, 1);
+function PDF.LOGISTIC (x, a, b > 0)
+     = gsl_ran_logistic_pdf ((x - a) / b, 1) / b;
+no_opt function RV.LOGISTIC (a, b > 0) 
+     = a + b * gsl_ran_logistic (get_rng (), 1);
+
+// Lognormal distribution.
+function CDF.LNORMAL (x >= 0, m > 0, s > 0)
+     = gsl_cdf_lognormal_P (x, log (m), s);
+function IDF.LNORMAL (P >= 0 && P < 1, m > 0, s > 0)
+     = gsl_cdf_lognormal_Pinv (P, log (m), s);
+function PDF.LNORMAL (x >= 0, m > 0, s > 0)
+     = gsl_ran_lognormal_pdf (x, log (m), s);
+no_opt function RV.LNORMAL (m > 0, s > 0) 
+     = gsl_ran_lognormal (get_rng (), log (m), s);
+
+// Normal distribution.
+function CDF.NORMAL (x, u, s > 0) = gsl_cdf_gaussian_P (x - u, s);
+function IDF.NORMAL (P > 0 && P < 1, u, s > 0)
+     = u + gsl_cdf_gaussian_Pinv (P, s);
+function PDF.NORMAL (x, u, s > 0) = gsl_ran_gaussian_pdf ((x - u) / s, 1) / s;
+no_opt function RV.NORMAL (u, s > 0) = u + gsl_ran_gaussian (get_rng (), s);
+function CDFNORM (x) = gsl_cdf_ugaussian_P (x);
+function PROBIT (P > 0 && P < 1) = gsl_cdf_ugaussian_Pinv (P);
+no_opt function NORMAL (s > 0) = gsl_ran_gaussian (get_rng (), s);
+
+// Normal tail distribution.
+function PDF.NTAIL (x, a > 0, sigma > 0)
+     = gsl_ran_gaussian_tail_pdf (x, a, sigma);
+no_opt function RV.NTAIL (a > 0, sigma > 0) 
+     = gsl_ran_gaussian_tail (get_rng (), a, sigma);
+
+// Pareto distribution.
+function CDF.PARETO (x >= a, a > 0, b > 0) = gsl_cdf_pareto_P (x, b, a);
+function IDF.PARETO (P >= 0 && P < 1, a > 0, b > 0)
+     = gsl_cdf_pareto_Pinv (P, b, a);
+function PDF.PARETO (x >= a, a > 0, b > 0) = gsl_ran_pareto_pdf (x, b, a);
+no_opt function RV.PARETO (a > 0, b > 0) = gsl_ran_pareto (get_rng (), b, a);
+
+// Rayleigh distribution.
+extension function CDF.RAYLEIGH (x, sigma > 0) = gsl_cdf_rayleigh_P (x, sigma);
+extension function IDF.RAYLEIGH (P >= 0 && P <= 1, sigma > 0)
+     = gsl_cdf_rayleigh_Pinv (P, sigma);
+extension function PDF.RAYLEIGH (x, sigma > 0)
+     = gsl_ran_rayleigh_pdf (x, sigma);
+no_opt extension function RV.RAYLEIGH (sigma > 0) 
+     = gsl_ran_rayleigh (get_rng (), sigma);
+
+// Rayleigh tail distribution.
+extension function PDF.RTAIL (x, a, sigma)
+     = gsl_ran_rayleigh_tail_pdf (x, a, sigma);
+no_opt extension function RV.RTAIL (a, sigma) 
+     = gsl_ran_rayleigh_tail (get_rng (), a, sigma);
+
+// Studentized maximum modulus distribution.
+function CDF.SMOD (x > 0, a >= 1, b >= 1) = unimplemented;
+function IDF.SMOD (P >= 0 && P < 1, a >= 1, b >= 1) = unimplemented;
+
+// Studentized range distribution.
+function CDF.SRANGE (x > 0, a >= 1, b >= 1) = unimplemented;
+function IDF.SRANGE (P >= 0 && P < 1, a >= 1, b >= 1) = unimplemented;
+
+// Student t distribution.
+function CDF.T (x, df > 0) = gsl_cdf_tdist_P (x, df);
+function IDF.T (P > 0 && P < 1, df > 0) = gsl_cdf_tdist_Pinv (P, df);
+function PDF.T (x, df > 0) = gsl_ran_tdist_pdf (x, df);
+no_opt function RV.T (df > 0) = gsl_ran_tdist (get_rng (), df);
+function NCDF.T (x, df > 0, nc) = unimplemented;
+function NPDF.T (x, df > 0, nc) = unimplemented;
+
+// Type-1 Gumbel distribution.
+extension function CDF.T1G (x, a, b) = gsl_cdf_gumbel1_P (x, a, b);
+extension function IDF.T1G (P >= 0 && P <= 1, a, b)
+     = gsl_cdf_gumbel1_P (P, a, b);
+extension function PDF.T1G (x, a, b) = gsl_ran_gumbel1_pdf (x, a, b);
+no_opt extension function RV.T1G (a, b) = gsl_ran_gumbel1 (get_rng (), a, b);
+
+// Type-2 Gumbel distribution.
+extension function CDF.T2G (x, a, b) = gsl_cdf_gumbel2_P (x, a, b);
+extension function IDF.T2G (P >= 0 && P <= 1, a, b)
+     = gsl_cdf_gumbel2_P (P, a, b);
+extension function PDF.T2G (x, a, b) = gsl_ran_gumbel2_pdf (x, a, b);
+no_opt extension function RV.T2G (a, b) = gsl_ran_gumbel2 (get_rng (), a, b);
+
+// Uniform distribution.
+function CDF.UNIFORM (x <= b, a <= x, b) = gsl_cdf_flat_P (x, a, b);
+function IDF.UNIFORM (P >= 0 && P <= 1, a <= b, b)
+     = gsl_cdf_flat_Pinv (P, a, b);
+function PDF.UNIFORM (x <= b, a <= x, b) = gsl_ran_flat_pdf (x, a, b);
+no_opt function RV.UNIFORM (a <= b, b) = gsl_ran_flat (get_rng (), a, b);
+no_opt function UNIFORM (b >= 0) = gsl_ran_flat (get_rng (), 0, b);
+
+// Weibull distribution.
+function CDF.WEIBULL (x >= 0, a > 0, b > 0) = gsl_cdf_weibull_P (x, a, b);
+function IDF.WEIBULL (P >= 0 && P < 1, a > 0, b > 0)
+     = gsl_cdf_weibull_Pinv (P, a, b);
+function PDF.WEIBULL (x >= 0, a > 0, b > 0) = gsl_ran_weibull_pdf (x, a, b);
+no_opt function RV.WEIBULL (a > 0, b > 0) = gsl_ran_weibull (get_rng (), a, b);
+
+// Bernoulli distribution.
+function CDF.BERNOULLI (k == 0 || k == 1, p >= 0 && p <= 1) 
+     = k ? 1 : 1 - p;
+function PDF.BERNOULLI (k == 0 || k == 1, p >= 0 && p <= 1)
+     = gsl_ran_bernoulli_pdf (k, p);
+no_opt function RV.BERNOULLI (p >= 0 && p <= 1) 
+     = gsl_ran_bernoulli (get_rng (), p);
+
+// Binomial distribution.
+function CDF.BINOM (k, n > 0 && n == floor (n), p >= 0 && p <= 1)
+     = gslextras_cdf_binomial_P (k, p, n);
+function PDF.BINOM (k >= 0 && k == floor (k) && k <= n,
+                    n > 0 && n == floor (n),
+                    p >= 0 && p <= 1)
+     = gsl_ran_binomial_pdf (k, p, n);
+no_opt function RV.BINOM (p > 0 && p == floor (p), n >= 0 && n <= 1) 
+     = gsl_ran_binomial (get_rng (), p, n);
+
+// Geometric distribution.
+function CDF.GEOM (k >= 1 && k == floor (k), p >= 0 && p <= 1)
+     = gslextras_cdf_geometric_P (k, p);
+function PDF.GEOM (k >= 1 && k == floor (k),
+                   p >= 0 && p <= 1)
+     = gsl_ran_geometric_pdf (k, p);
+no_opt function RV.GEOM (p >= 0 && p <= 1) = gsl_ran_geometric (get_rng (), p);
+
+// Hypergeometric distribution.
+function CDF.HYPER (k >= 0 && k == floor (k) && k <= c,
+                    a > 0 && a == floor (a),
+                    b > 0 && b == floor (b) && b <= a,
+                    c > 0 && c == floor (c) && c <= a)
+     = gslextras_cdf_hypergeometric_P (k, c, a - c, b);
+function PDF.HYPER (k >= 0 && k == floor (k) && k <= c,
+                    a > 0 && a == floor (a),
+                    b > 0 && b == floor (b) && b <= a,
+                    c > 0 && c == floor (c) && c <= a)
+     = gsl_ran_hypergeometric_pdf (k, c, a - c, b);
+no_opt function RV.HYPER (a > 0 && a == floor (a),
+                          b > 0 && b == floor (b) && b <= a,
+                          c > 0 && c == floor (c) && c <= a)
+     = gsl_ran_hypergeometric (get_rng (), c, a - c, b);
+
+// Logarithmic distribution.
+extension function PDF.LOG (k >= 1, p > 0 && p <= 1)
+     = gsl_ran_logarithmic_pdf (k, p);
+no_opt extension function RV.LOG (p > 0 && p <= 1) 
+     = gsl_ran_logarithmic (get_rng (), p);
+
+// Negative binomial distribution.
+function CDF.NEGBIN (k >= 1, n == floor (n), p > 0 && p <= 1)
+     = gslextras_cdf_negative_binomial_P (k, p, n);
+function PDF.NEGBIN (k >= 1, n == floor (n), p > 0 && p <= 1)
+     = gsl_ran_negative_binomial_pdf (k, p, n);
+no_opt function RV.NEGBIN (n == floor (n), p > 0 && p <= 1) 
+     = gsl_ran_negative_binomial (get_rng (), p, n);
+
+// Poisson distribution.
+function CDF.POISSON (k >= 0 && k == floor (k), mu > 0)
+     = gslextras_cdf_poisson_P (k, mu);
+function PDF.POISSON (k >= 0 && k == floor (k), mu > 0)
+     = gsl_ran_poisson_pdf (k, mu);
+no_opt function RV.POISSON (mu > 0) = gsl_ran_poisson (get_rng (), mu);
+
+// Weirdness.
+absorb_miss boolean function MISSING (x) = x == SYSMIS || !finite (x);
+absorb_miss boolean function SYSMIS (x) = x == SYSMIS || !finite (x);
+no_opt boolean function SYSMIS (num_var v)
+     case c;
+{
+  return case_num (c, v->fv) == SYSMIS;
+}
+no_opt boolean function VALUE (num_var v)
+     case c;
+{
+  return case_num (c, v->fv);
+}
+
+no_opt operator VEC_ELEM_NUM (idx)
+     vector v;
+     case c;
+{
+  if (idx >= 1 && idx <= v->cnt) 
+    {
+      const struct variable *var = v->var[(int) idx - 1];
+      double value = case_num (c, var->fv);
+      return !mv_is_num_user_missing (&var->miss, value) ? value : SYSMIS; 
+    }
+  else
+    {
+      if (idx == 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."),
+             idx, v->name);
+      return SYSMIS;
+    }
+}
+
+absorb_miss no_opt string operator VEC_ELEM_STR (idx)
+     expression e;
+     vector v;
+     case c;
+{
+  if (idx >= 1 && idx <= v->cnt)
+    {
+      struct variable *var = v->var[(int) idx - 1];
+      return copy_string (e, case_str (c, var->fv), var->width);
+    }
+  else
+    {
+      if (idx == SYSMIS)
+        msg (SE, _("SYSMIS is not a valid index value for vector "
+                   "%s.  The result will be set to the empty string."),
+             v->name);
+      else
+        msg (SE, _("%g is not a valid index value for vector %s.  "
+                   "The result will be set to the empty string."),
+             idx, v->name);
+      return empty_string;
+    }
+}
+
+// Terminals.
+
+no_opt operator NUM_VAR ()
+     case c;
+     num_var v;
+{
+  double d = case_num (c, v->fv);
+  return !mv_is_num_user_missing (&v->miss, d) ? d : SYSMIS;
+}
+
+no_opt string operator STR_VAR ()
+     case c;
+     expression e;
+     str_var v;
+{
+  struct fixed_string s = alloc_string (e, v->width);
+  memcpy (s.string, case_str (c, v->fv), v->width);
+  return s;
+}
+
+no_opt function LAG (num_var v, pos_int n_before)
+{
+  struct ccase *c = lagged_case (n_before);
+  if (c != NULL)
+    {
+      double x = case_num (c, v->fv);
+      return !mv_is_num_user_missing (&v->miss, x) ? x : SYSMIS;
+    }
+  else
+    return SYSMIS;
+}
+
+no_opt function LAG (num_var v)
+{
+  struct ccase *c = lagged_case (1);
+  if (c != NULL)
+    {
+      double x = case_num (c, v->fv);
+      return !mv_is_num_user_missing (&v->miss, x) ? x : SYSMIS;
+    }
+  else
+    return SYSMIS;
+}
+
+no_opt string function LAG (str_var v, pos_int n_before)
+     expression e;
+{
+  struct ccase *c = lagged_case (n_before);
+  if (c != NULL)
+    return copy_string (e, case_str (c, v->fv), v->width);
+  else
+    return empty_string;
+}
+
+no_opt string function LAG (str_var v)
+     expression e;
+{
+  struct ccase *c = lagged_case (1);
+  if (c != NULL)
+    return copy_string (e, case_str (c, v->fv), v->width);
+  else
+    return empty_string;
+}
+
+no_opt operator NUM_SYS ()
+     case c;
+     num_var v;
+{
+  return case_num (c, v->fv) == SYSMIS;
+}
+
+no_opt operator NUM_VAL ()
+     case c;
+     num_var v;
+{
+  return case_num (c, v->fv);
+}
+
+no_opt operator CASENUM ()
+     case_idx idx;
+{
+  return idx;
+}
diff --git a/src/language/expressions/operations.h.pl b/src/language/expressions/operations.h.pl
new file mode 100644 (file)
index 0000000..d43a502
--- /dev/null
@@ -0,0 +1,54 @@
+do 'generate.pl';
+
+sub generate_output {
+    print "#include <stdlib.h>\n";
+    print "#include <stdbool.h>\n\n";
+
+    print "typedef enum";
+    print "  {\n";
+    my (@atoms);
+    foreach my $type (@types) {
+       next if $type->{ROLE} eq 'fixed';
+       push (@atoms, "OP_$type->{NAME}");
+    }
+    print_operations ('atom', 1, \@atoms);
+    print_operations ('function', "OP_atom_last + 1", \@funcs);
+    print_operations ('operator', "OP_function_last + 1", \@opers);
+    print_range ("OP_composite", "OP_function_first", "OP_operator_last");
+    print ",\n\n";
+    print_range ("OP", "OP_atom_first", "OP_composite_last");
+    print "\n  }\n";
+    print "operation_type, atom_type;\n";
+
+    print_predicate ('is_operation', 'OP');
+    print_predicate ("is_$_", "OP_$_")
+       foreach qw (atom composite function operator);
+}
+
+sub print_operations {
+    my ($type, $first, $names) = @_;
+    print "    /* \u$type types. */\n";
+    print "    $names->[0] = $first,\n";
+    print "    $_,\n" foreach @$names[1...$#{$names}];
+    print_range ("OP_$type", $names->[0], $names->[$#{$names}]);
+    print ",\n\n";
+}
+
+sub print_range {
+    my ($prefix, $first, $last) = @_;
+    print "    ${prefix}_first = $first,\n";
+    print "    ${prefix}_last = $last,\n";
+    print "    ${prefix}_cnt = ${prefix}_last - ${prefix}_first + 1";
+}
+
+sub print_predicate {
+    my ($function, $category) = @_;
+    my ($assertion) = "";
+
+    print "\nstatic inline bool\n";
+    print "$function (operation_type op)\n";
+    print "{\n";
+    print "  assert (is_operation (op));\n" if $function ne 'is_operation';
+    print "  return op >= ${category}_first && op <= ${category}_last;\n";
+    print "}\n";
+}
diff --git a/src/language/expressions/optimize.c b/src/language/expressions/optimize.c
new file mode 100644 (file)
index 0000000..b280b6d
--- /dev/null
@@ -0,0 +1,382 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "private.h"
+#include <math.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "calendar.h"
+#include "data-in.h"
+#include "message.h"
+#include "evaluate.h"
+#include "helpers.h"
+#include "misc.h"
+#include "pool.h"
+#include "public.h"
+#include "str.h"
+#include "variable.h"
+
+static union any_node *evaluate_tree (struct composite_node *,
+                                      struct expression *);
+static union any_node *optimize_tree (union any_node *, struct expression *);
+
+union any_node *
+expr_optimize (union any_node *node, struct expression *e)
+{
+  int nonconst_cnt = 0; /* Number of nonconstant children. */
+  int sysmis_cnt = 0;   /* Number of system-missing children. */
+  struct operation *op;
+  struct composite_node *c;
+  int i;
+
+  /* We can't optimize an atom. */
+  if (is_atom (node->type))
+    return node;
+
+  /* Start by optimizing all the children. */
+  c = &node->composite;
+  for (i = 0; i < c->arg_cnt; i++)
+    {
+      c->args[i] = expr_optimize (c->args[i], e);
+      if (c->args[i]->type == OP_number)
+       {
+         if (c->args[i]->number.n == SYSMIS)
+           sysmis_cnt++;
+       }
+
+      if (!is_atom (c->args[i]->type))
+       nonconst_cnt++;
+    }
+
+  op = &operations[c->type];
+  if (sysmis_cnt && (op->flags & OPF_ABSORB_MISS) == 0)
+    {
+      /* Most operations produce SYSMIS given any SYSMIS
+         argument. */
+      assert (op->returns == OP_number || op->returns == OP_boolean);
+      if (op->returns == OP_number)
+        return expr_allocate_number (e, SYSMIS);
+      else
+        return expr_allocate_boolean (e, SYSMIS);
+    }
+  else if (!nonconst_cnt && (op->flags & OPF_NONOPTIMIZABLE) == 0) 
+    {
+      /* Evaluate constant expressions. */
+      return evaluate_tree (&node->composite, e);
+    }
+  else 
+    {
+      /* A few optimization possibilities are still left. */
+      return optimize_tree (node, e); 
+    }
+}
+
+static int
+eq_double (union any_node *node, double n) 
+{
+  return node->type == OP_number && node->number.n == n;
+}
+
+static union any_node *
+optimize_tree (union any_node *node, struct expression *e)
+{
+  struct composite_node *n = &node->composite;
+  assert (is_composite (node->type));
+
+  /* If you add to these optimizations, please also add a
+     correctness test in tests/expressions/expressions.sh. */
+
+  /* x+0, x-0, 0+x => x. */
+  if ((n->type == OP_ADD || n->type == OP_SUB) && eq_double (n->args[1], 0.))
+    return n->args[0];
+  else if (n->type == OP_ADD && eq_double (n->args[0], 0.)) 
+    return n->args[1];
+
+  /* x*1, x/1, 1*x => x. */
+  else if ((n->type == OP_MUL || n->type == OP_DIV)
+           && eq_double (n->args[1], 1.))
+    return n->args[0];
+  else if (n->type == OP_MUL && eq_double (n->args[0], 1.))
+    return n->args[1];
+  
+  /* 0*x, 0/x, x*0, MOD(0,x) => 0. */
+  else if (((n->type == OP_MUL || n->type == OP_DIV || n->type == OP_MOD_nn)
+            && eq_double (n->args[0], 0.))
+           || (n->type == OP_MUL && eq_double (n->args[1], 0.)))
+    return expr_allocate_number (e, 0.);
+
+  /* x**1 => x. */
+  else if (n->type == OP_POW && eq_double (n->args[1], 1))
+    return n->args[0];
+  
+  /* x**2 => SQUARE(x). */
+  else if (n->type == OP_POW && eq_double (n->args[1], 2))
+    return expr_allocate_unary (e, OP_SQUARE, n->args[0]);
+
+  /* Otherwise, nothing to do. */
+  else
+    return node;
+}
+
+static double get_number_arg (struct composite_node *, size_t arg_idx);
+static double *get_number_args (struct composite_node *,
+                                 size_t arg_idx, size_t arg_cnt,
+                                 struct expression *);
+static struct fixed_string get_string_arg (struct composite_node *,
+                                           size_t arg_idx);
+static struct fixed_string *get_string_args (struct composite_node *,
+                                             size_t arg_idx, size_t arg_cnt,
+                                             struct expression *);
+static const struct fmt_spec *get_format_arg (struct composite_node *,
+                                              size_t arg_idx);
+
+static union any_node *
+evaluate_tree (struct composite_node *node, struct expression *e)
+{
+  switch (node->type) 
+    {
+#include "optimize.inc"
+
+    default:
+      assert (0);
+    }
+
+  /* Not reached. */
+  assert (0);
+  abort ();
+}
+
+static double
+get_number_arg (struct composite_node *c, size_t arg_idx) 
+{
+  assert (arg_idx < c->arg_cnt);
+  assert (c->args[arg_idx]->type == OP_number
+          || c->args[arg_idx]->type == OP_boolean);
+  return c->args[arg_idx]->number.n;
+}
+
+static double *
+get_number_args (struct composite_node *c, size_t arg_idx, size_t arg_cnt,
+                 struct expression *e) 
+{
+  double *d;
+  size_t i;
+
+  d = pool_alloc (e->expr_pool, sizeof *d * arg_cnt);
+  for (i = 0; i < arg_cnt; i++)
+    d[i] = get_number_arg (c, i + arg_idx);
+  return d;
+}
+
+static struct fixed_string
+get_string_arg (struct composite_node *c, size_t arg_idx) 
+{
+  assert (arg_idx < c->arg_cnt);
+  assert (c->args[arg_idx]->type == OP_string);
+  return c->args[arg_idx]->string.s;
+}
+
+static struct fixed_string *
+get_string_args (struct composite_node *c, size_t arg_idx, size_t arg_cnt,
+                 struct expression *e) 
+{
+  struct fixed_string *s;
+  size_t i;
+
+  s = pool_alloc (e->expr_pool, sizeof *s * arg_cnt);
+  for (i = 0; i < arg_cnt; i++)
+    s[i] = get_string_arg (c, i + arg_idx);
+  return s;
+}
+
+static const struct fmt_spec *
+get_format_arg (struct composite_node *c, size_t arg_idx) 
+{
+  assert (arg_idx < c->arg_cnt);
+  assert (c->args[arg_idx]->type == OP_ni_format
+          || c->args[arg_idx]->type == OP_no_format);
+  return &c->args[arg_idx]->format.f;
+}
+\f
+/* Expression flattening. */
+
+static union operation_data *allocate_aux (struct expression *,
+                                                operation_type);
+static void flatten_node (union any_node *, struct expression *);
+
+static void
+emit_operation (struct expression *e, operation_type type) 
+{
+  allocate_aux (e, OP_operation)->operation = type;
+}
+
+static void
+emit_number (struct expression *e, double n) 
+{
+  allocate_aux (e, OP_number)->number = n;
+}
+
+static void
+emit_string (struct expression *e, struct fixed_string s) 
+{
+  allocate_aux (e, OP_string)->string = s;
+}
+
+static void
+emit_format (struct expression *e, const struct fmt_spec *f) 
+{
+  allocate_aux (e, OP_format)->format = pool_clone (e->expr_pool,
+                                                    f, sizeof *f);
+}
+
+static void
+emit_variable (struct expression *e, struct variable *v) 
+{
+  allocate_aux (e, OP_variable)->variable = v;
+}
+
+static void
+emit_vector (struct expression *e, const struct vector *v) 
+{
+  allocate_aux (e, OP_vector)->vector = v;
+}
+
+static void
+emit_integer (struct expression *e, int i) 
+{
+  allocate_aux (e, OP_integer)->integer = i;
+}
+
+void 
+expr_flatten (union any_node *n, struct expression *e) 
+{
+  flatten_node (n, e);
+  e->type = expr_node_returns (n);
+  emit_operation (e, (e->type == OP_string
+                      ? OP_return_string : OP_return_number));
+}
+
+static void
+flatten_atom (union any_node *n, struct expression *e)
+{
+  switch (n->type) 
+    {
+    case OP_number:
+    case OP_boolean:
+      emit_operation (e, OP_number);
+      emit_number (e, n->number.n);
+      break;
+
+    case OP_string:
+      emit_operation (e, OP_string);
+      emit_string (e, n->string.s);
+      break;
+
+    case OP_num_var:
+    case OP_str_var:
+    case OP_vector:
+    case OP_no_format:
+    case OP_ni_format:
+    case OP_pos_int:
+      /* These are passed as aux data following the
+         operation. */
+      break;
+
+    default:
+      abort ();
+    }
+}
+
+static void
+flatten_composite (union any_node *n, struct expression *e)
+{
+  struct operation *op = &operations[n->type];
+  size_t i;
+      
+  for (i = 0; i < n->composite.arg_cnt; i++)
+    flatten_node (n->composite.args[i], e);
+
+  if (n->type != OP_BOOLEAN_TO_NUM)
+    emit_operation (e, n->type);
+
+  for (i = 0; i < n->composite.arg_cnt; i++)
+    {
+      union any_node *arg = n->composite.args[i];
+      switch (arg->type) 
+        {
+        case OP_num_var:
+        case OP_str_var:
+          emit_variable (e, arg->variable.v);
+          break;
+
+        case OP_vector:
+          emit_vector (e, arg->vector.v);
+          break;
+
+        case OP_ni_format:
+        case OP_no_format:
+          emit_format (e, &arg->format.f);
+          break;
+
+        case OP_pos_int:
+          emit_integer (e, arg->integer.i);
+          break;
+              
+        default:
+          /* Nothing to do. */
+          break;
+        }
+    }
+
+  if (op->flags & OPF_ARRAY_OPERAND)
+    emit_integer (e, n->composite.arg_cnt - op->arg_cnt + 1);
+  if (op->flags & OPF_MIN_VALID)
+    emit_integer (e, n->composite.min_valid);
+}
+
+void
+flatten_node (union any_node *n, struct expression *e)
+{
+  assert (is_operation (n->type));
+
+  if (is_atom (n->type))
+    flatten_atom (n, e);
+  else if (is_composite (n->type))
+    flatten_composite (n, e);
+  else 
+    abort ();
+}
+
+static union operation_data *
+allocate_aux (struct expression *e, operation_type type) 
+{
+  if (e->op_cnt >= e->op_cap) 
+    {
+      e->op_cap = (e->op_cap + 8) * 3 / 2;
+      e->ops = pool_realloc (e->expr_pool, e->ops, sizeof *e->ops * e->op_cap);
+      e->op_types = pool_realloc (e->expr_pool, e->op_types,
+                                  sizeof *e->op_types * e->op_cap);
+    }
+
+  e->op_types[e->op_cnt] = type;
+  return &e->ops[e->op_cnt++];
+}
diff --git a/src/language/expressions/optimize.inc.pl b/src/language/expressions/optimize.inc.pl
new file mode 100644 (file)
index 0000000..798c500
--- /dev/null
@@ -0,0 +1,81 @@
+do 'generate.pl';
+
+sub generate_output {
+    for my $opname (@order) {
+       my ($op) = $ops{$opname};
+
+       if (!$op->{OPTIMIZABLE} || $op->{UNIMPLEMENTED}) {
+           print "case $opname:\n";
+           print "  abort ();\n\n";
+           next;
+       }
+
+       my (@decls);
+       my ($arg_idx) = 0;
+       for my $arg (@{$op->{ARGS}}) {
+           my ($decl);
+           my ($name) = $arg->{NAME};
+           my ($type) = $arg->{TYPE};
+           my ($ctype) = c_type ($type);
+           my ($idx) = $arg->{IDX};
+           if (!defined ($idx)) {
+               my ($func) = "get_$type->{ATOM}_arg";
+               push (@decls, "${ctype}arg_$name = $func (node, $arg_idx)");
+           } else {
+               my ($decl) = "size_t arg_$idx = node->arg_cnt";
+               $decl .= " - $arg_idx" if $arg_idx;
+               push (@decls, $decl);
+
+               push (@decls, "${ctype}*arg_$name = "
+                     . "get_$type->{ATOM}_args "
+                     . " (node, $arg_idx, arg_$idx, e)");
+           }
+           $arg_idx++;
+       }
+
+       my ($sysmis_cond) = make_sysmis_decl ($op, "node->min_valid");
+       push (@decls, $sysmis_cond) if defined $sysmis_cond;
+
+       my (@args);
+       for my $arg (@{$op->{ARGS}}) {
+           push (@args, "arg_$arg->{NAME}");
+           if (defined $arg->{IDX}) {
+               my ($idx) = "arg_$arg->{IDX}";
+               $idx .= " / $arg->{TIMES}" if $arg->{TIMES} != 1;
+               push (@args, $idx);
+           }
+       }
+       for my $aux (@{$op->{AUX}}) {
+           my ($type) = $aux->{TYPE};
+           if ($type->{ROLE} eq 'leaf') {
+               my ($func) = "get_$type->{ATOM}_arg";
+               push (@args, "$func (node, $arg_idx)");
+               $arg_idx++;
+           } elsif ($type->{ROLE} eq 'fixed') {
+               push (@args, $type->{FIXED_VALUE});
+           } else {
+               die;
+           }
+       }
+
+       my ($result) = "eval_$op->{OPNAME} (" . join (', ', @args) . ")";
+       if (@decls && defined ($sysmis_cond)) {
+           my ($miss_ret) = $op->{RETURNS}{MISSING_VALUE};
+           push (@decls, c_type ($op->{RETURNS}) . "result = "
+                 . "force_sysmis ? $miss_ret : $result");
+           $result = "result";
+       }
+
+       print "case $opname:\n";
+       my ($alloc_func) = "expr_allocate_$op->{RETURNS}{NAME}";
+       if (@decls) {
+           print "  {\n";
+           print "    $_;\n" foreach @decls;
+           print "    return $alloc_func (e, $result);\n";
+           print "  }\n";
+       } else {
+           print "  return $alloc_func (e, $result);\n";
+       }
+       print "\n";
+    }
+}
diff --git a/src/language/expressions/parse.c b/src/language/expressions/parse.c
new file mode 100644 (file)
index 0000000..c348361
--- /dev/null
@@ -0,0 +1,1461 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "private.h"
+#include <ctype.h>
+#include <float.h>
+#include <limits.h>
+#include <stdlib.h>
+#include "array.h"
+#include "alloc.h"
+#include "case.h"
+#include "dictionary.h"
+#include "message.h"
+#include "helpers.h"
+#include "lexer.h"
+#include "misc.h"
+#include "pool.h"
+#include "settings.h"
+#include "str.h"
+#include "variable.h"
+#include "procedure.h"
+\f
+/* Declarations. */
+
+/* Recursive descent parser in order of increasing precedence. */
+typedef union any_node *parse_recursively_func (struct expression *);
+static parse_recursively_func parse_or, parse_and, parse_not;
+static parse_recursively_func parse_rel, parse_add, parse_mul;
+static parse_recursively_func parse_neg, parse_exp;
+static parse_recursively_func parse_primary;
+static parse_recursively_func parse_vector_element, parse_function;
+
+/* Utility functions. */
+static struct expression *expr_create (struct dictionary *);
+atom_type expr_node_returns (const union any_node *);
+
+static const char *atom_type_name (atom_type);
+static struct expression *finish_expression (union any_node *,
+                                             struct expression *);
+static bool type_check (struct expression *, union any_node **,
+                        enum expr_type expected_type);
+static union any_node *allocate_unary_variable (struct expression *,
+                                                struct variable *); 
+\f
+/* Public functions. */
+
+/* Parses an expression of the given TYPE.
+   If DICT is nonnull then variables and vectors within it may be
+   referenced within the expression; otherwise, the expression
+   must not reference any variables or vectors.
+   Returns the new expression if successful or a null pointer
+   otherwise. */
+struct expression *
+expr_parse (struct dictionary *dict, enum expr_type type) 
+{
+  union any_node *n;
+  struct expression *e;
+
+  assert (type == EXPR_NUMBER || type == EXPR_STRING || type == EXPR_BOOLEAN);
+
+  e = expr_create (dict);
+  n = parse_or (e);
+  if (n != NULL && type_check (e, &n, type))
+    return finish_expression (expr_optimize (n, e), e);
+  else
+    {
+      expr_free (e);
+      return NULL; 
+    }
+}
+
+/* Parses and returns an expression of the given TYPE, as
+   expr_parse(), and sets up so that destroying POOL will free
+   the expression as well. */
+struct expression *
+expr_parse_pool (struct pool *pool,
+                 struct dictionary *dict, enum expr_type type) 
+{
+  struct expression *e = expr_parse (dict, type);
+  if (e != NULL)
+    pool_add_subpool (pool, e->expr_pool);
+  return e;
+}
+
+/* Free expression E. */
+void
+expr_free (struct expression *e)
+{
+  if (e != NULL)
+    pool_destroy (e->expr_pool);
+}
+
+struct expression *
+expr_parse_any (struct dictionary *dict, bool optimize)
+{
+  union any_node *n;
+  struct expression *e;
+
+  e = expr_create (dict);
+  n = parse_or (e);
+  if (n == NULL)
+    {
+      expr_free (e);
+      return NULL;
+    }
+  
+  if (optimize)
+    n = expr_optimize (n, e);
+  return finish_expression (n, e);
+}
+\f
+/* Finishing up expression building. */
+
+/* Height of an expression's stacks. */
+struct stack_heights 
+  {
+    int number_height;  /* Height of number stack. */
+    int string_height;  /* Height of string stack. */
+  };
+
+/* Stack heights used by different kinds of arguments. */
+static const struct stack_heights on_number_stack = {1, 0};
+static const struct stack_heights on_string_stack = {0, 1};
+static const struct stack_heights not_on_stack = {0, 0};
+
+/* Returns the stack heights used by an atom of the given
+   TYPE. */
+static const struct stack_heights *
+atom_type_stack (atom_type type)
+{
+  assert (is_atom (type));
+  
+  switch (type) 
+    {
+    case OP_number:
+    case OP_boolean:
+      return &on_number_stack;
+
+    case OP_string:
+      return &on_string_stack;
+
+    case OP_format:
+    case OP_ni_format:
+    case OP_no_format:
+    case OP_num_var:
+    case OP_str_var:
+    case OP_integer:
+    case OP_pos_int:
+    case OP_vector:
+      return &not_on_stack;
+          
+    default:
+      abort ();
+    }
+}
+
+/* Measures the stack height needed for node N, supposing that
+   the stack height is initially *HEIGHT and updating *HEIGHT to
+   the final stack height.  Updates *MAX, if necessary, to
+   reflect the maximum intermediate or final height. */
+static void
+measure_stack (const union any_node *n,
+               struct stack_heights *height, struct stack_heights *max)
+{
+  const struct stack_heights *return_height;
+
+  if (is_composite (n->type)) 
+    {
+      struct stack_heights args;
+      int i;
+
+      args = *height;
+      for (i = 0; i < n->composite.arg_cnt; i++)
+        measure_stack (n->composite.args[i], &args, max);
+
+      return_height = atom_type_stack (operations[n->type].returns);
+    }
+  else
+    return_height = atom_type_stack (n->type);
+
+  height->number_height += return_height->number_height;
+  height->string_height += return_height->string_height;
+
+  if (height->number_height > max->number_height)
+    max->number_height = height->number_height;
+  if (height->string_height > max->string_height)
+    max->string_height = height->string_height;
+}
+
+/* Allocates stacks within E sufficient for evaluating node N. */
+static void
+allocate_stacks (union any_node *n, struct expression *e) 
+{
+  struct stack_heights initial = {0, 0};
+  struct stack_heights max = {0, 0};
+
+  measure_stack (n, &initial, &max);
+  e->number_stack = pool_alloc (e->expr_pool,
+                                sizeof *e->number_stack * max.number_height);
+  e->string_stack = pool_alloc (e->expr_pool,
+                                sizeof *e->string_stack * max.string_height);
+}
+
+/* Finalizes expression E for evaluating node N. */
+static struct expression *
+finish_expression (union any_node *n, struct expression *e)
+{
+  /* Allocate stacks. */
+  allocate_stacks (n, e);
+
+  /* Output postfix representation. */
+  expr_flatten (n, e);
+
+  /* The eval_pool might have been used for allocating strings
+     during optimization.  We need to keep those strings around
+     for all subsequent evaluations, so start a new eval_pool. */
+  e->eval_pool = pool_create_subpool (e->expr_pool);
+
+  return e;
+}
+
+/* Verifies that expression E, whose root node is *N, can be
+   converted to type EXPECTED_TYPE, inserting a conversion at *N
+   if necessary.  Returns true if successful, false on failure. */
+static bool
+type_check (struct expression *e,
+            union any_node **n, enum expr_type expected_type)
+{
+  atom_type actual_type = expr_node_returns (*n);
+
+  switch (expected_type) 
+    {
+    case EXPR_BOOLEAN:
+    case EXPR_NUMBER:
+      if (actual_type != OP_number && actual_type != OP_boolean)
+       {
+         msg (SE, _("Type mismatch: expression has %s type, "
+                     "but a numeric value is required here."),
+               atom_type_name (actual_type));
+         return false;
+       }
+      if (actual_type == OP_number && expected_type == OP_boolean)
+       *n = expr_allocate_unary (e, OP_NUM_TO_BOOLEAN, *n);
+      break;
+      
+    case EXPR_STRING:
+      if (actual_type != OP_string)
+        {
+          msg (SE, _("Type mismatch: expression has %s type, "
+                     "but a string value is required here."),
+               atom_type_name (actual_type));
+          return false;
+        }
+      break;
+
+    default:
+      abort ();
+    }
+  
+  return true;
+}
+\f
+/* Recursive-descent expression parser. */
+
+/* Considers whether *NODE may be coerced to type REQUIRED_TYPE.
+   Returns true if possible, false if disallowed.
+
+   If DO_COERCION is false, then *NODE is not modified and there
+   are no side effects.
+
+   If DO_COERCION is true, we perform the coercion if possible,
+   modifying *NODE if necessary.  If the coercion is not possible
+   then we free *NODE and set *NODE to a null pointer.
+
+   This function's interface is somewhat awkward.  Use one of the
+   wrapper functions type_coercion(), type_coercion_assert(), or
+   is_coercible() instead. */
+static bool
+type_coercion_core (struct expression *e,
+                    atom_type required_type,
+                    union any_node **node,
+                    const char *operator_name,
+                    bool do_coercion) 
+{
+  atom_type actual_type;
+
+  assert (!!do_coercion == (e != NULL));
+  if (*node == NULL) 
+    {
+      /* Propagate error.  Whatever caused the original error
+         already emitted an error message. */
+      return false;
+    }
+
+  actual_type = expr_node_returns (*node);
+  if (actual_type == required_type) 
+    {
+      /* Type match. */
+      return true; 
+    }
+
+  switch (required_type) 
+    {
+    case OP_number:
+      if (actual_type == OP_boolean) 
+        {
+          /* To enforce strict typing rules, insert Boolean to
+             numeric "conversion".  This conversion is a no-op,
+             so it will be removed later. */
+          if (do_coercion)
+            *node = expr_allocate_unary (e, OP_BOOLEAN_TO_NUM, *node);
+          return true; 
+        }
+      break;
+
+    case OP_string:
+      /* No coercion to string. */
+      break;
+
+    case OP_boolean:
+      if (actual_type == OP_number)
+        {
+          /* Convert numeric to boolean. */
+          if (do_coercion)
+            *node = expr_allocate_unary (e, OP_NUM_TO_BOOLEAN, *node);
+          return true;
+        }
+      break;
+
+    case OP_format:
+      abort ();
+
+    case OP_ni_format:
+      if ((*node)->type == OP_format
+          && check_input_specifier (&(*node)->format.f, false)
+          && check_specifier_type (&(*node)->format.f, NUMERIC, false))
+        {
+          if (do_coercion)
+            (*node)->type = OP_ni_format;
+          return true;
+        }
+      break;
+
+    case OP_no_format:
+      if ((*node)->type == OP_format
+          && check_output_specifier (&(*node)->format.f, false)
+          && check_specifier_type (&(*node)->format.f, NUMERIC, false))
+        {
+          if (do_coercion)
+            (*node)->type = OP_no_format;
+          return true;
+        }
+      break;
+
+    case OP_num_var:
+      if ((*node)->type == OP_NUM_VAR)
+        {
+          if (do_coercion)
+            *node = (*node)->composite.args[0];
+          return true;
+        }
+      break;
+
+    case OP_str_var:
+      if ((*node)->type == OP_STR_VAR)
+        {
+          if (do_coercion)
+            *node = (*node)->composite.args[0];
+          return true;
+        }
+      break;
+
+    case OP_pos_int:
+      if ((*node)->type == OP_number
+          && floor ((*node)->number.n) == (*node)->number.n
+          && (*node)->number.n > 0 && (*node)->number.n < INT_MAX) 
+        {
+          if (do_coercion)
+            *node = expr_allocate_pos_int (e, (*node)->number.n);
+          return true;
+        }
+      break;
+
+    default:
+      abort ();
+    }
+
+  if (do_coercion) 
+    {
+      msg (SE, _("Type mismatch while applying %s operator: "
+                 "cannot convert %s to %s."),
+           operator_name,
+           atom_type_name (actual_type), atom_type_name (required_type));
+      *node = NULL;
+    }
+  return false;
+}
+
+/* Coerces *NODE to type REQUIRED_TYPE, and returns success.  If
+   *NODE cannot be coerced to the desired type then we issue an
+   error message about operator OPERATOR_NAME and free *NODE. */
+static bool
+type_coercion (struct expression *e,
+               atom_type required_type, union any_node **node,
+               const char *operator_name)
+{
+  return type_coercion_core (e, required_type, node, operator_name, true);
+}
+
+/* Coerces *NODE to type REQUIRED_TYPE.
+   Assert-fails if the coercion is disallowed. */
+static void
+type_coercion_assert (struct expression *e,
+                      atom_type required_type, union any_node **node)
+{
+  int success = type_coercion_core (e, required_type, node, NULL, true);
+  assert (success);
+}
+
+/* Returns true if *NODE may be coerced to type REQUIRED_TYPE,
+   false otherwise. */
+static bool
+is_coercible (atom_type required_type, union any_node *const *node)
+{
+  return type_coercion_core (NULL, required_type,
+                             (union any_node **) node, NULL, false);
+}
+
+/* How to parse an operator. */
+struct operator
+  {
+    int token;                  /* Token representing operator. */
+    operation_type type;        /* Operation type representing operation. */
+    const char *name;           /* Name of operator. */
+  };
+
+/* Attempts to match the current token against the tokens for the
+   OP_CNT operators in OPS[].  If successful, returns true
+   and, if OPERATOR is non-null, sets *OPERATOR to the operator.
+   On failure, returns false and, if OPERATOR is non-null, sets
+   *OPERATOR to a null pointer. */
+static bool
+match_operator (const struct operator ops[], size_t op_cnt,
+                const struct operator **operator) 
+{
+  const struct operator *op;
+
+  for (op = ops; op < ops + op_cnt; op++)
+    {
+      if (op->token == '-')
+        lex_negative_to_dash ();
+      if (lex_match (op->token)) 
+        {
+          if (operator != NULL)
+            *operator = op;
+          return true;
+        }
+    }
+  if (operator != NULL)
+    *operator = NULL;
+  return false;
+}
+
+static bool
+check_operator (const struct operator *op, int arg_cnt, atom_type arg_type) 
+{
+  const struct operation *o;
+  size_t i;
+
+  assert (op != NULL);
+  o = &operations[op->type];
+  assert (o->arg_cnt == arg_cnt);
+  assert ((o->flags & OPF_ARRAY_OPERAND) == 0);
+  for (i = 0; i < arg_cnt; i++) 
+    assert (o->args[i] == arg_type);
+  return true;
+}
+
+static bool
+check_binary_operators (const struct operator ops[], size_t op_cnt,
+                        atom_type arg_type)
+{
+  size_t i;
+
+  for (i = 0; i < op_cnt; i++)
+    check_operator (&ops[i], 2, arg_type);
+  return true;
+}
+
+static atom_type
+get_operand_type (const struct operator *op) 
+{
+  return operations[op->type].args[0];
+}
+
+/* Parses a chain of left-associative operator/operand pairs.
+   There are OP_CNT operators, specified in OPS[].  The
+   operators' operands must all be the same type.  The next
+   higher level is parsed by PARSE_NEXT_LEVEL.  If CHAIN_WARNING
+   is non-null, then it will be issued as a warning if more than
+   one operator/operand pair is parsed. */
+static union any_node *
+parse_binary_operators (struct expression *e, union any_node *node,
+                        const struct operator ops[], size_t op_cnt,
+                        parse_recursively_func *parse_next_level,
+                        const char *chain_warning)
+{
+  atom_type operand_type = get_operand_type (&ops[0]);
+  int op_count;
+  const struct operator *operator;
+
+  assert (check_binary_operators (ops, op_cnt, operand_type));
+  if (node == NULL)
+    return node;
+
+  for (op_count = 0; match_operator (ops, op_cnt, &operator); op_count++)
+    {
+      union any_node *rhs;
+
+      /* Convert the left-hand side to type OPERAND_TYPE. */
+      if (!type_coercion (e, operand_type, &node, operator->name))
+        return NULL;
+
+      /* Parse the right-hand side and coerce to type
+         OPERAND_TYPE. */
+      rhs = parse_next_level (e);
+      if (!type_coercion (e, operand_type, &rhs, operator->name))
+        return NULL;
+      node = expr_allocate_binary (e, operator->type, node, rhs);
+    }
+
+  if (op_count > 1 && chain_warning != NULL)
+    msg (SW, chain_warning);
+
+  return node;
+}
+
+static union any_node *
+parse_inverting_unary_operator (struct expression *e,
+                                const struct operator *op,
+                                parse_recursively_func *parse_next_level) 
+{
+  union any_node *node;
+  unsigned op_count;
+
+  check_operator (op, 1, get_operand_type (op));
+
+  op_count = 0;
+  while (match_operator (op, 1, NULL))
+    op_count++;
+
+  node = parse_next_level (e);
+  if (op_count > 0
+      && type_coercion (e, get_operand_type (op), &node, op->name)
+      && op_count % 2 != 0)
+    return expr_allocate_unary (e, op->type, node);
+  else
+    return node;
+}
+
+/* Parses the OR level. */
+static union any_node *
+parse_or (struct expression *e)
+{
+  static const struct operator op = 
+    { T_OR, OP_OR, "logical disjunction (\"OR\")" };
+  
+  return parse_binary_operators (e, parse_and (e), &op, 1, parse_and, NULL);
+}
+
+/* Parses the AND level. */
+static union any_node *
+parse_and (struct expression *e)
+{
+  static const struct operator op = 
+    { T_AND, OP_AND, "logical conjunction (\"AND\")" };
+  
+  return parse_binary_operators (e, parse_not (e), &op, 1, parse_not, NULL);
+}
+
+/* Parses the NOT level. */
+static union any_node *
+parse_not (struct expression *e)
+{
+  static const struct operator op
+    = { T_NOT, OP_NOT, "logical negation (\"NOT\")" };
+  return parse_inverting_unary_operator (e, &op, parse_rel);
+}
+
+/* Parse relational operators. */
+static union any_node *
+parse_rel (struct expression *e)
+{
+  const char *chain_warning = 
+    _("Chaining relational operators (e.g. \"a < b < c\") will "
+      "not produce the mathematically expected result.  "
+      "Use the AND logical operator to fix the problem "
+      "(e.g. \"a < b AND b < c\").  "
+      "If chaining is really intended, parentheses will disable "
+      "this warning (e.g. \"(a < b) < c\".)");
+
+  union any_node *node = parse_add (e);
+
+  if (node == NULL)
+    return NULL;
+  
+  switch (expr_node_returns (node)) 
+    {
+    case OP_number:
+    case OP_boolean: 
+      {
+        static const struct operator ops[] =
+          {
+            { '=', OP_EQ, "numeric equality (\"=\")" },
+            { T_EQ, OP_EQ, "numeric equality (\"EQ\")" },
+            { T_GE, OP_GE, "numeric greater-than-or-equal-to (\">=\")" },
+            { T_GT, OP_GT, "numeric greater than (\">\")" },
+            { T_LE, OP_LE, "numeric less-than-or-equal-to (\"<=\")" },
+            { T_LT, OP_LT, "numeric less than (\"<\")" },
+            { T_NE, OP_NE, "numeric inequality (\"<>\")" },
+          };
+
+        return parse_binary_operators (e, node, ops, sizeof ops / sizeof *ops,
+                                       parse_add, chain_warning);
+      }
+      
+    case OP_string:
+      {
+        static const struct operator ops[] =
+          {
+            { '=', OP_EQ_STRING, "string equality (\"=\")" },
+            { T_EQ, OP_EQ_STRING, "string equality (\"EQ\")" },
+            { T_GE, OP_GE_STRING, "string greater-than-or-equal-to (\">=\")" },
+            { T_GT, OP_GT_STRING, "string greater than (\">\")" },
+            { T_LE, OP_LE_STRING, "string less-than-or-equal-to (\"<=\")" },
+            { T_LT, OP_LT_STRING, "string less than (\"<\")" },
+            { T_NE, OP_NE_STRING, "string inequality (\"<>\")" },
+          };
+
+        return parse_binary_operators (e, node, ops, sizeof ops / sizeof *ops,
+                                       parse_add, chain_warning);
+      }
+      
+    default:
+      return node;
+    }
+}
+
+/* Parses the addition and subtraction level. */
+static union any_node *
+parse_add (struct expression *e)
+{
+  static const struct operator ops[] = 
+    {
+      { '+', OP_ADD, "addition (\"+\")" },
+      { '-', OP_SUB, "subtraction (\"-\")" },
+    };
+  
+  return parse_binary_operators (e, parse_mul (e),
+                                 ops, sizeof ops / sizeof *ops,
+                                 parse_mul, NULL);
+}
+
+/* Parses the multiplication and division level. */
+static union any_node *
+parse_mul (struct expression *e)
+{
+  static const struct operator ops[] = 
+    {
+      { '*', OP_MUL, "multiplication (\"*\")" },
+      { '/', OP_DIV, "division (\"/\")" },
+    };
+  
+  return parse_binary_operators (e, parse_neg (e),
+                                 ops, sizeof ops / sizeof *ops,
+                                 parse_neg, NULL);
+}
+
+/* Parses the unary minus level. */
+static union any_node *
+parse_neg (struct expression *e)
+{
+  static const struct operator op = { '-', OP_NEG, "negation (\"-\")" };
+  return parse_inverting_unary_operator (e, &op, parse_exp);
+}
+
+static union any_node *
+parse_exp (struct expression *e)
+{
+  static const struct operator op = 
+    { T_EXP, OP_POW, "exponentiation (\"**\")" };
+  
+  const char *chain_warning = 
+    _("The exponentiation operator (\"**\") is left-associative, "
+      "even though right-associative semantics are more useful.  "
+      "That is, \"a**b**c\" equals \"(a**b)**c\", not as \"a**(b**c)\".  "
+      "To disable this warning, insert parentheses.");
+
+  return parse_binary_operators (e, parse_primary (e), &op, 1,
+                                 parse_primary, chain_warning);
+}
+
+/* Parses system variables. */
+static union any_node *
+parse_sysvar (struct expression *e)
+{
+  if (lex_match_id ("$CASENUM"))
+    return expr_allocate_nullary (e, OP_CASENUM);
+  else if (lex_match_id ("$DATE"))
+    {
+      static const char *months[12] =
+        {
+          "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
+          "JUL", "AUG", "SEP", "OCT", "NOV", "DEC",
+        };
+
+      time_t last_vfm_invocation = vfm_last_invocation ();
+      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);
+
+      return expr_allocate_string_buffer (e, temp_buf, strlen (temp_buf));
+    }
+  else if (lex_match_id ("$TRUE"))
+    return expr_allocate_boolean (e, 1.0);
+  else if (lex_match_id ("$FALSE"))
+    return expr_allocate_boolean (e, 0.0);
+  else if (lex_match_id ("$SYSMIS"))
+    return expr_allocate_number (e, SYSMIS);
+  else if (lex_match_id ("$JDATE"))
+    {
+      time_t time = vfm_last_invocation ();
+      struct tm *tm = localtime (&time);
+      return expr_allocate_number (e, expr_ymd_to_ofs (tm->tm_year + 1900,
+                                                       tm->tm_mon + 1,
+                                                       tm->tm_mday));
+    }
+  else if (lex_match_id ("$TIME"))
+    {
+      time_t time = vfm_last_invocation ();
+      struct tm *tm = localtime (&time);
+      return expr_allocate_number (e,
+                                   expr_ymd_to_date (tm->tm_year + 1900,
+                                                     tm->tm_mon + 1,
+                                                     tm->tm_mday)
+                                   + tm->tm_hour * 60 * 60.
+                                   + tm->tm_min * 60.
+                                   + tm->tm_sec);
+    }
+  else if (lex_match_id ("$LENGTH"))
+    return expr_allocate_number (e, get_viewlength ());
+  else if (lex_match_id ("$WIDTH"))
+    return expr_allocate_number (e, get_viewwidth ());
+  else
+    {
+      msg (SE, _("Unknown system variable %s."), tokid);
+      return NULL;
+    }
+}
+
+/* Parses numbers, varnames, etc. */
+static union any_node *
+parse_primary (struct expression *e)
+{
+  switch (token)
+    {
+    case T_ID:
+      if (lex_look_ahead () == '(') 
+        {
+          /* An identifier followed by a left parenthesis may be
+             a vector element reference.  If not, it's a function
+             call. */
+          if (e->dict != NULL && dict_lookup_vector (e->dict, tokid) != NULL) 
+            return parse_vector_element (e);
+          else
+            return parse_function (e);
+        }
+      else if (tokid[0] == '$')
+        {
+          /* $ at the beginning indicates a system variable. */
+          return parse_sysvar (e);
+        }
+      else if (e->dict != NULL && dict_lookup_var (e->dict, tokid))
+        {
+          /* It looks like a user variable.
+             (It could be a format specifier, but we'll assume
+             it's a variable unless proven otherwise. */
+          return allocate_unary_variable (e, parse_dict_variable (e->dict));
+        }
+      else 
+        {
+          /* Try to parse it as a format specifier. */
+          struct fmt_spec fmt;
+          if (parse_format_specifier (&fmt, FMTP_SUPPRESS_ERRORS))
+            return expr_allocate_format (e, &fmt);
+
+          /* All attempts failed. */
+          msg (SE, _("Unknown identifier %s."), tokid);
+          return NULL;
+        }
+      break;
+      
+    case T_POS_NUM: 
+    case T_NEG_NUM: 
+      {
+        union any_node *node = expr_allocate_number (e, tokval);
+        lex_get ();
+        return node; 
+      }
+
+    case T_STRING:
+      {
+        union any_node *node = expr_allocate_string_buffer (e, ds_c_str (&tokstr),
+                                                       ds_length (&tokstr));
+       lex_get ();
+       return node;
+      }
+
+    case '(':
+      {
+        union any_node *node;
+       lex_get ();
+       node = parse_or (e);
+       if (node != NULL && !lex_match (')'))
+         {
+           lex_error (_("expecting `)'"));
+            return NULL;
+         }
+        return node;
+      }
+
+    default:
+      lex_error (_("in expression"));
+      return NULL;
+    }
+}
+
+static union any_node *
+parse_vector_element (struct expression *e)
+{
+  const struct vector *vector;
+  union any_node *element;
+
+  /* Find vector, skip token.
+     The caller must already have verified that the current token
+     is the name of a vector. */
+  vector = dict_lookup_vector (default_dict, tokid);
+  assert (vector != NULL);
+  lex_get ();
+
+  /* Skip left parenthesis token.
+     The caller must have verified that the lookahead is a left
+     parenthesis. */
+  assert (token == '(');
+  lex_get ();
+
+  element = parse_or (e);
+  if (!type_coercion (e, OP_number, &element, "vector indexing")
+      || !lex_match (')'))
+    return NULL;
+
+  return expr_allocate_binary (e, (vector->var[0]->type == NUMERIC
+                                   ? OP_VEC_ELEM_NUM : OP_VEC_ELEM_STR),
+                               element, expr_allocate_vector (e, vector));
+}
+\f
+/* Individual function parsing. */
+
+struct operation operations[OP_first + OP_cnt] = {
+#include "parse.inc"
+};
+    
+static bool
+word_matches (const char **test, const char **name) 
+{
+  size_t test_len = strcspn (*test, ".");
+  size_t name_len = strcspn (*name, ".");
+  if (test_len == name_len) 
+    {
+      if (buf_compare_case (*test, *name, test_len))
+        return false;
+    }
+  else if (test_len < 3 || test_len > name_len)
+    return false;
+  else 
+    {
+      if (buf_compare_case (*test, *name, test_len))
+        return false;
+    }
+
+  *test += test_len;
+  *name += name_len;
+  if (**test != **name)
+    return false;
+
+  if (**test == '.')
+    {
+      (*test)++;
+      (*name)++;
+    }
+  return true;
+}
+
+static int
+compare_names (const char *test, const char *name) 
+{
+  for (;;) 
+    {
+      if (!word_matches (&test, &name))
+        return true;
+      if (*name == '\0' && *test == '\0')
+        return false;
+    }
+}
+
+static int
+compare_strings (const char *test, const char *name) 
+{
+  return strcasecmp (test, name);
+}
+
+static bool
+lookup_function_helper (const char *name,
+                        int (*compare) (const char *test, const char *name),
+                        const struct operation **first,
+                        const struct operation **last)
+{
+  struct operation *f;
+  
+  for (f = operations + OP_function_first;
+       f <= operations + OP_function_last; f++) 
+    if (!compare (name, f->name)) 
+      {
+        *first = f;
+
+        while (f <= operations + OP_function_last && !compare (name, f->name))
+          f++;
+        *last = f;
+
+        return true;
+      }  
+
+  return false;
+}
+
+static bool
+lookup_function (const char *name,
+                 const struct operation **first,
+                 const struct operation **last) 
+{
+  *first = *last = NULL;
+  return (lookup_function_helper (name, compare_strings, first, last)
+          || lookup_function_helper (name, compare_names, first, last));
+}
+
+static int
+extract_min_valid (char *s) 
+{
+  char *p = strrchr (s, '.');
+  if (p == NULL
+      || p[1] < '0' || p[1] > '9'
+      || strspn (p + 1, "0123456789") != strlen (p + 1))
+    return -1;
+  *p = '\0';
+  return atoi (p + 1);
+}
+
+static atom_type
+function_arg_type (const struct operation *f, size_t arg_idx) 
+{
+  assert (arg_idx < f->arg_cnt || (f->flags & OPF_ARRAY_OPERAND));
+
+  return f->args[arg_idx < f->arg_cnt ? arg_idx : f->arg_cnt - 1];
+}
+
+static bool
+match_function (union any_node **args, int arg_cnt, const struct operation *f)
+{
+  size_t i;
+
+  if (arg_cnt < f->arg_cnt
+      || (arg_cnt > f->arg_cnt && (f->flags & OPF_ARRAY_OPERAND) == 0)
+      || arg_cnt - (f->arg_cnt - 1) < f->array_min_elems)
+    return false;
+
+  for (i = 0; i < arg_cnt; i++)
+    if (!is_coercible (function_arg_type (f, i), &args[i]))
+      return false; 
+
+  return true;
+}
+
+static void
+coerce_function_args (struct expression *e, const struct operation *f,
+                      union any_node **args, size_t arg_cnt) 
+{
+  int i;
+  
+  for (i = 0; i < arg_cnt; i++)
+    type_coercion_assert (e, function_arg_type (f, i), &args[i]);
+}
+
+static bool
+validate_function_args (const struct operation *f, int arg_cnt, int min_valid) 
+{
+  int array_arg_cnt = arg_cnt - (f->arg_cnt - 1);
+  if (array_arg_cnt < f->array_min_elems) 
+    {
+      msg (SE, _("%s must have at least %d arguments in list."),
+           f->prototype, f->array_min_elems);
+      return false;
+    }
+
+  if ((f->flags & OPF_ARRAY_OPERAND)
+      && array_arg_cnt % f->array_granularity != 0) 
+    {
+      if (f->array_granularity == 2)
+        msg (SE, _("%s must have even number of arguments in list."),
+             f->prototype);
+      else
+        msg (SE, _("%s must have multiple of %d arguments in list."),
+             f->prototype, f->array_granularity);
+      return false;
+    }
+  
+  if (min_valid != -1) 
+    {
+      if (f->array_min_elems == 0) 
+        {
+          assert ((f->flags & OPF_MIN_VALID) == 0);
+          msg (SE, _("%s function does not accept a minimum valid "
+                     "argument count."), f->prototype);
+          return false;
+        }
+      else 
+        {
+          assert (f->flags & OPF_MIN_VALID);
+          if (array_arg_cnt < f->array_min_elems)
+            {
+              msg (SE, _("%s requires at least %d valid arguments in list."),
+                   f->prototype, f->array_min_elems);
+              return false;
+            }
+          else if (min_valid > array_arg_cnt) 
+            {
+              msg (SE, _("With %s, "
+                         "using minimum valid argument count of %d "
+                         "does not make sense when passing only %d "
+                         "arguments in list."),
+                   f->prototype, min_valid, array_arg_cnt);
+              return false;
+            }
+        }
+    }
+
+  return true;
+}
+
+static void
+add_arg (union any_node ***args, int *arg_cnt, int *arg_cap,
+         union any_node *arg)
+{
+  if (*arg_cnt >= *arg_cap) 
+    {
+      *arg_cap += 8;
+      *args = xrealloc (*args, sizeof **args * *arg_cap);
+    }
+
+  (*args)[(*arg_cnt)++] = arg;
+}
+
+static void
+put_invocation (struct string *s,
+                const char *func_name, union any_node **args, size_t arg_cnt) 
+{
+  size_t i;
+
+  ds_printf (s, "%s(", func_name);
+  for (i = 0; i < arg_cnt; i++)
+    {
+      if (i > 0)
+        ds_puts (s, ", ");
+      ds_puts (s, operations[expr_node_returns (args[i])].prototype);
+    }
+  ds_putc (s, ')');
+}
+
+static void
+no_match (const char *func_name,
+          union any_node **args, size_t arg_cnt,
+          const struct operation *first, const struct operation *last) 
+{
+  struct string s;
+  const struct operation *f;
+
+  ds_init (&s, 128);
+
+  if (last - first == 1) 
+    {
+      ds_printf (&s, _("Type mismatch invoking %s as "), first->prototype);
+      put_invocation (&s, func_name, args, arg_cnt);
+    }
+  else 
+    {
+      ds_puts (&s, _("Function invocation "));
+      put_invocation (&s, func_name, args, arg_cnt);
+      ds_puts (&s, _(" does not match any known function.  Candidates are:"));
+
+      for (f = first; f < last; f++)
+        ds_printf (&s, "\n%s", f->prototype);
+    }
+  ds_putc (&s, '.');
+
+  msg (SE, "%s", ds_c_str (&s));
+    
+  ds_destroy (&s);
+}
+
+static union any_node *
+parse_function (struct expression *e)
+{
+  int min_valid;
+  const struct operation *f, *first, *last;
+
+  union any_node **args = NULL;
+  int arg_cnt = 0;
+  int arg_cap = 0;
+
+  struct fixed_string func_name;
+
+  union any_node *n;
+
+  ls_create (&func_name, ds_c_str (&tokstr));
+  min_valid = extract_min_valid (ds_c_str (&tokstr));
+  if (!lookup_function (ds_c_str (&tokstr), &first, &last)) 
+    {
+      msg (SE, _("No function or vector named %s."), ds_c_str (&tokstr));
+      ls_destroy (&func_name);
+      return NULL;
+    }
+
+  lex_get ();
+  if (!lex_force_match ('(')) 
+    {
+      ls_destroy (&func_name);
+      return NULL; 
+    }
+  
+  args = NULL;
+  arg_cnt = arg_cap = 0;
+  if (token != ')')
+    for (;;)
+      {
+        if (token == T_ID && lex_look_ahead () == 'T')
+          {
+            struct variable **vars;
+            size_t var_cnt;
+            size_t i;
+
+            if (!parse_variables (default_dict, &vars, &var_cnt, PV_SINGLE))
+              goto fail;
+            for (i = 0; i < var_cnt; i++)
+              add_arg (&args, &arg_cnt, &arg_cap,
+                       allocate_unary_variable (e, vars[i]));
+            free (vars);
+          }
+        else
+          {
+            union any_node *arg = parse_or (e);
+            if (arg == NULL)
+              goto fail;
+
+            add_arg (&args, &arg_cnt, &arg_cap, arg);
+          }
+        if (lex_match (')'))
+          break;
+        else if (!lex_match (','))
+          {
+            lex_error (_("expecting `,' or `)' invoking %s function"),
+                       first->name);
+            goto fail;
+          }
+      }
+
+  for (f = first; f < last; f++)
+    if (match_function (args, arg_cnt, f))
+      break;
+  if (f >= last) 
+    {
+      no_match (ls_c_str (&func_name), args, arg_cnt, first, last);
+      goto fail;
+    }
+
+  coerce_function_args (e, f, args, arg_cnt);
+  if (!validate_function_args (f, arg_cnt, min_valid))
+    goto fail;
+
+  if ((f->flags & OPF_EXTENSION) && get_syntax () == COMPATIBLE)
+    msg (SW, _("%s is a PSPP extension."), f->prototype);
+  if (f->flags & OPF_UNIMPLEMENTED) 
+    {
+      msg (SE, _("%s is not yet implemented."), f->prototype);
+      goto fail;
+    }
+  
+  n = expr_allocate_composite (e, f - operations, args, arg_cnt);
+  n->composite.min_valid = min_valid != -1 ? min_valid : f->array_min_elems; 
+
+  if (n->type == OP_LAG_Vn || n->type == OP_LAG_Vs) 
+    {
+      if (n_lag < 1)
+        n_lag = 1; 
+    }
+  else if (n->type == OP_LAG_Vnn || n->type == OP_LAG_Vsn)
+    {
+      int n_before;
+      assert (n->composite.arg_cnt == 2);
+      assert (n->composite.args[1]->type == OP_pos_int);
+      n_before = n->composite.args[1]->integer.i;
+      if (n_lag < n_before)
+        n_lag = n_before;
+    }
+  
+  free (args);
+  ls_destroy (&func_name);
+  return n;
+
+fail:
+  free (args);
+  ls_destroy (&func_name);
+  return NULL;
+}
+\f
+/* Utility functions. */
+
+static struct expression *
+expr_create (struct dictionary *dict)
+{
+  struct pool *pool = pool_create ();
+  struct expression *e = pool_alloc (pool, sizeof *e);
+  e->expr_pool = pool;
+  e->dict = dict;
+  e->eval_pool = pool_create_subpool (e->expr_pool);
+  e->ops = NULL;
+  e->op_types = NULL;
+  e->op_cnt = e->op_cap = 0;
+  return e;
+}
+
+atom_type
+expr_node_returns (const union any_node *n)
+{
+  assert (n != NULL);
+  assert (is_operation (n->type));
+  if (is_atom (n->type)) 
+    return n->type;
+  else if (is_composite (n->type))
+    return operations[n->type].returns;
+  else
+    abort ();
+}
+
+static const char *
+atom_type_name (atom_type type)
+{
+  assert (is_atom (type));
+  return operations[type].name;
+}
+
+union any_node *
+expr_allocate_nullary (struct expression *e, operation_type op)
+{
+  return expr_allocate_composite (e, op, NULL, 0);
+}
+
+union any_node *
+expr_allocate_unary (struct expression *e, operation_type op,
+                     union any_node *arg0)
+{
+  return expr_allocate_composite (e, op, &arg0, 1);
+}
+
+union any_node *
+expr_allocate_binary (struct expression *e, operation_type op,
+                      union any_node *arg0, union any_node *arg1)
+{
+  union any_node *args[2];
+  args[0] = arg0;
+  args[1] = arg1;
+  return expr_allocate_composite (e, op, args, 2);
+}
+
+static bool
+is_valid_node (union any_node *n) 
+{
+  struct operation *op;
+  size_t i;
+  
+  assert (n != NULL);
+  assert (is_operation (n->type));
+  op = &operations[n->type];
+  
+  if (!is_atom (n->type))
+    {
+      struct composite_node *c = &n->composite;
+      
+      assert (is_composite (n->type));
+      assert (c->arg_cnt >= op->arg_cnt);
+      for (i = 0; i < op->arg_cnt; i++) 
+        assert (expr_node_returns (c->args[i]) == op->args[i]);
+      if (c->arg_cnt > op->arg_cnt && !is_operator (n->type)) 
+        {
+          assert (op->flags & OPF_ARRAY_OPERAND);
+          for (i = 0; i < c->arg_cnt; i++)
+            assert (operations[c->args[i]->type].returns
+                    == op->args[op->arg_cnt - 1]);
+        }
+    }
+
+  return true; 
+}
+
+union any_node *
+expr_allocate_composite (struct expression *e, operation_type op,
+                         union any_node **args, size_t arg_cnt)
+{
+  union any_node *n;
+  size_t i;
+
+  n = pool_alloc (e->expr_pool, sizeof n->composite);
+  n->type = op;
+  n->composite.arg_cnt = arg_cnt;
+  n->composite.args = pool_alloc (e->expr_pool,
+                                  sizeof *n->composite.args * arg_cnt);
+  for (i = 0; i < arg_cnt; i++) 
+    {
+      if (args[i] == NULL)
+        return NULL;
+      n->composite.args[i] = args[i];
+    }
+  memcpy (n->composite.args, args, sizeof *n->composite.args * arg_cnt);
+  n->composite.min_valid = 0;
+  assert (is_valid_node (n));
+  return n;
+}
+
+union any_node *
+expr_allocate_number (struct expression *e, double d)
+{
+  union any_node *n = pool_alloc (e->expr_pool, sizeof n->number);
+  n->type = OP_number;
+  n->number.n = d;
+  return n;
+}
+
+union any_node *
+expr_allocate_boolean (struct expression *e, double b)
+{
+  union any_node *n = pool_alloc (e->expr_pool, sizeof n->number);
+  assert (b == 0.0 || b == 1.0 || b == SYSMIS);
+  n->type = OP_boolean;
+  n->number.n = b;
+  return n;
+}
+
+union any_node *
+expr_allocate_integer (struct expression *e, int i)
+{
+  union any_node *n = pool_alloc (e->expr_pool, sizeof n->integer);
+  n->type = OP_integer;
+  n->integer.i = i;
+  return n;
+}
+
+union any_node *
+expr_allocate_pos_int (struct expression *e, int i)
+{
+  union any_node *n = pool_alloc (e->expr_pool, sizeof n->integer);
+  assert (i > 0);
+  n->type = OP_pos_int;
+  n->integer.i = i;
+  return n;
+}
+
+union any_node *
+expr_allocate_vector (struct expression *e, const struct vector *vector)
+{
+  union any_node *n = pool_alloc (e->expr_pool, sizeof n->vector);
+  n->type = OP_vector;
+  n->vector.v = vector;
+  return n;
+}
+
+union any_node *
+expr_allocate_string_buffer (struct expression *e,
+                             const char *string, size_t length)
+{
+  union any_node *n = pool_alloc (e->expr_pool, sizeof n->string);
+  n->type = OP_string;
+  if (length > 255)
+    length = 255;
+  n->string.s = copy_string (e, string, length);
+  return n;
+}
+
+union any_node *
+expr_allocate_string (struct expression *e, struct fixed_string s)
+{
+  union any_node *n = pool_alloc (e->expr_pool, sizeof n->string);
+  n->type = OP_string;
+  n->string.s = s;
+  return n;
+}
+
+union any_node *
+expr_allocate_variable (struct expression *e, struct variable *v)
+{
+  union any_node *n = pool_alloc (e->expr_pool, sizeof n->variable);
+  n->type = v->type == NUMERIC ? OP_num_var : OP_str_var;
+  n->variable.v = v;
+  return n;
+}
+
+union any_node *
+expr_allocate_format (struct expression *e, const struct fmt_spec *format)
+{
+  union any_node *n = pool_alloc (e->expr_pool, sizeof n->format);
+  n->type = OP_format;
+  n->format.f = *format;
+  return n;
+}
+
+/* Allocates a unary composite node that represents the value of
+   variable V in expression E. */
+static union any_node *
+allocate_unary_variable (struct expression *e, struct variable *v) 
+{
+  assert (v != NULL);
+  return expr_allocate_unary (e, v->type == NUMERIC ? OP_NUM_VAR : OP_STR_VAR,
+                              expr_allocate_variable (e, v));
+}
diff --git a/src/language/expressions/parse.inc.pl b/src/language/expressions/parse.inc.pl
new file mode 100644 (file)
index 0000000..ea878c9
--- /dev/null
@@ -0,0 +1,73 @@
+do 'generate.pl';
+
+sub generate_output {
+    my (@members) = ("\"\"", "\"\"", 0, 0, 0, "{}", 0, 0);
+    print "{", join (', ', @members), "},\n";
+
+    for my $type (@types) {
+       next if $type->{ROLE} eq 'fixed';
+
+       my (@members) = ("\"$type->{NAME}\"", "\"$type->{HUMAN_NAME}\"",
+                        0, "OP_$type->{NAME}", 0, "{}", 0, 0);
+       print "{", join (', ', @members), "},\n";
+    }
+
+    for my $opname (@order) {
+       my ($op) = $ops{$opname};
+
+       my (@members);
+
+       push (@members, "\"$op->{NAME}\"");
+
+       if ($op->{CATEGORY} eq 'function') {
+           my (@args, @opt_args);
+           for my $arg (@{$op->{ARGS}}) {
+               push (@args, $arg->{TYPE}{HUMAN_NAME}) if !defined $arg->{IDX};
+           }
+
+           if (my ($array) = array_arg ($op)) {
+               if (!defined $op->{MIN_VALID}) {
+                   my (@array_args);
+                   for (my $i = 0; $i < $array->{TIMES}; $i++) {
+                       push (@array_args, $array->{TYPE}{HUMAN_NAME});
+                   }
+                   push (@args, @array_args);
+                   @opt_args = @array_args;
+               } else {
+                   for (my $i = 0; $i < $op->{MIN_VALID}; $i++) {
+                       push (@args, $array->{TYPE}{HUMAN_NAME});
+                   }
+                   push (@opt_args, $array->{TYPE}{HUMAN_NAME});
+               }
+           }
+           my ($human) = "$op->{NAME}(" . join (', ', @args);
+           $human .= '[, ' . join (', ', @opt_args) . ']...' if @opt_args;
+           $human .= ')';
+           push (@members, "\"$human\"");
+       } else {
+           push (@members, "NULL");
+       }
+
+       my (@flags);
+       push (@flags, "OPF_ABSORB_MISS") if defined $op->{ABSORB_MISS};
+       push (@flags, "OPF_ARRAY_OPERAND") if array_arg ($op);
+       push (@flags, "OPF_MIN_VALID") if defined $op->{MIN_VALID};
+       push (@flags, "OPF_NONOPTIMIZABLE") if !$op->{OPTIMIZABLE};
+       push (@flags, "OPF_EXTENSION") if $op->{EXTENSION};
+       push (@flags, "OPF_UNIMPLEMENTED") if $op->{UNIMPLEMENTED};
+       push (@members, @flags ? join (' | ', @flags) : 0);
+
+       push (@members, "OP_$op->{RETURNS}{NAME}");
+
+       push (@members, scalar (@{$op->{ARGS}}));
+
+       my (@arg_types) = map ("OP_$_->{TYPE}{NAME}", @{$op->{ARGS}});
+       push (@members, "{" . join (', ', @arg_types) . "}");
+
+       push (@members, $op->{MIN_VALID} || 0);
+
+       push (@members, array_arg ($op) ? ${array_arg ($op)}{TIMES} : 0);
+
+       print "{", join (', ', @members), "},\n";
+    }
+}
diff --git a/src/language/expressions/private.h b/src/language/expressions/private.h
new file mode 100644 (file)
index 0000000..85b8258
--- /dev/null
@@ -0,0 +1,197 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef EXPRESSIONS_PRIVATE_H 
+#define EXPRESSIONS_PRIVATE_H
+
+#include <assert.h>
+#include <stddef.h>
+#include "format.h"
+#include "str.h"
+
+#include "public.h"
+#include "operations.h"
+
+enum operation_flags 
+  {
+    /* Most operations produce a missing output value if any
+       input value is missing.  Setting this bit indicates that
+       this operation may produce a non-missing result given
+       missing input values (although it is not obliged to do
+       so).  Unless this bit is set, the operation's evaluation
+       function will never be passed a missing argument. */
+    OPF_ABSORB_MISS = 004,
+
+    /* If set, this operation's final operand is an array of one
+       or more elements. */
+    OPF_ARRAY_OPERAND = 001,
+
+    /* If set, the user can specify the minimum number of array
+       elements that must be non-missing for the function result
+       to be non-missing.  The operation must have an array
+       operand and the array must contain `double's.  Both
+       OPF_ABSORB_MISS and OPF_ARRAY_OPERAND must also be set. */
+    OPF_MIN_VALID = 002,     
+
+    /* If set, operation is non-optimizable in general.  Unless
+       combined with OPF_ABSORB_MISS, missing input values are
+       still assumed to yield missing results. */
+    OPF_NONOPTIMIZABLE = 010,
+
+    /* If set, this operation is not implemented. */
+    OPF_UNIMPLEMENTED = 020,
+
+    /* If set, this operation is a PSPP extension. */
+    OPF_EXTENSION = 040
+  };
+
+#define EXPR_ARG_MAX 4
+struct operation
+  {
+    const char *name;
+    const char *prototype;
+    enum operation_flags flags;
+    atom_type returns;
+    int arg_cnt;
+    atom_type args[EXPR_ARG_MAX];
+    int array_min_elems;
+    int array_granularity;
+  };
+
+extern struct operation operations[];
+
+/* Tree structured expressions. */ 
+
+/* Atoms. */
+struct number_node 
+  {
+    operation_type type;   /* OP_number. */
+    double n;
+  };
+
+struct string_node
+  {
+    operation_type type;   /* OP_string. */
+    struct fixed_string s;
+  };
+
+struct variable_node
+  {
+    operation_type type;   /* OP_variable. */
+    struct variable *v;
+  };
+
+struct integer_node
+  {
+    operation_type type;   /* OP_integer. */
+    int i;
+  };
+
+struct vector_node
+  {
+    operation_type type;   /* OP_vector. */
+    const struct vector *v;
+  };
+
+struct format_node
+  {
+    operation_type type;   /* OP_format. */
+    struct fmt_spec f;
+  };
+
+/* Any composite node. */
+struct composite_node
+  {
+    operation_type type;   /* One of OP_*. */
+    size_t arg_cnt;             /* Number of arguments. */
+    union any_node **args;     /* Arguments. */
+    size_t min_valid;           /* Min valid array args to get valid result. */
+  };
+
+/* Any node. */
+union any_node
+  {
+    operation_type type;
+    struct number_node number;
+    struct string_node string;
+    struct variable_node variable;
+    struct integer_node integer;
+    struct vector_node vector;
+    struct format_node format;
+    struct composite_node composite;
+  };
+
+union operation_data 
+  {
+    operation_type operation;
+    double number;
+    struct fixed_string string;
+    struct variable *variable;
+    const struct vector *vector;
+    struct fmt_spec *format;
+    int integer;
+  };
+
+/* An expression. */
+struct expression
+  {
+    struct pool *expr_pool;     /* Pool for expression static data. */
+    struct dictionary *dict;    /* Dictionary for variables, vectors. */
+    atom_type type;             /* Type of expression result. */
+
+    union operation_data *ops;  /* Expression data. */
+    operation_type *op_types;   /* ops[] element types (for debugging). */
+    size_t op_cnt, op_cap;      /* Number of ops, amount of allocated space. */
+
+    double *number_stack;       /* Evaluation stack: numerics, Booleans. */
+    struct fixed_string *string_stack; /* Evaluation stack: strings. */
+    struct pool *eval_pool;     /* Pool for evaluation temporaries. */
+  };
+
+struct expression *expr_parse_any (struct dictionary *, bool optimize);
+void expr_debug_print_postfix (const struct expression *);
+
+union any_node *expr_optimize (union any_node *, struct expression *);
+void expr_flatten (union any_node *, struct expression *);
+
+atom_type expr_node_returns (const union any_node *);
+
+union any_node *expr_allocate_nullary (struct expression *e, operation_type);
+union any_node *expr_allocate_unary (struct expression *e,
+                                     operation_type, union any_node *);
+union any_node *expr_allocate_binary (struct expression *e, operation_type,
+                                 union any_node *, union any_node *);
+union any_node *expr_allocate_composite (struct expression *e, operation_type,
+                                         union any_node **, size_t);
+union any_node *expr_allocate_number (struct expression *e, double);
+union any_node *expr_allocate_boolean (struct expression *e, double);
+union any_node *expr_allocate_integer (struct expression *e, int);
+union any_node *expr_allocate_pos_int (struct expression *e, int);
+union any_node *expr_allocate_string_buffer (struct expression *e,
+                                             const char *string, size_t length);
+union any_node *expr_allocate_string (struct expression *e,
+                                      struct fixed_string);
+union any_node *expr_allocate_variable (struct expression *e,
+                                        struct variable *);
+union any_node *expr_allocate_format (struct expression *e,
+                                 const struct fmt_spec *);
+union any_node *expr_allocate_vector (struct expression *e,
+                                      const struct vector *);
+
+#endif /* expressions/private.h */
diff --git a/src/language/expressions/public.h b/src/language/expressions/public.h
new file mode 100644 (file)
index 0000000..9a90cef
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !expr_h
+#define expr_h 1
+
+#include <stddef.h>
+
+/* Expression parsing flags. */
+enum expr_type
+  {
+    EXPR_NUMBER = 0xf000,       /* Number. */
+    EXPR_STRING,                /* String. */
+    EXPR_BOOLEAN,               /* Boolean (number limited to 0, 1, SYSMIS). */
+  };
+
+struct dictionary;
+struct expression;
+struct ccase;
+struct pool;
+union value;
+
+struct expression *expr_parse (struct dictionary *, enum expr_type);
+struct expression *expr_parse_pool (struct pool *,
+                                    struct dictionary *, enum expr_type);
+void expr_free (struct expression *);
+
+double expr_evaluate_num (struct expression *, const struct ccase *,
+                          int case_idx);
+void expr_evaluate_str (struct expression *, const struct ccase *,
+                        int case_idx, char *dst, size_t dst_size);
+
+#endif /* expr.h */
diff --git a/src/language/lexer/ChangeLog b/src/language/lexer/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/language/lexer/format-parser.c b/src/language/lexer/format-parser.c
new file mode 100644 (file)
index 0000000..b3ccba5
--- /dev/null
@@ -0,0 +1,157 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "format.h"
+#include <ctype.h>
+#include "message.h"
+#include <stdlib.h>
+#include "message.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+
+/* Parses the alphabetic prefix of the current token as a format
+   specifier name.  Returns the corresponding format specifier
+   type if successful, or -1 on failure.  If ALLOW_XT is zero,
+   then X and T format specifiers are not allowed.  If CP is
+   nonzero, then *CP is set to the first non-alphabetic character
+   in the current token on success or to a null pointer on
+   failure. */
+int
+parse_format_specifier_name (const char **cp, enum fmt_parse_flags flags)
+{
+  char *sp, *ep;
+  int idx;
+
+  sp = ep = ds_c_str (&tokstr);
+  while (isalpha ((unsigned char) *ep))
+    ep++;
+
+  if (sp != ep) 
+    {
+      /* Find format. */
+      for (idx = 0; idx < FMT_NUMBER_OF_FORMATS; idx++)
+        if (strlen (formats[idx].name) == ep - sp
+            && !buf_compare_case (formats[idx].name, sp, ep - sp))
+          break;
+
+      /* Check format. */
+      if (idx < FMT_NUMBER_OF_FORMATS)
+        {
+          if (!(flags & FMTP_ALLOW_XT) && (idx == FMT_T || idx == FMT_X)) 
+            {
+              if (!(flags & FMTP_SUPPRESS_ERRORS))
+                msg (SE, _("X and T format specifiers not allowed here."));
+              idx = -1; 
+            }
+        }
+      else 
+        {
+          /* No match. */
+          if (!(flags & FMTP_SUPPRESS_ERRORS))
+            msg (SE, _("%.*s is not a valid data format."),
+                 (int) (ep - sp), ds_c_str (&tokstr));
+          idx = -1; 
+        }
+    }
+  else 
+    {
+      lex_error ("expecting data format");
+      idx = -1;
+    }
+      
+  if (cp != NULL) 
+    {
+      if (idx != -1)
+        *cp = ep;
+      else
+        *cp = NULL;
+    }
+
+  return idx;
+}
+
+
+/* Parses a format specifier from the token stream and returns
+   nonzero only if successful.  Emits an error message on
+   failure.  Allows X and T format specifiers only if ALLOW_XT is
+   nonzero.  The caller should call check_input_specifier() or
+   check_output_specifier() on the parsed format as
+   necessary.  */
+int
+parse_format_specifier (struct fmt_spec *input, enum fmt_parse_flags flags)
+{
+  struct fmt_spec spec;
+  struct fmt_desc *f;
+  const char *cp;
+  char *cp2;
+  int type, w, d;
+
+  if (token != T_ID)
+    {
+      if (!(flags & FMTP_SUPPRESS_ERRORS))
+        msg (SE, _("Format specifier expected."));
+      return 0;
+    }
+  type = parse_format_specifier_name (&cp, flags);
+  if (type == -1)
+    return 0;
+  f = &formats[type];
+
+  w = strtol (cp, &cp2, 10);
+  if (cp2 == cp && type != FMT_X)
+    {
+      if (!(flags & FMTP_SUPPRESS_ERRORS))
+        msg (SE, _("Data format %s does not specify a width."),
+             ds_c_str (&tokstr));
+      return 0;
+    }
+
+  cp = cp2;
+  if (f->n_args > 1 && *cp == '.')
+    {
+      cp++;
+      d = strtol (cp, &cp2, 10);
+      cp = cp2;
+    }
+  else
+    d = 0;
+
+  if (*cp)
+    {
+      if (!(flags & FMTP_SUPPRESS_ERRORS))
+        msg (SE, _("Data format %s is not valid."), ds_c_str (&tokstr));
+      return 0;
+    }
+  lex_get ();
+
+  spec.type = type;
+  spec.w = w;
+  spec.d = d;
+  *input = spec;
+
+  return 1;
+}
+
diff --git a/src/language/lexer/lexer.c b/src/language/lexer/lexer.c
new file mode 100644 (file)
index 0000000..391fa79
--- /dev/null
@@ -0,0 +1,1217 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "lexer.h"
+#include "message.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 "message.h"
+#include "line-buffer.h"
+#include "magic.h"
+#include "settings.h"
+#include "str.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+/*
+#define DUMP_TOKENS 1
+*/
+
+\f
+/* Global variables. */
+
+extern const char *keywords[T_N_KEYWORDS + 1];
+
+
+/* Current token. */
+int token;
+
+/* T_POS_NUM, T_NEG_NUM: the token's value. */
+double tokval;
+
+/* T_ID: the identifier. */
+char tokid[LONG_NAME_LEN + 1];
+
+/* T_ID, T_STRING: token string value.
+   For T_ID, this is not truncated as is tokid. */
+struct string tokstr;
+\f
+/* Static variables. */
+
+/* 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_STOP. */
+static int eof;
+
+/* If nonzero, next token returned by lex_get().
+   Used only in exceptional circumstances. */
+static int put_token;
+static struct string put_tokstr;
+static double put_tokval;
+
+static int parse_id (void);
+
+/* How a string represents its contents. */
+enum string_type 
+  {
+    CHARACTER_STRING,   /* Characters. */
+    BINARY_STRING,      /* Binary digits. */
+    OCTAL_STRING,       /* Octal digits. */
+    HEX_STRING          /* Hexadecimal digits. */
+  };
+
+static void convert_numeric_string_to_char_string (enum string_type);
+static int parse_string (enum string_type);
+
+#if DUMP_TOKENS
+static void dump_token (void);
+#endif
+\f
+/* Initialization. */
+
+/* Initializes the lexer. */
+void
+lex_init (void)
+{
+  ds_init (&tokstr, 64);
+  ds_init (&put_tokstr, 64);
+  if (!lex_get_line ())
+    eof = true;
+}
+
+void
+lex_done (void)
+{
+  ds_destroy (&put_tokstr);
+  ds_destroy (&tokstr);
+}
+
+\f
+/* Common functions. */
+
+/* Copies put_token, put_tokstr, put_tokval into token, tokstr,
+   tokval, respectively, and sets tokid appropriately. */
+static void
+restore_token (void) 
+{
+  assert (put_token != 0);
+  token = put_token;
+  ds_replace (&tokstr, ds_c_str (&put_tokstr));
+  str_copy_trunc (tokid, sizeof tokid, ds_c_str (&tokstr));
+  tokval = put_tokval;
+  put_token = 0;
+}
+
+/* Copies token, tokstr, tokval into put_token, put_tokstr,
+   put_tokval respectively. */
+static void
+save_token (void) 
+{
+  put_token = token;
+  ds_replace (&put_tokstr, ds_c_str (&tokstr));
+  put_tokval = tokval;
+}
+
+/* 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)
+    {
+      restore_token ();
+#if DUMP_TOKENS
+      dump_token ();
+#endif
+      return;
+    }
+
+  /* Find a token. */
+  for (;;)
+    {
+      /* Skip whitespace. */
+      if (eof) 
+        {
+          token = T_STOP;
+          return;
+        }
+
+      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)
+           {
+              restore_token ();
+#if DUMP_TOKENS
+             dump_token ();
+#endif
+             return;
+           }
+       }
+
+
+      /* Actually parse the token. */
+      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 (*prog == '-')
+             {
+               ds_putc (&tokstr, *prog++);
+               while (isspace ((unsigned char) *prog))
+                 prog++;
+
+               if (!isdigit ((unsigned char) *prog) && *prog != '.')
+                 {
+                   token = '-';
+                   break;
+                 }
+                token = T_NEG_NUM;
+             }
+            else 
+              token = T_POS_NUM;
+                
+           /* Parse the number, copying it into tokstr. */
+           while (isdigit ((unsigned char) *prog))
+             ds_putc (&tokstr, *prog++);
+           if (*prog == '.')
+             {
+               ds_putc (&tokstr, *prog++);
+               while (isdigit ((unsigned char) *prog))
+                 ds_putc (&tokstr, *prog++);
+             }
+           if (*prog == 'e' || *prog == 'E')
+             {
+               ds_putc (&tokstr, *prog++);
+               if (*prog == '+' || *prog == '-')
+                 ds_putc (&tokstr, *prog++);
+               while (isdigit ((unsigned char) *prog))
+                 ds_putc (&tokstr, *prog++);
+             }
+
+           /* Parse as floating point. */
+           tokval = strtod (ds_c_str (&tokstr), &tail);
+           if (*tail)
+             {
+               msg (SE, _("%s does not form a valid number."),
+                    ds_c_str (&tokstr));
+               tokval = 0.0;
+
+               ds_clear (&tokstr);
+               ds_putc (&tokstr, '0');
+             }
+
+           break;
+         }
+
+       case '\'': case '"':
+         token = parse_string (CHARACTER_STRING);
+         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 'b': case 'B':
+          if (prog[1] == '\'' || prog[1] == '"')
+            token = parse_string (BINARY_STRING);
+          else
+            token = parse_id ();
+          break;
+          
+        case 'o': case 'O':
+          if (prog[1] == '\'' || prog[1] == '"')
+            token = parse_string (OCTAL_STRING);
+          else
+            token = parse_id ();
+          break;
+          
+        case 'x': case 'X':
+          if (prog[1] == '\'' || prog[1] == '"')
+            token = parse_string (HEX_STRING);
+          else
+            token = parse_id ();
+          break;
+          
+       default:
+          if (lex_is_id1 (*prog)) 
+            {
+              token = parse_id ();
+              break; 
+            }
+          else
+            {
+              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
+}
+
+/* Parses an identifier at the current position into tokid and
+   tokstr.
+   Returns the correct token type. */
+static int
+parse_id (void) 
+{
+  const char *start = prog;
+  prog = lex_skip_identifier (start);
+
+  ds_concat (&tokstr, start, prog - start);
+  str_copy_trunc (tokid, sizeof tokid, ds_c_str (&tokstr));
+  return lex_id_to_token (ds_c_str (&tokstr), ds_length (&tokstr));
+}
+
+/* Reports an error to the effect that subcommand SBC may only be
+   specified once. */
+void
+lex_sbc_only_once (const char *sbc) 
+{
+  msg (SE, _("Subcommand %s may only be specified once."), sbc);
+}
+
+/* Reports an error to the effect that subcommand SBC is
+   missing. */
+void
+lex_sbc_missing (const char *sbc) 
+{
+  lex_error (_("missing required subcommand %s"), sbc);
+}
+
+/* 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;
+  char where[128];
+
+  token_rep = lex_token_representation ();
+  if (token == T_STOP)
+    strcpy (where, "end of file");
+  else if (token == '.')
+    strcpy (where, "end of command");
+  else
+    snprintf (where, sizeof where, "`%s'", token_rep);
+  free (token_rep);
+
+  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, where);
+    }
+  else
+    msg (SE, _("Syntax error at %s."), where);
+}
+
+/* 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 true if the current token is a number. */
+bool
+lex_is_number (void) 
+{
+  return token == T_POS_NUM || token == T_NEG_NUM;
+}
+
+/* Returns the value of the current token, which must be a
+   floating point number. */
+double
+lex_number (void)
+{
+  assert (lex_is_number ());
+  return tokval;
+}
+
+/* Returns true iff the current token is an integer. */
+bool
+lex_is_integer (void)
+{
+  return (lex_is_number ()
+         && 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_is_integer ());
+  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.  The identifier may be abbreviated to its first three
+   letters.
+   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_is_integer () && 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_is_integer ())
+    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 (lex_is_number ())
+    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;
+    }
+}
+/* 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_token)
+    return put_token;
+
+  for (;;)
+    {
+      if (eof)
+        return 0;
+
+      for (;;)
+       {
+         while (isspace ((unsigned char) *prog))
+           prog++;
+         if (*prog)
+           break;
+
+         if (dot)
+           return '.';
+         else if (!lex_get_line ())
+            return 0;
+
+         if (put_token) 
+           return put_token;
+       }
+
+      if ((toupper ((unsigned char) *prog) == 'X'
+          || toupper ((unsigned char) *prog) == 'B'
+           || toupper ((unsigned char) *prog) == 'O')
+         && (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)
+{
+  save_token ();
+  token = t;
+}
+
+/* Makes the current token become the next token to be read; the
+   current token is set to the identifier ID. */
+void
+lex_put_back_id (const char *id)
+{
+  assert (lex_id_to_token (id, strlen (id)) == T_ID);
+  save_token ();
+  token = T_ID;
+  ds_replace (&tokstr, id);
+  str_copy_trunc (tokid, sizeof tokid, ds_c_str (&tokstr));
+}
+\f
+/* Weird line processing functions. */
+
+/* Returns the entire contents of the current line. */
+const char *
+lex_entire_line (void)
+{
+  return ds_c_str (&getl_buf);
+}
+
+/* As lex_entire_line(), but only returns the part of the current line
+   that hasn't already been tokenized.
+   If END_DOT is non-null, stores nonzero into *END_DOT if the line
+   ends with a terminal dot, or zero if it doesn't. */
+const char *
+lex_rest_of_line (int *end_dot)
+{
+  if (end_dot)
+    *end_dot = dot;
+  return prog;
+}
+
+/* Causes the rest of the current input line to be ignored for
+   tokenization purposes. */
+void
+lex_discard_line (void)
+{
+  prog = ds_end (&getl_buf);
+  dot = put_token = 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. */
+
+/* Remove C-style comments in STRING, begun by slash-star and
+   terminated by star-slash or newline. */
+static void
+strip_comments (struct string *string)
+{
+  char *cp;
+  int quote;
+  bool in_comment;
+
+  in_comment = false;
+  quote = EOF;
+  for (cp = ds_c_str (string); *cp; )
+    {
+      /* If we're not in a comment, check for quote marks. */
+      if (!in_comment)
+        {
+          if (*cp == quote)
+            quote = EOF;
+          else if (*cp == '\'' || *cp == '"')
+            quote = *cp;
+        }
+      
+      /* If we're not inside a quotation, check for comment. */
+      if (quote == EOF)
+        {
+          if (cp[0] == '/' && cp[1] == '*')
+            {
+              in_comment = true;
+              *cp++ = ' ';
+              *cp++ = ' ';
+              continue;
+            }
+          else if (in_comment && cp[0] == '*' && cp[1] == '/')
+            {
+              in_comment = false;
+              *cp++ = ' ';
+              *cp++ = ' ';
+              continue;
+            }
+        }
+      
+      /* Check commenting. */
+      if (in_comment)
+        *cp = ' ';
+      cp++;
+    }
+}
+
+/* Reads a line for use by the tokenizer, and preprocesses it by
+   removing comments, stripping trailing whitespace and the
+   terminal dot, and removing leading indentors. */
+bool
+lex_get_line (void)
+{
+  struct string *line = &getl_buf;
+  bool interactive;
+
+  if (!getl_read_line (&interactive))
+    return false;
+
+  strip_comments (line);
+  ds_rtrim_spaces (line);
+  
+  /* Check for and remove terminal dot. */
+  dot = (ds_chomp (line, get_endcmd ())
+         || (ds_is_empty (line) && get_nulline ()));
+  
+  /* Strip leading indentors or insert a terminal dot (unless the
+     line was obtained interactively). */
+  if (!interactive)
+    {
+      int first = ds_first (line);
+
+      if (first == '+' || first == '-')
+       *ds_data (line) = ' ';
+      else if (first != EOF && !isspace (first))
+       put_token = '.';
+    }
+
+  prog = ds_c_str (line);
+
+  return true;
+}
+\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_POS_NUM:
+    case T_NEG_NUM:
+      return xstrdup (ds_c_str (&tokstr));
+      break;
+
+    case T_STRING:
+      {
+       int hexstring = 0;
+       char *sp, *dp;
+
+       for (sp = ds_c_str (&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_c_str (&tokstr); *sp; )
+           {
+             if (*sp == '\'')
+               *dp++ = '\'';
+             *dp++ = (unsigned char) *sp++;
+           }
+       else
+         for (sp = ds_c_str (&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_NEG_NUM)
+    {
+      token = T_POS_NUM;
+      tokval = -tokval;
+      ds_replace (&tokstr, ds_c_str (&tokstr) + 1);
+      save_token ();
+      token = '-';
+    }
+}
+   
+/* We're not at eof any more. */
+void
+lex_reset_eof (void)
+{
+  eof = 0;
+}
+
+/* Skip a COMMENT command. */
+void
+lex_skip_comment (void)
+{
+  for (;;)
+    {
+      if (!lex_get_line ()) 
+        {
+          put_token = T_STOP;
+          eof = 1;
+          return;
+        }
+      
+      if (put_token == '.')
+       break;
+
+      prog = ds_end (&getl_buf);
+      if (dot)
+       break;
+    }
+}
+\f
+/* Private functions. */
+
+/* When invoked, tokstr contains a string of binary, octal, or
+   hex digits, according to TYPE.  The string is converted to
+   characters having the specified values. */
+static void
+convert_numeric_string_to_char_string (enum string_type type)
+{
+  const char *base_name;
+  int base;
+  int chars_per_byte;
+  size_t byte_cnt;
+  size_t i;
+  char *p;
+
+  switch (type) 
+    {
+    case BINARY_STRING:
+      base_name = _("binary");
+      base = 2;
+      chars_per_byte = 8;
+      break;
+    case OCTAL_STRING:
+      base_name = _("octal");
+      base = 8;
+      chars_per_byte = 3;
+      break;
+    case HEX_STRING:
+      base_name = _("hex");
+      base = 16;
+      chars_per_byte = 2;
+      break;
+    default:
+      abort ();
+    }
+  
+  byte_cnt = ds_length (&tokstr) / chars_per_byte;
+  if (ds_length (&tokstr) % chars_per_byte)
+    msg (SE, _("String of %s digits has %d characters, which is not a "
+              "multiple of %d."),
+        base_name, ds_length (&tokstr), chars_per_byte);
+
+  p = ds_c_str (&tokstr);
+  for (i = 0; i < byte_cnt; i++)
+    {
+      int value;
+      int j;
+         
+      value = 0;
+      for (j = 0; j < chars_per_byte; 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_c_str (&tokstr)[i] = (unsigned char) value;
+    }
+
+  ds_truncate (&tokstr, byte_cnt);
+}
+
+/* Parses a string from the input buffer into tokstr.  The input
+   buffer pointer prog must point to the initial single or double
+   quote.  TYPE indicates the type of string to be parsed.
+   Returns token type. */
+static int 
+parse_string (enum string_type 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_putc (&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 ())
+            goto finish;
+       }
+
+      /* 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 ())
+            {
+              msg (SE, _("Unexpected end of file in string concatenation."));
+              goto finish;
+            }
+       }
+
+      /* 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 != CHARACTER_STRING)
+    convert_numeric_string_to_char_string (type);
+
+  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_c_str (&tokstr)[i] == 0)
+       {
+         if (!warned)
+           {
+             msg (SE, _("Sorry, literal strings may not contain null "
+                        "characters.  Replacing with spaces."));
+             warned = 1;
+           }
+         ds_c_str (&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)
+      fprintf (stderr, "%s:%d\t", curfn, curln);
+  }
+  
+  switch (token)
+    {
+    case T_ID:
+      fprintf (stderr, "ID\t%s\n", tokid);
+      break;
+
+    case T_POS_NUM:
+    case T_NEG_NUM:
+      fprintf (stderr, "NUM\t%f\n", tokval);
+      break;
+
+    case T_STRING:
+      fprintf (stderr, "STRING\t\"%s\"\n", ds_c_str (&tokstr));
+      break;
+
+    case T_STOP:
+      fprintf (stderr, "STOP\n");
+      break;
+
+    case T_EXP:
+      fprintf (stderr, "MISC\tEXP\"");
+      break;
+
+    case 0:
+      fprintf (stderr, "MISC\tEOF\n");
+      break;
+
+    default:
+      if (token >= T_FIRST_KEYWORD && token <= T_LAST_KEYWORD)
+       fprintf (stderr, "KEYWORD\t%s\n", lex_token_name (token));
+      else
+       fprintf (stderr, "PUNCT\t%c\n", token);
+      break;
+    }
+}
+#endif /* DUMP_TOKENS */
diff --git a/src/language/lexer/lexer.h b/src/language/lexer/lexer.h
new file mode 100644 (file)
index 0000000..ad162c8
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !lexer_h
+#define lexer_h 1
+
+#include "variable.h"
+#include <ctype.h>
+#include <stdbool.h>
+
+#include "identifier.h"
+
+
+extern int token;
+extern double tokval;
+extern char tokid[LONG_NAME_LEN + 1];
+extern struct string tokstr;
+
+#include <stddef.h>
+
+/* Initialization. */
+void lex_init (void);
+void lex_done (void);
+
+/* Common functions. */
+void lex_get (void);
+void lex_error (const char *, ...);
+void lex_sbc_only_once (const char *);
+void lex_sbc_missing (const char *);
+int lex_end_of_command (void);
+
+/* Token testing functions. */
+bool lex_is_number (void);
+double lex_number (void);
+bool lex_is_integer (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);
+       
+/* Weird token functions. */
+int lex_look_ahead (void);
+void lex_put_back (int);
+void lex_put_back_id (const char *tokid);
+
+/* Weird line processing functions. */
+const char *lex_entire_line (void);
+const char *lex_rest_of_line (int *end_dot);
+void lex_discard_line (void);
+void lex_set_prog (char *p);
+
+/* Weird line reading functions. */
+bool lex_get_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/language/lexer/q2c.c b/src/language/lexer/q2c.c
new file mode 100644 (file)
index 0000000..7378870
--- /dev/null
@@ -0,0 +1,2080 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, 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
+*/
+     
+#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 *program_name;
+
+/* 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. */
+
+char nullstr[] = "";
+
+/* Close all open files and delete the output file, on failure. */
+static 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", program_name, ofn, strerror (errno));
+}
+
+void hcf (void) NO_RETURN;
+
+/* Terminate unsuccessfully. */
+void
+hcf (void)
+{
+  finish_up ();
+  exit (EXIT_FAILURE);
+}
+
+int fail (const char *, ...) PRINTF_FORMAT (1, 2);
+int error (const char *, ...) PRINTF_FORMAT (1, 2);
+
+/* Output an error message and terminate unsuccessfully. */
+int
+fail (const char *format, ...)
+{
+  va_list args;
+
+  va_start (args, format);
+  fprintf (stderr, "%s: ", program_name);
+  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. */
+static void *
+xmalloc (size_t size)
+{
+  void *vp;
+  
+  if (size == 0)
+    return NULL;
+  
+  vp = malloc (size);
+  if (!vp)
+    fail ("xmalloc(%lu): %s", (unsigned long) size, VME);
+  
+  return vp;
+}
+
+/* Make a dynamically allocated copy of string S and return a pointer
+   to the first character. */
+static 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. */
+static 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. */
+static 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. */
+static 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. */
+static 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. */
+static 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. */
+static int
+add_symbol (const char *name, int unique, int value)
+{
+  symbol *iter, *sym;
+  int x;
+
+  sym = xmalloc (sizeof *sym);
+  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. */
+static 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. */
+static 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. */
+static 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 == '"')
+    {
+      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 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
+    token = *cp++;
+  
+#if DEBUGGING
+  dump_token ();
+#endif
+  
+  return token;
+}
+
+/* Force the current token to be an identifier token. */
+static void
+force_id (void)
+{
+  if (token != T_ID)
+    error ("Identifier expected.");
+}
+
+/* Force the current token to be a string token. */
+static 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. */
+static 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. */
+static int
+match_token (int t)
+{
+  if (token == t)
+    {
+      lex_get ();
+      return 1;
+    }
+  return 0;
+}
+
+/* Force the current token to be T, and skip it. */
+static 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;
+
+typedef enum
+  {
+    ARITY_ONCE_EXACTLY,  /* must occur exactly once */
+    ARITY_ONCE_ONLY,     /* zero or once */
+    ARITY_MANY           /* 0, 1, ... , inf */
+  }subcommand_arity;
+
+/* 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_*. */
+    subcommand_arity arity;    /* How many times should the subcommand occur*/
+    int narray;                        /* Index of next array element. */
+    const char *prefix;                /* Prefix for variable and constant names. */
+    specifier *spec;           /* Array of specifiers. */
+    
+    /* SBC_STRING and SBC_INT only. */
+    char *restriction;         /* Expression restricting string length. */
+    char *message;             /* Error message. */
+    int translatable;           /* Error message is translatable */
+  };
+
+/* 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. */
+static 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. */
+static 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. */
+static 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 (':');
+  
+  if ( sbc->type == SBC_ARRAY && token == T_ID ) 
+    {
+       spec->varname = xstrdup (st_lower (tokstr));
+       spec->index = sbc->narray;
+       sbc->narray++;
+    }
+    
+  
+  
+  /* Parse all the settings. */
+  {
+    setting **s = &spec->s;
+    
+    for (;;)
+      {
+       *s = xmalloc (sizeof **s);
+       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. */
+static void
+parse_specifiers (subcommand *sbc)
+{
+  specifier **spec = &sbc->spec;
+
+  if (token == ';' || token == '.')
+    {
+      *spec = NULL;
+      return;
+    }
+  
+  for (;;)
+    {
+      *spec = xmalloc (sizeof **spec);
+      parse_specifier (*spec, sbc);
+      if (token == ';' || token == '.')
+       break;
+      skip_token (',');
+      spec = &(*spec)->next;
+    }
+  (*spec)->next = NULL;
+}
+
+/* Parse a subcommand into SBC. */
+static void
+parse_subcommand (subcommand *sbc)
+{
+  sbc->arity = ARITY_MANY;
+
+  if (match_token ('*'))
+    {
+      if (def)
+       error ("Multiple default subcommands.");
+      def = sbc;
+    }
+
+  if ( match_token('+'))
+    sbc->arity = ARITY_ONCE_ONLY ;
+  else if (match_token('^'))
+    sbc->arity = ARITY_ONCE_EXACTLY ;
+
+
+  force_id ();
+  sbc->name = xstrdup (tokstr);
+  lex_get ();
+  
+  sbc->narray = 0;
+  sbc->type = SBC_PLAIN;
+  sbc->spec = NULL;
+  sbc->translatable = 0;
+
+  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;
+        if ( token == T_STRING) 
+         {
+             sbc->restriction = xstrdup (tokstr);
+             lex_get ();
+              if ( match_id("N_") )
+              {
+               skip_token('(');
+               force_string ();
+               lex_get();
+               skip_token(')');
+               sbc->translatable = 1;
+              }
+             else {
+               force_string ();
+               lex_get ();
+              }
+             sbc->message = xstrdup (tokstr);
+         }
+       else
+           sbc->restriction = NULL;
+       }
+      else if (match_id ("PINT"))
+       sbc->type = SBC_PINT;
+      else if (match_id ("DOUBLE"))
+       {
+         if ( match_id ("LIST") )
+           sbc->type = SBC_DBL_LIST;
+         else
+           sbc->type = 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 **sbc);
+      (*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;
+
+void dump (int, const char *, ...) PRINTF_FORMAT (2, 3);
+
+/* 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. */
+static 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. */
+static 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. */
+static 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. */
+static 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);
+      }
+  }
+
+  /* Write out some type definitions */
+  {
+    dump (0, "#define MAXLISTS 10");
+  }
+
+
+  /* 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)
+               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[%s%scount];", 
+                               st_lower (sbc->name), 
+                               st_upper (prefix),
+                               st_upper (sbc->prefix)
+                               );
+
+                         f = 1;
+                       }
+                   }
+                 else
+                   dump_specifier_vars (spec, sbc);
+               }
+           }
+           break;
+
+         case SBC_VARLIST:
+           dump (0, "size_t %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[MAXLISTS];", st_lower (sbc->name));
+           break;
+
+         case SBC_DBL:
+           dump (0, "double n_%s[MAXLISTS];", st_lower (sbc->name));
+           break;
+
+         case SBC_DBL_LIST:
+           dump (0, "subc_list_double dl_%s[MAXLISTS];",
+                 st_lower(sbc->name));
+           break;
+
+         case SBC_INT_LIST:
+           dump (0, "subc_list_int il_%s[MAXLISTS];",
+                 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. */
+static 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. */
+static void
+dump_vars_init (int persistent)
+{
+  /* 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));
+       if ( ! persistent ) 
+         {
+           switch (sbc->type)
+             {
+             case SBC_INT_LIST:
+               break;
+
+             case SBC_DBL_LIST:
+               dump (1, "{");
+               dump (0, "int i;");
+               dump (1, "for (i = 0; i < MAXLISTS; ++i)");
+               dump (0, "subc_list_double_create(&p->dl_%s[i]) ;",
+                     st_lower (sbc->name)
+                     );
+               dump (-2, "}");
+               break;
+
+             case SBC_DBL:
+               dump (1, "{");
+               dump (0, "int i;");
+               dump (1, "for (i = 0; i < MAXLISTS; ++i)");
+               dump (0, "p->n_%s[i] = SYSMIS;", st_lower (sbc->name));
+               dump (-2, "}");
+               break;
+
+             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 (1, "{");
+               dump (0, "int i;");
+               dump (1, "for (i = 0; i < MAXLISTS; ++i)");
+               dump (0, "p->n_%s[i] = NOT_LONG;", st_lower (sbc->name));
+               dump (-2, "}");
+               break;
+
+             default:
+               assert (0);
+             }
+         }
+      }
+  }
+}
+
+/* Return a pointer to a static buffer containing an expression that
+   will match token T. */
+static 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. */
+static 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 ( sbc->type == SBC_ARRAY ) 
+               dump (0, "p->a_%s[%s%s%s] = 1;",
+                     st_lower (sbc->name),
+                     st_upper (prefix), st_upper (sbc->prefix),
+                     st_upper (spec->varname));
+           }
+
+
+         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_is_integer ())");
+             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 (!lex_is_number ())");
+             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. */
+static 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 (default_dict, &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, "free(p->s_%s);", st_lower(sbc->name) );
+      dump (0, "p->s_%s = xstrdup (ds_c_str (&tokstr));",
+           st_lower (sbc->name));
+      dump (0, "lex_get ();");
+      if (sbc->restriction)
+       dump (-1, "}");
+    }
+  else if (sbc->type == SBC_DBL)
+    {
+      dump (1, "if (!lex_force_num ())");
+      dump (0, "goto lossage;");
+      dump (-1, "p->n_%s[p->sbc_%s - 1] = lex_number ();", 
+           st_lower (sbc->name), st_lower (sbc->name) );
+      dump (0, "lex_get();");
+    }
+  else if (sbc->type == SBC_INT)
+    {
+      dump(1, "{");
+      dump(0, "int x;");
+      dump (1, "if (!lex_force_int ())");
+      dump (0, "goto lossage;");
+      dump (-1, "x = lex_integer ();");
+      dump (0, "lex_get();");
+      if (sbc->restriction)
+       {
+         char buf[1024];
+         dump (1, "if (!(%s))", sbc->restriction);
+         dump (1, "{"); 
+          sprintf(buf,sbc->message,sbc->name);
+         if ( sbc->translatable ) 
+                 dump (0, "msg (SE, gettext(\"%s\"));",buf);
+         else
+                 dump (0, "msg (SE, \"%s\");",buf);
+         dump (0, "goto lossage;");
+         dump (-1, "}");
+      }
+      dump (0, "p->n_%s[p->sbc_%s - 1] = x;", st_lower (sbc->name), st_lower(sbc->name) );
+      dump (-1,"}");
+    }
+  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_DBL_LIST)
+    {
+      dump (0, "if ( p->sbc_%s > MAXLISTS)",st_lower(sbc->name));
+      dump (1, "{");
+      dump (0, "msg (SE, \"No more than %%d %s subcommands allowed\",MAXLISTS);",st_lower(sbc->name));
+      dump (0, "goto lossage;");
+      dump (-1,"}");
+
+      dump (1, "while (token != '/' && token != '.')");
+      dump (1, "{");
+      dump (0, "lex_match(',');");
+      dump (0, "if (!lex_force_num ())");
+      dump (1, "{");
+      dump (0, "goto lossage;");
+      dump (-1,"}");
+
+      dump (0, "subc_list_double_push(&p->dl_%s[p->sbc_%s-1],lex_number ());", 
+           st_lower (sbc->name),st_lower (sbc->name)
+           );
+
+      dump (0, "lex_get();");
+      dump (-1,"}");
+
+    }
+  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. */
+static void
+dump_parser (int persistent)
+{
+  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 (persistent);
+
+  dump (1, "for (;;)");
+  dump (1, "{");
+
+  f = 0;
+  if (def && (def->type == SBC_VARLIST))
+    {
+      if (def->type == SBC_VARLIST)
+       dump (1, "if (token == T_ID "
+              "&& dict_lookup_var (default_dict, tokid) != NULL "
+             "&& lex_look_ahead () != '=')");
+      else
+       {
+         dump (0, "if ((token == T_ID "
+                "&& dict_lookup_var (default_dict, 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 (default_dict, &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->arity != ARITY_MANY)
+         {
+           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 ();
+      }
+  }
+
+
+  /* Now deal with the /ALGORITHM subcommand implicit to all commands */
+  dump(1,"else if ( get_syntax() != COMPATIBLE && lex_match_id(\"ALGORITHM\"))");
+  dump(1,"{");
+
+  dump (0, "lex_match ('=');");
+
+  dump(1,"if (lex_match_id(\"COMPATIBLE\"))");
+  dump(0,"set_cmd_algorithm(COMPATIBLE);");
+  outdent();
+  dump(1,"else if (lex_match_id(\"ENHANCED\"))");
+  dump(0,"set_cmd_algorithm(ENHANCED);");
+
+  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);
+
+  outdent ();
+
+  {
+    /*  Check that mandatory subcommands have been specified  */
+    subcommand *sbc;
+
+    for (sbc = subcommands; sbc; sbc = sbc->next)
+      {
+
+       if ( sbc->arity == ARITY_ONCE_EXACTLY ) 
+         {
+           dump (0, "if ( 0 == p->sbc_%s)", st_lower (sbc->name));
+           dump (1, "{");
+           dump (0, "msg (SE, _(\"%s subcommand must be given.\"));",
+                 sbc->name);
+           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. */
+static 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\t\t-*- mode: c; buffer-read-only: t -*-", ofn);
+  dump (0, nullstr);
+  dump (0, "   Generated by q2c from %s on %s.", ifn, timep);
+  dump (0, "   Do not modify!");
+  dump (0, " */");
+}
+
+/* Write out commands to free variable state. */
+static void
+dump_free (int persistent)
+{
+  subcommand *sbc;
+  int used;
+
+  indent = 0;
+
+  used = 0;
+  if ( ! persistent ) 
+    {
+      for (sbc = subcommands; sbc; sbc = sbc->next)
+       {
+       if (sbc->type == SBC_STRING)
+         used = 1;
+       if (sbc->type == SBC_DBL_LIST)
+         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, "{");
+
+  if ( ! persistent ) 
+    {
+
+      for (sbc = subcommands; sbc; sbc = sbc->next)
+       {
+         switch (sbc->type) 
+           {
+            case SBC_VARLIST:
+             dump (0, "free (p->v_variables);");
+              break;
+           case SBC_STRING:
+             dump (0, "free (p->s_%s);", st_lower (sbc->name));
+             break;
+           case SBC_DBL_LIST:
+             dump (0, "int i;");
+             dump (1, "for(i = 0; i < MAXLISTS ; ++i)");
+             dump (0, "subc_list_double_destroy(&p->dl_%s[i]);", st_lower (sbc->name));
+             outdent();
+             break;
+           default:
+             break;
+           }
+       }
+    }
+
+  dump (-1, "}");
+
+}
+
+
+
+/* Returns the name of a directive found on the current input line, if
+   any, or a null pointer if none found. */
+static 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[])
+{
+  program_name = 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 <stdlib.h>");
+         dump (0, "#include \"alloc.h\"");
+         dump (0, "#include \"message.h\"");
+         dump (0, "#include \"lexer.h\"");
+          dump (0, "#include \"settings.h\"");
+         dump (0, "#include \"str.h\"");
+          dump (0, "#include \"subcommand-list.h\"");
+         dump (0, "#include \"variable.h\"");
+         dump (0, nullstr);
+
+          dump (0, "#include \"gettext.h\"");
+          dump (0, "#define _(msgid) gettext (msgid)");
+         dump (0, nullstr);
+       }
+      else if (!strcmp (directive, "declarations"))
+       dump_declarations ();
+      else if (!strcmp (directive, "functions"))
+       {
+         dump_parser (0);
+         dump_free (0); 
+       }
+      else if (!strcmp (directive, "_functions"))
+       {
+         dump_parser (1);
+         dump_free (1); 
+       }
+      else
+       error ("unknown directive `%s'", directive);
+      indent = 0;
+      dump (0, "#line %d \"%s\"", ln + 1, ifn);
+    }
+
+
+
+  return EXIT_SUCCESS;
+}
diff --git a/src/language/lexer/range-parser.c b/src/language/lexer/range-parser.c
new file mode 100644 (file)
index 0000000..41d0198
--- /dev/null
@@ -0,0 +1,111 @@
+#include <config.h>
+#include "range-parser.h"
+#include <stdbool.h>
+#include "data-in.h"
+#include "message.h"
+#include "lexer.h"
+#include "magic.h"
+#include "str.h"
+#include "value.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+static bool parse_number (double *, const struct fmt_spec *);
+
+/* Parses and stores a numeric value, or a range of the form "x
+   THRU y".  Open-ended ranges may be specified as "LO(WEST) THRU
+   y" or "x THRU HI(GHEST)".  Sets *X and *Y to the range or the
+   value and returns success.
+
+   Numeric values are always accepted.  If F is nonnull, then
+   string values are also accepted, and converted to numeric
+   values using the specified format. */
+bool
+parse_num_range (double *x, double *y, const struct fmt_spec *f) 
+{
+  if (lex_match_id ("LO") || lex_match_id ("LOWEST"))
+    *x = LOWEST;
+  else if (!parse_number (x, f))
+    return false;
+
+  if (lex_match_id ("THRU")) 
+    {
+      if (lex_match_id ("HI") || lex_match_id ("HIGHEST"))
+        *y = HIGHEST;
+      else if (!parse_number (y, f))
+        return false;
+
+      if (*y < *x) 
+        {
+          double t;
+          msg (SW, _("Low end of range (%g) is below high end (%g).  "
+                     "The range will be treated as reversed."),
+               *x, *y);
+          t = *x;
+          *x = *y;
+          *y = t;
+        }
+      else if (*x == *y) 
+        msg (SW, _("Ends of range are equal (%g)."), *x);
+
+      return true;
+    }
+  else
+    {
+      if (*x == LOWEST) 
+        {
+          msg (SE, _("LO or LOWEST must be part of a range."));
+          return false;
+        }
+      *y = *x;
+    }
+  
+  return true;
+}
+
+/* Parses a number and stores it in *X.  Returns success.
+
+   Numeric values are always accepted.  If F is nonnull, then
+   string values are also accepted, and converted to numeric
+   values using the specified format. */
+static bool
+parse_number (double *x, const struct fmt_spec *f)
+{
+  if (lex_is_number ()) 
+    {
+      *x = lex_number ();
+      lex_get ();
+      return true;
+    }
+  else if (token == T_STRING && f != NULL) 
+    {
+      struct data_in di;
+      union value v;
+      di.s = ds_data (&tokstr);
+      di.e = ds_end (&tokstr);
+      di.v = &v;
+      di.flags = 0;
+      di.f1 = 1;
+      di.f2 = ds_length (&tokstr);
+      di.format = *f;
+      data_in (&di);
+      lex_get ();
+      *x = v.f;
+      if (*x == SYSMIS)
+        {
+          lex_error (_("System-missing value is not valid here."));
+          return false;
+        }
+      return true;
+    }
+  else 
+    {
+      if (f != NULL)
+        lex_error (_("expecting number or data string"));
+      else
+        lex_force_num ();
+      return false; 
+    }
+}
diff --git a/src/language/lexer/range-parser.h b/src/language/lexer/range-parser.h
new file mode 100644 (file)
index 0000000..f03a7e8
--- /dev/null
@@ -0,0 +1,28 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef RANGE_PRS_H
+#define RANGE_PRS_H 1
+
+#include <stdbool.h>
+
+struct fmt_spec;
+bool parse_num_range (double *x, double *y, const struct fmt_spec *fmt);
+
+#endif /* range-prs.h */
diff --git a/src/language/lexer/subcommand-list.c b/src/language/lexer/subcommand-list.c
new file mode 100644 (file)
index 0000000..95469aa
--- /dev/null
@@ -0,0 +1,75 @@
+/* subclist - lists for PSPP subcommands
+
+Copyright (C) 2004 Free Software Foundation, Inc.
+
+Written by John Darrington <john@darrington.wattle.id.au>
+
+
+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., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA. */
+
+
+#include "subcommand-list.h"
+#include <stdlib.h>
+#include "xalloc.h"
+
+/* I call these objects `lists' but they are in fact simple dynamic arrays */
+
+#define CHUNKSIZE 16
+
+/* Create a  list */
+void
+subc_list_double_create(subc_list_double *l)
+{
+  l->data = xnmalloc (CHUNKSIZE, sizeof *l->data);
+  l->sz = CHUNKSIZE;
+  l->n_data = 0;
+}
+
+/* Push a value onto the list */
+void
+subc_list_double_push(subc_list_double *l, double d)
+{
+  l->data[l->n_data++] = d;
+
+  if (l->n_data >= l->sz ) 
+    {
+      l->sz += CHUNKSIZE;
+      l->data = xnrealloc (l->data, l->sz, sizeof *l->data);
+    }
+
+}
+
+/* Return the number of items in the list */
+int 
+subc_list_double_count(const subc_list_double *l)
+{
+  return l->n_data;
+}
+
+
+/* Index into the list (array) */
+double
+subc_list_double_at(const subc_list_double *l, int idx)
+{
+  return l->data[idx];
+}
+
+/* Free up the list */
+void
+subc_list_double_destroy(subc_list_double *l)
+{
+  free(l->data);
+}
diff --git a/src/language/lexer/subcommand-list.h b/src/language/lexer/subcommand-list.h
new file mode 100644 (file)
index 0000000..5087cc1
--- /dev/null
@@ -0,0 +1,72 @@
+#ifndef SUBCLIST_H
+#define SUBCLIST_H
+
+/* subclist - lists for PSPP subcommands
+
+   Copyright (C) 2004 Free Software Foundation, Inc.
+
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+
+
+#include <sys/types.h>
+
+/* This module provides a rudimentary list class
+   It is intended for use by the command line parser for list subcommands
+*/
+
+
+struct subc_list_double {
+  double *data ;
+  size_t sz;
+  int n_data;
+};
+
+struct subc_list_int {
+  int *data ;
+  size_t sz;
+  int n_data;
+};
+
+
+typedef struct subc_list_double subc_list_double ;
+typedef struct subc_list_int subc_list_int ;
+
+/* Create a  list */
+void subc_list_double_create(subc_list_double *l) ;
+void subc_list_int_create(subc_list_int *l) ;
+
+/* Push a value onto the list */
+void subc_list_double_push(subc_list_double *l, double d) ;
+void subc_list_int_push(subc_list_int *l, int i) ;
+
+/* Index into the list */
+double subc_list_double_at(const subc_list_double *l, int idx);
+int subc_list_int_at(const subc_list_int *l, int idx);
+
+/* Return the number of values in the list */
+int subc_list_double_count(const subc_list_double *l);
+int subc_list_int_count(const subc_list_int *l);
+
+/* Destroy the list */
+void subc_list_double_destroy(subc_list_double *l) ;
+void subc_list_int_destroy(subc_list_int *l) ;
+
+
+#endif
diff --git a/src/language/lexer/variable-parser.c b/src/language/lexer/variable-parser.c
new file mode 100644 (file)
index 0000000..937e51c
--- /dev/null
@@ -0,0 +1,745 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "variable.h"
+#include <ctype.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "bit-vector.h"
+#include "dictionary.h"
+#include "message.h"
+#include "hash.h"
+#include "lexer.h"
+#include "misc.h"
+#include "pool.h"
+#include "size_max.h"
+#include "str.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* Parses a name as a variable within VS.  Sets *IDX to the
+   variable's index and returns true if successful.  On failure
+   emits an error message and returns false. */
+static bool
+parse_vs_variable_idx (const struct var_set *vs, size_t *idx)
+{
+  assert (idx != NULL);
+  
+  if (token != T_ID)
+    {
+      lex_error (_("expecting variable name"));
+      return false;
+    }
+  else if (var_set_lookup_var_idx (vs, tokid, idx)) 
+    {
+      lex_get ();
+      return true;
+    }
+  else 
+    {
+      msg (SE, _("%s is not a variable name."), tokid);
+      return false;
+    }
+}
+
+/* Parses a name as a variable within VS and returns the variable
+   if successful.  On failure emits an error message and returns
+   a null pointer. */
+static struct variable *
+parse_vs_variable (const struct var_set *vs)
+{
+  size_t idx;
+  return parse_vs_variable_idx (vs, &idx) ? var_set_get_var (vs, idx) : NULL;
+}
+
+/* Parses a variable name in dictionary D and returns the
+   variable if successful.  On failure emits an error message and
+   returns a null pointer. */
+struct variable *
+parse_dict_variable (const struct dictionary *d) 
+{
+  struct var_set *vs = var_set_create_from_dict (d);
+  struct variable *var = parse_vs_variable (vs);
+  var_set_destroy (vs);
+  return var;
+}
+
+/* Parses a variable name in default_dict and returns the
+   variable if successful.  On failure emits an error message and
+   returns a null pointer. */
+struct variable *
+parse_variable (void)
+{
+  return parse_dict_variable (default_dict);
+}
+
+
+/* Parses a set of variables from dictionary D given options
+   OPTS.  Resulting list of variables stored in *VAR and the
+   number of variables into *CNT.  Returns nonzero only if
+   successful. */
+int
+parse_variables (const struct dictionary *d, struct variable ***var,
+                 size_t *cnt, int opts) 
+{
+  struct var_set *vs;
+  int success;
+
+  assert (d != NULL);
+  assert (var != NULL);
+  assert (cnt != NULL);
+
+  vs = var_set_create_from_dict (d);
+  success = parse_var_set_vars (vs, var, cnt, opts);
+  if ( success == 0 )
+     free ( *var ) ;
+  var_set_destroy (vs);
+  return success;
+}
+
+/* Parses a variable name from VS.  If successful, sets *IDX to
+   the variable's index in VS, *CLASS to the variable's
+   dictionary class, and returns nonzero.  Returns zero on
+   failure. */
+static int
+parse_var_idx_class (const struct var_set *vs, size_t *idx,
+                     enum dict_class *class)
+{
+  if (!parse_vs_variable_idx (vs, idx))
+    return 0;
+
+  *class = dict_class_from_id (var_set_get_var (vs, *idx)->name);
+  return 1;
+}
+
+/* Add the variable from VS with index IDX to the list of
+   variables V that has *NV elements and room for *MV.
+   Uses and updates INCLUDED to avoid duplicates if indicated by
+   PV_OPTS, which also affects what variables are allowed in
+   appropriate ways. */
+static void
+add_variable (struct variable ***v, size_t *nv, size_t *mv,
+              char *included, int pv_opts,
+              const struct var_set *vs, size_t idx)
+{
+  struct variable *add = var_set_get_var (vs, idx);
+
+  if ((pv_opts & PV_NUMERIC) && add->type != NUMERIC) 
+    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) 
+    msg (SE, _("%s is not a string variable.  It will not be "
+               "included in the variable list."), add->name);
+  else if ((pv_opts & PV_NO_SCRATCH)
+           && dict_class_from_id (add->name) == DC_SCRATCH)
+    msg (SE, _("Scratch variables (such as %s) are not allowed "
+               "here."), add->name);
+  else if ((pv_opts & PV_SAME_TYPE) && *nv && add->type != (*v)[0]->type) 
+    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) && included[idx]) 
+    msg (SE, _("Variable %s appears twice in variable list."), add->name);
+  else 
+    {
+      if (*nv >= *mv)
+        {
+          *mv = 2 * (*nv + 1);
+          *v = xnrealloc (*v, *mv, sizeof **v);
+        }
+
+      if ((pv_opts & PV_DUPLICATE) || !included[idx])
+        {
+          (*v)[(*nv)++] = add;
+          if (!(pv_opts & PV_DUPLICATE))
+            included[idx] = 1;
+        }
+    }
+}
+
+/* Adds the variables in VS with indexes FIRST_IDX through
+   LAST_IDX, inclusive, to the list of variables V that has *NV
+   elements and room for *MV.  Uses and updates INCLUDED to avoid
+   duplicates if indicated by PV_OPTS, which also affects what
+   variables are allowed in appropriate ways. */
+static void
+add_variables (struct variable ***v, size_t *nv, size_t *mv, char *included,
+               int pv_opts,
+               const struct var_set *vs, int first_idx, int last_idx,
+               enum dict_class class) 
+{
+  size_t i;
+  
+  for (i = first_idx; i <= last_idx; i++)
+    if (dict_class_from_id (var_set_get_var (vs, i)->name) == class)
+      add_variable (v, nv, mv, included, pv_opts, vs, i);
+}
+
+/* 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_var_set_vars (const struct var_set *vs, 
+                    struct variable ***v, size_t *nv,
+                    int pv_opts)
+{
+  size_t mv;
+  char *included;
+
+  assert (vs != NULL);
+  assert (v != NULL);
+  assert (nv != NULL);
+
+  /* At most one of PV_NUMERIC, PV_STRING, PV_SAME_TYPE may be
+     specified. */
+  assert ((((pv_opts & PV_NUMERIC) != 0)
+           + ((pv_opts & PV_STRING) != 0)
+           + ((pv_opts & PV_SAME_TYPE) != 0)) <= 1);
+
+  /* PV_DUPLICATE and PV_NO_DUPLICATE are incompatible. */
+  assert (!(pv_opts & PV_DUPLICATE) || !(pv_opts & PV_NO_DUPLICATE));
+
+  if (!(pv_opts & PV_APPEND))
+    {
+      *v = NULL;
+      *nv = 0;
+      mv = 0;
+    }
+  else
+    mv = *nv;
+
+  if (!(pv_opts & PV_DUPLICATE))
+    {
+      size_t i;
+      
+      included = xcalloc (var_set_get_cnt (vs), sizeof *included);
+      for (i = 0; i < *nv; i++)
+        included[(*v)[i]->index] = 1;
+    }
+  else
+    included = NULL;
+
+  if (lex_match (T_ALL))
+    add_variables (v, nv, &mv, included, pv_opts,
+                   vs, 0, var_set_get_cnt (vs) - 1, DC_ORDINARY);
+  else 
+    {
+      do
+        {
+          enum dict_class class;
+          size_t first_idx;
+          
+          if (!parse_var_idx_class (vs, &first_idx, &class))
+            goto fail;
+
+          if (!lex_match (T_TO))
+            add_variable (v, nv, &mv, included, pv_opts, vs, first_idx);
+          else 
+            {
+              size_t last_idx;
+              enum dict_class last_class;
+              struct variable *first_var, *last_var;
+
+              if (!parse_var_idx_class (vs, &last_idx, &last_class))
+                goto fail;
+
+              first_var = var_set_get_var (vs, first_idx);
+              last_var = var_set_get_var (vs, last_idx);
+
+              if (last_idx < first_idx)
+                {
+                  msg (SE, _("%s TO %s is not valid syntax since %s "
+                             "precedes %s in the dictionary."),
+                       first_var->name, last_var->name,
+                       first_var->name, last_var->name);
+                  goto fail;
+                }
+
+              if (class != last_class)
+                {
+                  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 is a %s variable, whereas %s is %s."),
+                       first_var->name, dict_class_to_name (class),
+                       last_var->name, dict_class_to_name (last_class));
+                  goto fail;
+                }
+
+              add_variables (v, nv, &mv, included, pv_opts,
+                             vs, first_idx, last_idx, class);
+            }
+          if (pv_opts & PV_SINGLE)
+            break;
+          lex_match (',');
+        }
+      while (token == T_ID && var_set_lookup_var (vs, tokid) != NULL);
+    }
+  
+  if (*nv == 0)
+    goto fail;
+
+  free (included);
+  return 1;
+
+fail:
+  free (included);
+  free (*v);
+  *v = NULL;
+  *nv = 0;
+  return 0;
+}
+
+/* Extracts a numeric suffix from variable name S, copying it
+   into string R.  Sets *D to the length of R and *N to its
+   value. */
+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, size_t *nnames, int pv_opts)
+{
+  int n1, n2;
+  int d1, d2;
+  int n;
+  size_t nvar, mvar;
+  char name1[LONG_NAME_LEN + 1], name2[LONG_NAME_LEN + 1];
+  char root1[LONG_NAME_LEN + 1], root2[LONG_NAME_LEN + 1];
+  int success = 0;
+
+  assert (names != NULL);
+  assert (nnames != NULL);
+  assert ((pv_opts & ~(PV_APPEND | PV_SINGLE
+                       | PV_NO_SCRATCH | PV_NO_DUPLICATE)) == 0);
+  /* FIXME: PV_NO_DUPLICATE is not implemented. */
+
+  if (pv_opts & PV_APPEND)
+    nvar = mvar = *nnames;
+  else
+    {
+      nvar = mvar = 0;
+      *names = NULL;
+    }
+
+  do
+    {
+      if (token != T_ID)
+       {
+         lex_error ("expecting variable name");
+         goto fail;
+       }
+      if (dict_class_from_id (tokid) == DC_SCRATCH
+          && (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 (strcasecmp (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 = xnrealloc (*names, mvar, sizeof **names);
+           }
+
+         for (n = n1; n <= n2; n++)
+           {
+              char name[LONG_NAME_LEN + 1];
+             sprintf (name, "%s%0*d", root1, d1, n);
+             (*names)[nvar] = xstrdup (name);
+             nvar++;
+           }
+       }
+      else
+       {
+         if (nvar >= mvar)
+           {
+             mvar += 16;
+             *names = xnrealloc (*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;
+  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_DATA_LIST_vars(). */
+int
+parse_mixed_vars (char ***names, size_t *nnames, int pv_opts)
+{
+  size_t i;
+
+  assert (names != NULL);
+  assert (nnames != NULL);
+  assert ((pv_opts & ~PV_APPEND) == 0);
+
+  if (!(pv_opts & PV_APPEND))
+    {
+      *names = NULL;
+      *nnames = 0;
+    }
+  while (token == T_ID || token == T_ALL)
+    {
+      if (token == T_ALL || dict_lookup_var (default_dict, tokid) != NULL)
+       {
+         struct variable **v;
+         size_t nv;
+
+         if (!parse_variables (default_dict, &v, &nv, PV_NONE))
+           goto fail;
+         *names = xnrealloc (*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)[i]);
+  free (*names);
+  *names = NULL;
+  *nnames = 0;
+  return 0;
+}
+
+/* Parses a list of variables where some of the variables may be
+   existing and the rest are to be created.  Same args as
+   parse_DATA_LIST_vars(), except that all allocations are taken
+   from the given POOL. */
+int
+parse_mixed_vars_pool (struct pool *pool,
+                       char ***names, size_t *nnames, int pv_opts)
+{
+  int retval = parse_mixed_vars (names, nnames, pv_opts);
+  if (retval)
+    {
+      size_t i;
+
+      for (i = 0; i < *nnames; i++)
+        pool_register (pool, free, (*names)[i]);
+      pool_register (pool, free, *names);
+    }
+  return retval;
+}
+
+\f
+/* A set of variables. */
+struct var_set 
+  {
+    size_t (*get_cnt) (const struct var_set *);
+    struct variable *(*get_var) (const struct var_set *, size_t idx);
+    bool (*lookup_var_idx) (const struct var_set *, const char *, size_t *);
+    void (*destroy) (struct var_set *);
+    void *aux;
+  };
+
+/* Returns the number of variables in VS. */
+size_t
+var_set_get_cnt (const struct var_set *vs) 
+{
+  assert (vs != NULL);
+
+  return vs->get_cnt (vs);
+}
+
+/* Return variable with index IDX in VS.
+   IDX must be less than the number of variables in VS. */
+struct variable *
+var_set_get_var (const struct var_set *vs, size_t idx) 
+{
+  assert (vs != NULL);
+  assert (idx < var_set_get_cnt (vs));
+
+  return vs->get_var (vs, idx);
+}
+
+/* Returns the variable in VS named NAME, or a null pointer if VS
+   contains no variable with that name. */
+struct variable *
+var_set_lookup_var (const struct var_set *vs, const char *name) 
+{
+  size_t idx;
+  return (var_set_lookup_var_idx (vs, name, &idx)
+          ? var_set_get_var (vs, idx)
+          : NULL);
+}
+
+/* If VS contains a variable named NAME, sets *IDX to its index
+   and returns true.  Otherwise, returns false. */
+bool
+var_set_lookup_var_idx (const struct var_set *vs, const char *name,
+                        size_t *idx)
+{
+  assert (vs != NULL);
+  assert (name != NULL);
+  assert (strlen (name) <= LONG_NAME_LEN);
+
+  return vs->lookup_var_idx (vs, name, idx);
+}
+
+/* Destroys VS. */
+void
+var_set_destroy (struct var_set *vs) 
+{
+  if (vs != NULL)
+    vs->destroy (vs);
+}
+\f
+/* Returns the number of variables in VS. */
+static size_t
+dict_var_set_get_cnt (const struct var_set *vs) 
+{
+  struct dictionary *d = vs->aux;
+
+  return dict_get_var_cnt (d);
+}
+
+/* Return variable with index IDX in VS.
+   IDX must be less than the number of variables in VS. */
+static struct variable *
+dict_var_set_get_var (const struct var_set *vs, size_t idx) 
+{
+  struct dictionary *d = vs->aux;
+
+  return dict_get_var (d, idx);
+}
+
+/* If VS contains a variable named NAME, sets *IDX to its index
+   and returns true.  Otherwise, returns false. */
+static bool
+dict_var_set_lookup_var_idx (const struct var_set *vs, const char *name,
+                             size_t *idx) 
+{
+  struct dictionary *d = vs->aux;
+  struct variable *v = dict_lookup_var (d, name);
+  if (v != NULL) 
+    {
+      *idx = v->index;
+      return true;
+    }
+  else
+    return false;
+}
+
+/* Destroys VS. */
+static void
+dict_var_set_destroy (struct var_set *vs) 
+{
+  free (vs);
+}
+
+/* Returns a variable set based on D. */
+struct var_set *
+var_set_create_from_dict (const struct dictionary *d) 
+{
+  struct var_set *vs = xmalloc (sizeof *vs);
+  vs->get_cnt = dict_var_set_get_cnt;
+  vs->get_var = dict_var_set_get_var;
+  vs->lookup_var_idx = dict_var_set_lookup_var_idx;
+  vs->destroy = dict_var_set_destroy;
+  vs->aux = (void *) d;
+  return vs;
+}
+\f
+/* A variable set based on an array. */
+struct array_var_set 
+  {
+    struct variable *const *var;/* Array of variables. */
+    size_t var_cnt;             /* Number of elements in var. */
+    struct hsh_table *name_tab; /* Hash from variable names to variables. */
+  };
+
+/* Returns the number of variables in VS. */
+static size_t
+array_var_set_get_cnt (const struct var_set *vs) 
+{
+  struct array_var_set *avs = vs->aux;
+
+  return avs->var_cnt;
+}
+
+/* Return variable with index IDX in VS.
+   IDX must be less than the number of variables in VS. */
+static struct variable *
+array_var_set_get_var (const struct var_set *vs, size_t idx) 
+{
+  struct array_var_set *avs = vs->aux;
+
+  return (struct variable *) avs->var[idx];
+}
+
+/* If VS contains a variable named NAME, sets *IDX to its index
+   and returns true.  Otherwise, returns false. */
+static bool
+array_var_set_lookup_var_idx (const struct var_set *vs, const char *name,
+                              size_t *idx) 
+{
+  struct array_var_set *avs = vs->aux;
+  struct variable v, *vp, *const *vpp;
+
+  strcpy (v.name, name);
+  vp = &v;
+  vpp = hsh_find (avs->name_tab, &vp);
+  if (vpp != NULL) 
+    {
+      *idx = vpp - avs->var;
+      return true;
+    }
+  else
+    return false;
+}
+
+/* Destroys VS. */
+static void
+array_var_set_destroy (struct var_set *vs) 
+{
+  struct array_var_set *avs = vs->aux;
+
+  hsh_destroy (avs->name_tab);
+  free (avs);
+  free (vs);
+}
+
+/* Returns a variable set based on the VAR_CNT variables in
+   VAR. */
+struct var_set *
+var_set_create_from_array (struct variable *const *var, size_t var_cnt) 
+{
+  struct var_set *vs;
+  struct array_var_set *avs;
+  size_t i;
+
+  vs = xmalloc (sizeof *vs);
+  vs->get_cnt = array_var_set_get_cnt;
+  vs->get_var = array_var_set_get_var;
+  vs->lookup_var_idx = array_var_set_lookup_var_idx;
+  vs->destroy = array_var_set_destroy;
+  vs->aux = avs = xmalloc (sizeof *avs);
+  avs->var = var;
+  avs->var_cnt = var_cnt;
+  avs->name_tab = hsh_create (2 * var_cnt,
+                              compare_var_ptr_names, hash_var_ptr_name, NULL,
+                              NULL);
+  for (i = 0; i < var_cnt; i++)
+    if (hsh_insert (avs->name_tab, (void *) &var[i]) != NULL) 
+      {
+        var_set_destroy (vs);
+        return NULL;
+      }
+  
+  return vs;
+}
diff --git a/src/language/line-buffer.c b/src/language/line-buffer.c
new file mode 100644 (file)
index 0000000..ef0e74d
--- /dev/null
@@ -0,0 +1,592 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "line-buffer.h"
+#include "message.h"
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "message.h"
+#include "filename.h"
+#include "lexer.h"
+#include "settings.h"
+#include "str.h"
+#include "table.h"
+#include "variable.h"
+#include "version.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* Source file. */
+struct getl_source
+  {
+    struct getl_source *included_from; /* File that this is nested inside. */
+    struct getl_source *includes;      /* File nested inside this file. */
+    struct getl_source *next;          /* Next file in list. */
+
+    /* Current location. */
+    char *fn;                          /* Filename. */
+    int ln;                            /* Line number. */
+
+    enum getl_source_type
+      {
+        SYNTAX_FILE,
+        FILTER,
+        FUNCTION,
+        INTERACTIVE
+      }
+    type;
+
+    union 
+      {
+        /* SYNTAX_FILE. */
+        FILE *syntax_file;
+
+        /* FILTER. */
+        struct 
+          {
+            void (*filter) (struct string *line, void *aux);
+            void (*close) (void *aux);
+            void *aux;
+          }
+        filter;
+
+        /* FUNCTION. */
+        struct 
+          {
+            bool (*read) (struct string *line, char **fn, int *ln, void *aux);
+            void (*close) (void *aux);
+            void *aux;
+          }
+        function;
+
+        /* INTERACTIVE. */
+        bool (*interactive) (struct string *line, const char *prompt);
+      }
+    u;
+
+  };
+
+/* List of source files. */
+static struct getl_source *cur_source;
+static struct getl_source *last_source;
+
+static struct string getl_include_path;
+
+struct string getl_buf;
+
+static void close_source (void);
+
+static void init_prompts (void);
+static void uninit_prompts (void);
+static const char *get_prompt (void);
+
+/* Initialize getl. */
+void
+getl_initialize (void)
+{
+  ds_create (&getl_include_path,
+            fn_getenv_default ("STAT_INCLUDE_PATH", include_path));
+  ds_init (&getl_buf, 256);
+  init_prompts ();
+}
+
+/* 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_putc (&getl_include_path, PATH_DELIMITER);
+
+  ds_puts (&getl_include_path, path);
+}
+
+/* Appends source S to the list of source files. */
+static void
+append_source (struct getl_source *s) 
+{
+  s->included_from = s->includes = s->next = NULL;
+  if (last_source == NULL)
+    cur_source = s;
+  else
+    last_source->next = s;
+  last_source = s;
+}
+
+/* Nests source S within the current source file. */
+static void
+include_source (struct getl_source *s) 
+{
+  if (last_source == NULL)
+    append_source (s);
+  else 
+    {
+      s->included_from = cur_source;
+      s->includes = s->next = NULL;
+      s->next = NULL;
+      cur_source->includes = s;
+      cur_source = s;
+    }
+}
+
+/* Creates a source of the given TYPE.
+   Type-specific data must be initialized by the caller. */
+static struct getl_source *
+create_source (enum getl_source_type type) 
+{
+  struct getl_source *s = xmalloc (sizeof *s);
+  s->fn = NULL;
+  s->ln = 0;
+  s->type = type;
+  return s;
+}
+
+/* Creates a syntax file source with file name FN. */
+static struct getl_source *
+create_syntax_file_source (const char *fn) 
+{
+  struct getl_source *s = create_source (SYNTAX_FILE);
+  s->fn = xstrdup (fn);
+  s->u.syntax_file = NULL;
+  return s;
+}
+
+/* Creates a filter source with the given FILTER and CLOSE
+   functions that receive auxiliary data AUX. */
+static struct getl_source *
+create_filter_source (void (*filter) (struct string *, void *aux),
+                      void (*close) (void *aux),
+                      void *aux)
+{
+  struct getl_source *s = create_source (FILTER);
+  s->u.filter.filter = filter;
+  s->u.filter.close = close;
+  s->u.filter.aux = aux;
+  return s;
+}
+
+/* Creates a function source with the given READ and CLOSE
+   functions that receive auxiliary data AUX. */
+static struct getl_source *
+create_function_source (bool (*read) (struct string *line,
+                                      char **fn, int *ln, void *aux),
+                        void (*close) (void *aux),
+                        void *aux)
+{
+  struct getl_source *s = create_source (FUNCTION);
+  s->u.function.read = read;
+  s->u.function.close = close;
+  s->u.function.aux = aux;
+  return s;
+}
+
+/* Creates an interactive source with the given FUNCTION. */
+static struct getl_source *
+create_interactive_source (bool (*function) (struct string *line,
+                                             const char *prompt)) 
+{
+  struct getl_source *s = xmalloc (sizeof *s);
+  s->fn = NULL;
+  s->ln = 0;
+  s->type = INTERACTIVE;
+  s->u.interactive = function;
+  return s;
+}
+
+/* Adds FN to the tail end of the list of source files to
+   execute. */
+void
+getl_append_syntax_file (const char *fn)
+{
+  append_source (create_syntax_file_source (fn));
+}
+
+/* Inserts the given file with filename FN into the current file after
+   the current line. */
+void
+getl_include_syntax_file (const char *fn)
+{
+  if (cur_source != NULL) 
+    {
+      char *found_fn = fn_search_path (fn, ds_c_str (&getl_include_path),
+                                       fn_dirname (cur_source->fn));
+      if (found_fn != NULL) 
+        {
+          include_source (create_syntax_file_source (found_fn));
+          free (found_fn); 
+        }
+      else
+        msg (SE, _("Can't find `%s' in include file search path."), fn);
+    }
+  else 
+    getl_append_syntax_file (fn); 
+}
+
+/* Inserts the given filter into the current file after the
+   current line.  Each line read while the filter is in place
+   will be passed through FILTER, which may modify it as
+   necessary.  When the filter is closed, CLOSE will be called.
+   AUX will be passed to both functions.
+
+   The filter cannot itself output any new lines, and it will be
+   closed as soon as any line would be read from it.  This means
+   that, for a filter to be useful, another source must be nested
+   inside it with, e.g., getl_include_syntax_file(). */
+void
+getl_include_filter (void (*filter) (struct string *, void *aux),
+                     void (*close) (void *aux),
+                     void *aux) 
+{
+  include_source (create_filter_source (filter, close, aux));
+}
+
+/* Inserts the given functional source into the current file
+   after the current line.  Lines are read by calling READ, which
+   should write the next line in LINE, store the file name and
+   line number of the line in *FN and *LN, and return true.  The
+   string stored in *FN will not be freed by getl.  When no lines
+   are left, READ should return false.
+
+   When the source is closed, CLOSE will be called.
+
+   AUX will be passed to both READ and CLOSE. */
+void
+getl_include_function (bool (*read) (struct string *line,
+                                     char **fn, int *ln, void *aux),
+                       void (*close) (void *aux),
+                       void *aux) 
+{
+  include_source (create_function_source (read, close, aux));
+}
+
+/* Adds an interactive source to the end of the list of sources.
+   FUNCTION will be called to obtain a line.  It should store the
+   line in LINE.  PROMPT is the prompt to be displayed to the
+   user.  FUNCTION should return true when a line has been
+   obtained or false at end of file. */
+void
+getl_append_interactive (bool (*function) (struct string *line,
+                                           const char *prompt)) 
+{
+  append_source (create_interactive_source (function));
+}
+
+/* Closes all sources until an interactive source is
+   encountered. */
+void
+getl_abort_noninteractive (void) 
+{
+  while (cur_source != NULL && cur_source->type != INTERACTIVE)
+    close_source ();
+}
+
+/* Returns true if the current source is interactive,
+   false otherwise. */
+bool
+getl_is_interactive (void) 
+{
+  return cur_source != NULL && cur_source->type == INTERACTIVE;
+}
+
+/* Closes the current file, whether it be a main file or included
+   file, then moves cur_source to the next file in the chain. */
+static void
+close_source (void)
+{
+  struct getl_source *s;
+
+  s = cur_source;
+  switch (s->type) 
+    {
+    case SYNTAX_FILE:
+      if (s->u.syntax_file && EOF == fn_close (s->fn, s->u.syntax_file))
+        msg (MW, _("Closing `%s': %s."), s->fn, strerror (errno));
+      free (s->fn);
+      break;
+
+    case FILTER:
+      if (s->u.filter.close != NULL)
+        s->u.filter.close (s->u.filter.aux);
+      break;
+
+    case FUNCTION:
+      if (s->u.function.close != NULL)
+        s->u.function.close (s->u.function.aux);
+      break;
+
+    case INTERACTIVE:
+      break;
+    }
+
+  if (s->included_from != NULL)
+    {
+      cur_source = s->included_from;
+      cur_source->includes = NULL;
+    }
+  else
+    {
+      cur_source = s->next;
+      if (cur_source == NULL)
+       last_source = NULL;
+    }
+
+  free (s);
+}
+
+/* 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 = cur_source ? cur_source->fn : "";
+  if (ln != NULL)
+    *ln = cur_source ? cur_source->ln : -1;
+}
+
+/* File locator stack. */
+static const struct file_locator **file_loc;
+static int nfile_loc, mfile_loc;
+\f
+/* Close getl. */
+void
+getl_uninitialize (void)
+{
+  while (cur_source != NULL)
+    close_source ();
+  ds_destroy (&getl_buf);
+  ds_destroy (&getl_include_path);
+  free(file_loc);
+  file_loc = NULL;
+  nfile_loc = mfile_loc = 0;
+  uninit_prompts ();
+}
+
+
+/* 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 = xnrealloc (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);
+}
+
+/* Reads a line from syntax file source S into LINE.
+   Returns true if successful, false at end of file. */
+static bool
+read_syntax_file (struct string *line, struct getl_source *s)
+{
+  /* Open file, if not yet opened. */
+  if (s->u.syntax_file == NULL)
+    {
+      msg (VM (1), _("%s: Opening as syntax file."), s->fn);
+      s->u.syntax_file = fn_open (s->fn, "r");
+
+      if (s->u.syntax_file == NULL)
+        {
+          msg (ME, _("Opening `%s': %s."), s->fn, strerror (errno));
+          return false;
+        }
+    }
+
+  /* Read line from file and remove new-line.
+     Skip initial "#! /usr/bin/pspp" line. */
+  do 
+    {
+      s->ln++;
+      if (!ds_gets (line, s->u.syntax_file))
+        {
+          if (ferror (s->u.syntax_file))
+            msg (ME, _("Reading `%s': %s."), s->fn, strerror (errno));
+          return false;
+        }
+      ds_chomp (line, '\n');
+    }
+  while (s->ln == 1 && !memcmp (ds_c_str (line), "#!", 2));
+
+  /* Echo to listing file, if configured to do so. */
+  if (get_echo ())
+    tab_output_text (TAB_LEFT | TAT_FIX, ds_c_str (line));
+
+  return true;
+}
+
+/* Reads a line from source S into LINE.
+   Returns true if successful, false at end of file. */
+static bool
+read_line_from_source (struct string *line, struct getl_source *s)
+{
+  ds_clear (line);
+  switch (s->type) 
+    {
+    case SYNTAX_FILE:
+      return read_syntax_file (line, s);
+    case FILTER:
+      return false;
+    case FUNCTION:
+      return s->u.function.read (line, &s->fn, &s->ln, s->u.function.aux);
+    case INTERACTIVE:
+      return s->u.interactive (line, get_prompt ());
+    }
+
+  abort ();
+}
+
+/* Reads a single line into LINE.
+   Returns true when a line has been read, false at end of input.
+   If INTERACTIVE is non-null, then when true is returned
+   *INTERACTIVE will be set to true if the line was obtained
+   interactively, false otherwise. */
+static bool
+do_read_line (struct string *line, bool *interactive)
+{
+  while (cur_source != NULL)
+    {
+      struct getl_source *s = cur_source;
+      if (read_line_from_source (line, s))
+        {
+          if (interactive != NULL)
+            *interactive = s->type == INTERACTIVE;
+
+          while ((s = s->included_from) != NULL)
+            if (s->type == FILTER)
+              s->u.filter.filter (line, s->u.filter.aux);
+
+          return true;
+        }
+      close_source ();
+    }
+
+  return false;
+}
+
+/* Reads a single line into getl_buf.
+   Returns true when a line has been read, false at end of input.
+   If INTERACTIVE is non-null, then when true is returned
+   *INTERACTIVE will be set to true if the line was obtained
+   interactively, false otherwise. */
+bool
+getl_read_line (bool *interactive)
+{
+  return do_read_line (&getl_buf, interactive);
+}
+\f
+/* Current prompts in each style. */
+static char *prompts[GETL_PROMPT_CNT];
+
+/* Current prompting style. */
+static enum getl_prompt_style current_style;
+
+/* Initializes prompts. */
+static void
+init_prompts (void) 
+{
+  prompts[GETL_PROMPT_FIRST] = xstrdup ("PSPP> ");
+  prompts[GETL_PROMPT_LATER] = xstrdup ("    > ");
+  prompts[GETL_PROMPT_DATA] = xstrdup ("data> ");
+  current_style = GETL_PROMPT_FIRST;
+}
+
+/* Frees prompts. */
+static void
+uninit_prompts (void) 
+{
+  int i;
+
+  for (i = 0; i < GETL_PROMPT_CNT; i++) 
+    {
+      free (prompts[i]);
+      prompts[i] = NULL;
+    }
+}
+
+/* Gets the command prompt for the given STYLE. */
+const char * 
+getl_get_prompt (enum getl_prompt_style style)
+{
+  assert (style < GETL_PROMPT_CNT);
+  return prompts[style];
+}
+
+/* Sets the given STYLE's prompt to STRING. */
+void
+getl_set_prompt (enum getl_prompt_style style, const char *string)
+{
+  assert (style < GETL_PROMPT_CNT);
+  free (prompts[style]);
+  prompts[style] = xstrdup (string);
+}
+
+/* Sets STYLE as the current prompt style. */
+void
+getl_set_prompt_style (enum getl_prompt_style style) 
+{
+  assert (style < GETL_PROMPT_CNT);
+  current_style = style;
+}
+
+/* Returns the current prompt. */
+static const char *
+get_prompt (void) 
+{
+  return prompts[current_style];
+}
diff --git a/src/language/line-buffer.h b/src/language/line-buffer.h
new file mode 100644 (file)
index 0000000..1ca8a7b
--- /dev/null
@@ -0,0 +1,68 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef GETL_H
+#define GETL_H 1
+
+#include <stdbool.h>
+#include "str.h"
+
+/* Current line.  This line may be modified by modules other than
+   getl.c, and by lexer.c in particular.  (Ugh.) */
+extern struct string getl_buf;
+
+void getl_initialize (void);
+void getl_uninitialize (void);
+
+void getl_clear_include_path (void);
+void getl_add_include_dir (const char *);
+
+void getl_append_syntax_file (const char *);
+void getl_include_syntax_file (const char *);
+void getl_include_filter (void (*filter) (struct string *, void *aux),
+                          void (*close) (void *aux),
+                          void *aux);
+void getl_include_function (bool (*read) (struct string *line,
+                                          char **fn, int *ln, void *aux),
+                            void (*close) (void *aux),
+                            void *aux);
+void getl_append_interactive (bool (*function) (struct string *line,
+                                                const char *prompt));
+void getl_abort_noninteractive (void);
+bool getl_is_interactive (void);
+
+bool getl_read_line (bool *interactive);
+
+void getl_location (const char **, int *);
+\f
+/* Prompting. */
+
+enum getl_prompt_style
+  {
+    GETL_PROMPT_FIRST,         /* First line of command. */
+    GETL_PROMPT_LATER,           /* Second or later line of command. */
+    GETL_PROMPT_DATA,          /* Between BEGIN DATA and END DATA. */
+    GETL_PROMPT_CNT
+  };
+
+const char *getl_get_prompt (enum getl_prompt_style);
+void getl_set_prompt (enum getl_prompt_style, const char *);
+void getl_set_prompt_style (enum getl_prompt_style);
+
+#endif /* line-buffer.h */
diff --git a/src/language/stats/ChangeLog b/src/language/stats/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/language/stats/aggregate.c b/src/language/stats/aggregate.c
new file mode 100644 (file)
index 0000000..0485cca
--- /dev/null
@@ -0,0 +1,1093 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <stdlib.h>
+#include "alloc.h"
+#include "any-writer.h"
+#include "case.h"
+#include "casefile.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "file-handle-def.h"
+#include "lexer.h"
+#include "misc.h"
+#include "moments.h"
+#include "pool.h"
+#include "settings.h"
+#include "sys-file-writer.h"
+#include "sort.h"
+#include "str.h"
+#include "variable.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* 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;
+    struct moments1 *moments;
+  };
+
+/* 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. */
+  };
+
+/* Attributes of an aggregation function. */
+struct agr_func
+  {
+    const char *name;          /* Aggregation function name. */
+    size_t 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 const 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}},
+  };
+
+/* Missing value types. */
+enum missing_treatment
+  {
+    ITEMWISE,          /* Missing values item by item. */
+    COLUMNWISE         /* Missing values column by column. */
+  };
+
+/* An entire AGGREGATE procedure. */
+struct agr_proc 
+  {
+    /* We have either an output file or a sink. */
+    struct any_writer *writer;          /* Output file, or null if none. */
+    struct case_sink *sink;             /* Sink, or null if none. */
+
+    /* Break variables. */
+    struct sort_criteria *sort;         /* Sort criteria. */
+    struct variable **break_vars;       /* Break variables. */
+    size_t break_var_cnt;               /* Number of break variables. */
+    struct ccase break_case;            /* Last values of break variables. */
+
+    enum missing_treatment missing;     /* How to treat missing values. */
+    struct agr_var *agr_vars;           /* First aggregate variable. */
+    struct dictionary *dict;            /* Aggregate dictionary. */
+    int case_cnt;                       /* Counts aggregated cases. */
+    struct ccase agr_case;              /* Aggregate case for output. */
+  };
+
+static void initialize_aggregate_info (struct agr_proc *,
+                                       const struct ccase *);
+
+/* Prototypes. */
+static int parse_aggregate_functions (struct agr_proc *);
+static void agr_destroy (struct agr_proc *);
+static int aggregate_single_case (struct agr_proc *agr,
+                                  const struct ccase *input,
+                                  struct ccase *output);
+static void dump_aggregate_info (struct agr_proc *agr, struct ccase *output);
+
+/* Aggregating to the active file. */
+static bool agr_to_active_file (struct ccase *, void *aux);
+
+/* Aggregating to a system file. */
+static bool presorted_agr_to_sysfile (struct ccase *, void *aux);
+\f
+/* Parsing. */
+
+/* Parses and executes the AGGREGATE procedure. */
+int
+cmd_aggregate (void)
+{
+  struct agr_proc agr;
+  struct file_handle *out_file = NULL;
+
+  bool copy_documents = false;
+  bool presorted = false;
+  bool saw_direction;
+
+  memset(&agr, 0 , sizeof (agr));
+  agr.missing = ITEMWISE;
+  case_nullify (&agr.break_case);
+  
+  agr.dict = dict_create ();
+  dict_set_label (agr.dict, dict_get_label (default_dict));
+  dict_set_documents (agr.dict, dict_get_documents (default_dict));
+
+  /* OUTFILE subcommand must be first. */
+  if (!lex_force_match_id ("OUTFILE"))
+    goto error;
+  lex_match ('=');
+  if (!lex_match ('*'))
+    {
+      out_file = fh_parse (FH_REF_FILE | FH_REF_SCRATCH);
+      if (out_file == NULL)
+        goto error;
+    }
+  
+  /* Read most of the subcommands. */
+  for (;;)
+    {
+      lex_match ('/');
+      
+      if (lex_match_id ("MISSING"))
+       {
+         lex_match ('=');
+         if (!lex_match_id ("COLUMNWISE"))
+           {
+             lex_error (_("while expecting COLUMNWISE"));
+              goto error;
+           }
+         agr.missing = COLUMNWISE;
+       }
+      else if (lex_match_id ("DOCUMENT"))
+        copy_documents = true;
+      else if (lex_match_id ("PRESORTED"))
+        presorted = true;
+      else if (lex_match_id ("BREAK"))
+       {
+          int i;
+
+         lex_match ('=');
+          agr.sort = sort_parse_criteria (default_dict,
+                                          &agr.break_vars, &agr.break_var_cnt,
+                                          &saw_direction, NULL);
+          if (agr.sort == NULL)
+            goto error;
+         
+          for (i = 0; i < agr.break_var_cnt; i++)
+            dict_clone_var_assert (agr.dict, agr.break_vars[i],
+                                   agr.break_vars[i]->name);
+
+          /* BREAK must follow the options. */
+          break;
+       }
+      else
+        {
+          lex_error (_("expecting BREAK"));
+          goto error;
+        }
+    }
+  if (presorted && saw_direction)
+    msg (SW, _("When PRESORTED is specified, specifying sorting directions "
+               "with (A) or (D) has no effect.  Output data will be sorted "
+               "the same way as the input data."));
+      
+  /* Read in the aggregate functions. */
+  lex_match ('/');
+  if (!parse_aggregate_functions (&agr))
+    goto error;
+
+  /* Delete documents. */
+  if (!copy_documents)
+    dict_set_documents (agr.dict, NULL);
+
+  /* Cancel SPLIT FILE. */
+  dict_set_split_vars (agr.dict, NULL, 0);
+  
+  /* Initialize. */
+  agr.case_cnt = 0;
+  case_create (&agr.agr_case, dict_get_next_value_idx (agr.dict));
+
+  /* Output to active file or external file? */
+  if (out_file == NULL) 
+    {
+      /* The active file will be replaced by the aggregated data,
+         so TEMPORARY is moot. */
+      cancel_temporary ();
+
+      if (agr.sort != NULL && !presorted) 
+        {
+          if (!sort_active_file_in_place (agr.sort))
+            goto error;
+        }
+
+      agr.sink = create_case_sink (&storage_sink_class, agr.dict, NULL);
+      if (agr.sink->class->open != NULL)
+        agr.sink->class->open (agr.sink);
+      vfm_sink = create_case_sink (&null_sink_class, default_dict, NULL);
+      if (!procedure (agr_to_active_file, &agr))
+        goto error;
+      if (agr.case_cnt > 0) 
+        {
+          dump_aggregate_info (&agr, &agr.agr_case);
+          if (!agr.sink->class->write (agr.sink, &agr.agr_case))
+            goto error;
+        }
+      dict_destroy (default_dict);
+      default_dict = agr.dict;
+      agr.dict = NULL;
+      vfm_source = agr.sink->class->make_source (agr.sink);
+      free_case_sink (agr.sink);
+    }
+  else
+    {
+      agr.writer = any_writer_open (out_file, agr.dict);
+      if (agr.writer == NULL)
+        goto error;
+      
+      if (agr.sort != NULL && !presorted) 
+        {
+          /* Sorting is needed. */
+          struct casefile *dst;
+          struct casereader *reader;
+          struct ccase c;
+          bool ok = true;
+          
+          dst = sort_active_file_to_casefile (agr.sort);
+          if (dst == NULL)
+            goto error;
+          reader = casefile_get_destructive_reader (dst);
+          while (ok && casereader_read_xfer (reader, &c)) 
+            {
+              if (aggregate_single_case (&agr, &c, &agr.agr_case)) 
+                ok = any_writer_write (agr.writer, &agr.agr_case);
+              case_destroy (&c);
+            }
+          casereader_destroy (reader);
+          if (ok)
+            ok = !casefile_error (dst);
+          casefile_destroy (dst);
+          if (!ok)
+            goto error;
+        }
+      else 
+        {
+          /* Active file is already sorted. */
+          if (!procedure (presorted_agr_to_sysfile, &agr))
+            goto error;
+        }
+      
+      if (agr.case_cnt > 0) 
+        {
+          dump_aggregate_info (&agr, &agr.agr_case);
+          any_writer_write (agr.writer, &agr.agr_case);
+        }
+      if (any_writer_error (agr.writer))
+        goto error;
+    }
+  
+  agr_destroy (&agr);
+  return CMD_SUCCESS;
+
+error:
+  agr_destroy (&agr);
+  return CMD_CASCADING_FAILURE;
+}
+
+/* Parse all the aggregate functions. */
+static int
+parse_aggregate_functions (struct agr_proc *agr)
+{
+  struct agr_var *tail; /* Tail of linked list starting at agr->vars. */
+
+  /* Parse everything. */
+  tail = NULL;
+  for (;;)
+    {
+      char **dest;
+      char **dest_label;
+      size_t n_dest;
+
+      int include_missing;
+      const struct agr_func *function;
+      int func_index;
+
+      union value arg[2];
+
+      struct variable **src;
+      size_t n_src;
+
+      size_t i;
+
+      dest = NULL;
+      dest_label = NULL;
+      n_dest = 0;
+      src = NULL;
+      function = NULL;
+      n_src = 0;
+      arg[0].c = NULL;
+      arg[1].c = NULL;
+
+      /* Parse the list of target variables. */
+      while (!lex_match ('='))
+       {
+         size_t n_dest_prev = n_dest;
+         
+         if (!parse_DATA_LIST_vars (&dest, &n_dest,
+                                     PV_APPEND | PV_SINGLE | PV_NO_SCRATCH))
+           goto error;
+
+         /* Assign empty labels. */
+         {
+           int j;
+
+           dest_label = xnrealloc (dest_label, n_dest, sizeof *dest_label);
+           for (j = n_dest_prev; j < n_dest; j++)
+             dest_label[j] = NULL;
+         }
+         
+         if (token == T_STRING)
+           {
+             ds_truncate (&tokstr, 255);
+             dest_label[n_dest - 1] = xstrdup (ds_c_str (&tokstr));
+             lex_get ();
+           }
+       }
+
+      /* Get the name of the aggregation function. */
+      if (token != T_ID)
+       {
+         lex_error (_("expecting aggregation function"));
+         goto error;
+       }
+
+      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 (!strcasecmp (function->name, tokid))
+         break;
+      if (NULL == function->name)
+       {
+         msg (SE, _("Unknown aggregation function %s."), tokid);
+         goto error;
+       }
+      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 error;
+           }
+       }
+      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 error;
+         }
+
+         /* 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_c_str (&tokstr));
+                   type = ALPHA;
+                 }
+               else if (lex_is_number ())
+                 {
+                   arg[i].f = tokval;
+                   type = NUMERIC;
+                 } else {
+                   msg (SE, _("Missing argument %d to %s."), i + 1,
+                         function->name);
+                   goto error;
+                 }
+           
+               lex_get ();
+
+               if (type != src[0]->type)
+                 {
+                   msg (SE, _("Arguments to %s must be of same type as "
+                              "source variables."),
+                        function->name);
+                   goto error;
+                 }
+             }
+
+         /* Trailing rparen. */
+         if (!lex_match(')'))
+           {
+             lex_error (_("expecting `)'"));
+             goto error;
+           }
+         
+         /* Now check that the number of source variables match
+            the number of target variables.  If we check earlier
+            than this, the user can get very misleading error
+            message, 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 (%u) does not match "
+                        "number of target variables (%u)."),
+                  (unsigned) n_src, (unsigned) n_dest);
+             goto error;
+           }
+
+          if ((func_index == PIN || func_index == POUT
+              || func_index == FIN || func_index == FOUT) 
+              && ((src[0]->type == NUMERIC && arg[0].f > arg[1].f)
+                  || (src[0]->type == ALPHA
+                      && str_compare_rpad (arg[0].c, arg[1].c) > 0)))
+            {
+              union value t = arg[0];
+              arg[0] = arg[1];
+              arg[1] = t;
+                  
+              msg (SW, _("The value arguments passed to the %s function "
+                         "are out-of-order.  They will be treated as if "
+                         "they had been specified in the correct order."),
+                   function->name);
+            }
+       }
+       
+      /* 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->agr_vars != NULL)
+           tail->next = v;
+         else
+           agr->agr_vars = v;
+          tail = v;
+         tail->next = NULL;
+          v->moments = NULL;
+         
+         /* Create the target variable in the aggregate
+             dictionary. */
+         {
+           struct variable *destvar;
+           
+           v->function = func_index;
+
+           if (src)
+             {
+               v->src = src[i];
+               
+               if (src[i]->type == ALPHA)
+                 {
+                   v->function |= FSTRING;
+                   v->string = xmalloc (src[i]->width);
+                 }
+
+               if (function->alpha_type == ALPHA)
+                 destvar = dict_clone_var (agr->dict, v->src, dest[i]);
+               else
+                  {
+                    assert (v->src->type == NUMERIC
+                            || function->alpha_type == NUMERIC);
+                    destvar = dict_create_var (agr->dict, dest[i], 0);
+                    if (destvar != NULL) 
+                      {
+                        if ((func_index == N || func_index == NMISS)
+                            && dict_get_weight (default_dict) != NULL)
+                          destvar->print = destvar->write = f8_2; 
+                        else
+                          destvar->print = destvar->write = function->format;
+                      }
+                  }
+             } else {
+               v->src = NULL;
+               destvar = dict_create_var (agr->dict, dest[i], 0);
+                if (func_index == N_NO_VARS
+                    && dict_get_weight (default_dict) != NULL)
+                  destvar->print = destvar->write = f8_2; 
+                else
+                  destvar->print = destvar->write = function->format;
+             }
+         
+           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]);
+               goto error;
+             }
+
+           free (dest[i]);
+            destvar->init = 0;
+           if (dest_label[i])
+             {
+               destvar->label = dest_label[i];
+               dest_label[i] = NULL;
+             }
+
+           v->dest = destvar;
+         }
+         
+         v->include_missing = include_missing;
+
+         if (v->src != NULL)
+           {
+             int j;
+
+             if (v->src->type == NUMERIC)
+               for (j = 0; j < function->n_args; j++)
+                 v->arg[j].f = arg[j].f;
+             else
+               for (j = 0; j < function->n_args; j++)
+                 v->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;
+      
+    error:
+      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;
+    }
+}
+
+/* Destroys AGR. */
+static void
+agr_destroy (struct agr_proc *agr)
+{
+  struct agr_var *iter, *next;
+
+  any_writer_close (agr->writer);
+  if (agr->sort != NULL)
+    sort_destroy_criteria (agr->sort);
+  free (agr->break_vars);
+  case_destroy (&agr->break_case);
+  for (iter = agr->agr_vars; iter; iter = next)
+    {
+      next = iter->next;
+
+      if (iter->function & FSTRING)
+       {
+         size_t n_args;
+         size_t 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);
+       }
+      else if (iter->function == SD)
+        moments1_destroy (iter->moments);
+      free (iter);
+    }
+  if (agr->dict != NULL)
+    dict_destroy (agr->dict);
+
+  case_destroy (&agr->agr_case);
+}
+\f
+/* Execution. */
+
+static void accumulate_aggregate_info (struct agr_proc *,
+                                       const struct ccase *);
+static void dump_aggregate_info (struct agr_proc *, struct ccase *);
+
+/* Processes a single case INPUT for aggregation.  If output is
+   warranted, writes it to OUTPUT and returns nonzero.
+   Otherwise, returns zero and OUTPUT is unmodified. */
+static int
+aggregate_single_case (struct agr_proc *agr,
+                       const struct ccase *input, struct ccase *output)
+{
+  bool finished_group = false;
+  
+  if (agr->case_cnt++ == 0)
+    initialize_aggregate_info (agr, input);
+  else if (case_compare (&agr->break_case, input,
+                         agr->break_vars, agr->break_var_cnt))
+    {
+      dump_aggregate_info (agr, output);
+      finished_group = true;
+
+      initialize_aggregate_info (agr, input);
+    }
+
+  accumulate_aggregate_info (agr, input);
+  return finished_group;
+}
+
+/* Accumulates aggregation data from the case INPUT. */
+static void 
+accumulate_aggregate_info (struct agr_proc *agr,
+                           const struct ccase *input)
+{
+  struct agr_var *iter;
+  double weight;
+  int bad_warn = 1;
+
+  weight = dict_get_case_weight (default_dict, input, &bad_warn);
+
+  for (iter = agr->agr_vars; iter; iter = iter->next)
+    if (iter->src)
+      {
+       const union value *v = case_data (input, iter->src->fv);
+
+       if ((!iter->include_missing
+             && mv_is_value_missing (&iter->src->miss, v))
+           || (iter->include_missing && iter->src->type == NUMERIC
+               && v->f == SYSMIS))
+         {
+           switch (iter->function)
+             {
+             case NMISS:
+             case NMISS | FSTRING:
+               iter->dbl[0] += weight;
+                break;
+             case NUMISS:
+             case NUMISS | FSTRING:
+               iter->int1++;
+               break;
+             }
+           iter->missing = 1;
+           continue;
+         }
+       
+       /* This is horrible.  There are too many possibilities. */
+       switch (iter->function)
+         {
+         case SUM:
+           iter->dbl[0] += v->f * weight;
+            iter->int1 = 1;
+           break;
+         case MEAN:
+            iter->dbl[0] += v->f * weight;
+            iter->dbl[1] += weight;
+            break;
+         case SD:
+            moments1_add (iter->moments, v->f, weight);
+            break;
+         case MAX:
+           iter->dbl[0] = max (iter->dbl[0], v->f);
+           iter->int1 = 1;
+           break;
+         case MAX | FSTRING:
+           if (memcmp (iter->string, v->s, iter->src->width) < 0)
+             memcpy (iter->string, v->s, iter->src->width);
+           iter->int1 = 1;
+           break;
+         case MIN:
+           iter->dbl[0] = min (iter->dbl[0], v->f);
+           iter->int1 = 1;
+           break;
+         case MIN | FSTRING:
+           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 (v->f > iter->arg[0].f)
+              iter->dbl[0] += weight;
+            iter->dbl[1] += weight;
+            break;
+         case FGT | FSTRING:
+         case PGT | FSTRING:
+            if (memcmp (iter->arg[0].c, v->s, iter->src->width) < 0)
+              iter->dbl[0] += weight;
+            iter->dbl[1] += weight;
+            break;
+         case FLT:
+         case PLT:
+            if (v->f < iter->arg[0].f)
+              iter->dbl[0] += weight;
+            iter->dbl[1] += weight;
+            break;
+         case FLT | FSTRING:
+         case PLT | FSTRING:
+            if (memcmp (iter->arg[0].c, v->s, iter->src->width) > 0)
+              iter->dbl[0] += weight;
+            iter->dbl[1] += weight;
+            break;
+         case FIN:
+         case PIN:
+            if (iter->arg[0].f <= v->f && v->f <= iter->arg[1].f)
+              iter->dbl[0] += weight;
+            iter->dbl[1] += weight;
+            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->dbl[0] += weight;
+            iter->dbl[1] += weight;
+            break;
+         case FOUT:
+         case POUT:
+            if (iter->arg[0].f > v->f || v->f > iter->arg[1].f)
+              iter->dbl[0] += weight;
+            iter->dbl[1] += weight;
+            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->dbl[0] += weight;
+            iter->dbl[1] += weight;
+            break;
+         case N:
+         case N | FSTRING:
+           iter->dbl[0] += weight;
+           break;
+         case NU:
+         case NU | FSTRING:
+           iter->int1++;
+           break;
+         case FIRST:
+           if (iter->int1 == 0)
+             {
+               iter->dbl[0] = v->f;
+               iter->int1 = 1;
+             }
+           break;
+         case FIRST | FSTRING:
+           if (iter->int1 == 0)
+             {
+               memcpy (iter->string, v->s, iter->src->width);
+               iter->int1 = 1;
+             }
+           break;
+         case LAST:
+           iter->dbl[0] = v->f;
+           iter->int1 = 1;
+           break;
+         case LAST | FSTRING:
+           memcpy (iter->string, v->s, iter->src->width);
+           iter->int1 = 1;
+           break;
+          case NMISS:
+          case NMISS | FSTRING:
+          case NUMISS:
+          case NUMISS | FSTRING:
+            /* Our value is not missing or it would have been
+               caught earlier.  Nothing to do. */
+            break;
+         default:
+           assert (0);
+         }
+    } else {
+      switch (iter->function)
+       {
+       case N_NO_VARS:
+         iter->dbl[0] += weight;
+         break;
+       case NU_NO_VARS:
+         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 agr_proc *agr, struct ccase *output)
+{
+  {
+    int value_idx = 0;
+    int i;
+
+    for (i = 0; i < agr->break_var_cnt; i++) 
+      {
+        struct variable *v = agr->break_vars[i];
+        memcpy (case_data_rw (output, value_idx),
+                case_data (&agr->break_case, v->fv),
+                sizeof (union value) * v->nv);
+        value_idx += v->nv; 
+      }
+  }
+  
+  {
+    struct agr_var *i;
+  
+    for (i = agr->agr_vars; i; i = i->next)
+      {
+       union value *v = case_data_rw (output, i->dest->fv);
+
+       if (agr->missing == COLUMNWISE && i->missing != 0
+           && (i->function & FUNC) != N && (i->function & FUNC) != NU
+           && (i->function & FUNC) != NMISS && (i->function & FUNC) != NUMISS)
+         {
+           if (i->dest->type == ALPHA)
+             memset (v->s, ' ', i->dest->width);
+           else
+             v->f = SYSMIS;
+           continue;
+         }
+       
+       switch (i->function)
+         {
+         case SUM:
+           v->f = i->int1 ? i->dbl[0] : SYSMIS;
+           break;
+         case MEAN:
+           v->f = i->dbl[1] != 0.0 ? i->dbl[0] / i->dbl[1] : SYSMIS;
+           break;
+         case SD:
+            {
+              double variance;
+
+              /* FIXME: we should use two passes. */
+              moments1_calculate (i->moments, NULL, NULL, &variance,
+                                 NULL, NULL);
+              if (variance != SYSMIS)
+                v->f = sqrt (variance);
+              else
+                v->f = SYSMIS; 
+            }
+           break;
+         case MAX:
+         case MIN:
+           v->f = i->int1 ? i->dbl[0] : SYSMIS;
+           break;
+         case MAX | FSTRING:
+         case MIN | FSTRING:
+           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->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->dbl[1] ? i->dbl[0] / i->dbl[1] * 100.0 : SYSMIS;
+           break;
+         case N:
+         case N | FSTRING:
+           v->f = i->dbl[0];
+            break;
+         case NU:
+         case NU | FSTRING:
+           v->f = i->int1;
+           break;
+         case FIRST:
+         case LAST:
+           v->f = i->int1 ? i->dbl[0] : SYSMIS;
+           break;
+         case FIRST | FSTRING:
+         case LAST | FSTRING:
+           if (i->int1)
+             memcpy (v->s, i->string, i->dest->width);
+           else
+             memset (v->s, ' ', i->dest->width);
+           break;
+         case N_NO_VARS:
+           v->f = i->dbl[0];
+           break;
+         case NU_NO_VARS:
+           v->f = i->int1;
+           break;
+         case NMISS:
+         case NMISS | FSTRING:
+           v->f = i->dbl[0];
+           break;
+         case NUMISS:
+         case NUMISS | FSTRING:
+           v->f = i->int1;
+           break;
+         default:
+           assert (0);
+         }
+      }
+  }
+}
+
+/* Resets the state for all the aggregate functions. */
+static void
+initialize_aggregate_info (struct agr_proc *agr, const struct ccase *input)
+{
+  struct agr_var *iter;
+
+  case_destroy (&agr->break_case);
+  case_clone (&agr->break_case, input);
+
+  for (iter = agr->agr_vars; iter; iter = iter->next)
+    {
+      iter->missing = 0;
+      iter->dbl[0] = iter->dbl[1] = iter->dbl[2] = 0.0;
+      iter->int1 = iter->int2 = 0;
+      switch (iter->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;
+        case SD:
+          if (iter->moments == NULL)
+            iter->moments = moments1_create (MOMENT_VARIANCE);
+          else
+            moments1_clear (iter->moments);
+          break;
+        default:
+          break;
+       }
+    }
+}
+\f
+/* Aggregate each case as it comes through.  Cases which aren't needed
+   are dropped.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+agr_to_active_file (struct ccase *c, void *agr_)
+{
+  struct agr_proc *agr = agr_;
+
+  if (aggregate_single_case (agr, c, &agr->agr_case)) 
+    return agr->sink->class->write (agr->sink, &agr->agr_case);
+
+  return true;
+}
+
+/* Aggregate the current case and output it if we passed a
+   breakpoint. */
+static bool
+presorted_agr_to_sysfile (struct ccase *c, void *agr_) 
+{
+  struct agr_proc *agr = agr_;
+
+  if (aggregate_single_case (agr, c, &agr->agr_case)) 
+    return any_writer_write (agr->writer, &agr->agr_case);
+  
+  return true;
+}
diff --git a/src/language/stats/autorecode.c b/src/language/stats/autorecode.c
new file mode 100644 (file)
index 0000000..1ab48b9
--- /dev/null
@@ -0,0 +1,368 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <stdlib.h>
+#include "alloc.h"
+#include "case.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "hash.h"
+#include "lexer.h"
+#include "pool.h"
+#include "str.h"
+#include "variable.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* 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 pool *pool;         /* Contains AUTORECODE specs. */
+    struct arc_spec *specs;    /* AUTORECODE specifications. */
+    size_t spec_cnt;           /* Number of specifications. */
+  };
+
+/* Descending or ascending sort order. */
+enum direction 
+  {
+    ASCENDING,
+    DESCENDING
+  };
+
+/* AUTORECODE data. */
+struct autorecode_pgm 
+  {
+    struct variable **src_vars;    /* Source variables. */
+    char **dst_names;              /* Target variable names. */
+    struct variable **dst_vars;    /* Target variables. */
+    struct hsh_table **src_values; /* `union value's of source vars. */
+    size_t var_cnt;                /* Number of variables. */
+    struct pool *src_values_pool;  /* Pool used by src_values. */
+    enum direction direction;      /* Sort order. */
+    int print;                     /* Print mapping table if nonzero. */
+  };
+
+static trns_proc_func autorecode_trns_proc;
+static trns_free_func autorecode_trns_free;
+static bool autorecode_proc_func (struct ccase *, void *);
+static hsh_compare_func compare_alpha_value, compare_numeric_value;
+static hsh_hash_func hash_alpha_value, hash_numeric_value;
+
+static void recode (const struct autorecode_pgm *);
+static void arc_free (struct autorecode_pgm *);
+
+/* Performs the AUTORECODE procedure. */
+int
+cmd_autorecode (void)
+{
+  struct autorecode_pgm arc;
+  size_t dst_cnt;
+  size_t i;
+  bool ok;
+
+  arc.src_vars = NULL;
+  arc.dst_names = NULL;
+  arc.dst_vars = NULL;
+  arc.src_values = NULL;
+  arc.var_cnt = 0;
+  arc.src_values_pool = NULL;
+  arc.direction = ASCENDING;
+  arc.print = 0;
+  dst_cnt = 0;
+
+  lex_match_id ("VARIABLES");
+  lex_match ('=');
+  if (!parse_variables (default_dict, &arc.src_vars, &arc.var_cnt,
+                        PV_NO_DUPLICATE))
+    goto lossage;
+  if (!lex_force_match_id ("INTO"))
+    goto lossage;
+  lex_match ('=');
+  if (!parse_DATA_LIST_vars (&arc.dst_names, &dst_cnt, PV_NONE))
+    goto lossage;
+  if (dst_cnt != arc.var_cnt)
+    {
+      size_t i;
+
+      msg (SE, _("Source variable count (%u) does not match "
+                 "target variable count (%u)."),
+           (unsigned) arc.var_cnt, (unsigned) dst_cnt);
+
+      for (i = 0; i < dst_cnt; i++)
+        free (arc.dst_names[i]);
+      free (arc.dst_names);
+      arc.dst_names = NULL;
+
+      goto lossage;
+    }
+  while (lex_match ('/'))
+    if (lex_match_id ("DESCENDING"))
+      arc.direction = DESCENDING;
+    else if (lex_match_id ("PRINT"))
+      arc.print = 1;
+  if (token != '.')
+    {
+      lex_error (_("expecting end of command"));
+      goto lossage;
+    }
+
+  for (i = 0; i < arc.var_cnt; i++)
+    {
+      int j;
+
+      if (dict_lookup_var (default_dict, arc.dst_names[i]) != NULL)
+       {
+         msg (SE, _("Target variable %s duplicates existing variable %s."),
+              arc.dst_names[i], arc.dst_names[i]);
+         goto lossage;
+       }
+      for (j = 0; j < i; j++)
+       if (!strcasecmp (arc.dst_names[i], arc.dst_names[j]))
+         {
+           msg (SE, _("Duplicate variable name %s among target variables."),
+                arc.dst_names[i]);
+           goto lossage;
+         }
+    }
+
+  arc.src_values_pool = pool_create ();
+  arc.dst_vars = xnmalloc (arc.var_cnt, sizeof *arc.dst_vars);
+  arc.src_values = xnmalloc (arc.var_cnt, sizeof *arc.src_values);
+  for (i = 0; i < dst_cnt; i++)
+    if (arc.src_vars[i]->type == ALPHA)
+      arc.src_values[i] = hsh_create (10, compare_alpha_value,
+                                      hash_alpha_value, NULL, arc.src_vars[i]);
+    else
+      arc.src_values[i] = hsh_create (10, compare_numeric_value,
+                                      hash_numeric_value, NULL, NULL);
+
+  ok = procedure (autorecode_proc_func, &arc);
+
+  for (i = 0; i < arc.var_cnt; i++)
+    {
+      arc.dst_vars[i] = dict_create_var_assert (default_dict,
+                                                arc.dst_names[i], 0);
+      arc.dst_vars[i]->init = 0;
+    }
+
+  recode (&arc);
+  arc_free (&arc);
+  return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+
+lossage:
+  arc_free (&arc);
+  return CMD_CASCADING_FAILURE;
+}
+
+static void
+arc_free (struct autorecode_pgm *arc) 
+{
+  free (arc->src_vars);
+  if (arc->dst_names != NULL) 
+    {
+      size_t i;
+      
+      for (i = 0; i < arc->var_cnt; i++)
+        free (arc->dst_names[i]);
+      free (arc->dst_names);
+    }
+  free (arc->dst_vars);
+  if (arc->src_values != NULL) 
+    {
+      size_t i;
+
+      for (i = 0; i < arc->var_cnt; i++)
+        hsh_destroy (arc->src_values[i]);
+      free (arc->src_values);
+    }
+  pool_destroy (arc->src_values_pool);
+}
+
+\f
+/* AUTORECODE transformation. */
+
+static void
+recode (const struct autorecode_pgm *arc)
+{
+  struct autorecode_trns *trns;
+  size_t i;
+
+  trns = pool_create_container (struct autorecode_trns, pool);
+  trns->specs = pool_nalloc (trns->pool, arc->var_cnt, sizeof *trns->specs);
+  trns->spec_cnt = arc->var_cnt;
+  for (i = 0; i < arc->var_cnt; i++)
+    {
+      struct arc_spec *spec = &trns->specs[i];
+      void *const *p = hsh_sort (arc->src_values[i]);
+      int count = hsh_count (arc->src_values[i]);
+      int j;
+
+      spec->src = arc->src_vars[i];
+      spec->dest = arc->dst_vars[i];
+
+      if (arc->src_vars[i]->type == ALPHA)
+       spec->items = hsh_create (2 * count, compare_alpha_value,
+                                 hash_alpha_value, NULL, arc->src_vars[i]);
+      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 (trns->pool, sizeof *item);
+          union value *vp = *p;
+          
+         if (arc->src_vars[i]->type == NUMERIC)
+            item->from.f = vp->f;
+          else
+           item->from.c = pool_clone (trns->pool, vp->c,
+                                       arc->src_vars[i]->width);
+         item->to = arc->direction == ASCENDING ? j + 1 : count - j;
+         hsh_force_insert (spec->items, item);
+       }
+    }
+  add_transformation (autorecode_trns_proc, autorecode_trns_free, trns);
+}
+
+/* Executes an AUTORECODE transformation. */
+static int
+autorecode_trns_proc (void *trns_, struct ccase *c, int case_idx UNUSED)
+{
+  struct autorecode_trns *trns = trns_;
+  size_t i;
+
+  for (i = 0; i < trns->spec_cnt; i++)
+    {
+      struct arc_spec *spec = &trns->specs[i];
+      struct arc_item *item;
+      union value v;
+
+      if (spec->src->type == NUMERIC)
+        v.f = case_num (c, spec->src->fv);
+      else
+        v.c = (char *) case_str (c, spec->src->fv);
+      item = hsh_force_find (spec->items, &v);
+
+      case_data_rw (c, spec->dest->fv)->f = item->to;
+    }
+  return TRNS_CONTINUE;
+}
+
+/* Frees an AUTORECODE transformation. */
+static bool
+autorecode_trns_free (void *trns_)
+{
+  struct autorecode_trns *trns = trns_;
+  size_t i;
+
+  for (i = 0; i < trns->spec_cnt; i++)
+    hsh_destroy (trns->specs[i].items);
+  pool_destroy (trns->pool);
+  return true;
+}
+\f
+/* AUTORECODE procedure. */
+
+static int
+compare_alpha_value (const void *a_, const void *b_, void *v_)
+{
+  const union value *a = a_;
+  const union value *b = b_;
+  const struct variable *v = v_;
+
+  return memcmp (a->c, b->c, v->width);
+}
+
+static unsigned
+hash_alpha_value (const void *a_, void *v_)
+{
+  const union value *a = a_;
+  const struct variable *v = v_;
+  
+  return hsh_hash_bytes (a->c, v->width);
+}
+
+static int
+compare_numeric_value (const void *a_, const void *b_, void *foo UNUSED)
+{
+  const union value *a = a_;
+  const union value *b = b_;
+
+  return a->f < b->f ? -1 : a->f > b->f;
+}
+
+static unsigned
+hash_numeric_value (const void *a_, void *foo UNUSED)
+{
+  const union value *a = a_;
+
+  return hsh_hash_double (a->f);
+}
+
+static bool
+autorecode_proc_func (struct ccase *c, void *arc_)
+{
+  struct autorecode_pgm *arc = arc_;
+  size_t i;
+
+  for (i = 0; i < arc->var_cnt; i++)
+    {
+      union value v, *vp, **vpp;
+
+      if (arc->src_vars[i]->type == NUMERIC)
+        v.f = case_num (c, arc->src_vars[i]->fv);
+      else
+        v.c = (char *) case_str (c, arc->src_vars[i]->fv);
+
+      vpp = (union value **) hsh_probe (arc->src_values[i], &v);
+      if (*vpp == NULL)
+        {
+          vp = pool_alloc (arc->src_values_pool, sizeof *vp);
+          if (arc->src_vars[i]->type == NUMERIC)
+            vp->f = v.f;
+          else
+            vp->c = pool_clone (arc->src_values_pool,
+                                v.c, arc->src_vars[i]->width);
+          *vpp = vp;
+        }
+    }
+  return true;
+}
diff --git a/src/language/stats/correlations.q b/src/language/stats/correlations.q
new file mode 100644 (file)
index 0000000..db1b65b
--- /dev/null
@@ -0,0 +1,170 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "dictionary.h"
+#include "file-handle-def.h"
+#include "command.h"
+#include "lexer.h"
+#include "variable.h"
+/* (headers) */
+
+#include "debug-print.h"
+
+struct cor_set
+  {
+    struct cor_set *next;
+    struct variable **v1, **v2;
+    size_t 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;
+
+  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;
+  size_t nv1, nv2;
+  struct cor_set *cor;
+
+  /* Ensure that this is a VARIABLES subcommand. */
+  if (!lex_match_id ("VARIABLES")
+      && (token != T_ID || dict_lookup_var (default_dict, tokid) != NULL)
+      && 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 = NULL;
+  else 
+    {
+      matrix_file = fh_parse (FH_REF_FILE);
+      if (matrix_file == NULL)
+        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);
+    }
+}
+
+/*
+  Local Variables:
+  mode: c
+  End:
+*/
diff --git a/src/language/stats/crosstabs.q b/src/language/stats/crosstabs.q
new file mode 100644 (file)
index 0000000..1a6d569
--- /dev/null
@@ -0,0 +1,3202 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, 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.
+
+*/
+
+#include <config.h>
+#include "message.h"
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <gsl/gsl_cdf.h>
+#include "array.h"
+#include "alloc.h"
+#include "case.h"
+#include "dictionary.h"
+#include "hash.h"
+#include "pool.h"
+#include "command.h"
+#include "lexer.h"
+#include "message.h"
+#include "magic.h"
+#include "misc.h"
+#include "output.h"
+#include "str.h"
+#include "table.h"
+#include "value-labels.h"
+#include "variable.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+/* (headers) */
+
+#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,expected,row,column,total,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 values[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 *vars[2];  /* At least two variables; sorted by
+                                  larger indices first. */
+  };
+
+/* Integer mode variable info. */
+struct var_range
+  {
+    int min;                   /* Minimum value. */
+    int max;                   /* Maximum value + 1. */
+    int count;                 /* max - min. */
+  };
+
+static inline struct var_range *
+get_var_range (struct variable *v) 
+{
+  assert (v != NULL);
+  assert (v->aux != NULL);
+  return v->aux;
+}
+
+/* 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 specifies on VARIABLES. */
+static struct variable **variables;
+static size_t variables_cnt;
+
+/* 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. */
+
+/* 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 precalc (void *);
+static bool calc_general (struct ccase *, void *);
+static bool calc_integer (struct ccase *, void *);
+static void postcalc (void *);
+static void submit (struct tab_table *);
+
+static void format_short (char *s, const struct fmt_spec *fp,
+                         const union value *v);
+
+/* Parse and execute CROSSTABS, then clean up. */
+int
+cmd_crosstabs (void)
+{
+  int result = internal_cmd_crosstabs ();
+
+  free (variables);
+  pool_destroy (pl_tc);
+  pool_destroy (pl_col);
+  
+  return result;
+}
+
+/* Parses and executes the CROSSTABS procedure. */
+static int
+internal_cmd_crosstabs (void)
+{
+  int i;
+  bool ok;
+
+  variables = NULL;
+  variables_cnt = 0;
+  xtab = NULL;
+  nxtab = 0;
+  pl_tc = pool_create ();
+  pl_col = pool_create ();
+
+  if (!parse_crosstabs (&cmd))
+    return CMD_FAILURE;
+
+  mode = variables ? INTEGER : GENERAL;
+
+  /* CELLS. */
+  if (!cmd.sbc_cells)
+    {
+      cmd.a_cells[CRS_CL_COUNT] = 1;
+    }
+  else 
+    {
+      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])
+      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;
+
+  ok = procedure_with_splits (precalc,
+                              mode == GENERAL ? calc_general : calc_integer,
+                              postcalc, NULL);
+
+  return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+}
+
+/* Parses the TABLES subcommand. */
+static int
+crs_custom_tables (struct cmd_crosstabs *cmd UNUSED)
+{
+  struct var_set *var_set;
+  int n_by;
+  struct variable ***by = NULL;
+  size_t *by_nvar = NULL;
+  size_t nx = 1;
+  int success = 0;
+
+  /* Ensure that this is a TABLES subcommand. */
+  if (!lex_match_id ("TABLES")
+      && (token != T_ID || dict_lookup_var (default_dict, tokid) == NULL)
+      && token != T_ALL)
+    return 2;
+  lex_match ('=');
+
+  if (variables != NULL)
+    var_set = var_set_create_from_array (variables, variables_cnt);
+  else
+    var_set = var_set_create_from_dict (default_dict);
+  assert (var_set != NULL);
+  
+  for (n_by = 0; ;)
+    {
+      by = xnrealloc (by, n_by + 1, sizeof *by);
+      by_nvar = xnrealloc (by_nvar, n_by + 1, sizeof *by_nvar);
+      if (!parse_var_set_vars (var_set, &by[n_by], &by_nvar[n_by],
+                               PV_NO_DUPLICATE | PV_NO_SCRATCH))
+       goto done;
+      if (xalloc_oversized (nx, by_nvar[n_by])) 
+        {
+          msg (SE, _("Too many crosstabulation variables or dimensions."));
+          goto done;
+        }
+      nx *= by_nvar[n_by];
+      n_by++;
+
+      if (!lex_match (T_BY))
+       {
+         if (n_by < 2)
+           {
+             lex_error (_("expecting BY"));
+             goto done;
+           }
+         else 
+           break;
+       }
+    }
+  
+  {
+    int *by_iter = xcalloc (n_by, sizeof *by_iter);
+    int i;
+
+    xtab = xnrealloc (xtab, nxtab + nx, sizeof *xtab);
+    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;
+
+          for (i = 0; i < n_by; i++)
+            x->vars[i] = by[i][by_iter[i]];
+       }
+       
+       {
+         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;
+
+ done:
+  /* All return paths lead here. */
+  {
+    int i;
+
+    for (i = 0; i < n_by; i++)
+      free (by[i]);
+    free (by);
+    free (by_nvar);
+  }
+
+  var_set_destroy (var_set);
+
+  return success;
+}
+
+/* Parses the VARIABLES subcommand. */
+static int
+crs_custom_variables (struct cmd_crosstabs *cmd UNUSED)
+{
+  if (nxtab)
+    {
+      msg (SE, _("VARIABLES must be specified before TABLES."));
+      return 0;
+    }
+
+  lex_match ('=');
+  
+  for (;;)
+    {
+      size_t orig_nv = variables_cnt;
+      size_t i;
+
+      long min, max;
+      
+      if (!parse_variables (default_dict, &variables, &variables_cnt,
+                           (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 < variables_cnt; i++) 
+        {
+          struct var_range *vr = xmalloc (sizeof *vr);
+          vr->min = min;
+         vr->max = max + 1.;
+         vr->count = max - min + 1;
+          var_attach_aux (variables[i], vr, var_dtor_free);
+       }
+      
+      if (token == '/')
+       break;
+    }
+  
+  return 1;
+
+ lossage:
+  free (variables);
+  variables = NULL;
+  return 0;
+}
+\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 *aux UNUSED)
+{
+  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 *= get_var_range (x->vars[j - 2])->count;
+          
+         sorted_tab = xnrealloc (sorted_tab,
+                                  n_sorted_tab + count, sizeof *sorted_tab);
+         v = local_alloc (sizeof *v * x->nvar);
+         for (j = 2; j < x->nvar; j++) 
+            v[j] = get_var_range (x->vars[j])->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;
+             
+             {
+                int row_cnt = get_var_range (x->vars[0])->count;
+                int col_cnt = get_var_range (x->vars[1])->count;
+               const int mat_size = row_cnt * col_cnt;
+               int m;
+               
+               te->u.data = xnmalloc (mat_size, sizeof *te->u.data);
+               for (m = 0; m < mat_size; m++)
+                 te->u.data[m] = 0.;
+             }
+             
+             for (k = 2; k < x->nvar; k++)
+               te->values[k].f = v[k];
+             for (k = 2; k < x->nvar; k++) 
+                {
+                  struct var_range *vr = get_var_range (x->vars[k]);
+                  if (++v[k] >= vr->max)
+                    v[k] = vr->min;
+                  else
+                    break; 
+                }
+           }
+         local_free (v);
+       }
+
+      sorted_tab = xnrealloc (sorted_tab,
+                              n_sorted_tab + 1, sizeof *sorted_tab);
+      sorted_tab[n_sorted_tab] = NULL;
+    }
+}
+
+/* Form crosstabulations for general mode. */
+static bool
+calc_general (struct ccase *c, void *aux UNUSED)
+{
+  int bad_warn = 1;
+
+  /* Case weight. */
+  double weight = dict_get_case_weight (default_dict, c, &bad_warn);
+
+  /* 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++)
+         {
+            const union value *v = case_data (c, x->vars[j]->fv);
+            const struct missing_values *mv = &x->vars[j]->miss;
+           if ((cmd.miss == CRS_TABLE && mv_is_value_missing (mv, v))
+               || (cmd.miss == CRS_INCLUDE
+                   && mv_is_value_system_missing (mv, v)))
+             {
+               x->missing += weight;
+               goto next_crosstab;
+             }
+             
+           if (x->vars[j]->type == NUMERIC)
+             te->values[j].f = case_num (c, x->vars[j]->fv);
+           else
+             {
+               memcpy (te->values[j].s, case_str (c, x->vars[j]->fv),
+                        x->vars[j]->width);
+             
+               /* Necessary in order to simplify comparisons. */
+               memset (&te->values[j].s[x->vars[j]->width], 0,
+                       sizeof (union value) - x->vars[j]->width);
+             }
+         }
+      }
+
+      /* Add record to hash table. */
+      {
+       struct table_entry **tepp
+          = (struct table_entry **) hsh_probe (gen_tab, te);
+       if (*tepp == NULL)
+         {
+           struct table_entry *tep = pool_alloc (pl_tc, entry_size);
+           
+           te->u.freq = weight;
+           memcpy (tep, te, entry_size);
+           
+           *tepp = tep;
+         }
+       else
+         (*tepp)->u.freq += weight;
+      }
+
+    next_crosstab:
+      local_free (te);
+    }
+  
+  return true;
+}
+
+static bool
+calc_integer (struct ccase *c, void *aux UNUSED)
+{
+  int bad_warn = 1;
+
+  /* Case weight. */
+  double weight = dict_get_case_weight (default_dict, c, &bad_warn);
+  
+  /* 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->vars[i];
+          struct var_range *vr = get_var_range (v);
+         double value = case_num (c, v->fv);
+         
+         /* Note that the first test also rules out SYSMIS. */
+         if ((value < vr->min || value >= vr->max)
+             || (cmd.miss == CRS_TABLE
+                  && mv_is_num_user_missing (&v->miss, value)))
+           {
+             x->missing += weight;
+             goto next_crosstab;
+           }
+         
+         if (i > 1)
+           {
+             ofs += fact * ((int) value - vr->min);
+             fact *= vr->count;
+           }
+       }
+      
+      {
+        struct variable *row_var = x->vars[ROW_VAR];
+       const int row = case_num (c, row_var->fv) - get_var_range (row_var)->min;
+
+        struct variable *col_var = x->vars[COL_VAR];
+       const int col = case_num (c, col_var->fv) - get_var_range (col_var)->min;
+
+       const int col_dim = get_var_range (col_var)->count;
+
+       sorted_tab[ofs]->u.data[col + row * col_dim] += weight;
+      }
+      
+    next_crosstab: ;
+    }
+  
+  return true;
+}
+
+/* Compare the table_entry's at A and B and return a strcmp()-type
+   result. */
+static int 
+compare_table_entry (const void *a_, const void *b_, void *foo UNUSED)
+{
+  const struct table_entry *a = a_;
+  const struct table_entry *b = b_;
+
+  if (a->table > b->table)
+    return 1;
+  else if (a->table < b->table)
+    return -1;
+  
+  {
+    const struct crosstab *x = xtab[a->table];
+    int i;
+
+    for (i = x->nvar - 1; i >= 0; i--)
+      if (x->vars[i]->type == NUMERIC)
+       {
+         const double diffnum = a->values[i].f - b->values[i].f;
+         if (diffnum < 0)
+           return -1;
+         else if (diffnum > 0)
+           return 1;
+       }
+      else 
+       {
+         assert (x->vars[i]->type == ALPHA);
+         {
+           const int diffstr = strncmp (a->values[i].s, b->values[i].s,
+                                         x->vars[i]->width);
+           if (diffstr)
+             return diffstr;
+         }
+       }
+  }
+  
+  return 0;
+}
+
+/* Calculate a hash value from table_entry A. */
+static unsigned
+hash_table_entry (const void *a_, void *foo UNUSED)
+{
+  const struct table_entry *a = a_;
+  unsigned long hash;
+  int i;
+
+  hash = a->table;
+  for (i = 0; i < xtab[a->table]->nvar; i++)
+    hash ^= hsh_hash_bytes (&a->values[i], sizeof a->values[i]);
+  
+  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 **entries, int entry_cnt,
+                             int var_idx,
+                             union value **values, int *value_cnt);
+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 *aux UNUSED)
+{
+  if (mode == GENERAL)
+    {
+      n_sorted_tab = hsh_count (gen_tab);
+      sorted_tab = (struct table_entry **) hsh_sort (gen_tab);
+    }
+  
+  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 = get_var_range (x->vars[COL_VAR])->count;
+         const int n_rows = get_var_range (x->vars[ROW_VAR])->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->vars[i]->label ? x->vars[i]->label : x->vars[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 = NULL;
+
+  x = xtab[(*pb)->table];
+  enum_var_values (pb, pe - pb, COL_VAR, &cols, &n_cols);
+
+  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->vars[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->vars[i]->label
+                           ? x->vars[i]->label : x->vars[i]->name));
+       tab_text (table, nvar - 2, 1, TAB_RIGHT | TAT_TITLE,
+                 x->vars[ROW_VAR]->name);
+       for (i = 0; i < n_cols; i++)
+         table_value_missing (table, nvar + i - 1, 1, TAB_RIGHT, &cols[i],
+                              x->vars[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->vars[i]->name);
+           }
+       else
+         {
+           cp = spprintf (cp, "%s by %s for",
+                           x->vars[0]->name, x->vars[1]->name);
+           for (i = 2; i < nvar; i++)
+             {
+               char buf[64], *bufp;
+
+               if (i > 2)
+                 *cp++ = ',';
+               *cp++ = ' ';
+               cp = stpcpy (cp, x->vars[i]->name);
+               *cp++ = '=';
+               format_short (buf, &x->vars[i]->print, &(*pb)->values[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, ROW_VAR, &rows, &n_rows);
+
+      /* Allocate memory space for the column and row totals. */
+      if (n_rows > *maxrows)
+       {
+         *row_totp = xnrealloc (*row_totp, n_rows, sizeof **row_totp);
+         row_tot = *row_totp;
+         *maxrows = n_rows;
+       }
+      if (n_cols > *maxcols)
+       {
+         *col_totp = xnrealloc (*col_totp, n_cols, sizeof **col_totp);
+         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 = xnrealloc (*matp, n_cols * n_rows, sizeof **matp);
+             *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)->values[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)->values[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;
+      }
+      
+      /* 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->values[first_difference],
+                         &(*tb)->values[first_difference],
+                          sizeof *cmp->values))
+               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 (mv_is_num_user_missing (&x->vars[ROW_VAR]->miss, rows[r].f))
+       {
+         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 (mv_is_num_user_missing (&x->vars[COL_VAR]->miss, cols[c].f))
+       {
+         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->vars[i]->label ? x->vars[i]->label : x->vars[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)->values[2], &fp->values[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
+   CROSSTABS wart 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)->values[2], &fp->values[2],
+                  sizeof (union value) * (x->nvar - 2)))
+       break;
+    }
+
+  return tp;
+}
+
+/* Compares `union value's A_ and B_ and returns a strcmp()-like
+   result.  WIDTH_ points to an int which is either 0 for a
+   numeric value or a string width for a string value. */
+static int
+compare_value (const void *a_, const void *b_, void *width_)
+{
+  const union value *a = a_;
+  const union value *b = b_;
+  const int *pwidth = width_;
+  const int width = *pwidth;
+
+  if (width == 0)
+    return (a->f < b->f) ? -1 : (a->f > b->f);
+  else
+    return strncmp (a->s, b->s, width);
+}
+
+/* Given an array of ENTRY_CNT table_entry structures starting at
+   ENTRIES, creates a sorted list of the values that the variable
+   with index VAR_IDX takes on.  The values are returned as a
+   malloc()'darray stored in *VALUES, with the number of values
+   stored in *VALUE_CNT.
+   */
+static void 
+enum_var_values (struct table_entry **entries, int entry_cnt, int var_idx,
+                 union value **values, int *value_cnt)
+{
+  struct variable *v = xtab[(*entries)->table]->vars[var_idx];
+
+  if (mode == GENERAL)
+    {
+      int width = v->width;
+      int i;
+
+      *values = xnmalloc (entry_cnt, sizeof **values);
+      for (i = 0; i < entry_cnt; i++)
+        (*values)[i] = entries[i]->values[var_idx];
+      *value_cnt = sort_unique (*values, entry_cnt, sizeof **values,
+                                compare_value, &width);
+    }
+  else
+    {
+      struct var_range *vr = get_var_range (v);
+      int i;
+      
+      assert (mode == INTEGER);
+      *values = xnmalloc (vr->count, sizeof **values);
+      for (i = 0; i < vr->count; i++)
+       (*values)[i].f = i + vr->min;
+      *value_cnt = vr->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 fixed_string s;
+
+  const char *label = val_labs_find (var->val_labs, *v);
+  if (label) 
+    {
+      tab_text (table, c, r, TAB_LEFT, label);
+      return;
+    }
+
+  s.string = tab_alloc (table, var->print.w);
+  format_short (s.string, &var->print, v);
+  s.length = strlen (s.string);
+  if (cmd.miss == CRS_REPORT && mv_is_num_user_missing (&var->miss, v->f))
+    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->values[first_difference],
+                        x->vars[first_difference]);
+}
+
+/* Put VALUE into cell (C,R) of TABLE, suffixed with character
+   SUFFIX if nonzero.  If MARK_MISSING is nonzero the entry is
+   additionally suffixed with a letter `M'. */
+static void
+format_cell_entry (struct tab_table *table, int c, int r, double value,
+                   char suffix, int mark_missing)
+{
+  const struct fmt_spec f = {FMT_F, 10, 1};
+  union value v;
+  struct fixed_string s;
+  
+  s.length = 10;
+  s.string = tab_alloc (table, 16);
+  v.f = value;
+  data_out (s.string, &f, &v);
+  while (*s.string == ' ')
+    {
+      s.length--;
+      s.string++;
+    }
+  if (suffix != 0)
+    s.string[s.length++] = suffix;
+  if (mark_missing)
+    s.string[s.length++] = 'M';
+
+  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->vars[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++)
+         {
+            int mark_missing = 0;
+            double expected_value = row_tot[r] * col_tot[c] / W;
+            if (cmd.miss == CRS_REPORT
+                && (mv_is_num_user_missing (&x->vars[COL_VAR]->miss, cols[c].f)
+                    || mv_is_num_user_missing (&x->vars[ROW_VAR]->miss,
+                                               rows[r].f)))
+              mark_missing = 1;
+           for (i = 0; i < num_cells; i++)
+             {
+               double v;
+                int suffix = 0;
+
+               switch (cells[i])
+                 {
+                 case CRS_CL_COUNT:
+                   v = *mp;
+                   break;
+                 case CRS_CL_ROW:
+                   v = *mp / row_tot[r] * 100.;
+                    suffix = '%';
+                   break;
+                 case CRS_CL_COLUMN:
+                   v = *mp / col_tot[c] * 100.;
+                    suffix = '%';
+                   break;
+                 case CRS_CL_TOTAL:
+                   v = *mp / W * 100.;
+                    suffix = '%';
+                   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);
+                    abort ();
+                 }
+
+                format_cell_entry (table, c, i, v, suffix, mark_missing);
+             }
+
+           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++) 
+      {
+        char suffix = 0;
+        int mark_missing = 0;
+
+        if (cmd.miss == CRS_REPORT
+            && mv_is_num_user_missing (&x->vars[ROW_VAR]->miss, rows[r].f))
+          mark_missing = 1;
+
+        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.;
+                suffix = '%';
+                break;
+              case CRS_CL_COLUMN:
+                v = row_tot[r] / W * 100.;
+                suffix = '%';
+                break;
+              case CRS_CL_TOTAL:
+                v = row_tot[r] / W * 100.;
+                suffix = '%';
+                break;
+              case CRS_CL_EXPECTED:
+              case CRS_CL_RESIDUAL:
+              case CRS_CL_SRESIDUAL:
+              case CRS_CL_ASRESIDUAL:
+                v = 0.;
+                break;
+              default:
+                assert (0);
+                abort ();
+              }
+
+            format_cell_entry (table, n_cols, 0, v, suffix, mark_missing);
+            tab_next_row (table);
+          } 
+      }
+  }
+
+  /* Column totals, grand total. */
+  {
+    int c;
+    int last_row = 0;
+
+    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 mark_missing = 0;
+        char suffix = 0;
+        int i;
+           
+        if (cmd.miss == CRS_REPORT && c < n_cols 
+            && mv_is_num_user_missing (&x->vars[COL_VAR]->miss, cols[c].f))
+          mark_missing = 1;
+
+        for (i = 0; i < num_cells; i++)
+         {
+           double v;
+
+           switch (cells[i])
+             {
+             case CRS_CL_COUNT:
+               v = ct;
+                suffix = '%';
+               break;
+             case CRS_CL_ROW:
+               v = ct / W * 100.;
+                suffix = '%';
+               break;
+             case CRS_CL_COLUMN:
+               v = 100.;
+                suffix = '%';
+               break;
+             case CRS_CL_TOTAL:
+               v = ct / W * 100.;
+                suffix = '%';
+               break;
+             case CRS_CL_EXPECTED:
+             case CRS_CL_RESIDUAL:
+             case CRS_CL_SRESIDUAL:
+             case CRS_CL_ASRESIDUAL:
+               continue;
+             default:
+               assert (0);
+                abort ();
+             }
+
+            format_cell_entry (table, c, i, v, suffix, mark_missing);
+         }
+        last_row = i;
+      }
+
+    tab_offset (table, -1, tab_row (table) + last_row);
+  }
+  
+  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,
+                    gsl_cdf_chisq_Q (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->vars[COL_VAR]->type == NUMERIC)
+           sprintf (buf, _("Odds Ratio for %s (%g / %g)"),
+                    x->vars[COL_VAR]->name, c[0].f, c[1].f);
+         else
+           sprintf (buf, _("Odds Ratio for %s (%.*s / %.*s)"),
+                    x->vars[COL_VAR]->name,
+                    x->vars[COL_VAR]->width, c[0].s,
+                    x->vars[COL_VAR]->width, c[1].s);
+         break;
+       case 1:
+       case 2:
+         if (x->vars[ROW_VAR]->type == NUMERIC)
+           sprintf (buf, _("For cohort %s = %g"),
+                    x->vars[ROW_VAR]->name, rows[i - 1].f);
+         else
+           sprintf (buf, _("For cohort %s = %.*s"),
+                    x->vars[ROW_VAR]->name,
+                    x->vars[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->vars[0]->name;
+                 else
+                   string = x->vars[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. */
+static 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;
+    
+        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->vars[ROW_VAR]->type == NUMERIC && x->vars[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;
+    
+              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 = xnmalloc (n_cols * n_rows, sizeof *cum);
+       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 * pow2 (Dr * (Cij - Dij)
+                                            - (P - Q) * (W - row_tot[i]));
+                   d_xy_cum += fij * pow2 (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 * pow2 (W * (P - Q) / sqrt (Dr * Dc) * (Dr + Dc))))
+                 / pow2 (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 - pow2 (P - Q) / W)));
+         somers_d_v[1] = (P - Q) / Dc;
+         somers_d_ase[1] = 2. / pow2 (Dc) * sqrt (d_xy_cum);
+         somers_d_t[1] = (somers_d_v[1]
+                          / (2. / Dc
+                             * sqrt (ctau_cum - pow2 (P - Q) / W)));
+         somers_d_v[2] = (P - Q) / Dr;
+         somers_d_ase[2] = 2. / pow2 (Dr) * sqrt (d_yx_cum);
+         somers_d_t[2] = (somers_d_v[2]
+                          / (2. / Dr
+                             * sqrt (ctau_cum - pow2 (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))
+                               / pow2 (W * W - sum_rici))
+                              + ((2. * (W - sum_fii)
+                                  * (2. * sum_fii * sum_rici
+                                     - W * sum_fiiri_ci))
+                                 / cube (W * W - sum_rici))
+                              + (pow2 (W - sum_fii)
+                                 * (W * sum_fijri_ci2 - 4.
+                                    * sum_rici * sum_rici)
+                                 / pow4 (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 = xnmalloc (n_rows, sizeof *fim);
+      int *fim_index = xnmalloc (n_rows, sizeof *fim_index);
+      double *fmj = xnmalloc (n_cols, sizeof *fmj);
+      int *fmj_index = xnmalloc (n_cols, sizeof *fmj_index);
+      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]
+                       * pow2 ((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 - pow2 (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]
+                       * pow2 ((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 - pow2 (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] * pow2 (temp0 - temp1);
+             accum1 += (mat[j + i * n_cols]
+                        * pow2 (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 - pow2 ((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 = pow2 (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 * pow2 (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 * pow2 (UY * log (entry / row_tot[i])
+                                   + (UX - UXY) * log (col_tot[j] / W));
+           ase1_xy += entry * pow2 (UX * log (entry / col_tot[j])
+                                   + (UY - UXY) * log (row_tot[i] / W));
+           ase1_sym += entry * pow2 ((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 * pow2 (UX + UY))) * sqrt (ase1_sym);
+      t[5] = v[5] / ((2. / (W * (UX + UY)))
+                    * sqrt (P - pow2 (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 * pow2 (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 * pow2 (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;
+}
+
+/* A wrapper around data_out() that limits string output to short
+   string width and null terminates the result. */
+static void
+format_short (char *s, const struct fmt_spec *fp, const union value *v)
+{
+  struct fmt_spec fmt_subst;
+
+  /* Limit to short string width. */
+  if (formats[fp->type].cat & FCAT_STRING) 
+    {
+      fmt_subst = *fp;
+
+      assert (fmt_subst.type == FMT_A || fmt_subst.type == FMT_AHEX);
+      if (fmt_subst.type == FMT_A)
+        fmt_subst.w = min (8, fmt_subst.w);
+      else
+        fmt_subst.w = min (16, fmt_subst.w);
+
+      fp = &fmt_subst;
+    }
+
+  /* Format. */
+  data_out (s, fp, v);
+  
+  /* Null terminate. */
+  s[fp->w] = '\0';
+}
+
+/* 
+   Local Variables:
+   mode: c
+   End:
+*/
diff --git a/src/language/stats/descriptives.c b/src/language/stats/descriptives.c
new file mode 100644 (file)
index 0000000..fb89e90
--- /dev/null
@@ -0,0 +1,948 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+/* FIXME: Many possible optimizations. */
+
+#include <config.h>
+#include "message.h"
+#include <limits.h>
+#include <math.h>
+#include <stdlib.h>
+#include "array.h"
+#include "alloc.h"
+#include "case.h"
+#include "casefile.h"
+#include "command.h"
+#include "dictionary.h"
+#include "lexer.h"
+#include "message.h"
+#include "magic.h"
+#include "moments.h"
+#include "manager.h"
+#include "table.h"
+#include "variable.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+/* DESCRIPTIVES private data. */
+
+struct dsc_proc;
+
+/* Handling of missing values. */
+enum dsc_missing_type
+  {
+    DSC_VARIABLE,       /* Handle missing values on a per-variable basis. */
+    DSC_LISTWISE        /* Discard entire case if any variable is missing. */
+  };
+
+/* Describes properties of a distribution for the purpose of
+   calculating a Z-score. */
+struct dsc_z_score
+  {
+    int src_idx;                /* Source index into case data. */
+    int dst_idx;                /* Destination index into case data. */
+    double mean;               /* Distribution mean. */
+    double std_dev;            /* Distribution standard deviation. */
+    struct variable *v;         /* Variable on which z-score is based. */
+  };
+
+/* DESCRIPTIVES transformation (for calculating Z-scores). */
+struct dsc_trns
+  {
+    struct dsc_z_score *z_scores; /* Array of Z-scores. */
+    int z_score_cnt;            /* Number of Z-scores. */
+    struct variable **vars;     /* Variables for listwise missing checks. */
+    size_t var_cnt;             /* Number of variables. */
+    enum dsc_missing_type missing_type; /* Treatment of missing values. */
+    int include_user_missing;   /* Nonzero to include user-missing values. */
+  };
+
+/* Statistics.  Used as bit indexes, so must be 32 or fewer. */
+enum dsc_statistic
+  {
+    DSC_MEAN = 0, DSC_SEMEAN, DSC_STDDEV, DSC_VARIANCE, DSC_KURTOSIS,
+    DSC_SEKURT, DSC_SKEWNESS, DSC_SESKEW, DSC_RANGE, DSC_MIN,
+    DSC_MAX, DSC_SUM, DSC_N_STATS,
+
+    /* Only valid as sort criteria. */
+    DSC_NAME = -2,              /* Sort by name. */
+    DSC_NONE = -1               /* Unsorted. */
+  };
+
+/* Describes one statistic. */
+struct dsc_statistic_info
+  {
+    const char *identifier;     /* Identifier. */
+    const char *name;          /* Full name. */
+    enum moment moment;                /* Highest moment needed to calculate. */
+  };
+
+/* Table of statistics, indexed by DSC_*. */
+static const struct dsc_statistic_info dsc_info[DSC_N_STATS] =
+  {
+    {"MEAN", N_("Mean"), MOMENT_MEAN},
+    {"SEMEAN", N_("S E Mean"), MOMENT_VARIANCE},
+    {"STDDEV", N_("Std Dev"), MOMENT_VARIANCE},
+    {"VARIANCE", N_("Variance"), MOMENT_VARIANCE},
+    {"KURTOSIS", N_("Kurtosis"), MOMENT_KURTOSIS},
+    {"SEKURTOSIS", N_("S E Kurt"), MOMENT_NONE},
+    {"SKEWNESS", N_("Skewness"), MOMENT_SKEWNESS},
+    {"SESKEWNESS", N_("S E Skew"), MOMENT_NONE},
+    {"RANGE", N_("Range"), MOMENT_NONE},
+    {"MINIMUM", N_("Minimum"), MOMENT_NONE},
+    {"MAXIMUM", N_("Maximum"), MOMENT_NONE},
+    {"SUM", N_("Sum"), MOMENT_MEAN},
+  };
+
+/* Statistics calculated by default if none are explicitly
+   requested. */
+#define DEFAULT_STATS                                                   \
+       ((1ul << DSC_MEAN) | (1ul << DSC_STDDEV) | (1ul << DSC_MIN)     \
+         | (1ul << DSC_MAX))
+     
+/* A variable specified on DESCRIPTIVES. */
+struct dsc_var
+  {
+    struct variable *v;         /* Variable to calculate on. */
+    char z_name[LONG_NAME_LEN + 1]; /* Name for z-score variable. */
+    double valid, missing;     /* Valid, missing counts. */
+    struct moments *moments;    /* Moments. */
+    double min, max;            /* Maximum and mimimum values. */
+    double stats[DSC_N_STATS]; /* All the stats' values. */
+  };
+
+/* Output format. */
+enum dsc_format 
+  {
+    DSC_LINE,           /* Abbreviated format. */
+    DSC_SERIAL          /* Long format. */
+  };
+
+/* A DESCRIPTIVES procedure. */
+struct dsc_proc 
+  {
+    /* Per-variable info. */
+    struct dsc_var *vars;       /* Variables. */
+    size_t var_cnt;             /* Number of variables. */
+
+    /* User options. */
+    enum dsc_missing_type missing_type; /* Treatment of missing values. */
+    int include_user_missing;   /* Nonzero to include user-missing values. */
+    int show_var_labels;        /* Nonzero to show variable labels. */
+    int show_index;             /* Nonzero to show variable index. */
+    enum dsc_format format;     /* Output format. */
+
+    /* Accumulated results. */
+    double missing_listwise;    /* Sum of weights of cases missing listwise. */
+    double valid;               /* Sum of weights of valid cases. */
+    int bad_warn;               /* Warn if bad weight found. */
+    enum dsc_statistic sort_by_stat; /* Statistic to sort by; -1: name. */
+    int sort_ascending;         /* !0: ascending order; 0: descending. */
+    unsigned long show_stats;   /* Statistics to display. */
+    unsigned long calc_stats;   /* Statistics to calculate. */
+    enum moment max_moment;     /* Highest moment needed for stats. */
+  };
+
+/* Parsing. */
+static enum dsc_statistic match_statistic (void);
+static void free_dsc_proc (struct dsc_proc *);
+
+/* Z-score functions. */
+static int try_name (struct dsc_proc *dsc, char *name);
+static int generate_z_varname (struct dsc_proc *dsc, char *z_name,
+                               const char *name, size_t *z_cnt);
+static void dump_z_table (struct dsc_proc *);
+static void setup_z_trns (struct dsc_proc *);
+
+/* Procedure execution functions. */
+static bool calc_descriptives (const struct casefile *, void *dsc_);
+static void display (struct dsc_proc *dsc);
+\f
+/* Parser and outline. */
+
+/* Handles DESCRIPTIVES. */
+int
+cmd_descriptives (void)
+{
+  struct dsc_proc *dsc;
+  struct variable **vars = NULL;
+  size_t var_cnt = 0;
+  int save_z_scores = 0;
+  size_t z_cnt = 0;
+  size_t i;
+  bool ok;
+
+  /* Create and initialize dsc. */
+  dsc = xmalloc (sizeof *dsc);
+  dsc->vars = NULL;
+  dsc->var_cnt = 0;
+  dsc->missing_type = DSC_VARIABLE;
+  dsc->include_user_missing = 0;
+  dsc->show_var_labels = 1;
+  dsc->show_index = 0;
+  dsc->format = DSC_LINE;
+  dsc->missing_listwise = 0.;
+  dsc->valid = 0.;
+  dsc->bad_warn = 1;
+  dsc->sort_by_stat = DSC_NONE;
+  dsc->sort_ascending = 1;
+  dsc->show_stats = dsc->calc_stats = DEFAULT_STATS;
+
+  /* Parse DESCRIPTIVES. */
+  while (token != '.') 
+    {
+      if (lex_match_id ("MISSING"))
+        {
+          lex_match ('=');
+          while (token != '.' && token != '/') 
+            {
+              if (lex_match_id ("VARIABLE"))
+                dsc->missing_type = DSC_VARIABLE;
+              else if (lex_match_id ("LISTWISE"))
+                dsc->missing_type = DSC_LISTWISE;
+              else if (lex_match_id ("INCLUDE"))
+                dsc->include_user_missing = 1;
+              else
+                {
+                  lex_error (NULL);
+                  goto error;
+                }
+              lex_match (',');
+            }
+        }
+      else if (lex_match_id ("SAVE"))
+        save_z_scores = 1;
+      else if (lex_match_id ("FORMAT")) 
+        {
+          lex_match ('=');
+          while (token != '.' && token != '/') 
+            {
+              if (lex_match_id ("LABELS"))
+                dsc->show_var_labels = 1;
+              else if (lex_match_id ("NOLABELS"))
+                dsc->show_var_labels = 0;
+              else if (lex_match_id ("INDEX"))
+                dsc->show_index = 1;
+              else if (lex_match_id ("NOINDEX"))
+                dsc->show_index = 0;
+              else if (lex_match_id ("LINE"))
+                dsc->format = DSC_LINE;
+              else if (lex_match_id ("SERIAL"))
+                dsc->format = DSC_SERIAL;
+              else
+                {
+                  lex_error (NULL);
+                  goto error;
+                }
+              lex_match (',');
+            }
+        }
+      else if (lex_match_id ("STATISTICS")) 
+        {
+          lex_match ('=');
+          dsc->show_stats = 0;
+          while (token != '.' && token != '/') 
+            {
+              if (lex_match (T_ALL)) 
+                dsc->show_stats |= (1ul << DSC_N_STATS) - 1;
+              else if (lex_match_id ("DEFAULT"))
+                dsc->show_stats |= DEFAULT_STATS;
+              else
+               dsc->show_stats |= 1ul << (match_statistic ());
+              lex_match (',');
+            }
+          if (dsc->show_stats == 0)
+            dsc->show_stats = DEFAULT_STATS;
+        }
+      else if (lex_match_id ("SORT")) 
+        {
+          lex_match ('=');
+          if (lex_match_id ("NAME"))
+            dsc->sort_by_stat = DSC_NAME;
+          else 
+           {
+             dsc->sort_by_stat = match_statistic ();
+             if (dsc->sort_by_stat == DSC_NONE )
+               dsc->sort_by_stat = DSC_MEAN;
+           }
+          if (lex_match ('(')) 
+            {
+              if (lex_match_id ("A"))
+                dsc->sort_ascending = 1;
+              else if (lex_match_id ("D"))
+                dsc->sort_ascending = 0;
+              else
+                lex_error (NULL);
+              lex_force_match (')');
+            }
+        }
+      else if (var_cnt == 0)
+        {
+          if (lex_look_ahead () == '=') 
+            {
+              lex_match_id ("VARIABLES");
+              lex_match ('=');
+            }
+
+          while (token != '.' && token != '/') 
+            {
+              int i;
+              
+              if (!parse_variables (default_dict, &vars, &var_cnt,
+                                    PV_APPEND | PV_NO_DUPLICATE | PV_NUMERIC))
+               goto error;
+
+              dsc->vars = xnrealloc (dsc->vars, var_cnt, sizeof *dsc->vars);
+              for (i = dsc->var_cnt; i < var_cnt; i++)
+                {
+                  struct dsc_var *dv = &dsc->vars[i];
+                  dv->v = vars[i];
+                  dv->z_name[0] = '\0';
+                  dv->moments = NULL;
+                }
+              dsc->var_cnt = var_cnt;
+
+              if (lex_match ('(')) 
+                {
+                  if (token != T_ID) 
+                    {
+                      lex_error (NULL);
+                      goto error;
+                    }
+                  if (try_name (dsc, tokid)) 
+                    {
+                      strcpy (dsc->vars[dsc->var_cnt - 1].z_name, tokid);
+                      z_cnt++;
+                    }
+                  else
+                    msg (SE, _("Z-score variable name %s would be"
+                               " a duplicate variable name."), tokid);
+                  lex_get ();
+                  if (!lex_force_match (')'))
+                   goto error;
+                }
+            }
+        }
+      else 
+        {
+          lex_error (NULL);
+          goto error; 
+        }
+
+      lex_match ('/');
+    }
+  if (var_cnt == 0)
+    {
+      msg (SE, _("No variables specified."));
+      goto error;
+    }
+
+  /* Construct z-score varnames, show translation table. */
+  if (z_cnt || save_z_scores)
+    {
+      if (save_z_scores) 
+        {
+          size_t gen_cnt = 0;
+
+          for (i = 0; i < dsc->var_cnt; i++)
+            if (dsc->vars[i].z_name[0] == 0) 
+              {
+                if (!generate_z_varname (dsc, dsc->vars[i].z_name,
+                                         dsc->vars[i].v->name, &gen_cnt))
+                  goto error;
+                z_cnt++;
+              } 
+        }
+      dump_z_table (dsc);
+    }
+
+  /* Figure out statistics to display. */
+  if (dsc->show_stats & (1ul << DSC_SKEWNESS))
+    dsc->show_stats |= 1ul << DSC_SESKEW;
+  if (dsc->show_stats & (1ul << DSC_KURTOSIS))
+    dsc->show_stats |= 1ul << DSC_SEKURT;
+
+  /* Figure out which statistics to calculate. */
+  dsc->calc_stats = dsc->show_stats;
+  if (z_cnt > 0)
+    dsc->calc_stats |= (1ul << DSC_MEAN) | (1ul << DSC_STDDEV);
+  if (dsc->sort_by_stat >= 0)
+    dsc->calc_stats |= 1ul << dsc->sort_by_stat;
+  if (dsc->show_stats & (1ul << DSC_SESKEW))
+    dsc->calc_stats |= 1ul << DSC_SKEWNESS;
+  if (dsc->show_stats & (1ul << DSC_SEKURT))
+    dsc->calc_stats |= 1ul << DSC_KURTOSIS;
+
+  /* Figure out maximum moment needed and allocate moments for
+     the variables. */
+  dsc->max_moment = MOMENT_NONE;
+  for (i = 0; i < DSC_N_STATS; i++) 
+    if (dsc->calc_stats & (1ul << i) && dsc_info[i].moment > dsc->max_moment)
+      dsc->max_moment = dsc_info[i].moment;
+  if (dsc->max_moment != MOMENT_NONE)
+    for (i = 0; i < dsc->var_cnt; i++)
+      dsc->vars[i].moments = moments_create (dsc->max_moment);
+
+  /* Data pass. */
+  ok = multipass_procedure_with_splits (calc_descriptives, dsc);
+
+  /* Z-scoring! */
+  if (ok && z_cnt)
+    setup_z_trns (dsc);
+
+  /* Done. */
+  free (vars);
+  free_dsc_proc (dsc);
+  return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+
+ error:
+  free (vars);
+  free_dsc_proc (dsc);
+  return CMD_FAILURE;
+}
+
+/* Returns the statistic named by the current token and skips past the token.
+   Returns DSC_NONE if no statistic is given (e.g., subcommand with no
+   specifiers). Emits an error if the current token ID does not name a
+   statistic. */
+static enum dsc_statistic
+match_statistic (void) 
+{
+  if (token == T_ID) 
+    {
+      enum dsc_statistic stat;
+
+      for (stat = 0; stat < DSC_N_STATS; stat++)
+        if (lex_match_id (dsc_info[stat].identifier)) 
+         return stat;
+
+      lex_get();
+      lex_error (_("expecting statistic name: reverting to default"));
+    }
+
+  return DSC_NONE;
+}
+
+/* Frees DSC. */
+static void
+free_dsc_proc (struct dsc_proc *dsc)
+{
+  size_t i;
+
+  if (dsc == NULL)
+    return;
+  
+  for (i = 0; i < dsc->var_cnt; i++)
+    moments_destroy (dsc->vars[i].moments);
+  free (dsc->vars);
+  free (dsc);
+}
+\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 (struct dsc_proc *dsc, char *name)
+{
+  size_t i;
+
+  if (dict_lookup_var (default_dict, name) != NULL)
+    return 0;
+  for (i = 0; i < dsc->var_cnt; i++)
+    if (!strcasecmp (dsc->vars[i].z_name, name))
+      return 0;
+  return 1;
+}
+
+/* Generates a name for a Z-score variable based on a variable
+   named VAR_NAME, given that *Z_CNT generated variable names are
+   known to already exist.  If successful, returns nonzero and
+   copies the new name into Z_NAME.  On failure, returns zero. */
+static int
+generate_z_varname (struct dsc_proc *dsc, char *z_name,
+                    const char *var_name, size_t *z_cnt)
+{
+  char name[LONG_NAME_LEN + 1];
+
+  /* Try a name based on the original variable name. */
+  name[0] = 'Z';
+  str_copy_trunc (name + 1, sizeof name - 1, var_name);
+  if (try_name (dsc, name))
+    {
+      strcpy (z_name, name);
+      return 1;
+    }
+
+  /* Generate a synthetic name. */
+  for (;;)
+    {
+      (*z_cnt)++;
+
+      if (*z_cnt <= 99)
+       sprintf (name, "ZSC%03d", *z_cnt);
+      else if (*z_cnt <= 108)
+       sprintf (name, "STDZ%02d", *z_cnt - 99);
+      else if (*z_cnt <= 117)
+       sprintf (name, "ZZZZ%02d", *z_cnt - 108);
+      else if (*z_cnt <= 126)
+       sprintf (name, "ZQZQ%02d", *z_cnt - 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 (dsc, name))
+       {
+         strcpy (z_name, name);
+         return 1;
+       }
+    }
+}
+
+/* Outputs a table describing the mapping between source
+   variables and Z-score variables. */
+static void
+dump_z_table (struct dsc_proc *dsc)
+{
+  size_t cnt = 0;
+  struct tab_table *t;
+  
+  {
+    size_t i;
+    
+    for (i = 0; i < dsc->var_cnt; i++)
+      if (dsc->vars[i].z_name[0] != '\0')
+       cnt++;
+  }
+  
+  t = tab_create (2, cnt + 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, cnt);
+  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);
+
+  {
+    size_t i, y;
+    
+    for (i = 0, y = 1; i < dsc->var_cnt; i++)
+      if (dsc->vars[i].z_name[0] != '\0')
+       {
+         tab_text (t, 0, y, TAB_LEFT, dsc->vars[i].v->name);
+         tab_text (t, 1, y++, TAB_LEFT, dsc->vars[i].z_name);
+       }
+  }
+  
+  tab_submit (t);
+}
+
+/* Transformation function to calculate Z-scores. Will return SYSMIS if any of
+   the following are true: 1) mean or standard deviation is SYSMIS 2) score is
+   SYSMIS 3) score is user missing and they were not included in the original
+   analyis. 4) any of the variables in the original analysis were missing
+   (either system or user-missing values that weren't included).
+*/
+static int
+descriptives_trns_proc (void *trns_, struct ccase * c,
+                        int case_idx UNUSED)
+{
+  struct dsc_trns *t = trns_;
+  struct dsc_z_score *z;
+  struct variable **vars;
+  int all_sysmis = 0;
+
+  if (t->missing_type == DSC_LISTWISE)
+    {
+      assert(t->vars);
+      for (vars = t->vars; vars < t->vars + t->var_cnt; vars++)
+       {
+         double score = case_num (c, (*vars)->fv);
+         if ( score == SYSMIS
+               || (!t->include_user_missing 
+                   && mv_is_num_user_missing (&(*vars)->miss, score)))
+           {
+             all_sysmis = 1;
+             break;
+           }
+       }
+    }
+      
+  for (z = t->z_scores; z < t->z_scores + t->z_score_cnt; z++)
+    {
+      double input = case_num (c, z->src_idx);
+      double *output = &case_data_rw (c, z->dst_idx)->f;
+
+      if (z->mean == SYSMIS || z->std_dev == SYSMIS 
+         || all_sysmis || input == SYSMIS 
+         || (!t->include_user_missing
+              && mv_is_num_user_missing (&z->v->miss, input)))
+       *output = SYSMIS;
+      else
+       *output = (input - z->mean) / z->std_dev;
+    }
+  return TRNS_CONTINUE;
+}
+
+/* Frees a descriptives_trns struct. */
+static bool
+descriptives_trns_free (void *trns_)
+{
+  struct dsc_trns *t = trns_;
+
+  free (t->z_scores);
+  assert((t->missing_type != DSC_LISTWISE) ^ (t->vars != NULL));
+  free (t->vars);
+  return true;
+}
+
+/* Sets up a transformation to calculate Z scores. */
+static void
+setup_z_trns (struct dsc_proc *dsc)
+{
+  struct dsc_trns *t;
+  size_t cnt, i;
+
+  for (cnt = i = 0; i < dsc->var_cnt; i++)
+    if (dsc->vars[i].z_name[0] != '\0')
+      cnt++;
+
+  t = xmalloc (sizeof *t);
+  t->z_scores = xnmalloc (cnt, sizeof *t->z_scores);
+  t->z_score_cnt = cnt;
+  t->missing_type = dsc->missing_type;
+  t->include_user_missing = dsc->include_user_missing;
+  if ( t->missing_type == DSC_LISTWISE )
+    {
+      t->var_cnt = dsc->var_cnt;
+      t->vars = xnmalloc (t->var_cnt, sizeof *t->vars);
+      for (i = 0; i < t->var_cnt; i++)
+       t->vars[i] = dsc->vars[i].v;
+    }
+  else
+    {
+      t->var_cnt = 0;
+      t->vars = NULL;
+    }
+
+  for (cnt = i = 0; i < dsc->var_cnt; i++)
+    {
+      struct dsc_var *dv = &dsc->vars[i];
+      if (dv->z_name[0] != '\0')
+       {
+          struct dsc_z_score *z;
+         char *cp;
+         struct variable *dst_var;
+
+         dst_var = dict_create_var_assert (default_dict, dv->z_name, 0);
+          dst_var->init = 0;
+         if (dv->v->label)
+           {
+             dst_var->label = xmalloc (strlen (dv->v->label) + 12);
+             cp = stpcpy (dst_var->label, _("Z-score of "));
+             strcpy (cp, dv->v->label);
+           }
+         else
+           {
+             dst_var->label = xmalloc (strlen (dv->v->name) + 12);
+             cp = stpcpy (dst_var->label, _("Z-score of "));
+             strcpy (cp, dv->v->name);
+           }
+
+          z = &t->z_scores[cnt++];
+          z->src_idx = dv->v->fv;
+          z->dst_idx = dst_var->fv;
+          z->mean = dv->stats[DSC_MEAN];
+          z->std_dev = dv->stats[DSC_STDDEV];
+         z->v = dv->v;
+       }
+    }
+
+  add_transformation (descriptives_trns_proc, descriptives_trns_free, t);
+}
+\f
+/* Statistical calculation. */
+
+static int listwise_missing (struct dsc_proc *dsc, const struct ccase *c);
+
+/* Calculates and displays descriptive statistics for the cases
+   in CF. */
+static bool
+calc_descriptives (const struct casefile *cf, void *dsc_) 
+{
+  struct dsc_proc *dsc = dsc_;
+  struct casereader *reader;
+  struct ccase c;
+  size_t i;
+
+  for (i = 0; i < dsc->var_cnt; i++)
+    {
+      struct dsc_var *dv = &dsc->vars[i];
+      
+      dv->valid = dv->missing = 0.0;
+      if (dv->moments != NULL)
+        moments_clear (dv->moments);
+      dv->min = DBL_MAX;
+      dv->max = -DBL_MAX;
+    }
+  dsc->missing_listwise = 0.;
+  dsc->valid = 0.;
+
+  /* First pass to handle most of the work. */
+  for (reader = casefile_get_reader (cf);
+       casereader_read (reader, &c);
+       case_destroy (&c))
+    {
+      double weight = dict_get_case_weight (default_dict, &c, &dsc->bad_warn);
+      if (weight <= 0.0) 
+        continue;
+       
+      /* Check for missing values. */
+      if (listwise_missing (dsc, &c)) 
+        {
+          dsc->missing_listwise += weight;
+          if (dsc->missing_type == DSC_LISTWISE)
+            continue; 
+        }
+      dsc->valid += weight;
+
+      for (i = 0; i < dsc->var_cnt; i++) 
+        {
+          struct dsc_var *dv = &dsc->vars[i];
+          double x = case_num (&c, dv->v->fv);
+          
+          if (dsc->missing_type != DSC_LISTWISE
+              && (x == SYSMIS
+                  || (!dsc->include_user_missing
+                      && mv_is_num_user_missing (&dv->v->miss, x))))
+            {
+              dv->missing += weight;
+              continue;
+            }
+
+          if (dv->moments != NULL) 
+            moments_pass_one (dv->moments, x, weight);
+
+          if (x < dv->min)
+            dv->min = x;
+          if (x > dv->max)
+            dv->max = x;
+        }
+    }
+  casereader_destroy (reader);
+
+  /* Second pass for higher-order moments. */
+  if (dsc->max_moment > MOMENT_MEAN) 
+    {
+      for (reader = casefile_get_reader (cf);
+           casereader_read (reader, &c);
+           case_destroy (&c))
+        {
+          double weight = dict_get_case_weight (default_dict, &c, 
+                                               &dsc->bad_warn);
+          if (weight <= 0.0)
+            continue;
+      
+          /* Check for missing values. */
+          if (listwise_missing (dsc, &c) 
+              && dsc->missing_type == DSC_LISTWISE)
+            continue; 
+
+          for (i = 0; i < dsc->var_cnt; i++) 
+            {
+              struct dsc_var *dv = &dsc->vars[i];
+              double x = case_num (&c, dv->v->fv);
+          
+              if (dsc->missing_type != DSC_LISTWISE
+                  && (x == SYSMIS
+                      || (!dsc->include_user_missing
+                          && mv_is_num_user_missing (&dv->v->miss, x))))
+                continue;
+
+              if (dv->moments != NULL)
+                moments_pass_two (dv->moments, x, weight);
+            }
+        }
+      casereader_destroy (reader);
+    }
+  
+  /* Calculate results. */
+  for (i = 0; i < dsc->var_cnt; i++)
+    {
+      struct dsc_var *dv = &dsc->vars[i];
+      double W;
+      int j;
+
+      for (j = 0; j < DSC_N_STATS; j++)
+        dv->stats[j] = SYSMIS;
+
+      dv->valid = W = dsc->valid - dv->missing;
+
+      if (dv->moments != NULL)
+        moments_calculate (dv->moments, NULL,
+                           &dv->stats[DSC_MEAN], &dv->stats[DSC_VARIANCE],
+                           &dv->stats[DSC_SKEWNESS], &dv->stats[DSC_KURTOSIS]);
+      if (dsc->calc_stats & (1ul << DSC_SEMEAN)
+          && dv->stats[DSC_VARIANCE] != SYSMIS && W > 0.)
+        dv->stats[DSC_SEMEAN] = sqrt (dv->stats[DSC_VARIANCE]) / sqrt (W);
+      if (dsc->calc_stats & (1ul << DSC_STDDEV)
+          && dv->stats[DSC_VARIANCE] != SYSMIS)
+        dv->stats[DSC_STDDEV] = sqrt (dv->stats[DSC_VARIANCE]);
+      if (dsc->calc_stats & (1ul << DSC_SEKURT)) 
+        if (dv->stats[DSC_KURTOSIS] != SYSMIS)
+            dv->stats[DSC_SEKURT] = calc_sekurt (W);
+      if (dsc->calc_stats & (1ul << DSC_SESKEW)
+          && dv->stats[DSC_SKEWNESS] != SYSMIS)
+        dv->stats[DSC_SESKEW] = calc_seskew (W);
+      dv->stats[DSC_RANGE] = ((dv->min == DBL_MAX || dv->max == -DBL_MAX)
+                              ? SYSMIS : dv->max - dv->min);
+      dv->stats[DSC_MIN] = dv->min == DBL_MAX ? SYSMIS : dv->min;
+      dv->stats[DSC_MAX] = dv->max == -DBL_MAX ? SYSMIS : dv->max;
+      if (dsc->calc_stats & (1ul << DSC_SUM))
+        dv->stats[DSC_SUM] = W * dv->stats[DSC_MEAN];
+    }
+
+  /* Output results. */
+  display (dsc);
+
+  return true;
+}
+
+/* Returns nonzero if any of the descriptives variables in DSC's
+   variable list have missing values in case C, zero otherwise. */
+static int
+listwise_missing (struct dsc_proc *dsc, const struct ccase *c) 
+{
+  size_t i;
+
+  for (i = 0; i < dsc->var_cnt; i++)
+    {
+      struct dsc_var *dv = &dsc->vars[i];
+      double x = case_num (c, dv->v->fv);
+
+      if (x == SYSMIS
+          || (!dsc->include_user_missing
+              && mv_is_num_user_missing (&dv->v->miss, x)))
+        return 1;
+    }
+  return 0;
+}
+\f
+/* Statistical display. */
+
+static algo_compare_func descriptives_compare_dsc_vars;
+
+/* Displays a table of descriptive statistics for DSC. */
+static void
+display (struct dsc_proc *dsc)
+{
+  size_t i;
+  int nc;
+  struct tab_table *t;
+
+  nc = 1 + (dsc->format == DSC_SERIAL ? 2 : 1);
+  for (i = 0; i < DSC_N_STATS; i++)
+    if (dsc->show_stats & (1ul << i))
+      nc++;
+
+  if (dsc->sort_by_stat != DSC_NONE)
+    sort (dsc->vars, dsc->var_cnt, sizeof *dsc->vars,
+          descriptives_compare_dsc_vars, dsc);
+
+  t = tab_create (nc, dsc->var_cnt + 1, 0);
+  tab_headers (t, 1, 0, 1, 0);
+  tab_box (t, TAL_1, TAL_1, -1, -1, 0, 0, nc - 1, dsc->var_cnt);
+  tab_box (t, -1, -1, -1, TAL_1, 1, 0, nc - 1, dsc->var_cnt);
+  tab_hline (t, TAL_2, 0, nc - 1, 1);
+  tab_vline (t, TAL_2, 1, 0, dsc->var_cnt);
+  tab_dim (t, tab_natural_dimensions);
+
+  nc = 0;
+  tab_text (t, nc++, 0, TAB_LEFT | TAT_TITLE, _("Variable"));
+  if (dsc->format == DSC_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 (dsc->show_stats & (1ul << i))
+      {
+       const char *title = gettext (dsc_info[i].name);
+       tab_text (t, nc++, 0, TAB_CENTER | TAT_TITLE, title);
+      }
+
+  for (i = 0; i < dsc->var_cnt; i++)
+    {
+      struct dsc_var *dv = &dsc->vars[i];
+      size_t j;
+
+      nc = 0;
+      tab_text (t, nc++, i + 1, TAB_LEFT, dv->v->name);
+      tab_text (t, nc++, i + 1, TAT_PRINTF, "%g", dv->valid);
+      if (dsc->format == DSC_SERIAL)
+       tab_text (t, nc++, i + 1, TAT_PRINTF, "%g", dv->missing);
+      for (j = 0; j < DSC_N_STATS; j++)
+       if (dsc->show_stats & (1ul << j))
+         tab_float (t, nc++, i + 1, TAB_NONE, dv->stats[j], 10, 3);
+    }
+
+  tab_title (t, 1, _("Valid cases = %g; cases with missing value(s) = %g."),
+            dsc->valid, dsc->missing_listwise);
+
+  tab_submit (t);
+}
+
+/* Compares `struct dsc_var's A and B according to the ordering
+   specified by CMD. */
+static int
+descriptives_compare_dsc_vars (const void *a_, const void *b_, void *dsc_)
+{
+  const struct dsc_var *a = a_;
+  const struct dsc_var *b = b_;
+  struct dsc_proc *dsc = dsc_;
+
+  int result;
+
+  if (dsc->sort_by_stat == DSC_NAME)
+    result = strcasecmp (a->v->name, b->v->name);
+  else 
+    {
+      double as = a->stats[dsc->sort_by_stat];
+      double bs = b->stats[dsc->sort_by_stat];
+
+      result = as < bs ? -1 : as > bs;
+    }
+
+  if (!dsc->sort_ascending)
+    result = -result;
+
+  return result;
+}
diff --git a/src/language/stats/examine.q b/src/language/stats/examine.q
new file mode 100644 (file)
index 0000000..940a439
--- /dev/null
@@ -0,0 +1,2208 @@
+/* PSPP - EXAMINE data for normality . -*-c-*-
+
+Copyright (C) 2004 Free Software Foundation, Inc.
+Author: John Darrington 2004
+
+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., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA. */
+
+#include <config.h>
+#include <gsl/gsl_cdf.h>
+#include "message.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "alloc.h"
+#include "str.h"
+#include "case.h"
+#include "dictionary.h"
+#include "command.h"
+#include "lexer.h"
+#include "message.h"
+#include "magic.h"
+#include "misc.h"
+#include "table.h"
+#include "manager.h"
+#include "value-labels.h"
+#include "variable.h"
+#include "procedure.h"
+#include "hash.h"
+#include "casefile.h"
+#include "factor-stats.h"
+#include "moments.h"
+#include "percentiles.h"
+#include "box-whisker.h"
+#include "cartesian.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+/* (headers) */
+#include "chart.h"
+#include "plot-hist.h"
+#include "plot-chart.h"
+
+/* (specification)
+   "EXAMINE" (xmn_):
+   *^variables=custom;
+   +total=custom;
+   +nototal=custom;
+   +missing=miss:pairwise/!listwise,
+   rep:report/!noreport,
+   incl:include/!exclude;
+   +compare=cmp:variables/!groups;
+   +percentiles=custom;
+   +id=var;
+   +plot[plt_]=stemleaf,boxplot,npplot,:spreadlevel(*d:n),histogram,all,none;
+   +cinterval=double;
+   +statistics[st_]=descriptives,:extreme(*d:n),all,none.
+*/
+
+/* (declarations) */
+
+/* (functions) */
+
+
+
+static struct cmd_examine cmd;
+
+static struct variable **dependent_vars;
+
+static size_t n_dependent_vars;
+
+
+struct factor 
+{
+  /* The independent variable */
+  struct variable *indep_var[2];
+
+
+  /* Hash table of factor stats indexed by 2 values */
+  struct hsh_table *fstats;
+
+  /* The hash table after it has been crunched */
+  struct factor_statistics **fs;
+
+  struct factor *next;
+
+};
+
+/* Linked list of factors */
+static struct factor *factors=0;
+
+static struct metrics *totals=0;
+
+/* Parse the clause specifying the factors */
+static int examine_parse_independent_vars(struct cmd_examine *cmd);
+
+
+
+/* Output functions */
+static void show_summary(struct variable **dependent_var, int n_dep_var, 
+                        const struct factor *f);
+
+static void show_extremes(struct variable **dependent_var, 
+                         int n_dep_var, 
+                         const struct factor *factor,
+                         int n_extremities);
+
+static void show_descriptives(struct variable **dependent_var, 
+                             int n_dep_var, 
+                             struct factor *factor);
+
+static void show_percentiles(struct variable **dependent_var, 
+                            int n_dep_var, 
+                            struct factor *factor);
+
+
+
+
+void np_plot(const struct metrics *m, const char *factorname);
+
+
+void box_plot_group(const struct factor *fctr, 
+                   const struct variable **vars, int n_vars,
+                   const struct variable *id
+                   ) ;
+
+
+void box_plot_variables(const struct factor *fctr, 
+                       const struct variable **vars, int n_vars, 
+                       const struct variable *id
+                       );
+
+
+
+/* Per Split function */
+static bool run_examine(const struct casefile *cf, void *cmd_);
+
+static void output_examine(void);
+
+
+void factor_calc(struct ccase *c, int case_no, 
+                double weight, int case_missing);
+
+
+/* Represent a factor as a string, so it can be
+   printed in a human readable fashion */
+const char * factor_to_string(const struct factor *fctr, 
+                             struct factor_statistics *fs,
+                             const struct variable *var);
+
+
+/* Represent a factor as a string, so it can be
+   printed in a human readable fashion,
+   but sacrificing some readablility for the sake of brevity */
+const char *factor_to_string_concise(const struct factor *fctr, 
+                                    struct factor_statistics *fs);
+
+
+
+
+/* Function to use for testing for missing values */
+static is_missing_func *value_is_missing;
+
+
+/* PERCENTILES */
+
+static subc_list_double percentile_list;
+
+static enum pc_alg percentile_algorithm;
+
+static short sbc_percentile;
+
+
+int
+cmd_examine(void)
+{
+  bool ok;
+
+  subc_list_double_create(&percentile_list);
+  percentile_algorithm = PC_HAVERAGE;
+
+  if ( !parse_examine(&cmd) )
+    return CMD_FAILURE;
+
+  /* If /MISSING=INCLUDE is set, then user missing values are ignored */
+  if (cmd.incl == XMN_INCLUDE ) 
+    value_is_missing = mv_is_value_system_missing;
+  else
+    value_is_missing = mv_is_value_missing;
+
+  if ( cmd.st_n == SYSMIS ) 
+    cmd.st_n = 5;
+
+  if ( ! cmd.sbc_cinterval) 
+    cmd.n_cinterval[0] = 95.0;
+
+  /* If descriptives have been requested, make sure the 
+     quartiles are calculated */
+  if ( cmd.a_statistics[XMN_ST_DESCRIPTIVES] )
+    {
+      subc_list_double_push(&percentile_list, 25);
+      subc_list_double_push(&percentile_list, 50);
+      subc_list_double_push(&percentile_list, 75);
+    }
+
+  ok = multipass_procedure_with_splits (run_examine, &cmd);
+
+  if ( totals ) 
+    {
+      free( totals );
+    }
+  
+  if ( dependent_vars ) 
+    free (dependent_vars);
+
+  {
+    struct factor *f = factors ;
+    while ( f ) 
+      {
+       struct factor *ff = f;
+
+       f = f->next;
+       free ( ff->fs );
+       hsh_destroy ( ff->fstats ) ;
+       free ( ff ) ;
+      }
+    factors = 0;
+  }
+
+  subc_list_double_destroy(&percentile_list);
+
+  return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+};
+
+
+
+/* Show all the appropriate tables */
+static void
+output_examine(void)
+{
+  struct factor *fctr;
+
+  /* Show totals if appropriate */
+  if ( ! cmd.sbc_nototal || factors == 0 )
+    {
+      show_summary(dependent_vars, n_dependent_vars, 0);
+
+      if ( cmd.sbc_statistics ) 
+       {
+         if ( cmd.a_statistics[XMN_ST_EXTREME]) 
+           show_extremes(dependent_vars, n_dependent_vars, 0, cmd.st_n);
+
+         if ( cmd.a_statistics[XMN_ST_DESCRIPTIVES]) 
+           show_descriptives(dependent_vars, n_dependent_vars, 0);
+
+       }
+      if ( sbc_percentile ) 
+       show_percentiles(dependent_vars, n_dependent_vars, 0);
+
+      if ( cmd.sbc_plot) 
+       {
+         int v;
+         if ( cmd.a_plot[XMN_PLT_NPPLOT] ) 
+           {
+             for ( v = 0 ; v < n_dependent_vars; ++v ) 
+               np_plot(&totals[v], var_to_string(dependent_vars[v]));
+           }
+
+         if ( cmd.a_plot[XMN_PLT_BOXPLOT] ) 
+           {
+             if ( cmd.cmp == XMN_GROUPS ) 
+               {
+                 box_plot_group(0, dependent_vars, n_dependent_vars, 
+                                cmd.v_id);
+               }
+             else
+               box_plot_variables(0, dependent_vars, n_dependent_vars,
+                                  cmd.v_id);
+           }
+
+         if ( cmd.a_plot[XMN_PLT_HISTOGRAM] ) 
+           {
+             for ( v = 0 ; v < n_dependent_vars; ++v ) 
+               {
+                 struct normal_curve normal;
+
+                 normal.N      = totals[v].n;
+                 normal.mean   = totals[v].mean;
+                 normal.stddev = totals[v].stddev;
+                 
+                 histogram_plot(totals[v].histogram, 
+                                var_to_string(dependent_vars[v]),
+                                &normal, 0);
+               }
+           }
+
+       }
+
+    }
+
+
+  /* Show grouped statistics  as appropriate */
+  fctr = factors;
+  while ( fctr ) 
+    {
+      show_summary(dependent_vars, n_dependent_vars, fctr);
+
+      if ( cmd.sbc_statistics ) 
+       {
+         if ( cmd.a_statistics[XMN_ST_EXTREME]) 
+           show_extremes(dependent_vars, n_dependent_vars, fctr, cmd.st_n);
+
+         if ( cmd.a_statistics[XMN_ST_DESCRIPTIVES]) 
+           show_descriptives(dependent_vars, n_dependent_vars, fctr);
+       }
+
+      if ( sbc_percentile ) 
+       show_percentiles(dependent_vars, n_dependent_vars, fctr);
+
+
+      if ( cmd.sbc_plot) 
+       {
+         size_t v;
+
+         struct factor_statistics **fs = fctr->fs ;
+
+         if ( cmd.a_plot[XMN_PLT_BOXPLOT] )
+           {
+             if ( cmd.cmp == XMN_VARIABLES ) 
+               box_plot_variables(fctr, dependent_vars, n_dependent_vars, 
+                                  cmd.v_id);
+             else
+               box_plot_group(fctr, dependent_vars, n_dependent_vars, 
+                              cmd.v_id);
+           }
+
+         for ( v = 0 ; v < n_dependent_vars; ++v )
+           {
+
+             for ( fs = fctr->fs ; *fs ; ++fs ) 
+               {
+                 const char *s = factor_to_string(fctr, *fs, dependent_vars[v]);
+
+                 if ( cmd.a_plot[XMN_PLT_NPPLOT] ) 
+                   np_plot(&(*fs)->m[v], s);
+
+                 if ( cmd.a_plot[XMN_PLT_HISTOGRAM] ) 
+                   {
+                     struct normal_curve normal;
+
+                     normal.N      = (*fs)->m[v].n;
+                     normal.mean   = (*fs)->m[v].mean;
+                     normal.stddev = (*fs)->m[v].stddev;
+                 
+                     histogram_plot((*fs)->m[v].histogram, 
+                                    s,  &normal, 0);
+                   }
+                 
+               } /* for ( fs .... */
+
+           } /* for ( v = 0 ..... */
+
+       }
+
+      fctr = fctr->next;
+    }
+
+}
+
+
+/* Create a hash table of percentiles and their values from the list of
+   percentiles */
+static struct hsh_table *
+list_to_ptile_hash(const subc_list_double *l)
+{
+  int i;
+  
+  struct hsh_table *h ; 
+
+  h = hsh_create(subc_list_double_count(l), 
+                (hsh_compare_func *) ptile_compare,
+                (hsh_hash_func *) ptile_hash, 
+                (hsh_free_func *) free,
+                0);
+
+
+  for ( i = 0 ; i < subc_list_double_count(l) ; ++i )
+    {
+      struct percentile *p = xmalloc (sizeof *p);
+      
+      p->p = subc_list_double_at(l,i);
+      p->v = SYSMIS;
+
+      hsh_insert(h, p);
+
+    }
+
+  return h;
+
+}
+
+/* Parse the PERCENTILES subcommand */
+static int
+xmn_custom_percentiles(struct cmd_examine *p UNUSED)
+{
+  sbc_percentile = 1;
+
+  lex_match('=');
+
+  lex_match('(');
+
+  while ( lex_is_number() ) 
+    {
+      subc_list_double_push(&percentile_list,lex_number());
+
+      lex_get();
+
+      lex_match(',') ;
+    }
+  lex_match(')');
+
+  lex_match('=');
+
+  if ( lex_match_id("HAVERAGE"))
+    percentile_algorithm = PC_HAVERAGE; 
+
+  else if ( lex_match_id("WAVERAGE"))
+    percentile_algorithm = PC_WAVERAGE; 
+
+  else if ( lex_match_id("ROUND"))
+    percentile_algorithm = PC_ROUND;
+
+  else if ( lex_match_id("EMPIRICAL"))
+    percentile_algorithm = PC_EMPIRICAL;
+
+  else if ( lex_match_id("AEMPIRICAL"))
+    percentile_algorithm = PC_AEMPIRICAL; 
+
+  else if ( lex_match_id("NONE"))
+    percentile_algorithm = PC_NONE; 
+
+
+  if ( 0 == subc_list_double_count(&percentile_list))
+    {
+      subc_list_double_push(&percentile_list, 5);
+      subc_list_double_push(&percentile_list, 10);
+      subc_list_double_push(&percentile_list, 25);
+      subc_list_double_push(&percentile_list, 50);
+      subc_list_double_push(&percentile_list, 75);
+      subc_list_double_push(&percentile_list, 90);
+      subc_list_double_push(&percentile_list, 95);
+    }
+
+  return 1;
+}
+
+/* TOTAL and NOTOTAL are simple, mutually exclusive flags */
+static int
+xmn_custom_total(struct cmd_examine *p)
+{
+  if ( p->sbc_nototal ) 
+    {
+      msg (SE, _("%s and %s are mutually exclusive"),"TOTAL","NOTOTAL");
+      return 0;
+    }
+
+  return 1;
+}
+
+static int
+xmn_custom_nototal(struct cmd_examine *p)
+{
+  if ( p->sbc_total ) 
+    {
+      msg (SE, _("%s and %s are mutually exclusive"),"TOTAL","NOTOTAL");
+      return 0;
+    }
+
+  return 1;
+}
+
+
+
+/* Parser for the variables sub command  
+   Returns 1 on success */
+static int
+xmn_custom_variables(struct cmd_examine *cmd )
+{
+  lex_match('=');
+
+  if ((token != T_ID || dict_lookup_var (default_dict, tokid) == NULL)
+      && token != T_ALL)
+    {
+      return 2;
+    }
+  
+  if (!parse_variables (default_dict, &dependent_vars, &n_dependent_vars,
+                       PV_NO_DUPLICATE | PV_NUMERIC | PV_NO_SCRATCH) )
+    {
+      free (dependent_vars);
+      return 0;
+    }
+
+  assert(n_dependent_vars);
+
+  totals = xnmalloc (n_dependent_vars, sizeof *totals);
+
+  if ( lex_match(T_BY))
+    {
+      int success ; 
+      success =  examine_parse_independent_vars(cmd);
+      if ( success != 1 ) {
+        free (dependent_vars);
+       free (totals) ; 
+      }
+      return success;
+    }
+
+  return 1;
+}
+
+
+
+/* Parse the clause specifying the factors */
+static int
+examine_parse_independent_vars(struct cmd_examine *cmd)
+{
+  int success;
+  struct factor *sf = xmalloc (sizeof *sf);
+
+  if ((token != T_ID || dict_lookup_var (default_dict, tokid) == NULL)
+      && token != T_ALL)
+    {
+      free ( sf ) ;
+      return 2;
+    }
+
+
+  sf->indep_var[0] = parse_variable();
+  sf->indep_var[1] = 0;
+
+  if ( token == T_BY ) 
+    {
+
+      lex_match(T_BY);
+
+      if ((token != T_ID || dict_lookup_var (default_dict, tokid) == NULL)
+         && token != T_ALL)
+       {
+         free ( sf ) ;
+         return 2;
+       }
+
+      sf->indep_var[1] = parse_variable();
+
+    }
+
+
+  sf->fstats = hsh_create(4,
+                         (hsh_compare_func *) factor_statistics_compare,
+                         (hsh_hash_func *) factor_statistics_hash,
+                         (hsh_free_func *) factor_statistics_free,
+                         0);
+
+  sf->next = factors;
+  factors = sf;
+  
+  lex_match(',');
+
+  if ( token == '.' || token == '/' ) 
+    return 1;
+
+  success =  examine_parse_independent_vars(cmd);
+  
+  if ( success != 1 ) 
+    free ( sf ) ; 
+
+  return success;
+}
+
+
+
+
+void populate_percentiles(struct tab_table *tbl, int col, int row, 
+                         const struct metrics *m);
+
+void populate_descriptives(struct tab_table *t, int col, int row, 
+                          const struct metrics *fs);
+
+void populate_extremes(struct tab_table *t, int col, int row, int n, 
+                      const struct metrics *m);
+
+void populate_summary(struct tab_table *t, int col, int row,
+                     const struct metrics *m);
+
+
+
+
+static int bad_weight_warn = 1;
+
+
+/* Perform calculations for the sub factors */
+void
+factor_calc(struct ccase *c, int case_no, double weight, int case_missing)
+{
+  size_t v;
+  struct factor *fctr = factors;
+
+  while ( fctr) 
+    {
+      struct factor_statistics **foo ;
+      union value indep_vals[2] ;
+
+      indep_vals[0] = * case_data(c, fctr->indep_var[0]->fv);
+
+      if ( fctr->indep_var[1] ) 
+       indep_vals[1] = * case_data(c, fctr->indep_var[1]->fv);
+      else
+       indep_vals[1].f = SYSMIS;
+
+      assert(fctr->fstats);
+
+      foo = ( struct factor_statistics ** ) 
+       hsh_probe(fctr->fstats, (void *) &indep_vals);
+
+      if ( !*foo ) 
+       {
+
+         *foo = create_factor_statistics(n_dependent_vars, 
+                                         &indep_vals[0],
+                                         &indep_vals[1]);
+
+         for ( v =  0 ; v  < n_dependent_vars ; ++v ) 
+           {
+             metrics_precalc( &(*foo)->m[v] );
+           }
+
+       }
+
+      for ( v =  0 ; v  < n_dependent_vars ; ++v ) 
+       {
+         const struct variable *var = dependent_vars[v];
+         const union value *val = case_data (c, var->fv);
+
+         if ( value_is_missing (&var->miss, val) || case_missing ) 
+           val = 0;
+         
+         metrics_calc( &(*foo)->m[v], val, weight, case_no);
+         
+       }
+
+      fctr = fctr->next;
+    }
+
+
+}
+
+static bool 
+run_examine(const struct casefile *cf, void *cmd_ )
+{
+  struct casereader *r;
+  struct ccase c;
+  int v;
+
+  const struct cmd_examine *cmd = (struct cmd_examine *) cmd_;
+
+  /* Make sure we haven't got rubbish left over from a 
+     previous split */
+  struct factor *fctr = factors;
+  while (fctr) 
+    {
+      struct factor *next = fctr->next;
+
+      hsh_clear(fctr->fstats);
+
+      fctr->fs = 0;
+
+      fctr = next;
+    }
+
+
+
+  for ( v = 0 ; v < n_dependent_vars ; ++v ) 
+    metrics_precalc(&totals[v]);
+
+  for(r = casefile_get_reader (cf);
+      casereader_read (r, &c) ;
+      case_destroy (&c) ) 
+    {
+      int case_missing=0;
+      const int case_no = casereader_cnum(r);
+
+      const double weight = 
+       dict_get_case_weight(default_dict, &c, &bad_weight_warn);
+
+      if ( cmd->miss == XMN_LISTWISE ) 
+       {
+         for ( v = 0 ; v < n_dependent_vars ; ++v ) 
+           {
+             const struct variable *var = dependent_vars[v];
+             const union value *val = case_data (&c, var->fv);
+
+             if ( value_is_missing(&var->miss, val))
+               case_missing = 1;
+                  
+           }
+       }
+
+      for ( v = 0 ; v < n_dependent_vars ; ++v ) 
+       {
+         const struct variable *var = dependent_vars[v];
+         const union value *val = case_data (&c, var->fv);
+
+         if ( value_is_missing(&var->miss, val) || case_missing ) 
+           val = 0;
+
+         metrics_calc(&totals[v], val, weight, case_no);
+    
+       }
+
+      factor_calc(&c, case_no, weight, case_missing);
+
+    }
+
+
+  for ( v = 0 ; v < n_dependent_vars ; ++v)
+    {
+      fctr = factors;
+      while ( fctr ) 
+       {
+         struct hsh_iterator hi;
+         struct factor_statistics *fs;
+
+         for ( fs = hsh_first(fctr->fstats, &hi);
+               fs != 0 ;
+               fs = hsh_next(fctr->fstats, &hi))
+           {
+             
+             fs->m[v].ptile_hash = list_to_ptile_hash(&percentile_list);
+             fs->m[v].ptile_alg = percentile_algorithm;
+             metrics_postcalc(&fs->m[v]);
+           }
+
+         fctr = fctr->next;
+       }
+
+      totals[v].ptile_hash = list_to_ptile_hash(&percentile_list);
+      totals[v].ptile_alg = percentile_algorithm;
+      metrics_postcalc(&totals[v]);
+    }
+
+
+  /* Make sure that the combination of factors are complete */
+
+  fctr = factors;
+  while ( fctr ) 
+    {
+      struct hsh_iterator hi;
+      struct hsh_iterator hi0;
+      struct hsh_iterator hi1;
+      struct factor_statistics *fs;
+
+      struct hsh_table *idh0=0;
+      struct hsh_table *idh1=0;
+      union value *val0;
+      union value *val1;
+         
+      idh0 = hsh_create(4, (hsh_compare_func *) compare_values,
+                       (hsh_hash_func *) hash_value,
+                       0,0);
+
+      idh1 = hsh_create(4, (hsh_compare_func *) compare_values,
+                       (hsh_hash_func *) hash_value,
+                       0,0);
+
+
+      for ( fs = hsh_first(fctr->fstats, &hi);
+           fs != 0 ;
+           fs = hsh_next(fctr->fstats, &hi))
+       {
+         hsh_insert(idh0,(void *) &fs->id[0]);
+         hsh_insert(idh1,(void *) &fs->id[1]);
+       }
+
+      /* Ensure that the factors combination is complete */
+      for ( val0 = hsh_first(idh0, &hi0);
+           val0 != 0 ;
+           val0 = hsh_next(idh0, &hi0))
+       {
+         for ( val1 = hsh_first(idh1, &hi1);
+               val1 != 0 ;
+               val1 = hsh_next(idh1, &hi1))
+           {
+             struct factor_statistics **ffs;
+             union value key[2];
+             key[0] = *val0;
+             key[1] = *val1;
+                 
+             ffs = (struct factor_statistics **) 
+               hsh_probe(fctr->fstats, (void *) &key );
+
+             if ( !*ffs ) {
+               size_t i;
+               (*ffs) = create_factor_statistics (n_dependent_vars,
+                                                  &key[0], &key[1]);
+               for ( i = 0 ; i < n_dependent_vars ; ++i ) 
+                 metrics_precalc( &(*ffs)->m[i]);
+             }
+           }
+       }
+
+      hsh_destroy(idh0);
+      hsh_destroy(idh1);
+
+      fctr->fs = (struct factor_statistics **) hsh_sort_copy(fctr->fstats);
+
+      fctr = fctr->next;
+    }
+
+  output_examine();
+
+
+  if ( totals ) 
+    {
+      size_t i;
+      for ( i = 0 ; i < n_dependent_vars ; ++i ) 
+       {
+         metrics_destroy(&totals[i]);
+       }
+    }
+
+  return true;
+}
+
+
+static void
+show_summary(struct variable **dependent_var, int n_dep_var, 
+            const struct factor *fctr)
+{
+  static const char *subtitle[]=
+    {
+      N_("Valid"),
+      N_("Missing"),
+      N_("Total")
+    };
+
+  int i;
+  int heading_columns ;
+  int n_cols;
+  const int heading_rows = 3;
+  struct tab_table *tbl;
+
+  int n_rows ;
+  int n_factors = 1;
+
+  if ( fctr )
+    {
+      heading_columns = 2;
+      n_factors = hsh_count(fctr->fstats);
+      n_rows = n_dep_var * n_factors ;
+
+      if ( fctr->indep_var[1] )
+       heading_columns = 3;
+    }
+  else
+    {
+      heading_columns = 1;
+      n_rows = n_dep_var;
+    }
+
+  n_rows += heading_rows;
+
+  n_cols = heading_columns + 6;
+
+  tbl = tab_create (n_cols,n_rows,0);
+  tab_headers (tbl, heading_columns, 0, heading_rows, 0);
+
+  tab_dim (tbl, tab_natural_dimensions);
+  
+  /* Outline the box */
+  tab_box (tbl, 
+          TAL_2, TAL_2,
+          -1, -1,
+          0, 0,
+          n_cols - 1, n_rows - 1);
+
+  /* Vertical lines for the data only */
+  tab_box (tbl, 
+          -1, -1,
+          -1, TAL_1,
+          heading_columns, 0,
+          n_cols - 1, n_rows - 1);
+
+
+  tab_hline (tbl, TAL_2, 0, n_cols - 1, heading_rows );
+  tab_hline (tbl, TAL_1, heading_columns, n_cols - 1, 1 );
+  tab_hline (tbl, TAL_1, heading_columns, n_cols - 1, heading_rows -1 );
+
+  tab_vline (tbl, TAL_2, heading_columns, 0, n_rows - 1);
+
+
+  tab_title (tbl, 0, _("Case Processing Summary"));
+  
+
+  tab_joint_text(tbl, heading_columns, 0, 
+                n_cols -1, 0,
+                TAB_CENTER | TAT_TITLE,
+                _("Cases"));
+
+  /* Remove lines ... */
+  tab_box (tbl, 
+          -1, -1,
+          TAL_0, TAL_0,
+          heading_columns, 0,
+          n_cols - 1, 0);
+
+  for ( i = 0 ; i < 3 ; ++i ) 
+    {
+      tab_text (tbl, heading_columns + i*2 , 2, TAB_CENTER | TAT_TITLE, 
+               _("N"));
+
+      tab_text (tbl, heading_columns + i*2 + 1, 2, TAB_CENTER | TAT_TITLE, 
+               _("Percent"));
+
+      tab_joint_text(tbl, heading_columns + i*2 , 1,
+                    heading_columns + i*2 + 1, 1,
+                    TAB_CENTER | TAT_TITLE,
+                    subtitle[i]);
+
+      tab_box (tbl, -1, -1,
+              TAL_0, TAL_0,
+              heading_columns + i*2, 1,
+              heading_columns + i*2 + 1, 1);
+
+    }
+
+
+  /* Titles for the independent variables */
+  if ( fctr ) 
+    {
+      tab_text (tbl, 1, heading_rows - 1, TAB_CENTER | TAT_TITLE, 
+               var_to_string(fctr->indep_var[0]));
+
+      if ( fctr->indep_var[1] ) 
+       {
+         tab_text (tbl, 2, heading_rows - 1, TAB_CENTER | TAT_TITLE, 
+                   var_to_string(fctr->indep_var[1]));
+       }
+               
+    }
+
+
+  for ( i = 0 ; i < n_dep_var ; ++i ) 
+    {
+      int n_factors = 1;
+      if ( fctr ) 
+       n_factors = hsh_count(fctr->fstats);
+      
+
+      if ( i > 0 ) 
+       tab_hline(tbl, TAL_1, 0, n_cols -1 , i * n_factors + heading_rows);
+      
+      tab_text (tbl, 
+               0, i * n_factors + heading_rows,
+               TAB_LEFT | TAT_TITLE, 
+               var_to_string(dependent_var[i])
+               );
+
+
+      if ( !fctr ) 
+       populate_summary(tbl, heading_columns, 
+                        (i * n_factors) + heading_rows,
+                        &totals[i]);
+
+
+      else
+       {
+         struct factor_statistics **fs = fctr->fs;
+         int count = 0 ;
+
+         while (*fs) 
+           {
+             static union value prev;
+             
+             if ( 0 != compare_values(&prev, &(*fs)->id[0], 
+                                      fctr->indep_var[0]->width))
+               {
+                 tab_text (tbl, 
+                           1,
+                           (i * n_factors ) + count + 
+                           heading_rows,
+                           TAB_LEFT | TAT_TITLE, 
+                           value_to_string(&(*fs)->id[0], fctr->indep_var[0])
+                           );
+
+                 if (fctr->indep_var[1] && count > 0 ) 
+                   tab_hline(tbl, TAL_1, 1, n_cols - 1, 
+                             (i * n_factors ) + count + heading_rows);
+
+               }
+             
+             prev = (*fs)->id[0];
+
+
+             if ( fctr->indep_var[1]) 
+               tab_text (tbl, 
+                         2,
+                         (i * n_factors ) + count + 
+                         heading_rows,
+                         TAB_LEFT | TAT_TITLE, 
+                         value_to_string(&(*fs)->id[1], fctr->indep_var[1])
+                         );
+
+             populate_summary(tbl, heading_columns, 
+                              (i * n_factors) + count 
+                              + heading_rows,
+                              &(*fs)->m[i]);
+
+             count++ ; 
+             fs++;
+           }
+       }
+    }
+
+  tab_submit (tbl);
+}
+
+
+void 
+populate_summary(struct tab_table *t, int col, int row,
+                const struct metrics *m)
+
+{
+  const double total = m->n + m->n_missing ; 
+
+  tab_float(t, col + 0, row + 0, TAB_RIGHT, m->n, 8, 0);
+  tab_float(t, col + 2, row + 0, TAB_RIGHT, m->n_missing, 8, 0);
+  tab_float(t, col + 4, row + 0, TAB_RIGHT, total, 8, 0);
+
+
+  if ( total > 0 ) {
+    tab_text (t, col + 1, row + 0, TAB_RIGHT | TAT_PRINTF, "%2.0f%%", 
+             100.0 * m->n / total );
+
+    tab_text (t, col + 3, row + 0, TAB_RIGHT | TAT_PRINTF, "%2.0f%%", 
+             100.0 * m->n_missing / total );
+
+    /* This seems a bit pointless !!! */
+    tab_text (t, col + 5, row + 0, TAB_RIGHT | TAT_PRINTF, "%2.0f%%", 
+             100.0 * total / total );
+
+
+  }
+
+
+}  
+
+
+
+static void 
+show_extremes(struct variable **dependent_var, int n_dep_var, 
+             const struct factor *fctr, int n_extremities)
+{
+  int i;
+  int heading_columns ;
+  int n_cols;
+  const int heading_rows = 1;
+  struct tab_table *tbl;
+
+  int n_factors = 1;
+  int n_rows ;
+
+  if ( fctr )
+    {
+      heading_columns = 2;
+      n_factors = hsh_count(fctr->fstats);
+
+      n_rows = n_dep_var * 2 * n_extremities * n_factors;
+
+      if ( fctr->indep_var[1] )
+       heading_columns = 3;
+    }
+  else
+    {
+      heading_columns = 1;
+      n_rows = n_dep_var * 2 * n_extremities;
+    }
+
+  n_rows += heading_rows;
+
+  heading_columns += 2;
+  n_cols = heading_columns + 2;
+
+  tbl = tab_create (n_cols,n_rows,0);
+  tab_headers (tbl, heading_columns, 0, heading_rows, 0);
+
+  tab_dim (tbl, tab_natural_dimensions);
+  
+  /* Outline the box, No internal lines*/
+  tab_box (tbl, 
+          TAL_2, TAL_2,
+          -1, -1,
+          0, 0,
+          n_cols - 1, n_rows - 1);
+
+  tab_hline (tbl, TAL_2, 0, n_cols - 1, heading_rows );
+
+  tab_title (tbl, 0, _("Extreme Values"));
+
+  tab_vline (tbl, TAL_2, n_cols - 2, 0, n_rows -1);
+  tab_vline (tbl, TAL_1, n_cols - 1, 0, n_rows -1);
+
+  if ( fctr ) 
+    {
+      tab_text (tbl, 1, heading_rows - 1, TAB_CENTER | TAT_TITLE, 
+               var_to_string(fctr->indep_var[0]));
+
+      if ( fctr->indep_var[1] ) 
+       tab_text (tbl, 2, heading_rows - 1, TAB_CENTER | TAT_TITLE, 
+                 var_to_string(fctr->indep_var[1]));
+    }
+
+  tab_text (tbl, n_cols - 1, 0, TAB_CENTER | TAT_TITLE, _("Value"));
+  tab_text (tbl, n_cols - 2, 0, TAB_CENTER | TAT_TITLE, _("Case Number"));
+
+  for ( i = 0 ; i < n_dep_var ; ++i ) 
+    {
+
+      if ( i > 0 ) 
+       tab_hline(tbl, TAL_1, 0, n_cols -1 , 
+                 i * 2 * n_extremities * n_factors + heading_rows);
+      
+      tab_text (tbl, 0,
+               i * 2 * n_extremities * n_factors  + heading_rows,
+               TAB_LEFT | TAT_TITLE, 
+               var_to_string(dependent_var[i])
+               );
+
+
+      if ( !fctr ) 
+       populate_extremes(tbl, heading_columns - 2, 
+                         i * 2 * n_extremities * n_factors  + heading_rows,
+                         n_extremities, &totals[i]);
+
+      else
+       {
+         struct factor_statistics **fs = fctr->fs;
+         int count = 0 ;
+
+         while (*fs) 
+           {
+             static union value prev ;
+
+             const int row = heading_rows + ( 2 * n_extremities )  * 
+               ( ( i  * n_factors  ) +  count );
+
+
+             if ( 0 != compare_values(&prev, &(*fs)->id[0], 
+                                      fctr->indep_var[0]->width))
+               {
+                 
+                 if ( count > 0 ) 
+                   tab_hline (tbl, TAL_1, 1, n_cols - 1, row);
+
+                 tab_text (tbl, 
+                           1, row,
+                           TAB_LEFT | TAT_TITLE, 
+                           value_to_string(&(*fs)->id[0], fctr->indep_var[0])
+                           );
+               }
+
+             prev = (*fs)->id[0];
+
+             if (fctr->indep_var[1] && count > 0 ) 
+               tab_hline(tbl, TAL_1, 2, n_cols - 1, row);
+
+             if ( fctr->indep_var[1]) 
+               tab_text (tbl, 2, row,
+                         TAB_LEFT | TAT_TITLE, 
+                         value_to_string(&(*fs)->id[1], fctr->indep_var[1])
+                         );
+
+             populate_extremes(tbl, heading_columns - 2, 
+                               row, n_extremities,
+                               &(*fs)->m[i]);
+
+             count++ ; 
+             fs++;
+           }
+       }
+    }
+
+  tab_submit(tbl);
+}
+
+
+
+/* Fill in the extremities table */
+void 
+populate_extremes(struct tab_table *t, 
+                 int col, int row, int n, const struct metrics *m)
+{
+  int extremity;
+  int idx=0;
+
+
+  tab_text(t, col, row,
+          TAB_RIGHT | TAT_TITLE ,
+          _("Highest")
+          );
+
+  tab_text(t, col, row + n ,
+          TAB_RIGHT | TAT_TITLE ,
+          _("Lowest")
+          );
+
+
+  tab_hline(t, TAL_1, col, col + 3, row + n );
+           
+  for (extremity = 0; extremity < n ; ++extremity ) 
+    {
+      /* Highest */
+      tab_float(t, col + 1, row + extremity,
+               TAB_RIGHT,
+               extremity + 1, 8, 0);
+
+
+      /* Lowest */
+      tab_float(t, col + 1, row + extremity + n,
+               TAB_RIGHT,
+               extremity + 1, 8, 0);
+
+    }
+
+
+  /* Lowest */
+  for (idx = 0, extremity = 0; extremity < n && idx < m->n_data ; ++idx ) 
+    {
+      int j;
+      const struct weighted_value *wv = m->wvp[idx];
+      struct case_node *cn = wv->case_nos;
+
+      
+      for (j = 0 ; j < wv->w ; ++j  )
+       {
+         if ( extremity + j >= n ) 
+           break ;
+
+         tab_float(t, col + 3, row + extremity + j  + n,
+                   TAB_RIGHT,
+                   wv->v.f, 8, 2);
+
+         tab_float(t, col + 2, row + extremity + j  + n,
+                   TAB_RIGHT,
+                   cn->num, 8, 0);
+
+         if ( cn->next ) 
+           cn = cn->next;
+
+       }
+
+      extremity +=  wv->w ;
+    }
+
+
+  /* Highest */
+  for (idx = m->n_data - 1, extremity = 0; extremity < n && idx >= 0; --idx ) 
+    {
+      int j;
+      const struct weighted_value *wv = m->wvp[idx];
+      struct case_node *cn = wv->case_nos;
+
+      for (j = 0 ; j < wv->w ; ++j  )
+       {
+         if ( extremity + j >= n ) 
+           break ;
+
+         tab_float(t, col + 3, row + extremity + j,
+                   TAB_RIGHT,
+                   wv->v.f, 8, 2);
+
+         tab_float(t, col + 2, row + extremity + j,
+                   TAB_RIGHT,
+                   cn->num, 8, 0);
+
+         if ( cn->next ) 
+           cn = cn->next;
+
+       }
+
+      extremity +=  wv->w ;
+    }
+}
+
+
+/* Show the descriptives table */
+void
+show_descriptives(struct variable **dependent_var, 
+                 int n_dep_var, 
+                 struct factor *fctr)
+{
+  int i;
+  int heading_columns ;
+  int n_cols;
+  const int n_stat_rows = 13;
+
+  const int heading_rows = 1;
+
+  struct tab_table *tbl;
+
+  int n_factors = 1;
+  int n_rows ;
+
+  if ( fctr )
+    {
+      heading_columns = 4;
+      n_factors = hsh_count(fctr->fstats);
+
+      n_rows = n_dep_var * n_stat_rows * n_factors;
+
+      if ( fctr->indep_var[1] )
+       heading_columns = 5;
+    }
+  else
+    {
+      heading_columns = 3;
+      n_rows = n_dep_var * n_stat_rows;
+    }
+
+  n_rows += heading_rows;
+
+  n_cols = heading_columns + 2;
+
+
+  tbl = tab_create (n_cols, n_rows, 0);
+
+  tab_headers (tbl, heading_columns + 1, 0, heading_rows, 0);
+
+  tab_dim (tbl, tab_natural_dimensions);
+
+  /* Outline the box and have no internal lines*/
+  tab_box (tbl, 
+          TAL_2, TAL_2,
+          -1, -1,
+          0, 0,
+          n_cols - 1, n_rows - 1);
+
+  tab_hline (tbl, TAL_2, 0, n_cols - 1, heading_rows );
+
+  tab_vline (tbl, TAL_1, heading_columns, 0, n_rows - 1);
+  tab_vline (tbl, TAL_2, n_cols - 2, 0, n_rows - 1);
+  tab_vline (tbl, TAL_1, n_cols - 1, 0, n_rows - 1);
+
+  tab_text (tbl, n_cols - 2, 0, TAB_CENTER | TAT_TITLE, _("Statistic"));
+  tab_text (tbl, n_cols - 1, 0, TAB_CENTER | TAT_TITLE, _("Std. Error"));
+
+  tab_title (tbl, 0, _("Descriptives"));
+
+
+  for ( i = 0 ; i < n_dep_var ; ++i ) 
+    {
+      const int row = heading_rows + i * n_stat_rows * n_factors ;
+
+      if ( i > 0 )
+       tab_hline(tbl, TAL_1, 0, n_cols - 1, row );
+
+      tab_text (tbl, 0,
+               i * n_stat_rows * n_factors  + heading_rows,
+               TAB_LEFT | TAT_TITLE, 
+               var_to_string(dependent_var[i])
+               );
+
+
+      if ( fctr  )
+       {
+         struct factor_statistics **fs = fctr->fs;
+         int count = 0;
+
+         tab_text (tbl, 1, heading_rows - 1, TAB_CENTER | TAT_TITLE, 
+                   var_to_string(fctr->indep_var[0]));
+
+
+         if ( fctr->indep_var[1])
+           tab_text (tbl, 2, heading_rows - 1, TAB_CENTER | TAT_TITLE, 
+                     var_to_string(fctr->indep_var[1]));
+
+         while( *fs ) 
+           {
+
+             static union value prev ;
+
+             const int row = heading_rows + n_stat_rows  * 
+               ( ( i  * n_factors  ) +  count );
+
+
+             if ( 0 != compare_values(&prev, &(*fs)->id[0], 
+                                      fctr->indep_var[0]->width))
+               {
+                 
+                 if ( count > 0 ) 
+                   tab_hline (tbl, TAL_1, 1, n_cols - 1, row);
+
+                 tab_text (tbl, 
+                           1, row,
+                           TAB_LEFT | TAT_TITLE, 
+                           value_to_string(&(*fs)->id[0], fctr->indep_var[0])
+                           );
+               }
+
+             prev = (*fs)->id[0];
+
+             if (fctr->indep_var[1] && count > 0 ) 
+               tab_hline(tbl, TAL_1, 2, n_cols - 1, row);
+
+             if ( fctr->indep_var[1]) 
+               tab_text (tbl, 2, row,
+                         TAB_LEFT | TAT_TITLE, 
+                         value_to_string(&(*fs)->id[1], fctr->indep_var[1])
+                         );
+
+             populate_descriptives(tbl, heading_columns - 2, 
+                                   row, &(*fs)->m[i]);
+
+             count++ ; 
+             fs++;
+           }
+
+       }
+
+      else 
+       {
+         
+         populate_descriptives(tbl, heading_columns - 2, 
+                               i * n_stat_rows * n_factors  + heading_rows,
+                               &totals[i]);
+       }
+    }
+
+  tab_submit(tbl);
+
+}
+
+
+
+
+/* Fill in the descriptives data */
+void
+populate_descriptives(struct tab_table *tbl, int col, int row, 
+                     const struct metrics *m)
+{
+
+  const double t = gsl_cdf_tdist_Qinv(1 - cmd.n_cinterval[0]/100.0/2.0, \
+                                     m->n -1);
+
+
+  tab_text (tbl, col, 
+           row,
+           TAB_LEFT | TAT_TITLE,
+           _("Mean"));
+
+  tab_float (tbl, col + 2,
+            row,
+            TAB_CENTER,
+            m->mean,
+            8,2);
+  
+  tab_float (tbl, col + 3,
+            row,
+            TAB_CENTER,
+            m->se_mean,
+            8,3);
+  
+
+  tab_text (tbl, col, 
+           row + 1,
+           TAB_LEFT | TAT_TITLE | TAT_PRINTF,
+           _("%g%% Confidence Interval for Mean"), cmd.n_cinterval[0]);
+
+
+  tab_text (tbl, col + 1, 
+           row  + 1,
+           TAB_LEFT | TAT_TITLE,
+           _("Lower Bound"));
+
+  tab_float (tbl, col + 2,
+            row + 1,
+            TAB_CENTER,
+            m->mean - t * m->se_mean, 
+            8,3);
+
+  tab_text (tbl, col + 1,  
+           row + 2,
+           TAB_LEFT | TAT_TITLE,
+           _("Upper Bound"));
+
+
+  tab_float (tbl, col + 2,
+            row + 2,
+            TAB_CENTER,
+            m->mean + t * m->se_mean, 
+            8,3);
+
+  tab_text (tbl, col, 
+           row + 3,
+           TAB_LEFT | TAT_TITLE | TAT_PRINTF,
+           _("5%% Trimmed Mean"));
+
+  tab_float (tbl, col + 2, 
+            row + 3,
+            TAB_CENTER,
+            m->trimmed_mean,
+            8,2);
+
+  tab_text (tbl, col, 
+           row + 4,
+           TAB_LEFT | TAT_TITLE,
+           _("Median"));
+
+  {
+    struct percentile *p;
+    double d = 50;
+    
+    p = hsh_find(m->ptile_hash, &d);
+    
+    assert(p);
+
+
+    tab_float (tbl, col + 2, 
+              row + 4,
+              TAB_CENTER,
+              p->v,
+              8, 2);
+  }
+    
+
+  tab_text (tbl, col, 
+           row + 5,
+           TAB_LEFT | TAT_TITLE,
+           _("Variance"));
+
+  tab_float (tbl, col + 2,
+            row + 5,
+            TAB_CENTER,
+            m->var,
+            8,3);
+
+
+  tab_text (tbl, col, 
+           row + 6,
+           TAB_LEFT | TAT_TITLE,
+           _("Std. Deviation"));
+
+
+  tab_float (tbl, col + 2,
+            row + 6,
+            TAB_CENTER,
+            m->stddev,
+            8,3);
+
+  
+  tab_text (tbl, col, 
+           row + 7,
+           TAB_LEFT | TAT_TITLE,
+           _("Minimum"));
+
+  tab_float (tbl, col + 2,
+            row + 7,
+            TAB_CENTER,
+            m->min,
+            8,3);
+
+  tab_text (tbl, col, 
+           row + 8,
+           TAB_LEFT | TAT_TITLE,
+           _("Maximum"));
+
+  tab_float (tbl, col + 2,
+            row + 8,
+            TAB_CENTER,
+            m->max,
+            8,3);
+
+
+  tab_text (tbl, col, 
+           row + 9,
+           TAB_LEFT | TAT_TITLE,
+           _("Range"));
+
+
+  tab_float (tbl, col + 2,
+            row + 9,
+            TAB_CENTER,
+            m->max - m->min,
+            8,3);
+
+  tab_text (tbl, col, 
+           row + 10,
+           TAB_LEFT | TAT_TITLE,
+           _("Interquartile Range"));
+
+  {
+    struct percentile *p1;
+    struct percentile *p2;
+
+    double d = 75;
+    p1 = hsh_find(m->ptile_hash, &d);
+
+    d = 25;
+    p2 = hsh_find(m->ptile_hash, &d);
+
+    assert(p1);
+    assert(p2);
+
+    tab_float (tbl, col + 2, 
+              row + 10,
+              TAB_CENTER,
+              p1->v - p2->v,
+              8, 2);
+  }
+
+
+
+  tab_text (tbl, col, 
+           row + 11,
+           TAB_LEFT | TAT_TITLE,
+           _("Skewness"));
+
+
+  tab_float (tbl, col + 2,
+            row + 11,
+            TAB_CENTER,
+            m->skewness,
+            8,3);
+
+  /* stderr of skewness */
+  tab_float (tbl, col + 3,
+            row + 11,
+            TAB_CENTER,
+            calc_seskew(m->n),
+            8,3);
+
+
+  tab_text (tbl, col, 
+           row + 12,
+           TAB_LEFT | TAT_TITLE,
+           _("Kurtosis"));
+
+
+  tab_float (tbl, col + 2,
+            row + 12,
+            TAB_CENTER,
+            m->kurtosis,
+            8,3);
+
+  /* stderr of kurtosis */
+  tab_float (tbl, col + 3,
+            row + 12,
+            TAB_CENTER,
+            calc_sekurt(m->n),
+            8,3);
+
+
+}
+
+
+
+void
+box_plot_variables(const struct factor *fctr, 
+                  const struct variable **vars, int n_vars, 
+                  const struct variable *id)
+{
+
+  int i;
+  struct factor_statistics **fs ;
+
+  if ( ! fctr ) 
+    {
+      box_plot_group(fctr, vars, n_vars, id);
+      return;
+    }
+
+  for ( fs = fctr->fs ; *fs ; ++fs ) 
+    {
+      double y_min = DBL_MAX;
+      double y_max = -DBL_MAX;
+      struct chart *ch = chart_create();
+      const char *s = factor_to_string(fctr, *fs, 0 );
+
+      chart_write_title(ch, s);
+
+      for ( i = 0 ; i < n_vars ; ++i ) 
+       {
+         y_max = max(y_max, (*fs)->m[i].max);
+         y_min = min(y_min, (*fs)->m[i].min);
+       }
+      
+      boxplot_draw_yscale(ch, y_max, y_min);
+         
+      for ( i = 0 ; i < n_vars ; ++i ) 
+       {
+
+         const double box_width = (ch->data_right - ch->data_left) 
+           / (n_vars * 2.0 ) ;
+
+         const double box_centre = ( i * 2 + 1) * box_width 
+           + ch->data_left;
+             
+         boxplot_draw_boxplot(ch,
+                              box_centre, box_width,
+                              &(*fs)->m[i],
+                              var_to_string(vars[i]));
+
+
+       }
+
+      chart_submit(ch);
+
+    }
+}
+
+
+
+/* Do a box plot, grouping all factors into one plot ;
+   each dependent variable has its own plot.
+*/
+void
+box_plot_group(const struct factor *fctr, 
+              const struct variable **vars, 
+              int n_vars,
+              const struct variable *id UNUSED)
+{
+
+  int i;
+
+  for ( i = 0 ; i < n_vars ; ++i ) 
+    {
+      struct factor_statistics **fs ;
+      struct chart *ch;
+
+      ch = chart_create();
+
+      boxplot_draw_yscale(ch, totals[i].max, totals[i].min);
+
+      if ( fctr ) 
+       {
+         int n_factors = 0;
+         int f=0;
+         for ( fs = fctr->fs ; *fs ; ++fs ) 
+           ++n_factors;
+
+         chart_write_title(ch, _("Boxplot of %s vs. %s"), 
+                           var_to_string(vars[i]), var_to_string(fctr->indep_var[0]) );
+
+         for ( fs = fctr->fs ; *fs ; ++fs ) 
+           {
+             
+             const char *s = factor_to_string_concise(fctr, *fs);
+
+             const double box_width = (ch->data_right - ch->data_left) 
+               / (n_factors * 2.0 ) ;
+
+             const double box_centre = ( f++ * 2 + 1) * box_width 
+               + ch->data_left;
+             
+             boxplot_draw_boxplot(ch,
+                                  box_centre, box_width,
+                                  &(*fs)->m[i],
+                                  s);
+           }
+       }
+      else if ( ch )
+       {
+         const double box_width = (ch->data_right - ch->data_left) / 3.0;
+         const double box_centre = (ch->data_right + ch->data_left) / 2.0;
+
+         chart_write_title(ch, _("Boxplot"));
+
+         boxplot_draw_boxplot(ch,
+                              box_centre,    box_width, 
+                              &totals[i],
+                              var_to_string(vars[i]) );
+         
+       }
+
+      chart_submit(ch);
+    }
+}
+
+
+/* Plot the normal and detrended normal plots for m
+   Label the plots with factorname */
+void
+np_plot(const struct metrics *m, const char *factorname)
+{
+  int i;
+  double yfirst=0, ylast=0;
+
+  /* Normal Plot */
+  struct chart *np_chart;
+
+  /* Detrended Normal Plot */
+  struct chart *dnp_chart;
+
+  /* The slope and intercept of the ideal normal probability line */
+  const double slope = 1.0 / m->stddev;
+  const double intercept = - m->mean / m->stddev;
+
+  /* Cowardly refuse to plot an empty data set */
+  if ( m->n_data == 0 ) 
+    return ; 
+
+  np_chart = chart_create();
+  dnp_chart = chart_create();
+
+  if ( !np_chart || ! dnp_chart ) 
+    return ;
+
+  chart_write_title(np_chart, _("Normal Q-Q Plot of %s"), factorname);
+  chart_write_xlabel(np_chart, _("Observed Value"));
+  chart_write_ylabel(np_chart, _("Expected Normal"));
+
+
+  chart_write_title(dnp_chart, _("Detrended Normal Q-Q Plot of %s"), 
+                   factorname);
+  chart_write_xlabel(dnp_chart, _("Observed Value"));
+  chart_write_ylabel(dnp_chart, _("Dev from Normal"));
+
+  yfirst = gsl_cdf_ugaussian_Pinv (m->wvp[0]->rank / ( m->n + 1));
+  ylast =  gsl_cdf_ugaussian_Pinv (m->wvp[m->n_data-1]->rank / ( m->n + 1));
+
+
+  {
+    /* Need to make sure that both the scatter plot and the ideal fit into the
+       plot */
+    double x_lower = min(m->min, (yfirst - intercept) / slope) ;
+    double x_upper = max(m->max, (ylast  - intercept) / slope) ;
+    double slack = (x_upper - x_lower)  * 0.05 ;
+
+    chart_write_xscale(np_chart, x_lower - slack, x_upper + slack, 5);
+
+    chart_write_xscale(dnp_chart, m->min, m->max, 5);
+
+  }
+
+  chart_write_yscale(np_chart, yfirst, ylast, 5);
+
+  {
+    /* We have to cache the detrended data, beacause we need to 
+       find its limits before we can plot it */
+    double *d_data = xnmalloc (m->n_data, sizeof *d_data);
+    double d_max = -DBL_MAX;
+    double d_min = DBL_MAX;
+    for ( i = 0 ; i < m->n_data; ++i ) 
+      {
+       const double ns = gsl_cdf_ugaussian_Pinv (m->wvp[i]->rank / ( m->n + 1));
+
+       chart_datum(np_chart, 0, m->wvp[i]->v.f, ns);
+
+       d_data[i] = (m->wvp[i]->v.f - m->mean) / m->stddev  - ns;
+   
+       if ( d_data[i] < d_min ) d_min = d_data[i];
+       if ( d_data[i] > d_max ) d_max = d_data[i];
+      }
+    chart_write_yscale(dnp_chart, d_min, d_max, 5);
+
+    for ( i = 0 ; i < m->n_data; ++i ) 
+      chart_datum(dnp_chart, 0, m->wvp[i]->v.f, d_data[i]);
+
+    free(d_data);
+  }
+
+  chart_line(np_chart, slope, intercept, yfirst, ylast , CHART_DIM_Y);
+  chart_line(dnp_chart, 0, 0, m->min, m->max , CHART_DIM_X);
+
+  chart_submit(np_chart);
+  chart_submit(dnp_chart);
+}
+
+
+
+
+/* Show the percentiles */
+void
+show_percentiles(struct variable **dependent_var, 
+                int n_dep_var, 
+                struct factor *fctr)
+{
+  struct tab_table *tbl;
+  int i;
+  
+  int n_cols, n_rows;
+  int n_factors;
+
+  struct hsh_table *ptiles ;
+
+  int n_heading_columns;
+  const int n_heading_rows = 2;
+  const int n_stat_rows = 2;
+
+  int n_ptiles ;
+
+  if ( fctr )
+    {
+      struct factor_statistics **fs = fctr->fs ; 
+      n_heading_columns = 3;
+      n_factors = hsh_count(fctr->fstats);
+
+      ptiles = (*fs)->m[0].ptile_hash;
+
+      if ( fctr->indep_var[1] )
+       n_heading_columns = 4;
+    }
+  else
+    {
+      n_factors = 1;
+      n_heading_columns = 2;
+
+      ptiles = totals[0].ptile_hash;
+    }
+
+  n_ptiles = hsh_count(ptiles);
+
+  n_rows = n_heading_rows + n_dep_var * n_stat_rows * n_factors;
+
+  n_cols = n_heading_columns + n_ptiles ; 
+
+  tbl = tab_create (n_cols, n_rows, 0);
+
+  tab_headers (tbl, n_heading_columns + 1, 0, n_heading_rows, 0);
+
+  tab_dim (tbl, tab_natural_dimensions);
+
+  /* Outline the box and have no internal lines*/
+  tab_box (tbl, 
+          TAL_2, TAL_2,
+          -1, -1,
+          0, 0,
+          n_cols - 1, n_rows - 1);
+
+  tab_hline (tbl, TAL_2, 0, n_cols - 1, n_heading_rows );
+
+  tab_vline (tbl, TAL_2, n_heading_columns, 0, n_rows - 1);
+
+
+  tab_title (tbl, 0, _("Percentiles"));
+
+
+  tab_hline (tbl, TAL_1, n_heading_columns, n_cols - 1, 1 );
+
+
+  tab_box (tbl, 
+          -1, -1,
+          -1, TAL_1,
+          0, n_heading_rows,
+          n_heading_columns - 1, n_rows - 1);
+
+
+  tab_box (tbl, 
+          -1, -1,
+          -1, TAL_1,
+          n_heading_columns, n_heading_rows - 1,
+          n_cols - 1, n_rows - 1);
+
+  tab_joint_text(tbl, n_heading_columns + 1, 0,
+                n_cols - 1 , 0,
+                TAB_CENTER | TAT_TITLE ,
+                _("Percentiles"));
+
+
+  {
+    /* Put in the percentile break points as headings */
+
+    struct percentile **p = (struct percentile **) hsh_sort(ptiles);
+
+    i = 0;
+    while ( (*p)  ) 
+      {
+       tab_float(tbl, n_heading_columns + i++ , 1, 
+                 TAB_CENTER,
+                 (*p)->p, 8, 0);
+       
+       p++;
+      }
+
+  }
+
+  for ( i = 0 ; i < n_dep_var ; ++i ) 
+    {
+      const int n_stat_rows = 2;
+      const int row = n_heading_rows + i * n_stat_rows * n_factors ;
+
+      if ( i > 0 )
+       tab_hline(tbl, TAL_1, 0, n_cols - 1, row );
+
+      tab_text (tbl, 0,
+               i * n_stat_rows * n_factors  + n_heading_rows,
+               TAB_LEFT | TAT_TITLE, 
+               var_to_string(dependent_var[i])
+               );
+
+      if ( fctr  )
+       {
+         struct factor_statistics **fs = fctr->fs;
+         int count = 0;
+
+         tab_text (tbl, 1, n_heading_rows - 1, 
+                   TAB_CENTER | TAT_TITLE, 
+                   var_to_string(fctr->indep_var[0]));
+
+
+         if ( fctr->indep_var[1])
+           tab_text (tbl, 2, n_heading_rows - 1, TAB_CENTER | TAT_TITLE, 
+                     var_to_string(fctr->indep_var[1]));
+
+         while( *fs ) 
+           {
+
+             static union value prev ;
+
+             const int row = n_heading_rows + n_stat_rows  * 
+               ( ( i  * n_factors  ) +  count );
+
+
+             if ( 0 != compare_values(&prev, &(*fs)->id[0], 
+                                      fctr->indep_var[0]->width))
+               {
+                 
+                 if ( count > 0 ) 
+                   tab_hline (tbl, TAL_1, 1, n_cols - 1, row);
+
+                 tab_text (tbl, 
+                           1, row,
+                           TAB_LEFT | TAT_TITLE, 
+                           value_to_string(&(*fs)->id[0], fctr->indep_var[0])
+                           );
+
+
+               }
+
+             prev = (*fs)->id[0];
+
+             if (fctr->indep_var[1] && count > 0 ) 
+               tab_hline(tbl, TAL_1, 2, n_cols - 1, row);
+
+             if ( fctr->indep_var[1]) 
+               tab_text (tbl, 2, row,
+                         TAB_LEFT | TAT_TITLE, 
+                         value_to_string(&(*fs)->id[1], fctr->indep_var[1])
+                         );
+
+
+             populate_percentiles(tbl, n_heading_columns - 1, 
+                                  row, &(*fs)->m[i]);
+
+
+             count++ ; 
+             fs++;
+           }
+
+
+       }
+      else 
+       {
+         populate_percentiles(tbl, n_heading_columns - 1, 
+                              i * n_stat_rows * n_factors  + n_heading_rows,
+                              &totals[i]);
+       }
+
+
+    }
+
+
+  tab_submit(tbl);
+
+
+}
+
+
+
+
+void
+populate_percentiles(struct tab_table *tbl, int col, int row, 
+                    const struct metrics *m)
+{
+  int i;
+
+  struct percentile **p = (struct percentile **) hsh_sort(m->ptile_hash);
+  
+  tab_text (tbl, 
+           col, row + 1,
+           TAB_LEFT | TAT_TITLE, 
+           _("Tukey\'s Hinges")
+           );
+
+  tab_text (tbl, 
+           col, row, 
+           TAB_LEFT | TAT_TITLE, 
+           ptile_alg_desc[m->ptile_alg]
+           );
+
+
+  i = 0;
+  while ( (*p)  ) 
+    {
+      tab_float(tbl, col + i + 1 , row, 
+               TAB_CENTER,
+               (*p)->v, 8, 2);
+      if ( (*p)->p == 25 ) 
+       tab_float(tbl, col + i + 1 , row + 1, 
+                 TAB_CENTER,
+                 m->hinge[0], 8, 2);
+
+      if ( (*p)->p == 50 ) 
+       tab_float(tbl, col + i + 1 , row + 1, 
+                 TAB_CENTER,
+                 m->hinge[1], 8, 2);
+
+      if ( (*p)->p == 75 ) 
+       tab_float(tbl, col + i + 1 , row + 1, 
+                 TAB_CENTER,
+                 m->hinge[2], 8, 2);
+
+
+      i++;
+
+      p++;
+    }
+
+}
+
+
+
+const char *
+factor_to_string(const struct factor *fctr, 
+                struct factor_statistics *fs,
+                const struct variable *var)
+{
+
+  static char buf1[100];
+  char buf2[100];
+
+  strcpy(buf1,"");
+
+  if (var)
+    sprintf(buf1, "%s (",var_to_string(var) );
+
+                     
+  snprintf(buf2, 100, "%s = %s",
+          var_to_string(fctr->indep_var[0]),
+          value_to_string(&fs->id[0],fctr->indep_var[0]));
+                     
+  strcat(buf1, buf2);
+                     
+  if ( fctr->indep_var[1] ) 
+    {
+      sprintf(buf2, "; %s = %s)",
+             var_to_string(fctr->indep_var[1]),
+             value_to_string(&fs->id[1],
+                             fctr->indep_var[1]));
+      strcat(buf1, buf2);
+    }
+  else
+    {
+      if ( var ) 
+       strcat(buf1, ")");
+    }
+
+  return buf1;
+}
+
+
+
+const char *
+factor_to_string_concise(const struct factor *fctr, 
+                        struct factor_statistics *fs)
+
+{
+
+  static char buf[100];
+
+  char buf2[100];
+
+  snprintf(buf, 100, "%s",
+          value_to_string(&fs->id[0], fctr->indep_var[0]));
+                     
+  if ( fctr->indep_var[1] ) 
+    {
+      sprintf(buf2, ",%s)", value_to_string(&fs->id[1], fctr->indep_var[1]) );
+      strcat(buf, buf2);
+    }
+
+
+  return buf;
+}
diff --git a/src/language/stats/flip.c b/src/language/stats/flip.c
new file mode 100644 (file)
index 0000000..f816a0f
--- /dev/null
@@ -0,0 +1,565 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include "config.h"
+#include "message.h"
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <limits.h>
+#include <stdlib.h>
+#include "array.h"
+#include "alloc.h"
+#include "case.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "lexer.h"
+#include "misc.h"
+#include "pool.h"
+#include "settings.h"
+#include "str.h"
+#include "value.h"
+#include "variable.h"
+#include "procedure.h"
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* List of variable names. */
+struct varname
+  {
+    struct varname *next;
+    char name[SHORT_NAME_LEN + 1];
+  };
+
+/* Represents a FLIP input program. */
+struct flip_pgm 
+  {
+    struct pool *pool;          /* Pool containing FLIP data. */
+    struct variable **var;      /* Variables to transpose. */
+    int *idx_to_fv;             /* var[]->index to compacted sink case fv. */
+    size_t var_cnt;             /* Number of elements in `var'. */
+    int case_cnt;               /* Pre-flip case count. */
+    size_t case_size;           /* Post-flip bytes per case. */
+
+    union value *output_buf;            /* Case output buffer. */
+
+    struct variable *new_names; /* Variable containing new variable names. */
+    struct varname *new_names_head; /* First new variable. */
+    struct varname *new_names_tail; /* Last new variable. */
+
+    FILE *file;                 /* Temporary file containing data. */
+  };
+
+static void destroy_flip_pgm (struct flip_pgm *);
+static struct case_sink *flip_sink_create (struct flip_pgm *);
+static struct case_source *flip_source_create (struct flip_pgm *);
+static bool flip_file (struct flip_pgm *);
+static int build_dictionary (struct flip_pgm *);
+
+static const struct case_source_class flip_source_class;
+static const struct case_sink_class flip_sink_class;
+
+/* Parses and executes FLIP. */
+int
+cmd_flip (void)
+{
+  struct flip_pgm *flip;
+  bool ok;
+
+  if (temporary != 0)
+    {
+      msg (SM, _("FLIP ignores TEMPORARY.  "
+                 "Temporary transformations will be made permanent."));
+      cancel_temporary (); 
+    }
+
+  flip = pool_create_container (struct flip_pgm, pool);
+  flip->var = NULL;
+  flip->idx_to_fv = dict_get_compacted_idx_to_fv (default_dict);
+  pool_register (flip->pool, free, flip->idx_to_fv);
+  flip->var_cnt = 0;
+  flip->case_cnt = 0;
+  flip->new_names = NULL;
+  flip->new_names_head = NULL;
+  flip->new_names_tail = NULL;
+  flip->file = NULL;
+
+  lex_match ('/');
+  if (lex_match_id ("VARIABLES"))
+    {
+      lex_match ('=');
+      if (!parse_variables (default_dict, &flip->var, &flip->var_cnt,
+                            PV_NO_DUPLICATE))
+       goto error;
+      lex_match ('/');
+    }
+  else
+    dict_get_vars (default_dict, &flip->var, &flip->var_cnt, 1u << DC_SYSTEM);
+  pool_register (flip->pool, free, flip->var);
+
+  lex_match ('/');
+  if (lex_match_id ("NEWNAMES"))
+    {
+      lex_match ('=');
+      flip->new_names = parse_variable ();
+      if (!flip->new_names)
+        goto error;
+    }
+  else
+    flip->new_names = dict_lookup_var (default_dict, "CASE_LBL");
+
+  if (flip->new_names)
+    {
+      size_t i;
+      
+      for (i = 0; i < flip->var_cnt; i++)
+       if (flip->var[i] == flip->new_names)
+         {
+            remove_element (flip->var, flip->var_cnt, sizeof *flip->var, i);
+           flip->var_cnt--;
+           break;
+         }
+    }
+
+  /* Read the active file into a flip_sink. */
+  flip->case_cnt = 0;
+  temp_trns = temporary = 0;
+  vfm_sink = flip_sink_create (flip);
+  if (vfm_sink == NULL)
+    goto error;
+  flip->new_names_tail = NULL;
+  ok = procedure (NULL, NULL);
+
+  /* Flip the data we read. */
+  if (!flip_file (flip)) 
+    {
+      discard_variables ();
+      goto error;
+    }
+
+  /* Flip the dictionary. */
+  dict_clear (default_dict);
+  if (!build_dictionary (flip))
+    {
+      discard_variables ();
+      goto error;
+    }
+  flip->case_size = dict_get_case_size (default_dict);
+
+  /* Set up flipped data for reading. */
+  vfm_source = flip_source_create (flip);
+
+  return ok ? lex_end_of_command () : CMD_CASCADING_FAILURE;
+
+ error:
+  destroy_flip_pgm (flip);
+  return CMD_CASCADING_FAILURE;
+}
+
+/* Destroys FLIP. */
+static void
+destroy_flip_pgm (struct flip_pgm *flip) 
+{
+  if (flip != NULL)
+    pool_destroy (flip->pool);
+}
+
+/* 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[])
+{
+  char *cp;
+
+  /* Trim trailing spaces. */
+  cp = strchr (name, '\0');
+  while (cp > name && isspace ((unsigned char) cp[-1]))
+    *--cp = '\0';
+
+  /* Fix invalid characters. */
+  for (cp = name; *cp && cp < name + SHORT_NAME_LEN; cp++)
+    if (cp == name) 
+      {
+        if (!lex_is_id1 (*cp) || *cp == '$')
+          *cp = 'V';
+      }
+    else
+      {
+        if (!lex_is_idn (*cp))
+          *cp = '_'; 
+      }
+  *cp = '\0';
+  str_uppercase (name);
+  
+  if (dict_create_var (default_dict, name, 0))
+    return 1;
+
+  /* Add numeric extensions until acceptable. */
+  {
+    const int len = (int) strlen (name);
+    char n[SHORT_NAME_LEN + 1];
+    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 (dict_create_var (default_dict, n, 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 (struct flip_pgm *flip)
+{
+  dict_create_var_assert (default_dict, "CASE_LBL", 8);
+
+  if (flip->new_names_head == NULL)
+    {
+      int i;
+      
+      if (flip->case_cnt > 99999)
+       {
+         msg (SE, _("Cannot create more than 99999 variable names."));
+         return 0;
+       }
+      
+      for (i = 0; i < flip->case_cnt; i++)
+       {
+          struct variable *v;
+         char s[SHORT_NAME_LEN + 1];
+
+         sprintf (s, "VAR%03d", i);
+         v = dict_create_var_assert (default_dict, s, 0);
+       }
+    }
+  else
+    {
+      struct varname *v;
+
+      for (v = flip->new_names_head; v; v = v->next)
+        if (!make_new_var (v->name))
+          return 0;
+    }
+  
+  return 1;
+}
+     
+/* Creates a flip sink based on FLIP. */
+static struct case_sink *
+flip_sink_create (struct flip_pgm *flip) 
+{
+  size_t i;
+
+  flip->output_buf = pool_nalloc (flip->pool,
+                                  flip->var_cnt, sizeof *flip->output_buf);
+
+  flip->file = pool_tmpfile (flip->pool);
+  if (flip->file == NULL)
+    {
+      msg (SE, _("Could not create temporary file for FLIP."));
+      return NULL;
+    }
+
+  /* Write variable names as first case. */
+  for (i = 0; i < flip->var_cnt; i++) 
+    buf_copy_str_rpad (flip->output_buf[i].s, MAX_SHORT_STRING,
+                       flip->var[i]->name);
+  if (fwrite (flip->output_buf, sizeof *flip->output_buf,
+              flip->var_cnt, flip->file) != (size_t) flip->var_cnt) 
+    {
+      msg (SE, _("Error writing FLIP file: %s."), strerror (errno));
+      return NULL;
+    }
+
+  flip->case_cnt = 1;
+
+  return create_case_sink (&flip_sink_class, default_dict, flip);
+}
+
+/* Writes case C to the FLIP sink.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+flip_sink_write (struct case_sink *sink, const struct ccase *c)
+{
+  struct flip_pgm *flip = sink->aux;
+  size_t i;
+  
+  flip->case_cnt++;
+
+  if (flip->new_names != NULL)
+    {
+      struct varname *v = pool_alloc (flip->pool, sizeof *v);
+      v->next = NULL;
+      if (flip->new_names->type == NUMERIC) 
+        {
+          double f = case_num (c, flip->idx_to_fv[flip->new_names->index]);
+
+          if (f == SYSMIS)
+            strcpy (v->name, "VSYSMIS");
+          else if (f < INT_MIN)
+            strcpy (v->name, "VNEGINF");
+          else if (f > INT_MAX)
+            strcpy (v->name, "VPOSINF");
+          else 
+            {
+              char name[INT_DIGITS + 2];
+              sprintf (name, "V%d", (int) f);
+              str_copy_trunc (v->name, sizeof v->name, name);
+            }
+        }
+      else
+       {
+         int width = min (flip->new_names->width, MAX_SHORT_STRING);
+         memcpy (v->name, case_str (c, flip->idx_to_fv[flip->new_names->index]),
+                  width);
+         v->name[width] = 0;
+       }
+      
+      if (flip->new_names_head == NULL)
+       flip->new_names_head = v;
+      else
+       flip->new_names_tail->next = v;
+      flip->new_names_tail = v;
+    }
+
+  /* Write to external file. */
+  for (i = 0; i < flip->var_cnt; i++)
+    {
+      double out;
+      
+      if (flip->var[i]->type == NUMERIC)
+        out = case_num (c, flip->idx_to_fv[flip->var[i]->index]);
+      else
+        out = SYSMIS;
+      flip->output_buf[i].f = out;
+    }
+         
+  if (fwrite (flip->output_buf, sizeof *flip->output_buf,
+              flip->var_cnt, flip->file) != (size_t) flip->var_cnt) 
+    {
+      msg (SE, _("Error writing FLIP file: %s."), strerror (errno));
+      return false; 
+    }
+  return true;
+}
+
+/* Transposes the external file into a new file. */
+static bool
+flip_file (struct flip_pgm *flip)
+{
+  size_t case_bytes;
+  size_t case_capacity;
+  size_t case_idx;
+  union value *input_buf, *output_buf;
+  FILE *input_file, *output_file;
+
+  /* Allocate memory for many cases. */
+  case_bytes = flip->var_cnt * sizeof *input_buf;
+  case_capacity = get_workspace () / case_bytes;
+  if (case_capacity > flip->case_cnt * 2)
+    case_capacity = flip->case_cnt * 2;
+  if (case_capacity < 2)
+    case_capacity = 2;
+  for (;;)
+    {
+      size_t bytes = case_bytes * case_capacity;
+      if (case_capacity > 2)
+        input_buf = malloc (bytes);
+      else
+        input_buf = xmalloc (bytes);
+      if (input_buf != NULL)
+       break;
+
+      case_capacity /= 2;
+      if (case_capacity < 2)
+       case_capacity = 2;
+    }
+  pool_register (flip->pool, free, input_buf);
+
+  /* Use half the allocated memory for input_buf, half for
+     output_buf. */
+  case_capacity /= 2;
+  output_buf = input_buf + flip->var_cnt * case_capacity;
+
+  input_file = flip->file;
+  if (fseek (input_file, 0, SEEK_SET) != 0) 
+    {
+      msg (SE, _("Error rewinding FLIP file: %s."), strerror (errno));
+      return false;
+    }
+      
+  output_file = pool_tmpfile (flip->pool);
+  if (output_file == NULL) 
+    {
+      msg (SE, _("Error creating FLIP source file."));
+      return false;
+    }
+  
+  for (case_idx = 0; case_idx < flip->case_cnt; )
+    {
+      unsigned long read_cases = min (flip->case_cnt - case_idx,
+                                      case_capacity);
+      size_t i;
+
+      if (read_cases != fread (input_buf, case_bytes, read_cases, input_file)) 
+        {
+          msg (SE, _("Error reading FLIP file: %s."), strerror (errno));
+          return false;
+        }
+
+      for (i = 0; i < flip->var_cnt; i++)
+       {
+         unsigned long j;
+         
+         for (j = 0; j < read_cases; j++)
+           output_buf[j] = input_buf[i + j * flip->var_cnt];
+
+#ifndef HAVE_FSEEKO
+#define fseeko fseek
+#endif
+
+#ifndef HAVE_OFF_T
+#define off_t long int
+#endif
+
+         if (fseeko (output_file,
+                      sizeof *input_buf * (case_idx
+                                           + (off_t) i * flip->case_cnt),
+                      SEEK_SET) != 0) 
+            {
+              msg (SE, _("Error seeking FLIP source file: %s."),
+                   strerror (errno));
+              return false;
+            }
+
+         if (fwrite (output_buf, sizeof *output_buf, read_cases, output_file)
+             != read_cases) 
+            {
+              msg (SE, _("Error writing FLIP source file: %s."),
+                   strerror (errno));
+              return false; 
+            }
+       }
+
+      case_idx += read_cases;
+    }
+
+  pool_fclose (flip->pool, input_file);
+  pool_unregister (flip->pool, input_buf);
+  free (input_buf);
+  
+  if (fseek (output_file, 0, SEEK_SET) != 0) 
+    {
+      msg (SE, _("Error rewinding FLIP source file: %s."), strerror (errno));
+      return false; 
+    }
+  flip->file = output_file;
+
+  return true;
+}
+
+/* FLIP sink class. */
+static const struct case_sink_class flip_sink_class = 
+  {
+    "FLIP",
+    NULL,
+    flip_sink_write,
+    NULL,
+    NULL,
+  };
+
+/* Creates and returns a FLIP source based on PGM,
+   which should have already been used as a sink. */
+static struct case_source *
+flip_source_create (struct flip_pgm *pgm)
+{
+  return create_case_source (&flip_source_class, pgm);
+}
+
+/* Reads the FLIP stream.  Copies each case into C and calls
+   WRITE_CASE passing WC_DATA.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+flip_source_read (struct case_source *source,
+                  struct ccase *c,
+                  write_case_func *write_case, write_case_data wc_data)
+{
+  struct flip_pgm *flip = source->aux;
+  union value *input_buf;
+  size_t i;
+  bool ok = true;
+
+  input_buf = xnmalloc (flip->case_cnt, sizeof *input_buf);
+  for (i = 0; ok && i < flip->var_cnt; i++)
+    {
+      size_t j;
+      
+      if (fread (input_buf, sizeof *input_buf, flip->case_cnt,
+                 flip->file) != flip->case_cnt) 
+        {
+          if (ferror (flip->file))
+            msg (SE, _("Error reading FLIP temporary file: %s."),
+                 strerror (errno));
+          else if (feof (flip->file))
+            msg (SE, _("Unexpected end of file reading FLIP temporary file."));
+          else
+            abort ();
+          ok = false;
+          break;
+        }
+
+      for (j = 0; j < flip->case_cnt; j++)
+        case_data_rw (c, j)->f = input_buf[j].f;
+      ok = write_case (wc_data);
+    }
+  free (input_buf);
+
+  return ok;
+}
+
+/* Destroy internal data in SOURCE. */
+static void
+flip_source_destroy (struct case_source *source)
+{
+  struct flip_pgm *flip = source->aux;
+
+  destroy_flip_pgm (flip);
+}
+
+static const struct case_source_class flip_source_class = 
+  {
+    "FLIP",
+    NULL,
+    flip_source_read,
+    flip_source_destroy
+  };
diff --git a/src/language/stats/frequencies.q b/src/language/stats/frequencies.q
new file mode 100644 (file)
index 0000000..c3b3a49
--- /dev/null
@@ -0,0 +1,1643 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+/*
+  TODO:
+
+  * Remember that histograms, bar charts need mean, stddev.
+*/
+
+#include <config.h>
+#include "message.h"
+#include <math.h>
+#include <stdlib.h>
+#include <gsl/gsl_histogram.h>
+
+#include "alloc.h"
+#include "bit-vector.h"
+#include "case.h"
+#include "dictionary.h"
+#include "hash.h"
+#include "pool.h"
+#include "command.h"
+#include "lexer.h"
+#include "moments.h"
+#include "message.h"
+#include "array.h"
+#include "magic.h"
+#include "misc.h"
+#include "output.h"
+#include "manager.h"
+#include "str.h"
+#include "table.h"
+#include "value-labels.h"
+#include "variable.h"
+#include "procedure.h"
+#include "settings.h"
+#include "piechart.h"
+#include "chart.h"
+#include "plot-hist.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+/* (headers) */
+
+#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");
+     piechart(pie_)=:minimum(d:min),
+           :maximum(d:max),
+           missing:missing/!nomissing;
+     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=integer;
+     +percentiles = double list;
+     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) */
+
+/* Statistics. */
+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
+  };
+
+/* 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. */
+
+struct percentile
+{
+  double p;        /* the %ile to be calculated */
+  double value;    /* the %ile's value */
+  double x1;       /* The datum value <= the percentile */
+  double x2;       /* The datum value >= the percentile */
+  int flag;        
+  int flag2;       /* Set to 1 if this percentile value has been found */
+};
+
+
+static void add_percentile (double x) ;
+
+static struct percentile *percentiles;
+static int n_percentiles;
+
+static int implicit_50th ; 
+
+/* 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_PIE,                    /* Draw piechart */
+    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. */
+/* FIXME: These should not be mututally exclusive */
+static int chart;              /* NONE/BAR/HIST/HBAR/PIE. */
+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 size_t 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. */
+
+/* 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 hsh_table *data;    /* 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. */
+  };
+
+
+/* Per-variable frequency data. */
+struct var_freqs
+  {
+    /* Freqency table. */
+    struct freq_tab tab;       /* Frequencies table to use. */
+
+    /* Percentiles. */
+    int n_groups;              /* Number of groups. */
+    double *groups;            /* Groups. */
+
+    /* Statistics. */
+    double stat[frq_n_stats];
+  };
+
+static inline struct var_freqs *
+get_var_freqs (struct variable *v)
+{
+  assert (v != NULL);
+  assert (v->aux != NULL);
+  return v->aux;
+}
+
+static void determine_charts (void);
+
+static void calc_stats (struct variable *v, double d[frq_n_stats]);
+
+static void precalc (void *);
+static bool calc (struct ccase *, void *);
+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 hsh_hash_func hash_value_numeric, hash_value_alpha;
+static hsh_compare_func compare_value_numeric_a, compare_value_alpha_a;
+static hsh_compare_func compare_value_numeric_d, compare_value_alpha_d;
+static hsh_compare_func compare_freq_numeric_a, compare_freq_alpha_a;
+static hsh_compare_func compare_freq_numeric_d, compare_freq_alpha_d;
+
+
+static void do_piechart(const struct variable *var,
+                       const struct freq_tab *frq_tab);
+
+gsl_histogram * 
+freq_tab_to_hist(const struct freq_tab *ft, const struct variable *var);
+
+
+\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);
+  int_pool=0;
+  pool_destroy (gen_pool);
+  gen_pool=0;
+  free (v_variables);
+  v_variables=0;
+  return result;
+}
+
+static int
+internal_cmd_frequencies (void)
+{
+  int i;
+  bool ok;
+
+  n_percentiles = 0;
+  percentiles = NULL;
+
+  n_variables = 0;
+  v_variables = NULL;
+
+  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 (cmd.a_statistics[FRQ_ST_DEFAULT] || !cmd.sbc_statistics)
+    stats |= frq_default;
+  if (cmd.a_statistics[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 (cmd.a_statistics[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;
+
+  /* Work out what percentiles need to be calculated */
+  if ( cmd.sbc_percentiles ) 
+    {
+      for ( i = 0 ; i < MAXLISTS ; ++i ) 
+       {
+         int pl;
+         subc_list_double *ptl_list = &cmd.dl_percentiles[i];
+         for ( pl = 0 ; pl < subc_list_double_count(ptl_list); ++pl)
+             add_percentile(subc_list_double_at(ptl_list,pl) / 100.0 );
+       }
+    }
+  if ( cmd.sbc_ntiles ) 
+    {
+      for ( i = 0 ; i < cmd.sbc_ntiles ; ++i ) 
+       {
+         int j;
+         for (j = 0; j <= cmd.n_ntiles[i]; ++j ) 
+             add_percentile(j / (double) cmd.n_ntiles[i]);
+       }
+    }
+  
+
+  /* Do it! */
+  ok = procedure_with_splits (precalc, calc, postcalc, NULL);
+
+  free_frequencies(&cmd);
+
+  return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+}
+
+/* Figure out which charts the user requested.  */
+static void
+determine_charts (void)
+{
+  int count = (!!cmd.sbc_histogram) + (!!cmd.sbc_barchart) + 
+    (!!cmd.sbc_hbar) + (!!cmd.sbc_piechart);
+
+  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 if (cmd.sbc_piechart)
+    chart = GFT_PIE;
+  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 != FRQ_NONORMAL )
+       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;
+    }
+}
+
+/* Add data from case C to the frequency table. */
+static bool
+calc (struct ccase *c, void *aux UNUSED)
+{
+  double weight;
+  size_t i;
+  int bad_warn = 1;
+
+  weight = dict_get_case_weight (default_dict, c, &bad_warn);
+
+  for (i = 0; i < n_variables; i++)
+    {
+      struct variable *v = v_variables[i];
+      const union value *val = case_data (c, v->fv);
+      struct freq_tab *ft = &get_var_freqs (v)->tab;
+
+      switch (ft->mode)
+       {
+         case FRQM_GENERAL:
+           {
+
+             /* General mode. */
+             struct freq **fpp = (struct freq **) hsh_probe (ft->data, val);
+
+             if (*fpp != NULL)
+               (*fpp)->c += weight;
+             else
+               {
+                 struct freq *fp = *fpp = pool_alloc (gen_pool, sizeof *fp);
+                 fp->v = *val;
+                 fp->c = weight;
+               }
+           }
+         break;
+       case FRQM_INTEGER:
+         /* Integer mode. */
+         if (val->f == SYSMIS)
+           ft->sysmis += weight;
+         else if (val->f > INT_MIN+1 && val->f < INT_MAX-1)
+           {
+             int i = val->f;
+             if (i >= ft->min && i <= ft->max)
+               ft->vector[i - ft->min] += weight;
+           }
+         else
+           ft->out_of_range += weight;
+         break;
+       default:
+         assert (0);
+       }
+    }
+  return true;
+}
+
+/* Prepares each variable that is the target of FREQUENCIES by setting
+   up its hash table. */
+static void
+precalc (void *aux UNUSED)
+{
+  size_t i;
+
+  pool_destroy (gen_pool);
+  gen_pool = pool_create ();
+  
+  for (i = 0; i < n_variables; i++)
+    {
+      struct variable *v = v_variables[i];
+      struct freq_tab *ft = &get_var_freqs (v)->tab;
+
+      if (ft->mode == FRQM_GENERAL)
+       {
+          hsh_hash_func *hash;
+         hsh_compare_func *compare;
+
+         if (v->type == NUMERIC) 
+            {
+              hash = hash_value_numeric;
+              compare = compare_value_numeric_a; 
+            }
+         else 
+            {
+              hash = hash_value_alpha;
+              compare = compare_value_alpha_a;
+            }
+         ft->data = hsh_create (16, compare, hash, NULL, v);
+       }
+      else
+       {
+         int j;
+
+         for (j = (ft->max - ft->min); j >= 0; j--)
+           ft->vector[j] = 0.0;
+         ft->out_of_range = 0.0;
+         ft->sysmis = 0.0;
+       }
+    }
+}
+
+/* Finishes up with the variables after frequencies have been
+   calculated.  Displays statistics, percentiles, ... */
+static void
+postcalc (void *aux UNUSED)
+{
+  size_t i;
+
+  for (i = 0; i < n_variables; i++)
+    {
+      struct variable *v = v_variables[i];
+      struct var_freqs *vf = get_var_freqs (v);
+      struct freq_tab *ft = &vf->tab;
+      int n_categories;
+      int dumped_freq_tab = 1;
+
+      postprocess_freq_tab (v);
+
+      /* Frequencies tables. */
+      n_categories = ft->n_valid + ft->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);
+
+
+
+      if ( chart == GFT_HIST) 
+       {
+         double d[frq_n_stats];
+         struct normal_curve norm;
+         gsl_histogram *hist ;
+
+
+         norm.N = vf->tab.valid_cases;
+
+         calc_stats(v,d);
+         norm.mean = d[frq_mean];
+         norm.stddev = d[frq_stddev];
+
+         hist = freq_tab_to_hist(ft,v);
+
+         histogram_plot(hist, var_to_string(v), &norm, normal);
+
+         gsl_histogram_free(hist);
+       }
+
+
+      if ( chart == GFT_PIE) 
+       {
+         do_piechart(v_variables[i], ft);
+       }
+
+
+
+      cleanup_freq_tab (v);
+
+    }
+}
+
+/* Returns the comparison function that should be used for
+   sorting a frequency table by FRQ_SORT using VAR_TYPE
+   variables. */
+static hsh_compare_func *
+get_freq_comparator (int frq_sort, int var_type) 
+{
+  /* Note that q2c generates tags beginning with 1000. */
+  switch (frq_sort | (var_type << 16))
+    {
+    case FRQ_AVALUE | (NUMERIC << 16):  return compare_value_numeric_a;
+    case FRQ_AVALUE | (ALPHA << 16):    return compare_value_alpha_a;
+    case FRQ_DVALUE | (NUMERIC << 16):  return compare_value_numeric_d;
+    case FRQ_DVALUE | (ALPHA << 16):    return compare_value_alpha_d;
+    case FRQ_AFREQ | (NUMERIC << 16):   return compare_freq_numeric_a;
+    case FRQ_AFREQ | (ALPHA << 16):     return compare_freq_alpha_a;
+    case FRQ_DFREQ | (NUMERIC << 16):   return compare_freq_numeric_d;
+    case FRQ_DFREQ | (ALPHA << 16):     return compare_freq_alpha_d;
+    default: assert (0);
+    }
+
+  return 0;
+}
+
+/* Returns nonzero iff the value in struct freq F is non-missing
+   for variable V. */
+static int
+not_missing (const void *f_, void *v_) 
+{
+  const struct freq *f = f_;
+  struct variable *v = v_;
+
+  return !mv_is_value_missing (&v->miss, &f->v);
+}
+
+/* Summarizes the frequency table data for variable V. */
+static void
+postprocess_freq_tab (struct variable *v)
+{
+  hsh_compare_func *compare;
+  struct freq_tab *ft;
+  size_t count;
+  void *const *data;
+  struct freq *freqs, *f;
+  size_t i;
+
+  ft = &get_var_freqs (v)->tab;
+  assert (ft->mode == FRQM_GENERAL);
+  compare = get_freq_comparator (cmd.sort, v->type);
+
+  /* Extract data from hash table. */
+  count = hsh_count (ft->data);
+  data = hsh_data (ft->data);
+
+  /* Copy dereferenced data into freqs. */
+  freqs = xnmalloc (count, sizeof *freqs);
+  for (i = 0; i < count; i++) 
+    {
+      struct freq *f = data[i];
+      freqs[i] = *f; 
+    }
+
+  /* Put data into ft. */
+  ft->valid = freqs;
+  ft->n_valid = partition (freqs, count, sizeof *freqs, not_missing, v);
+  ft->missing = freqs + ft->n_valid;
+  ft->n_missing = count - ft->n_valid;
+
+  /* Sort data. */
+  sort (ft->valid, ft->n_valid, sizeof *ft->valid, compare, v);
+  sort (ft->missing, ft->n_missing, sizeof *ft->missing, compare, v);
+
+  /* Summary statistics. */
+  ft->valid_cases = 0.0;
+  for(i = 0 ;  i < ft->n_valid ; ++i ) 
+    {
+      f = &ft->valid[i];
+      ft->valid_cases += f->c;
+
+    }
+
+  ft->total_cases = ft->valid_cases ; 
+  for(i = 0 ;  i < ft->n_missing ; ++i ) 
+    {
+      f = &ft->missing[i];
+      ft->total_cases += f->c;
+    }
+
+}
+
+/* Frees the frequency table for variable V. */
+static void
+cleanup_freq_tab (struct variable *v)
+{
+  struct freq_tab *ft = &get_var_freqs (v)->tab;
+  assert (ft->mode == FRQM_GENERAL);
+  free (ft->valid);
+  hsh_destroy (ft->data);
+}
+
+/* Parses the VARIABLES subcommand, adding to
+   {n_variables,v_variables}. */
+static int
+frq_custom_variables (struct cmd_frequencies *cmd UNUSED)
+{
+  int mode;
+  int min = 0, max = 0;
+
+  size_t old_n_variables = n_variables;
+  size_t i;
+
+  lex_match ('=');
+  if (token != T_ALL && (token != T_ID
+                         || dict_lookup_var (default_dict, tokid) == NULL))
+    return 2;
+
+  if (!parse_variables (default_dict, &v_variables, &n_variables,
+                       PV_APPEND | PV_NO_SCRATCH))
+    return 0;
+
+  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];
+      struct var_freqs *vf;
+
+      if (v->aux != NULL)
+       {
+         msg (SE, _("Variable %s specified multiple times on VARIABLES "
+                    "subcommand."), v->name);
+         return 0;
+       }
+      if (mode == FRQM_INTEGER && v->type != NUMERIC)
+        {
+          msg (SE, _("Integer mode specified, but %s is not a numeric "
+                     "variable."), v->name);
+          return 0;
+        }
+
+      vf = var_attach_aux (v, xmalloc (sizeof *vf), var_dtor_free);
+      vf->tab.mode = mode;
+      vf->tab.valid = vf->tab.missing = NULL;
+      if (mode == FRQM_INTEGER)
+       {
+         vf->tab.min = min;
+         vf->tab.max = max;
+         vf->tab.vector = pool_nalloc (int_pool,
+                                        max - min + 1, sizeof *vf->tab.vector);
+       }
+      else
+       vf->tab.vector = NULL;
+      vf->n_groups = 0;
+      vf->groups = NULL;
+    }
+  return 1;
+}
+
+/* Parses the GROUPED subcommand, setting the n_grouped, grouped
+   fields of specified variables. */
+static int
+frq_custom_grouped (struct cmd_frequencies *cmd UNUSED)
+{
+  lex_match ('=');
+  if ((token == T_ID && dict_lookup_var (default_dict, tokid) != NULL)
+      || token == T_ID)
+    for (;;)
+      {
+       size_t i;
+
+       /* Max, current size of list; list itself. */
+       int nl, ml;
+       double *dl;
+
+       /* Variable list. */
+       size_t n;
+       struct variable **v;
+
+       if (!parse_variables (default_dict, &v, &n,
+                              PV_NO_DUPLICATE | PV_NUMERIC))
+         return 0;
+       if (lex_match ('('))
+         {
+           nl = ml = 0;
+           dl = NULL;
+           while (lex_integer ())
+             {
+               if (nl >= ml)
+                 {
+                   ml += 16;
+                   dl = pool_nrealloc (int_pool, dl, ml, sizeof *dl);
+                 }
+               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;
+            dl = NULL;
+          }
+
+       for (i = 0; i < n; i++)
+          if (v[i]->aux == NULL)
+            msg (SE, _("Variables %s specified on GROUPED but not on "
+                       "VARIABLES."), v[i]->name);
+          else 
+            {
+              struct var_freqs *vf = get_var_freqs (v[i]);
+                
+              if (vf->groups != NULL)
+                msg (SE, _("Variables %s specified multiple times on GROUPED "
+                           "subcommand."), v[i]->name);
+              else
+                {
+                  vf->n_groups = nl;
+                  vf->groups = dl;
+                }
+            }
+       free (v);
+       if (!lex_match ('/'))
+         break;
+       if ((token != T_ID || dict_lookup_var (default_dict, tokid) != NULL)
+            && 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++)
+    {
+      /* Do nothing if it's already in the list */
+      if ( fabs(x - percentiles[i].p) < DBL_EPSILON ) 
+       return;
+
+      if (x < percentiles[i].p)
+       break;
+    }
+
+  if (i >= n_percentiles || tokval != percentiles[i].p)
+    {
+      percentiles = pool_nrealloc (int_pool, percentiles,
+                                   n_percentiles + 1, sizeof *percentiles);
+
+      if (i < n_percentiles)
+          memmove (&percentiles[i + 1], &percentiles[i],
+                   (n_percentiles - i) * sizeof (struct percentile) );
+
+      percentiles[i].p = x;
+      n_percentiles++;
+    }
+}
+
+/* Comparison functions. */
+
+/* Hash of numeric values. */
+static unsigned
+hash_value_numeric (const void *value_, void *foo UNUSED)
+{
+  const struct freq *value = value_;
+  return hsh_hash_double (value->v.f);
+}
+
+/* Hash of string values. */
+static unsigned
+hash_value_alpha (const void *value_, void *v_)
+{
+  const struct freq *value = value_;
+  struct variable *v = v_;
+
+  return hsh_hash_bytes (value->v.s, v->width);
+}
+
+/* Ascending numeric compare of values. */
+static int
+compare_value_numeric_a (const void *a_, const void *b_, void *foo UNUSED)
+{
+  const struct freq *a = a_;
+  const struct freq *b = b_;
+
+  if (a->v.f > b->v.f)
+    return 1;
+  else if (a->v.f < b->v.f)
+    return -1;
+  else
+    return 0;
+}
+
+/* Ascending string compare of values. */
+static int
+compare_value_alpha_a (const void *a_, const void *b_, void *v_)
+{
+  const struct freq *a = a_;
+  const struct freq *b = b_;
+  const struct variable *v = v_;
+
+  return memcmp (a->v.s, b->v.s, v->width);
+}
+
+/* Descending numeric compare of values. */
+static int
+compare_value_numeric_d (const void *a, const void *b, void *foo UNUSED)
+{
+  return -compare_value_numeric_a (a, b, foo);
+}
+
+/* Descending string compare of values. */
+static int
+compare_value_alpha_d (const void *a, const void *b, void *v)
+{
+  return -compare_value_alpha_a (a, b, v);
+}
+
+/* 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)
+{
+  const struct freq *a = a_;
+  const struct freq *b = b_;
+
+  if (a->c > b->c)
+    return 1;
+  else if (a->c < b->c)
+    return -1;
+
+  if (a->v.f > b->v.f)
+    return 1;
+  else if (a->v.f < b->v.f)
+    return -1;
+  else
+    return 0;
+}
+
+/* Ascending numeric compare of frequency;
+   secondary key on ascending string value. */
+static int
+compare_freq_alpha_a (const void *a_, const void *b_, void *v_)
+{
+  const struct freq *a = a_;
+  const struct freq *b = b_;
+  const struct variable *v = v_;
+
+  if (a->c > b->c)
+    return 1;
+  else if (a->c < b->c)
+    return -1;
+  else
+    return memcmp (a->v.s, b->v.s, v->width);
+}
+
+/* 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)
+{
+  const struct freq *a = a_;
+  const struct freq *b = b_;
+
+  if (a->c > b->c)
+    return -1;
+  else if (a->c < b->c)
+    return 1;
+
+  if (a->v.f > b->v.f)
+    return 1;
+  else if (a->v.f < b->v.f)
+    return -1;
+  else
+    return 0;
+}
+
+/* Descending numeric compare of frequency;
+   secondary key on ascending string value. */
+static int
+compare_freq_alpha_d (const void *a_, const void *b_, void *v_)
+{
+  const struct freq *a = a_;
+  const struct freq *b = b_;
+  const struct variable *v = v_;
+
+  if (a->c > b->c)
+    return -1;
+  else if (a->c < b->c)
+    return 1;
+  else
+    return memcmp (a->v.s, b->v.s, v->width);
+}
+\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_tab *ft;
+  struct freq *f;
+  struct tab_table *t;
+  int r;
+  double cum_total = 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;
+
+  ft = &get_var_freqs (v)->tab;
+  n_categories = ft->n_valid + ft->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 = ft->valid; f < ft->missing; f++)
+    {
+      double percent, valid_percent;
+
+      cum_freq += f->c;
+
+      percent = f->c / ft->total_cases * 100.0;
+      valid_percent = f->c / ft->valid_cases * 100.0;
+      cum_total += valid_percent;
+
+      if (lab)
+       {
+         const char *label = val_labs_find (v->val_labs, f->v);
+         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_total, 5, 1);
+      r++;
+    }
+  for (; f < &ft->valid[n_categories]; f++)
+    {
+      cum_freq += f->c;
+
+      if (lab)
+       {
+         const char *label = val_labs_find (v->val_labs, f->v);
+         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 / ft->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_tab *ft;
+  struct freq *f;
+  struct tab_table *t;
+  int r;
+  double cum_total = 0.0;
+
+  ft = &get_var_freqs (v)->tab;
+  n_categories = ft->n_valid + ft->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 = ft->valid; f < ft->missing; f++)
+    {
+      double percent;
+
+      percent = f->c / ft->total_cases * 100.0;
+      cum_total += f->c / ft->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_total, 3, 0);
+      r++;
+    }
+  for (; f < &ft->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 / ft->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])
+{
+  struct freq_tab *ft = &get_var_freqs (v)->tab;
+  double W = ft->valid_cases;
+  struct moments *m;
+  struct freq *f=0; 
+  int most_often;
+  double X_mode;
+
+  double rank;
+  int i = 0;
+  int idx;
+  double *median_value;
+
+  /* Calculate percentiles. */
+
+  /* If the 50th percentile was not explicitly requested then we must 
+     calculate it anyway --- it's the median */
+  median_value = 0 ;
+  for (i = 0; i < n_percentiles; i++) 
+    {
+      if (percentiles[i].p == 0.5)
+       {
+         median_value = &percentiles[i].value;
+         break;
+       }
+    }
+
+  if ( 0 == median_value )  
+    {
+      add_percentile (0.5);
+      implicit_50th = 1;
+    }
+
+  for (i = 0; i < n_percentiles; i++) 
+    {
+      percentiles[i].flag = 0;
+      percentiles[i].flag2 = 0;
+    }
+
+  rank = 0;
+  for (idx = 0; idx < ft->n_valid; ++idx)
+    {
+      static double prev_value = SYSMIS;
+      f = &ft->valid[idx]; 
+      rank += f->c ;
+      for (i = 0; i < n_percentiles; i++) 
+        {
+         double tp;
+         if ( percentiles[i].flag2  ) continue ; 
+
+         if ( get_algorithm() != COMPATIBLE ) 
+           tp = 
+             (ft->valid_cases - 1) *  percentiles[i].p;
+         else
+           tp = 
+             (ft->valid_cases + 1) *  percentiles[i].p - 1;
+
+         if ( percentiles[i].flag ) 
+           {
+             percentiles[i].x2 = f->v.f;
+             percentiles[i].x1 = prev_value;
+             percentiles[i].flag2 = 1;
+             continue;
+           }
+
+          if (rank >  tp ) 
+         {
+           if ( f->c > 1 && rank - (f->c - 1) > tp ) 
+             {
+               percentiles[i].x2 = percentiles[i].x1 = f->v.f;
+               percentiles[i].flag2 = 1;
+             }
+           else
+             {
+               percentiles[i].flag=1;
+             }
+
+           continue;
+         }
+        }
+      prev_value = f->v.f;
+    }
+
+  for (i = 0; i < n_percentiles; i++) 
+    {
+      /* Catches the case when p == 100% */
+      if ( ! percentiles[i].flag2 ) 
+       percentiles[i].x1 = percentiles[i].x2 = f->v.f;
+
+      /*
+      printf("percentile %d (p==%.2f); X1 = %g; X2 = %g\n",
+            i,percentiles[i].p,percentiles[i].x1,percentiles[i].x2);
+      */
+    }
+
+  for (i = 0; i < n_percentiles; i++) 
+    {
+      struct freq_tab *ft = &get_var_freqs (v)->tab;
+      double s;
+
+      double dummy;
+      if ( get_algorithm() != COMPATIBLE ) 
+       {
+         s = modf((ft->valid_cases - 1) * percentiles[i].p , &dummy);
+       }
+      else
+       {
+         s = modf((ft->valid_cases + 1) * percentiles[i].p -1, &dummy);
+       }
+
+      percentiles[i].value = percentiles[i].x1 + 
+       ( percentiles[i].x2 - percentiles[i].x1) * s ; 
+
+      if ( percentiles[i].p == 0.50) 
+       median_value = &percentiles[i].value; 
+    }
+
+
+  /* Calculate the mode. */
+  most_often = -1;
+  X_mode = SYSMIS;
+  for (f = ft->valid; f < ft->missing; f++)
+    {
+      if (most_often < f->c) 
+        {
+          most_often = f->c;
+          X_mode = f->v.f;
+        }
+      else if (most_often == f->c) 
+        {
+          /* A duplicate mode is undefined.
+             FIXME: keep track of *all* the modes. */
+          X_mode = SYSMIS;
+        }
+    }
+
+  /* Calculate moments. */
+  m = moments_create (MOMENT_KURTOSIS);
+  for (f = ft->valid; f < ft->missing; f++)
+    moments_pass_one (m, f->v.f, f->c);
+  for (f = ft->valid; f < ft->missing; f++)
+    moments_pass_two (m, f->v.f, f->c);
+  moments_calculate (m, NULL, &d[frq_mean], &d[frq_variance],
+                     &d[frq_skew], &d[frq_kurt]);
+  moments_destroy (m);
+                     
+  /* Formulas below are taken from _SPSS Statistical Algorithms_. */
+  d[frq_min] = ft->valid[0].v.f;
+  d[frq_max] = ft->valid[ft->n_valid - 1].v.f;
+  d[frq_mode] = X_mode;
+  d[frq_range] = d[frq_max] - d[frq_min];
+  d[frq_median] = *median_value;
+  d[frq_sum] = d[frq_mean] * W;
+  d[frq_stddev] = sqrt (d[frq_variance]);
+  d[frq_semean] = d[frq_stddev] / sqrt (W);
+  d[frq_seskew] = calc_seskew (W);
+  d[frq_sekurt] = calc_sekurt (W);
+}
+
+/* Displays a table of all the statistics requested for variable V. */
+static void
+dump_statistics (struct variable *v, int show_varname)
+{
+  struct freq_tab *ft;
+  double stat_value[frq_n_stats];
+  struct tab_table *t;
+  int i, r;
+
+  int n_explicit_percentiles = n_percentiles;
+
+  if ( implicit_50th && n_percentiles > 0 ) 
+    --n_percentiles;
+
+  if (v->type == ALPHA)
+    return;
+  ft = &get_var_freqs (v)->tab;
+  if (ft->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 (3, n_stats + n_explicit_percentiles + 2, 0);
+  tab_dim (t, tab_natural_dimensions);
+
+  tab_box (t, TAL_1, TAL_1, -1, -1 , 0 , 0 , 2, tab_nr(t) - 1) ;
+
+
+  tab_vline (t, TAL_1 , 2, 0, tab_nr(t) - 1);
+  tab_vline (t, TAL_1 | TAL_SPACING , 1, 0, tab_nr(t) - 1 ) ;
+  
+  r=2; /* N missing and N valid are always dumped */
+
+  for (i = 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, 2, r, TAB_NONE, stat_value[i], 11, 3);
+       r++;
+      }
+
+  tab_text (t, 0, 0, TAB_LEFT | TAT_TITLE, _("N"));
+  tab_text (t, 1, 0, TAB_LEFT | TAT_TITLE, _("Valid"));
+  tab_text (t, 1, 1, TAB_LEFT | TAT_TITLE, _("Missing"));
+
+  tab_float(t, 2, 0, TAB_NONE, ft->valid_cases, 11, 0);
+  tab_float(t, 2, 1, TAB_NONE, ft->total_cases - ft->valid_cases, 11, 0);
+
+
+  for (i = 0; i < n_explicit_percentiles; i++, r++) 
+    {
+      if ( i == 0 ) 
+       { 
+         tab_text (t, 0, r, TAB_LEFT | TAT_TITLE, _("Percentiles"));
+       }
+
+      tab_float (t, 1, r, TAB_LEFT, percentiles[i].p * 100, 3, 0 );
+      tab_float (t, 2, r, TAB_NONE, percentiles[i].value, 11, 3);
+
+    }
+
+  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);
+}
+
+
+/* Create a gsl_histogram from a freq_tab */
+gsl_histogram *
+freq_tab_to_hist(const struct freq_tab *ft, const struct variable *var)
+{
+  int i;
+  double x_min = DBL_MAX;
+  double x_max = -DBL_MAX;
+
+  gsl_histogram *hist;
+  const double bins = 11;
+
+  struct hsh_iterator hi;
+  struct hsh_table *fh = ft->data;
+  struct freq *frq;
+
+  /* Find out the extremes of the x value */
+  for ( frq = hsh_first(fh, &hi); frq != 0; frq = hsh_next(fh, &hi) ) 
+    {
+      if ( mv_is_value_missing(&var->miss, &frq->v))
+       continue;
+
+      if ( frq->v.f < x_min ) x_min = frq->v.f ;
+      if ( frq->v.f > x_max ) x_max = frq->v.f ;
+    }
+
+  hist = histogram_create(bins, x_min, x_max);
+
+  for( i = 0 ; i < ft->n_valid ; ++i ) 
+    {
+      frq = &ft->valid[i];
+      gsl_histogram_accumulate(hist, frq->v.f, frq->c);
+    }
+
+  return hist;
+}
+
+
+static struct slice *
+freq_tab_to_slice_array(const struct freq_tab *frq_tab, 
+                       const struct variable *var,
+                       int *n_slices);
+
+
+/* Allocate an array of slices and fill them from the data in frq_tab
+   n_slices will contain the number of slices allocated.
+   The caller is responsible for freeing slices
+*/
+static struct slice *
+freq_tab_to_slice_array(const struct freq_tab *frq_tab, 
+                       const struct variable *var,
+                       int *n_slices)
+{
+  int i;
+  struct slice *slices;
+
+  *n_slices = frq_tab->n_valid;
+  
+  slices = xnmalloc (*n_slices, sizeof *slices);
+
+  for (i = 0 ; i < *n_slices ; ++i ) 
+    {
+      const struct freq *frq = &frq_tab->valid[i];
+
+      slices[i].label = value_to_string(&frq->v, var);
+
+      slices[i].magnetude = frq->c;
+    }
+
+  return slices;
+}
+
+
+
+
+static void
+do_piechart(const struct variable *var, const struct freq_tab *frq_tab)
+{
+  struct slice *slices;
+  int n_slices;
+
+  slices = freq_tab_to_slice_array(frq_tab, var, &n_slices);
+
+  piechart_plot(var_to_string(var), slices, n_slices);
+
+  free(slices);
+}
+
+
+/* 
+   Local Variables:
+   mode: c
+   End:
+*/
diff --git a/src/language/stats/means.q b/src/language/stats/means.q
new file mode 100644 (file)
index 0000000..017b53f
--- /dev/null
@@ -0,0 +1,176 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "dictionary.h"
+#include "message.h"
+#include "alloc.h"
+#include "command.h"
+#include "hash.h"
+#include "lexer.h"
+#include "message.h"
+#include "magic.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* (headers) */
+
+#include "debug-print.h"
+
+/* (specification)
+   means (mns_):
+     *tables=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) */
+
+/* TABLES: Variable lists for each dimension. */
+int n_dim;             /* Number of dimensions. */
+size_t *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;
+
+  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;
+    }
+
+  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;
+}
+
+/* Parses the TABLES subcommand. */
+static int
+mns_custom_tables (struct cmd_means *cmd)
+{
+  struct var_set *var_set;
+  
+  if (!lex_match_id ("TABLES")
+      && (token != T_ID || dict_lookup_var (default_dict, tokid) == NULL)
+      && token != T_ALL)
+    return 2;
+  lex_match ('=');
+
+  if (cmd->sbc_tables)
+    {
+      msg (SE, _("TABLES subcommand may not appear more "
+                "than once."));
+      return 0;
+    }
+
+  var_set = var_set_create_from_dict (default_dict);
+  assert (var_set != NULL);
+
+  do
+    {
+      size_t nvl;
+      struct variable **vl;
+
+      if (!parse_var_set_vars (var_set, &vl, &nvl,
+                               PV_NO_DUPLICATE | PV_NO_SCRATCH)) 
+        goto lossage;
+      
+      n_dim++;
+      nv_dim = xnrealloc (nv_dim, n_dim, sizeof *nv_dim);
+      v_dim = xnrealloc (v_dim, n_dim, sizeof *v_dim);
+
+      nv_dim[n_dim - 1] = nvl;
+      v_dim[n_dim - 1] = vl;
+    }
+  while (lex_match (T_BY));
+
+  var_set_destroy (var_set);
+  return 1;
+
+ lossage:
+  var_set_destroy (var_set);
+  return 0;
+}
+
+/* 
+   Local Variables:
+   mode: c
+   End:
+*/
diff --git a/src/language/stats/oneway.q b/src/language/stats/oneway.q
new file mode 100644 (file)
index 0000000..9cdb7db
--- /dev/null
@@ -0,0 +1,1059 @@
+/* PSPP - One way ANOVA. -*-c-*-
+
+Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+Author: John Darrington 2004
+
+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., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA. */
+
+#include <config.h>
+#include <gsl/gsl_cdf.h>
+#include "message.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "alloc.h"
+#include "str.h"
+#include "case.h"
+#include "dictionary.h"
+#include "command.h"
+#include "lexer.h"
+#include "message.h"
+#include "magic.h"
+#include "misc.h"
+#include "table.h"
+#include "manager.h"
+#include "value-labels.h"
+#include "variable.h"
+#include "procedure.h"
+#include "hash.h"
+#include "casefile.h"
+#include "group-proc.h"
+#include "group.h"
+#include "levene.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* (headers) */
+
+/* (specification)
+   "ONEWAY" (oneway_):
+   *^variables=custom;
+   +missing=miss:!analysis/listwise,
+   incl:include/!exclude;
+   contrast= double list;
+   statistics[st_]=descriptives,homogeneity.
+*/
+/* (declarations) */
+/* (functions) */
+
+
+
+static int bad_weight_warn = 1;
+
+
+static struct cmd_oneway cmd;
+
+/* The independent variable */
+static struct variable *indep_var;
+
+/* Number of dependent variables */
+static size_t n_vars;
+
+/* The dependent variables */
+static struct variable **vars;
+
+
+/* A  hash table containing all the distinct values of the independent
+   variables */
+static struct hsh_table *global_group_hash ;
+
+/* The number of distinct values of the independent variable, when all 
+   missing values are disregarded */
+static int ostensible_number_of_groups=-1;
+
+
+/* Function to use for testing for missing values */
+static is_missing_func *value_is_missing;
+
+
+static bool run_oneway(const struct casefile *cf, void *_mode);
+
+
+/* Routines to show the output tables */
+static void show_anova_table(void);
+static void show_descriptives(void);
+static void show_homogeneity(void);
+
+static void show_contrast_coeffs(short *);
+static void show_contrast_tests(short *);
+
+
+enum stat_table_t {STAT_DESC = 1, STAT_HOMO = 2};
+
+static enum stat_table_t stat_tables ;
+
+void output_oneway(void);
+
+
+int
+cmd_oneway(void)
+{
+  int i;
+  bool ok;
+
+  if ( !parse_oneway(&cmd) )
+    return CMD_FAILURE;
+
+  /* If /MISSING=INCLUDE is set, then user missing values are ignored */
+  if (cmd.incl == ONEWAY_INCLUDE ) 
+    value_is_missing = mv_is_value_system_missing;
+  else
+    value_is_missing = mv_is_value_missing;
+
+  /* What statistics were requested */
+  if ( cmd.sbc_statistics ) 
+    {
+
+      for (i = 0 ; i < ONEWAY_ST_count ; ++i ) 
+       {
+         if  ( ! cmd.a_statistics[i]  ) continue;
+
+         switch (i) {
+         case ONEWAY_ST_DESCRIPTIVES:
+           stat_tables |= STAT_DESC;
+           break;
+         case ONEWAY_ST_HOMOGENEITY:
+           stat_tables |= STAT_HOMO;
+           break;
+         }
+       }
+    }
+
+  ok = multipass_procedure_with_splits (run_oneway, &cmd);
+
+  free (vars);
+  free_oneway (&cmd);
+
+  return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+}
+
+
+void
+output_oneway(void)
+{
+  size_t i;
+  short *bad_contrast ; 
+
+  bad_contrast = xnmalloc (cmd.sbc_contrast, sizeof *bad_contrast);
+
+  /* Check the sanity of the given contrast values */
+  for (i = 0 ; i < cmd.sbc_contrast ; ++i ) 
+    {
+      int j;
+      double sum = 0;
+
+      bad_contrast[i] = 0;
+      if ( subc_list_double_count(&cmd.dl_contrast[i]) != 
+          ostensible_number_of_groups )
+       {
+         msg(SW, 
+             _("Number of contrast coefficients must equal the number of groups"));
+         bad_contrast[i] = 1;
+         continue;
+       }
+
+      for (j=0; j < ostensible_number_of_groups ; ++j )
+       sum += subc_list_double_at(&cmd.dl_contrast[i],j);
+
+      if ( sum != 0.0 ) 
+       msg(SW,_("Coefficients for contrast %d do not total zero"),i + 1);
+    }
+
+  if ( stat_tables & STAT_DESC ) 
+    show_descriptives();
+
+  if ( stat_tables & STAT_HOMO )
+    show_homogeneity();
+
+  show_anova_table();
+     
+  if (cmd.sbc_contrast )
+    {
+      show_contrast_coeffs(bad_contrast);
+      show_contrast_tests(bad_contrast);
+    }
+
+
+  free(bad_contrast);
+
+  /* Clean up */
+  for (i = 0 ; i < n_vars ; ++i ) 
+    {
+      struct hsh_table *group_hash = group_proc_get (vars[i])->group_hash;
+
+      hsh_destroy(group_hash);
+    }
+
+  hsh_destroy(global_group_hash);
+
+}
+
+
+
+
+/* Parser for the variables sub command */
+static int
+oneway_custom_variables(struct cmd_oneway *cmd UNUSED)
+{
+
+  lex_match('=');
+
+  if ((token != T_ID || dict_lookup_var (default_dict, tokid) == NULL)
+      && token != T_ALL)
+    return 2;
+  
+
+  if (!parse_variables (default_dict, &vars, &n_vars,
+                       PV_DUPLICATE 
+                       | PV_NUMERIC | PV_NO_SCRATCH) )
+    {
+      free (vars);
+      return 0;
+    }
+
+  assert(n_vars);
+
+  if ( ! lex_match(T_BY))
+    return 2;
+
+
+  indep_var = parse_variable();
+
+  if ( !indep_var ) 
+    {
+      msg(SE,_("`%s' is not a variable name"),tokid);
+      return 0;
+    }
+
+
+  return 1;
+}
+
+
+/* Show the ANOVA table */
+static void  
+show_anova_table(void)
+{
+  size_t i;
+  int n_cols =7;
+  size_t n_rows = n_vars * 3 + 1;
+
+  struct tab_table *t;
+
+
+  t = tab_create (n_cols,n_rows,0);
+  tab_headers (t, 2, 0, 1, 0);
+  tab_dim (t, tab_natural_dimensions);
+
+
+  tab_box (t, 
+          TAL_2, TAL_2,
+          -1, TAL_1,
+          0, 0,
+          n_cols - 1, n_rows - 1);
+
+  tab_hline (t, TAL_2, 0, n_cols - 1, 1 );
+  tab_vline (t, TAL_2, 2, 0, n_rows - 1);
+  tab_vline (t, TAL_0, 1, 0, 0);
+  
+  tab_text (t, 2, 0, TAB_CENTER | TAT_TITLE, _("Sum of Squares"));
+  tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("df"));
+  tab_text (t, 4, 0, TAB_CENTER | TAT_TITLE, _("Mean Square"));
+  tab_text (t, 5, 0, TAB_CENTER | TAT_TITLE, _("F"));
+  tab_text (t, 6, 0, TAB_CENTER | TAT_TITLE, _("Significance"));
+
+
+  for ( i=0 ; i < n_vars ; ++i ) 
+    {
+      struct group_statistics *totals = &group_proc_get (vars[i])->ugs;
+      struct hsh_table *group_hash = group_proc_get (vars[i])->group_hash;
+      struct hsh_iterator g;
+      struct group_statistics *gs;
+      double ssa=0;
+      const char *s = var_to_string(vars[i]);
+
+      for (gs =  hsh_first (group_hash,&g); 
+          gs != 0; 
+          gs = hsh_next(group_hash,&g))
+       {
+         ssa += (gs->sum * gs->sum)/gs->n;
+       }
+      
+      ssa -= ( totals->sum * totals->sum ) / totals->n ;
+
+      tab_text (t, 0, i * 3 + 1, TAB_LEFT | TAT_TITLE, s);
+      tab_text (t, 1, i * 3 + 1, TAB_LEFT | TAT_TITLE, _("Between Groups"));
+      tab_text (t, 1, i * 3 + 2, TAB_LEFT | TAT_TITLE, _("Within Groups"));
+      tab_text (t, 1, i * 3 + 3, TAB_LEFT | TAT_TITLE, _("Total"));
+      
+      if (i > 0)
+       tab_hline(t, TAL_1, 0, n_cols - 1 , i * 3 + 1);
+
+      {
+        struct group_proc *gp = group_proc_get (vars[i]);
+       const double sst = totals->ssq - ( totals->sum * totals->sum) / totals->n ;
+       const double df1 = gp->n_groups - 1;
+       const double df2 = totals->n - gp->n_groups ;
+       const double msa = ssa / df1;
+       
+       gp->mse  = (sst - ssa) / df2;
+       
+       
+       /* Sums of Squares */
+       tab_float (t, 2, i * 3 + 1, 0, ssa, 10, 2);
+       tab_float (t, 2, i * 3 + 3, 0, sst, 10, 2);
+       tab_float (t, 2, i * 3 + 2, 0, sst - ssa, 10, 2);
+
+
+       /* Degrees of freedom */
+       tab_float (t, 3, i * 3 + 1, 0, df1, 4, 0);
+       tab_float (t, 3, i * 3 + 2, 0, df2, 4, 0);
+       tab_float (t, 3, i * 3 + 3, 0, totals->n - 1, 4, 0);
+
+       /* Mean Squares */
+       tab_float (t, 4, i * 3 + 1, TAB_RIGHT, msa, 8, 3);
+       tab_float (t, 4, i * 3 + 2, TAB_RIGHT, gp->mse, 8, 3);
+       
+
+       { 
+         const double F = msa/gp->mse ;
+
+         /* The F value */
+         tab_float (t, 5, i * 3 + 1, 0,  F, 8, 3);
+       
+         /* The significance */
+         tab_float (t, 6, i * 3 + 1, 0, gsl_cdf_fdist_Q(F,df1,df2), 8, 3);
+       }
+
+      }
+
+    }
+
+
+  tab_title (t, 0, _("ANOVA"));
+  tab_submit (t);
+
+
+}
+
+/* Show the descriptives table */
+static void  
+show_descriptives(void)
+{
+  size_t v;
+  int n_cols =10;
+  struct tab_table *t;
+  int row;
+
+  const double confidence=0.95;
+  const double q = (1.0 - confidence) / 2.0;
+
+  
+  int n_rows = 2 ; 
+
+  for ( v = 0 ; v < n_vars ; ++v ) 
+    n_rows += group_proc_get (vars[v])->n_groups + 1;
+
+  t = tab_create (n_cols,n_rows,0);
+  tab_headers (t, 2, 0, 2, 0);
+  tab_dim (t, tab_natural_dimensions);
+
+
+  /* Put a frame around the entire box, and vertical lines inside */
+  tab_box (t, 
+          TAL_2, TAL_2,
+          -1, TAL_1,
+          0, 0,
+          n_cols - 1, n_rows - 1);
+
+  /* Underline headers */
+  tab_hline (t, TAL_2, 0, n_cols - 1, 2 );
+  tab_vline (t, TAL_2, 2, 0, n_rows - 1);
+  
+  tab_text (t, 2, 1, TAB_CENTER | TAT_TITLE, _("N"));
+  tab_text (t, 3, 1, TAB_CENTER | TAT_TITLE, _("Mean"));
+  tab_text (t, 4, 1, TAB_CENTER | TAT_TITLE, _("Std. Deviation"));
+  tab_text (t, 5, 1, TAB_CENTER | TAT_TITLE, _("Std. Error"));
+
+
+  tab_vline(t, TAL_0, 7, 0, 0);
+  tab_hline(t, TAL_1, 6, 7, 1);
+  tab_joint_text (t, 6, 0, 7, 0, TAB_CENTER | TAT_TITLE | TAT_PRINTF, _("%g%% Confidence Interval for Mean"),confidence*100.0);
+
+  tab_text (t, 6, 1, TAB_CENTER | TAT_TITLE, _("Lower Bound"));
+  tab_text (t, 7, 1, TAB_CENTER | TAT_TITLE, _("Upper Bound"));
+
+  tab_text (t, 8, 1, TAB_CENTER | TAT_TITLE, _("Minimum"));
+  tab_text (t, 9, 1, TAB_CENTER | TAT_TITLE, _("Maximum"));
+
+
+  tab_title (t, 0, _("Descriptives"));
+
+
+  row = 2;
+  for ( v=0 ; v < n_vars ; ++v ) 
+    {
+      double T;
+      double std_error;
+      
+      struct group_proc *gp = group_proc_get (vars[v]);
+
+      struct group_statistics *gs;
+      struct group_statistics *totals = &gp->ugs; 
+
+      const char *s = var_to_string(vars[v]);
+
+      struct group_statistics *const *gs_array = hsh_sort(gp->group_hash);
+      int count = 0;
+
+      tab_text (t, 0, row, TAB_LEFT | TAT_TITLE, s);
+      if ( v > 0) 
+       tab_hline(t, TAL_1, 0, n_cols - 1 , row);
+
+      for (count = 0 ; count < hsh_count(gp->group_hash) ; ++count)
+       {
+         gs = gs_array[count];
+
+         tab_text (t, 1, row + count, 
+                   TAB_LEFT | TAT_TITLE ,value_to_string(&gs->id,indep_var));
+
+         /* Now fill in the numbers ... */
+
+         tab_float (t, 2, row + count, 0, gs->n, 8,0);
+
+         tab_float (t, 3, row + count, 0, gs->mean,8,2);
+         
+         tab_float (t, 4, row + count, 0, gs->std_dev,8,2);
+
+         std_error = gs->std_dev/sqrt(gs->n) ;
+         tab_float (t, 5, row + count, 0, 
+                    std_error, 8,2);
+
+         /* Now the confidence interval */
+      
+         T = gsl_cdf_tdist_Qinv(q,gs->n - 1);
+
+         tab_float(t, 6, row + count, 0,
+                   gs->mean - T * std_error, 8, 2); 
+
+         tab_float(t, 7, row + count, 0,
+                   gs->mean + T * std_error, 8, 2); 
+
+         /* Min and Max */
+
+         tab_float(t, 8, row + count, 0,  gs->minimum, 8, 2); 
+         tab_float(t, 9, row + count, 0,  gs->maximum, 8, 2); 
+
+       }
+
+      tab_text (t, 1, row + count, 
+               TAB_LEFT | TAT_TITLE ,_("Total"));
+
+      tab_float (t, 2, row + count, 0, totals->n, 8,0);
+
+      tab_float (t, 3, row + count, 0, totals->mean, 8,2);
+
+      tab_float (t, 4, row + count, 0, totals->std_dev,8,2);
+
+      std_error = totals->std_dev/sqrt(totals->n) ;
+
+      tab_float (t, 5, row + count, 0, std_error, 8,2);
+
+      /* Now the confidence interval */
+      
+      T = gsl_cdf_tdist_Qinv(q,totals->n - 1);
+
+      tab_float(t, 6, row + count, 0,
+               totals->mean - T * std_error, 8, 2); 
+
+      tab_float(t, 7, row + count, 0,
+               totals->mean + T * std_error, 8, 2); 
+
+      /* Min and Max */
+
+      tab_float(t, 8, row + count, 0,  totals->minimum, 8, 2); 
+      tab_float(t, 9, row + count, 0,  totals->maximum, 8, 2); 
+
+      row += gp->n_groups + 1;
+    }
+
+
+  tab_submit (t);
+
+
+}
+
+/* Show the homogeneity table */
+static void 
+show_homogeneity(void)
+{
+  size_t v;
+  int n_cols = 5;
+  size_t n_rows = n_vars + 1;
+
+  struct tab_table *t;
+
+
+  t = tab_create (n_cols,n_rows,0);
+  tab_headers (t, 1, 0, 1, 0);
+  tab_dim (t, tab_natural_dimensions);
+
+  /* Put a frame around the entire box, and vertical lines inside */
+  tab_box (t, 
+          TAL_2, TAL_2,
+          -1, TAL_1,
+          0, 0,
+          n_cols - 1, n_rows - 1);
+
+
+  tab_hline(t, TAL_2, 0, n_cols - 1, 1);
+  tab_vline(t, TAL_2, 1, 0, n_rows - 1);
+
+
+  tab_text (t,  1, 0, TAB_CENTER | TAT_TITLE, _("Levene Statistic"));
+  tab_text (t,  2, 0, TAB_CENTER | TAT_TITLE, _("df1"));
+  tab_text (t,  3, 0, TAB_CENTER | TAT_TITLE, _("df2"));
+  tab_text (t,  4, 0, TAB_CENTER | TAT_TITLE, _("Significance"));
+  
+
+  tab_title (t, 0, _("Test of Homogeneity of Variances"));
+
+  for ( v=0 ; v < n_vars ; ++v ) 
+    {
+      double F;
+      const struct variable *var = vars[v];
+      const struct group_proc *gp = group_proc_get (vars[v]);
+      const char *s = var_to_string(var);
+      const struct group_statistics *totals = &gp->ugs;
+
+      const double df1 = gp->n_groups - 1;
+      const double df2 = totals->n - gp->n_groups ;
+
+      tab_text (t, 0, v + 1, TAB_LEFT | TAT_TITLE, s);
+
+      F = gp->levene;
+      tab_float (t, 1, v + 1, TAB_RIGHT, F, 8,3);
+      tab_float (t, 2, v + 1, TAB_RIGHT, df1 ,8,0);
+      tab_float (t, 3, v + 1, TAB_RIGHT, df2 ,8,0);
+
+      /* Now the significance */
+      tab_float (t, 4, v + 1, TAB_RIGHT,gsl_cdf_fdist_Q(F,df1,df2), 8, 3);
+    }
+
+  tab_submit (t);
+
+
+}
+
+
+/* Show the contrast coefficients table */
+static void 
+show_contrast_coeffs(short *bad_contrast)
+{
+  int n_cols = 2 + ostensible_number_of_groups;
+  int n_rows = 2 + cmd.sbc_contrast;
+  union value *group_value;
+  int count = 0 ;      
+  void *const *group_values ;
+
+  struct tab_table *t;
+
+  t = tab_create (n_cols,n_rows,0);
+  tab_headers (t, 2, 0, 2, 0);
+  tab_dim (t, tab_natural_dimensions);
+
+  /* Put a frame around the entire box, and vertical lines inside */
+  tab_box (t, 
+          TAL_2, TAL_2,
+          -1, TAL_1,
+          0, 0,
+          n_cols - 1, n_rows - 1);
+
+  tab_box (t, 
+          -1,-1,
+          TAL_0, TAL_0,
+          2, 0,
+          n_cols - 1, 0);
+
+  tab_box (t,
+          -1,-1,
+          TAL_0, TAL_0,
+          0,0,
+          1,1);
+
+  tab_hline(t, TAL_1, 2, n_cols - 1, 1);
+  tab_hline(t, TAL_2, 0, n_cols - 1, 2);
+
+  tab_vline(t, TAL_2, 2, 0, n_rows - 1);
+
+  tab_title (t, 0, _("Contrast Coefficients"));
+
+  tab_text (t,  0, 2, TAB_LEFT | TAT_TITLE, _("Contrast"));
+
+
+  tab_joint_text (t, 2, 0, n_cols - 1, 0, TAB_CENTER | TAT_TITLE, 
+                 var_to_string(indep_var));
+
+  group_values = hsh_sort(global_group_hash);
+  for (count = 0 ; 
+       count < hsh_count(global_group_hash) ; 
+       ++count)
+    {
+      int i;
+      group_value = group_values[count];
+
+      tab_text (t, count + 2, 1, TAB_CENTER | TAT_TITLE, 
+               value_to_string(group_value, indep_var));
+
+      for (i = 0 ; i < cmd.sbc_contrast ; ++i ) 
+       {
+         tab_text(t, 1, i + 2, TAB_CENTER | TAT_PRINTF, "%d", i + 1);
+
+         if ( bad_contrast[i] ) 
+           tab_text(t, count + 2, i + 2, TAB_RIGHT, "?" );
+         else
+           tab_text(t, count + 2, i + 2, TAB_RIGHT | TAT_PRINTF, "%g", 
+                    subc_list_double_at(&cmd.dl_contrast[i], count)
+                    );
+       }
+    }
+  
+  tab_submit (t);
+}
+
+
+/* Show the results of the contrast tests */
+static void 
+show_contrast_tests(short *bad_contrast)
+{
+  size_t v;
+  int n_cols = 8;
+  size_t n_rows = 1 + n_vars * 2 * cmd.sbc_contrast;
+
+  struct tab_table *t;
+
+  t = tab_create (n_cols,n_rows,0);
+  tab_headers (t, 3, 0, 1, 0);
+  tab_dim (t, tab_natural_dimensions);
+
+  /* Put a frame around the entire box, and vertical lines inside */
+  tab_box (t, 
+          TAL_2, TAL_2,
+          -1, TAL_1,
+          0, 0,
+          n_cols - 1, n_rows - 1);
+
+  tab_box (t, 
+          -1,-1,
+          TAL_0, TAL_0,
+          0, 0,
+          2, 0);
+
+  tab_hline(t, TAL_2, 0, n_cols - 1, 1);
+  tab_vline(t, TAL_2, 3, 0, n_rows - 1);
+
+
+  tab_title (t, 0, _("Contrast Tests"));
+
+  tab_text (t,  2, 0, TAB_CENTER | TAT_TITLE, _("Contrast"));
+  tab_text (t,  3, 0, TAB_CENTER | TAT_TITLE, _("Value of Contrast"));
+  tab_text (t,  4, 0, TAB_CENTER | TAT_TITLE, _("Std. Error"));
+  tab_text (t,  5, 0, TAB_CENTER | TAT_TITLE, _("t"));
+  tab_text (t,  6, 0, TAB_CENTER | TAT_TITLE, _("df"));
+  tab_text (t,  7, 0, TAB_CENTER | TAT_TITLE, _("Sig. (2-tailed)"));
+
+  for ( v = 0 ; v < n_vars ; ++v ) 
+    {
+      int i;
+      int lines_per_variable = 2 * cmd.sbc_contrast;
+
+
+      tab_text (t,  0, (v * lines_per_variable) + 1, TAB_LEFT | TAT_TITLE,
+               var_to_string(vars[v]));
+
+      for ( i = 0 ; i < cmd.sbc_contrast ; ++i ) 
+       {
+         int ci;
+         double contrast_value = 0.0;
+         double coef_msq = 0.0;
+         struct group_proc *grp_data = group_proc_get (vars[v]);
+         struct hsh_table *group_hash = grp_data->group_hash;
+
+         void *const *group_stat_array;
+
+         double T;
+         double std_error_contrast ;
+         double df;
+         double sec_vneq=0.0;
+
+
+         /* Note: The calculation of the degrees of freedom in the 
+            "variances not equal" case is painfull!!
+            The following formula may help to understand it:
+            \frac{\left(\sum_{i=1}^k{c_i^2\frac{s_i^2}{n_i}}\right)^2}
+            {
+            \sum_{i=1}^k\left(
+            \frac{\left(c_i^2\frac{s_i^2}{n_i}\right)^2}  {n_i-1}
+            \right)
+            }
+         */
+
+         double df_denominator = 0.0;
+         double df_numerator = 0.0;
+         if ( i == 0 ) 
+           {
+             tab_text (t,  1, (v * lines_per_variable) + i + 1, 
+                       TAB_LEFT | TAT_TITLE,
+                       _("Assume equal variances"));
+
+             tab_text (t,  1, (v * lines_per_variable) + i + 1 + cmd.sbc_contrast, 
+                       TAB_LEFT | TAT_TITLE, 
+                       _("Does not assume equal"));
+           }
+
+         tab_text (t,  2, (v * lines_per_variable) + i + 1, 
+                   TAB_CENTER | TAT_TITLE | TAT_PRINTF, "%d",i+1);
+
+
+         tab_text (t,  2, (v * lines_per_variable) + i + 1 + cmd.sbc_contrast,
+                   TAB_CENTER | TAT_TITLE | TAT_PRINTF, "%d",i+1);
+
+
+         if ( bad_contrast[i]) 
+           continue;
+
+         group_stat_array = hsh_sort(group_hash);
+         
+         for (ci = 0 ; ci < hsh_count(group_hash) ;  ++ci)
+           {
+             const double coef = subc_list_double_at(&cmd.dl_contrast[i], ci);
+             struct group_statistics *gs = group_stat_array[ci];
+
+             const double winv = (gs->std_dev * gs->std_dev) / gs->n;
+
+             contrast_value += coef * gs->mean;
+
+             coef_msq += (coef * coef) / gs->n ; 
+
+             sec_vneq += (coef * coef) * (gs->std_dev * gs->std_dev ) /gs->n ;
+
+             df_numerator += (coef * coef) * winv;
+             df_denominator += pow2((coef * coef) * winv) / (gs->n - 1);
+           }
+         sec_vneq = sqrt(sec_vneq);
+
+         df_numerator = pow2(df_numerator);
+
+         tab_float (t,  3, (v * lines_per_variable) + i + 1, 
+                    TAB_RIGHT, contrast_value, 8,2);
+
+         tab_float (t,  3, (v * lines_per_variable) + i + 1 + 
+                    cmd.sbc_contrast,
+                    TAB_RIGHT, contrast_value, 8,2);
+
+         std_error_contrast = sqrt(grp_data->mse * coef_msq);
+
+         /* Std. Error */
+         tab_float (t,  4, (v * lines_per_variable) + i + 1, 
+                    TAB_RIGHT, std_error_contrast,
+                    8,3);
+
+         T = fabs(contrast_value / std_error_contrast) ;
+
+         /* T Statistic */
+
+         tab_float (t,  5, (v * lines_per_variable) + i + 1, 
+                    TAB_RIGHT, T,
+                    8,3);
+
+         df = grp_data->ugs.n - grp_data->n_groups;
+
+         /* Degrees of Freedom */
+         tab_float (t,  6, (v * lines_per_variable) + i + 1, 
+                    TAB_RIGHT,  df,
+                    8,0);
+
+
+         /* Significance TWO TAILED !!*/
+         tab_float (t,  7, (v * lines_per_variable) + i + 1, 
+                    TAB_RIGHT,  2 * gsl_cdf_tdist_Q(T,df),
+                    8,3);
+
+
+         /* Now for the Variances NOT Equal case */
+
+         /* Std. Error */
+         tab_float (t,  4, 
+                    (v * lines_per_variable) + i + 1 + cmd.sbc_contrast, 
+                    TAB_RIGHT, sec_vneq,
+                    8,3);
+
+
+         T = contrast_value / sec_vneq;
+         tab_float (t,  5, 
+                    (v * lines_per_variable) + i + 1 + cmd.sbc_contrast, 
+                    TAB_RIGHT, T,
+                    8,3);
+
+
+         df = df_numerator / df_denominator;
+
+         tab_float (t,  6, 
+                    (v * lines_per_variable) + i + 1 + cmd.sbc_contrast, 
+                    TAB_RIGHT, df,
+                    8,3);
+
+         /* The Significance */
+
+         tab_float (t, 7, (v * lines_per_variable) + i + 1 + cmd.sbc_contrast,
+                    TAB_RIGHT,  2 * gsl_cdf_tdist_Q(T,df),
+                    8,3);
+
+
+       }
+
+      if ( v > 0 ) 
+       tab_hline(t, TAL_1, 0, n_cols - 1, (v * lines_per_variable) + 1);
+    }
+
+  tab_submit (t);
+
+}
+
+
+/* ONEWAY ANOVA Calculations */
+
+static void  postcalc (  struct cmd_oneway *cmd UNUSED );
+
+static void  precalc ( struct cmd_oneway *cmd UNUSED );
+
+
+
+/* Pre calculations */
+static void 
+precalc ( struct cmd_oneway *cmd UNUSED )
+{
+  size_t i=0;
+
+  for(i=0; i< n_vars ; ++i) 
+    {
+      struct group_proc *gp = group_proc_get (vars[i]);
+      struct group_statistics *totals = &gp->ugs;
+      
+      /* Create a hash for each of the dependent variables.
+        The hash contains a group_statistics structure, 
+        and is keyed by value of the independent variable */
+
+      gp->group_hash = 
+       hsh_create(4, 
+                  (hsh_compare_func *) compare_group,
+                  (hsh_hash_func *) hash_group,
+                  (hsh_free_func *) free_group,
+                  (void *) indep_var->width );
+
+
+      totals->sum=0;
+      totals->n=0;
+      totals->ssq=0;
+      totals->sum_diff=0;
+      totals->maximum = - DBL_MAX;
+      totals->minimum = DBL_MAX;
+    }
+}
+
+
+static bool
+run_oneway(const struct casefile *cf, void *cmd_)
+{
+  struct casereader *r;
+  struct ccase c;
+
+  struct cmd_oneway *cmd = (struct cmd_oneway *) cmd_;
+
+  global_group_hash = hsh_create(4, 
+                                (hsh_compare_func *) compare_values,
+                                (hsh_hash_func *) hash_value,
+                                0,
+                                (void *) indep_var->width );
+  precalc(cmd);
+
+  for(r = casefile_get_reader (cf);
+      casereader_read (r, &c) ;
+      case_destroy (&c)) 
+    {
+      size_t i;
+
+      const double weight = 
+       dict_get_case_weight(default_dict,&c,&bad_weight_warn);
+      
+      const union value *indep_val = case_data (&c, indep_var->fv);
+
+      /* Deal with missing values */
+      if ( value_is_missing(&indep_var->miss, indep_val) )
+       continue;
+
+      /* Skip the entire case if /MISSING=LISTWISE is set */
+      if ( cmd->miss == ONEWAY_LISTWISE ) 
+       {
+         for(i = 0; i < n_vars ; ++i) 
+           {
+             const struct variable *v = vars[i];
+             const union value *val = case_data (&c, v->fv);
+
+             if (value_is_missing(&v->miss, val) )
+               break;
+           }
+         if ( i != n_vars ) 
+           continue;
+
+       }
+      
+         
+      hsh_insert ( global_group_hash, (void *) indep_val );
+
+      for ( i = 0 ; i < n_vars ; ++i ) 
+       {
+         const struct variable *v = vars[i];
+
+         const union value *val = case_data (&c, v->fv);
+
+          struct group_proc *gp = group_proc_get (vars[i]);
+         struct hsh_table *group_hash = gp->group_hash;
+
+         struct group_statistics *gs;
+
+         gs = hsh_find(group_hash, (void *) indep_val );
+
+         if ( ! gs ) 
+           {
+             gs = xmalloc (sizeof *gs);
+             gs->id = *indep_val;
+             gs->sum=0;
+             gs->n=0;
+             gs->ssq=0;
+             gs->sum_diff=0;
+             gs->minimum = DBL_MAX;
+             gs->maximum = -DBL_MAX;
+
+             hsh_insert ( group_hash, (void *) gs );
+           }
+         
+         if (! value_is_missing(&v->miss, val) )
+           {
+             struct group_statistics *totals = &gp->ugs;
+
+             totals->n+=weight;
+             totals->sum+=weight * val->f;
+             totals->ssq+=weight * val->f * val->f;
+
+             if ( val->f * weight  < totals->minimum ) 
+               totals->minimum = val->f * weight;
+
+             if ( val->f * weight  > totals->maximum ) 
+               totals->maximum = val->f * weight;
+
+             gs->n+=weight;
+             gs->sum+=weight * val->f;
+             gs->ssq+=weight * val->f * val->f;
+
+             if ( val->f * weight  < gs->minimum ) 
+               gs->minimum = val->f * weight;
+
+             if ( val->f * weight  > gs->maximum ) 
+               gs->maximum = val->f * weight;
+           }
+
+         gp->n_groups = hsh_count ( group_hash );
+       }
+  
+    }
+  casereader_destroy (r);
+
+  postcalc(cmd);
+
+  
+  if ( stat_tables & STAT_HOMO ) 
+    levene(cf, indep_var, n_vars, vars, 
+          (cmd->miss == ONEWAY_LISTWISE) ? LEV_LISTWISE : LEV_ANALYSIS ,
+          value_is_missing);
+
+  ostensible_number_of_groups = hsh_count (global_group_hash);
+
+
+  output_oneway();
+
+  return true;
+}
+
+
+/* Post calculations for the ONEWAY command */
+void 
+postcalc (  struct cmd_oneway *cmd UNUSED )
+{
+  size_t i=0;
+
+
+  for(i = 0; i < n_vars ; ++i) 
+    {
+      struct group_proc *gp = group_proc_get (vars[i]);
+      struct hsh_table *group_hash = gp->group_hash;
+      struct group_statistics *totals = &gp->ugs;
+
+      struct hsh_iterator g;
+      struct group_statistics *gs;
+
+      for (gs =  hsh_first (group_hash,&g); 
+          gs != 0; 
+          gs = hsh_next(group_hash,&g))
+       {
+         gs->mean=gs->sum / gs->n;
+         gs->s_std_dev= sqrt(
+                             ( (gs->ssq / gs->n ) - gs->mean * gs->mean )
+                             ) ;
+
+         gs->std_dev= sqrt(
+                           gs->n/(gs->n-1) *
+                           ( (gs->ssq / gs->n ) - gs->mean * gs->mean )
+                           ) ;
+
+         gs->se_mean = gs->std_dev / sqrt(gs->n);
+         gs->mean_diff= gs->sum_diff / gs->n;
+
+       }
+
+
+
+      totals->mean = totals->sum / totals->n;
+      totals->std_dev= sqrt(
+                           totals->n/(totals->n-1) *
+                           ( (totals->ssq / totals->n ) - totals->mean * totals->mean )
+                           ) ;
+
+      totals->se_mean = totals->std_dev / sqrt(totals->n);
+       
+    }
+}
diff --git a/src/language/stats/rank.q b/src/language/stats/rank.q
new file mode 100644 (file)
index 0000000..db1feea
--- /dev/null
@@ -0,0 +1,356 @@
+/* PSPP - RANK. -*-c-*-
+
+Copyright (C) 2005 Free Software Foundation, Inc.
+Author: John Darrington 2005
+
+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., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA. */
+
+#include <config.h>
+#include "command.h"
+#include "dictionary.h"
+#include "sort.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* (headers) */
+
+/* (specification)
+   "RANK" (rank_):
+   *^variables=custom;
+   +rank=custom;
+   +normal=custom;
+   +percent=custom;
+   +ntiles=custom;
+   +rfraction=custom;
+   +proportion=custom;
+   +n=custom;
+   +savage=custom;
+   +print=print:!yes/no;
+   +missing=miss:!exclude/include.
+*/
+/* (declarations) */
+/* (functions) */
+
+
+
+enum RANK_FUNC
+  {
+    RANK,
+    NORMAL,
+    PERCENT,
+    RFRACTION,
+    PROPORTION,
+    N,
+    NTILES,
+    SAVAGE,
+  };
+
+
+struct rank_spec
+{
+  enum RANK_FUNC rfunc;
+  struct variable **destvars;
+  struct variable *srcvar;
+};
+
+
+static struct rank_spec *rank_specs;
+static size_t n_rank_specs;
+
+static struct sort_criteria *sc;
+
+static struct variable **group_vars;
+static size_t n_group_vars;
+
+static struct cmd_rank cmd;
+
+
+
+int cmd_rank(void);
+
+int
+cmd_rank(void)
+{
+  size_t i;
+  n_rank_specs = 0;
+
+  if ( !parse_rank(&cmd) )
+    return CMD_FAILURE;
+
+#if 1
+  for (i = 0 ; i <  sc->crit_cnt ; ++i )
+    {
+      struct sort_criterion *crit = &sc->crits[i];
+      
+      printf("Dir: %d; Index: %d\n", crit->dir, crit->fv);
+    }
+
+  for (i = 0 ; i <  n_group_vars ; ++i )
+    printf("Group var: %s\n",group_vars[0]->name);
+
+  for (i = 0 ; i <  n_rank_specs ; ++i )
+    {
+      int j;
+      printf("Ranks spec %d; Func: %d\n",i, rank_specs[i].rfunc);
+      
+      for (j=0; j < sc->crit_cnt ; ++j )
+       printf("Dest var is \"%s\"\n", rank_specs[i].destvars[j]->name);
+    }
+#endif 
+
+
+  free(group_vars);
+  
+  for (i = 0 ; i <  n_rank_specs ; ++i )
+    {
+      free(rank_specs[i].destvars);
+    }
+      
+  free(rank_specs);
+
+  sort_destroy_criteria(sc);
+
+  return CMD_SUCCESS;
+}
+
+
+
+/* Parser for the variables sub command  
+   Returns 1 on success */
+static int
+rank_custom_variables(struct cmd_rank *cmd UNUSED)
+{
+  static const int terminators[2] = {T_BY, 0};
+
+  lex_match('=');
+
+  if ((token != T_ID || dict_lookup_var (default_dict, tokid) == NULL)
+      && token != T_ALL)
+      return 2;
+
+  sc = sort_parse_criteria (default_dict, 0, 0, 0, terminators);
+
+  if ( lex_match(T_BY)  )
+    {
+      if ((token != T_ID || dict_lookup_var (default_dict, tokid) == NULL))
+       {
+         return 2;
+       }
+
+      if (!parse_variables (default_dict, &group_vars, &n_group_vars,
+                           PV_NO_DUPLICATE | PV_NUMERIC | PV_NO_SCRATCH) )
+       {
+         free (group_vars);
+         return 0;
+       }
+    }
+
+  return 1;
+}
+
+
+/* Return a name for a new variable which ranks the variable VAR_NAME,
+   according to the ranking function F.
+   If IDX is non zero, then IDX is used as a disambiguating number.
+   FIXME: This is not very robust.
+*/
+static char *
+new_variable_name(const char *ranked_var_name, enum RANK_FUNC f, int idx)
+{
+  static char new_name[SHORT_NAME_LEN + 1];
+  char temp[SHORT_NAME_LEN + 1];
+  if ( idx == 0 ) 
+    {
+      switch (f) 
+       {
+       case RANK:
+       case RFRACTION:
+         strcpy(new_name,"R");
+         break;
+
+       case NORMAL:
+       case N:
+       case NTILES:
+         strcpy(new_name,"N");
+         break;
+      
+       case PERCENT:
+       case PROPORTION:
+         strcpy(new_name,"P");
+         break;
+
+       case SAVAGE:
+         strcpy(new_name,"S");
+         break;
+
+       default:
+         assert(false);
+         break;
+       }
+  
+      strncat(new_name, ranked_var_name, 7);
+    }
+  else
+    {
+      strncpy(temp, ranked_var_name, 3);
+      snprintf(new_name, SHORT_NAME_LEN, "%s%03d", temp, idx);
+    }
+
+  return new_name;
+}
+
+/* Parse the [/rank INTO var1 var2 ... varN ] clause */
+static int
+parse_rank_function(struct cmd_rank *cmd UNUSED, enum RANK_FUNC f)
+{
+  static const struct fmt_spec f8_2 = {FMT_F, 8, 2};
+  int var_count = 0;
+  
+  n_rank_specs++;
+  rank_specs = xnrealloc(rank_specs, n_rank_specs, sizeof *rank_specs);
+  rank_specs[n_rank_specs - 1].rfunc = f;
+
+  rank_specs[n_rank_specs - 1].destvars = 
+           xcalloc (sc->crit_cnt, sizeof (struct variable *));
+         
+  if (lex_match_id("INTO"))
+    {
+      struct variable *destvar;
+
+      while( token == T_ID ) 
+       {
+         ++var_count;
+         if ( dict_lookup_var (default_dict, tokid) != NULL )
+           {
+             msg(ME, _("Variable %s already exists."), tokid);
+             return 0;
+           }
+         if ( var_count > sc->crit_cnt ) 
+           {
+             msg(ME, _("Too many variables in INTO clause."));
+             return 0;
+           }
+
+         destvar = dict_create_var (default_dict, tokid, 0);
+         if ( destvar ) 
+           {
+             destvar->print = destvar->write = f8_2;
+           }
+         
+         rank_specs[n_rank_specs - 1].destvars[var_count - 1] = destvar ;
+
+         lex_get();
+         
+       }
+    }
+
+  /* Allocate rank  variable names to all those which haven't had INTO 
+     variables assigned */
+  while (var_count < sc->crit_cnt)
+    {
+      static int idx=0;
+      struct variable *destvar ; 
+      const struct variable *v = dict_get_var(default_dict,
+                                             sc->crits[var_count].fv);
+
+      char *new_name;
+      
+      do {
+       new_name = new_variable_name(v->name, f, idx);
+
+       destvar = dict_create_var (default_dict, new_name, 0);
+       if (!destvar ) 
+         ++idx;
+
+      } while( !destvar ) ;
+
+      destvar->print = destvar->write = f8_2;
+
+      rank_specs[n_rank_specs - 1].destvars[var_count] = destvar ;
+      
+      ++var_count;
+    }
+
+  return 1;
+}
+
+
+static int
+rank_custom_rank(struct cmd_rank *cmd )
+{
+  return parse_rank_function(cmd, RANK);
+}
+
+static int
+rank_custom_normal(struct cmd_rank *cmd )
+{
+  return parse_rank_function(cmd, NORMAL);
+}
+
+static int
+rank_custom_percent(struct cmd_rank *cmd )
+{
+  return parse_rank_function(cmd, NORMAL);
+}
+
+static int
+rank_custom_rfraction(struct cmd_rank *cmd )
+{
+  return parse_rank_function(cmd, RFRACTION);
+}
+
+static int
+rank_custom_proportion(struct cmd_rank *cmd )
+{
+  return parse_rank_function(cmd, PROPORTION);
+}
+
+static int
+rank_custom_n(struct cmd_rank *cmd )
+{
+  return parse_rank_function(cmd, N);
+}
+
+static int
+rank_custom_savage(struct cmd_rank *cmd )
+{
+  return parse_rank_function(cmd, SAVAGE);
+}
+
+
+static int
+rank_custom_ntiles(struct cmd_rank *cmd )
+{
+  if ( lex_force_match('(') ) 
+    {
+      if ( lex_force_int() ) 
+       {
+         lex_get();
+         lex_force_match(')');
+       }
+      else
+       return 0;
+    }
+  else
+    return 0;
+
+  return parse_rank_function(cmd, NTILES);
+}
+
+
diff --git a/src/language/stats/regression-export.h b/src/language/stats/regression-export.h
new file mode 100644 (file)
index 0000000..083064d
--- /dev/null
@@ -0,0 +1,148 @@
+/* PSPP - Comments for C files generated by REGRESSION's EXPORT subcommand.
+   Copyright (C) 2005 Free Software Foundation, Inc.
+   Written by Jason H Stover <jason@sakla.net>.
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+/*
+  Exported C code for a regression model. The EXPORT subcommand causes PSPP
+  to save a model as a small C program. This file contains some of the code
+  of that saved program.
+ */
+#ifndef REG_EXPORT_COMMENTS_H
+#define REG_EXPORT_COMMENTS_H
+const char reg_header[] = "#ifndef REG_EXPORT_COMMENTS_H\n#define REG_EXPORT_COMMENTS_H\n"
+"double pspp_reg_estimate (const double *, const char *[]);\n\n"
+"double pspp_reg_variance (const double *var_vals, const char *[]);\n\n"
+"double pspp_reg_confidence_interval_U "
+"(const double *var_vals, const char *var_names[], double p);\n\n"
+"double pspp_reg_confidence_interval_L "
+"(const double *var_vals, const char *var_names[], double p);\n\n"
+"double pspp_reg_prediction_interval_U "
+"(const double *var_vals, const char *var_names[], double p);\n\n"
+"double pspp_reg_prediction_interval_L "
+"(const double *var_vals, const char *var_names[], double p);\n"
+"#endif\n";
+
+const char reg_preamble[] =  "/*\n   This program contains functions which return estimates\n"
+"   and confidence intervals for a linear model. The EXPORT subcommand\n"
+"   of the REGRESSION procedure of GNU PSPP generated this program.\n*/\n\n"
+"#include <string.h>\n#include <math.h>\n#define PSPP_REG_MAXLEN 1024\n\n";
+
+const char reg_mean_cmt[] =  "/*\n   Estimate the mean of Y, the dependent variable for\n"
+"   the linear model of the form \n\n"
+"      Y = b0 + b1 * X1 + b2 * X2 + ... + bk * Xk + error\n\n"
+"   where X1, ..., Xk are the independent variables\n"
+"   whose values are stored in var_vals and whose names, \n"
+"   as known by PSPP, are stored in var_names. The estimated \n"
+"   regression coefficients (i.e., the estimates of b0,...,bk) \n"
+"   are stored in model_coeffs.\n*/\n";
+
+const char reg_getvar[] = "{\n\t\tj = pspp_reg_getvar (var_names[i]);\n"
+"\t\testimate += var_vals[j] * model_coeffs[j];\n"
+"\t}\n\t\n\treturn estimate;\n}\n\n"
+"/*\n    Variance of an estimated mean of this form:\n\t"
+"Y = b0 + b1 * X1 + ... + bk * Xk\n    where X1,...Xk are the dependent variables,"
+" stored in\n    var_vals and b0,...,bk are the estimated regression coefficients.\n*/\n"
+"double\npspp_reg_variance (const double *var_vals, "
+"const char *var_names[])\n{\n\t";
+
+const char reg_export_t_quantiles_1[] = "/*\n    Quantiles for the T distribution.\n*/\n"
+"static int\npspp_reg_t_quantile "
+"(double prob)\n{\n\n\tint i;\n\tdouble quantiles[] = {\n\t\t";
+
+const char reg_export_t_quantiles_2[] = "i = (int) 100.0 * prob;\n\treturn quantiles[i];\n}\n";
+
+const char reg_variance[] = "double result = 0.0;\n\n\tfor(i = 0; i < n_vars; i++)\n\t"
+"{\n\t\tj = pspp_reg_getvar (var_names[i]);\n\t\t"
+"unshuffled_vals[j] = var_vals[i];\n\t}\n\t"
+"for (i = 0; i < n_vars; i++)\n\t"
+"{\n\t\tresult += cov[i][i] * unshuffled_vals[i] * unshuffled_vals[i];\n\t\t"
+"for (j = i + 1; j < n_vars; j++)\n\t\t{\n\t\t\t"
+"result += 2.0 * cov[i][j] * unshuffled_vals[i] * unshuffled_vals[j];"
+"\n\t\t}\n\t}\n\treturn result;\n}\n";
+
+const char reg_export_confidence_interval[] = "/*\n    Upper confidence limit for an "
+"estimated mean b0 + b1 * X1 + ... + bk * Xk.\n    The confidence interval is a "
+"100 * p percent confidence interval.\n*/\n"
+"double pspp_reg_confidence_interval_U "
+"(const double *var_vals, const char *var_names[], double p)\n{\n\t"
+"double result;\n\t"
+"result = sqrt (pspp_reg_variance (var_vals, var_names));\n\t"
+"result *= pspp_reg_t_quantile ((1.0 + p) / 2.0);\n\t"
+"result += pspp_reg_estimate (var_vals, var_names);\n\treturn result;\n}\n"
+"/*\n    Lower confidence limit for an "
+"estimated mean b0 + b1 * X1 + ... + bk * Xk.\n    The confidence interval is a "
+"100 * p percent confidence interval.\n*/\n"
+"double pspp_reg_confidence_interval_L "
+"(const double *var_vals, const char *var_names[], double p)\n{\n\t"
+"double result;\n\t"
+"result = -sqrt (pspp_reg_variance (var_vals, var_names));\n\t"
+"result *= pspp_reg_t_quantile ((1.0 + p) / 2.0);\n\t"
+"result += pspp_reg_estimate (var_vals, var_names);\n\treturn result;\n}\n";
+
+const char reg_export_prediction_interval_1[] = "/*\n    Upper prediction limit for a "
+"predicted value b0 + b1 * X1 + ... + bk * Xk.\n    The prediction interval is a "
+"100 * p percent prediction interval.\n*/\n"
+"double pspp_reg_prediction_interval_U "
+"(const double *var_vals, const char *var_names[], double p)\n{\n\t"
+"double result;\n\tresult = sqrt (";
+
+const char reg_export_prediction_interval_2[] = " + pspp_reg_variance (var_vals, var_names));\n"
+"\tresult *= pspp_reg_t_quantile ((1.0 + p) / 2.0);\n\t"
+"result += pspp_reg_estimate (var_vals, var_names);\n\treturn result;\n}\n"
+"/*\n    Lower prediction limit for a "
+"predicted value b0 + b1 * X1 + ... + bk * Xk.\n    The prediction interval is a "
+"100 * p percent prediction interval.\n*/\n"
+"double pspp_reg_prediction_interval_L "
+"(const double *var_vals, const char *var_names[], double p)\n{\n\t"
+"double result;\n\t"
+"result = -sqrt (";
+
+const char reg_export_prediction_interval_3[] = " + pspp_reg_variance (var_vals, var_names));"
+"\n\tresult *= pspp_reg_t_quantile ((1.0 + p) / 2.0);\n\t"
+"result += pspp_reg_estimate (var_vals, var_names);\n\treturn result;\n}\n";
+
+/*
+  Change categorical values to binary vectors. The routine will use
+  an encoding in which a categorical variable with n values is mapped
+  to a vector with n-1 entries. Value 0 is mapped to the zero vector,
+  value 1 is mapped to a vector whose first entry is 1 and all others are
+  0, etc. For example, if a variable can have 'a', 'b' or 'c' as values,
+  then the value 'a' will be encoded as (0,0), 'b' as (1,0) and 'c' as
+  (0,1). If the design matrix used to create the model used a different
+  encoding, then the function pspp_reg_categorical_encode () will return
+  a vector which does not match its categorical value in the model.
+ */
+const char reg_export_categorical_encode_1[] = "struct pspp_reg_categorical_variable\n"
+"{\n\tchar * name;\n\tsize_t n_vals;\n\tchar *values[1024];\n};\n\n"
+"/*\n   This function returns the binary vector which corresponds to the value\n"
+"   of the categorical variable stored in 'value'. The name of the variable is\n"
+"   stored in the 'var' argument. Notice the values stored in the\n"
+"   pspp_categorical_variable structures all end with a space character.\n"
+"   That means the values of the categorical variables you pass to any function\n"
+"   in this program should also end with a space character.\n*/\n"
+"static\ndouble * pspp_reg_get_value_vector (char *var, char *value)\n{\n\tdouble *result;\n\t"
+"int i;\n\t";
+
+const char reg_export_categorical_encode_2[] = "int v_index = 0;\n\t"
+"while (v_index < n_vars && strncmp (var, varlist[i]->name, PSPP_REG_MAXLEN) != 0)\n\t{\n\t\t"
+"v_index++;\n\t}\n\tresult = (double *) malloc (varlist[v_index]->n_vals * sizeof (*result));\n\t"
+"for (i = 0; i < varlist[v_index]->n_vals; i++)\n\t{\n\t\t"
+"if (strncmp ( (varlist[v_index]->values)[i], value, PSPP_REG_MAXLEN) == 0)\n\t\t{\n\t\t\t"
+"result[i] = 1.0;\n\t\t}\n\t\telse result[i] = 0.0;\n\t}\n\n\t"
+"return result;\n}\n\n";
+#endif
diff --git a/src/language/stats/regression.q b/src/language/stats/regression.q
new file mode 100644 (file)
index 0000000..1459628
--- /dev/null
@@ -0,0 +1,942 @@
+/* PSPP - linear regression.
+   Copyright (C) 2005 Free Software Foundation, Inc.
+   Written by Jason H Stover <jason@sakla.net>.
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <gsl/gsl_cdf.h>
+#include <gsl/gsl_vector.h>
+#include <gsl/gsl_matrix.h>
+#include <math.h>
+#include "alloc.h"
+#include "case.h"
+#include "casefile.h"
+#include "category.h"
+#include "cat-routines.h"
+#include "command.h"
+#include "design-matrix.h"
+#include "dictionary.h"
+#include "message.h"
+#include "file-handle-def.h"
+#include "gettext.h"
+#include "lexer.h"
+#include "linreg.h"
+#include "coefficient.h"
+#include "missing-values.h"
+#include "regression-export.h"
+#include "table.h"
+#include "value-labels.h"
+#include "variable.h"
+#include "procedure.h"
+
+#define REG_LARGE_DATA 1000
+
+/* (headers) */
+
+/* (specification)
+   "REGRESSION" (regression_):
+   *variables=varlist;
+   statistics[st_]=r,
+   coeff,
+   anova,
+   outs,
+   zpp,
+   label,
+   sha,
+   ci,
+   bcov,
+   ses,
+   xtx,
+   collin,
+   tol,
+   selection,
+   f,
+   defaults,
+   all;
+   export=custom;
+   ^dependent=varlist;
+   method=enter.
+*/
+/* (declarations) */
+/* (functions) */
+static struct cmd_regression cmd;
+
+/*
+  Array holding the subscripts of the independent variables.
+ */
+size_t *indep_vars;
+
+/*
+  File where the model will be saved if the EXPORT subcommand
+  is given. 
+ */
+struct file_handle *model_file;
+
+/*
+  Return value for the procedure.
+ */
+int pspp_reg_rc = CMD_SUCCESS;
+
+static bool run_regression (const struct casefile *, void *);
+
+/* 
+   STATISTICS subcommand output functions.
+ */
+static void reg_stats_r (pspp_linreg_cache *);
+static void reg_stats_coeff (pspp_linreg_cache *);
+static void reg_stats_anova (pspp_linreg_cache *);
+static void reg_stats_outs (pspp_linreg_cache *);
+static void reg_stats_zpp (pspp_linreg_cache *);
+static void reg_stats_label (pspp_linreg_cache *);
+static void reg_stats_sha (pspp_linreg_cache *);
+static void reg_stats_ci (pspp_linreg_cache *);
+static void reg_stats_f (pspp_linreg_cache *);
+static void reg_stats_bcov (pspp_linreg_cache *);
+static void reg_stats_ses (pspp_linreg_cache *);
+static void reg_stats_xtx (pspp_linreg_cache *);
+static void reg_stats_collin (pspp_linreg_cache *);
+static void reg_stats_tol (pspp_linreg_cache *);
+static void reg_stats_selection (pspp_linreg_cache *);
+static void statistics_keyword_output (void (*)(pspp_linreg_cache *),
+                                      int, pspp_linreg_cache *);
+
+static void
+reg_stats_r (pspp_linreg_cache * c)
+{
+  struct tab_table *t;
+  int n_rows = 2;
+  int n_cols = 5;
+  double rsq;
+  double adjrsq;
+  double std_error;
+
+  assert (c != NULL);
+  rsq = c->ssm / c->sst;
+  adjrsq = 1.0 - (1.0 - rsq) * (c->n_obs - 1.0) / (c->n_obs - c->n_indeps);
+  std_error = sqrt ((c->n_indeps - 1.0) / (c->n_obs - 1.0));
+  t = tab_create (n_cols, n_rows, 0);
+  tab_dim (t, tab_natural_dimensions);
+  tab_box (t, TAL_2, TAL_2, -1, TAL_1, 0, 0, n_cols - 1, n_rows - 1);
+  tab_hline (t, TAL_2, 0, n_cols - 1, 1);
+  tab_vline (t, TAL_2, 2, 0, n_rows - 1);
+  tab_vline (t, TAL_0, 1, 0, 0);
+
+  tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("R"));
+  tab_text (t, 2, 0, TAB_CENTER | TAT_TITLE, _("R Square"));
+  tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("Adjusted R Square"));
+  tab_text (t, 4, 0, TAB_CENTER | TAT_TITLE, _("Std. Error of the Estimate"));
+  tab_float (t, 1, 1, TAB_RIGHT, sqrt (rsq), 10, 2);
+  tab_float (t, 2, 1, TAB_RIGHT, rsq, 10, 2);
+  tab_float (t, 3, 1, TAB_RIGHT, adjrsq, 10, 2);
+  tab_float (t, 4, 1, TAB_RIGHT, std_error, 10, 2);
+  tab_title (t, 0, _("Model Summary"));
+  tab_submit (t);
+}
+
+/*
+  Table showing estimated regression coefficients.
+ */
+static void
+reg_stats_coeff (pspp_linreg_cache * c)
+{
+  size_t i;
+  size_t j;
+  int n_cols = 7;
+  int n_rows;
+  double t_stat;
+  double pval;
+  double coeff;
+  double std_err;
+  double beta;
+  const char *label;
+  char *tmp;
+  const struct variable *v;
+  const union value *val;
+  const char *val_s;
+  struct tab_table *t;
+
+  assert (c != NULL);
+  tmp = xnmalloc (MAX_STRING, sizeof (*tmp));
+  n_rows = c->n_coeffs + 2;
+
+  t = tab_create (n_cols, n_rows, 0);
+  tab_headers (t, 2, 0, 1, 0);
+  tab_dim (t, tab_natural_dimensions);
+  tab_box (t, TAL_2, TAL_2, -1, TAL_1, 0, 0, n_cols - 1, n_rows - 1);
+  tab_hline (t, TAL_2, 0, n_cols - 1, 1);
+  tab_vline (t, TAL_2, 2, 0, n_rows - 1);
+  tab_vline (t, TAL_0, 1, 0, 0);
+
+  tab_text (t, 2, 0, TAB_CENTER | TAT_TITLE, _("B"));
+  tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("Std. Error"));
+  tab_text (t, 4, 0, TAB_CENTER | TAT_TITLE, _("Beta"));
+  tab_text (t, 5, 0, TAB_CENTER | TAT_TITLE, _("t"));
+  tab_text (t, 6, 0, TAB_CENTER | TAT_TITLE, _("Significance"));
+  tab_text (t, 1, 1, TAB_LEFT | TAT_TITLE, _("(Constant)"));
+  coeff = c->coeff[0].estimate;
+  tab_float (t, 2, 1, 0, coeff, 10, 2);
+  std_err = sqrt (gsl_matrix_get (c->cov, 0, 0));
+  tab_float (t, 3, 1, 0, std_err, 10, 2);
+  beta = coeff / c->depvar_std;
+  tab_float (t, 4, 1, 0, beta, 10, 2);
+  t_stat = coeff / std_err;
+  tab_float (t, 5, 1, 0, t_stat, 10, 2);
+  pval = 2 * gsl_cdf_tdist_Q (fabs (t_stat), 1.0);
+  tab_float (t, 6, 1, 0, pval, 10, 2);
+  for (j = 1; j <= c->n_indeps; j++)
+    {
+      i = indep_vars[j];
+      v = pspp_linreg_coeff_get_var (c->coeff + j, 0);
+      label = var_to_string (v);
+      /* Do not overwrite the variable's name. */
+      strncpy (tmp, label, MAX_STRING);
+      if (v->type == ALPHA)
+       {
+         /*
+            Append the value associated with this coefficient.
+            This makes sense only if we us the usual binary encoding
+            for that value.
+          */
+
+         val = pspp_linreg_coeff_get_value (c->coeff + j, v);
+         val_s = value_to_string (val, v);
+         strncat (tmp, val_s, MAX_STRING);
+       }
+
+      tab_text (t, 1, j + 1, TAB_CENTER, tmp);
+      /*
+         Regression coefficients.
+       */
+      coeff = c->coeff[j].estimate;
+      tab_float (t, 2, j + 1, 0, coeff, 10, 2);
+      /*
+         Standard error of the coefficients.
+       */
+      std_err = sqrt (gsl_matrix_get (c->cov, j, j));
+      tab_float (t, 3, j + 1, 0, std_err, 10, 2);
+      /*
+         'Standardized' coefficient, i.e., regression coefficient
+         if all variables had unit variance.
+       */
+      beta = gsl_vector_get (c->indep_std, j);
+      beta *= coeff / c->depvar_std;
+      tab_float (t, 4, j + 1, 0, beta, 10, 2);
+
+      /*
+         Test statistic for H0: coefficient is 0.
+       */
+      t_stat = coeff / std_err;
+      tab_float (t, 5, j + 1, 0, t_stat, 10, 2);
+      /*
+         P values for the test statistic above.
+       */
+      pval = 2 * gsl_cdf_tdist_Q (fabs (t_stat), 1.0);
+      tab_float (t, 6, j + 1, 0, pval, 10, 2);
+    }
+  tab_title (t, 0, _("Coefficients"));
+  tab_submit (t);
+  free (tmp);
+}
+
+/*
+  Display the ANOVA table.
+ */
+static void
+reg_stats_anova (pspp_linreg_cache * c)
+{
+  int n_cols = 7;
+  int n_rows = 4;
+  const double msm = c->ssm / c->dfm;
+  const double mse = c->sse / c->dfe;
+  const double F = msm / mse;
+  const double pval = gsl_cdf_fdist_Q (F, c->dfm, c->dfe);
+
+  struct tab_table *t;
+
+  assert (c != NULL);
+  t = tab_create (n_cols, n_rows, 0);
+  tab_headers (t, 2, 0, 1, 0);
+  tab_dim (t, tab_natural_dimensions);
+
+  tab_box (t, TAL_2, TAL_2, -1, TAL_1, 0, 0, n_cols - 1, n_rows - 1);
+
+  tab_hline (t, TAL_2, 0, n_cols - 1, 1);
+  tab_vline (t, TAL_2, 2, 0, n_rows - 1);
+  tab_vline (t, TAL_0, 1, 0, 0);
+
+  tab_text (t, 2, 0, TAB_CENTER | TAT_TITLE, _("Sum of Squares"));
+  tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("df"));
+  tab_text (t, 4, 0, TAB_CENTER | TAT_TITLE, _("Mean Square"));
+  tab_text (t, 5, 0, TAB_CENTER | TAT_TITLE, _("F"));
+  tab_text (t, 6, 0, TAB_CENTER | TAT_TITLE, _("Significance"));
+
+  tab_text (t, 1, 1, TAB_LEFT | TAT_TITLE, _("Regression"));
+  tab_text (t, 1, 2, TAB_LEFT | TAT_TITLE, _("Residual"));
+  tab_text (t, 1, 3, TAB_LEFT | TAT_TITLE, _("Total"));
+
+  /* Sums of Squares */
+  tab_float (t, 2, 1, 0, c->ssm, 10, 2);
+  tab_float (t, 2, 3, 0, c->sst, 10, 2);
+  tab_float (t, 2, 2, 0, c->sse, 10, 2);
+
+
+  /* Degrees of freedom */
+  tab_float (t, 3, 1, 0, c->dfm, 4, 0);
+  tab_float (t, 3, 2, 0, c->dfe, 4, 0);
+  tab_float (t, 3, 3, 0, c->dft, 4, 0);
+
+  /* Mean Squares */
+
+  tab_float (t, 4, 1, TAB_RIGHT, msm, 8, 3);
+  tab_float (t, 4, 2, TAB_RIGHT, mse, 8, 3);
+
+  tab_float (t, 5, 1, 0, F, 8, 3);
+
+  tab_float (t, 6, 1, 0, pval, 8, 3);
+
+  tab_title (t, 0, _("ANOVA"));
+  tab_submit (t);
+}
+static void
+reg_stats_outs (pspp_linreg_cache * c)
+{
+  assert (c != NULL);
+}
+static void
+reg_stats_zpp (pspp_linreg_cache * c)
+{
+  assert (c != NULL);
+}
+static void
+reg_stats_label (pspp_linreg_cache * c)
+{
+  assert (c != NULL);
+}
+static void
+reg_stats_sha (pspp_linreg_cache * c)
+{
+  assert (c != NULL);
+}
+static void
+reg_stats_ci (pspp_linreg_cache * c)
+{
+  assert (c != NULL);
+}
+static void
+reg_stats_f (pspp_linreg_cache * c)
+{
+  assert (c != NULL);
+}
+static void
+reg_stats_bcov (pspp_linreg_cache * c)
+{
+  int n_cols;
+  int n_rows;
+  int i;
+  int j;
+  int k;
+  int row;
+  int col;
+  const char *label;
+  struct tab_table *t;
+
+  assert (c != NULL);
+  n_cols = c->n_indeps + 1 + 2;
+  n_rows = 2 * (c->n_indeps + 1);
+  t = tab_create (n_cols, n_rows, 0);
+  tab_headers (t, 2, 0, 1, 0);
+  tab_dim (t, tab_natural_dimensions);
+  tab_box (t, TAL_2, TAL_2, -1, TAL_1, 0, 0, n_cols - 1, n_rows - 1);
+  tab_hline (t, TAL_2, 0, n_cols - 1, 1);
+  tab_vline (t, TAL_2, 2, 0, n_rows - 1);
+  tab_vline (t, TAL_0, 1, 0, 0);
+  tab_text (t, 0, 0, TAB_CENTER | TAT_TITLE, _("Model"));
+  tab_text (t, 1, 1, TAB_CENTER | TAT_TITLE, _("Covariances"));
+  for (i = 1; i < c->n_indeps + 1; i++)
+    {
+      j = indep_vars[(i - 1)];
+      struct variable *v = cmd.v_variables[j];
+      label = var_to_string (v);
+      tab_text (t, 2, i, TAB_CENTER, label);
+      tab_text (t, i + 2, 0, TAB_CENTER, label);
+      for (k = 1; k < c->n_indeps + 1; k++)
+       {
+         col = (i <= k) ? k : i;
+         row = (i <= k) ? i : k;
+         tab_float (t, k + 2, i, TAB_CENTER,
+                    gsl_matrix_get (c->cov, row, col), 8, 3);
+       }
+    }
+  tab_title (t, 0, _("Coefficient Correlations"));
+  tab_submit (t);
+}
+static void
+reg_stats_ses (pspp_linreg_cache * c)
+{
+  assert (c != NULL);
+}
+static void
+reg_stats_xtx (pspp_linreg_cache * c)
+{
+  assert (c != NULL);
+}
+static void
+reg_stats_collin (pspp_linreg_cache * c)
+{
+  assert (c != NULL);
+}
+static void
+reg_stats_tol (pspp_linreg_cache * c)
+{
+  assert (c != NULL);
+}
+static void
+reg_stats_selection (pspp_linreg_cache * c)
+{
+  assert (c != NULL);
+}
+
+static void
+statistics_keyword_output (void (*function) (pspp_linreg_cache *),
+                          int keyword, pspp_linreg_cache * c)
+{
+  if (keyword)
+    {
+      (*function) (c);
+    }
+}
+
+static void
+subcommand_statistics (int *keywords, pspp_linreg_cache * c)
+{
+  /* 
+     The order here must match the order in which the STATISTICS 
+     keywords appear in the specification section above.
+   */
+  enum
+  { r,
+    coeff,
+    anova,
+    outs,
+    zpp,
+    label,
+    sha,
+    ci,
+    bcov,
+    ses,
+    xtx,
+    collin,
+    tol,
+    selection,
+    f,
+    defaults,
+    all
+  };
+  int i;
+  int d = 1;
+
+  if (keywords[all])
+    {
+      /*
+         Set everything but F.
+       */
+      for (i = 0; i < f; i++)
+       {
+         keywords[i] = 1;
+       }
+    }
+  else
+    {
+      for (i = 0; i < all; i++)
+       {
+         if (keywords[i])
+           {
+             d = 0;
+           }
+       }
+      /*
+         Default output: ANOVA table, parameter estimates,
+         and statistics for variables not entered into model,
+         if appropriate.
+       */
+      if (keywords[defaults] | d)
+       {
+         keywords[anova] = 1;
+         keywords[outs] = 1;
+         keywords[coeff] = 1;
+         keywords[r] = 1;
+       }
+    }
+  statistics_keyword_output (reg_stats_r, keywords[r], c);
+  statistics_keyword_output (reg_stats_anova, keywords[anova], c);
+  statistics_keyword_output (reg_stats_coeff, keywords[coeff], c);
+  statistics_keyword_output (reg_stats_outs, keywords[outs], c);
+  statistics_keyword_output (reg_stats_zpp, keywords[zpp], c);
+  statistics_keyword_output (reg_stats_label, keywords[label], c);
+  statistics_keyword_output (reg_stats_sha, keywords[sha], c);
+  statistics_keyword_output (reg_stats_ci, keywords[ci], c);
+  statistics_keyword_output (reg_stats_f, keywords[f], c);
+  statistics_keyword_output (reg_stats_bcov, keywords[bcov], c);
+  statistics_keyword_output (reg_stats_ses, keywords[ses], c);
+  statistics_keyword_output (reg_stats_xtx, keywords[xtx], c);
+  statistics_keyword_output (reg_stats_collin, keywords[collin], c);
+  statistics_keyword_output (reg_stats_tol, keywords[tol], c);
+  statistics_keyword_output (reg_stats_selection, keywords[selection], c);
+}
+static int
+reg_inserted (const struct variable *v, struct variable **varlist, int n_vars)
+{
+  int i;
+
+  for (i = 0; i < n_vars; i++)
+    {
+      if (v->index == varlist[i]->index)
+       {
+         return 1;
+       }
+    }
+  return 0;
+}
+static void
+reg_print_categorical_encoding (FILE * fp, pspp_linreg_cache * c)
+{
+  int i;
+  size_t j;
+  int n_vars = 0;
+  struct variable **varlist;
+  struct pspp_linreg_coeff *coeff;
+  const struct variable *v;
+  union value *val;
+
+  fprintf (fp, "%s", reg_export_categorical_encode_1);
+
+  varlist = xnmalloc (c->n_indeps, sizeof (*varlist));
+  for (i = 1; i < c->n_indeps; i++)    /* c->coeff[0] is the intercept. */
+    {
+      coeff = c->coeff + i;
+      v = pspp_linreg_coeff_get_var (coeff, 0);
+      if (v->type == ALPHA)
+       {
+         if (!reg_inserted (v, varlist, n_vars))
+           {
+             fprintf (fp, "struct pspp_reg_categorical_variable %s;\n\t",
+                      v->name);
+             varlist[n_vars] = (struct variable *) v;
+             n_vars++;
+           }
+       }
+    }
+  fprintf (fp, "int n_vars = %d;\n\t", n_vars);
+  fprintf (fp, "struct pspp_reg_categorical_variable *varlist[%d] = {",
+          n_vars);
+  for (i = 0; i < n_vars - 1; i++)
+    {
+      fprintf (fp, "&%s,\n\t\t", varlist[i]->name);
+    }
+  fprintf (fp, "&%s};\n\t", varlist[i]->name);
+
+  for (i = 0; i < n_vars; i++)
+    {
+      coeff = c->coeff + i;
+      fprintf (fp, "%s.name = \"%s\";\n\t", varlist[i]->name,
+              varlist[i]->name);
+      fprintf (fp, "%s.n_vals = %d;\n\t", varlist[i]->name,
+              varlist[i]->obs_vals->n_categories);
+
+      for (j = 0; j < varlist[i]->obs_vals->n_categories; j++)
+       {
+         val = cat_subscript_to_value ((const size_t) j, varlist[i]);
+         fprintf (fp, "%s.values[%d] = \"%s\";\n\t", varlist[i]->name, j,
+                  value_to_string (val, varlist[i]));
+       }
+    }
+  fprintf (fp, "%s", reg_export_categorical_encode_2);
+}
+
+static void
+reg_print_depvars (FILE * fp, pspp_linreg_cache * c)
+{
+  int i;
+  struct pspp_linreg_coeff *coeff;
+  const struct variable *v;
+
+  fprintf (fp, "char *model_depvars[%d] = {", c->n_indeps);
+  for (i = 1; i < c->n_indeps; i++)
+    {
+      coeff = c->coeff + i;
+      v = pspp_linreg_coeff_get_var (coeff, 0);
+      fprintf (fp, "\"%s\",\n\t\t", v->name);
+    }
+  coeff = c->coeff + i;
+  v = pspp_linreg_coeff_get_var (coeff, 0);
+  fprintf (fp, "\"%s\"};\n\t", v->name);
+}
+static void
+reg_print_getvar (FILE * fp, pspp_linreg_cache * c)
+{
+  fprintf (fp, "static int\npspp_reg_getvar (char *v_name)\n{\n\t");
+  fprintf (fp, "int i;\n\tint n_vars = %d;\n\t", c->n_indeps);
+  reg_print_depvars (fp, c);
+  fprintf (fp, "for (i = 0; i < n_vars; i++)\n\t{\n\t\t");
+  fprintf (fp,
+          "if (strncmp (v_name, model_depvars[i], PSPP_REG_MAXLEN) == 0)\n\t\t{\n\t\t\t");
+  fprintf (fp, "return i;\n\t\t}\n\t}\n}\n");
+}
+static void
+subcommand_export (int export, pspp_linreg_cache * c)
+{
+  size_t i;
+  size_t j;
+  int n_quantiles = 100;
+  double increment;
+  double tmp;
+  struct pspp_linreg_coeff coeff;
+
+  if (export)
+    {
+      FILE *fp;
+      assert (c != NULL);
+      assert (model_file != NULL);
+      assert (fp != NULL);
+      fp = fopen (fh_get_filename (model_file), "w");
+      fprintf (fp, "%s", reg_preamble);
+      reg_print_getvar (fp, c);
+      reg_print_categorical_encoding (fp, c);
+      fprintf (fp, "%s", reg_export_t_quantiles_1);
+      increment = 0.5 / (double) increment;
+      for (i = 0; i < n_quantiles - 1; i++)
+       {
+         tmp = 0.5 + 0.005 * (double) i;
+         fprintf (fp, "%.15e,\n\t\t",
+                  gsl_cdf_tdist_Pinv (tmp, c->n_obs - c->n_indeps));
+       }
+      fprintf (fp, "%.15e};\n\t",
+              gsl_cdf_tdist_Pinv (.9995, c->n_obs - c->n_indeps));
+      fprintf (fp, "%s", reg_export_t_quantiles_2);
+      fprintf (fp, "%s", reg_mean_cmt);
+      fprintf (fp, "double\npspp_reg_estimate (const double *var_vals,");
+      fprintf (fp, "const char *var_names[])\n{\n\t");
+      fprintf (fp, "double model_coeffs[%d] = {", c->n_indeps);
+      for (i = 1; i < c->n_indeps; i++)
+       {
+         coeff = c->coeff[i];
+         fprintf (fp, "%.15e,\n\t\t", coeff.estimate);
+       }
+      coeff = c->coeff[i];
+      fprintf (fp, "%.15e};\n\t", coeff.estimate);
+      coeff = c->coeff[0];
+      fprintf (fp, "double estimate = %.15e;\n\t", coeff.estimate);
+      fprintf (fp, "int i;\n\tint j;\n\n\t");
+      fprintf (fp, "for (i = 0; i < %d; i++)\n\t", c->n_indeps);
+      fprintf (fp, "%s", reg_getvar);
+      fprintf (fp, "const double cov[%d][%d] = {\n\t", c->n_coeffs,
+              c->n_coeffs);
+      for (i = 0; i < c->cov->size1 - 1; i++)
+       {
+         fprintf (fp, "{");
+         for (j = 0; j < c->cov->size2 - 1; j++)
+           {
+             fprintf (fp, "%.15e, ", gsl_matrix_get (c->cov, i, j));
+           }
+         fprintf (fp, "%.15e},\n\t", gsl_matrix_get (c->cov, i, j));
+       }
+      fprintf (fp, "{");
+      for (j = 0; j < c->cov->size2 - 1; j++)
+       {
+         fprintf (fp, "%.15e, ",
+                  gsl_matrix_get (c->cov, c->cov->size1 - 1, j));
+       }
+      fprintf (fp, "%.15e}\n\t",
+              gsl_matrix_get (c->cov, c->cov->size1 - 1, c->cov->size2 - 1));
+      fprintf (fp, "};\n\tint n_vars = %d;\n\tint i;\n\tint j;\n\t",
+              c->n_indeps);
+      fprintf (fp, "double unshuffled_vals[%d];\n\t", c->n_indeps);
+      fprintf (fp, "%s", reg_variance);
+      fprintf (fp, "%s", reg_export_confidence_interval);
+      tmp = c->mse * c->mse;
+      fprintf (fp, "%s %.15e", reg_export_prediction_interval_1, tmp);
+      fprintf (fp, "%s %.15e", reg_export_prediction_interval_2, tmp);
+      fprintf (fp, "%s", reg_export_prediction_interval_3);
+      fclose (fp);
+      fp = fopen ("pspp_model_reg.h", "w");
+      fprintf (fp, "%s", reg_header);
+      fclose (fp);
+    }
+}
+static int
+regression_custom_export (struct cmd_regression *cmd)
+{
+  /* 0 on failure, 1 on success, 2 on failure that should result in syntax error */
+  if (!lex_force_match ('('))
+    return 0;
+
+  if (lex_match ('*'))
+    model_file = NULL;
+  else
+    {
+      model_file = fh_parse (FH_REF_FILE);
+      if (model_file == NULL)
+       return 0;
+    }
+
+  if (!lex_force_match (')'))
+    return 0;
+
+  return 1;
+}
+
+int
+cmd_regression (void)
+{
+  if (!parse_regression (&cmd))
+    return CMD_FAILURE;
+  if (!multipass_procedure_with_splits (run_regression, &cmd))
+    return CMD_CASCADING_FAILURE;
+
+  return pspp_reg_rc;
+}
+
+/*
+  Is variable k one of the dependent variables?
+ */
+static int
+is_depvar (size_t k)
+{
+  size_t j = 0;
+  for (j = 0; j < cmd.n_dependent; j++)
+    {
+      /*
+         compare_var_names returns 0 if the variable
+         names match.
+       */
+      if (!compare_var_names (cmd.v_dependent[j], cmd.v_variables[k], NULL))
+       return 1;
+    }
+  return 0;
+}
+
+/*
+  Mark missing cases. Return the number of non-missing cases.
+ */
+static size_t
+mark_missing_cases (const struct casefile *cf, struct variable *v,
+                   int *is_missing_case, double n_data)
+{
+  struct casereader *r;
+  struct ccase c;
+  size_t row;
+  const union value *val;
+
+  for (r = casefile_get_reader (cf);
+       casereader_read (r, &c); case_destroy (&c))
+    {
+      row = casereader_cnum (r) - 1;
+
+      val = case_data (&c, v->fv);
+      cat_value_update (v, val);
+      if (mv_is_value_missing (&v->miss, val))
+       {
+         if (!is_missing_case[row])
+           {
+             /* Now it is missing. */
+             n_data--;
+             is_missing_case[row] = 1;
+           }
+       }
+    }
+  casereader_destroy (r);
+
+  return n_data;
+}
+
+static bool
+run_regression (const struct casefile *cf, void *cmd_ UNUSED)
+{
+  size_t i;
+  size_t n_data = 0;
+  size_t row;
+  size_t case_num;
+  int n_indep;
+  int j = 0;
+  int k;
+  /*
+     Keep track of the missing cases.
+   */
+  int *is_missing_case;
+  const union value *val;
+  struct casereader *r;
+  struct ccase c;
+  struct variable *v;
+  struct variable *depvar;
+  struct variable **indep_vars;
+  struct design_matrix *X;
+  gsl_vector *Y;
+  pspp_linreg_cache *lcache;
+  pspp_linreg_opts lopts;
+
+  n_data = casefile_get_case_cnt (cf);
+
+  for (i = 0; i < cmd.n_dependent; i++)
+    {
+      if (cmd.v_dependent[i]->type != NUMERIC)
+       {
+         msg (SE, gettext ("Dependent variable must be numeric."));
+         pspp_reg_rc = CMD_FAILURE;
+         return true;
+       }
+    }
+
+  is_missing_case = xnmalloc (n_data, sizeof (*is_missing_case));
+  for (i = 0; i < n_data; i++)
+    is_missing_case[i] = 0;
+
+  n_indep = cmd.n_variables - cmd.n_dependent;
+  indep_vars = xnmalloc (n_indep, sizeof *indep_vars);
+
+  lopts.get_depvar_mean_std = 1;
+  lopts.get_indep_mean_std = xnmalloc (n_indep, sizeof (int));
+
+  /*
+     Read from the active file. The first pass encodes categorical
+     variables and drops cases with missing values.
+   */
+  j = 0;
+  for (i = 0; i < cmd.n_variables; i++)
+    {
+      if (!is_depvar (i))
+       {
+         v = cmd.v_variables[i];
+         indep_vars[j] = v;
+         j++;
+         if (v->type == ALPHA)
+           {
+             /* Make a place to hold the binary vectors 
+                corresponding to this variable's values. */
+             cat_stored_values_create (v);
+           }
+         n_data = mark_missing_cases (cf, v, is_missing_case, n_data);
+       }
+    }
+
+  /*
+     Drop cases with missing values for any dependent variable.
+   */
+  j = 0;
+  for (i = 0; i < cmd.n_dependent; i++)
+    {
+      v = cmd.v_dependent[i];
+      j++;
+      n_data = mark_missing_cases (cf, v, is_missing_case, n_data);
+    }
+
+  for (k = 0; k < cmd.n_dependent; k++)
+    {
+      depvar = cmd.v_dependent[k];
+      Y = gsl_vector_alloc (n_data);
+
+      X =
+       design_matrix_create (n_indep, (const struct variable **) indep_vars,
+                             n_data);
+      for (i = 0; i < X->m->size2; i++)
+       {
+         lopts.get_indep_mean_std[i] = 1;
+       }
+      lcache = pspp_linreg_cache_alloc (X->m->size1, X->m->size2);
+      lcache->indep_means = gsl_vector_alloc (X->m->size2);
+      lcache->indep_std = gsl_vector_alloc (X->m->size2);
+      lcache->depvar = (const struct variable *) depvar;
+      /*
+         For large data sets, use QR decomposition.
+       */
+      if (n_data > sqrt (n_indep) && n_data > REG_LARGE_DATA)
+       {
+         lcache->method = PSPP_LINREG_SVD;
+       }
+
+      /*
+         The second pass creates the design matrix.
+       */
+      row = 0;
+      for (r = casefile_get_reader (cf); casereader_read (r, &c);
+          case_destroy (&c))
+       /* Iterate over the cases. */
+       {
+         case_num = casereader_cnum (r) - 1;
+         if (!is_missing_case[case_num])
+           {
+             for (i = 0; i < cmd.n_variables; ++i)     /* Iterate over the variables
+                                                          for the current case. 
+                                                        */
+               {
+                 v = cmd.v_variables[i];
+                 val = case_data (&c, v->fv);
+                 /*
+                    Independent/dependent variable separation. The
+                    'variables' subcommand specifies a varlist which contains
+                    both dependent and independent variables. The dependent
+                    variables are specified with the 'dependent'
+                    subcommand, and maybe also in the 'variables' subcommand. 
+                    We need to separate the two.
+                  */
+                 if (!is_depvar (i))
+                   {
+                     if (v->type == ALPHA)
+                       {
+                         design_matrix_set_categorical (X, row, v, val);
+                       }
+                     else if (v->type == NUMERIC)
+                       {
+                         design_matrix_set_numeric (X, row, v, val);
+                       }
+                   }
+               }
+             val = case_data (&c, depvar->fv);
+             gsl_vector_set (Y, row, val->f);
+             row++;
+           }
+       }
+      /*
+         Now that we know the number of coefficients, allocate space
+         and store pointers to the variables that correspond to the
+         coefficients.
+       */
+      pspp_linreg_coeff_init (lcache, X);
+
+      /* 
+         Find the least-squares estimates and other statistics.
+       */
+      pspp_linreg ((const gsl_vector *) Y, X->m, &lopts, lcache);
+      subcommand_statistics (cmd.a_statistics, lcache);
+      subcommand_export (cmd.sbc_export, lcache);
+      gsl_vector_free (Y);
+      design_matrix_destroy (X);
+      pspp_linreg_cache_free (lcache);
+      free (lopts.get_indep_mean_std);
+      casereader_destroy (r);
+    }
+  free (indep_vars);
+  free (is_missing_case);
+
+  return true;
+}
+
+/*
+  Local Variables:   
+  mode: c
+  End:
+*/
diff --git a/src/language/stats/sort-cases.c b/src/language/stats/sort-cases.c
new file mode 100644 (file)
index 0000000..ed442f7
--- /dev/null
@@ -0,0 +1,78 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <limits.h>
+#include "alloc.h"
+#include "command.h"
+#include "message.h"
+#include "lexer.h"
+#include "settings.h"
+#include "sort-criteria.h"
+#include "sort.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+
+/* Performs the SORT CASES procedures. */
+int
+cmd_sort_cases (void)
+{
+  struct sort_criteria *criteria;
+  bool success = false;
+
+  lex_match (T_BY);
+
+  criteria = sort_parse_criteria (default_dict, NULL, NULL, NULL, NULL);
+  if (criteria == NULL)
+    return CMD_CASCADING_FAILURE;
+
+  if (get_testing_mode () && lex_match ('/')) 
+    {
+      if (!lex_force_match_id ("BUFFERS") || !lex_match ('=')
+          || !lex_force_int ())
+        goto done;
+
+      min_buffers = max_buffers = lex_integer ();
+      allow_internal_sort = false;
+      if (max_buffers < 2) 
+        {
+          msg (SE, _("Buffer limit must be at least 2."));
+          goto done;
+        }
+
+      lex_get ();
+    }
+
+  success = sort_active_file_in_place (criteria);
+
+ done:
+  min_buffers = 64;
+  max_buffers = INT_MAX;
+  allow_internal_sort = true;
+  
+  sort_destroy_criteria (criteria);
+  return success ? lex_end_of_command () : CMD_CASCADING_FAILURE;
+}
+
diff --git a/src/language/stats/sort-criteria.c b/src/language/stats/sort-criteria.c
new file mode 100644 (file)
index 0000000..5bc26d9
--- /dev/null
@@ -0,0 +1,164 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <limits.h>
+#include "alloc.h"
+#include "command.h"
+#include "message.h"
+#include "lexer.h"
+#include "settings.h"
+#include "variable.h"
+#include "sort-criteria.h"
+#include "sort.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+static bool  is_terminator(int tok, const int *terminators);
+
+
+/* Parses a list of sort keys and returns a struct sort_criteria
+   based on it.  Returns a null pointer on error.
+   If SAW_DIRECTION is nonnull, sets *SAW_DIRECTION to true if at
+   least one parenthesized sort direction was specified, false
+   otherwise. 
+   If TERMINATORS is non-null, then it must be a pointer to a 
+   null terminated list of tokens, in addition to the defaults,
+   which are to be considered terminators of the clause being parsed.
+   The default terminators are '/' and '.'
+   
+*/
+struct sort_criteria *
+sort_parse_criteria (const struct dictionary *dict,
+                     struct variable ***vars, size_t *var_cnt,
+                     bool *saw_direction,
+                    const int *terminators
+                    )
+{
+  struct sort_criteria *criteria;
+  struct variable **local_vars = NULL;
+  size_t local_var_cnt;
+
+  assert ((vars == NULL) == (var_cnt == NULL));
+  if (vars == NULL) 
+    {
+      vars = &local_vars;
+      var_cnt = &local_var_cnt;
+    }
+
+  criteria = xmalloc (sizeof *criteria);
+  criteria->crits = NULL;
+  criteria->crit_cnt = 0;
+
+  *vars = NULL;
+  *var_cnt = 0;
+  if (saw_direction != NULL)
+    *saw_direction = false;
+
+  do
+    {
+      size_t prev_var_cnt = *var_cnt;
+      enum sort_direction direction;
+
+      /* Variables. */
+      if (!parse_variables (dict, vars, var_cnt,
+                           PV_NO_DUPLICATE | PV_APPEND | PV_NO_SCRATCH))
+        goto error;
+
+      /* Sort direction. */
+      if (lex_match ('('))
+       {
+         if (lex_match_id ("D") || lex_match_id ("DOWN"))
+           direction = SRT_DESCEND;
+         else if (lex_match_id ("A") || lex_match_id ("UP"))
+            direction = SRT_ASCEND;
+          else
+           {
+             msg (SE, _("`A' or `D' expected inside parentheses."));
+              goto error;
+           }
+         if (!lex_match (')'))
+           {
+             msg (SE, _("`)' expected."));
+              goto error;
+           }
+          if (saw_direction != NULL)
+            *saw_direction = true;
+       }
+      else
+        direction = SRT_ASCEND;
+
+      criteria->crits = xnrealloc (criteria->crits,
+                                   *var_cnt, sizeof *criteria->crits);
+      criteria->crit_cnt = *var_cnt;
+      for (; prev_var_cnt < criteria->crit_cnt; prev_var_cnt++) 
+        {
+          struct sort_criterion *c = &criteria->crits[prev_var_cnt];
+          c->fv = (*vars)[prev_var_cnt]->fv;
+          c->width = (*vars)[prev_var_cnt]->width;
+          c->dir = direction;
+        }
+    }
+  while (token != '.' && token != '/' && !is_terminator(token, terminators));
+
+  free (local_vars);
+  return criteria;
+
+ error:
+  free (local_vars);
+  sort_destroy_criteria (criteria);
+  return NULL;
+}
+
+/* Return TRUE if TOK is a member of the list of TERMINATORS.
+   FALSE otherwise */
+static bool 
+is_terminator(int tok, const int *terminators)
+{
+  if (terminators == NULL ) 
+    return false;
+
+  while ( *terminators) 
+    {
+      if (tok == *terminators++)
+       return true;
+    }
+
+  return false;
+}
+
+
+
+/* Destroys a SORT CASES program. */
+void
+sort_destroy_criteria (struct sort_criteria *criteria) 
+{
+  if (criteria != NULL) 
+    {
+      free (criteria->crits);
+      free (criteria);
+    }
+}
+
+
+
diff --git a/src/language/stats/sort-criteria.h b/src/language/stats/sort-criteria.h
new file mode 100644 (file)
index 0000000..8ee3dba
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef SORT_CRITERIA_H
+#define SORT_CRITERIA_H
+
+#include <config.h>
+#include <stdbool.h>
+
+struct variable;
+struct dictionary;
+
+struct sort_criteria *sort_parse_criteria (const struct dictionary *,
+                                           struct variable ***, size_t *,
+                                           bool *saw_direction,
+                                          const int *terminators
+                                          );
+
+void sort_destroy_criteria (struct sort_criteria *criteria) ;
+
+
+#endif /* SORT_PRS_H */
diff --git a/src/language/stats/t-test.q b/src/language/stats/t-test.q
new file mode 100644 (file)
index 0000000..d0fff3f
--- /dev/null
@@ -0,0 +1,1987 @@
+/* PSPP - computes sample statistics. -*-c-*-
+
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by John Williams <johnr.williams@stonebow.otago.ac.nz>.
+   Almost completly re-written by John Darrington 2004
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <gsl/gsl_cdf.h>
+#include "message.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "alloc.h"
+#include "case.h"
+#include "casefile.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "group-proc.h"
+#include "hash.h"
+#include "levene.h"
+#include "lexer.h"
+#include "magic.h"
+#include "misc.h"
+#include "size_max.h"
+#include "manager.h"
+#include "str.h"
+#include "table.h"
+#include "value-labels.h"
+#include "variable.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* (headers) */
+
+/* (specification)
+   "T-TEST" (tts_):
+     +groups=custom;
+     testval=double;
+     variables=varlist("PV_NO_SCRATCH | PV_NUMERIC");
+     pairs=custom;
+     +missing=miss:!analysis/listwise,
+             incl:include/!exclude;
+     format=fmt:!labels/nolabels;
+     criteria=:cin(d:criteria,"%s > 0. && %s < 1.").
+*/
+/* (declarations) */
+/* (functions) */
+
+
+
+
+/* Function to use for testing for missing values */
+static is_missing_func *value_is_missing;
+
+/* Variable for the GROUPS subcommand, if given. */
+static struct variable *indep_var;
+
+enum comparison
+  {
+    CMP_LE = -2,
+    CMP_EQ = 0,
+  };
+
+struct group_properties
+{
+  /* The comparison criterion */
+  enum comparison criterion;
+
+  /* The width of the independent variable */
+  int indep_width ;  
+
+  union {
+    /* The value of the independent variable at which groups are determined to 
+       belong to one group or the other */
+    double critical_value;
+    
+
+    /* The values of the independent variable for each group */
+    union value g_value[2];
+  } v ;
+
+};
+
+
+static struct group_properties gp ;
+
+
+
+/* PAIRS: Number of pairs to be compared ; each pair. */
+static int n_pairs = 0 ;
+struct pair 
+{
+  /* The variables comprising the pair */
+  struct variable *v[2];
+
+  /* The number of valid variable pairs */
+  double n;
+
+  /* The sum of the members */
+  double sum[2];
+
+  /* sum of squares of the members */
+  double ssq[2];
+
+  /* Std deviation of the members */
+  double std_dev[2];
+
+
+  /* Sample Std deviation of the members */
+  double s_std_dev[2];
+
+  /* The means of the members */
+  double mean[2];
+
+  /* The correlation coefficient between the variables */
+  double correlation;
+
+  /* The sum of the differences */
+  double sum_of_diffs;
+
+  /* The sum of the products */
+  double sum_of_prod;
+
+  /* The mean of the differences */
+  double mean_diff;
+
+  /* The sum of the squares of the differences */
+  double ssq_diffs;
+
+  /* The std deviation of the differences */
+  double std_dev_diff;
+};
+
+static struct pair *pairs=0;
+
+static int parse_value (union value * v, int type) ;
+
+/* Structures and Functions for the Statistics Summary Box */
+struct ssbox;
+typedef void populate_ssbox_func(struct ssbox *ssb,
+                                           struct cmd_t_test *cmd);
+typedef void finalize_ssbox_func(struct ssbox *ssb);
+
+struct ssbox
+{
+  struct tab_table *t;
+
+  populate_ssbox_func *populate;
+  finalize_ssbox_func *finalize;
+
+};
+
+/* Create a ssbox */
+void ssbox_create(struct ssbox *ssb,   struct cmd_t_test *cmd, int mode);
+
+/* Populate a ssbox according to cmd */
+void ssbox_populate(struct ssbox *ssb, struct cmd_t_test *cmd);
+
+/* Submit and destroy a ssbox */
+void ssbox_finalize(struct ssbox *ssb);
+
+/* A function to create, populate and submit the Paired Samples Correlation 
+   box */
+void pscbox(void);
+
+
+/* Structures and Functions for the Test Results Box */
+struct trbox;
+
+typedef void populate_trbox_func(struct trbox *trb,
+                                struct cmd_t_test *cmd);
+typedef void finalize_trbox_func(struct trbox *trb);
+
+struct trbox {
+  struct tab_table *t;
+  populate_trbox_func *populate;
+  finalize_trbox_func *finalize;
+};
+
+/* Create a trbox */
+void trbox_create(struct trbox *trb,   struct cmd_t_test *cmd, int mode);
+
+/* Populate a ssbox according to cmd */
+void trbox_populate(struct trbox *trb, struct cmd_t_test *cmd);
+
+/* Submit and destroy a ssbox */
+void trbox_finalize(struct trbox *trb);
+
+/* Which mode was T-TEST invoked */
+enum {
+  T_1_SAMPLE = 0 ,
+  T_IND_SAMPLES, 
+  T_PAIRED
+};
+
+
+static int common_calc (const struct ccase *, void *);
+static void common_precalc (struct cmd_t_test *);
+static void common_postcalc (struct cmd_t_test *);
+
+static int one_sample_calc (const struct ccase *, void *);
+static void one_sample_precalc (struct cmd_t_test *);
+static void one_sample_postcalc (struct cmd_t_test *);
+
+static int  paired_calc (const struct ccase *, void *);
+static void paired_precalc (struct cmd_t_test *);
+static void paired_postcalc (struct cmd_t_test *);
+
+static void group_precalc (struct cmd_t_test *);
+static int  group_calc (const struct ccase *, struct cmd_t_test *);
+static void group_postcalc (struct cmd_t_test *);
+
+
+static bool calculate(const struct casefile *cf, void *_mode);
+
+static  int mode;
+
+static struct cmd_t_test cmd;
+
+static int bad_weight_warn;
+
+
+static int compare_group_binary(const struct group_statistics *a, 
+                               const struct group_statistics *b, 
+                               const struct group_properties *p);
+
+
+static unsigned  hash_group_binary(const struct group_statistics *g, 
+                                  const struct group_properties *p);
+
+
+
+int
+cmd_t_test(void)
+{
+  bool ok;
+  
+  if ( !parse_t_test(&cmd) )
+    return CMD_FAILURE;
+
+  if (! cmd.sbc_criteria)
+    cmd.criteria=0.95;
+
+  {
+    int m=0;
+    if (cmd.sbc_testval) ++m;
+    if (cmd.sbc_groups) ++m;
+    if (cmd.sbc_pairs) ++m;
+
+    if ( m != 1)
+      {
+       msg(SE, 
+           _("TESTVAL, GROUPS and PAIRS subcommands are mutually exclusive.")
+           );
+        free_t_test(&cmd);
+       return CMD_FAILURE;
+      }
+  }
+
+  if (cmd.sbc_testval) 
+    mode=T_1_SAMPLE;
+  else if (cmd.sbc_groups)
+    mode=T_IND_SAMPLES;
+  else
+    mode=T_PAIRED;
+
+  if ( mode == T_PAIRED) 
+    {
+      if (cmd.sbc_variables) 
+       {
+         msg(SE, _("VARIABLES subcommand is not appropriate with PAIRS"));
+          free_t_test(&cmd);
+         return CMD_FAILURE;
+       }
+      else
+       {
+         /* Iterate through the pairs and put each variable that is a 
+            member of a pair into cmd.v_variables */
+
+         int i;
+         struct hsh_iterator hi;
+         struct hsh_table *hash;
+         struct variable *v;
+
+         hash = hsh_create (n_pairs, compare_var_names, hash_var_name, 0, 0);
+
+         for (i=0; i < n_pairs; ++i)
+           {
+             hsh_insert(hash,pairs[i].v[0]);
+             hsh_insert(hash,pairs[i].v[1]);
+           }
+
+         assert(cmd.n_variables == 0);
+         cmd.n_variables = hsh_count(hash);
+
+         cmd.v_variables = xnrealloc (cmd.v_variables, cmd.n_variables,
+                                       sizeof *cmd.v_variables);
+         /* Iterate through the hash */
+         for (i=0,v = (struct variable *) hsh_first(hash,&hi);
+              v != 0;
+              v=hsh_next(hash,&hi) ) 
+           cmd.v_variables[i++]=v;
+
+         hsh_destroy(hash);
+       }
+    }
+  else if ( !cmd.sbc_variables) 
+    {
+      msg(SE, _("One or more VARIABLES must be specified."));
+      free_t_test(&cmd);
+      return CMD_FAILURE;
+    }
+
+
+  /* If /MISSING=INCLUDE is set, then user missing values are ignored */
+  if (cmd.incl == TTS_INCLUDE ) 
+    value_is_missing = mv_is_value_system_missing;
+  else
+    value_is_missing = mv_is_value_missing;
+
+  bad_weight_warn = 1;
+
+  ok = multipass_procedure_with_splits (calculate, &cmd);
+
+  n_pairs=0;
+  free(pairs);
+  pairs=0;
+
+  if ( mode == T_IND_SAMPLES) 
+    {
+      int v;
+      /* Destroy any group statistics we created */
+      for (v = 0 ; v < cmd.n_variables ; ++v ) 
+       {
+         struct group_proc *grpp = group_proc_get (cmd.v_variables[v]);
+         hsh_destroy (grpp->group_hash);
+       }
+    }
+    
+  free_t_test(&cmd);
+  return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+}
+
+static int
+tts_custom_groups (struct cmd_t_test *cmd UNUSED)
+{
+  int n_group_values=0;
+
+  lex_match('=');
+
+  indep_var = parse_variable ();
+  if (!indep_var)
+    {
+      lex_error ("expecting variable name in GROUPS subcommand");
+      return 0;
+    }
+
+  if (indep_var->type == T_STRING && indep_var->width > MAX_SHORT_STRING)
+    {
+      msg (SE, _("Long string variable %s is not valid here."),
+          indep_var->name);
+      return 0;
+    }
+
+  if (!lex_match ('('))
+    {
+      if (indep_var->type == NUMERIC)
+       {
+         gp.v.g_value[0].f = 1;
+         gp.v.g_value[1].f = 2;
+
+         gp.criterion = CMP_EQ;
+         
+         n_group_values = 2;
+
+         return 1;
+       }
+      else
+       {
+         msg (SE, _("When applying GROUPS to a string variable, two "
+                    "values must be specified."));
+         return 0;
+       }
+    }
+
+  if (!parse_value (&gp.v.g_value[0], indep_var->type))
+      return 0;
+
+  lex_match (',');
+  if (lex_match (')'))
+    {
+      if (indep_var->type != NUMERIC)
+       {
+
+         msg (SE, _("When applying GROUPS to a string variable, two "
+                    "values must be specified."));
+         return 0;
+       }
+      gp.criterion = CMP_LE;
+      gp.v.critical_value = gp.v.g_value[0].f;
+
+      n_group_values = 1;
+      return 1;
+    }
+
+  if (!parse_value (&gp.v.g_value[1], indep_var->type))
+    return 0;
+
+  n_group_values = 2;
+  if (!lex_force_match (')'))
+    return 0;
+
+  if ( n_group_values == 2 ) 
+    gp.criterion = CMP_EQ ;
+  else
+    gp.criterion = CMP_LE ;
+
+
+  return 1;
+}
+
+
+static int
+tts_custom_pairs (struct cmd_t_test *cmd UNUSED)
+{
+  struct variable **vars;
+  size_t n_vars;
+  size_t n_pairs_local;
+
+  size_t n_before_WITH;
+  size_t n_after_WITH = SIZE_MAX;
+  int paired ; /* Was the PAIRED keyword given ? */
+
+  lex_match('=');
+
+  n_vars=0;
+  if (!parse_variables (default_dict, &vars, &n_vars,
+                       PV_DUPLICATE | PV_NUMERIC | PV_NO_SCRATCH))
+    {
+      free (vars);
+      return 0;
+    }
+  assert (n_vars);
+
+  n_before_WITH = 0;
+  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;
+       }
+      n_after_WITH = n_vars - n_before_WITH;
+    }
+
+  paired = (lex_match ('(') && lex_match_id ("PAIRED") && lex_match (')'));
+
+  /* Determine the number of pairs needed */
+  if (paired)
+    {
+      if (n_before_WITH != n_after_WITH)
+       {
+         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_after_WITH );
+         return 0;
+       }
+      n_pairs_local = n_before_WITH;
+    }
+  else if (n_before_WITH > 0) /* WITH keyword given, but not PAIRED keyword */
+    {
+      n_pairs_local = n_before_WITH * n_after_WITH ;
+    }
+  else /* Neither WITH nor PAIRED keyword given */
+    {
+      if (n_vars < 2)
+       {
+         free (vars);
+         msg (SE, _("At least two variables must be specified "
+                    "on PAIRS."));
+         return 0;
+       }
+
+      /* how many ways can you pick 2 from n_vars ? */
+      n_pairs_local = n_vars * (n_vars - 1) / 2;
+    }
+
+
+  /* Allocate storage for the pairs */
+  pairs = xnrealloc (pairs, n_pairs + n_pairs_local, sizeof *pairs);
+
+  /* Populate the pairs with the appropriate variables */
+  if ( paired ) 
+    {
+      int i;
+
+      assert(n_pairs_local == n_vars / 2);
+      for (i = 0; i < n_pairs_local; ++i)
+       {
+         pairs[i].v[n_pairs] = vars[i];
+         pairs[i].v[n_pairs + 1] = vars[i + n_pairs_local];
+       }
+    }
+  else if (n_before_WITH > 0) /* WITH keyword given, but not PAIRED keyword */
+    {
+      int i,j;
+      size_t p = n_pairs;
+
+      for(i=0 ; i < n_before_WITH ; ++i ) 
+       {
+         for(j=0 ; j < n_after_WITH ; ++j)
+           {
+             pairs[p].v[0] = vars[i];
+             pairs[p].v[1] = vars[j+n_before_WITH];
+             ++p;
+           }
+       }
+    }
+  else /* Neither WITH nor PAIRED given */
+    {
+      size_t i,j;
+      size_t p=n_pairs;
+      
+      for(i=0 ; i < n_vars ; ++i ) 
+       {
+         for(j=i+1 ; j < n_vars ; ++j)
+           {
+             pairs[p].v[0] = vars[i];
+             pairs[p].v[1] = vars[j];
+             ++p;
+           }
+       }
+    }
+
+  n_pairs+=n_pairs_local;
+
+  free (vars);
+  return 1;
+}
+
+/* Parses the current token (numeric or string, depending on type)
+    value v and returns success. */
+static int
+parse_value (union value * v, int type )
+{
+  if (type == NUMERIC)
+    {
+      if (!lex_force_num ())
+       return 0;
+      v->f = tokval;
+    }
+  else
+    {
+      if (!lex_force_string ())
+       return 0;
+      strncpy (v->s, ds_c_str (&tokstr), ds_length (&tokstr));
+    }
+
+  lex_get ();
+
+  return 1;
+}
+
+
+/* Implementation of the SSBOX object */
+
+void ssbox_base_init(struct ssbox *this, int cols,int rows);
+
+void ssbox_base_finalize(struct ssbox *ssb);
+
+void ssbox_one_sample_init(struct ssbox *this, 
+                          struct cmd_t_test *cmd );
+
+void ssbox_independent_samples_init(struct ssbox *this,
+                                   struct cmd_t_test *cmd);
+
+void ssbox_paired_init(struct ssbox *this,
+                          struct cmd_t_test *cmd);
+
+
+/* Factory to create an ssbox */
+void 
+ssbox_create(struct ssbox *ssb, struct cmd_t_test *cmd, int mode)
+{
+    switch (mode) 
+      {
+      case T_1_SAMPLE:
+       ssbox_one_sample_init(ssb,cmd);
+       break;
+      case T_IND_SAMPLES:
+       ssbox_independent_samples_init(ssb,cmd);
+       break;
+      case T_PAIRED:
+       ssbox_paired_init(ssb,cmd);
+       break;
+      default:
+       assert(0);
+      }
+}
+
+
+
+/* Despatcher for the populate method */
+void
+ssbox_populate(struct ssbox *ssb,struct cmd_t_test *cmd)
+{
+  ssb->populate(ssb,cmd);
+}
+
+
+/* Despatcher for finalize */
+void
+ssbox_finalize(struct ssbox *ssb)
+{
+  ssb->finalize(ssb);
+}
+
+
+/* Submit the box and clear up */
+void 
+ssbox_base_finalize(struct ssbox *ssb)
+{
+  tab_submit(ssb->t);
+}
+
+
+
+/* Initialize a ssbox struct */
+void 
+ssbox_base_init(struct ssbox *this, int cols,int rows)
+{
+  this->finalize = ssbox_base_finalize;
+  this->t = tab_create (cols, rows, 0);
+
+  tab_columns (this->t, SOM_COL_DOWN, 1);
+  tab_headers (this->t,0,0,1,0); 
+  tab_box (this->t, TAL_2, TAL_2, TAL_0, TAL_1, 0, 0, cols -1, rows -1 );
+  tab_hline(this->t, TAL_2,0,cols-1,1);
+  tab_dim (this->t, tab_natural_dimensions);
+}
+
+void  ssbox_one_sample_populate(struct ssbox *ssb,
+                             struct cmd_t_test *cmd);
+
+/* Initialize the one_sample ssbox */
+void 
+ssbox_one_sample_init(struct ssbox *this, 
+                          struct cmd_t_test *cmd )
+{
+  const int hsize=5;
+  const int vsize=cmd->n_variables+1;
+
+  this->populate = ssbox_one_sample_populate;
+
+  ssbox_base_init(this, hsize,vsize);
+  tab_title (this->t, 0, _("One-Sample Statistics"));
+  tab_vline(this->t, TAL_2, 1,0,vsize - 1);
+  tab_text (this->t, 1, 0, TAB_CENTER | TAT_TITLE, _("N"));
+  tab_text (this->t, 2, 0, TAB_CENTER | TAT_TITLE, _("Mean"));
+  tab_text (this->t, 3, 0, TAB_CENTER | TAT_TITLE, _("Std. Deviation"));
+  tab_text (this->t, 4, 0, TAB_CENTER | TAT_TITLE, _("SE. Mean"));
+}
+
+void ssbox_independent_samples_populate(struct ssbox *ssb,
+                                       struct cmd_t_test *cmd);
+
+/* Initialize the independent samples ssbox */
+void 
+ssbox_independent_samples_init(struct ssbox *this, 
+       struct cmd_t_test *cmd)
+{
+  int hsize=6;
+  int vsize = cmd->n_variables*2 +1;
+
+  this->populate = ssbox_independent_samples_populate;
+
+  ssbox_base_init(this, hsize,vsize);
+  tab_title (this->t, 0, _("Group Statistics"));
+  tab_vline(this->t,0,1,0,vsize - 1);
+  tab_text (this->t, 1, 0, TAB_CENTER | TAT_TITLE, indep_var->name);
+  tab_text (this->t, 2, 0, TAB_CENTER | TAT_TITLE, _("N"));
+  tab_text (this->t, 3, 0, TAB_CENTER | TAT_TITLE, _("Mean"));
+  tab_text (this->t, 4, 0, TAB_CENTER | TAT_TITLE, _("Std. Deviation"));
+  tab_text (this->t, 5, 0, TAB_CENTER | TAT_TITLE, _("SE. Mean"));
+}
+
+
+/* Populate the ssbox for independent samples */
+void 
+ssbox_independent_samples_populate(struct ssbox *ssb,
+                             struct cmd_t_test *cmd)
+{
+  int i;
+
+  char *val_lab0=0;
+  char *val_lab1=0;
+  double indep_value[2];
+
+  char prefix[2][3]={"",""};
+
+  if ( indep_var->type == NUMERIC ) 
+    {
+      val_lab0 = val_labs_find( indep_var->val_labs,gp.v.g_value[0]); 
+      val_lab1 = val_labs_find( indep_var->val_labs,gp.v.g_value[1]);
+    }
+  else
+    {
+      val_lab0 = gp.v.g_value[0].s;
+      val_lab1 = gp.v.g_value[1].s;
+    }
+
+  if (gp.criterion == CMP_LE ) 
+    {
+      strcpy(prefix[0],"< ");
+      strcpy(prefix[1],">=");
+      indep_value[0] = gp.v.critical_value;
+      indep_value[1] = gp.v.critical_value;
+    }
+  else
+    {
+      indep_value[0] = gp.v.g_value[0].f;
+      indep_value[1] = gp.v.g_value[1].f;
+    }
+
+  assert(ssb->t);
+
+  for (i=0; i < cmd->n_variables; ++i)
+    {
+      struct variable *var = cmd->v_variables[i];
+      struct hsh_table *grp_hash = group_proc_get (var)->group_hash;
+      int count=0;
+
+      tab_text (ssb->t, 0, i*2+1, TAB_LEFT, cmd->v_variables[i]->name);
+
+      if (val_lab0)
+       tab_text (ssb->t, 1, i*2+1, TAB_LEFT | TAT_PRINTF, 
+                 "%s%s", prefix[0], val_lab0);
+      else
+         tab_text (ssb->t, 1, i*2+1, TAB_LEFT | TAT_PRINTF, 
+                   "%s%g", prefix[0], indep_value[0]);
+
+
+      if (val_lab1)
+       tab_text (ssb->t, 1, i*2+1+1, TAB_LEFT | TAT_PRINTF, 
+                 "%s%s", prefix[1], val_lab1);
+      else
+         tab_text (ssb->t, 1, i*2+1+1, TAB_LEFT | TAT_PRINTF, 
+                   "%s%g", prefix[1], indep_value[1]);
+
+
+      /* Fill in the group statistics */
+      for ( count = 0 ; count < 2 ; ++count ) 
+       {
+         union value search_val;
+
+         struct group_statistics *gs;
+
+         if ( gp.criterion == CMP_LE ) 
+           {
+             if ( count == 0 ) 
+               {
+                 /*  less than ( < )  case */
+                 search_val.f = gp.v.critical_value - 1.0;
+               }
+             else
+               {
+                 /* >= case  */
+                 search_val.f = gp.v.critical_value + 1.0;
+               }
+           }
+         else
+           {
+             search_val = gp.v.g_value[count];
+           }
+
+         gs = hsh_find(grp_hash, (void *) &search_val);
+         assert(gs);
+
+         tab_float(ssb->t, 2 ,i*2+count+1, TAB_RIGHT, gs->n, 2, 0);
+         tab_float(ssb->t, 3 ,i*2+count+1, TAB_RIGHT, gs->mean, 8, 2);
+         tab_float(ssb->t, 4 ,i*2+count+1, TAB_RIGHT, gs->std_dev, 8, 3);
+         tab_float(ssb->t, 5 ,i*2+count+1, TAB_RIGHT, gs->se_mean, 8, 3);
+       }
+    }
+}
+
+
+void ssbox_paired_populate(struct ssbox *ssb,
+                          struct cmd_t_test *cmd);
+
+/* Initialize the paired values ssbox */
+void 
+ssbox_paired_init(struct ssbox *this, struct cmd_t_test *cmd UNUSED)
+{
+  int hsize=6;
+
+  int vsize = n_pairs*2+1;
+
+  this->populate = ssbox_paired_populate;
+
+  ssbox_base_init(this, hsize,vsize);
+  tab_title (this->t, 0, _("Paired Sample Statistics"));
+  tab_vline(this->t,TAL_0,1,0,vsize-1);
+  tab_vline(this->t,TAL_2,2,0,vsize-1);
+  tab_text (this->t, 2, 0, TAB_CENTER | TAT_TITLE, _("Mean"));
+  tab_text (this->t, 3, 0, TAB_CENTER | TAT_TITLE, _("N"));
+  tab_text (this->t, 4, 0, TAB_CENTER | TAT_TITLE, _("Std. Deviation"));
+  tab_text (this->t, 5, 0, TAB_CENTER | TAT_TITLE, _("SE. Mean"));
+}
+
+
+/* Populate the ssbox for paired values */
+void 
+ssbox_paired_populate(struct ssbox *ssb,struct cmd_t_test *cmd UNUSED)
+{
+  int i;
+
+  assert(ssb->t);
+
+  for (i=0; i < n_pairs; ++i)
+    {
+      int j;
+
+      tab_text (ssb->t, 0, i*2+1, TAB_LEFT | TAT_PRINTF , _("Pair %d"),i);
+
+      for (j=0 ; j < 2 ; ++j) 
+       {
+         struct group_statistics *gs;
+
+         gs = &group_proc_get (pairs[i].v[j])->ugs;
+
+         /* Titles */
+
+         tab_text (ssb->t, 1, i*2+j+1, TAB_LEFT, pairs[i].v[j]->name);
+
+         /* Values */
+         tab_float (ssb->t,2, i*2+j+1, TAB_RIGHT, pairs[i].mean[j], 8, 2);
+         tab_float (ssb->t,3, i*2+j+1, TAB_RIGHT, pairs[i].n, 2, 0);
+         tab_float (ssb->t,4, i*2+j+1, TAB_RIGHT, pairs[i].std_dev[j], 8, 3);
+         tab_float (ssb->t,5, i*2+j+1, TAB_RIGHT, pairs[i].std_dev[j]/sqrt(pairs[i].n), 8, 3);
+
+       }
+    }
+}
+
+/* Populate the one sample ssbox */
+void 
+ssbox_one_sample_populate(struct ssbox *ssb, struct cmd_t_test *cmd)
+{
+  int i;
+
+  assert(ssb->t);
+
+  for (i=0; i < cmd->n_variables; ++i)
+    {
+      struct group_statistics *gs = &group_proc_get (cmd->v_variables[i])->ugs;
+
+      tab_text (ssb->t, 0, i+1, TAB_LEFT, cmd->v_variables[i]->name);
+      tab_float (ssb->t,1, i+1, TAB_RIGHT, gs->n, 2, 0);
+      tab_float (ssb->t,2, i+1, TAB_RIGHT, gs->mean, 8, 2);
+      tab_float (ssb->t,3, i+1, TAB_RIGHT, gs->std_dev, 8, 2);
+      tab_float (ssb->t,4, i+1, TAB_RIGHT, gs->se_mean, 8, 3);
+    }
+  
+}
+
+
+
+/* Implementation of the Test Results box struct */
+
+void trbox_base_init(struct trbox *self,size_t n_vars, int cols);
+void trbox_base_finalize(struct trbox *trb);
+
+void trbox_independent_samples_init(struct trbox *trb,
+                                   struct cmd_t_test *cmd );
+
+void trbox_independent_samples_populate(struct trbox *trb,
+                                       struct cmd_t_test *cmd);
+
+void trbox_one_sample_init(struct trbox *self,
+                     struct cmd_t_test *cmd );
+
+void trbox_one_sample_populate(struct trbox *trb,
+                              struct cmd_t_test *cmd);
+
+void trbox_paired_init(struct trbox *self,
+                      struct cmd_t_test *cmd );
+
+void trbox_paired_populate(struct trbox *trb,
+                     struct cmd_t_test *cmd);
+
+
+
+/* Create a trbox according to mode*/
+void 
+trbox_create(struct trbox *trb,   
+            struct cmd_t_test *cmd, int mode)
+{
+    switch (mode) 
+      {
+      case T_1_SAMPLE:
+       trbox_one_sample_init(trb,cmd);
+       break;
+      case T_IND_SAMPLES:
+       trbox_independent_samples_init(trb,cmd);
+       break;
+      case T_PAIRED:
+       trbox_paired_init(trb,cmd);
+       break;
+      default:
+       assert(0);
+      }
+}
+
+/* Populate a trbox according to cmd */
+void 
+trbox_populate(struct trbox *trb, struct cmd_t_test *cmd)
+{
+  trb->populate(trb,cmd);
+}
+
+/* Submit and destroy a trbox */
+void 
+trbox_finalize(struct trbox *trb)
+{
+  trb->finalize(trb);
+}
+
+/* Initialize the independent samples trbox */
+void 
+trbox_independent_samples_init(struct trbox *self,
+                          struct cmd_t_test *cmd UNUSED)
+{
+  const int hsize=11;
+  const int vsize=cmd->n_variables*2+3;
+
+  assert(self);
+  self->populate = trbox_independent_samples_populate;
+
+  trbox_base_init(self,cmd->n_variables*2,hsize);
+  tab_title(self->t,0,_("Independent Samples Test"));
+  tab_hline(self->t,TAL_1,2,hsize-1,1);
+  tab_vline(self->t,TAL_2,2,0,vsize-1);
+  tab_vline(self->t,TAL_1,4,0,vsize-1);
+  tab_box(self->t,-1,-1,-1,TAL_1, 2,1,hsize-2,vsize-1);
+  tab_hline(self->t,TAL_1, hsize-2,hsize-1,2);
+  tab_box(self->t,-1,-1,-1,TAL_1, hsize-2,2,hsize-1,vsize-1);
+  tab_joint_text(self->t, 2, 0, 3, 0, 
+                TAB_CENTER,_("Levene's Test for Equality of Variances"));
+  tab_joint_text(self->t, 4,0,hsize-1,0,
+                TAB_CENTER,_("t-test for Equality of Means"));
+
+  tab_text(self->t,2,2, TAB_CENTER | TAT_TITLE,_("F"));
+  tab_text(self->t,3,2, TAB_CENTER | TAT_TITLE,_("Sig."));
+  tab_text(self->t,4,2, TAB_CENTER | TAT_TITLE,_("t"));
+  tab_text(self->t,5,2, TAB_CENTER | TAT_TITLE,_("df"));
+  tab_text(self->t,6,2, TAB_CENTER | TAT_TITLE,_("Sig. (2-tailed)"));
+  tab_text(self->t,7,2, TAB_CENTER | TAT_TITLE,_("Mean Difference"));
+  tab_text(self->t,8,2, TAB_CENTER | TAT_TITLE,_("Std. Error Difference"));
+  tab_text(self->t,9,2, TAB_CENTER | TAT_TITLE,_("Lower"));
+  tab_text(self->t,10,2, TAB_CENTER | TAT_TITLE,_("Upper"));
+
+  tab_joint_text(self->t, 9, 1, 10, 1, TAB_CENTER | TAT_PRINTF, 
+                _("%g%% Confidence Interval of the Difference"),
+                cmd->criteria*100.0);
+
+}
+
+/* Populate the independent samples trbox */
+void 
+trbox_independent_samples_populate(struct trbox *self,
+                                  struct cmd_t_test *cmd )
+{
+  int i;
+
+  assert(self);
+  for (i=0; i < cmd->n_variables; ++i)
+    {
+      double p,q;
+
+      double t;
+      double df;
+
+      double df1, df2;
+
+      double pooled_variance;
+      double std_err_diff;
+      double mean_diff;
+
+      struct variable *var = cmd->v_variables[i];
+      struct group_proc *grp_data = group_proc_get (var);
+
+      struct hsh_table *grp_hash = grp_data->group_hash;
+
+      struct group_statistics *gs0 ;
+      struct group_statistics *gs1 ;
+         
+      union value search_val;
+         
+      if ( gp.criterion == CMP_LE ) 
+       search_val.f = gp.v.critical_value - 1.0;
+      else
+       search_val = gp.v.g_value[0];
+
+      gs0 = hsh_find(grp_hash, (void *) &search_val);
+      assert(gs0);
+
+      if ( gp.criterion == CMP_LE ) 
+       search_val.f = gp.v.critical_value + 1.0;
+      else
+       search_val = gp.v.g_value[1];
+
+      gs1 = hsh_find(grp_hash, (void *) &search_val);
+      assert(gs1);
+
+         
+      tab_text (self->t, 0, i*2+3, TAB_LEFT, cmd->v_variables[i]->name);
+
+      tab_text (self->t, 1, i*2+3, TAB_LEFT, _("Equal variances assumed"));
+
+
+      tab_float(self->t, 2, i*2+3, TAB_CENTER, grp_data->levene, 8,3);
+
+      /* Now work out the significance of the Levene test */
+      df1 = 1; df2 = grp_data->ugs.n - 2;
+      q = gsl_cdf_fdist_Q(grp_data->levene, df1, df2);
+
+      tab_float(self->t, 3, i*2+3, TAB_CENTER, q, 8,3 );
+
+      df = gs0->n + gs1->n - 2.0 ;
+      tab_float (self->t, 5, i*2+3, TAB_RIGHT, df, 2, 0);
+
+      pooled_variance = ( (gs0->n )*pow2(gs0->s_std_dev)
+                         + 
+                         (gs1->n )*pow2(gs1->s_std_dev) 
+                       ) / df  ;
+
+      t = (gs0->mean - gs1->mean) / sqrt(pooled_variance) ;
+      t /= sqrt((gs0->n + gs1->n)/(gs0->n*gs1->n)); 
+
+      tab_float (self->t, 4, i*2+3, TAB_RIGHT, t, 8, 3);
+
+      p = gsl_cdf_tdist_P(t, df);
+      q = gsl_cdf_tdist_Q(t, df);
+
+      tab_float(self->t, 6, i*2+3, TAB_RIGHT, 2.0*(t>0?q:p) , 8, 3);
+
+      mean_diff = gs0->mean - gs1->mean;
+      tab_float(self->t, 7, i*2+3, TAB_RIGHT, mean_diff, 8, 3);
+
+
+      std_err_diff = sqrt( pow2(gs0->se_mean) + pow2(gs1->se_mean));
+      tab_float(self->t, 8, i*2+3, TAB_RIGHT, std_err_diff, 8, 3);
+
+
+      /* Now work out the confidence interval */
+      q = (1 - cmd->criteria)/2.0;  /* 2-tailed test */
+
+      t = gsl_cdf_tdist_Qinv(q,df);
+      tab_float(self->t, 9, i*2+3, TAB_RIGHT, 
+               mean_diff - t * std_err_diff, 8, 3); 
+
+      tab_float(self->t, 10, i*2+3, TAB_RIGHT, 
+               mean_diff + t * std_err_diff, 8, 3); 
+
+
+      {
+       double se2;
+      /* Now for the \sigma_1 != \sigma_2 case */
+      tab_text (self->t, 1, i*2+3+1, 
+               TAB_LEFT, _("Equal variances not assumed"));
+
+
+      se2 = (pow2(gs0->s_std_dev)/(gs0->n -1) ) +
+       (pow2(gs1->s_std_dev)/(gs1->n -1) );
+
+      t = mean_diff / sqrt(se2) ;
+      tab_float (self->t, 4, i*2+3+1, TAB_RIGHT, t, 8, 3);
+               
+      df = pow2(se2) / ( 
+                      (pow2(pow2(gs0->s_std_dev)/(gs0->n - 1 )) 
+                       /(gs0->n -1 )
+                       )
+                      + 
+                      (pow2(pow2(gs1->s_std_dev)/(gs1->n - 1 ))
+                       /(gs1->n -1 )
+                       )
+                      ) ;
+      tab_float (self->t, 5, i*2+3+1, TAB_RIGHT, df, 8, 3);
+
+      p = gsl_cdf_tdist_P(t, df);
+      q = gsl_cdf_tdist_Q(t, df);
+
+      tab_float(self->t, 6, i*2+3+1, TAB_RIGHT, 2.0*(t>0?q:p) , 8, 3);
+
+      /* Now work out the confidence interval */
+      q = (1 - cmd->criteria)/2.0;  /* 2-tailed test */
+
+      t = gsl_cdf_tdist_Qinv(q, df);
+
+      tab_float(self->t, 7, i*2+3+1, TAB_RIGHT, mean_diff, 8, 3);
+
+
+      tab_float(self->t, 8, i*2+3+1, TAB_RIGHT, std_err_diff, 8, 3);
+
+
+      tab_float(self->t, 9, i*2+3+1, TAB_RIGHT, 
+               mean_diff - t * std_err_diff, 8, 3); 
+
+      tab_float(self->t, 10, i*2+3+1, TAB_RIGHT, 
+               mean_diff + t * std_err_diff, 8, 3); 
+
+      }
+    }
+}
+
+/* Initialize the paired samples trbox */
+void 
+trbox_paired_init(struct trbox *self,
+                          struct cmd_t_test *cmd UNUSED)
+{
+
+  const int hsize=10;
+  const int vsize=n_pairs+3;
+
+  self->populate = trbox_paired_populate;
+
+  trbox_base_init(self,n_pairs,hsize);
+  tab_title (self->t, 0, _("Paired Samples Test"));
+  tab_hline(self->t,TAL_1,2,6,1);
+  tab_vline(self->t,TAL_2,2,0,vsize - 1);
+  tab_joint_text(self->t,2,0,6,0,TAB_CENTER,_("Paired Differences"));
+  tab_box(self->t,-1,-1,-1,TAL_1, 2,1,6,vsize-1);
+  tab_box(self->t,-1,-1,-1,TAL_1, 6,0,hsize-1,vsize-1);
+  tab_hline(self->t,TAL_1,5,6, 2);
+  tab_vline(self->t,TAL_0,6,0,1);
+
+  tab_joint_text(self->t, 5, 1, 6, 1, TAB_CENTER | TAT_PRINTF, 
+                _("%g%% Confidence Interval of the Difference"),
+                cmd->criteria*100.0);
+
+  tab_text (self->t, 2, 2, TAB_CENTER | TAT_TITLE, _("Mean"));
+  tab_text (self->t, 3, 2, TAB_CENTER | TAT_TITLE, _("Std. Deviation"));
+  tab_text (self->t, 4, 2, TAB_CENTER | TAT_TITLE, _("Std. Error Mean"));
+  tab_text (self->t, 5, 2, TAB_CENTER | TAT_TITLE, _("Lower"));
+  tab_text (self->t, 6, 2, TAB_CENTER | TAT_TITLE, _("Upper"));
+  tab_text (self->t, 7, 2, TAB_CENTER | TAT_TITLE, _("t"));
+  tab_text (self->t, 8, 2, TAB_CENTER | TAT_TITLE, _("df"));
+  tab_text (self->t, 9, 2, TAB_CENTER | TAT_TITLE, _("Sig. (2-tailed)"));
+}
+
+/* Populate the paired samples trbox */
+void 
+trbox_paired_populate(struct trbox *trb,
+                             struct cmd_t_test *cmd UNUSED)
+{
+  int i;
+
+  for (i=0; i < n_pairs; ++i)
+    {
+      double p,q;
+      double se_mean;
+
+      double n = pairs[i].n;
+      double t;
+      double df = n - 1;
+      
+      tab_text (trb->t, 0, i+3, TAB_LEFT | TAT_PRINTF, _("Pair %d"),i); 
+
+      tab_text (trb->t, 1, i+3, TAB_LEFT | TAT_PRINTF, "%s - %s",
+               pairs[i].v[0]->name, pairs[i].v[1]->name);
+
+      tab_float(trb->t, 2, i+3, TAB_RIGHT, pairs[i].mean_diff, 8, 4);
+
+      tab_float(trb->t, 3, i+3, TAB_RIGHT, pairs[i].std_dev_diff, 8, 5);
+
+      /* SE Mean */
+      se_mean = pairs[i].std_dev_diff / sqrt(n) ;
+      tab_float(trb->t, 4, i+3, TAB_RIGHT, se_mean, 8,5 );
+
+      /* Now work out the confidence interval */
+      q = (1 - cmd->criteria)/2.0;  /* 2-tailed test */
+
+      t = gsl_cdf_tdist_Qinv(q, df);
+
+      tab_float(trb->t, 5, i+3, TAB_RIGHT, 
+               pairs[i].mean_diff - t * se_mean , 8, 4); 
+
+      tab_float(trb->t, 6, i+3, TAB_RIGHT, 
+               pairs[i].mean_diff + t * se_mean , 8, 4); 
+
+      t = (pairs[i].mean[0] - pairs[i].mean[1])
+       / sqrt (
+               ( pow2 (pairs[i].s_std_dev[0]) + pow2 (pairs[i].s_std_dev[1]) -
+                 2 * pairs[i].correlation * 
+                 pairs[i].s_std_dev[0] * pairs[i].s_std_dev[1] )
+               / (n - 1)
+               );
+
+      tab_float(trb->t, 7, i+3, TAB_RIGHT, t , 8,3 );
+
+      /* Degrees of freedom */
+      tab_float(trb->t, 8, i+3, TAB_RIGHT, df , 2, 0 );
+
+      p = gsl_cdf_tdist_P(t,df);
+      q = gsl_cdf_tdist_P(t,df);
+
+      tab_float(trb->t, 9, i+3, TAB_RIGHT, 2.0*(t>0?q:p) , 8, 3);
+
+    }
+}
+
+/* Initialize the one sample trbox */
+void 
+trbox_one_sample_init(struct trbox *self, struct cmd_t_test *cmd )
+{
+  const int hsize=7;
+  const int vsize=cmd->n_variables+3;
+
+  self->populate = trbox_one_sample_populate;
+
+  trbox_base_init(self, cmd->n_variables,hsize);
+  tab_title (self->t, 0, _("One-Sample Test"));
+  tab_hline(self->t, TAL_1, 1, hsize - 1, 1);
+  tab_vline(self->t, TAL_2, 1, 0, vsize - 1);
+
+  tab_joint_text(self->t, 1, 0, hsize-1,0, TAB_CENTER | TAT_PRINTF, 
+                _("Test Value = %f"), cmd->n_testval[0]);
+
+  tab_box(self->t, -1, -1, -1, TAL_1, 1,1,hsize-1,vsize-1);
+
+
+  tab_joint_text(self->t,5,1,6,1,TAB_CENTER  | TAT_PRINTF, 
+                _("%g%% Confidence Interval of the Difference"),
+                cmd->criteria*100.0);
+
+  tab_vline(self->t,TAL_0,6,1,1);
+  tab_hline(self->t,TAL_1,5,6,2);
+  tab_text (self->t, 1, 2, TAB_CENTER | TAT_TITLE, _("t"));
+  tab_text (self->t, 2, 2, TAB_CENTER | TAT_TITLE, _("df"));
+  tab_text (self->t, 3, 2, TAB_CENTER | TAT_TITLE, _("Sig. (2-tailed)"));
+  tab_text (self->t, 4, 2, TAB_CENTER | TAT_TITLE, _("Mean Difference"));
+  tab_text (self->t, 5, 2, TAB_CENTER | TAT_TITLE, _("Lower"));
+  tab_text (self->t, 6, 2, TAB_CENTER | TAT_TITLE, _("Upper"));
+
+}
+
+
+/* Populate the one sample trbox */
+void 
+trbox_one_sample_populate(struct trbox *trb, struct cmd_t_test *cmd)
+{
+  int i;
+
+  assert(trb->t);
+
+  for (i=0; i < cmd->n_variables; ++i)
+    {
+      double t;
+      double p,q;
+      double df;
+      struct group_statistics *gs = &group_proc_get (cmd->v_variables[i])->ugs;
+
+
+      tab_text (trb->t, 0, i+3, TAB_LEFT, cmd->v_variables[i]->name);
+
+      t = (gs->mean - cmd->n_testval[0] ) * sqrt(gs->n) / gs->std_dev ;
+
+      tab_float (trb->t, 1, i+3, TAB_RIGHT, t, 8,3);
+
+      /* degrees of freedom */
+      df = gs->n - 1;
+
+      tab_float (trb->t, 2, i+3, TAB_RIGHT, df, 8,0);
+
+      p = gsl_cdf_tdist_P(t, df);
+      q = gsl_cdf_tdist_Q(t, df);
+
+      /* Multiply by 2 to get 2-tailed significance, makeing sure we've got 
+        the correct tail*/
+      tab_float (trb->t, 3, i+3, TAB_RIGHT, 2.0*(t>0?q:p), 8,3);
+
+      tab_float (trb->t, 4, i+3, TAB_RIGHT, gs->mean_diff, 8,3);
+
+
+      q = (1 - cmd->criteria)/2.0;  /* 2-tailed test */
+      t = gsl_cdf_tdist_Qinv(q, df);
+
+      tab_float (trb->t, 5, i+3, TAB_RIGHT,
+                gs->mean_diff - t * gs->se_mean, 8,4);
+
+      tab_float (trb->t, 6, i+3, TAB_RIGHT,
+                gs->mean_diff + t * gs->se_mean, 8,4);
+    }
+}
+
+/* Base initializer for the generalized trbox */
+void 
+trbox_base_init(struct trbox *self, size_t data_rows, int cols)
+{
+  const size_t rows = 3 + data_rows;
+
+  self->finalize = trbox_base_finalize;
+  self->t = tab_create (cols, rows, 0);
+  tab_headers (self->t,0,0,3,0); 
+  tab_box (self->t, TAL_2, TAL_2, TAL_0, TAL_0, 0, 0, cols -1, rows -1);
+  tab_hline(self->t, TAL_2,0,cols-1,3);
+  tab_dim (self->t, tab_natural_dimensions);
+}
+
+
+/* Base finalizer for the trbox */
+void 
+trbox_base_finalize(struct trbox *trb)
+{
+  tab_submit(trb->t);
+}
+
+
+/* Create , populate and submit the Paired Samples Correlation box */
+void
+pscbox(void)
+{
+  const int rows=1+n_pairs;
+  const int cols=5;
+  int i;
+  
+  struct tab_table *table;
+  
+  table = tab_create (cols,rows,0);
+
+  tab_columns (table, SOM_COL_DOWN, 1);
+  tab_headers (table,0,0,1,0); 
+  tab_box (table, TAL_2, TAL_2, TAL_0, TAL_1, 0, 0, cols -1, rows -1 );
+  tab_hline(table, TAL_2, 0, cols - 1, 1);
+  tab_vline(table, TAL_2, 2, 0, rows - 1);
+  tab_dim(table, tab_natural_dimensions);
+  tab_title(table, 0, _("Paired Samples Correlations"));
+
+  /* column headings */
+  tab_text(table, 2,0, TAB_CENTER | TAT_TITLE, _("N"));
+  tab_text(table, 3,0, TAB_CENTER | TAT_TITLE, _("Correlation"));
+  tab_text(table, 4,0, TAB_CENTER | TAT_TITLE, _("Sig."));
+
+  for (i=0; i < n_pairs; ++i)
+    {
+      double p,q;
+
+      double df = pairs[i].n -2;
+
+      double correlation_t = 
+       pairs[i].correlation * sqrt(df) /
+       sqrt(1 - pow2(pairs[i].correlation));
+
+
+      /* row headings */
+      tab_text(table, 0,i+1, TAB_LEFT | TAT_TITLE | TAT_PRINTF, 
+              _("Pair %d"), i);
+      
+      tab_text(table, 1,i+1, TAB_LEFT | TAT_TITLE | TAT_PRINTF, 
+              _("%s & %s"), pairs[i].v[0]->name, pairs[i].v[1]->name);
+
+
+      /* row data */
+      tab_float(table, 2, i+1, TAB_RIGHT, pairs[i].n, 4, 0);
+      tab_float(table, 3, i+1, TAB_RIGHT, pairs[i].correlation, 8, 3);
+
+      p = gsl_cdf_tdist_P(correlation_t, df);
+      q = gsl_cdf_tdist_Q(correlation_t, df);
+
+      tab_float(table, 4, i+1, TAB_RIGHT, 2.0*(correlation_t>0?q:p), 8, 3);
+    }
+
+  tab_submit(table);
+}
+
+
+
+
+/* Calculation Implementation */
+
+/* Per case calculations common to all variants of the T test */
+static int 
+common_calc (const struct ccase *c, void *_cmd)
+{
+  int i;
+  struct cmd_t_test *cmd = (struct cmd_t_test *)_cmd;  
+
+  double weight = dict_get_case_weight(default_dict,c,&bad_weight_warn);
+
+
+  /* Skip the entire case if /MISSING=LISTWISE is set */
+  if ( cmd->miss == TTS_LISTWISE ) 
+    {
+      for(i=0; i< cmd->n_variables ; ++i) 
+       {
+         struct variable *v = cmd->v_variables[i];
+         const union value *val = case_data (c, v->fv);
+
+         if (value_is_missing(&v->miss, val) )
+           {
+             return 0;
+           }
+       }
+    }
+
+  /* Listwise has to be implicit if the independent variable is missing ?? */
+  if ( cmd->sbc_groups )
+    {
+      const union value *gv = case_data (c, indep_var->fv);
+      if ( value_is_missing(&indep_var->miss, gv) )
+       {
+         return 0;
+       }
+    }
+
+
+  for(i=0; i< cmd->n_variables ; ++i) 
+    {
+      struct group_statistics *gs;
+      struct variable *v = cmd->v_variables[i];
+      const union value *val = case_data (c, v->fv);
+
+      gs= &group_proc_get (cmd->v_variables[i])->ugs;
+
+      if (! value_is_missing(&v->miss, val) )
+       {
+         gs->n+=weight;
+         gs->sum+=weight * val->f;
+         gs->ssq+=weight * val->f * val->f;
+       }
+    }
+  return 0;
+}
+
+/* Pre calculations common to all variants of the T test */
+static void 
+common_precalc ( struct cmd_t_test *cmd )
+{
+  int i=0;
+
+  for(i=0; i< cmd->n_variables ; ++i) 
+    {
+      struct group_statistics *gs;
+      gs= &group_proc_get (cmd->v_variables[i])->ugs;
+      
+      gs->sum=0;
+      gs->n=0;
+      gs->ssq=0;
+      gs->sum_diff=0;
+    }
+}
+
+/* Post calculations common to all variants of the T test */
+void 
+common_postcalc (  struct cmd_t_test *cmd )
+{
+  int i=0;
+
+
+  for(i=0; i< cmd->n_variables ; ++i) 
+    {
+      struct group_statistics *gs;
+      gs= &group_proc_get (cmd->v_variables[i])->ugs;
+      
+      gs->mean=gs->sum / gs->n;
+      gs->s_std_dev= sqrt(
+                        ( (gs->ssq / gs->n ) - gs->mean * gs->mean )
+                        ) ;
+
+      gs->std_dev= sqrt(
+                        gs->n/(gs->n-1) *
+                        ( (gs->ssq / gs->n ) - gs->mean * gs->mean )
+                        ) ;
+
+      gs->se_mean = gs->std_dev / sqrt(gs->n);
+      gs->mean_diff= gs->sum_diff / gs->n;
+    }
+}
+
+/* Per case calculations for one sample t test  */
+static int 
+one_sample_calc (const struct ccase *c, void *cmd_)
+{
+  int i;
+  struct cmd_t_test *cmd = (struct cmd_t_test *)cmd_;
+
+
+  double weight = dict_get_case_weight(default_dict,c,&bad_weight_warn);
+
+  /* Skip the entire case if /MISSING=LISTWISE is set */
+  if ( cmd->miss == TTS_LISTWISE ) 
+    {
+      for(i=0; i< cmd->n_variables ; ++i) 
+       {
+         struct variable *v = cmd->v_variables[i];
+         const union value *val = case_data (c, v->fv);
+
+         if (value_is_missing(&v->miss, val) )
+           {
+             return 0;
+           }
+       }
+    }
+
+  for(i=0; i< cmd->n_variables ; ++i) 
+    {
+      struct group_statistics *gs;
+      struct variable *v = cmd->v_variables[i];
+      const union value *val = case_data (c, v->fv);
+
+      gs= &group_proc_get (cmd->v_variables[i])->ugs;
+      
+      if ( ! value_is_missing(&v->miss, val))
+       gs->sum_diff += weight * (val->f - cmd->n_testval[0]);
+    }
+
+  return 0;
+}
+
+/* Pre calculations for one sample t test */
+static void 
+one_sample_precalc ( struct cmd_t_test *cmd )
+{
+  int i=0; 
+  for(i=0; i< cmd->n_variables ; ++i) 
+    {
+      struct group_statistics *gs;
+      gs= &group_proc_get (cmd->v_variables[i])->ugs;
+      
+      gs->sum_diff=0;
+    }
+}
+
+/* Post calculations for one sample t test */
+static void 
+one_sample_postcalc (struct cmd_t_test *cmd)
+{
+  int i=0;
+  
+  for(i=0; i< cmd->n_variables ; ++i) 
+    {
+      struct group_statistics *gs;
+      gs= &group_proc_get (cmd->v_variables[i])->ugs;
+
+      gs->mean_diff = gs->sum_diff / gs->n ;
+    }
+}
+
+
+
+static void 
+paired_precalc (struct cmd_t_test *cmd UNUSED)
+{
+  int i;
+
+  for(i=0; i < n_pairs ; ++i )
+    {
+      pairs[i].n = 0;
+      pairs[i].sum[0] = 0;      pairs[i].sum[1] = 0;
+      pairs[i].ssq[0] = 0;      pairs[i].ssq[1] = 0;
+      pairs[i].sum_of_prod = 0;
+      pairs[i].correlation = 0;
+      pairs[i].sum_of_diffs = 0;
+      pairs[i].ssq_diffs = 0;
+    }
+
+}
+
+
+static int  
+paired_calc (const struct ccase *c, void *cmd_)
+{
+  int i;
+
+  struct cmd_t_test *cmd  = (struct cmd_t_test *) cmd_;
+
+  double weight = dict_get_case_weight(default_dict,c,&bad_weight_warn);
+
+  /* Skip the entire case if /MISSING=LISTWISE is set , 
+   AND one member of a pair is missing */
+  if ( cmd->miss == TTS_LISTWISE ) 
+    {
+      for(i=0; i < n_pairs ; ++i )
+       {
+         struct variable *v0 = pairs[i].v[0];
+         struct variable *v1 = pairs[i].v[1];
+
+         const union value *val0 = case_data (c, v0->fv);
+         const union value *val1 = case_data (c, v1->fv);
+         
+         if ( value_is_missing(&v0->miss, val0) ||
+              value_is_missing(&v1->miss, val1) )
+           {
+             return 0;
+           }
+       }
+    }
+
+  for(i=0; i < n_pairs ; ++i )
+    {
+      struct variable *v0 = pairs[i].v[0];
+      struct variable *v1 = pairs[i].v[1];
+
+      const union value *val0 = case_data (c, v0->fv);
+      const union value *val1 = case_data (c, v1->fv);
+
+      if ( ( !value_is_missing(&v0->miss, val0)
+             && !value_is_missing(&v1->miss, val1) ) )
+      {
+       pairs[i].n += weight;
+       pairs[i].sum[0] += weight * val0->f;
+       pairs[i].sum[1] += weight * val1->f;
+
+       pairs[i].ssq[0] += weight * pow2(val0->f);
+       pairs[i].ssq[1] += weight * pow2(val1->f);
+
+       pairs[i].sum_of_prod += weight * val0->f * val1->f ;
+
+       pairs[i].sum_of_diffs += weight * ( val0->f - val1->f ) ;
+       pairs[i].ssq_diffs += weight * pow2(val0->f - val1->f);
+      }
+    }
+
+  return 0;
+}
+
+static void 
+paired_postcalc (struct cmd_t_test *cmd UNUSED)
+{
+  int i;
+
+  for(i=0; i < n_pairs ; ++i )
+    {
+      int j;
+      const double n = pairs[i].n;
+
+      for (j=0; j < 2 ; ++j) 
+       {
+         pairs[i].mean[j] = pairs[i].sum[j] / n ;
+         pairs[i].s_std_dev[j] = sqrt((pairs[i].ssq[j] / n - 
+                                             pow2(pairs[i].mean[j]))
+                                    );
+
+         pairs[i].std_dev[j] = sqrt(n/(n-1)*(pairs[i].ssq[j] / n - 
+                                             pow2(pairs[i].mean[j]))
+                                    );
+       }
+      
+      pairs[i].correlation = pairs[i].sum_of_prod / pairs[i].n - 
+       pairs[i].mean[0] * pairs[i].mean[1] ;
+      /* correlation now actually contains the covariance */
+      
+      pairs[i].correlation /= pairs[i].std_dev[0] * pairs[i].std_dev[1];
+      pairs[i].correlation *= pairs[i].n / ( pairs[i].n - 1 );
+      
+      pairs[i].mean_diff = pairs[i].sum_of_diffs / n ;
+
+      pairs[i].std_dev_diff = sqrt (  n / (n - 1) * (
+                                   ( pairs[i].ssq_diffs / n )
+                                   - 
+                                   pow2(pairs[i].mean_diff )
+                                   ) );
+    }
+}
+
+static void 
+group_precalc (struct cmd_t_test *cmd )
+{
+  int i;
+  int j;
+
+  for(i=0; i< cmd->n_variables ; ++i) 
+    {
+      struct group_proc *ttpr = group_proc_get (cmd->v_variables[i]);
+
+      /* There's always 2 groups for a T - TEST */
+      ttpr->n_groups = 2;
+
+      gp.indep_width = indep_var->width;
+      
+      ttpr->group_hash = hsh_create(2, 
+                                   (hsh_compare_func *) compare_group_binary,
+                                   (hsh_hash_func *) hash_group_binary,
+                                   (hsh_free_func *) free_group,
+                                   (void *) &gp );
+
+      for (j=0 ; j < 2 ; ++j)
+       {
+
+         struct group_statistics *gs = xmalloc (sizeof *gs);
+
+         gs->sum = 0;
+         gs->n = 0;
+         gs->ssq = 0;
+       
+         if ( gp.criterion == CMP_EQ ) 
+           {
+             gs->id = gp.v.g_value[j];
+           }
+         else
+           {
+             if ( j == 0 ) 
+               gs->id.f = gp.v.critical_value - 1.0 ;
+             else
+               gs->id.f = gp.v.critical_value + 1.0 ;
+           }
+         
+         hsh_insert ( ttpr->group_hash, (void *) gs );
+
+       }
+    }
+
+}
+
+static int  
+group_calc (const struct ccase *c, struct cmd_t_test *cmd)
+{
+  int i;
+
+  const union value *gv = case_data (c, indep_var->fv);
+
+  const double weight = dict_get_case_weight(default_dict,c,&bad_weight_warn);
+
+  if ( value_is_missing(&indep_var->miss, gv) )
+    {
+      return 0;
+    }
+
+  if ( cmd->miss == TTS_LISTWISE ) 
+    {
+      for(i=0; i< cmd->n_variables ; ++i) 
+       {
+         struct variable *v = cmd->v_variables[i];
+         const union value *val = case_data (c, v->fv);
+
+         if (value_is_missing(&v->miss, val) )
+           {
+             return 0;
+           }
+       }
+    }
+
+  gv = case_data (c, indep_var->fv);
+
+  for(i=0; i< cmd->n_variables ; ++i) 
+    {
+      struct variable *var = cmd->v_variables[i];
+      const union value *val = case_data (c, var->fv);
+      struct hsh_table *grp_hash = group_proc_get (var)->group_hash;
+      struct group_statistics *gs;
+
+      gs = hsh_find(grp_hash, (void *) gv);
+
+      /* If the independent variable doesn't match either of the values 
+         for this case then move on to the next case */
+      if ( ! gs ) 
+       return 0;
+
+      if ( !value_is_missing(&var->miss, val) )
+       {
+         gs->n+=weight;
+         gs->sum+=weight * val->f;
+         gs->ssq+=weight * pow2(val->f);
+       }
+    }
+
+  return 0;
+}
+
+
+static void 
+group_postcalc ( struct cmd_t_test *cmd )
+{
+  int i;
+
+  for(i=0; i< cmd->n_variables ; ++i) 
+    {
+      struct variable *var = cmd->v_variables[i];
+      struct hsh_table *grp_hash = group_proc_get (var)->group_hash;
+      struct hsh_iterator g;
+      struct group_statistics *gs;
+      int count=0;
+
+      for (gs =  hsh_first (grp_hash,&g); 
+          gs != 0; 
+          gs = hsh_next(grp_hash,&g))
+       {
+         gs->mean = gs->sum / gs->n;
+         
+         gs->s_std_dev= sqrt(
+                             ( (gs->ssq / gs->n ) - gs->mean * gs->mean )
+                             ) ;
+
+         gs->std_dev= sqrt(
+                           gs->n/(gs->n-1) *
+                           ( (gs->ssq / gs->n ) - gs->mean * gs->mean )
+                           ) ;
+         
+         gs->se_mean = gs->std_dev / sqrt(gs->n);
+         count ++;
+       }
+      assert(count == 2);
+    }
+}
+
+
+
+static bool
+calculate(const struct casefile *cf, void *cmd_)
+{
+  struct ssbox stat_summary_box;
+  struct trbox test_results_box;
+
+  struct casereader *r;
+  struct ccase c;
+
+  struct cmd_t_test *cmd = (struct cmd_t_test *) cmd_;
+
+  common_precalc(cmd);
+  for(r = casefile_get_reader (cf);
+      casereader_read (r, &c) ;
+      case_destroy (&c)) 
+    {
+      common_calc(&c,cmd);
+    }
+  casereader_destroy (r);
+  common_postcalc(cmd);
+
+  switch(mode)
+    {
+    case T_1_SAMPLE:
+      one_sample_precalc(cmd);
+      for(r = casefile_get_reader (cf);
+         casereader_read (r, &c) ;
+          case_destroy (&c)) 
+       {
+         one_sample_calc(&c,cmd);
+       }
+      casereader_destroy (r);
+      one_sample_postcalc(cmd);
+
+      break;
+    case T_PAIRED:
+      paired_precalc(cmd);
+      for(r = casefile_get_reader (cf);
+         casereader_read (r, &c) ;
+          case_destroy (&c)) 
+       {
+         paired_calc(&c,cmd);
+       }
+      casereader_destroy (r);
+      paired_postcalc(cmd);
+
+      break;
+    case T_IND_SAMPLES:
+
+      group_precalc(cmd);
+      for(r = casefile_get_reader (cf);
+         casereader_read (r, &c) ;
+          case_destroy (&c)) 
+       {
+         group_calc(&c,cmd);
+       }
+      casereader_destroy (r);
+      group_postcalc(cmd);
+
+      levene(cf, indep_var, cmd->n_variables, cmd->v_variables,
+            (cmd->miss == TTS_LISTWISE)?LEV_LISTWISE:LEV_ANALYSIS ,
+            value_is_missing);
+      break;
+    }
+
+  ssbox_create(&stat_summary_box,cmd,mode);
+  ssbox_populate(&stat_summary_box,cmd);
+  ssbox_finalize(&stat_summary_box);
+
+  if ( mode == T_PAIRED) 
+      pscbox();
+
+  trbox_create(&test_results_box,cmd,mode);
+  trbox_populate(&test_results_box,cmd);
+  trbox_finalize(&test_results_box);
+
+  return true;
+}
+
+short which_group(const struct group_statistics *g,
+                 const struct group_properties *p);
+
+/* Return -1 if the id of a is less than b; +1 if greater than and 
+   0 if equal */
+static int 
+compare_group_binary(const struct group_statistics *a, 
+                    const struct group_statistics *b, 
+                    const struct group_properties *p)
+{
+  short flag_a;
+  short flag_b;
+  
+  if ( p->criterion == CMP_LE ) 
+    {
+      /* less-than-or-equal comparision is not meaningfull for
+        alpha variables, so we shouldn't ever arrive here */
+      assert(p->indep_width == 0 ) ;
+      
+      flag_a = ( a->id.f < p->v.critical_value ) ;
+      flag_b = ( b->id.f < p->v.critical_value ) ;
+    }
+  else
+    {
+      flag_a = which_group(a, p);
+      flag_b = which_group(b, p);
+    }
+
+  if (flag_a < flag_b ) 
+    return -1;
+
+  return (flag_a > flag_b);
+}
+
+/* This is a degenerate case of a hash, since it can only return three possible
+   values.  It's really a comparison, being used as a hash function */
+
+static unsigned 
+hash_group_binary(const struct group_statistics *g, 
+                 const struct group_properties *p)
+{
+  short flag = -1;
+
+  if ( p->criterion == CMP_LE ) 
+    {
+      /* Not meaningfull to do a less than compare for alpha values ? */
+      assert(p->indep_width == 0 ) ;
+      flag = ( g->id.f < p->v.critical_value ) ; 
+    }
+  else if ( p->criterion == CMP_EQ) 
+    {
+      flag = which_group(g,p);
+    }
+  else
+    assert(0);
+
+  return flag;
+}
+
+/* return 0 if G belongs to group 0, 
+          1 if it belongs to group 1,
+         2 if it belongs to neither group */
+short
+which_group(const struct group_statistics *g,
+           const struct group_properties *p)
+{
+  if ( 0 == compare_values (&g->id, &p->v.g_value[0], p->indep_width))
+    return 0;
+
+  if ( 0 == compare_values (&g->id, &p->v.g_value[1], p->indep_width))
+    return 1;
+
+  return 2;
+}
+           
diff --git a/src/language/tests/ChangeLog b/src/language/tests/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/language/tests/casefile-test.c b/src/language/tests/casefile-test.c
new file mode 100644 (file)
index 0000000..4a0c699
--- /dev/null
@@ -0,0 +1,213 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "casefile.h"
+#include "case.h"
+
+#include <gsl/gsl_rng.h>
+#include <stdarg.h>
+#include "command.h"
+#include "lexer.h"
+
+static void test_casefile (int pattern, size_t value_cnt, size_t case_cnt);
+static void get_random_case (struct ccase *, size_t value_cnt,
+                             size_t case_idx);
+static void write_random_case (struct casefile *cf, size_t case_idx);
+static void read_and_verify_random_case (struct casefile *cf,
+                                         struct casereader *reader,
+                                         size_t case_idx);
+static void fail_test (const char *message, ...);
+
+int
+cmd_debug_casefile (void) 
+{
+  static const size_t sizes[] =
+    {
+      1, 2, 3, 4, 5, 6, 7, 14, 15, 16, 17, 31, 55, 73,
+      100, 137, 257, 521, 1031, 2053
+    };
+  int size_max;
+  int case_max;
+  int pattern;
+
+  size_max = sizeof sizes / sizeof *sizes;
+  if (lex_match_id ("SMALL")) 
+    {
+      size_max -= 4;
+      case_max = 511; 
+    }
+  else
+    case_max = 4095;
+  if (token != '.')
+    return lex_end_of_command ();
+    
+  for (pattern = 0; pattern < 6; pattern++) 
+    {
+      const size_t *size;
+
+      for (size = sizes; size < sizes + size_max; size++) 
+        {
+          size_t case_cnt;
+
+          for (case_cnt = 0; case_cnt <= case_max;
+               case_cnt = (case_cnt * 2) + 1)
+            test_casefile (pattern, *size, case_cnt);
+        }
+    }
+  printf ("Casefile tests succeeded.\n");
+  return CMD_SUCCESS;
+}
+
+static void
+test_casefile (int pattern, size_t value_cnt, size_t case_cnt) 
+{
+  struct casefile *cf;
+  struct casereader *r1, *r2;
+  struct ccase c;
+  gsl_rng *rng;
+  size_t i, j;
+
+  rng = gsl_rng_alloc (gsl_rng_mt19937);
+  cf = casefile_create (value_cnt);
+  if (pattern == 5)
+    casefile_to_disk (cf);
+  for (i = 0; i < case_cnt; i++)
+    write_random_case (cf, i);
+  if (pattern == 5)
+    casefile_sleep (cf);
+  r1 = casefile_get_reader (cf);
+  r2 = casefile_get_reader (cf);
+  switch (pattern) 
+    {
+    case 0:
+    case 5:
+      for (i = 0; i < case_cnt; i++) 
+        {
+          read_and_verify_random_case (cf, r1, i);
+          read_and_verify_random_case (cf, r2, i);
+        } 
+      break;
+    case 1:
+      for (i = 0; i < case_cnt; i++)
+        read_and_verify_random_case (cf, r1, i);
+      for (i = 0; i < case_cnt; i++) 
+        read_and_verify_random_case (cf, r2, i);
+      break;
+    case 2:
+    case 3:
+    case 4:
+      for (i = j = 0; i < case_cnt; i++) 
+        {
+          read_and_verify_random_case (cf, r1, i);
+          if (gsl_rng_get (rng) % pattern == 0) 
+            read_and_verify_random_case (cf, r2, j++); 
+          if (i == case_cnt / 2)
+            casefile_to_disk (cf);
+        }
+      for (; j < case_cnt; j++) 
+        read_and_verify_random_case (cf, r2, j);
+      break;
+    }
+  if (casereader_read (r1, &c))
+    fail_test ("Casereader 1 not at end of file.");
+  if (casereader_read (r2, &c))
+    fail_test ("Casereader 2 not at end of file.");
+  if (pattern != 1)
+    casereader_destroy (r1);
+  if (pattern != 2)
+    casereader_destroy (r2);
+  if (pattern > 2) 
+    {
+      r1 = casefile_get_destructive_reader (cf);
+      for (i = 0; i < case_cnt; i++) 
+        {
+          struct ccase read_case, expected_case;
+          
+          get_random_case (&expected_case, value_cnt, i);
+          if (!casereader_read_xfer (r1, &read_case)) 
+            fail_test ("Premature end of casefile.");
+          for (j = 0; j < value_cnt; j++) 
+            {
+              double a = case_num (&read_case, j);
+              double b = case_num (&expected_case, j);
+              if (a != b)
+                fail_test ("Case %lu fails comparison.", (unsigned long) i); 
+            }
+          case_destroy (&expected_case);
+          case_destroy (&read_case);
+        }
+      casereader_destroy (r1);
+    }
+  casefile_destroy (cf);
+  gsl_rng_free (rng);
+}
+
+static void
+get_random_case (struct ccase *c, size_t value_cnt, size_t case_idx) 
+{
+  int i;
+  case_create (c, value_cnt);
+  for (i = 0; i < value_cnt; i++)
+    case_data_rw (c, i)->f = case_idx % 257 + i;
+}
+
+static void
+write_random_case (struct casefile *cf, size_t case_idx) 
+{
+  struct ccase c;
+  get_random_case (&c, casefile_get_value_cnt (cf), case_idx);
+  casefile_append_xfer (cf, &c);
+}
+
+static void
+read_and_verify_random_case (struct casefile *cf,
+                             struct casereader *reader, size_t case_idx) 
+{
+  struct ccase read_case, expected_case;
+  size_t value_cnt;
+  size_t i;
+  
+  value_cnt = casefile_get_value_cnt (cf);
+  get_random_case (&expected_case, value_cnt, case_idx);
+  if (!casereader_read (reader, &read_case)) 
+    fail_test ("Premature end of casefile.");
+  for (i = 0; i < value_cnt; i++) 
+    {
+      double a = case_num (&read_case, i);
+      double b = case_num (&expected_case, i);
+      if (a != b)
+        fail_test ("Case %lu fails comparison.", (unsigned long) case_idx); 
+    }
+  case_destroy (&read_case);
+  case_destroy (&expected_case);
+}
+
+static void
+fail_test (const char *message, ...) 
+{
+  va_list args;
+
+  va_start (args, message);
+  vprintf (message, args);
+  putchar ('\n');
+  va_end (args);
+  
+  exit (1);
+}
diff --git a/src/language/tests/moments-test.c b/src/language/tests/moments-test.c
new file mode 100644 (file)
index 0000000..c038a63
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdio.h>
+#include "gettext.h"
+#include "command.h"
+#include "lexer.h"
+#include "moments.h"
+
+#define _(msgid) gettext (msgid)
+
+static int
+read_values (double **values, double **weights, size_t *cnt) 
+{
+  size_t cap = 0;
+
+  *values = NULL;
+  *weights = NULL;
+  *cnt = 0;
+  while (lex_is_number ())
+    {
+      double value = tokval;
+      double weight = 1.;
+      lex_get ();
+      if (lex_match ('*'))
+        {
+          if (!lex_is_number ())
+            {
+              lex_error (_("expecting weight value"));
+              return 0;
+            }
+          weight = tokval;
+          lex_get ();
+        }
+
+      if (*cnt >= cap) 
+        {
+          cap = 2 * (cap + 8);
+          *values = xnrealloc (*values, cap, sizeof **values);
+          *weights = xnrealloc (*weights, cap, sizeof **weights);
+        }
+
+      (*values)[*cnt] = value;
+      (*weights)[*cnt] = weight;
+      (*cnt)++;
+    }
+
+  return 1;
+}
+
+int
+cmd_debug_moments (void) 
+{
+  int retval = CMD_FAILURE;
+  double *values = NULL;
+  double *weights = NULL;
+  double weight, M[4];
+  int two_pass = 1;
+  size_t cnt;
+  size_t i;
+
+  if (lex_match_id ("ONEPASS"))
+    two_pass = 0;
+  if (token != '/') 
+    {
+      lex_force_match ('/');
+      goto done;
+    }
+  fprintf (stderr, "%s => ", lex_rest_of_line (NULL));
+  lex_get ();
+
+  if (two_pass) 
+    {
+      struct moments *m = NULL;
+  
+      m = moments_create (MOMENT_KURTOSIS);
+      if (!read_values (&values, &weights, &cnt)) 
+        {
+          moments_destroy (m);
+          goto done; 
+        }
+      for (i = 0; i < cnt; i++)
+        moments_pass_one (m, values[i], weights[i]); 
+      for (i = 0; i < cnt; i++)
+        moments_pass_two (m, values[i], weights[i]);
+      moments_calculate (m, &weight, &M[0], &M[1], &M[2], &M[3]);
+      moments_destroy (m);
+    }
+  else 
+    {
+      struct moments1 *m = NULL;
+  
+      m = moments1_create (MOMENT_KURTOSIS);
+      if (!read_values (&values, &weights, &cnt)) 
+        {
+          moments1_destroy (m);
+          goto done; 
+        }
+      for (i = 0; i < cnt; i++)
+        moments1_add (m, values[i], weights[i]);
+      moments1_calculate (m, &weight, &M[0], &M[1], &M[2], &M[3]);
+      moments1_destroy (m);
+    }
+  
+  fprintf (stderr, "W=%.3f", weight);
+  for (i = 0; i < 4; i++) 
+    {
+      fprintf (stderr, " M%d=", i + 1);
+      if (M[i] == SYSMIS)
+        fprintf (stderr, "sysmis");
+      else if (fabs (M[i]) <= 0.0005)
+        fprintf (stderr, "0.000");
+      else
+        fprintf (stderr, "%.3f", M[i]);
+    }
+  fprintf (stderr, "\n");
+
+  retval = lex_end_of_command ();
+  
+ done:
+  free (values);
+  free (weights);
+  return retval;
+}
diff --git a/src/language/tests/pool-test.c b/src/language/tests/pool-test.c
new file mode 100644 (file)
index 0000000..86d50fc
--- /dev/null
@@ -0,0 +1,119 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "pool.h"
+#include "command.h"
+#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
+cmd_debug_pool (void)
+{
+  int 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');
+    }
+
+  return CMD_SUCCESS;
+}
+
diff --git a/src/language/utilities/ChangeLog b/src/language/utilities/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/language/utilities/date.c b/src/language/utilities/date.c
new file mode 100644 (file)
index 0000000..aa6cb22
--- /dev/null
@@ -0,0 +1,37 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "command.h"
+#include "message.h"
+#include "lexer.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* Stub for USE command. */
+int
+cmd_use (void) 
+{
+  if (lex_match (T_ALL))
+    return lex_end_of_command ();
+
+  msg (SW, _("Only USE ALL is currently implemented."));
+  return CMD_FAILURE;
+}
diff --git a/src/language/utilities/echo.c b/src/language/utilities/echo.c
new file mode 100644 (file)
index 0000000..33d569f
--- /dev/null
@@ -0,0 +1,49 @@
+/* PSPP - computes sample statistics. -*-c-*-
+
+   Copyright (C) 2005 Free Software Foundation, Inc.
+   Written by John Darrington 2005
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include "alloc.h"
+#include "str.h"
+#include "lexer.h"
+#include "command.h"
+#include "table.h"
+#include "manager.h"
+
+/* Echos a string to the output stream */
+int
+cmd_echo(void)
+{
+  struct tab_table *tab;
+
+  if (token != T_STRING) 
+    return CMD_FAILURE;
+  
+  tab = tab_create(1, 1, 0);
+
+  tab_dim (tab, tab_natural_dimensions);
+  tab_flags (tab, SOMF_NO_TITLE );
+
+  tab_text(tab, 0, 0, 0, tokstr.string);
+
+  tab_submit(tab);
+
+  return CMD_SUCCESS;
+}
diff --git a/src/language/utilities/include.c b/src/language/utilities/include.c
new file mode 100644 (file)
index 0000000..aab817c
--- /dev/null
@@ -0,0 +1,50 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "message.h"
+#include "line-buffer.h"
+#include "lexer.h"
+#include "str.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+int
+cmd_include (void)
+{
+  /* Skip optional FILE=. */
+  if (lex_match_id ("FILE"))
+    lex_match ('=');
+
+  /* Filename can be identifier or string. */
+  if (token != T_ID && token != T_STRING) 
+    {
+      lex_error (_("expecting filename")); 
+      return CMD_CASCADING_FAILURE;
+    }
+  getl_include_syntax_file (ds_c_str (&tokstr));
+
+  lex_get ();
+  return lex_end_of_command ();
+}
diff --git a/src/language/utilities/permissions.c b/src/language/utilities/permissions.c
new file mode 100644 (file)
index 0000000..05a503c
--- /dev/null
@@ -0,0 +1,128 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 Free Software Foundation, Inc.
+   Author: John Darrington
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include "settings.h"
+#include "command.h"
+#include "message.h"
+#include "lexer.h"
+#include "misc.h"
+#include "stat-macros.h"
+#include "str.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+enum PER {PER_RO, PER_RW};
+
+int change_permissions(const char *filename, enum PER per);
+
+
+/* Parses the PERMISSIONS command. */
+int
+cmd_permissions (void)
+{
+  char  *fn = 0;
+
+  lex_match ('/');
+
+  if (lex_match_id ("FILE"))
+    lex_match ('=');
+
+  fn = strdup(ds_c_str(&tokstr));
+  lex_force_match(T_STRING);
+
+
+  lex_match ('/');
+  
+  if ( ! lex_match_id ("PERMISSIONS"))
+    goto error;
+
+  lex_match('=');
+
+  if ( lex_match_id("READONLY"))
+    {
+      if ( ! change_permissions(fn, PER_RO ) ) 
+       goto error;
+    }
+  else if ( lex_match_id("WRITEABLE"))
+    {
+      if ( ! change_permissions(fn, PER_RW ) ) 
+       goto error;
+    }
+  else
+    {
+      msg(ME, _("Expecting %s or %s."), "WRITEABLE", "READONLY");
+      goto error;
+    }
+
+  free(fn);
+
+  return CMD_SUCCESS;
+
+ error:
+
+  free(fn);
+
+  return CMD_FAILURE;
+}
+
+
+
+int
+change_permissions(const char *filename, enum PER per)
+{
+  struct stat buf;
+  mode_t mode;
+
+  if (get_safer_mode ())
+    {
+      msg (SE, _("This command not allowed when the SAFER option is set."));
+      return CMD_FAILURE;
+    }
+
+
+  if ( -1 == stat(filename, &buf) ) 
+    {
+      const int errnum = errno;
+      msg(ME,_("Cannot stat %s: %s"), filename, strerror(errnum));
+      return 0;
+    }
+
+  if ( per == PER_RW )
+    mode = buf.st_mode | S_IWUSR ;
+  else
+    mode = buf.st_mode & ~( S_IWOTH | S_IWUSR | S_IWGRP );
+
+  if ( -1 == chmod(filename, mode))
+
+    {
+      const int errnum = errno;
+      msg(ME,_("Cannot change mode of %s: %s"), filename, strerror(errnum));
+      return 0;
+    }
+
+  return 1;
+}
diff --git a/src/language/utilities/set.q b/src/language/utilities/set.q
new file mode 100644 (file)
index 0000000..b6b3963
--- /dev/null
@@ -0,0 +1,723 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "settings.h"
+#include "message.h"
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <time.h>
+#include "alloc.h"
+#include "command.h"
+#include "dictionary.h"
+#include "line-buffer.h"
+#include "lexer.h"
+#include "message.h"
+#include "magic.h"
+#include "output.h"
+#include "random.h"
+#include "variable.h"
+#include "format.h"
+#include "copyleft.h"
+#include "variable.h"
+
+
+#if HAVE_LIBTERMCAP
+#if HAVE_TERMCAP_H
+#include <termcap.h>
+#else /* !HAVE_TERMCAP_H */
+int tgetent (char *, const char *);
+int tgetnum (const char *);
+#endif /* !HAVE_TERMCAP_H */
+#endif /* !HAVE_LIBTERMCAP */
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* (specification)
+   "SET" (stc_):
+     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;
+     compression=compress:on/off;
+     cpi=integer "x>0" "%s must be greater than 0";
+     cprompt=string;
+     decimal=dec:dot/comma;
+     disk=custom;
+     dprompt=string;
+     echo=echo:on/off;
+     endcmd=string "x==1" "one character long";
+     epoch=custom;
+     errorbreak=errbrk:on/off;
+     errors=errors:on/off/terminal/listing/both/none;
+     format=custom;
+     headers=headers:no/yes/blank;
+     highres=hires:on/off;
+     histogram=string "x==1" "one character long";
+     include=inc:on/off;
+     journal=custom;
+     length=custom;
+     listing=custom;
+     lowres=lores:auto/on/off;
+     lpi=integer "x>0" "%s must be greater than 0";
+     menus=menus:standard/extended;
+     messages=messages:on/off/terminal/listing/both/none;
+     mexpand=mexp:on/off;
+     miterate=integer "x>0" "%s must be greater than 0";
+     mnest=integer "x>0" "%s must be greater than 0";
+     mprint=mprint:on/off;
+     mxerrs=integer "x >= 1" "%s must be at least 1";
+     mxloops=integer "x >=1" "%s must be at least 1";
+     mxmemory=integer;
+     mxwarns=integer;
+     nulline=null:on/off;
+     printback=prtbck:on/off;
+     prompt=string;
+     results=res:on/off/terminal/listing/both/none;
+     safer=safe:on;
+     scompression=scompress: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;
+     width=custom;
+     workspace=integer "x>=1024" "%s must be at least 1 MB";
+     xsort=xsort:yes/no.
+*/
+
+/* (declarations) */
+
+/* (_functions) */
+
+static bool do_cc (const char *cc_string, int idx);
+
+int
+cmd_set (void)
+{
+  struct cmd_set cmd;
+  bool ok = true;
+
+  if (!parse_set (&cmd))
+    return CMD_FAILURE;
+
+  if (cmd.sbc_cca)
+    ok = ok && do_cc (cmd.s_cca, 0);
+  if (cmd.sbc_ccb)
+    ok = ok && do_cc (cmd.s_ccb, 1);
+  if (cmd.sbc_ccc)
+    ok = ok && do_cc (cmd.s_ccc, 2);
+  if (cmd.sbc_ccd)
+    ok = ok && do_cc (cmd.s_ccd, 3);
+  if (cmd.sbc_cce)
+    ok = ok && do_cc (cmd.s_cce, 4);
+
+  if (cmd.sbc_prompt)
+    getl_set_prompt (GETL_PROMPT_FIRST, cmd.s_prompt);
+  if (cmd.sbc_cprompt)
+    getl_set_prompt (GETL_PROMPT_LATER, cmd.s_cprompt);
+  if (cmd.sbc_dprompt)
+    getl_set_prompt (GETL_PROMPT_DATA, cmd.s_dprompt);
+
+  if (cmd.sbc_decimal)
+    set_decimal (cmd.dec == STC_DOT ? '.' : ',');
+  if (cmd.sbc_echo)
+    set_echo (cmd.echo == STC_ON);
+  if (cmd.sbc_endcmd)
+    set_endcmd (cmd.s_endcmd[0]);
+  if (cmd.sbc_errorbreak)
+    set_errorbreak (cmd.errbrk == STC_ON);
+  if (cmd.sbc_include)
+    set_include (cmd.inc == STC_ON);
+  if (cmd.sbc_mxerrs)
+    set_mxerrs (cmd.n_mxerrs[0]);
+  if (cmd.sbc_mxwarns)
+    set_mxwarns (cmd.n_mxwarns[0]);
+  if (cmd.sbc_nulline)
+    set_nulline (cmd.null == STC_ON);
+  if (cmd.sbc_safer)
+    set_safer_mode ();
+  if (cmd.sbc_scompression)
+    set_scompression (cmd.scompress == STC_ON);
+  if (cmd.sbc_undefined)
+    set_undefined (cmd.undef == STC_WARN);
+  if (cmd.sbc_workspace)
+    set_workspace (cmd.n_workspace[0] * 1024L);
+
+  if (cmd.sbc_block)
+    msg (SW, _("%s is obsolete."),"BLOCK");
+  if (cmd.sbc_boxstring)
+    msg (SW, _("%s is obsolete."),"BOXSTRING");
+  if (cmd.sbc_histogram)
+    msg (MW, _("%s is obsolete."),"HISTOGRAM");
+  if (cmd.sbc_menus )
+    msg (MW, _("%s is obsolete."),"MENUS");
+  if (cmd.sbc_xsort )
+    msg (SW, _("%s is obsolete."),"XSORT");
+  if (cmd.sbc_mxmemory )
+    msg (SE, _("%s is obsolete."),"MXMEMORY");
+  if (cmd.sbc_scripttab)
+    msg (SE, _("%s is obsolete."),"SCRIPTTAB");
+  if (cmd.sbc_tbfonts)
+    msg (SW, _("%s is obsolete."),"TBFONTS");
+  if (cmd.sbc_tb1 && cmd.s_tb1)
+    msg (SW, _("%s is obsolete."),"TB1");
+
+  if (cmd.sbc_case)
+    msg (SW, _("%s is not implemented."), "CASE");
+
+  if (cmd.sbc_compression)
+    msg (MW, _("Active file compression is not implemented."));
+
+  return CMD_SUCCESS;
+}
+
+/* Find the grouping characters in CC_STRING and set CC's
+   grouping and decimal members appropriately.  Returns true if
+   successful, false otherwise. */
+static bool
+find_cc_separators (const char *cc_string, struct custom_currency *cc)
+{
+  const char *sp;
+  int comma_cnt, dot_cnt;
+  
+  /* Count commas and periods.  There must be exactly three of
+     one or the other, except that an apostrophe acts escapes a
+     following comma or period. */
+  comma_cnt = dot_cnt = 0;
+  for (sp = cc_string; *sp; sp++)
+    if (*sp == ',')
+      comma_cnt++;
+    else if (*sp == '.')
+      dot_cnt++;
+    else if (*sp == '\'' && (sp[1] == '.' || sp[1] == ',' || sp[1] == '\''))
+      sp++;
+  
+  if ((comma_cnt == 3) == (dot_cnt == 3))
+    return false;
+
+  if (comma_cnt == 3)
+    {
+      cc->decimal = '.';
+      cc->grouping = ',';
+    }
+  else
+    {
+      cc->decimal = ',';
+      cc->grouping = '.';
+    }
+  return true;
+}
+
+/* Extracts a token from IN into TOKEn.  Tokens are delimited by
+   GROUPING.  The token is truncated to at most CC_WIDTH
+   characters (including null terminator).  Returns the first
+   character following the token. */
+static const char *
+extract_cc_token (const char *in, int grouping, char token[CC_WIDTH]) 
+{
+  char *out = token;
+  
+  for (; *in != '\0' && *in != grouping; in++) 
+    {
+      if (*in == '\'' && in[1] == grouping)
+        in++;
+      if (out < &token[CC_WIDTH - 1])
+        *out++ = *in;
+    }
+  *out = '\0';
+
+  if (*in == grouping)
+    in++;
+  return in;
+}
+
+/* Sets custom currency specifier CC having name CC_NAME ('A' through
+   'E') to correspond to the settings in CC_STRING. */
+static bool
+do_cc (const char *cc_string, int idx)
+{
+  struct custom_currency cc;
+  
+  /* Determine separators. */
+  if (!find_cc_separators (cc_string, &cc)) 
+    {
+      msg (SE, _("CC%c: Custom currency string `%s' does not contain "
+                 "exactly three periods or commas (not both)."),
+           "ABCDE"[idx], cc_string);
+      return false;
+    }
+  
+  cc_string = extract_cc_token (cc_string, cc.grouping, cc.neg_prefix);
+  cc_string = extract_cc_token (cc_string, cc.grouping, cc.prefix);
+  cc_string = extract_cc_token (cc_string, cc.grouping, cc.suffix);
+  cc_string = extract_cc_token (cc_string, cc.grouping, cc.neg_suffix);
+
+  set_cc (idx, &cc);
+  
+  return true;
+}
+
+/* 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. */
+static int
+stc_custom_blanks (struct cmd_set *cmd UNUSED)
+{
+  lex_match ('=');
+  if ((token == T_ID && lex_id_match ("SYSMIS", tokid)))
+    {
+      lex_get ();
+      set_blanks (SYSMIS);
+    }
+  else
+    {
+      if (!lex_force_num ())
+       return 0;
+      set_blanks (lex_number ());
+      lex_get ();
+    }
+  return 1;
+}
+
+/* Parses the EPOCH subcommand, which controls the epoch used for
+   parsing 2-digit years. */
+static int
+stc_custom_epoch (struct cmd_set *cmd UNUSED) 
+{
+  lex_match ('=');
+  if (lex_match_id ("AUTOMATIC"))
+    set_epoch (-1);
+  else if (lex_is_integer ()) 
+    {
+      int new_epoch = lex_integer ();
+      lex_get ();
+      if (new_epoch < 1500) 
+        {
+          msg (SE, _("EPOCH must be 1500 or later."));
+          return 0;
+        }
+      set_epoch (new_epoch);
+    }
+  else 
+    {
+      lex_error (_("expecting AUTOMATIC or year"));
+      return 0;
+    }
+
+  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 ();
+    }
+
+  if (page_length != -1) 
+    set_viewlength (page_length);
+
+  return 1;
+}
+
+static int
+stc_custom_seed (struct cmd_set *cmd UNUSED)
+{
+  lex_match ('=');
+  if (lex_match_id ("RANDOM"))
+    set_rng (time (0));
+  else
+    {
+      if (!lex_force_num ())
+       return 0;
+      set_rng (lex_number ());
+      lex_get ();
+    }
+
+  return 1;
+}
+
+static int
+stc_custom_width (struct cmd_set *cmd UNUSED)
+{
+  lex_match ('=');
+  if (lex_match_id ("NARROW"))
+    set_viewwidth (79);
+  else if (lex_match_id ("WIDE"))
+    set_viewwidth (131);
+  else
+    {
+      if (!lex_force_int ())
+       return 0;
+      if (lex_integer () < 40)
+       {
+         msg (SE, _("WIDTH must be at least 40."));
+         return 0;
+       }
+      set_viewwidth (lex_integer ());
+      lex_get ();
+    }
+
+  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") && !lex_match_id ("OFF")) 
+    {
+      if (token == T_STRING)
+        lex_get ();
+      else
+        {
+          lex_error (NULL);
+          return 0;
+        }
+    }
+  return 1;
+}
+
+static int
+stc_custom_listing (struct cmd_set *cmd UNUSED)
+{
+  bool listing;
+
+  lex_match ('=');
+  if (lex_match_id ("ON") || lex_match_id ("YES"))
+    listing = true;
+  else if (lex_match_id ("OFF") || lex_match_id ("NO"))
+    listing = false;
+  else
+    {
+      /* FIXME */
+      return 0;
+    }
+  outp_enable_device (listing, OUTP_DEV_LISTING);
+
+  return 1;
+}
+
+static int
+stc_custom_disk (struct cmd_set *cmd UNUSED)
+{
+  return stc_custom_listing (cmd);
+}
+\f
+static void
+show_blanks (void) 
+{
+  if (get_blanks () == SYSMIS)
+    msg (MM, _("BLANKS is SYSMIS."));
+  else
+    msg (MM, _("BLANKS is %g."), get_blanks ());
+
+}
+
+static char *
+format_cc (const char *in, char grouping, char *out) 
+{
+  while (*in != '\0') 
+    {
+      if (*in == grouping || *in == '\'')
+        *out++ = '\'';
+      *out++ = *in++;
+    }
+  return out;
+}
+
+static void
+show_cc (int idx) 
+{
+  const struct custom_currency *cc = get_cc (idx);
+  char cc_string[CC_WIDTH * 4 * 2 + 3 + 1];
+  char *out;
+
+  out = format_cc (cc->neg_prefix, cc->grouping, cc_string);
+  *out++ = cc->grouping;
+  out = format_cc (cc->prefix, cc->grouping, out);
+  *out++ = cc->grouping;
+  out = format_cc (cc->suffix, cc->grouping, out);
+  *out++ = cc->grouping;
+  out = format_cc (cc->neg_suffix, cc->grouping, out);
+  *out = '\0';
+  
+  msg (MM, _("CC%c is \"%s\"."), "ABCDE"[idx], cc_string);
+}
+
+
+static void
+show_cca (void) 
+{
+  show_cc (0);
+}
+
+static void
+show_ccb (void) 
+{
+  show_cc (1);
+}
+
+static void
+show_ccc (void) 
+{
+  show_cc (2);
+}
+
+static void
+show_ccd (void) 
+{
+  show_cc (3);
+}
+
+static void
+show_cce (void) 
+{
+  show_cc (4);
+}
+
+static void
+show_decimals (void) 
+{
+  msg (MM, _("DECIMAL is \"%c\"."), get_decimal ());
+}
+
+static void
+show_endcmd (void) 
+{
+  msg (MM, _("ENDCMD is \"%c\"."), get_endcmd ());
+}
+
+static void
+show_format (void) 
+{
+  msg (MM, _("FORMAT is %s."), fmt_to_string (get_format ()));
+}
+
+static void
+show_length (void) 
+{
+  msg (MM, _("LENGTH is %d."), get_viewlength ());
+}
+
+static void
+show_mxerrs (void) 
+{
+  msg (MM, _("MXERRS is %d."), get_mxerrs ());
+}
+
+static void
+show_mxloops (void) 
+{
+  msg (MM, _("MXLOOPS is %d."), get_mxloops ());
+}
+
+static void
+show_mxwarns (void) 
+{
+  msg (MM, _("MXWARNS is %d."), get_mxwarns ());
+}
+
+static void
+show_scompression (void) 
+{
+  if (get_scompression ())
+    msg (MM, _("SCOMPRESSION is ON."));
+  else
+    msg (MM, _("SCOMPRESSION is OFF."));
+}
+
+static void
+show_undefined (void) 
+{
+  if (get_undefined ())
+    msg (MM, _("UNDEFINED is WARN."));
+  else
+    msg (MM, _("UNDEFINED is NOWARN."));
+}
+
+static void
+show_weight (void) 
+{
+  struct variable *var = dict_get_weight (default_dict);
+  if (var == NULL)
+    msg (MM, _("WEIGHT is off."));
+  else
+    msg (MM, _("WEIGHT is variable %s."), var->name);
+}
+
+static void
+show_width (void) 
+{
+  msg (MM, _("WIDTH is %d."), get_viewwidth ());
+}
+
+struct show_sbc 
+  {
+    const char *name;
+    void (*function) (void);
+  };
+
+struct show_sbc show_table[] = 
+  {
+    {"BLANKS", show_blanks},
+    {"CCA", show_cca},
+    {"CCB", show_ccb},
+    {"CCC", show_ccc},
+    {"CCD", show_ccd},
+    {"CCE", show_cce},
+    {"DECIMALS", show_decimals},
+    {"ENDCMD", show_endcmd},
+    {"FORMAT", show_format},
+    {"LENGTH", show_length},
+    {"MXERRS", show_mxerrs},
+    {"MXLOOPS", show_mxloops},
+    {"MXWARNS", show_mxwarns},
+    {"SCOMPRESSION", show_scompression},
+    {"UNDEFINED", show_undefined},
+    {"WEIGHT", show_weight},
+    {"WIDTH", show_width},
+  };
+
+static void
+show_all (void) 
+{
+  size_t i;
+  
+  for (i = 0; i < sizeof show_table / sizeof *show_table; i++)
+    show_table[i].function ();
+}
+
+static void
+show_all_cc (void) 
+{
+  int i;
+
+  for (i = 0; i < 5; i++)
+    show_cc (i);
+}
+
+static void
+show_warranty (void) 
+{
+  msg (MM, lack_of_warranty);
+}
+
+static void
+show_copying (void) 
+{
+  msg (MM, copyleft);
+}
+
+int
+cmd_show (void) 
+{
+  if (token == '.') 
+    {
+      show_all ();
+      return CMD_SUCCESS;
+    }
+
+  do 
+    {
+      if (lex_match (T_ALL))
+        show_all ();
+      else if (lex_match_id ("CC")) 
+        show_all_cc ();
+      else if (lex_match_id ("WARRANTY"))
+        show_warranty ();
+      else if (lex_match_id ("COPYING"))
+        show_copying ();
+      else if (token == T_ID)
+        {
+          int i;
+
+          for (i = 0; i < sizeof show_table / sizeof *show_table; i++)
+            if (lex_match_id (show_table[i].name)) 
+              {
+                show_table[i].function ();
+                goto found;
+              }
+          lex_error (NULL);
+          return CMD_PART_SUCCESS_MAYBE;
+
+        found: ;
+        }
+      else 
+        {
+          lex_error (NULL);
+          return CMD_PART_SUCCESS_MAYBE;
+        }
+
+      lex_match ('/');
+    }
+  while (token != '.');
+
+  return CMD_SUCCESS;
+}
+
+/*
+   Local Variables:
+   mode: c
+   End:
+*/
diff --git a/src/language/utilities/title.c b/src/language/utilities/title.c
new file mode 100644 (file)
index 0000000..759208d
--- /dev/null
@@ -0,0 +1,181 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "lexer.h"
+#include "output.h"
+#include "start-date.h"
+#include "variable.h"
+#include "version.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#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_c_str (&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));
+      lex_discard_line ();
+      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)
+{
+  const char *label;
+
+  label = lex_rest_of_line (NULL);
+  lex_discard_line ();
+  while (isspace ((unsigned char) *label))
+    label++;
+
+  dict_set_label (default_dict, label);
+  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)
+{
+  const char *old_documents;
+  size_t old_len;
+  char *new_documents;
+
+  old_documents = dict_get_documents (default_dict);
+  old_len = old_documents != NULL ? strlen (old_documents) : 0;
+  new_documents = xmalloc (old_len + 81);
+
+  memcpy (new_documents, old_documents, old_len);
+  memset (new_documents + old_len, ' ', indent);
+  buf_copy_str_rpad (new_documents + old_len + indent, 80 - indent, line);
+  new_documents[old_len + 80] = '\0';
+
+  dict_set_documents (default_dict, new_documents);
+
+  free (new_documents);
+}
+
+/* Performs the DOCUMENT command. */
+int
+cmd_document (void)
+{
+  /* Add a few header lines for reference. */
+  {
+    char buf[256];
+
+    if (dict_get_documents (default_dict) != NULL)
+      add_document_line ("", 0);
+
+    sprintf (buf, _("Document entered %s by %s:"), get_start_date (), version);
+    add_document_line (buf, 1);
+  }
+
+  for (;;)
+    {
+      int had_dot;
+      const char *orig_line;
+      char *copy_line;
+
+      orig_line = lex_rest_of_line (&had_dot);
+      lex_discard_line ();
+      while (isspace ((unsigned char) *orig_line))
+       orig_line++;
+
+      copy_line = xmalloc (strlen (orig_line) + 2);
+      strcpy (copy_line, orig_line);
+      if (had_dot)
+        strcat (copy_line, ".");
+
+      add_document_line (copy_line, 3);
+      free (copy_line);
+
+      lex_get_line ();
+      if (had_dot)
+       break;
+    }
+
+  token = '.';
+  return CMD_SUCCESS;
+}
+
+/* Performs the DROP DOCUMENTS command. */
+int
+cmd_drop_documents (void)
+{
+  dict_set_documents (default_dict, NULL);
+
+  return lex_end_of_command ();
+}
diff --git a/src/language/xforms/ChangeLog b/src/language/xforms/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/language/xforms/compute.c b/src/language/xforms/compute.c
new file mode 100644 (file)
index 0000000..13499a0
--- /dev/null
@@ -0,0 +1,416 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <stdlib.h>
+#include "alloc.h"
+#include "case.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "expressions/public.h"
+#include "lexer.h"
+#include "misc.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+struct compute_trns;
+struct lvalue;
+
+/* Target of a COMPUTE or IF assignment, either a variable or a
+   vector element. */
+static struct lvalue *lvalue_parse (void);
+static int lvalue_get_type (const struct lvalue *);
+static bool lvalue_is_vector (const struct lvalue *);
+static void lvalue_finalize (struct lvalue *,
+                             struct compute_trns *);
+static void lvalue_destroy (struct lvalue *);
+
+/* COMPUTE and IF transformation. */
+struct compute_trns
+  {
+    /* Test expression (IF only). */
+    struct expression *test;    /* Test expression. */
+
+    /* Variable lvalue, if variable != NULL. */
+    struct variable *variable;   /* Destination variable, if any. */
+    int fv;                     /* `value' index of destination variable. */
+    int width;                  /* Lvalue string width; 0=numeric. */
+
+    /* Vector lvalue, if vector != NULL. */
+    const struct vector *vector; /* Destination vector, if any. */
+    struct expression *element;  /* Destination vector element expr. */
+
+    /* Rvalue. */
+    struct expression *rvalue;  /* Rvalue expression. */
+  };
+
+static struct expression *parse_rvalue (const struct lvalue *);
+static struct compute_trns *compute_trns_create (void);
+static trns_proc_func *get_proc_func (const struct lvalue *);
+static trns_free_func compute_trns_free;
+\f
+/* COMPUTE. */
+
+int
+cmd_compute (void)
+{
+  struct lvalue *lvalue = NULL;
+  struct compute_trns *compute = NULL;
+
+  compute = compute_trns_create ();
+
+  lvalue = lvalue_parse ();
+  if (lvalue == NULL)
+    goto fail;
+
+  if (!lex_force_match ('='))
+    goto fail;
+  compute->rvalue = parse_rvalue (lvalue);
+  if (compute->rvalue == NULL)
+    goto fail;
+
+  add_transformation (get_proc_func (lvalue), compute_trns_free, compute);
+
+  lvalue_finalize (lvalue, compute);
+
+  return lex_end_of_command ();
+
+ fail:
+  lvalue_destroy (lvalue);
+  compute_trns_free (compute);
+  return CMD_CASCADING_FAILURE;
+}
+\f
+/* Transformation functions. */
+
+/* Handle COMPUTE or IF with numeric target variable. */
+static int
+compute_num (void *compute_, struct ccase *c, int case_num)
+{
+  struct compute_trns *compute = compute_;
+
+  if (compute->test == NULL
+      || expr_evaluate_num (compute->test, c, case_num) == 1.0) 
+    case_data_rw (c, compute->fv)->f = expr_evaluate_num (compute->rvalue, c,
+                                                          case_num); 
+  
+  return TRNS_CONTINUE;
+}
+
+/* Handle COMPUTE or IF with numeric vector element target
+   variable. */
+static int
+compute_num_vec (void *compute_, struct ccase *c, int case_num)
+{
+  struct compute_trns *compute = compute_;
+
+  if (compute->test == NULL
+      || expr_evaluate_num (compute->test, c, case_num) == 1.0) 
+    {
+      double index;     /* Index into the vector. */
+      int rindx;        /* Rounded index value. */
+
+      index = expr_evaluate_num (compute->element, c, case_num);
+      rindx = floor (index + EPSILON);
+      if (index == SYSMIS || rindx < 1 || rindx > compute->vector->cnt)
+        {
+          if (index == SYSMIS)
+            msg (SW, _("When executing COMPUTE: SYSMIS is not a valid value as "
+                       "an index into vector %s."), compute->vector->name);
+          else
+            msg (SW, _("When executing COMPUTE: %g is not a valid value as "
+                       "an index into vector %s."),
+                 index, compute->vector->name);
+          return TRNS_CONTINUE;
+        }
+      case_data_rw (c, compute->vector->var[rindx - 1]->fv)->f
+        = expr_evaluate_num (compute->rvalue, c, case_num);
+    }
+  
+  return TRNS_CONTINUE;
+}
+
+/* Handle COMPUTE or IF with string target variable. */
+static int
+compute_str (void *compute_, struct ccase *c, int case_num)
+{
+  struct compute_trns *compute = compute_;
+
+  if (compute->test == NULL
+      || expr_evaluate_num (compute->test, c, case_num) == 1.0) 
+    expr_evaluate_str (compute->rvalue, c, case_num,
+                       case_data_rw (c, compute->fv)->s, compute->width);
+  
+  return TRNS_CONTINUE;
+}
+
+/* Handle COMPUTE or IF with string vector element target
+   variable. */
+static int
+compute_str_vec (void *compute_, struct ccase *c, int case_num)
+{
+  struct compute_trns *compute = compute_;
+
+  if (compute->test == NULL
+      || expr_evaluate_num (compute->test, c, case_num) == 1.0) 
+    {
+      double index;             /* Index into the vector. */
+      int rindx;                /* Rounded index value. */
+      struct variable *vr;      /* Variable reference by indexed vector. */
+
+      index = expr_evaluate_num (compute->element, c, case_num);
+      rindx = floor (index + EPSILON);
+      if (index == SYSMIS) 
+        {
+          msg (SW, _("When executing COMPUTE: SYSMIS is not a valid "
+                     "value as an index into vector %s."),
+               compute->vector->name);
+          return TRNS_CONTINUE; 
+        }
+      else if (rindx < 1 || rindx > compute->vector->cnt)
+        {
+          msg (SW, _("When executing COMPUTE: %g is not a valid value as "
+                     "an index into vector %s."),
+               index, compute->vector->name);
+          return TRNS_CONTINUE;
+        }
+
+      vr = compute->vector->var[rindx - 1];
+      expr_evaluate_str (compute->rvalue, c, case_num,
+                         case_data_rw (c, vr->fv)->s, vr->width);
+    }
+  
+  return TRNS_CONTINUE;
+}
+\f
+/* IF. */
+
+int
+cmd_if (void)
+{
+  struct compute_trns *compute = NULL;
+  struct lvalue *lvalue = NULL;
+
+  compute = compute_trns_create ();
+
+  /* Test expression. */
+  compute->test = expr_parse (default_dict, EXPR_BOOLEAN);
+  if (compute->test == NULL)
+    goto fail;
+
+  /* Lvalue variable. */
+  lvalue = lvalue_parse ();
+  if (lvalue == NULL)
+    goto fail;
+
+  /* Rvalue expression. */
+  if (!lex_force_match ('='))
+    goto fail;
+  compute->rvalue = parse_rvalue (lvalue);
+  if (compute->rvalue == NULL)
+    goto fail;
+
+  add_transformation (get_proc_func (lvalue), compute_trns_free, compute);
+
+  lvalue_finalize (lvalue, compute);
+
+  return lex_end_of_command ();
+
+ fail:
+  lvalue_destroy (lvalue);
+  compute_trns_free (compute);
+  return CMD_CASCADING_FAILURE;
+}
+\f
+/* Code common to COMPUTE and IF. */
+
+static trns_proc_func *
+get_proc_func (const struct lvalue *lvalue) 
+{
+  bool is_numeric = lvalue_get_type (lvalue) == NUMERIC;
+  bool is_vector = lvalue_is_vector (lvalue);
+
+  return (is_numeric
+          ? (is_vector ? compute_num_vec : compute_num)
+          : (is_vector ? compute_str_vec : compute_str));
+}
+
+/* Parses and returns an rvalue expression of the same type as
+   LVALUE, or a null pointer on failure. */
+static struct expression *
+parse_rvalue (const struct lvalue *lvalue)
+{
+  bool is_numeric = lvalue_get_type (lvalue) == NUMERIC;
+
+  return expr_parse (default_dict, is_numeric ? EXPR_NUMBER : EXPR_STRING);
+}
+
+/* Returns a new struct compute_trns after initializing its fields. */
+static struct compute_trns *
+compute_trns_create (void)
+{
+  struct compute_trns *compute = xmalloc (sizeof *compute);
+  compute->test = NULL;
+  compute->variable = NULL;
+  compute->vector = NULL;
+  compute->element = NULL;
+  compute->rvalue = NULL;
+  return compute;
+}
+
+/* Deletes all the fields in COMPUTE. */
+static bool
+compute_trns_free (void *compute_)
+{
+  struct compute_trns *compute = compute_;
+
+  if (compute != NULL) 
+    {
+      expr_free (compute->test);
+      expr_free (compute->element);
+      expr_free (compute->rvalue);
+      free (compute);
+    }
+  return true;
+}
+\f
+/* COMPUTE or IF target variable or vector element. */
+struct lvalue
+  {
+    char var_name[LONG_NAME_LEN + 1];   /* Destination variable name, or "". */
+    const struct vector *vector; /* Destination vector, if any, or NULL. */
+    struct expression *element;  /* Destination vector element, or NULL. */
+  };
+
+/* Parses the target variable or vector element into a new
+   `struct lvalue', which is returned. */
+static struct lvalue *
+lvalue_parse (void) 
+{
+  struct lvalue *lvalue;
+
+  lvalue = xmalloc (sizeof *lvalue);
+  lvalue->var_name[0] = '\0';
+  lvalue->vector = NULL;
+  lvalue->element = NULL;
+
+  if (!lex_force_id ())
+    goto lossage;
+  
+  if (lex_look_ahead () == '(')
+    {
+      /* Vector. */
+      lvalue->vector = dict_lookup_vector (default_dict, tokid);
+      if (lvalue->vector == NULL)
+       {
+         msg (SE, _("There is no vector named %s."), tokid);
+          goto lossage;
+       }
+
+      /* Vector element. */
+      lex_get ();
+      if (!lex_force_match ('('))
+       goto lossage;
+      lvalue->element = expr_parse (default_dict, EXPR_NUMBER);
+      if (lvalue->element == NULL)
+        goto lossage;
+      if (!lex_force_match (')'))
+        goto lossage;
+    }
+  else
+    {
+      /* Variable name. */
+      str_copy_trunc (lvalue->var_name, sizeof lvalue->var_name, tokid);
+      lex_get ();
+    }
+  return lvalue;
+
+ lossage:
+  lvalue_destroy (lvalue);
+  return NULL;
+}
+
+/* Returns the type (NUMERIC or ALPHA) of the target variable or
+   vector in LVALUE. */
+static int
+lvalue_get_type (const struct lvalue *lvalue) 
+{
+  if (lvalue->vector == NULL) 
+    {
+      struct variable *var = dict_lookup_var (default_dict, lvalue->var_name);
+      if (var == NULL)
+        return NUMERIC;
+      else
+        return var->type;
+    }
+  else 
+    return lvalue->vector->var[0]->type;
+}
+
+/* Returns nonzero if LVALUE has a vector as its target. */
+static bool
+lvalue_is_vector (const struct lvalue *lvalue) 
+{
+  return lvalue->vector != NULL;
+}
+
+/* Finalizes making LVALUE the target of COMPUTE, by creating the
+   target variable if necessary and setting fields in COMPUTE. */
+static void
+lvalue_finalize (struct lvalue *lvalue, struct compute_trns *compute) 
+{
+  if (lvalue->vector == NULL)
+    {
+      compute->variable = dict_lookup_var (default_dict, lvalue->var_name);
+      if (compute->variable == NULL)
+         compute->variable = dict_create_var_assert (default_dict,
+                                                     lvalue->var_name, 0);
+
+      compute->fv = compute->variable->fv;
+      compute->width = compute->variable->width;
+
+      /* Goofy behavior, but compatible: Turn off LEAVE. */
+      if (dict_class_from_id (compute->variable->name) != DC_SCRATCH)
+        compute->variable->reinit = 1;
+    }
+  else 
+    {
+      compute->vector = lvalue->vector;
+      compute->element = lvalue->element;
+      lvalue->element = NULL;
+    }
+
+  lvalue_destroy (lvalue);
+}
+
+/* Destroys LVALUE. */
+static void 
+lvalue_destroy (struct lvalue *lvalue) 
+{
+  if (lvalue == NULL) 
+     return;
+
+  expr_free (lvalue->element);
+  free (lvalue);
+}
diff --git a/src/language/xforms/count.c b/src/language/xforms/count.c
new file mode 100644 (file)
index 0000000..e5f3991
--- /dev/null
@@ -0,0 +1,350 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <stdlib.h>
+#include "alloc.h"
+#include "case.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "lexer.h"
+#include "pool.h"
+#include "range-parser.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* Value or range? */
+enum value_type
+  {
+    CNT_SINGLE,                        /* Single value. */
+    CNT_RANGE                  /* a <= x <= b. */
+  };
+
+/* Numeric count criteria. */
+struct num_value
+  {
+    enum value_type type;       /* How to interpret a, b. */
+    double a, b;                /* Values to count. */
+  };
+
+struct criteria
+  {
+    struct criteria *next;
+
+    /* Variables to count. */
+    struct variable **vars;
+    size_t var_cnt;
+
+    /* Count special values?. */
+    bool count_system_missing;  /* Count system missing? */
+    bool count_user_missing;    /* Count user missing? */
+
+    /* Criterion values. */    
+    size_t value_cnt;
+    union
+      {
+       struct num_value *num;
+       char **str;
+      }
+    values;
+  };
+
+struct dst_var
+  {
+    struct dst_var *next;
+    struct variable *var;       /* Destination variable. */
+    char *name;                 /* Name of dest var. */
+    struct criteria *crit;      /* The criteria specifications. */
+  };
+
+struct count_trns
+  {
+    struct dst_var *dst_vars;
+    struct pool *pool;
+  };
+
+static trns_proc_func count_trns_proc;
+static trns_free_func count_trns_free;
+
+static bool parse_numeric_criteria (struct pool *, struct criteria *);
+static bool parse_string_criteria (struct pool *, struct criteria *);
+\f
+int
+cmd_count (void)
+{
+  struct dst_var *dv;           /* Destination var being parsed. */
+  struct count_trns *trns;      /* Transformation. */
+
+  /* Parses each slash-delimited specification. */
+  trns = pool_create_container (struct count_trns, pool);
+  trns->dst_vars = dv = pool_alloc (trns->pool, sizeof *dv);
+  for (;;)
+    {
+      struct criteria *crit;
+
+      /* Initialize this struct dst_var to ensure proper cleanup. */
+      dv->next = NULL;
+      dv->var = NULL;
+      dv->crit = NULL;
+
+      /* Get destination variable, or at least its name. */
+      if (!lex_force_id ())
+       goto fail;
+      dv->var = dict_lookup_var (default_dict, tokid);
+      if (dv->var != NULL)
+        {
+          if (dv->var->type == ALPHA)
+            {
+              msg (SE, _("Destination cannot be a string variable."));
+              goto fail;
+            }
+        }
+      else
+        dv->name = pool_strdup (trns->pool, tokid);
+
+      lex_get ();
+      if (!lex_force_match ('='))
+       goto fail;
+
+      crit = dv->crit = pool_alloc (trns->pool, sizeof *crit);
+      for (;;)
+       {
+          bool ok;
+          
+         crit->next = NULL;
+         crit->vars = NULL;
+         if (!parse_variables (default_dict, &crit->vars, &crit->var_cnt,
+                                PV_DUPLICATE | PV_SAME_TYPE))
+           goto fail;
+          pool_register (trns->pool, free, crit->vars);
+
+         if (!lex_force_match ('('))
+           goto fail;
+
+          crit->value_cnt = 0;
+          if (crit->vars[0]->type == NUMERIC)
+            ok = parse_numeric_criteria (trns->pool, crit);
+          else
+            ok = parse_string_criteria (trns->pool, crit);
+         if (!ok)
+           goto fail;
+
+         if (token == '/' || token == '.')
+           break;
+
+         crit = crit->next = pool_alloc (trns->pool, sizeof *crit);
+       }
+
+      if (token == '.')
+       break;
+
+      if (!lex_force_match ('/'))
+       goto fail;
+      dv = dv->next = pool_alloc (trns->pool, sizeof *dv);
+    }
+
+  /* Create all the nonexistent destination variables. */
+  for (dv = trns->dst_vars; dv; dv = dv->next)
+    if (dv->var == NULL)
+      {
+       /* It's valid, though motivationally questionable, to count to
+          the same dest var more than once. */
+       dv->var = dict_lookup_var (default_dict, dv->name);
+
+       if (dv->var == NULL) 
+          dv->var = dict_create_var_assert (default_dict, dv->name, 0);
+      }
+
+  add_transformation (count_trns_proc, count_trns_free, trns);
+  return CMD_SUCCESS;
+
+fail:
+  count_trns_free (trns);
+  return CMD_FAILURE;
+}
+
+/* Parses a set of numeric criterion values.  Returns success. */
+static bool
+parse_numeric_criteria (struct pool *pool, struct criteria *crit)
+{
+  size_t allocated = 0;
+
+  crit->values.num = NULL;
+  crit->count_system_missing = false;
+  crit->count_user_missing = false;
+  for (;;)
+    {
+      double low, high;
+      
+      if (lex_match_id ("SYSMIS"))
+        crit->count_system_missing = true;
+      else if (lex_match_id ("MISSING"))
+       crit->count_user_missing = true;
+      else if (parse_num_range (&low, &high, NULL)) 
+        {
+          struct num_value *cur;
+
+          if (crit->value_cnt >= allocated)
+            crit->values.num = pool_2nrealloc (pool, crit->values.num,
+                                               &allocated,
+                                               sizeof *crit->values.num);
+          cur = &crit->values.num[crit->value_cnt++];
+          cur->type = low == high ? CNT_SINGLE : CNT_RANGE;
+          cur->a = low;
+          cur->b = high;
+        }
+      else
+        return false;
+
+      lex_match (',');
+      if (lex_match (')'))
+       break;
+    }
+  return true;
+}
+
+/* Parses a set of string criteria values.  Returns success. */
+static bool
+parse_string_criteria (struct pool *pool, struct criteria *crit)
+{
+  int len = 0;
+  size_t allocated = 0;
+  size_t i;
+
+  for (i = 0; i < crit->var_cnt; i++)
+    if (crit->vars[i]->width > len)
+      len = crit->vars[i]->width;
+
+  crit->values.str = NULL;
+  for (;;)
+    {
+      char **cur;
+      if (crit->value_cnt >= allocated)
+        crit->values.str = pool_2nrealloc (pool, crit->values.str,
+                                           &allocated,
+                                           sizeof *crit->values.str);
+
+      if (!lex_force_string ())
+       return false;
+      cur = &crit->values.str[crit->value_cnt++];
+      *cur = pool_alloc (pool, len + 1);
+      str_copy_rpad (*cur, len + 1, ds_c_str (&tokstr));
+      lex_get ();
+
+      lex_match (',');
+      if (lex_match (')'))
+       break;
+    }
+
+  return true;
+}
+\f
+/* Transformation. */
+
+/* Counts the number of values in case C matching CRIT. */
+static inline int
+count_numeric (struct criteria *crit, struct ccase *c)
+{
+  int counter = 0;
+  size_t i;
+
+  for (i = 0; i < crit->var_cnt; i++)
+    {
+      double x = case_num (c, crit->vars[i]->fv);
+      if (x == SYSMIS)
+        counter += crit->count_system_missing;
+      else if (crit->count_user_missing
+               && mv_is_num_user_missing (&crit->vars[i]->miss, x))
+        counter++;
+      else 
+        {
+          struct num_value *v;
+          
+          for (v = crit->values.num; v < crit->values.num + crit->value_cnt;
+               v++) 
+            if (v->type == CNT_SINGLE ? x == v->a : x >= v->a && x <= v->b) 
+              {
+                counter++;
+                break;
+              } 
+        }
+    }
+  
+  return counter;
+}
+
+/* Counts the number of values in case C matching CRIT. */
+static inline int
+count_string (struct criteria *crit, struct ccase *c)
+{
+  int counter = 0;
+  size_t i;
+
+  for (i = 0; i < crit->var_cnt; i++)
+    {
+      char **v;
+      for (v = crit->values.str; v < crit->values.str + crit->value_cnt; v++)
+        if (!memcmp (case_str (c, crit->vars[i]->fv), *v,
+                     crit->vars[i]->width))
+          {
+           counter++;
+            break;
+          }
+    }
+
+  return counter;
+}
+
+/* Performs the COUNT transformation T on case C. */
+static int
+count_trns_proc (void *trns_, struct ccase *c,
+                 int case_num UNUSED)
+{
+  struct count_trns *trns = trns_;
+  struct dst_var *dv;
+
+  for (dv = trns->dst_vars; dv; dv = dv->next)
+    {
+      struct criteria *crit;
+      int counter;
+
+      counter = 0;
+      for (crit = dv->crit; crit; crit = crit->next)
+       if (crit->vars[0]->type == NUMERIC)
+         counter += count_numeric (crit, c);
+       else
+         counter += count_string (crit, c);
+      case_data_rw (c, dv->var->fv)->f = counter;
+    }
+  return TRNS_CONTINUE;
+}
+
+/* Destroys all dynamic data structures associated with TRNS. */
+static bool
+count_trns_free (void *trns_)
+{
+  struct count_trns *trns = (struct count_trns *) trns_;
+  pool_destroy (trns->pool);
+  return true;
+}
diff --git a/src/language/xforms/recode.c b/src/language/xforms/recode.c
new file mode 100644 (file)
index 0000000..7da3ab3
--- /dev/null
@@ -0,0 +1,661 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <ctype.h>
+#include <math.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "case.h"
+#include "command.h"
+#include "data-in.h"
+#include "dictionary.h"
+#include "message.h"
+#include "lexer.h"
+#include "magic.h"
+#include "pool.h"
+#include "range-parser.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+\f
+/* Definitions. */
+
+/* Type of source value for RECODE. */
+enum map_in_type
+  {
+    MAP_SINGLE,                        /* Specific value. */
+    MAP_RANGE,                 /* Range of values. */
+    MAP_SYSMIS,                 /* System missing value. */
+    MAP_MISSING,                /* Any missing value. */
+    MAP_ELSE,                  /* Any value. */
+    MAP_CONVERT                        /* "123" => 123. */
+  };
+
+/* Describes input values to be mapped. */
+struct map_in
+  {
+    enum map_in_type type;      /* One of MAP_*. */
+    union value x, y;           /* Source values. */
+  };
+
+/* Describes the value used as output from a mapping. */
+struct map_out 
+  {
+    bool copy_input;            /* If true, copy input to output. */
+    union value value;          /* If copy_input false, recoded value. */
+    int width;                  /* If copy_input false, output value width. */ 
+  };
+
+/* Describes how to recode a single value or range of values into a
+   single value.  */
+struct mapping 
+  {
+    struct map_in in;           /* Input values. */
+    struct map_out out;         /* Output value. */
+  };
+
+/* RECODE transformation. */
+struct recode_trns
+  {
+    struct pool *pool;
+
+    /* Variable types, for convenience. */
+    enum var_type src_type;     /* src_vars[*]->type. */
+    enum var_type dst_type;     /* dst_vars[*]->type. */
+
+    /* Variables. */
+    struct variable **src_vars;        /* Source variables. */
+    struct variable **dst_vars;        /* Destination variables. */
+    char **dst_names;          /* Name of dest variables, if they're new. */
+    size_t var_cnt;             /* Number of variables. */
+
+    /* Mappings. */
+    struct mapping *mappings;   /* Value mappings. */
+    size_t map_cnt;             /* Number of mappings. */
+  };
+
+static bool parse_src_vars (struct recode_trns *);
+static bool parse_mappings (struct recode_trns *);
+static bool parse_dst_vars (struct recode_trns *);
+
+static void add_mapping (struct recode_trns *,
+                         size_t *map_allocated, const struct map_in *);
+
+static bool parse_map_in (struct map_in *, struct pool *,
+                          enum var_type src_type, size_t max_src_width);
+static void set_map_in_generic (struct map_in *, enum map_in_type);
+static void set_map_in_num (struct map_in *, enum map_in_type, double, double);
+static void set_map_in_str (struct map_in *, struct pool *,
+                            const struct string *, size_t width);
+
+static bool parse_map_out (struct pool *, struct map_out *);
+static void set_map_out_num (struct map_out *, double);
+static void set_map_out_str (struct map_out *, struct pool *,
+                             const struct string *);
+
+static void enlarge_dst_widths (struct recode_trns *);
+static void create_dst_vars (struct recode_trns *);
+
+static trns_proc_func recode_trns_proc;
+static trns_free_func recode_trns_free;
+\f
+/* Parser. */
+
+/* Parses the RECODE transformation. */
+int
+cmd_recode (void)
+{
+  do
+    {
+      struct recode_trns *trns
+        = pool_create_container (struct recode_trns, pool);
+
+      /* Parse source variable names,
+         then input to output mappings,
+         then destintation variable names. */
+      if (!parse_src_vars (trns)
+          || !parse_mappings (trns)
+          || !parse_dst_vars (trns))
+        {
+          recode_trns_free (trns);
+          return CMD_PART_SUCCESS;
+        }
+
+      /* Ensure that all the output strings are at least as wide
+         as the widest destination variable. */
+      if (trns->dst_type == ALPHA)
+        enlarge_dst_widths (trns);
+
+      /* Create destination variables, if needed.
+         This must be the final step; otherwise we'd have to
+         delete destination variables on failure. */
+      if (trns->src_vars != trns->dst_vars)
+        create_dst_vars (trns);
+
+      /* Done. */
+      add_transformation (recode_trns_proc, recode_trns_free, trns);
+    }
+  while (lex_match ('/'));
+  
+  return lex_end_of_command ();
+}
+
+/* Parses a set of variables to recode into TRNS->src_vars and
+   TRNS->var_cnt.  Sets TRNS->src_type.  Returns true if
+   successful, false on parse error. */
+static bool
+parse_src_vars (struct recode_trns *trns) 
+{
+  if (!parse_variables (default_dict, &trns->src_vars, &trns->var_cnt,
+                        PV_SAME_TYPE))
+    return false;
+  pool_register (trns->pool, free, trns->src_vars);
+  trns->src_type = trns->src_vars[0]->type;
+  return true;
+}
+
+/* Parses a set of mappings, which take the form (input=output),
+   into TRNS->mappings and TRNS->map_cnt.  Sets TRNS->dst_type.
+   Returns true if successful, false on parse error. */
+static bool
+parse_mappings (struct recode_trns *trns) 
+{
+  size_t max_src_width;
+  size_t map_allocated;
+  bool have_dst_type;
+  size_t i;
+  
+  /* Find length of longest source variable. */
+  max_src_width = trns->src_vars[0]->width;
+  for (i = 1; i < trns->var_cnt; i++) 
+    {
+      size_t var_width = trns->src_vars[i]->width;
+      if (var_width > max_src_width)
+        max_src_width = var_width;
+    }
+      
+  /* Parse the mappings in parentheses. */
+  trns->mappings = NULL;
+  trns->map_cnt = 0;
+  map_allocated = 0;
+  have_dst_type = false;
+  if (!lex_force_match ('('))
+    return false;
+  do
+    {
+      enum var_type dst_type;
+
+      if (!lex_match_id ("CONVERT")) 
+        {
+          struct map_out out;
+          size_t first_map_idx;
+          size_t i;
+
+          first_map_idx = trns->map_cnt;
+
+          /* Parse source specifications. */
+          do
+            {
+              struct map_in in;
+              if (!parse_map_in (&in, trns->pool,
+                                 trns->src_type, max_src_width))
+                return false;
+              add_mapping (trns, &map_allocated, &in);
+              lex_match (',');
+            }
+          while (!lex_match ('='));
+
+          if (!parse_map_out (trns->pool, &out))
+            return false;
+          dst_type = out.width == 0 ? NUMERIC : ALPHA;
+          if (have_dst_type && dst_type != trns->dst_type)
+            {
+              msg (SE, _("Inconsistent target variable types.  "
+                         "Target variables "
+                         "must be all numeric or all string."));
+              return false;
+            }
+              
+          for (i = first_map_idx; i < trns->map_cnt; i++)
+            trns->mappings[i].out = out;
+        }
+      else 
+        {
+          /* Parse CONVERT as a special case. */
+          struct map_in in;
+          set_map_in_generic (&in, MAP_CONVERT);
+          add_mapping (trns, &map_allocated, &in);
+              
+          dst_type = NUMERIC;
+          if (trns->src_type != ALPHA
+              || (have_dst_type && trns->dst_type != NUMERIC)) 
+            {
+              msg (SE, _("CONVERT requires string input values and "
+                         "numeric output values."));
+              return false;
+            }
+        }
+      trns->dst_type = dst_type;
+      have_dst_type = true;
+
+      if (!lex_force_match (')'))
+        return false; 
+    }
+  while (lex_match ('('));
+
+  return true;
+}
+
+/* Parses a mapping input value into IN, allocating memory from
+   POOL.  The source value type must be provided as SRC_TYPE and,
+   if string, the maximum width of a string source variable must
+   be provided in MAX_SRC_WIDTH.  Returns true if successful,
+   false on parse error. */
+static bool
+parse_map_in (struct map_in *in, struct pool *pool,
+              enum var_type src_type, size_t max_src_width)
+{
+  if (lex_match_id ("ELSE"))
+    set_map_in_generic (in, MAP_ELSE);
+  else if (src_type == NUMERIC)
+    {
+      if (lex_match_id ("MISSING"))
+        set_map_in_generic (in, MAP_MISSING);
+      else if (lex_match_id ("SYSMIS"))
+        set_map_in_generic (in, MAP_SYSMIS);
+      else 
+        {
+          double x, y;
+          if (!parse_num_range (&x, &y, NULL))
+            return false;
+          set_map_in_num (in, x == y ? MAP_SINGLE : MAP_RANGE, x, y);
+        }
+    }
+  else
+    {
+      if (!lex_force_string ())
+        return false;
+      set_map_in_str (in, pool, &tokstr, max_src_width);
+      lex_get ();
+    }
+
+  return true;
+}
+
+/* Adds IN to the list of mappings in TRNS.
+   MAP_ALLOCATED is the current number of allocated mappings,
+   which is updated as needed. */
+static void
+add_mapping (struct recode_trns *trns,
+             size_t *map_allocated, const struct map_in *in)
+{
+  struct mapping *m;
+  if (trns->map_cnt >= *map_allocated)
+    trns->mappings = pool_2nrealloc (trns->pool, trns->mappings,
+                                     map_allocated,
+                                     sizeof *trns->mappings);
+  m = &trns->mappings[trns->map_cnt++];
+  m->in = *in;
+}
+
+/* Sets IN as a mapping of the given TYPE. */
+static void
+set_map_in_generic (struct map_in *in, enum map_in_type type) 
+{
+  in->type = type;
+}
+
+/* Sets IN as a numeric mapping of the given TYPE,
+   with X and Y as the two numeric values. */
+static void
+set_map_in_num (struct map_in *in, enum map_in_type type, double x, double y) 
+{
+  in->type = type;
+  in->x.f = x;
+  in->y.f = y;
+}
+
+/* Sets IN as a string mapping, with STRING as the string,
+   allocated from POOL.  The string is padded with spaces on the
+   right to WIDTH characters long. */
+static void
+set_map_in_str (struct map_in *in, struct pool *pool,
+                const struct string *string, size_t width) 
+{
+  in->type = MAP_SINGLE;
+  in->x.c = pool_alloc_unaligned (pool, width);
+  buf_copy_rpad (in->x.c, width, ds_data (string), ds_length (string));
+}
+
+/* Parses a mapping output value into OUT, allocating memory from
+   POOL.  Returns true if successful, false on parse error. */
+static bool
+parse_map_out (struct pool *pool, struct map_out *out)
+{
+  if (lex_is_number ())
+    {
+      set_map_out_num (out, lex_number ());
+      lex_get ();
+    }
+  else if (lex_match_id ("SYSMIS"))
+    set_map_out_num (out, SYSMIS);
+  else if (token == T_STRING)
+    {
+      set_map_out_str (out, pool, &tokstr);
+      lex_get ();
+    }
+  else if (lex_match_id ("COPY"))
+    out->copy_input = true;
+  else 
+    {
+      lex_error (_("expecting output value"));
+      return false;
+    }
+  return true; 
+}
+
+/* Sets OUT as a numeric mapping output with the given VALUE. */
+static void
+set_map_out_num (struct map_out *out, double value) 
+{
+  out->copy_input = false;
+  out->value.f = value;
+  out->width = 0;
+}
+
+/* Sets OUT as a string mapping output with the given VALUE. */
+static void
+set_map_out_str (struct map_out *out, struct pool *pool,
+                 const struct string *value)
+{
+  const char *string = ds_data (value);
+  size_t length = ds_length (value);
+
+  out->copy_input = false;
+  out->value.c = pool_alloc_unaligned (pool, length);
+  memcpy (out->value.c, string, length);
+  out->width = length;
+}
+
+/* Parses a set of target variables into TRNS->dst_vars and
+   TRNS->dst_names. */
+static bool
+parse_dst_vars (struct recode_trns *trns) 
+{
+  size_t i;
+  
+  if (lex_match_id ("INTO"))
+    {
+      size_t name_cnt;
+      size_t i;
+
+      if (!parse_mixed_vars_pool (trns->pool, &trns->dst_names, &name_cnt,
+                                  PV_NONE))
+        return false;
+
+      if (name_cnt != trns->var_cnt)
+        {
+          msg (SE, _("%u variable(s) cannot be recoded into "
+                     "%u variable(s).  Specify the same number "
+                     "of variables as source and target variables."),
+               (unsigned) trns->var_cnt, (unsigned) name_cnt);
+          return false;
+        }
+
+      trns->dst_vars = pool_nalloc (trns->pool,
+                                    trns->var_cnt, sizeof *trns->dst_vars);
+      for (i = 0; i < trns->var_cnt; i++)
+        {
+          struct variable *v;
+          v = trns->dst_vars[i] = dict_lookup_var (default_dict,
+                                                  trns->dst_names[i]);
+          if (v == NULL && trns->dst_type == ALPHA) 
+            {
+              msg (SE, _("There is no variable named "
+                         "%s.  (All string variables specified "
+                         "on INTO must already exist.  Use the "
+                         "STRING command to create a string "
+                         "variable.)"),
+                   trns->dst_names[i]);
+              return false;
+            }
+        }
+    }
+  else 
+    {
+      trns->dst_vars = trns->src_vars;
+      if (trns->src_type != trns->dst_type)
+        {
+          msg (SE, _("INTO is required with %s input values "
+                     "and %s output values."),
+               var_type_adj (trns->src_type),
+               var_type_adj (trns->dst_type));
+          return false;
+        }
+    }
+
+  for (i = 0; i < trns->var_cnt; i++)
+    {
+      struct variable *v = trns->dst_vars[i];
+      if (v != NULL && v->type != trns->dst_type)
+        {
+          msg (SE, _("Type mismatch.  Cannot store %s data in "
+                     "%s variable %s."),
+               trns->dst_type == ALPHA ? _("string") : _("numeric"),
+               v->type == ALPHA ? _("string") : _("numeric"),
+               v->name);
+          return false;
+        }
+    }
+
+  return true;
+}
+
+/* Ensures that all the output values in TRNS are as wide as the
+   widest destination variable. */
+static void
+enlarge_dst_widths (struct recode_trns *trns) 
+{
+  size_t max_dst_width;
+  size_t i;
+
+  max_dst_width = 0;
+  for (i = 0; i < trns->var_cnt; i++)
+    {
+      struct variable *v = trns->dst_vars[i];
+      if (v->width > max_dst_width)
+        max_dst_width = v->width;
+    }
+
+  for (i = 0; i < trns->map_cnt; i++)
+    {
+      struct map_out *out = &trns->mappings[i].out;
+      if (!out->copy_input && out->width < max_dst_width) 
+        {
+          char *s = pool_alloc_unaligned (trns->pool, max_dst_width + 1);
+          str_copy_rpad (s, max_dst_width + 1, out->value.c);
+          out->value.c = s;
+        }
+    }
+}
+
+/* Creates destination variables that don't already exist. */
+static void
+create_dst_vars (struct recode_trns *trns)
+{
+  size_t i;
+
+  for (i = 0; i < trns->var_cnt; i++) 
+    {
+      struct variable **var = &trns->dst_vars[i];
+      const char *name = trns->dst_names[i];
+          
+      *var = dict_lookup_var (default_dict, name);
+      if (*var == NULL)
+        *var = dict_create_var_assert (default_dict, name, 0);
+      assert ((*var)->type == trns->dst_type);
+    }
+}
+\f
+/* Data transformation. */
+
+/* Returns the output mapping in TRNS for an input of VALUE on
+   variable V, or a null pointer if there is no mapping. */
+static const struct map_out *
+find_src_numeric (struct recode_trns *trns, double value, struct variable *v)
+{
+  struct mapping *m;
+
+  for (m = trns->mappings; m < trns->mappings + trns->map_cnt; m++)
+    {
+      const struct map_in *in = &m->in;
+      const struct map_out *out = &m->out;
+      bool match;
+      
+      switch (in->type)
+        {
+        case MAP_SINGLE:
+          match = value == in->x.f;
+          break;
+        case MAP_MISSING:
+          match = mv_is_num_user_missing (&v->miss, value);
+          break;
+        case MAP_RANGE:
+          match = value >= in->x.f && value <= in->y.f;
+          break;
+        case MAP_ELSE:
+          match = true;
+          break;
+        default:
+          abort ();
+        }
+
+      if (match)
+        return out;
+    }
+
+  return NULL;
+}
+
+/* Returns the output mapping in TRNS for an input of VALUE with
+   the given WIDTH, or a null pointer if there is no mapping. */
+static const struct map_out *
+find_src_string (struct recode_trns *trns, const char *value, int width)
+{
+  struct mapping *m;
+
+  for (m = trns->mappings; m < trns->mappings + trns->map_cnt; m++)
+    {
+      const struct map_in *in = &m->in;
+      struct map_out *out = &m->out;
+      bool match;
+      
+      switch (in->type)
+        {
+        case MAP_SINGLE:
+          match = !memcmp (value, in->x.c, width);
+          break;
+        case MAP_ELSE:
+          match = true;
+          break;
+        case MAP_CONVERT:
+          {
+            struct data_in di;
+
+            di.s = value;
+            di.e = value + width;
+            di.v = &out->value;
+            di.flags = DI_IGNORE_ERROR;
+            di.f1 = di.f2 = 0;
+            di.format.type = FMT_F;
+            di.format.w = width;
+            di.format.d = 0;
+            match = data_in (&di);
+            break;
+          }
+        default:
+          abort ();
+        }
+
+      if (match)
+        return out;
+    }
+
+  return NULL;
+}
+
+/* Performs RECODE transformation. */
+static int
+recode_trns_proc (void *trns_, struct ccase *c, int case_idx UNUSED)
+{
+  struct recode_trns *trns = trns_;
+  size_t i;
+
+  for (i = 0; i < trns->var_cnt; i++) 
+    {
+      struct variable *src_var = trns->src_vars[i];
+      struct variable *dst_var = trns->dst_vars[i];
+
+      const union value *src_data = case_data (c, src_var->fv);
+      union value *dst_data = case_data_rw (c, dst_var->fv);
+
+      const struct map_out *out;
+
+      if (trns->src_type == NUMERIC) 
+          out = find_src_numeric (trns, src_data->f, src_var);
+      else
+          out = find_src_string (trns, src_data->s, src_var->width);
+
+      if (trns->dst_type == NUMERIC) 
+        {
+          if (out != NULL)
+            dst_data->f = !out->copy_input ? out->value.f : src_data->f; 
+          else if (trns->src_vars != trns->dst_vars)
+            dst_data->f = SYSMIS;
+        }
+      else 
+        {
+          if (out != NULL)
+            {
+              if (!out->copy_input) 
+                memcpy (dst_data->s, out->value.c, dst_var->width); 
+              else if (trns->src_vars != trns->dst_vars)
+                buf_copy_rpad (dst_data->s, dst_var->width,
+                               src_data->s, src_var->width); 
+            }
+          else if (trns->src_vars != trns->dst_vars)
+            memset (dst_data->s, ' ', dst_var->width);
+        }
+    }
+
+  return TRNS_CONTINUE;
+}
+
+/* Frees a RECODE transformation. */
+static bool
+recode_trns_free (void *trns_)
+{
+  struct recode_trns *trns = trns_;
+  pool_destroy (trns->pool);
+  return true;
+}
diff --git a/src/language/xforms/sample.c b/src/language/xforms/sample.c
new file mode 100644 (file)
index 0000000..0635caa
--- /dev/null
@@ -0,0 +1,156 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <gsl/gsl_rng.h>
+#include <limits.h>
+#include <stdio.h>
+#include <math.h>
+#include "alloc.h"
+#include "command.h"
+#include "message.h"
+#include "lexer.h"
+#include "random.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#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
+  {
+    int type;                  /* One of TYPE_*. */
+    int n, N;                  /* TYPE_A_FROM_B: n from N. */
+    int m, t;                  /* TYPE_A_FROM_B: # picked so far; # so far. */
+    unsigned frac;              /* TYPE_FRACTION: a fraction of UINT_MAX. */
+  };
+
+static trns_proc_func sample_trns_proc;
+static trns_free_func sample_trns_free;
+
+int
+cmd_sample (void)
+{
+  struct sample_trns *trns;
+
+  int type;
+  int a, b;
+  unsigned frac;
+
+  if (!lex_force_num ())
+    return CMD_FAILURE;
+  if (!lex_is_integer ())
+    {
+      unsigned long min = gsl_rng_min (get_rng ());
+      unsigned long max = gsl_rng_max (get_rng ());
+
+      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 * (max - min) + min;
+      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 ();
+
+  trns = xmalloc (sizeof *trns);
+  trns->type = type;
+  trns->n = a;
+  trns->N = b;
+  trns->m = trns->t = 0;
+  trns->frac = frac;
+  add_transformation (sample_trns_proc, sample_trns_free, trns);
+
+  return lex_end_of_command ();
+}
+
+/* Executes a SAMPLE transformation. */
+static int
+sample_trns_proc (void *t_, struct ccase *c UNUSED,
+                  int case_num UNUSED)
+{
+  struct sample_trns *t = t_;
+  double U;
+
+  if (t->type == TYPE_FRACTION) 
+    {
+      if (gsl_rng_get (get_rng ()) <= t->frac)
+        return TRNS_CONTINUE;
+      else
+        return TRNS_DROP_CASE;
+    }
+
+  if (t->m >= t->n)
+    return TRNS_DROP_CASE;
+
+  U = gsl_rng_uniform (get_rng ());
+  if ((t->N - t->t) * U >= t->n - t->m)
+    {
+      t->t++;
+      return TRNS_DROP_CASE;
+    }
+  else
+    {
+      t->m++;
+      t->t++;
+      return TRNS_CONTINUE;
+    }
+}
+
+static bool
+sample_trns_free (void *t_) 
+{
+  struct sample_trns *t = t_;
+  free (t);
+  return true;
+}
diff --git a/src/language/xforms/select-if.c b/src/language/xforms/select-if.c
new file mode 100644 (file)
index 0000000..23426ed
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "expressions/public.h"
+#include "lexer.h"
+#include "str.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* SELECT IF transformation. */
+struct select_if_trns
+  {
+    struct expression *e;      /* Test expression. */
+  };
+
+static trns_proc_func select_if_proc;
+static trns_free_func select_if_free;
+
+/* Parses the SELECT IF transformation. */
+int
+cmd_select_if (void)
+{
+  struct expression *e;
+  struct select_if_trns *t;
+
+  e = expr_parse (default_dict, EXPR_BOOLEAN);
+  if (!e)
+    return CMD_CASCADING_FAILURE;
+
+  if (token != '.')
+    {
+      expr_free (e);
+      lex_error (_("expecting end of command"));
+      return CMD_CASCADING_FAILURE;
+    }
+
+  t = xmalloc (sizeof *t);
+  t->e = e;
+  add_transformation (select_if_proc, select_if_free, t);
+
+  return CMD_SUCCESS;
+}
+
+/* Performs the SELECT IF transformation T on case C. */
+static int
+select_if_proc (void *t_, struct ccase *c,
+                int case_num)
+{
+  struct select_if_trns *t = t_;
+  return (expr_evaluate_num (t->e, c, case_num) == 1.0
+          ? TRNS_CONTINUE : TRNS_DROP_CASE);
+}
+
+/* Frees SELECT IF transformation T. */
+static bool
+select_if_free (void *t_)
+{
+  struct select_if_trns *t = t_;
+  expr_free (t->e);
+  free (t);
+  return true;
+}
+
+/* Parses the FILTER command. */
+int
+cmd_filter (void)
+{
+  if (lex_match_id ("OFF"))
+    dict_set_filter (default_dict, NULL);
+  else
+    {
+      struct variable *v;
+
+      lex_match (T_BY);
+      v = parse_variable ();
+      if (!v)
+       return CMD_CASCADING_FAILURE;
+
+      if (v->type == ALPHA)
+       {
+         msg (SE, _("The filter variable must be numeric."));
+         return CMD_CASCADING_FAILURE;
+       }
+
+      if (dict_class_from_id (v->name) == DC_SCRATCH)
+       {
+         msg (SE, _("The filter variable may not be scratch."));
+         return CMD_CASCADING_FAILURE;
+       }
+
+      dict_set_filter (default_dict, v);
+    }
+
+  return CMD_SUCCESS;
+}
+\f
+/* Expression on PROCESS IF. */
+struct expression *process_if_expr;
+
+/* Parses the PROCESS IF command. */
+int
+cmd_process_if (void)
+{
+  struct expression *e;
+
+  e = expr_parse (default_dict, EXPR_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/libpspp/ChangeLog b/src/libpspp/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/libpspp/alloc.c b/src/libpspp/alloc.c
new file mode 100644 (file)
index 0000000..a7b2028
--- /dev/null
@@ -0,0 +1,32 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "alloc.h"
+#include <stdlib.h>
+
+/* Allocates and returns N elements of S bytes each.
+   N must be nonnegative, S must be positive.
+   Returns a null pointer if the memory cannot be obtained,
+   including the case where N * S overflows the range of size_t. */
+void *
+nmalloc (size_t n, size_t s) 
+{
+  return !xalloc_oversized (n, s) ? malloc (n * s) : NULL;
+}
diff --git a/src/libpspp/alloc.h b/src/libpspp/alloc.h
new file mode 100644 (file)
index 0000000..0f4492e
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !alloc_h
+#define alloc_h 1
+
+#include <stddef.h>
+
+/* malloc() wrapper functions. */
+#include "xalloc.h"
+
+void *nmalloc (size_t n, size_t s);
+
+/* alloca() wrapper functions. */
+#if defined (HAVE_ALLOCA) || defined (C_ALLOCA)
+#ifdef HAVE_ALLOCA_H
+#include <alloca.h>
+#endif
+#define local_alloc(X) alloca (X)
+#define local_free(P) ((void) 0)
+#else
+#define local_alloc(X) xmalloc (X)
+#define local_free(P) free (P)
+#endif
+
+#endif /* alloc.h */
diff --git a/src/libpspp/array.c b/src/libpspp/array.c
new file mode 100644 (file)
index 0000000..945874c
--- /dev/null
@@ -0,0 +1,987 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+/* Copyright (C) 2001 Free Software Foundation, Inc.
+  
+   This file is part of the GNU ISO C++ Library.  This library 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 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with this library; see the file COPYING.  If not, write to the Free
+   Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+   USA.
+
+   As a special exception, you may use this file as part of a free software
+   library without restriction.  Specifically, if other files instantiate
+   templates or use macros or inline functions from this file, or you compile
+   this file and link it with other files to produce an executable, this
+   file does not by itself cause the resulting executable to be covered by
+   the GNU General Public License.  This exception does not however
+   invalidate any other reasons why the executable file might be covered by
+   the GNU General Public License. */
+
+/*
+ *
+ * Copyright (c) 1994
+ * Hewlett-Packard Company
+ *
+ * Permission to use, copy, modify, distribute and sell this software
+ * and its documentation for any purpose is hereby granted without fee,
+ * provided that the above copyright notice appear in all copies and
+ * that both that copyright notice and this permission notice appear
+ * in supporting documentation.  Hewlett-Packard Company makes no
+ * representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied warranty.
+ *
+ *
+ * Copyright (c) 1996
+ * Silicon Graphics Computer Systems, Inc.
+ *
+ * Permission to use, copy, modify, distribute and sell this software
+ * and its documentation for any purpose is hereby granted without fee,
+ * provided that the above copyright notice appear in all copies and
+ * that both that copyright notice and this permission notice appear
+ * in supporting documentation.  Silicon Graphics makes no
+ * representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied warranty.
+ */
+
+/* Copyright (C) 1991, 1992, 1996, 1997, 1999 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 Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301 USA.  */
+
+#include <config.h>
+#include "array.h"
+#include <gsl/gsl_rng.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include "alloc.h"
+
+/* Some of the assertions in this file are very expensive.  We
+   don't use them by default. */
+#ifdef EXTRA_CHECKS
+#define expensive_assert(X) assert(X)
+#else
+#define expensive_assert(X) ((void) 0)
+#endif
+#include "message.h"
+\f
+/* Finds an element in ARRAY, which contains COUNT elements of
+   SIZE bytes each, using COMPARE for comparisons.  Returns the
+   first element in ARRAY that matches TARGET, or a null pointer
+   on failure.  AUX is passed to each comparison as auxiliary
+   data. */
+void *
+find (const void *array, size_t count, size_t size,
+      const void *target,
+      algo_compare_func *compare, void *aux) 
+{
+  const char *element = array;
+
+  while (count-- > 0) 
+    {
+      if (compare (target, element, aux) == 0)
+        return (void *) element;
+
+      element += size;
+    }
+
+  return NULL;
+}
+
+/* Counts and return the number of elements in ARRAY, which
+   contains COUNT elements of SIZE bytes each, which are equal to
+   ELEMENT as compared with COMPARE.  AUX is passed as auxiliary
+   data to COMPARE. */
+size_t
+count_equal (const void *array, size_t count, size_t size,
+             const void *element,
+             algo_compare_func *compare, void *aux)
+{
+  const char *first = array;
+  size_t equal_cnt = 0;
+
+  while (count-- > 0) 
+    {
+      if (compare (element, first, aux) == 0)
+        equal_cnt++;
+      
+      first += size;
+    }
+
+  return equal_cnt;
+}
+
+/* Counts and return the number of elements in ARRAY, which
+   contains COUNT elements of SIZE bytes each, for which
+   PREDICATE returns nonzero.  AUX is passed as auxiliary data to
+   PREDICATE. */
+size_t
+count_if (const void *array, size_t count, size_t size,
+          algo_predicate_func *predicate, void *aux) 
+{
+  const char *first = array;
+  size_t nonzero_cnt = 0;
+
+  while (count-- > 0) 
+    {
+      if (predicate (first, aux) != 0)
+        nonzero_cnt++;
+      
+      first += size;
+    }
+
+  return nonzero_cnt;
+}
+\f
+/* 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)
+
+/* Makes the elements in ARRAY unique, by moving up duplicates,
+   and returns the new number of elements in the array.  Sorted
+   arrays only.  Arguments same as for sort() above. */
+size_t
+unique (void *array, size_t count, size_t size,
+        algo_compare_func *compare, void *aux) 
+{
+  char *first = array;
+  char *last = first + size * count;
+  char *result = array;
+
+  for (;;) 
+    {
+      first += size;
+      if (first >= last) 
+        {
+          assert (adjacent_find_equal (array, count,
+                                       size, compare, aux) == NULL);
+          return count; 
+        }
+
+      if (compare (result, first, aux)) 
+        {
+          result += size;
+          if (result != first)
+            memcpy (result, first, size);
+        }
+      else 
+        count--;
+    }
+}
+
+/* Helper function that calls sort(), then unique(). */
+size_t
+sort_unique (void *array, size_t count, size_t size,
+             algo_compare_func *compare, void *aux) 
+{
+  sort (array, count, size, compare, aux);
+  return unique (array, count, size, compare, aux);
+}
+\f
+/* Reorders ARRAY, which contains COUNT elements of SIZE bytes
+   each, so that the elements for which PREDICATE returns nonzero
+   precede those for which PREDICATE returns zero.  AUX is
+   passed to each predicate as auxiliary data.  Returns the
+   number of elements for which PREDICATE returns nonzero.  Not
+   stable. */
+size_t 
+partition (void *array, size_t count, size_t size,
+           algo_predicate_func *predicate, void *aux) 
+{
+  size_t nonzero_cnt = count;
+  char *first = array;
+  char *last = first + nonzero_cnt * size;
+
+  for (;;)
+    {
+      /* Move FIRST forward to point to first element that fails
+         PREDICATE. */
+      for (;;) 
+        {
+          if (first == last)
+            goto done;
+          else if (!predicate (first, aux)) 
+            break;
+
+          first += size; 
+        }
+      nonzero_cnt--;
+
+      /* Move LAST backward to point to last element that passes
+         PREDICATE. */
+      for (;;) 
+        {
+          last -= size;
+
+          if (first == last)
+            goto done;
+          else if (predicate (last, aux)) 
+            break;
+          else
+            nonzero_cnt--;
+        }
+      
+      /* By swapping FIRST and LAST we extend the starting and
+         ending sequences that pass and fail, respectively,
+         PREDICATE. */
+      SWAP (first, last, size);
+      first += size;
+    }
+
+ done:
+  assert (is_partitioned (array, count, size, nonzero_cnt, predicate, aux));
+  return nonzero_cnt; 
+}
+
+/* Checks whether ARRAY, which contains COUNT elements of SIZE
+   bytes each, is partitioned such that PREDICATE returns nonzero
+   for the first NONZERO_CNT elements and zero for the remaining
+   elements.  AUX is passed as auxiliary data to PREDICATE. */
+int
+is_partitioned (const void *array, size_t count, size_t size,
+                size_t nonzero_cnt,
+                algo_predicate_func *predicate, void *aux) 
+{
+  const char *first = array;
+  size_t idx;
+
+  assert (nonzero_cnt <= count);
+  for (idx = 0; idx < nonzero_cnt; idx++)
+    if (predicate (first + idx * size, aux) == 0)
+      return 0;
+  for (idx = nonzero_cnt; idx < count; idx++)
+    if (predicate (first + idx * size, aux) != 0)
+      return 0;
+  return 1;
+}
+\f
+/* Copies the COUNT elements of SIZE bytes each from ARRAY to
+   RESULT, except that elements for which PREDICATE is false are
+   not copied.  Returns the number of elements copied.  AUX is
+   passed to PREDICATE as auxiliary data.  */
+size_t 
+copy_if (const void *array, size_t count, size_t size,
+         void *result,
+         algo_predicate_func *predicate, void *aux) 
+{
+  const char *input = array;
+  const char *last = input + size * count;
+  char *output = result;
+  size_t nonzero_cnt = 0;
+  
+  while (input < last)
+    {
+      if (predicate (input, aux)) 
+        {
+          memcpy (output, input, size);
+          output += size;
+          nonzero_cnt++;
+        }
+
+      input += size;
+    }
+
+  assert (nonzero_cnt == count_if (array, count, size, predicate, aux));
+  assert (nonzero_cnt == count_if (result, nonzero_cnt, size, predicate, aux));
+
+  return nonzero_cnt;
+}
+
+/* Removes N elements starting at IDX from ARRAY, which consists
+   of COUNT elements of SIZE bytes each, by shifting the elements
+   following them, if any, into its position. */
+void
+remove_range (void *array_, size_t count, size_t size,
+              size_t idx, size_t n) 
+{
+  char *array = array_;
+  
+  assert (array != NULL);
+  assert (idx <= count);
+  assert (idx + n <= count);
+
+  if (idx + n < count)
+    memmove (array + idx * size, array + (idx + n) * size,
+             size * (count - idx - n));
+}
+
+/* Removes element IDX from ARRAY, which consists of COUNT
+   elements of SIZE bytes each, by shifting the elements
+   following it, if any, into its position. */
+void
+remove_element (void *array, size_t count, size_t size,
+                size_t idx) 
+{
+  remove_range (array, count, size, idx, 1);
+}
+
+/* Moves an element in ARRAY, which consists of COUNT elements of
+   SIZE bytes each, from OLD_IDX to NEW_IDX, shifting around
+   other elements as needed.  Runs in O(abs(OLD_IDX - NEW_IDX))
+   time. */
+void
+move_element (void *array_, size_t count, size_t size,
+              size_t old_idx, size_t new_idx) 
+{
+  assert (array_ != NULL || count == 0);
+  assert (old_idx < count);
+  assert (new_idx < count);
+  
+  if (old_idx != new_idx) 
+    {
+      char *array = array_;
+      char *element = xmalloc (size);
+      char *new = array + new_idx * size;
+      char *old = array + old_idx * size;
+
+      memcpy (element, old, size);
+      if (new < old)
+        memmove (new + size, new, (old_idx - new_idx) * size);
+      else
+        memmove (old, old + size, (new_idx - old_idx) * size);
+      memcpy (new, element, size);
+
+      free (element);
+    }
+}
+
+/* A predicate and its auxiliary data. */
+struct pred_aux 
+  {
+    algo_predicate_func *predicate;
+    void *aux;
+  };
+
+static int
+not (const void *data, void *pred_aux_) 
+{
+  const struct pred_aux *pred_aux = pred_aux_;
+
+  return !pred_aux->predicate (data, pred_aux->aux);
+}
+
+/* Removes elements equal to ELEMENT from ARRAY, which consists
+   of COUNT elements of SIZE bytes each.  Returns the number of
+   remaining elements.  AUX is passed to COMPARE as auxiliary
+   data. */
+size_t
+remove_equal (void *array, size_t count, size_t size,
+              void *element,
+              algo_compare_func *compare, void *aux) 
+{
+  char *first = array;
+  char *last = first + count * size;
+  char *result;
+
+  for (;;)
+    {
+      if (first >= last)
+        goto done;
+      if (compare (first, element, aux) == 0)
+        break;
+
+      first += size;
+    }
+
+  result = first;
+  count--;
+  for (;;) 
+    {
+      first += size;
+      if (first >= last)
+        goto done;
+
+      if (compare (first, element, aux) == 0) 
+        {
+          count--; 
+          continue;
+        }
+      
+      memcpy (result, first, size);
+      result += size;
+    }
+
+ done:
+  assert (count_equal (array, count, size, element, compare, aux) == 0);
+  return count;
+}
+
+/* Copies the COUNT elements of SIZE bytes each from ARRAY to
+   RESULT, except that elements for which PREDICATE is true are
+   not copied.  Returns the number of elements copied.  AUX is
+   passed to PREDICATE as auxiliary data.  */
+size_t 
+remove_copy_if (const void *array, size_t count, size_t size,
+                void *result,
+                algo_predicate_func *predicate, void *aux) 
+{
+  struct pred_aux pred_aux;
+  pred_aux.predicate = predicate;
+  pred_aux.aux = aux;
+  return copy_if (array, count, size, result, not, &pred_aux);
+}
+\f
+/* Searches ARRAY, which contains COUNT of SIZE bytes each, using
+   a binary search.  Returns any element that equals VALUE, if
+   one exists, or a null pointer otherwise.  ARRAY must ordered
+   according to COMPARE.  AUX is passed to COMPARE as auxiliary
+   data. */
+void *
+binary_search (const void *array, size_t count, size_t size,
+               void *value,
+               algo_compare_func *compare, void *aux) 
+{
+  assert (array != NULL);
+  assert (count <= INT_MAX);
+  assert (compare != NULL);
+
+  if (count != 0) 
+    {
+      const char *first = array;
+      int low = 0;
+      int high = count - 1;
+
+      while (low <= high) 
+        {
+          int middle = (low + high) / 2;
+          const char *element = first + middle * size;
+          int cmp = compare (value, element, aux);
+
+          if (cmp > 0) 
+            low = middle + 1;
+          else if (cmp < 0)
+            high = middle - 1;
+          else
+            return (void *) element;
+        }
+    }
+
+  expensive_assert (find (array, count, size, value, compare, aux) == NULL);
+  return NULL;
+}
+\f
+/* Lexicographically compares ARRAY1, which contains COUNT1
+   elements of SIZE bytes each, to ARRAY2, which contains COUNT2
+   elements of SIZE bytes, according to COMPARE.  Returns a
+   strcmp()-type result.  AUX is passed to COMPARE as auxiliary
+   data. */
+int
+lexicographical_compare_3way (const void *array1, size_t count1,
+                              const void *array2, size_t count2,
+                              size_t size,
+                              algo_compare_func *compare, void *aux) 
+{
+  const char *first1 = array1;
+  const char *first2 = array2;
+  size_t min_count = count1 < count2 ? count1 : count2;
+
+  while (min_count > 0)
+    {
+      int cmp = compare (first1, first2, aux);
+      if (cmp != 0)
+        return cmp;
+
+      first1 += size;
+      first2 += size;
+      min_count--;
+    }
+
+  return count1 < count2 ? -1 : count1 > count2;
+}
+\f
+/* If you consider tuning this algorithm, you should consult first:
+   Engineering a sort function; Jon Bentley and M. Douglas McIlroy;
+   Software - Practice and Experience; Vol. 23 (11), 1249-1265, 1993.  */
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* 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. */
+/* The stack needs log (total_elements) entries (we could even subtract
+   log(MAX_THRESH)).  Since total_elements has type size_t, we get as
+   upper bound for log (total_elements):
+   bits per byte (CHAR_BIT) * sizeof(size_t).  */
+#define STACK_SIZE     (CHAR_BIT * sizeof(size_t))
+#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 SIZE_MAX is allocated on the
+      stack.  Assuming a 32-bit (64 bit) integer for size_t, this needs
+      only 32 * sizeof(stack_node) == 256 bytes (for 64 bit: 1024 bytes).
+      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 segments.
+
+   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 (total_elems)
+      stack size is needed (actually O(1) in this case)!  */
+
+void
+sort (void *array, size_t count, size_t size,
+      algo_compare_func *compare, void *aux)
+{
+  char *const first = array;
+  const size_t max_thresh = MAX_THRESH * size;
+
+  if (count == 0)
+    /* Avoid lossage with unsigned arithmetic below.  */
+    return;
+
+  if (count > MAX_THRESH)
+    {
+      char *lo = first;
+      char *hi = &lo[size * (count - 1)];
+      stack_node stack[STACK_SIZE];
+      stack_node *top = stack + 1;
+
+      while (STACK_NOT_EMPTY)
+        {
+          char *left_ptr;
+          char *right_ptr;
+
+         /* 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 in
+            the while loops. */
+
+         char *mid = lo + size * ((hi - lo) / size >> 1);
+
+         if (compare (mid, lo, aux) < 0)
+           SWAP (mid, lo, size);
+         if (compare (hi, mid, aux) < 0)
+           SWAP (mid, hi, size);
+         else
+           goto jump_over;
+         if (compare (mid, lo, aux) < 0)
+           SWAP (mid, lo, size);
+       jump_over:;
+
+         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 (compare (left_ptr, mid, aux) < 0)
+               left_ptr += size;
+
+             while (compare (mid, right_ptr, aux) < 0)
+               right_ptr -= size;
+
+             if (left_ptr < right_ptr)
+               {
+                 SWAP (left_ptr, right_ptr, size);
+                 if (mid == left_ptr)
+                   mid = right_ptr;
+                 else if (mid == right_ptr)
+                   mid = left_ptr;
+                 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 FIRST array is partially sorted by quicksort the rest
+     is completely sorted using insertion sort, since this is efficient
+     for partitions below MAX_THRESH size. FIRST 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 = &first[size * (count - 1)];
+    char *tmp_ptr = first;
+    char *thresh = min(end_ptr, first + 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 (compare (run_ptr, tmp_ptr, aux) < 0)
+        tmp_ptr = run_ptr;
+
+    if (tmp_ptr != first)
+      SWAP (tmp_ptr, first, size);
+
+    /* Insertion sort, running from left-hand-side up to right-hand-side.  */
+
+    run_ptr = first + size;
+    while ((run_ptr += size) <= end_ptr)
+      {
+       tmp_ptr = run_ptr - size;
+       while (compare (run_ptr, tmp_ptr, aux) < 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;
+              }
+          }
+      }
+  }
+
+  assert (is_sorted (array, count, size, compare, aux));
+}
+
+/* Tests whether ARRAY, which contains COUNT elements of SIZE
+   bytes each, is sorted in order according to COMPARE.  AUX is
+   passed to COMPARE as auxiliary data. */
+int
+is_sorted (const void *array, size_t count, size_t size,
+           algo_compare_func *compare, void *aux) 
+{
+  const char *first = array;
+  size_t idx;
+      
+  for (idx = 0; idx + 1 < count; idx++)
+    if (compare (first + idx * size, first + (idx + 1) * size, aux) > 0)
+      return 0; 
+  
+  return 1;
+}
+\f
+/* Computes the generalized set difference, ARRAY1 minus ARRAY2,
+   into RESULT, and returns the number of elements written to
+   RESULT.  If a value appears M times in ARRAY1 and N times in
+   ARRAY2, then it will appear max(M - N, 0) in RESULT.  ARRAY1
+   and ARRAY2 must be sorted, and RESULT is sorted and stable.
+   ARRAY1 consists of COUNT1 elements, ARRAY2 of COUNT2 elements,
+   each SIZE bytes.  AUX is passed to COMPARE as auxiliary
+   data. */
+size_t set_difference (const void *array1, size_t count1,
+                       const void *array2, size_t count2,
+                       size_t size,
+                       void *result_,
+                       algo_compare_func *compare, void *aux) 
+{
+  const char *first1 = array1;
+  const char *last1 = first1 + count1 * size;
+  const char *first2 = array2;
+  const char *last2 = first2 + count2 * size;
+  char *result = result_;
+  size_t result_count = 0;
+  
+  while (first1 != last1 && first2 != last2) 
+    {
+      int cmp = compare (first1, first2, aux);
+      if (cmp < 0)
+        {
+          memcpy (result, first1, size);
+          first1 += size;
+          result += size;
+          result_count++;
+        }
+      else if (cmp > 0)
+        first2 += size;
+      else
+        {
+          first1 += size;
+          first2 += size;
+        }
+    }
+
+  while (first1 != last1) 
+    {
+      memcpy (result, first1, size);
+      first1 += size;
+      result += size;
+      result_count++;
+    }
+
+  return result_count;
+}
+\f
+/* Finds the first pair of adjacent equal elements in ARRAY,
+   which has COUNT elements of SIZE bytes.  Returns the first
+   element in ARRAY such that COMPARE returns zero when it and
+   its successor element are compared, or a null pointer if no
+   such element exists.  AUX is passed to COMPARE as auxiliary
+   data. */
+void *
+adjacent_find_equal (const void *array, size_t count, size_t size,
+                     algo_compare_func *compare, void *aux) 
+{
+  const char *first = array;
+  const char *last = first + count * size;
+
+  while (first < last && first + size < last) 
+    {
+      if (compare (first, first + size, aux) == 0)
+        return (void *) first;
+      first += size;
+    }
+
+  return NULL;
+}
+\f
+/* ARRAY contains COUNT elements of SIZE bytes each.  Initially
+   the first COUNT - 1 elements of these form a heap, followed by
+   a single element not part of the heap.  This function adds the
+   final element, forming a heap of COUNT elements in ARRAY.
+   Uses COMPARE to compare elements, passing AUX as auxiliary
+   data. */
+void
+push_heap (void *array, size_t count, size_t size,
+           algo_compare_func *compare, void *aux) 
+{
+  char *first = array;
+  size_t i;
+  
+  expensive_assert (count < 1 || is_heap (array, count - 1,
+                                          size, compare, aux));
+  for (i = count; i > 1; i /= 2) 
+    {
+      char *parent = first + (i / 2 - 1) * size;
+      char *element = first + (i - 1) * size;
+      if (compare (parent, element, aux) < 0)
+        SWAP (parent, element, size);
+      else
+        break; 
+    }
+  expensive_assert (is_heap (array, count, size, compare, aux));
+}
+
+/* ARRAY contains COUNT elements of SIZE bytes each.  Initially
+   the children of ARRAY[idx - 1] are heaps, but ARRAY[idx - 1]
+   may be smaller than its children.  This function fixes that,
+   so that ARRAY[idx - 1] itself is a heap.  Uses COMPARE to
+   compare elements, passing AUX as auxiliary data. */
+static void
+heapify (void *array, size_t count, size_t size,
+         size_t idx,
+         algo_compare_func *compare, void *aux) 
+{
+  char *first = array;
+  
+  for (;;) 
+    {
+      size_t left = 2 * idx;
+      size_t right = left + 1;
+      size_t largest = idx;
+
+      if (left <= count
+          && compare (first + size * (left - 1),
+                      first + size * (idx - 1), aux) > 0)
+        largest = left;
+
+      if (right <= count
+          && compare (first + size * (right - 1),
+                      first + size * (largest - 1), aux) > 0)
+        largest = right;
+
+      if (largest == idx)
+        break;
+
+      SWAP (first + size * (idx - 1), first + size * (largest - 1), size);
+      idx = largest;
+    }
+}
+
+/* ARRAY contains COUNT elements of SIZE bytes each.  Initially
+   all COUNT elements form a heap.  This function moves the
+   largest element in the heap to the final position in ARRAY and
+   reforms a heap of the remaining COUNT - 1 elements at the
+   beginning of ARRAY.  Uses COMPARE to compare elements, passing
+   AUX as auxiliary data. */
+void
+pop_heap (void *array, size_t count, size_t size,
+          algo_compare_func *compare, void *aux) 
+{
+  char *first = array;
+
+  expensive_assert (is_heap (array, count, size, compare, aux));
+  SWAP (first, first + (count - 1) * size, size);
+  heapify (first, count - 1, size, 1, compare, aux);
+  expensive_assert (count < 1 || is_heap (array, count - 1,
+                                          size, compare, aux));
+}
+
+/* Turns ARRAY, which contains COUNT elements of SIZE bytes, into
+   a heap.  Uses COMPARE to compare elements, passing AUX as
+   auxiliary data. */
+void
+make_heap (void *array, size_t count, size_t size,
+           algo_compare_func *compare, void *aux) 
+{
+  size_t idx;
+  
+  for (idx = count / 2; idx >= 1; idx--)
+    heapify (array, count, size, idx, compare, aux);
+  expensive_assert (count < 1 || is_heap (array, count, size, compare, aux));
+}
+
+/* ARRAY contains COUNT elements of SIZE bytes each.  Initially
+   all COUNT elements form a heap.  This function turns the heap
+   into a fully sorted array.  Uses COMPARE to compare elements,
+   passing AUX as auxiliary data. */
+void
+sort_heap (void *array, size_t count, size_t size,
+           algo_compare_func *compare, void *aux) 
+{
+  char *first = array;
+  size_t idx;
+
+  expensive_assert (is_heap (array, count, size, compare, aux));
+  for (idx = count; idx >= 2; idx--)
+    {
+      SWAP (first, first + (idx - 1) * size, size);
+      heapify (array, idx - 1, size, 1, compare, aux);
+    }
+  expensive_assert (is_sorted (array, count, size, compare, aux));
+}
+
+/* ARRAY contains COUNT elements of SIZE bytes each.  This
+   function tests whether ARRAY is a heap and returns 1 if so, 0
+   otherwise.  Uses COMPARE to compare elements, passing AUX as
+   auxiliary data. */
+int
+is_heap (const void *array, size_t count, size_t size,
+         algo_compare_func *compare, void *aux) 
+{
+  const char *first = array;
+  size_t child;
+  
+  for (child = 2; child <= count; child++)
+    {
+      size_t parent = child / 2;
+      if (compare (first + (parent - 1) * size,
+                   first + (child - 1) * size, aux) < 0)
+        return 0;
+    }
+
+  return 1;
+}
+
diff --git a/src/libpspp/array.h b/src/libpspp/array.h
new file mode 100644 (file)
index 0000000..10e589a
--- /dev/null
@@ -0,0 +1,212 @@
+#ifndef ALGORITHM_H
+#define ALGORITHM_H 1
+
+#include <stddef.h>
+
+/* Compares A and B, given auxiliary data AUX, and returns a
+   strcmp()-type result. */
+typedef int algo_compare_func (const void *a, const void *b, void *aux);
+
+/* Tests a predicate on DATA, given auxiliary data AUX, and
+   returns nonzero if true or zero if false. */
+typedef int algo_predicate_func (const void *data, void *aux);
+
+/* Returns a random number in the range 0 through MAX exclusive,
+   given auxiliary data AUX. */
+typedef unsigned algo_random_func (unsigned max, void *aux);
+
+/* A generally suitable random function. */
+algo_random_func algo_default_random;
+
+/* Finds an element in ARRAY, which contains COUNT elements of
+   SIZE bytes each, using COMPARE for comparisons.  Returns the
+   first element in ARRAY that matches TARGET, or a null pointer
+   on failure.  AUX is passed to each comparison as auxiliary
+   data. */
+void *find (const void *array, size_t count, size_t size,
+            const void *target,
+            algo_compare_func *compare, void *aux);
+
+/* Counts and return the number of elements in ARRAY, which
+   contains COUNT elements of SIZE bytes each, which are equal to
+   ELEMENT as compared with COMPARE.  AUX is passed as auxiliary
+   data to COMPARE. */
+size_t count_equal (const void *array, size_t count, size_t size,
+                    const void *element,
+                    algo_compare_func *compare, void *aux);
+
+/* Counts and return the number of elements in ARRAY, which
+   contains COUNT elements of SIZE bytes each, for which
+   PREDICATE returns nonzero.  AUX is passed as auxiliary data to
+   PREDICATE. */
+size_t count_if (const void *array, size_t count, size_t size,
+                 algo_predicate_func *predicate, void *aux);
+
+/* Sorts ARRAY, which contains COUNT elements of SIZE bytes each,
+   using COMPARE for comparisons.  AUX is passed to each
+   comparison as auxiliary data. */
+void sort (void *array, size_t count, size_t size,
+           algo_compare_func *compare, void *aux);
+
+/* Tests whether ARRAY, which contains COUNT elements of SIZE
+   bytes each, is sorted in order according to COMPARE.  AUX is
+   passed to COMPARE as auxiliary data. */
+int is_sorted (const void *array, size_t count, size_t size,
+               algo_compare_func *compare, void *aux);
+
+/* Makes the elements in ARRAY unique, by moving up duplicates,
+   and returns the new number of elements in the array.  Sorted
+   arrays only.  Arguments same as for sort() above. */
+size_t unique (void *array, size_t count, size_t size,
+               algo_compare_func *compare, void *aux);
+
+/* Helper function that calls sort(), then unique(). */
+size_t sort_unique (void *array, size_t count, size_t size,
+                    algo_compare_func *compare, void *aux);
+
+/* Reorders ARRAY, which contains COUNT elements of SIZE bytes
+   each, so that the elements for which PREDICATE returns nonzero
+   precede those for which PREDICATE returns zero.  AUX is passed
+   as auxiliary data to PREDICATE.  Returns the number of
+   elements for which PREDICATE returns nonzero.  Not stable. */
+size_t partition (void *array, size_t count, size_t size,
+                  algo_predicate_func *predicate, void *aux);
+
+/* Checks whether ARRAY, which contains COUNT elements of SIZE
+   bytes each, is partitioned such that PREDICATE returns nonzero
+   for the first NONZERO_CNT elements and zero for the remaining
+   elements.  AUX is passed as auxiliary data to PREDICATE. */
+int is_partitioned (const void *array, size_t count, size_t size,
+                    size_t nonzero_cnt,
+                    algo_predicate_func *predicate, void *aux);
+
+/* Randomly reorders ARRAY, which contains COUNT elements of SIZE
+   bytes each.  Uses RANDOM as a source of random data, passing
+   AUX as the auxiliary data.  RANDOM may be null to use a
+   default random source. */
+void random_shuffle (void *array, size_t count, size_t size,
+                     algo_random_func *random, void *aux);
+
+/* Copies the COUNT elements of SIZE bytes each from ARRAY to
+   RESULT, except that elements for which PREDICATE is false are
+   not copied.  Returns the number of elements copied.  AUX is
+   passed to PREDICATE as auxiliary data.  */
+size_t copy_if (const void *array, size_t count, size_t size,
+                void *result,
+                algo_predicate_func *predicate, void *aux);
+
+/* Removes N elements starting at IDX from ARRAY, which consists
+   of COUNT elements of SIZE bytes each, by shifting the elements
+   following them, if any, into its position. */
+void remove_range (void *array, size_t count, size_t size,
+                   size_t idx, size_t n);
+
+/* Removes element IDX from ARRAY, which consists of COUNT
+   elements of SIZE bytes each, by shifting the elements
+   following it, if any, into its position. */
+void remove_element (void *array, size_t count, size_t size,
+                     size_t idx);
+
+/* Moves an element in ARRAY, which consists of COUNT elements of
+   SIZE bytes each, from OLD_IDX to NEW_IDX, shifting around
+   other elements as needed.  Runs in O(abs(OLD_IDX - NEW_IDX))
+   time. */
+void move_element (void *array, size_t count, size_t size,
+                   size_t old_idx, size_t new_idx);
+
+/* Removes elements equal to ELEMENT from ARRAY, which consists
+   of COUNT elements of SIZE bytes each.  Returns the number of
+   remaining elements.  AUX is passed to COMPARE as auxiliary
+   data. */
+size_t remove_equal (void *array, size_t count, size_t size,
+                     void *element,
+                     algo_compare_func *compare, void *aux);
+
+/* Copies the COUNT elements of SIZE bytes each from ARRAY to
+   RESULT, except that elements for which PREDICATE is true are
+   not copied.  Returns the number of elements copied.  AUX is
+   passed to PREDICATE as auxiliary data.  */
+size_t remove_copy_if (const void *array, size_t count, size_t size,
+                       void *result,
+                       algo_predicate_func *predicate, void *aux);
+
+/* Searches ARRAY, which contains COUNT elements of SIZE bytes
+   each, for VALUE, using a binary search.  ARRAY must ordered
+   according to COMPARE.  AUX is passed to COMPARE as auxiliary
+   data. */
+void *binary_search (const void *array, size_t count, size_t size,
+                     void *value,
+                     algo_compare_func *compare, void *aux);
+
+/* Lexicographically compares ARRAY1, which contains COUNT1
+   elements of SIZE bytes each, to ARRAY2, which contains COUNT2
+   elements of SIZE bytes, according to COMPARE.  Returns a
+   strcmp()-type result.  AUX is passed to COMPARE as auxiliary
+   data. */
+int lexicographical_compare_3way (const void *array1, size_t count1,
+                                  const void *array2, size_t count2,
+                                  size_t size,
+                                  algo_compare_func *compare, void *aux);
+
+/* Computes the generalized set difference, ARRAY1 minus ARRAY2,
+   into RESULT, and returns the number of elements written to
+   RESULT.  If a value appears M times in ARRAY1 and N times in
+   ARRAY2, then it will appear max(M - N, 0) in RESULT.  ARRAY1
+   and ARRAY2 must be sorted, and RESULT is sorted and stable.
+   ARRAY1 consists of COUNT1 elements, ARRAY2 of COUNT2 elements,
+   each SIZE bytes.  AUX is passed to COMPARE as auxiliary
+   data. */
+size_t set_difference (const void *array1, size_t count1,
+                       const void *array2, size_t count2,
+                       size_t size,
+                       void *result,
+                       algo_compare_func *compare, void *aux);
+
+/* Finds the first pair of adjacent equal elements in ARRAY,
+   which has COUNT elements of SIZE bytes.  Returns the first
+   element in ARRAY such that COMPARE returns zero when it and
+   its successor element are compared.  AUX is passed to COMPARE
+   as auxiliary data. */
+void *adjacent_find_equal (const void *array, size_t count, size_t size,
+                           algo_compare_func *compare, void *aux);
+
+/* ARRAY contains COUNT elements of SIZE bytes each.  Initially
+   the first COUNT - 1 elements of these form a heap, followed by
+   a single element not part of the heap.  This function adds the
+   final element, forming a heap of COUNT elements in ARRAY.
+   Uses COMPARE to compare elements, passing AUX as auxiliary
+   data. */
+void push_heap (void *array, size_t count, size_t size,
+                algo_compare_func *compare, void *aux);
+
+/* ARRAY contains COUNT elements of SIZE bytes each.  Initially
+   all COUNT elements form a heap.  This function moves the
+   largest element in the heap to the final position in ARRAY and
+   reforms a heap of the remaining COUNT - 1 elements at the
+   beginning of ARRAY.  Uses COMPARE to compare elements, passing
+   AUX as auxiliary data. */
+void pop_heap (void *array, size_t count, size_t size,
+               algo_compare_func *compare, void *aux);
+
+/* Turns ARRAY, which contains COUNT elements of SIZE bytes, into
+   a heap.  Uses COMPARE to compare elements, passing AUX as
+   auxiliary data. */
+void make_heap (void *array, size_t count, size_t size,
+                algo_compare_func *compare, void *aux);
+
+/* ARRAY contains COUNT elements of SIZE bytes each.  Initially
+   all COUNT elements form a heap.  This function turns the heap
+   into a fully sorted array.  Uses COMPARE to compare elements,
+   passing AUX as auxiliary data. */
+void sort_heap (void *array, size_t count, size_t size,
+                algo_compare_func *compare, void *aux);
+
+/* ARRAY contains COUNT elements of SIZE bytes each.  This
+   function tests whether ARRAY is a heap and returns 1 if so, 0
+   otherwise.  Uses COMPARE to compare elements, passing AUX as
+   auxiliary data. */
+int is_heap (const void *array, size_t count, size_t size,
+             algo_compare_func *compare, void *aux);
+
+
+#endif /* algorithm.h */
diff --git a/src/libpspp/bit-vector.h b/src/libpspp/bit-vector.h
new file mode 100644 (file)
index 0000000..7011250
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, 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/libpspp/copyleft.c b/src/libpspp/copyleft.c
new file mode 100644 (file)
index 0000000..f04bf58
--- /dev/null
@@ -0,0 +1,374 @@
+const char legal[]=""
+"Copyright (C) 1997-9, 2000, 2005 Free Software Foundation, Inc.\n"
+"GNU PSPP comes with NO WARRANTY,\n"
+"to the extent permitted by law.\n"
+"You may redistribute copies of GNU PSPP\n"
+"under the terms of the GNU General Public License.\n"
+"For more information about these matters,\n"
+"see the file named COPYING.\n";
+
+const char lack_of_warranty[]=""
+"                          NO WARRANTY\n"
+"\n"
+"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.\n"
+"\n"
+"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, 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.";
+
+const char copyleft[]=""
+"                  GNU GENERAL PUBLIC LICENSE\n "
+"                     Version 2, June 1991\n "
+" \n"
+" Copyright (C) 1989, 1991 Free Software Foundation, Inc. \n"
+"     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n "
+" Everyone is permitted to copy and distribute verbatim copies "
+" of this license document, but changing it is not allowed. "
+" \n"
+"                          Preamble \n"
+"\n"
+"  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. "
+"\n"
+"  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. "
+" \n"
+"  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. "
+" \n"
+"  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. "
+" \n"
+"  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. "
+" \n"
+"  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. "
+" \n"
+"  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. "
+" \n"
+"  The precise terms and conditions for copying, distribution and "
+"modification follow. "
+"\n "
+"                  GNU GENERAL PUBLIC LICENSE \n"
+"   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION \n"
+" \n"
+"  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\". "
+" \n"
+"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. "
+"\n"
+"  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. "
+"\n"
+"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. "
+"\n"
+"  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: "
+"\n"
+"    a) You must cause the modified files to carry prominent notices "
+"    stating that you changed the files and the date of any change. "
+"\n"
+"    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. "
+"\n"
+"    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.) "
+"\n "
+"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. "
+"\n"
+"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. "
+"\n"
+"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. "
+"\n"
+"  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: "
+"\n"
+"    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, "
+"\n"
+"    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, "
+"\n"
+"    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.) "
+"\n"
+"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. "
+"\n"
+"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. "
+"\n "
+"  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. "
+"\n"
+"  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. "
+"\n"
+"  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. "
+"\n"
+"  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. "
+"\n"
+"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. "
+"\n"
+"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. "
+"\n"
+"This section is intended to make thoroughly clear what is believed to "
+"be a consequence of the rest of this License. "
+"\n "
+"  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. "
+"\n"
+"  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. "
+"\n"
+"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. "
+"\n"
+"  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. "
+"\n"
+"                          NO WARRANTY "
+"\n"
+"  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. "
+"\n"
+"  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. "
+"\n"
+"                   END OF TERMS AND CONDITIONS "
+"\n "
+"          How to Apply These Terms to Your New Programs "
+"\n"
+"  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. "
+"\n"
+"  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. "
+"\n"
+"    <one line to give the program's name and a brief idea of what it does.>\n"
+"    Copyright (C) <year>  <name of author>\n"
+"\n"
+"    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.\n"
+"\n"
+"    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.\n"
+"\n"
+"    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n"
+"\n"
+"\n"
+"Also add information on how to contact you by electronic and paper mail. "
+"\n"
+"If the program is interactive, make it output a short notice like this "
+"when it starts in an interactive mode: "
+"\n"
+"    Gnomovision version 69, Copyright (C) year  name of author\n"
+"    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n"
+"    This is free software, and you are welcome to redistribute it\n"
+"    under certain conditions; type `show c' for details.\n"
+"\n"
+"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. "
+"\n"
+"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: "
+"\n"
+"  Yoyodyne, Inc., hereby disclaims all copyright interest in the program"
+"  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n"
+"\n"
+"  <signature of Ty Coon>, 1 April 1989\n"
+"  Ty Coon, President of Vice\n"
+"\n"
+"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/src/libpspp/copyleft.h b/src/libpspp/copyleft.h
new file mode 100644 (file)
index 0000000..8abb426
--- /dev/null
@@ -0,0 +1,27 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !copyleft_h
+#define copyleft_h 1
+
+extern const char lack_of_warranty[];
+extern const char copyleft[];
+extern const char legal[];
+
+#endif
diff --git a/src/libpspp/debug-print.h b/src/libpspp/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/libpspp/hash.c b/src/libpspp/hash.c
new file mode 100644 (file)
index 0000000..7650366
--- /dev/null
@@ -0,0 +1,617 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "hash.h"
+#include "message.h"
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdlib.h>
+#include "array.h"
+#include "alloc.h"
+#include <stdbool.h>
+#include "misc.h"
+#include "str.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* 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. */
+
+/* Returns smallest power of 2 greater than X. */
+static size_t
+next_power_of_2 (size_t x) 
+{
+  assert (x != 0);
+
+  for (;;) 
+    {
+      /* Turn off rightmost 1-bit in x. */
+      size_t y = x & (x - 1);
+
+      /* If y is 0 then x only had a single 1-bit. */
+      if (y == 0)
+        return 2 * x;
+
+      /* Otherwise turn off the next. */
+      x = y;
+    }
+}
+
+/* Fowler-Noll-Vo hash constants, for 32-bit word sizes. */
+#define FNV_32_PRIME 16777619u
+#define FNV_32_BASIS 2166136261u
+
+/* Fowler-Noll-Vo 32-bit hash, for bytes. */
+unsigned
+hsh_hash_bytes (const void *buf_, size_t size)
+{
+  const unsigned char *buf = (const unsigned char *) buf_;
+  unsigned hash;
+
+  assert (buf != NULL);
+
+  hash = FNV_32_BASIS;
+  while (size-- > 0)
+    hash = (hash * FNV_32_PRIME) ^ *buf++;
+
+  return hash;
+} 
+
+/* Fowler-Noll-Vo 32-bit hash, for strings. */
+unsigned
+hsh_hash_string (const char *s_) 
+{
+  const unsigned char *s = (const unsigned char *) s_;
+  unsigned hash;
+
+  assert (s != NULL);
+
+  hash = FNV_32_BASIS;
+  while (*s != '\0')
+    hash = (hash * FNV_32_PRIME) ^ *s++;
+
+  return hash;
+}
+
+/* Fowler-Noll-Vo 32-bit hash, for case-insensitive strings. */
+unsigned
+hsh_hash_case_string (const char *s_) 
+{
+  const unsigned char *s = (const unsigned char *) s_;
+  unsigned hash;
+
+  assert (s != NULL);
+
+  hash = FNV_32_BASIS;
+  while (*s != '\0')
+    hash = (hash * FNV_32_PRIME) ^ toupper (*s++);
+
+  return hash;
+}
+
+/* Hash for ints. */
+unsigned
+hsh_hash_int (int i) 
+{
+  return hsh_hash_bytes (&i, sizeof i);
+}
+
+/* Hash for double. */
+unsigned
+hsh_hash_double (double d) 
+{
+  if (!isnan (d))
+    return hsh_hash_bytes (&d, sizeof d);
+  else
+    return 0;
+}
+\f
+/* Hash tables. */
+
+/* Hash table. */
+struct hsh_table
+  {
+    size_t used;                /* Number of filled entries. */
+    size_t size;                /* Number of entries (a power of 2). */
+    void **entries;            /* Hash table proper. */
+
+    void *aux;                  /* Auxiliary data for comparison functions. */
+    hsh_compare_func *compare;
+    hsh_hash_func *hash;
+    hsh_free_func *free;
+    
+#ifndef NDEBUG
+    /* Set to false if hsh_data() or hsh_sort() has been called,
+       so that most hsh_*() functions may no longer be called. */
+    bool hash_ordered;
+#endif
+  };
+
+/* 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 size, hsh_compare_func *compare, hsh_hash_func *hash,
+            hsh_free_func *free, void *aux)
+{
+  struct hsh_table *h;
+  int i;
+
+  assert (compare != NULL);
+  assert (hash != NULL);
+  
+  h = xmalloc (sizeof *h);
+  h->used = 0;
+  if (size < 4)
+    size = 4;
+  h->size = next_power_of_2 (size);
+  h->entries = xnmalloc (h->size, sizeof *h->entries);
+  for (i = 0; i < h->size; i++)
+    h->entries[i] = NULL;
+  h->aux = aux;
+  h->compare = compare;
+  h->hash = hash;
+  h->free = free;
+#ifndef NDEBUG
+  h->hash_ordered = true;
+#endif
+  return h;
+}
+
+/* Destroys the contents of table H. */
+void
+hsh_clear (struct hsh_table *h)
+{
+  int i;
+
+  assert (h != NULL);
+  if (h->free)
+    for (i = 0; i < h->size; i++)
+      if (h->entries[i] != NULL)
+        h->free (h->entries[i], h->aux);
+
+  for (i = 0; i < h->size; i++)
+    h->entries[i] = NULL;
+
+  h->used = 0;
+
+#ifndef NDEBUG
+  h->hash_ordered = true;
+#endif
+}
+
+/* Destroys table H and all its contents. */
+void
+hsh_destroy (struct hsh_table *h)
+{
+  int i;
+
+  if (h != NULL) 
+    {
+      if (h->free)
+        for (i = 0; i < h->size; i++)
+          if (h->entries[i] != NULL)
+            h->free (h->entries[i], h->aux);
+      free (h->entries);
+      free (h);
+    }
+}
+
+/* Locates an entry matching TARGET.  Returns a pointer to the
+   entry, or a null pointer on failure. */
+static inline unsigned
+locate_matching_entry (struct hsh_table *h, const void *target) 
+{
+  unsigned i = h->hash (target, h->aux);
+
+  assert (h->hash_ordered);
+  for (;;)
+    {
+      void *entry;
+      i &= h->size - 1;
+      entry = h->entries[i];
+      if (entry == NULL || !h->compare (entry, target, h->aux))
+       return i;
+      i--;
+    }
+}
+
+/* Changes the capacity of H to NEW_SIZE, which must be a
+   positive power of 2 at least as large as the number of
+   elements in H. */
+static void
+rehash (struct hsh_table *h, size_t new_size)
+{
+  void **begin, **end, **table_p;
+  int i;
+
+  assert (h != NULL);
+  assert (new_size >= h->used);
+
+  /* Verify that NEW_SIZE is a positive power of 2. */
+  assert (new_size > 0 && (new_size & (new_size - 1)) == 0);
+
+  begin = h->entries;
+  end = begin + h->size;
+
+  h->size = new_size;
+  h->entries = xnmalloc (h->size, sizeof *h->entries);
+  for (i = 0; i < h->size; i++)
+    h->entries[i] = NULL;
+  for (table_p = begin; table_p < end; table_p++) 
+    {
+      void *entry = *table_p;
+      if (entry != NULL)
+        h->entries[locate_matching_entry (h, entry)] = entry;
+    }
+  free (begin);
+
+#ifndef NDEBUG
+  h->hash_ordered = true;
+#endif
+}
+
+/* A "algo_predicate_func" that returns nonzero if DATA points
+   to a non-null void. */
+static int
+not_null (const void *data_, void *aux UNUSED) 
+{
+  void *const *data = data_;
+
+  return *data != NULL;
+}
+
+/* Compacts hash table H and returns a pointer to its data.  The
+   returned data consists of hsh_count(H) non-null pointers, in
+   no particular order, followed by a null pointer.
+
+   After calling this function, only hsh_destroy() and
+   hsh_count() should be applied to H.  hsh_first() and
+   hsh_next() could also be used, but you're better off just
+   iterating through the returned array.
+
+   This function is intended for use in situations where data
+   processing occurs in two phases.  In the first phase, data is
+   added, removed, and searched for within a hash table.  In the
+   second phase, the contents of the hash table are output and
+   the hash property itself is no longer of interest.
+
+   Use hsh_sort() instead, if the second phase wants data in
+   sorted order.  Use hsh_data_copy() or hsh_sort_copy() instead,
+   if the second phase still needs to search the hash table. */
+void *const *
+hsh_data (struct hsh_table *h) 
+{
+  size_t n;
+
+  assert (h != NULL);
+  n = partition (h->entries, h->size, sizeof *h->entries, not_null, NULL);
+  assert (n == h->used);
+#ifndef NDEBUG
+  h->hash_ordered = false;
+#endif
+  return h->entries;
+}
+
+/* Dereferences void ** pointers and passes them to the hash
+   comparison function. */
+static int
+comparison_helper (const void *a_, const void *b_, void *h_) 
+{
+  void *const *a = a_;
+  void *const *b = b_;
+  struct hsh_table *h = h_;
+
+  assert(a);
+  assert(b);
+
+  return h->compare (*a, *b, h->aux);
+}
+
+/* Sorts hash table H based on hash comparison function.  The
+   returned data consists of hsh_count(H) non-null pointers,
+   sorted in order of the hash comparison function, followed by a
+   null pointer.
+
+   After calling this function, only hsh_destroy() and
+   hsh_count() should be applied to H.  hsh_first() and
+   hsh_next() could also be used, but you're better off just
+   iterating through the returned array.
+
+   This function is intended for use in situations where data
+   processing occurs in two phases.  In the first phase, data is
+   added, removed, and searched for within a hash table.  In the
+   second phase, the contents of the hash table are output and
+   the hash property itself is no longer of interest.
+
+   Use hsh_data() instead, if the second phase doesn't need the
+   data in any particular order.  Use hsh_data_copy() or
+   hsh_sort_copy() instead, if the second phase still needs to
+   search the hash table. */
+void *const *
+hsh_sort (struct hsh_table *h)
+{
+  assert (h != NULL);
+
+  hsh_data (h);
+  sort (h->entries, h->used, sizeof *h->entries, comparison_helper, h);
+  return h->entries;
+}
+
+/* Makes and returns a copy of the pointers to the data in H.
+   The returned data consists of hsh_count(H) non-null pointers,
+   in no particular order, followed by a null pointer.  The hash
+   table is not modified.  The caller is responsible for freeing
+   the allocated data.
+
+   If you don't need to search or modify the hash table, then
+   hsh_data() is a more efficient choice. */
+void **
+hsh_data_copy (struct hsh_table *h) 
+{
+  void **copy;
+
+  assert (h != NULL);
+  copy = xnmalloc ((h->used + 1), sizeof *copy);
+  copy_if (h->entries, h->size, sizeof *h->entries, copy, not_null, NULL);
+  copy[h->used] = NULL;
+  return copy;
+}
+
+/* Makes and returns a copy of the pointers to the data in H.
+   The returned data consists of hsh_count(H) non-null pointers,
+   sorted in order of the hash comparison function, followed by a
+   null pointer.  The hash table is not modified.  The caller is
+   responsible for freeing the allocated data.
+
+   If you don't need to search or modify the hash table, then
+   hsh_sort() is a more efficient choice. */
+void **
+hsh_sort_copy (struct hsh_table *h) 
+{
+  void **copy;
+
+  assert (h != NULL);
+  copy = hsh_data_copy (h);
+  sort (copy, h->used, sizeof *copy, comparison_helper, h);
+  return copy;
+}
+\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)
+{
+  unsigned i;
+  
+  assert (h != NULL);
+  assert (target != NULL);
+  assert (h->hash_ordered);
+
+  if (h->used > h->size / 2)
+    rehash (h, h->size * 2);
+  i = locate_matching_entry (h, target);
+  if (h->entries[i] == NULL)
+    h->used++;
+  return &h->entries[i];
+}
+
+/* Searches hash table H for TARGET.  If not found, inserts
+   TARGET and returns a null pointer.  If found, returns the
+   match, without replacing it in the table. */
+void *
+hsh_insert (struct hsh_table *h, void *target) 
+{
+  void **entry;
+
+  assert (h != NULL);
+  assert (target != NULL);
+
+  entry = hsh_probe (h, target);
+  if (*entry == NULL) 
+    {
+      *entry = target;
+      return NULL;
+    }
+  else
+    return *entry;
+}
+
+/* Searches hash table H for TARGET.  If not found, inserts
+   TARGET and returns a null pointer.  If found, returns the
+   match, after replacing it in the table by TARGET. */
+void *
+hsh_replace (struct hsh_table *h, void *target) 
+{
+  void **entry = hsh_probe (h, target);
+  void *old = *entry;
+  *entry = target;
+  return old;
+}
+
+/* Returns the entry in hash table H that matches TARGET, or NULL
+   if there is none. */
+void *
+hsh_find (struct hsh_table *h, const void *target)
+{
+  return h->entries[locate_matching_entry (h, target)];
+}
+
+/* Deletes the entry in hash table H that matches TARGET.
+   Returns nonzero if an entry was deleted.
+
+   Uses Knuth's Algorithm 6.4R (Deletion with linear probing).
+   Because our load factor is at most 1/2, the average number of
+   moves that this algorithm makes should be at most 2 - ln 2 ~=
+   1.65. */
+int
+hsh_delete (struct hsh_table *h, const void *target) 
+{
+  unsigned i = locate_matching_entry (h, target);
+  if (h->entries[i] != NULL) 
+    {
+      h->used--;
+      if (h->free != NULL)
+        h->free (h->entries[i], h->aux);
+
+      for (;;) 
+        {
+          unsigned r;
+          ptrdiff_t j;
+
+          h->entries[i] = NULL;
+          j = i;
+          do 
+            {
+              i = (i - 1) & (h->size - 1);
+              if (h->entries[i] == NULL)
+                return 1;
+              
+              r = h->hash (h->entries[i], h->aux) & (h->size - 1);
+            }
+          while ((i <= r && r < j) || (r < j && j < i) || (j < i && i <= r));
+          h->entries[j] = h->entries[i]; 
+        }
+    }
+  else
+    return 0;
+}
+\f
+/* Iteration. */
+
+/* Finds and returns an entry in TABLE, and initializes ITER for
+   use with hsh_next().  If TABLE is empty, returns a null
+   pointer. */
+void *
+hsh_first (struct hsh_table *h, struct hsh_iterator *iter) 
+{
+  assert (h != NULL);
+  assert (iter != NULL);
+
+  iter->next = 0;
+  return hsh_next (h, iter);
+}
+
+/* Iterates through TABLE with iterator ITER.  Returns the next
+   entry in TABLE, or a null pointer after the last entry.
+
+   Entries are returned in an undefined order.  Modifying TABLE
+   during iteration may cause some entries to be returned
+   multiple times or not at all. */
+void *
+hsh_next (struct hsh_table *h, struct hsh_iterator *iter)
+{
+  size_t i;
+
+  assert (h != NULL);
+  assert (iter != NULL);
+  assert (iter->next <= h->size);
+
+  for (i = iter->next; i < h->size; i++)
+    if (h->entries[i])
+      {
+       iter->next = i + 1;
+       return h->entries[i];
+      }
+
+  iter->next = h->size;
+  return NULL;
+}
+\f
+/* Returns the number of items in H. */
+size_t 
+hsh_count (struct hsh_table *h) 
+{
+  assert (h != NULL);
+  
+  return h->used;
+}
+\f
+/* Debug helpers. */
+
+#if GLOBAL_DEBUGGING
+#undef NDEBUG
+#include "message.h"
+#include <stdio.h>
+
+/* Displays contents of hash table H on stdout. */
+void
+hsh_dump (struct hsh_table *h)
+{
+  void **entry = h->entries;
+  int i;
+
+  printf (_("hash table:"));
+  for (i = 0; i < h->size; 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
+hsh_force_insert (struct hsh_table *h, void *p)
+{
+  void **pp = hsh_probe (h, p);
+  assert (*pp == NULL);
+  *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 *
+hsh_force_find (struct hsh_table *h, const void *target)
+{
+  void *found = hsh_find (h, target);
+  assert (found != NULL);
+  return found;
+}
+
+/* This wrapper for hsh_delete() verifies that an item really was
+   deleted. */
+void
+hsh_force_delete (struct hsh_table *h, const void *target)
+{
+  int found = hsh_delete (h, target);
+  assert (found != 0);
+}
+#endif
diff --git a/src/libpspp/hash.h b/src/libpspp/hash.h
new file mode 100644 (file)
index 0000000..e426483
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !hash_h
+#define hash_h 1
+
+#include <stddef.h>
+
+typedef int hsh_compare_func (const void *, const void *, void *aux);
+typedef unsigned hsh_hash_func (const void *, void *aux);
+typedef void hsh_free_func (void *, void *aux);
+
+/* Hash table iterator (opaque). */
+struct hsh_iterator
+  {
+    size_t next;               /* Index of next entry. */
+  };
+
+/* Hash functions. */
+unsigned hsh_hash_bytes (const void *, size_t);
+unsigned hsh_hash_string (const char *);
+unsigned hsh_hash_case_string (const char *);
+unsigned hsh_hash_int (int);
+unsigned hsh_hash_double (double);
+
+/* Hash tables. */
+struct hsh_table *hsh_create (int m, hsh_compare_func *,
+                              hsh_hash_func *, hsh_free_func *,
+                             void *aux);
+void hsh_clear (struct hsh_table *);
+void hsh_destroy (struct hsh_table *);
+void *const *hsh_sort (struct hsh_table *);
+void *const *hsh_data (struct hsh_table *);
+void **hsh_sort_copy (struct hsh_table *);
+void **hsh_data_copy (struct hsh_table *);
+
+/* Search and insertion. */
+void **hsh_probe (struct hsh_table *, const void *);
+void *hsh_insert (struct hsh_table *, void *);
+void *hsh_replace (struct hsh_table *, void *);
+void *hsh_find (struct hsh_table *, const void *);
+int hsh_delete (struct hsh_table *, const void *);
+
+/* Iteration. */
+void *hsh_first (struct hsh_table *, struct hsh_iterator *);
+void *hsh_next (struct hsh_table *, struct hsh_iterator *);
+
+/* Search and insertion with assertion. */
+#if GLOBAL_DEBUGGING
+void hsh_force_insert (struct hsh_table *, void *);
+void *hsh_force_find (struct hsh_table *, const void *);
+void hsh_force_delete (struct hsh_table *, const void *);
+#else
+#define hsh_force_insert(A, B)  ((void) (*hsh_probe (A, B) = B))
+#define hsh_force_find(A, B)    (hsh_find (A, B))
+#define hsh_force_delete(A, B)  ((void) hsh_delete (A, B))
+#endif
+
+/* Number of entries in hash table H. */
+size_t hsh_count (struct hsh_table *);
+
+/* Debugging. */
+#if GLOBAL_DEBUGGING
+void hsh_dump (struct hsh_table *);
+#endif
+
+#endif /* hash_h */
diff --git a/src/libpspp/magic.c b/src/libpspp/magic.c
new file mode 100644 (file)
index 0000000..40164b2
--- /dev/null
@@ -0,0 +1,32 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, 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
+
+/* magic.h */
+#ifndef __GNUC__
+union cvt_dbl second_lowest_value_union = {SECOND_LOWEST_BYTES};
+#endif
diff --git a/src/libpspp/magic.h b/src/libpspp/magic.h
new file mode 100644 (file)
index 0000000..e6bc7ed
--- /dev/null
@@ -0,0 +1,62 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !magic_h
+#define magic_h 1
+
+/* Magic numbers. */
+
+#include <float.h>
+#include <limits.h>
+
+/* Check that the floating-point representation is one that we
+   understand. */
+#ifndef FPREP_IEEE754
+#error Only IEEE-754 floating point currently supported.
+#endif
+
+/* Allows us to specify individual bytes of a double. */     
+union cvt_dbl {
+  unsigned char cvt_dbl_i[8];
+  double cvt_dbl_d;
+};
+
+
+/* "Second-lowest value" bytes for an IEEE-754 double. */
+#if WORDS_BIGENDIAN
+#define SECOND_LOWEST_BYTES {0xff,0xef,0xff,0xff, 0xff,0xff,0xff,0xfe}
+#else
+#define SECOND_LOWEST_BYTES {0xfe,0xff,0xff,0xff, 0xff,0xff,0xef,0xff}
+#endif
+
+/* "Second-lowest value" for a double. */
+#if __GNUC__
+#define second_lowest_value                                               \
+        (__extension__ ((union cvt_dbl) {SECOND_LOWEST_BYTES}).cvt_dbl_d)
+#else /* not GNU C */
+extern union cvt_dbl second_lowest_value_union;
+#define second_lowest_value (second_lowest_value_union.cvt_dbl_d)
+#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/libpspp/message.h b/src/libpspp/message.h
new file mode 100644 (file)
index 0000000..8f86d0a
--- /dev/null
@@ -0,0 +1,102 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !error_h
+#define error_h 1
+
+#include <stdarg.h>
+#include <stdbool.h>
+
+/* Message classes. */
+enum
+  {
+    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. */
+  };
+
+/* 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, ...)
+     PRINTF_FORMAT (2, 3);
+void tmsg (int class, const char *title, const char *format, ...)
+     PRINTF_FORMAT (3, 4);
+
+/* 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_set_command_name (const char *);
+void err_done (void);
+void err_check_count (void);
+void err_vmsg (const struct error *, const char *, va_list);
+
+/* Used in panic situations only */
+void request_bug_report_and_abort(const char *msg );
+
+void err_assert_fail(const char *expr, const char *file, int line);
+
+#undef __STRING
+#define __STRING(x) #x
+#undef assert
+
+                              
+#define assert(expr) ( (void) ( expr ? (void) 0 : \
+              err_assert_fail(__STRING(expr), __FILE__, __LINE__)) )
+
+
+
+#endif /* error.h */
diff --git a/src/libpspp/misc.c b/src/libpspp/misc.c
new file mode 100644 (file)
index 0000000..4bd64b6
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, 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/libpspp/misc.h b/src/libpspp/misc.h
new file mode 100644 (file)
index 0000000..a1be9f9
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !math_misc_h
+#define math_misc_h 1
+
+#include <float.h>
+#include <math.h>
+
+#define EPSILON (10 * DBL_EPSILON)
+
+/* 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
+
+#ifndef min
+#define min(A, B) ((A) < (B) ? (A) : (B))
+#endif
+
+#ifndef max
+#define max(A, B) ((A) > (B) ? (A) : (B))
+#endif
+
+/* 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}. */
+#define REM_RND_UP(X, Y) ((X) % (Y) ? (Y) - (X) % (Y) : 0)
+
+/* 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);
+
+/* Returns the square of X. */
+static inline double
+pow2 (double x) 
+{
+  return x * x;
+}
+
+/* Returns the cube of X. */
+static inline double
+pow3 (double x) 
+{
+  return x * x * x;
+}
+
+/* Returns the fourth power of X. */
+static inline double
+pow4 (double x) 
+{
+  double y = x * x;
+  y *= y;
+  return y;
+}
+
+#endif /* math/misc.h */
diff --git a/src/libpspp/pool.c b/src/libpspp/pool.c
new file mode 100644 (file)
index 0000000..105937b
--- /dev/null
@@ -0,0 +1,878 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "pool.h"
+#include <stdlib.h>
+#include "alloc.h"
+#include "message.h"
+#include "size_max.h"
+#include "str.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 *pool;
+    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. */
+#define ALIGN_SIZE sizeof (union align)
+
+/* DISCRETE_BLOCKS may be declared as nonzero to prevent
+   suballocation of blocks.  This is useful under memory
+   debuggers like Checker or valgrind 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. */
+/*#define DISCRETE_BLOCKS 1*/
+
+/* Size of each block allocated in the pool, in bytes.
+   Should be at least 1k. */
+#ifndef BLOCK_SIZE
+#define BLOCK_SIZE 1024
+#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 free_all_gizmos (struct pool *pool);
+static void delete_gizmo (struct pool *, struct pool_gizmo *);
+static void check_gizmo (struct pool *, struct pool_gizmo *);
+\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;
+}
+
+/* Creates a pool, allocates a block STRUCT_SIZE bytes in
+   length from it, stores the pool's address at offset
+   POOL_MEMBER_OFFSET within the block, and returns the allocated
+   block.
+
+   Meant for use indirectly via pool_create_container(). */
+void *
+pool_create_at_offset (size_t struct_size, size_t pool_member_offset) 
+{
+  struct pool *pool;
+  char *struct_;
+
+  assert (struct_size >= sizeof pool);
+  assert (pool_member_offset <= struct_size - sizeof pool);
+
+  pool = pool_create ();
+  struct_ = pool_alloc (pool, struct_size);
+  *(struct pool **) (struct_ + pool_member_offset) = pool;
+  return struct_;
+}
+
+/* Destroy the specified pool, including all subpools. */
+void
+pool_destroy (struct pool *pool)
+{
+  if (pool == NULL)
+    return;
+
+  /* Remove this pool from its parent's list of gizmos. */
+  if (pool->parent) 
+    delete_gizmo (pool->parent, (void *) (((char *) pool) + POOL_SIZE));
+  
+  free_all_gizmos (pool);
+
+  /* Free all the memory. */
+  {
+    struct pool_block *cur, *next;
+
+    pool->blocks->prev->next = NULL;
+    for (cur = pool->blocks; cur; cur = next)
+      {
+       next = cur->next;
+       free (cur);
+      }
+  }
+}
+
+/* Release all the memory and gizmos in POOL.
+   Blocks are not given back with free() but kept for later
+   allocations.  To give back memory, use a subpool instead. */ 
+void
+pool_clear (struct pool *pool) 
+{
+  free_all_gizmos (pool);
+
+  /* Zero out block sizes. */
+  {
+    struct pool_block *cur;
+    
+    cur = pool->blocks;
+    do
+      {
+        cur->ofs = POOL_BLOCK_SIZE;
+        if ((char *) cur + POOL_BLOCK_SIZE == (char *) pool) 
+          {
+            cur->ofs += POOL_SIZE;
+            if (pool->parent != NULL)
+              cur->ofs += POOL_GIZMO_SIZE; 
+          }
+        cur = cur->next;
+      }
+    while (cur != pool->blocks);
+  }
+}
+\f
+/* Suballocation routines. */
+
+/* Allocates a memory region AMT bytes in size from POOL and returns a
+   pointer to the region's start.
+   The region is properly aligned for storing any object. */
+void *
+pool_alloc (struct pool *pool, size_t amt)
+{
+  assert (pool != NULL);
+
+  if (amt == 0)
+    return NULL;
+  
+#ifndef DISCRETE_BLOCKS
+  if (amt <= MAX_SUBALLOC)
+    {
+      /* If there is space in this block, take it. */
+      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;
+       }
+
+      /* No space in this block, so we must make other
+         arrangements. */
+      if (b->next->ofs == 0) 
+        {
+          /* The next block is empty.  Use it. */
+          b = b->next;
+          b->ofs = POOL_BLOCK_SIZE;
+          if ((char *) b + POOL_BLOCK_SIZE == (char *) pool)
+            b->ofs += POOL_SIZE;
+        }
+      else 
+        {
+          /* Create a new block at the start of the list. */
+          b = xmalloc (BLOCK_SIZE);
+          b->next = pool->blocks;
+          b->prev = pool->blocks->prev;
+          b->ofs = POOL_BLOCK_SIZE;
+          pool->blocks->prev->next = b;
+          pool->blocks->prev = b;
+        }
+      pool->blocks = b;
+
+      /* Allocate space from B. */
+      b->ofs += amt;
+      return ((char *) b) + b->ofs - amt;
+    }
+  else
+#endif
+    return pool_malloc (pool, amt);
+}
+
+/* Allocates a memory region AMT bytes in size from POOL and
+   returns a pointer to the region's start.  The region is not
+   necessarily aligned, so it is most suitable for storing
+   strings. */
+void *
+pool_alloc_unaligned (struct pool *pool, size_t amt)
+{
+  assert (pool != NULL);
+
+#ifndef DISCRETE_BLOCKS
+  /* Strings need not be aligned on any boundary, but some
+     operations may be more efficient when they are.  However,
+     that's only going to help with reasonably long strings. */
+  if (amt < ALIGN_SIZE) 
+    {
+      if (amt == 0)
+        return NULL;
+      else
+        {
+          struct pool_block *const b = pool->blocks;
+
+          if (b->ofs + amt <= BLOCK_SIZE)
+            {
+              void *p = ((char *) b) + b->ofs;
+              b->ofs += amt;
+              return p;
+            }
+        }
+    }
+#endif
+
+  return pool_alloc (pool, amt);
+}
+
+/* Allocates a memory region N * S bytes in size from POOL and
+   returns a pointer to the region's start.
+   N must be nonnegative, S must be positive.
+   Terminates the program if the memory cannot be obtained,
+   including the case where N * S overflows the range of size_t. */
+void *
+pool_nalloc (struct pool *pool, size_t n, size_t s) 
+{
+  if (xalloc_oversized (n, s))
+    xalloc_die ();
+  return pool_alloc (pool, n * s);
+}
+
+/* Allocates SIZE bytes in POOL, copies BUFFER into it, and
+   returns the new copy. */
+void *
+pool_clone (struct pool *pool, const void *buffer, size_t size)
+{
+  void *block = pool_alloc (pool, size);
+  memcpy (block, buffer, size);
+  return block;
+}
+
+/* Allocates SIZE bytes of unaligned data in POOL, copies BUFFER
+   into it, and returns the new copy. */
+void *
+pool_clone_unaligned (struct pool *pool, const void *buffer, size_t size)
+{
+  void *block = pool_alloc_unaligned (pool, size);
+  memcpy (block, buffer, size);
+  return block;
+}
+
+/* Duplicates null-terminated STRING, within POOL, and returns a
+   pointer to the duplicate.  For use only with strings, because
+   the returned pointere may not be aligned properly for other
+   types. */
+char *
+pool_strdup (struct pool *pool, const char *string) 
+{
+  return pool_clone_unaligned (pool, string, strlen (string) + 1);
+}
+\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 xmalloc().  */
+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);
+}
+
+/* Allocates and returns N elements of S bytes each, to be
+   managed by POOL.
+   If POOL is a null pointer, then allocates a normal memory block
+   with malloc().
+   N must be nonnegative, S must be positive.
+   Terminates the program if the memory cannot be obtained,
+   including the case where N * S overflows the range of size_t. */
+void *
+pool_nmalloc (struct pool *pool, size_t n, size_t s) 
+{
+  if (xalloc_oversized (n, s))
+    xalloc_die ();
+  return pool_malloc (pool, n * s);
+}
+
+/* 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 = (void *) (((char *) p) - POOL_GIZMO_SIZE);
+              check_gizmo (pool, g);
+
+             g = xrealloc (g, amt + POOL_GIZMO_SIZE);
+             if (g->next)
+               g->next->prev = g;
+             if (g->prev)
+               g->prev->next = g;
+             else
+               pool->gizmos = g;
+              check_gizmo (pool, 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);
+}
+
+/* Changes the allocation size of the specified memory block P
+   managed by POOL to N * S bytes and returns a pointer to the
+   beginning of the block.
+   N must be nonnegative, S must be positive.
+   If POOL is a null pointer, then the block is reallocated in
+   the usual way with xrealloc().
+   Terminates the program if the memory cannot be obtained,
+   including the case where N * S overflows the range of size_t. */
+void *
+pool_nrealloc (struct pool *pool, void *p, size_t n, size_t s)
+{
+  if (xalloc_oversized (n, s))
+    xalloc_die ();
+  return pool_realloc (pool, p, n * s);
+}
+
+/* If P is null, allocate a block of at least *PN such objects;
+   otherwise, reallocate P so that it contains more than *PN
+   objects each of S bytes.  *PN must be nonzero unless P is
+   null, and S must be nonzero.  Set *PN to the new number of
+   objects, and return the pointer to the new block.  *PN is
+   never set to zero, and the returned pointer is never null.
+
+   The block returned is managed by POOL.  If POOL is a null
+   pointer, then the block is reallocated in the usual way with
+   x2nrealloc().
+
+   Terminates the program if the memory cannot be obtained,
+   including the case where the memory required overflows the
+   range of size_t.
+
+   Repeated reallocations are guaranteed to make progress, either by
+   allocating an initial block with a nonzero size, or by allocating a
+   larger block.
+
+   In the following implementation, nonzero sizes are doubled so that
+   repeated reallocations have O(N log N) overall cost rather than
+   O(N**2) cost, but the specification for this function does not
+   guarantee that sizes are doubled.
+
+   Here is an example of use:
+
+     int *p = NULL;
+     struct pool *pool;
+     size_t used = 0;
+     size_t allocated = 0;
+
+     void
+     append_int (int value)
+       {
+        if (used == allocated)
+          p = pool_2nrealloc (pool, p, &allocated, sizeof *p);
+        p[used++] = value;
+       }
+
+   This causes x2nrealloc to allocate a block of some nonzero size the
+   first time it is called.
+
+   To have finer-grained control over the initial size, set *PN to a
+   nonzero value before calling this function with P == NULL.  For
+   example:
+
+     int *p = NULL;
+     struct pool *pool;
+     size_t used = 0;
+     size_t allocated = 0;
+     size_t allocated1 = 1000;
+
+     void
+     append_int (int value)
+       {
+        if (used == allocated)
+          {
+            p = pool_2nrealloc (pool, p, &allocated1, sizeof *p);
+            allocated = allocated1;
+          }
+        p[used++] = value;
+       }
+
+   This function implementation is from gnulib. */
+void *
+pool_2nrealloc (struct pool *pool, void *p, size_t *pn, size_t s)
+{
+  size_t n = *pn;
+
+  if (p == NULL)
+    {
+      if (n == 0)
+       {
+         /* The approximate size to use for initial small allocation
+            requests, when the invoking code specifies an old size of
+            zero.  64 bytes is the largest "small" request for the
+            GNU C library malloc.  */
+         enum { DEFAULT_MXFAST = 64 };
+
+         n = DEFAULT_MXFAST / s;
+         n += !n;
+       }
+    }
+  else
+    {
+      if (SIZE_MAX / 2 / s < n)
+       xalloc_die ();
+      n *= 2;
+    }
+
+  *pn = n;
+  return pool_realloc (pool, p, n * s);
+}
+
+/* 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);
+      check_gizmo (pool, g);
+      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->blocks) + subpool->blocks->ofs);
+  subpool->blocks->ofs += POOL_GIZMO_SIZE;
+  
+  g->type = POOL_GIZMO_SUBPOOL;
+  g->p.subpool = subpool;
+
+  add_gizmo (pool, g);
+
+  return subpool;
+}
+
+/* Makes SUBPOOL a subpool of POOL.
+   SUBPOOL must not already have a parent pool.
+   The subpool will be destroyed automatically when POOL is destroyed.
+   It may also be destroyed explicitly in advance. */
+void
+pool_add_subpool (struct pool *pool, struct pool *subpool) 
+{
+  struct pool_gizmo *g;
+
+  assert (pool != NULL);
+  assert (subpool != NULL);
+  assert (subpool->parent == NULL);
+  
+  g = pool_alloc (subpool, sizeof *g);
+  g->type = POOL_GIZMO_SUBPOOL;
+  g->p.subpool = subpool;
+  add_gizmo (pool, g);
+
+  subpool->parent = pool;
+}
+
+/* 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(), or
+   detached from the pool with pool_detach_file(). */
+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;
+
+  return f;
+}
+
+/* Closes file FILE managed by POOL.
+   Returns 0 if successful, EOF if an I/O error occurred. */
+int
+pool_fclose (struct pool *pool, FILE *file)
+{
+  assert (pool && file);
+  pool_detach_file (pool, file);
+  return fclose (file);
+}
+
+/* Creates a temporary file with tmpfile() 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(), or
+   detached from the pool with pool_detach_file(). */
+FILE *
+pool_tmpfile (struct pool *pool) 
+{
+  FILE *file = tmpfile ();
+  if (file != NULL)
+    pool_attach_file (pool, file);
+  return file;
+}
+
+/* Attaches FILE to POOL.
+   The file will be closed automatically when POOL is destroyed, or it
+   may be closed explicitly in advance using pool_fclose(), or
+   detached from the pool with pool_detach_file(). */
+void
+pool_attach_file (struct pool *pool, FILE *file)
+{
+  struct pool_gizmo *g = pool_alloc (pool, sizeof *g);
+  g->type = POOL_GIZMO_FILE;
+  g->p.file = file;
+  add_gizmo (pool, g);
+}
+
+/* Detaches FILE from POOL. */
+void
+pool_detach_file (struct pool *pool, FILE *file)
+{
+  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);
+        return;
+      }
+}
+\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.
+   Emptied blocks are not given back with free() but kept for
+   later allocations.  To get that behavior, use a subpool
+   instead. */ 
+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;
+
+    for (cur = pool->blocks; cur != mark->block; cur = cur->next) 
+      {
+        cur->ofs = POOL_BLOCK_SIZE;
+        if ((char *) cur + POOL_BLOCK_SIZE == (char *) pool) 
+          {
+            cur->ofs += POOL_SIZE;
+            if (pool->parent != NULL)
+              cur->ofs += POOL_GIZMO_SIZE; 
+          }
+      }
+    pool->blocks = mark->block;
+    pool->blocks->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->pool = pool;
+  gizmo->next = pool->gizmos;
+  gizmo->prev = NULL;
+  if (pool->gizmos)
+    pool->gizmos->prev = gizmo;
+  pool->gizmos = gizmo;
+
+  gizmo->serial = serial++;
+
+  check_gizmo (pool, gizmo);
+}
+/* Removes GIZMO from POOL's gizmo list. */
+static void
+delete_gizmo (struct pool *pool, struct pool_gizmo *gizmo)
+{
+  assert (pool && gizmo);
+
+  check_gizmo (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);
+    }
+}
+
+/* Free all the gizmos in POOL. */
+static void
+free_all_gizmos (struct pool *pool) 
+{
+  struct pool_gizmo *cur, *next;
+
+  for (cur = pool->gizmos; cur; cur = next)
+    {
+      next = cur->next;
+      free_gizmo (cur);
+    }
+  pool->gizmos = NULL;
+}
+
+static void
+check_gizmo (struct pool *p, struct pool_gizmo *g) 
+{
+  assert (g->pool == p);
+  assert (g->next == NULL || g->next->prev == g);
+  assert ((g->prev != NULL && g->prev->next == g)
+          || (g->prev == NULL && p->gizmos == g));
+
+}
diff --git a/src/libpspp/pool.h b/src/libpspp/pool.h
new file mode 100644 (file)
index 0000000..daa866b
--- /dev/null
@@ -0,0 +1,98 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !pool_h
+#define pool_h 1
+
+#include <stdio.h>
+
+/* 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
+
+
+/* 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 *);
+void pool_clear (struct pool *);
+
+/* Creates a pool, allocates an instance of the given STRUCT
+   within it, sets the struct's MEMBER to the pool's address, and
+   returns the allocated structure. */
+#define pool_create_container(STRUCT, MEMBER)                           \
+        ((STRUCT *) pool_create_at_offset (sizeof (STRUCT),             \
+                                           offsetof (STRUCT, MEMBER)))
+void *pool_create_at_offset (size_t struct_size, size_t pool_member_offset);
+
+/* Suballocation routines. */
+void *pool_alloc (struct pool *, size_t) MALLOC_LIKE;
+void *pool_nalloc (struct pool *, size_t n, size_t s) MALLOC_LIKE;
+void *pool_clone (struct pool *, const void *, size_t) MALLOC_LIKE;
+
+void *pool_alloc_unaligned (struct pool *, size_t) MALLOC_LIKE;
+void *pool_clone_unaligned (struct pool *, const void *, size_t) MALLOC_LIKE;
+char *pool_strdup (struct pool *, const char *) MALLOC_LIKE;
+char *pool_strcat (struct pool *, const char *, ...) MALLOC_LIKE;
+
+/* Standard allocation routines. */
+void *pool_malloc (struct pool *, size_t) MALLOC_LIKE;
+void *pool_nmalloc (struct pool *, size_t n, size_t s) MALLOC_LIKE;
+void *pool_realloc (struct pool *, void *, size_t);
+void *pool_nrealloc (struct pool *, void *, size_t n, size_t s);
+void *pool_2nrealloc (struct pool *, void *, size_t *pn, size_t s);
+void pool_free (struct pool *, void *);
+
+/* Subpools. */
+struct pool *pool_create_subpool (struct pool *);
+void pool_add_subpool (struct pool *, struct pool *subpool);
+
+/* Files. */
+FILE *pool_fopen (struct pool *, const char *, const char *);
+int pool_fclose (struct pool *, FILE *);
+FILE *pool_tmpfile (struct pool *);
+void pool_attach_file (struct pool *, FILE *);
+void pool_detach_file (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/libpspp/start-date.c b/src/libpspp/start-date.c
new file mode 100644 (file)
index 0000000..5becc88
--- /dev/null
@@ -0,0 +1,53 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "start-date.h"
+#include <time.h>
+#include "str.h"
+#include "strftime.h"
+
+/* Writes the current date into CUR_DATE in the format DD MMM
+   YYYY. */
+static void
+get_cur_date (char cur_date[12])
+{
+  time_t now = time (NULL);
+  if (now != (time_t) -1) 
+    {
+      struct tm *tm = localtime (&now);
+      if (tm != NULL) 
+        {
+          strftime (cur_date, 12, "%d %b %Y", tm);
+          return;
+        }
+    }
+  strcpy (cur_date, "?? ??? 2???");
+}
+
+/* Returns the date at which PSPP was started, as a string in the
+   format DD MMM YYYY. */
+const char *
+get_start_date (void)
+{
+  static char start_date[12];
+  if (start_date[0] == '\0')
+    get_cur_date (start_date);
+  return start_date; 
+}
diff --git a/src/libpspp/start-date.h b/src/libpspp/start-date.h
new file mode 100644 (file)
index 0000000..2123541
--- /dev/null
@@ -0,0 +1,25 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef START_DATE_H
+#define START_DATE_H 1
+
+const char *get_start_date (void);
+
+#endif /* start-date.h */
diff --git a/src/libpspp/str.c b/src/libpspp/str.c
new file mode 100644 (file)
index 0000000..530d1ce
--- /dev/null
@@ -0,0 +1,766 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "str.h"
+#include "message.h"
+#include <ctype.h>
+#include <limits.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "message.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
+buf_reverse (char *p, size_t nbytes)
+{
+  char *h = p, *t = &h[nbytes - 1];
+  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 *
+buf_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 the SIZE bytes in A to those in B, disregarding case,
+   and returns a strcmp()-type result. */
+int
+buf_compare_case (const char *a_, const char *b_, size_t size)
+{
+  const unsigned char *a = (unsigned char *) a_;
+  const unsigned char *b = (unsigned char *) b_;
+
+  while (size-- > 0) 
+    {
+      unsigned char ac = toupper (*a++);
+      unsigned char bc = toupper (*b++);
+
+      if (ac != bc) 
+        return ac > bc ? 1 : -1;
+    }
+
+  return 0;
+}
+
+/* Compares A of length A_LEN to B of length B_LEN.  The shorter
+   string is considered to be padded with spaces to the length of
+   the longer. */
+int
+buf_compare_rpad (const char *a, size_t a_len, const char *b, size_t b_len)
+{
+  size_t min_len;
+  int result;
+
+  min_len = a_len < b_len ? a_len : b_len;
+  result = memcmp (a, b, min_len);
+  if (result != 0)
+    return result;
+  else 
+    {
+      size_t idx;
+      
+      if (a_len < b_len) 
+        {
+          for (idx = min_len; idx < b_len; idx++)
+            if (' ' != b[idx])
+              return ' ' > b[idx] ? 1 : -1;
+        }
+      else 
+        {
+          for (idx = min_len; idx < a_len; idx++)
+            if (a[idx] != ' ')
+              return a[idx] > ' ' ? 1 : -1;
+        }
+      return 0;
+    }
+}
+
+/* Compares strin A to string B.  The shorter string is
+   considered to be padded with spaces to the length of the
+   longer. */
+int
+str_compare_rpad (const char *a, const char *b)
+{
+  return buf_compare_rpad (a, strlen (a), b, strlen (b));
+}
+
+/* Copies string SRC to buffer DST, of size DST_SIZE bytes.
+   DST is truncated to DST_SIZE bytes or padded on the right with
+   spaces as needed. */
+void
+buf_copy_str_rpad (char *dst, size_t dst_size, const char *src)
+{
+  size_t src_len = strlen (src);
+  if (src_len >= dst_size)
+    memcpy (dst, src, dst_size);
+  else
+    {
+      memcpy (dst, src, src_len);
+      memset (&dst[src_len], ' ', dst_size - src_len);
+    }
+}
+
+/* Copies string SRC to buffer DST, of size DST_SIZE bytes.
+   DST is truncated to DST_SIZE bytes or padded on the left with
+   spaces as needed. */
+void
+buf_copy_str_lpad (char *dst, size_t dst_size, const char *src)
+{
+  size_t src_len = strlen (src);
+  if (src_len >= dst_size)
+    memcpy (dst, src, dst_size);
+  else
+    {
+      size_t pad_cnt = dst_size - src_len;
+      memset (&dst[0], ' ', pad_cnt);
+      memcpy (dst + pad_cnt, src, src_len);
+    }
+}
+
+/* Copies buffer SRC, of SRC_SIZE bytes, to DST, of DST_SIZE bytes.
+   DST is truncated to DST_SIZE bytes or padded on the right with
+   spaces as needed. */
+void
+buf_copy_rpad (char *dst, size_t dst_size,
+               const char *src, size_t src_size)
+{
+  if (src_size >= dst_size)
+    memmove (dst, src, dst_size);
+  else
+    {
+      memmove (dst, src, src_size);
+      memset (&dst[src_size], ' ', dst_size - src_size);
+    }
+}
+
+/* Copies string SRC to string DST, which is in a buffer DST_SIZE
+   bytes long.
+   Truncates DST to DST_SIZE - 1 characters or right-pads with
+   spaces to DST_SIZE - 1 characters if necessary. */
+void
+str_copy_rpad (char *dst, size_t dst_size, const char *src)
+{
+  size_t src_len = strlen (src);
+  if (src_len < dst_size - 1)
+    {
+      memcpy (dst, src, src_len);
+      memset (&dst[src_len], ' ', dst_size - 1 - src_len);
+    }
+  else
+    memcpy (dst, src, dst_size - 1);
+  dst[dst_size - 1] = 0;
+}
+
+/* Copies SRC to DST, which is in a buffer DST_SIZE bytes long.
+   Truncates DST to DST_SIZE - 1 characters, if necessary. */
+void
+str_copy_trunc (char *dst, size_t dst_size, const char *src) 
+{
+  size_t src_len = strlen (src);
+  assert (dst_size > 0);
+  if (src_len + 1 < dst_size)
+    memcpy (dst, src, src_len + 1);
+  else 
+    {
+      memcpy (dst, src, dst_size - 1);
+      dst[dst_size - 1] = '\0';
+    }
+}
+
+/* Copies buffer SRC, of SRC_LEN bytes,
+   to DST, which is in a buffer DST_SIZE bytes long.
+   Truncates DST to DST_SIZE - 1 characters, if necessary. */
+void
+str_copy_buf_trunc (char *dst, size_t dst_size,
+                    const char *src, size_t src_size) 
+{
+  size_t dst_len;
+  assert (dst_size > 0);
+
+  dst_len = src_size < dst_size ? src_size : dst_size - 1;
+  memcpy (dst, src, dst_len);
+  dst[dst_len] = '\0';
+}
+
+/* Converts each character in S to uppercase. */
+void
+str_uppercase (char *s) 
+{
+  for (; *s != '\0'; s++)
+    *s = toupper ((unsigned char) *s);
+}
+
+/* Converts each character in S to lowercase. */
+void
+str_lowercase (char *s) 
+{
+  for (; *s != '\0'; s++)
+    *s = tolower ((unsigned char) *s);
+}
+\f
+/* Initializes ST with initial contents S. */
+void
+ds_create (struct string *st, const char *s)
+{
+  st->length = strlen (s);
+  st->capacity = 8 + st->length * 2;
+  st->string = xmalloc (st->capacity + 1);
+  strcpy (st->string, s);
+}
+
+/* Initializes ST, making room for at least CAPACITY characters. */
+void
+ds_init (struct string *st, size_t capacity)
+{
+  st->length = 0;
+  if (capacity > 8)
+    st->capacity = capacity;
+  else
+    st->capacity = 8;
+  st->string = xmalloc (st->capacity + 1);
+}
+
+/* Replaces the contents of ST with STRING.  STRING may overlap with
+   ST. */
+void
+ds_replace (struct string *st, const char *string)
+{
+  size_t new_length = strlen (string);
+  if (new_length > st->capacity) 
+    {
+      /* The new length is longer than the allocated length, so
+         there can be no overlap. */
+      st->length = 0;
+      ds_concat (st, string, new_length);
+    }
+  else
+    {
+      /* Overlap is possible, but the new string will fit in the
+         allocated space, so we can just copy data. */
+      st->length = new_length;
+      memmove (st->string, string, st->length);
+    }
+}
+
+/* Frees ST. */
+void
+ds_destroy (struct string *st)
+{
+  free (st->string);
+  st->string = NULL;
+}
+
+/* Swaps the contents of strings A and B. */
+void
+ds_swap (struct string *a, struct string *b) 
+{
+  struct string tmp = *a;
+  *a = *b;
+  *b = tmp;
+}
+
+/* Truncates ST to zero length. */
+void
+ds_clear (struct string *st)
+{
+  st->length = 0;
+}
+
+/* Pad ST on the right with copies of PAD until ST is at least
+   LENGTH characters in size.  If ST is initially LENGTH
+   characters or longer, this is a no-op. */
+void
+ds_rpad (struct string *st, size_t length, char pad) 
+{
+  assert (st != NULL);
+  if (st->length < length) 
+    {
+      if (st->capacity < length)
+        ds_extend (st, length);
+      memset (&st->string[st->length], pad, length - st->length);
+      st->length = length;
+    }
+}
+
+/* Removes trailing spaces from ST.
+   Returns number of spaces removed. */
+int
+ds_rtrim_spaces (struct string *st) 
+{
+  int cnt = 0;
+  while (isspace (ds_last (st))) 
+    {
+      st->length--;
+      cnt++;
+    }
+  return cnt;
+}
+
+/* If the last character in ST is C, removes it and returns true.
+   Otherwise, returns false without modifying ST. */
+bool
+ds_chomp (struct string *st, char c_) 
+{
+  unsigned char c = c_;
+  if (ds_last (st) == c)
+    {
+      st->length--;
+      return true;
+    }
+  else
+    return false;
+}
+
+/* Ensures that ST can hold at least MIN_CAPACITY characters plus a null
+   terminator. */
+void
+ds_extend (struct string *st, size_t min_capacity)
+{
+  if (min_capacity > st->capacity)
+    {
+      st->capacity *= 2;
+      if (st->capacity < min_capacity)
+       st->capacity = min_capacity * 2;
+      
+      st->string = xrealloc (st->string, st->capacity + 1);
+    }
+}
+
+/* Shrink ST to the minimum capacity need to contain its content. */
+void
+ds_shrink (struct string *st)
+{
+  if (st->capacity != st->length)
+    {
+      st->capacity = st->length;
+      st->string = xrealloc (st->string, st->capacity + 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 true if ST is empty, false otherwise. */
+bool
+ds_is_empty (const struct string *st) 
+{
+  return st->length == 0;
+}
+
+/* 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_capacity (const struct string *st)
+{
+  return st->capacity;
+}
+
+/* Returns the first character in ST as a value in the range of
+   unsigned char.  Returns EOF if ST is the empty string. */
+int
+ds_first (const struct string *st) 
+{
+  return st->length > 0 ? (unsigned char) st->string[0] : EOF;
+}
+
+/* Returns the last character in ST as a value in the range of
+   unsigned char.  Returns EOF if ST is the empty string. */
+int
+ds_last (const struct string *st) 
+{
+  return st->length > 0 ? (unsigned char) st->string[st->length - 1] : EOF;
+}
+
+/* Returns the value of ST as a null-terminated string. */
+char *
+ds_c_str (const struct string *st)
+{
+  ((char *) st->string)[st->length] = '\0';
+  return st->string;
+}
+
+/* Returns the string data inside ST. */
+char *
+ds_data (const struct string *st)
+{
+  return st->string;
+}
+
+/* Returns a pointer to the null terminator ST.
+   This might not be an actual null character unless ds_c_str() 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_puts (struct string *st, const char *s)
+{
+  size_t s_len;
+
+  if (!s) return;
+
+  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 (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;
+}
+
+void ds_vprintf (struct string *st, const char *format, va_list args);
+
+
+/* Formats FORMAT as a printf string and appends the result to ST. */
+void
+ds_printf (struct string *st, const char *format, ...)
+{
+  va_list args;
+
+  va_start (args, format);
+  ds_vprintf(st,format,args);
+  va_end (args);
+
+}
+
+/* Formats FORMAT as a printf string and appends the result to ST. */
+void
+ds_vprintf (struct string *st, const char *format, va_list args)
+{
+  /* 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. */
+
+  int avail, needed;
+  va_list a1;
+
+  va_copy(a1, args);
+  avail = st->capacity - st->length + 1;
+  needed = vsnprintf (st->string + st->length, avail, format, args);
+
+
+  if (needed >= avail)
+    {
+      ds_extend (st, st->length + needed);
+      
+      vsprintf (st->string + st->length, format, a1);
+    }
+  else
+    while (needed == -1)
+      {
+       va_list a2;
+       va_copy(a2, a1);
+
+       ds_extend (st, (st->capacity + 1) * 2);
+       avail = st->capacity - st->length + 1;
+
+       needed = vsnprintf (st->string + st->length, avail, format, a2);
+       va_end(a2);
+
+      }
+  va_end(a1);
+
+  st->length += needed;
+}
+
+/* Appends character CH to ST. */
+void
+ds_putc (struct string *st, int ch)
+{
+  if (st->length == st->capacity)
+    ds_extend (st, st->length + 1);
+  st->string[st->length++] = ch;
+}
+
+/* Appends to ST a newline-terminated line read from STREAM.
+   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_gets (struct string *st, FILE *stream)
+{
+  int c;
+
+  c = getc (stream);
+  if (c == EOF)
+    return 0;
+
+  for (;;)
+    {
+      ds_putc (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_gets (st, stream))
+    return 0;
+
+  /* Read additional lines, if any. */
+  for (;;)
+    {
+      /* Remove trailing whitespace. */
+      {
+       char *s = ds_c_str (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_c_str (st)[ds_length (st) - 1] != '\\')
+       break;
+      ds_truncate (st, ds_length (st) - 1);
+
+      /* Append another line and go around again. */
+      {
+       int success = ds_gets (st, stream);
+       where->line_number++;
+       if (!success)
+         return 1;
+      }
+    }
+
+  /* Find a comment and remove. */
+  {
+    char *cp;
+    int quote = 0;
+      
+    for (cp = ds_c_str (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_c_str (st));
+         break;
+       }
+  }
+
+  return 1;
+}
+\f
+/* Lengthed strings. */
+
+/* Creates a new lengthed string LS with contents as a copy of
+   S. */
+void
+ls_create (struct fixed_string *ls, const char *s)
+{
+  ls->length = strlen (s);
+  ls->string = xmalloc (ls->length + 1);
+  memcpy (ls->string, s, ls->length + 1);
+}
+
+/* Creates a new lengthed string LS with contents as a copy of
+   BUFFER with length LEN. */
+void
+ls_create_buffer (struct fixed_string *ls,
+                 const char *buffer, size_t len)
+{
+  ls->length = len;
+  ls->string = xmalloc (len + 1);
+  memcpy (ls->string, buffer, len);
+  ls->string[len] = '\0';
+}
+
+/* Sets the fields of LS to the specified values. */
+void
+ls_init (struct fixed_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 fixed_string *dst, const struct fixed_string *src)
+{
+  *dst = *src;
+}
+
+/* Frees the memory backing LS. */
+void
+ls_destroy (struct fixed_string *ls)
+{
+  free (ls->string);
+}
+
+/* Sets LS to a null pointer value. */
+void
+ls_null (struct fixed_string *ls)
+{
+  ls->string = NULL;
+}
+
+/* Returns nonzero only if LS has a null pointer value. */
+int
+ls_null_p (const struct fixed_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 fixed_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 fixed_string *ls)
+{
+  return ls->length;
+}
+
+/* Returns a pointer to the character string in LS. */
+char *
+ls_c_str (const struct fixed_string *ls)
+{
+  return (char *) ls->string;
+}
+
+/* Returns a pointer to the null terminator of the character string in
+   LS. */
+char *
+ls_end (const struct fixed_string *ls)
+{
+  return (char *) (ls->string + ls->length);
+}
diff --git a/src/libpspp/str.h b/src/libpspp/str.h
new file mode 100644 (file)
index 0000000..22adc29
--- /dev/null
@@ -0,0 +1,241 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !str_h
+#define str_h 1
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "memcasecmp.h"
+#include "memmem.h"
+#include "snprintf.h"
+#include "stpcpy.h"
+#include "strcase.h"
+#include "strftime.h"
+#include "strstr.h"
+#include "strtok_r.h"
+#include "vsnprintf.h"
+#include "xvasprintf.h"
+
+#ifndef HAVE_STRCHR
+#define strchr index
+#endif
+#ifndef HAVE_STRRCHR
+#define strrchr rindex
+#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. */
+\f
+/* Miscellaneous. */
+
+void buf_reverse (char *, size_t);
+char *buf_find_reverse (const char *, size_t, const char *, size_t);
+int buf_compare_case (const char *, const char *, size_t);
+int buf_compare_rpad (const char *, size_t, const char *, size_t);
+void buf_copy_rpad (char *, size_t, const char *, size_t);
+void buf_copy_str_lpad (char *, size_t, const char *);
+void buf_copy_str_rpad (char *, size_t, const char *);
+
+int str_compare_rpad (const char *, const char *);
+void str_copy_rpad (char *, size_t, const char *);
+void str_copy_trunc (char *, size_t, const char *);
+void str_copy_buf_trunc (char *, size_t, const char *, size_t);
+void str_uppercase (char *);
+void str_lowercase (char *);
+\f
+/* Fixed-length strings. */
+struct fixed_string 
+  {
+    char *string;
+    size_t length;
+  };
+
+void ls_create (struct fixed_string *, const char *);
+void ls_create_buffer (struct fixed_string *,
+                      const char *, size_t len);
+void ls_init (struct fixed_string *, const char *, size_t);
+void ls_shallow_copy (struct fixed_string *, const struct fixed_string *);
+void ls_destroy (struct fixed_string *);
+
+void ls_null (struct fixed_string *);
+int ls_null_p (const struct fixed_string *);
+int ls_empty_p (const struct fixed_string *);
+
+size_t ls_length (const struct fixed_string *);
+char *ls_c_str (const struct fixed_string *);
+char *ls_end (const struct fixed_string *);
+
+#if __GNUC__ > 1
+extern inline size_t
+ls_length (const struct fixed_string *st)
+{
+  return st->length;
+}
+
+extern inline char *
+ls_c_str (const struct fixed_string *st)
+{
+  return st->string;
+}
+
+extern inline char *
+ls_end (const struct fixed_string *st)
+{
+  return st->string + st->length;
+}
+#endif
+\f
+/* Variable length strings. */
+
+struct string
+  {
+    size_t length;      /* Length, not including a null terminator. */
+    size_t capacity;    /* Allocated capacity, not including one
+                           extra byte allocated for null terminator. */
+    char *string;       /* String data, not necessarily null
+                           terminated. */
+  };
+
+/* Constructors, destructors. */
+void ds_create (struct string *, const char *);
+void ds_init (struct string *, size_t);
+void ds_destroy (struct string *);
+void ds_swap (struct string *, struct string *);
+
+/* Copy, shrink, extend. */
+void ds_replace (struct string *, const char *);
+void ds_clear (struct string *);
+void ds_extend (struct string *, size_t);
+void ds_shrink (struct string *);
+void ds_truncate (struct string *, size_t);
+void ds_rpad (struct string *, size_t length, char pad);
+int ds_rtrim_spaces (struct string *);
+bool ds_chomp (struct string *, char);
+
+/* Inspectors. */
+bool ds_is_empty (const struct string *);
+size_t ds_length (const struct string *);
+char *ds_c_str (const struct string *);
+char *ds_data (const struct string *);
+char *ds_end (const struct string *);
+size_t ds_capacity (const struct string *);
+int ds_first (const struct string *);
+int ds_last (const struct string *);
+
+/* File input. */
+struct file_locator;
+int ds_gets (struct string *, FILE *);
+int ds_get_config_line (FILE *, struct string *, struct file_locator *);
+
+/* Append. */
+void ds_putc (struct string *, int ch);
+void ds_puts (struct string *, const char *);
+void ds_concat (struct string *, const char *, size_t);
+void ds_vprintf (struct string *st, const char *, va_list);
+void ds_printf (struct string *, const char *, ...)
+     PRINTF_FORMAT (2, 3);
+
+#if __GNUC__ > 1
+extern inline void
+ds_putc (struct string *st, int ch)
+{
+  if (st->length == st->capacity)
+    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_c_str (const struct string *st)
+{
+  ((char *) st->string)[st->length] = '\0';
+  return st->string;
+}
+
+extern inline char *
+ds_data (const struct string *st)
+{
+  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/libpspp/version.h b/src/libpspp/version.h
new file mode 100644 (file)
index 0000000..35ba707
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, 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/math/ChangeLog b/src/math/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/math/chart-geometry.c b/src/math/chart-geometry.c
new file mode 100644 (file)
index 0000000..0125360
--- /dev/null
@@ -0,0 +1,55 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+
+#include <math.h>
+#include <float.h>
+
+#include "chart-geometry.h"
+
+/* Adjust tick to be a sensible value 
+   ie:  ... 0.1,0.2,0.5,   1,2,5,  10,20,50 ... */
+double
+chart_rounded_tick(double tick)
+{
+
+  int i;
+
+  double diff = DBL_MAX;
+  double t = tick;
+    
+  static const double standard_ticks[] = {1, 2, 5, 10};
+
+  const double factor = pow(10,ceil(log10(standard_ticks[0] / tick))) ;
+
+  for (i = 3  ; i >= 0 ; --i) 
+    {
+      const double d = fabs( tick - standard_ticks[i] / factor ) ;
+
+      if ( d < diff ) 
+       {
+         diff = d;
+         t = standard_ticks[i] / factor ;
+       }
+    }
+
+  return t;
+    
+}
+
diff --git a/src/math/chart-geometry.h b/src/math/chart-geometry.h
new file mode 100644 (file)
index 0000000..cb30d07
--- /dev/null
@@ -0,0 +1,26 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+
+#ifndef CHART_GEOMETRY_H
+#define CHART_GEOMETRY_H
+
+double chart_rounded_tick(double tick);
+
+#endif
diff --git a/src/math/design-matrix.c b/src/math/design-matrix.c
new file mode 100644 (file)
index 0000000..138813d
--- /dev/null
@@ -0,0 +1,271 @@
+/* PSPP - Creates design-matrices.
+   Copyright (C) 2005 Free Software Foundation, Inc.
+   Written by Jason H Stover <jason@sakla.net>.
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+/*
+  Create design matrices for procedures that need them.
+*/
+#include <config.h>
+#include <stdlib.h>
+#include <message.h>
+#include "alloc.h"
+#include "message.h"
+#include "variable.h"
+#include "category.h"
+#include "design-matrix.h"
+#include <string.h>
+#include <math.h>
+#include <gsl/gsl_machine.h>
+#include <gsl/gsl_vector.h>
+#include <gsl/gsl_matrix.h>
+
+#define DM_COLUMN_NOT_FOUND -1
+#define DM_INDEX_NOT_FOUND -3
+
+/*
+  Which element of a vector is equal to the value x?
+ */
+static size_t
+cat_which_element_eq (const gsl_vector * vec, double x)
+{
+  size_t i;
+
+  for (i = 0; i < vec->size; i++)
+    {
+      if (fabs (gsl_vector_get (vec, i) - x) < GSL_DBL_EPSILON)
+       {
+         return i;
+       }
+    }
+  return CAT_VALUE_NOT_FOUND;
+}
+static int
+cat_is_zero_vector (const gsl_vector * vec)
+{
+  size_t i;
+
+  for (i = 0; i < vec->size; i++)
+    {
+      if (gsl_vector_get (vec, i) != 0.0)
+       {
+         return 0;
+       }
+    }
+  return 1;
+}
+
+/*
+  Return the value of v corresponding to the vector vec.
+ */
+union value *
+cat_vector_to_value (const gsl_vector * vec, struct variable *v)
+{
+  size_t i;
+
+  i = cat_which_element_eq (vec, 1.0);
+  if (i != CAT_VALUE_NOT_FOUND)
+    {
+      return cat_subscript_to_value (i + 1, v);
+    }
+  if (cat_is_zero_vector (vec))
+    {
+      return cat_subscript_to_value (0, v);
+    }
+  return NULL;
+}
+
+struct design_matrix *
+design_matrix_create (int n_variables,
+                     const struct variable *v_variables[],
+                     const size_t n_data)
+{
+  struct design_matrix *dm;
+  const struct variable *v;
+  size_t i;
+  size_t n_cols = 0;
+  size_t col;
+
+  dm = xmalloc (sizeof *dm);
+  dm->vars = xnmalloc (n_variables, sizeof *dm->vars);
+  dm->n_vars = n_variables;
+
+  for (i = 0; i < n_variables; i++)
+    {
+      v = v_variables[i];
+      assert ((dm->vars + i) != NULL);
+      (dm->vars + i)->v = v;   /* Allows us to look up the variable from
+                                  the design matrix. */
+      (dm->vars + i)->first_column = n_cols;
+      if (v->type == NUMERIC)
+       {
+         (dm->vars + i)->last_column = n_cols;
+         n_cols++;
+       }
+      else if (v->type == ALPHA)
+       {
+         assert (v->obs_vals != NULL);
+         (dm->vars + i)->last_column =
+           (dm->vars + i)->first_column + v->obs_vals->n_categories - 2;
+         n_cols += v->obs_vals->n_categories - 1;
+       }
+    }
+  dm->m = gsl_matrix_calloc (n_data, n_cols);
+  col = 0;
+
+  return dm;
+}
+
+void
+design_matrix_destroy (struct design_matrix *dm)
+{
+  free (dm->vars);
+  gsl_matrix_free (dm->m);
+  free (dm);
+}
+
+/*
+  Return the index of the variable for the
+  given column.
+ */
+static size_t
+design_matrix_col_to_var_index (const struct design_matrix *dm, size_t col)
+{
+  size_t i;
+  struct design_matrix_var v;
+
+  for (i = 0; i < dm->n_vars; i++)
+    {
+      v = dm->vars[i];
+      if (v.first_column <= col && col <= v.last_column)
+       return (v.v)->index;
+    }
+  return DM_INDEX_NOT_FOUND;
+}
+
+/*
+  Return a pointer to the variable whose values
+  are stored in column col.
+ */
+struct variable *
+design_matrix_col_to_var (const struct design_matrix *dm, size_t col)
+{
+  size_t index;
+  size_t i;
+  struct design_matrix_var dmv;
+
+  index = design_matrix_col_to_var_index (dm, col);
+  for (i = 0; i < dm->n_vars; i++)
+    {
+      dmv = dm->vars[i];
+      if ((dmv.v)->index == index)
+       {
+         return (struct variable *) dmv.v;
+       }
+    }
+  return NULL;
+}
+
+static size_t
+cmp_dm_var_index (const struct design_matrix_var *dmv, size_t index)
+{
+  if (dmv->v->index == index)
+    return 1;
+  return 0;
+}
+
+/*
+  Return the number of the first column which holds the
+  values for variable v.
+ */
+size_t
+design_matrix_var_to_column (const struct design_matrix * dm,
+                            const struct variable * v)
+{
+  size_t i;
+  struct design_matrix_var tmp;
+
+  for (i = 0; i < dm->n_vars; i++)
+    {
+      tmp = dm->vars[i];
+      if (cmp_dm_var_index (&tmp, v->index))
+       {
+         return tmp.first_column;
+       }
+    }
+  return DM_COLUMN_NOT_FOUND;
+}
+
+/* Last column. */
+static size_t
+dm_var_to_last_column (const struct design_matrix *dm,
+                      const struct variable *v)
+{
+  size_t i;
+  struct design_matrix_var tmp;
+
+  for (i = 0; i < dm->n_vars; i++)
+    {
+      tmp = dm->vars[i];
+      if (cmp_dm_var_index (&tmp, v->index))
+       {
+         return tmp.last_column;
+       }
+    }
+  return DM_COLUMN_NOT_FOUND;
+}
+
+/*
+  Set the appropriate value in the design matrix, 
+  whether that value is from a categorical or numeric
+  variable. For a categorical variable, only the usual
+  binary encoding is allowed.
+ */
+void
+design_matrix_set_categorical (struct design_matrix *dm, size_t row,
+                              const struct variable *var,
+                              const union value *val)
+{
+  size_t col;
+  size_t is_one;
+  size_t fc;
+  size_t lc;
+  double entry;
+
+  assert (var->type == ALPHA);
+  fc = design_matrix_var_to_column (dm, var);
+  lc = dm_var_to_last_column (dm, var);
+  assert (lc != DM_COLUMN_NOT_FOUND);
+  assert (fc != DM_COLUMN_NOT_FOUND);
+  is_one = fc + cat_value_find (var, val);
+  for (col = fc; col <= lc; col++)
+    {
+      entry = (col == is_one) ? 1.0 : 0.0;
+      gsl_matrix_set (dm->m, row, col, entry);
+    }
+}
+void
+design_matrix_set_numeric (struct design_matrix *dm, size_t row,
+                          const struct variable *var, const union value *val)
+{
+  size_t col;
+
+  assert (var->type == NUMERIC);
+  col = design_matrix_var_to_column ((const struct design_matrix *) dm, var);
+  assert (col != DM_COLUMN_NOT_FOUND);
+  gsl_matrix_set (dm->m, row, col, val->f);
+}
diff --git a/src/math/design-matrix.h b/src/math/design-matrix.h
new file mode 100644 (file)
index 0000000..7f13fe8
--- /dev/null
@@ -0,0 +1,85 @@
+/* PSPP - Creates design matrices.
+   Copyright (C) 2005 Free Software Foundation, Inc.
+   Written by Jason H Stover <jason@sakla.net>.
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+/*
+  Create design matrices for procedures that need them.
+ */
+
+#ifndef DESIGN_MATRIX_H
+#define DESIGN_MATRIX_H
+
+#include <gsl/gsl_matrix.h>
+#include <stdbool.h>
+#include "category.h"
+#include "cat-routines.h"
+struct design_matrix_var
+{
+  size_t first_column;         /* First column for this variable in
+                                  the design_matix. If this variable
+                                  is categorical, its values are
+                                  stored in multiple, contiguous
+                                  columns, as dictated by its vector
+                                  encoding in the variable's struct
+                                  cat_vals.
+                                */
+  size_t last_column;
+  const struct variable *v;
+};
+struct design_matrix
+{
+  gsl_matrix *m;
+  struct design_matrix_var *vars;      /* Element i corresponds to
+                                          the variable whose values
+                                          are stored in at least one
+                                          column of m. If that
+                                          variable is categorical
+                                          with more than two
+                                          categories, its values are
+                                          stored in multiple,
+                                          contiguous columns. The
+                                          variable's values are then
+                                          stored in the columns
+                                          first_column through
+                                          last_column of the
+                                          design_matrix_var
+                                          structure.
+                                        */
+  size_t n_vars;
+};
+union value *cat_vector_to_value (const gsl_vector *, struct variable *);
+
+struct design_matrix *design_matrix_create (int, const struct variable *[],
+                                           const size_t);
+
+void design_matrix_destroy (struct design_matrix *);
+
+void design_matrix_set_categorical (struct design_matrix *, size_t,
+                                   const struct variable *,
+                                   const union value *);
+
+void design_matrix_set_numeric (struct design_matrix *, size_t,
+                               const struct variable *, const union value *);
+
+size_t design_matrix_var_to_column (const struct design_matrix *,
+                                   const struct variable *);
+
+struct variable *design_matrix_col_to_var (const struct design_matrix *,
+                                          size_t);
+
+#endif
diff --git a/src/math/factor-stats.c b/src/math/factor-stats.c
new file mode 100644 (file)
index 0000000..8b6f4e8
--- /dev/null
@@ -0,0 +1,333 @@
+/* PSPP - A program for statistical analysis . -*-c-*-
+
+Copyright (C) 2004 Free Software Foundation, Inc.
+Author: John Darrington 2004
+
+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., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA. */
+
+#include <config.h>
+#include "factor-stats.h"
+#include "value.h"
+#include "hash.h"
+#include "array.h"
+#include "alloc.h"
+#include "moments.h"
+#include "percentiles.h"
+
+#include <stdlib.h>
+#include <math.h>
+#include <float.h>
+#include <assert.h>
+#include "histogram.h"
+
+
+void
+metrics_precalc(struct metrics *m)
+{
+  assert (m) ;
+
+  m->n_missing = 0;
+
+  m->min = DBL_MAX;
+  m->max = -DBL_MAX;
+
+  m->histogram = 0;
+
+  m->moments = moments1_create(MOMENT_KURTOSIS);
+
+  m->ordered_data = hsh_create(20,
+                               (hsh_compare_func *) compare_values,
+                               (hsh_hash_func *) hash_value,
+                               (hsh_free_func *) weighted_value_free,
+                               (void *) 0);
+}
+
+
+/* Include val in the calculation for the metrics.
+   If val is null, then treat it as MISSING
+*/
+void
+metrics_calc(struct metrics *fs, const union value *val, 
+            double weight, int case_no)
+{
+  struct weighted_value **wv;
+  double x;
+  
+  if ( ! val ) 
+    {
+      fs->n_missing += weight;
+      return ;
+    }
+
+  x = val->f;
+
+  moments1_add(fs->moments, x, weight);
+
+
+  if ( x < fs->min) fs->min = x;
+  if ( x > fs->max) fs->max = x;
+
+
+  wv = (struct weighted_value **) hsh_probe (fs->ordered_data,(void *) val );
+
+  if ( *wv  ) 
+    {
+      /* If this value has already been seen, then simply 
+        increase its weight  and push a new case number */
+
+      struct case_node *cn;
+
+      assert( (*wv)->v.f == val->f );
+      (*wv)->w += weight;      
+
+      cn = xmalloc ( sizeof *cn);
+      cn->next = (*wv)->case_nos ;
+      cn->num = case_no;
+
+      (*wv)->case_nos = cn;
+    }
+  else
+    {
+      struct case_node *cn;
+
+      *wv = weighted_value_create();
+      (*wv)->v = *val;
+      (*wv)->w = weight;
+      
+      cn = xmalloc (sizeof *cn);
+      cn->next=0;
+      cn->num = case_no;
+      (*wv)->case_nos  = cn;
+
+    }
+
+}
+
+void
+metrics_postcalc(struct metrics *m)
+{
+  double cc = 0.0;
+  double tc ;
+  int k1, k2 ;
+  int i;
+  int j = 1;  
+
+  moments1_calculate (m->moments, &m->n, &m->mean, &m->var, 
+                     &m->skewness, &m->kurtosis);
+
+  moments1_destroy (m->moments);
+
+
+  m->stddev = sqrt(m->var);
+
+  /* FIXME: Check this is correct ???
+     Shouldn't we use the sample variance ??? */
+  m->se_mean = sqrt (m->var / m->n) ;
+
+
+
+  m->wvp = (struct weighted_value **) hsh_sort(m->ordered_data);
+  m->n_data = hsh_count(m->ordered_data);
+
+  /* Trimmed mean calculation */
+  if ( m->n_data <= 1 ) 
+    {
+      m->trimmed_mean = m->mean;
+      return;
+    }
+
+  m->histogram = histogram_create(10, m->min, m->max);
+
+  for ( i = 0 ; i < m->n_data ; ++i ) 
+    {
+      struct weighted_value **wv = (m->wvp) ;
+      gsl_histogram_accumulate(m->histogram, wv[i]->v.f, wv[i]->w);
+    }
+
+  tc = m->n * 0.05 ;
+  k1 = -1;
+  k2 = -1;
+
+  for ( i = 0 ; i < m->n_data ; ++i ) 
+    {
+      cc += m->wvp[i]->w;
+      m->wvp[i]->cc = cc;
+
+      m->wvp[i]->rank = j + (m->wvp[i]->w - 1) / 2.0 ;
+      
+      j += m->wvp[i]->w;
+      
+      if ( cc < tc ) 
+       k1 = i;
+    }
+
+  
+
+  k2 = m->n_data;
+  for ( i = m->n_data -1  ; i >= 0; --i ) 
+    {
+      if ( tc > m->n - m->wvp[i]->cc) 
+       k2 = i;
+    }
+
+
+  /* Calculate the percentiles */
+  ptiles(m->ptile_hash, m->wvp, m->n_data, m->n, m->ptile_alg);
+
+  tukey_hinges(m->wvp, m->n_data, m->n, m->hinge);
+
+  /* Special case here */
+  if ( k1 + 1 == k2 ) 
+    {
+      m->trimmed_mean = m->wvp[k2]->v.f;
+      return;
+    }
+
+  m->trimmed_mean = 0;
+  for ( i = k1 + 2 ; i <= k2 - 1 ; ++i ) 
+    {
+      m->trimmed_mean += m->wvp[i]->v.f * m->wvp[i]->w;
+    }
+
+
+  m->trimmed_mean += (m->n - m->wvp[k2 - 1]->cc - tc) * m->wvp[k2]->v.f ;
+  m->trimmed_mean += (m->wvp[k1 + 1]->cc - tc) * m->wvp[k1 + 1]->v.f ;
+  m->trimmed_mean /= 0.9 * m->n ;
+
+
+}
+
+
+struct weighted_value *
+weighted_value_create(void)
+{
+  struct weighted_value *wv;
+  wv = xmalloc (sizeof *wv);
+
+  wv->cc = 0;
+  wv->case_nos = 0;
+
+  return wv;
+}
+
+void 
+weighted_value_free(struct weighted_value *wv)
+{
+  struct case_node *cn ;
+
+  if ( !wv ) 
+    return ;
+
+  cn = wv->case_nos;
+
+  while(cn)
+    {
+      struct case_node *next = cn->next;
+      
+      free(cn);
+      cn = next;
+    }
+
+  free(wv);
+
+}
+
+
+
+
+
+/* Create a factor statistics object with for N dependent vars
+   and ID as the value of the independent variable */
+struct factor_statistics * 
+create_factor_statistics (int n, union value *id0, union value *id1)
+{
+  struct factor_statistics *f;
+
+  f = xmalloc (sizeof *f);
+
+  f->id[0] = *id0;
+  f->id[1] = *id1;
+  f->m = xnmalloc (n, sizeof *f->m);
+  memset (f->m, 0, sizeof(struct metrics) * n);
+  f->n_var = n;
+
+  return f;
+}
+
+
+void 
+metrics_destroy(struct metrics *m)
+{
+  hsh_destroy(m->ordered_data);
+  hsh_destroy(m->ptile_hash);
+  if ( m-> histogram ) 
+    gsl_histogram_free(m->histogram);
+}
+
+void
+factor_statistics_free(struct factor_statistics *f)
+{
+
+  int i; 
+  for ( i = 0 ; i < f->n_var; ++i ) 
+       metrics_destroy(&f->m[i]);
+  free(f->m) ; 
+  free(f);
+}
+
+
+
+
+int 
+factor_statistics_compare(const struct factor_statistics *f0,
+                         const struct factor_statistics *f1, int width)
+{
+
+  int cmp0;
+
+  assert(f0);
+  assert(f1);
+
+  cmp0 = compare_values(&f0->id[0], &f1->id[0], width);
+
+  if ( cmp0 != 0 ) 
+    return cmp0;
+
+
+  if ( ( f0->id[1].f == SYSMIS )  && (f1->id[1].f != SYSMIS) ) 
+    return 1;
+
+  if ( ( f0->id[1].f != SYSMIS )  && (f1->id[1].f == SYSMIS) ) 
+    return -1;
+
+  return compare_values(&f0->id[1], &f1->id[1], width);
+  
+}
+
+unsigned int 
+factor_statistics_hash(const struct factor_statistics *f, int width)
+{
+  
+  unsigned int h;
+
+  h = hash_value(&f->id[0], width);
+  
+  if ( f->id[1].f != SYSMIS )
+    h += hash_value(&f->id[1], width);
+
+  return h;
+}
+       
diff --git a/src/math/factor-stats.h b/src/math/factor-stats.h
new file mode 100644 (file)
index 0000000..6fa8650
--- /dev/null
@@ -0,0 +1,167 @@
+/* PSPP - A program for statistical analysis . -*-c-*-
+
+Copyright (C) 2004 Free Software Foundation, Inc.
+Author: John Darrington 2004
+
+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., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA. */
+
+#ifndef FACTOR_STATS
+#define FACTOR_STATS
+
+
+/* FIXME: These things should probably be amalgamated with the 
+   group_statistics struct */
+
+#include "hash.h"
+#include "value.h"
+#include <string.h>
+#include <gsl/gsl_histogram.h>
+#include "percentiles.h"
+
+
+struct moments1;
+
+struct metrics
+{
+  double n;
+
+  double n_missing;
+  
+  double min;
+
+  double max;
+
+  double mean;
+  
+  double se_mean;
+
+  double var;
+
+  double stddev;
+
+  struct moments1 *moments;
+
+  gsl_histogram *histogram;
+
+  double skewness;
+  double kurtosis;
+
+  double trimmed_mean;
+
+  /* A hash of data for this factor. */
+  struct hsh_table *ordered_data;
+
+  /* A Pointer to this hash table AFTER it has been SORTED and crunched */
+  struct weighted_value **wvp;
+
+  /* The number of values in the above array
+     (if all the weights are 1, then this will
+     be the same as n) */
+  int n_data;
+
+  /* Percentile stuff */
+
+  /* A hash of struct percentiles */
+  struct hsh_table *ptile_hash;
+
+  /* Algorithm to be used for calculating percentiles */
+  enum pc_alg ptile_alg;
+
+  /* Tukey's Hinges */
+  double hinge[3];
+
+};
+
+
+struct metrics * metrics_create(void);
+
+void metrics_precalc(struct metrics *m);
+
+void metrics_calc(struct metrics *m, const union value *f, double weight, 
+                 int case_no);
+
+void metrics_postcalc(struct metrics *m);
+
+void  metrics_destroy(struct metrics *m);
+
+
+
+/* Linked list of case nos */
+struct case_node
+{
+  int num;
+  struct case_node *next;
+};
+
+struct weighted_value 
+{
+  union value v;
+
+  /* The weight */
+  double w;
+
+  /* The cumulative weight */
+  double cc; 
+
+  /* The rank */
+  double rank;
+
+  /* Linked list of cases nos which have this value */
+  struct case_node *case_nos;
+  
+};
+
+
+struct weighted_value *weighted_value_create(void);
+
+void weighted_value_free(struct weighted_value *wv);
+
+
+
+struct factor_statistics {
+
+  /* The values of the independent variables */
+  union value id[2];
+
+  /* The an array stats for this factor, one for each dependent var */
+  struct metrics *m;
+
+  /* The number of dependent variables */
+  int n_var;
+};
+
+
+/* Create a factor statistics object with for N dependent vars
+   and ID as the value of the independent variable */
+struct factor_statistics * 
+create_factor_statistics (int n, union value *id0, union value *id1);
+
+
+void factor_statistics_free(struct factor_statistics *f);
+
+
+/* Compare f0 and f1.
+   width is the width of the independent variable */
+int 
+factor_statistics_compare(const struct factor_statistics *f0,
+                         const struct factor_statistics *f1, int width);
+
+                             
+
+unsigned int 
+factor_statistics_hash(const struct factor_statistics *f, int width);
+
+#endif
diff --git a/src/math/group-proc.h b/src/math/group-proc.h
new file mode 100644 (file)
index 0000000..9132ef9
--- /dev/null
@@ -0,0 +1,51 @@
+/* PSPP - computes sample statistics.
+
+   Copyright (C) 2004 Free Software Foundation, Inc.
+
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef GROUP_DATA_H
+#define GROUP_DATA_H
+
+#include "group.h"
+
+/* private data for commands dealing with grouped data*/
+struct group_proc
+{
+  /* Stats for the `universal group'  (ie the totals) */
+  struct group_statistics ugs;
+
+  /* The number of groups */
+  int n_groups;
+
+  /* The levene statistic */
+  double levene ;
+
+  /* A hash of group statistics keyed by the value of the 
+     independent variable */
+  struct hsh_table *group_hash;
+
+  /* Mean square error */
+  double mse ; 
+
+};
+
+struct variable;
+struct group_proc *group_proc_get (struct variable *);
+
+#endif
diff --git a/src/math/group.c b/src/math/group.c
new file mode 100644 (file)
index 0000000..58d5c92
--- /dev/null
@@ -0,0 +1,68 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "hash.h"
+#include "group.h"
+#include "group-proc.h"
+#include "str.h"
+#include "variable.h"
+#include "misc.h"
+
+
+/* Return -1 if the id of a is less than b; +1 if greater than and 
+   0 if equal */
+int 
+compare_group(const struct group_statistics *a, 
+                const struct group_statistics *b, 
+                int width)
+{
+  return compare_values(&a->id, &b->id, width);
+}
+
+
+
+unsigned 
+hash_group(const struct group_statistics *g, int width)
+{
+  unsigned id_hash;
+
+  id_hash = hash_value(&g->id, width);
+
+  return id_hash;
+}
+
+
+void  
+free_group(struct group_statistics *v, void *aux UNUSED)
+{
+  free(v);
+}
+
+
+struct group_proc *
+group_proc_get (struct variable *v)
+{
+  /* This is not ideal, obviously. */
+  if (v->aux == NULL) 
+    var_attach_aux (v, xmalloc (sizeof (struct group_proc)), var_dtor_free);
+  return v->aux;
+}
diff --git a/src/math/group.h b/src/math/group.h
new file mode 100644 (file)
index 0000000..43714d9
--- /dev/null
@@ -0,0 +1,91 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+
+#ifndef GROUP_H
+#define GROUP_H
+
+
+#include "value.h"
+
+
+/* Statistics for grouped data */
+struct group_statistics
+  {
+    /* The value of the independent variable for this group */
+    union value id;
+
+    /* The arithmetic mean */
+    double mean;
+
+    /* Population std. deviation */
+    double std_dev;
+
+    /* Sample std. deviation */
+    double s_std_dev;
+    
+    /* count */
+    double n;
+
+    double sum;
+
+    /* Sum of squares */
+    double ssq;
+
+    /* Std Err of Mean */
+    double se_mean;
+
+    /* Sum of differences */
+    double sum_diff;
+
+    /* Mean of differences */
+    double mean_diff ;
+
+    /* Running total of the Levene for this group */
+    double lz_total;
+    
+    /* Group mean of Levene */
+    double lz_mean; 
+
+
+    /* min and max values */
+    double minimum ; 
+    double maximum ;
+
+
+  };
+
+
+
+
+/* These funcs are useful for hash tables */
+
+/* Return -1 if the id of a is less than b; +1 if greater than and 
+   0 if equal */
+int  compare_group(const struct group_statistics *a, 
+                  const struct group_statistics *b, 
+                  int width);
+
+unsigned hash_group(const struct group_statistics *g, int width);
+
+void  free_group(struct group_statistics *v, void *aux);
+
+
+
+#endif
diff --git a/src/math/histogram.c b/src/math/histogram.c
new file mode 100644 (file)
index 0000000..6097a47
--- /dev/null
@@ -0,0 +1,53 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <math.h>
+#include <gsl/gsl_histogram.h>
+#include <assert.h>
+#include "histogram.h"
+#include "chart-geometry.h"
+
+
+gsl_histogram *
+histogram_create(double bins, double x_min, double x_max)
+{
+  int n;
+  double bin_width ;
+  double bin_width_2 ;
+  double upper_limit, lower_limit;
+
+  gsl_histogram *hist = gsl_histogram_alloc(bins);
+
+  bin_width = chart_rounded_tick((x_max - x_min)/ bins);
+  bin_width_2 = bin_width / 2.0;
+    
+  n =  ceil( x_max / (bin_width_2) ) ; 
+  if ( ! (n % 2 ) ) n++;
+  upper_limit = n * bin_width_2;
+
+  n =  floor( x_min / (bin_width_2) ) ; 
+  if ( ! (n % 2 ) ) n--;
+  lower_limit = n * bin_width_2;
+
+  gsl_histogram_set_ranges_uniform(hist, lower_limit, upper_limit);
+
+  return hist;
+}
+
diff --git a/src/math/histogram.h b/src/math/histogram.h
new file mode 100644 (file)
index 0000000..d1ee7c3
--- /dev/null
@@ -0,0 +1,27 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef HISTOGRAM_H
+#define HISTOGRAM_H
+
+#include <gsl/gsl_histogram.h>
+
+gsl_histogram * histogram_create(double bins, double x_min, double x_max);
+
+#endif
diff --git a/src/math/levene.c b/src/math/levene.c
new file mode 100644 (file)
index 0000000..105fc09
--- /dev/null
@@ -0,0 +1,378 @@
+/* This file is part of GNU PSPP 
+   Computes Levene test  statistic.
+
+   Copyright (C) 2004 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "levene.h"
+#include "message.h"
+#include "case.h"
+#include "casefile.h"
+#include "dictionary.h"
+#include "group-proc.h"
+#include "hash.h"
+#include "str.h"
+#include "variable.h"
+#include "procedure.h"
+#include "alloc.h"
+#include "misc.h"
+#include "group.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+
+/* This module calculates the Levene statistic for variables.
+
+   Just for reference, the Levene Statistic is a defines as follows:
+
+   W = \frac{ (n-k)\sum_{i=1}^k n_i(Z_{iL} - Z_{LL})^2}
+            { (k-1)\sum_{i=1}^k \sum_{j=1}^{n_i} (Z_{ij} - Z_{iL})^2}
+
+   where:
+        k is the number of groups
+       n is the total number of samples
+        n_i is the number of samples in the ith group
+        Z_{ij} is | Y_{ij} - Y_{iL} | where Y_{iL} is the mean of the ith group
+       Z_{iL} is the  mean of Z_{ij} over the ith group
+       Z_{LL} is the grand mean of Z_{ij}
+
+   Imagine calculating that with pencil and paper!
+
+ */
+
+
+struct levene_info
+{
+
+  /* Per group statistics */
+  struct t_test_proc **group_stats;
+
+  /* The independent variable */
+  struct variable *v_indep; 
+
+  /* Number of dependent variables */
+  size_t n_dep;
+
+  /* The dependent variables */
+  struct variable  **v_dep;
+
+  /* How to treat missing values */
+  enum lev_missing missing;
+
+  /* Function to test for missing values */
+  is_missing_func *is_missing;
+};
+
+/* First pass */
+static void  levene_precalc (const struct levene_info *l);
+static int levene_calc (const struct ccase *, void *);
+static void levene_postcalc (void *);
+
+
+/* Second pass */
+static void levene2_precalc (void *);
+static int levene2_calc (const struct ccase *, void *);
+static void levene2_postcalc (void *);
+
+
+void  
+levene(const struct casefile *cf,
+       struct variable *v_indep, size_t n_dep, struct variable **v_dep,
+            enum lev_missing missing,   is_missing_func value_is_missing)
+{
+  struct casereader *r;
+  struct ccase c;
+  struct levene_info l;
+
+  l.n_dep      = n_dep;
+  l.v_indep    = v_indep;
+  l.v_dep      = v_dep;
+  l.missing    = missing;
+  l.is_missing = value_is_missing;
+
+
+
+  levene_precalc(&l);
+  for(r = casefile_get_reader (cf);
+      casereader_read (r, &c) ;
+      case_destroy (&c)) 
+    {
+      levene_calc(&c,&l);
+    }
+  casereader_destroy (r);
+  levene_postcalc(&l);
+
+  levene2_precalc(&l);
+  for(r = casefile_get_reader (cf);
+      casereader_read (r, &c) ;
+      case_destroy (&c)) 
+    {
+      levene2_calc(&c,&l);
+    }
+  casereader_destroy (r);
+  levene2_postcalc(&l);
+
+}
+
+/* Internal variables used in calculating the Levene statistic */
+
+/* Per variable statistics */
+struct lz_stats
+{
+  /* Total of all lz */
+  double grand_total;
+
+  /* Mean of all lz */
+  double grand_mean;
+
+  /* The total number of cases */
+  double total_n ; 
+
+  /* Number of groups */
+  int n_groups;
+};
+
+/* An array of lz_stats for each variable */
+static struct lz_stats *lz;
+
+
+static void 
+levene_precalc (const struct levene_info *l)
+{
+  size_t i;
+
+  lz = xnmalloc (l->n_dep, sizeof *lz);
+
+  for(i = 0; i < l->n_dep ; ++i ) 
+    {
+      struct variable *var = l->v_dep[i];
+      struct group_proc *gp = group_proc_get (var);
+      struct group_statistics *gs;
+      struct hsh_iterator hi;
+
+      lz[i].grand_total = 0;
+      lz[i].total_n = 0;
+      lz[i].n_groups = gp->n_groups ; 
+
+      
+      for ( gs = hsh_first(gp->group_hash, &hi);
+           gs != 0;
+           gs = hsh_next(gp->group_hash, &hi))
+       {
+         gs->lz_total = 0;
+       }
+           
+    }
+
+}
+
+static int 
+levene_calc (const struct ccase *c, void *_l)
+{
+  size_t i;
+  int warn = 0;
+  struct levene_info *l = (struct levene_info *) _l;
+  const union value *gv = case_data (c, l->v_indep->fv);
+  struct group_statistics key;
+  double weight = dict_get_case_weight(default_dict,c,&warn); 
+
+  /* Skip the entire case if /MISSING=LISTWISE is set */
+  if ( l->missing == LEV_LISTWISE ) 
+    {
+      for (i = 0; i < l->n_dep; ++i) 
+       {
+         struct variable *v = l->v_dep[i];
+         const union value *val = case_data (c, v->fv);
+
+         if (l->is_missing (&v->miss, val) )
+           {
+             return 0;
+           }
+       }
+    }
+
+  
+  key.id = *gv;
+
+  for (i = 0; i < l->n_dep; ++i) 
+    {
+      struct variable *var = l->v_dep[i];
+      struct group_proc *gp = group_proc_get (var);
+      double levene_z;
+      const union value *v = case_data (c, var->fv);
+      struct group_statistics *gs;
+
+      gs = hsh_find(gp->group_hash,(void *) &key );
+
+      if ( 0 == gs ) 
+       continue ;
+
+      if ( ! l->is_missing(&var->miss, v))
+       {
+         levene_z= fabs(v->f - gs->mean);
+         lz[i].grand_total += levene_z * weight;
+         lz[i].total_n += weight; 
+
+         gs->lz_total += levene_z * weight;
+       }
+
+    }
+  return 0;
+}
+
+
+static void 
+levene_postcalc (void *_l)
+{
+  size_t v;
+
+  struct levene_info *l = (struct levene_info *) _l;
+
+  for (v = 0; v < l->n_dep; ++v) 
+    {
+      /* This is Z_LL */
+      lz[v].grand_mean = lz[v].grand_total / lz[v].total_n ;
+    }
+
+  
+}
+
+
+/* The denominator for the expression for the Levene */
+static double *lz_denominator;
+
+static void 
+levene2_precalc (void *_l)
+{
+  size_t v;
+
+  struct levene_info *l = (struct levene_info *) _l;
+
+  lz_denominator = xnmalloc (l->n_dep, sizeof *lz_denominator);
+
+  /* This stuff could go in the first post calc . . . */
+  for (v = 0; v < l->n_dep; ++v) 
+    {
+      struct hsh_iterator hi;
+      struct group_statistics *g;
+
+      struct variable *var = l->v_dep[v] ;
+      struct hsh_table *hash = group_proc_get (var)->group_hash;
+
+
+      for(g = (struct group_statistics *) hsh_first(hash,&hi);
+         g != 0 ;
+         g = (struct group_statistics *) hsh_next(hash,&hi) )
+       {
+         g->lz_mean = g->lz_total / g->n ;
+       }
+      lz_denominator[v] = 0;
+  }
+}
+
+static int 
+levene2_calc (const struct ccase *c, void *_l)
+{
+  size_t i;
+  int warn = 0;
+
+  struct levene_info *l = (struct levene_info *) _l;
+
+  double weight = dict_get_case_weight(default_dict,c,&warn); 
+
+  const union value *gv = case_data (c, l->v_indep->fv);
+  struct group_statistics key;
+
+  /* Skip the entire case if /MISSING=LISTWISE is set */
+  if ( l->missing == LEV_LISTWISE ) 
+    {
+      for (i = 0; i < l->n_dep; ++i) 
+       {
+         struct variable *v = l->v_dep[i];
+         const union value *val = case_data (c, v->fv);
+
+         if (l->is_missing(&v->miss, val) )
+           {
+             return 0;
+           }
+       }
+    }
+
+  key.id = *gv;
+
+  for (i = 0; i < l->n_dep; ++i) 
+    {
+      double levene_z;
+      struct variable *var = l->v_dep[i] ;
+      const union value *v = case_data (c, var->fv);
+      struct group_statistics *gs;
+
+      gs = hsh_find(group_proc_get (var)->group_hash,(void *) &key );
+
+      if ( 0 == gs ) 
+       continue;
+
+      if ( ! l->is_missing (&var->miss, v) )
+       {
+         levene_z = fabs(v->f - gs->mean); 
+         lz_denominator[i] += weight * pow2(levene_z - gs->lz_mean);
+       }
+    }
+
+  return 0;
+}
+
+
+static void 
+levene2_postcalc (void *_l)
+{
+  size_t v;
+
+  struct levene_info *l = (struct levene_info *) _l;
+
+  for (v = 0; v < l->n_dep; ++v) 
+    {
+      double lz_numerator = 0;
+      struct hsh_iterator hi;
+      struct group_statistics *g;
+
+      struct variable *var = l->v_dep[v] ;
+      struct group_proc *gp = group_proc_get (var);
+      struct hsh_table *hash = gp->group_hash;
+
+      for(g = (struct group_statistics *) hsh_first(hash,&hi);
+         g != 0 ;
+         g = (struct group_statistics *) hsh_next(hash,&hi) )
+       {
+         lz_numerator += g->n * pow2(g->lz_mean - lz[v].grand_mean );
+       }
+      lz_numerator *= ( gp->ugs.n - gp->n_groups );
+
+      lz_denominator[v] *= (gp->n_groups - 1);
+
+      gp->levene = lz_numerator / lz_denominator[v] ;
+
+    }
+
+  /* Now clear up after ourselves */
+  free(lz_denominator);
+  free(lz);
+}
+
diff --git a/src/math/levene.h b/src/math/levene.h
new file mode 100644 (file)
index 0000000..edbcd80
--- /dev/null
@@ -0,0 +1,49 @@
+/* This file is part of GNU PSPP 
+   Computes Levene test  statistic.
+
+   Copyright (C) 2004 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !levene_h
+#define levene_h 1
+
+
+#include "variable.h"
+#include "casefile.h"
+
+/* What to do with missing values */
+enum lev_missing { LEV_ANALYSIS, LEV_LISTWISE };
+
+/* Calculate the Levene statistic 
+
+The independent variable :   v_indep; 
+
+Number of dependent variables :   n_dep;
+
+The dependent variables :   v_dep;
+
+*/
+
+
+void  levene(const struct casefile *cf, 
+            struct variable *v_indep, size_t n_dep, struct variable **v_dep,
+            enum lev_missing,   is_missing_func);
+
+
+
+#endif /* levene_h */
diff --git a/src/math/moments.c b/src/math/moments.c
new file mode 100644 (file)
index 0000000..ce2f825
--- /dev/null
@@ -0,0 +1,493 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "moments.h"
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "misc.h"
+#include "value.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+\f
+/* Calculates variance, skewness, and kurtosis into *VARIANCE,
+   *SKEWNESS, and *KURTOSIS if they are non-null and not greater
+   moments than MAX_MOMENT.  Accepts W as the total weight, D1 as
+   the total deviation from the estimated mean, and D2, D3, and
+   D4 as the sum of the squares, cubes, and 4th powers,
+   respectively, of the deviation from the estimated mean. */
+static void
+calc_moments (enum moment max_moment,
+              double w, double d1, double d2, double d3, double d4,
+              double *variance, double *skewness, double *kurtosis) 
+{
+  assert (w > 0.);
+
+  if (max_moment >= MOMENT_VARIANCE && w > 1.) 
+    {
+      double s2;
+
+      /* From _Numerical Recipes in C_, 2nd ed., 0-521-43108-5,
+         section 14.1. */
+      s2 = (d2 - pow2 (d1) / w) / (w - 1.);
+      if (variance != NULL)
+        *variance = s2;
+
+      /* From _SPSS Statistical Algorithms, 2nd ed.,
+         0-918469-89-9, section "DESCRIPTIVES". */
+      if (fabs (*variance) >= 1e-20) 
+        {
+          if (max_moment >= MOMENT_SKEWNESS && skewness != NULL && w > 2.)
+            {
+              double s3 = s2 * sqrt (s2);
+              double g1 = (w * d3) / ((w - 1.0) * (w - 2.0) * s3);
+              if (finite (g1))
+                *skewness = g1; 
+            }
+          if (max_moment >= MOMENT_KURTOSIS && kurtosis != NULL && w > 3.)
+            {
+              double den = (w - 2.) * (w - 3.) * pow2 (s2);
+              double g2 = (w * (w + 1) * d4 / (w - 1.) / den
+                           - 3. * pow2 (d2) / den);
+              if (finite (g2))
+                *kurtosis = g2; 
+            }
+        } 
+    }
+}
+\f
+/* Two-pass moments. */
+
+/* A set of two-pass moments. */
+struct moments 
+  {
+    enum moment max_moment;     /* Highest-order moment we're computing. */
+    int pass;                   /* Current pass (1 or 2). */
+
+    /* Pass one. */
+    double w1;                  /* Total weight for pass 1, so far. */
+    double sum;                 /* Sum of values so far. */
+    double mean;                /* Mean = sum / w1. */
+
+    /* Pass two. */
+    double w2;                  /* Total weight for pass 2, so far. */
+    double d1;                  /* Sum of deviations from the mean. */
+    double d2;                  /* Sum of squared deviations from the mean. */
+    double d3;                  /* Sum of cubed deviations from the mean. */
+    double d4;                  /* Sum of (deviations from the mean)**4. */
+  };
+
+/* Initializes moments M for calculating moment MAX_MOMENT and
+   lower moments. */
+static void
+init_moments (struct moments *m, enum moment max_moment)
+{
+  assert (m != NULL);
+  assert (max_moment == MOMENT_MEAN || max_moment == MOMENT_VARIANCE
+          || max_moment == MOMENT_SKEWNESS || max_moment == MOMENT_KURTOSIS);
+  m->max_moment = max_moment;
+  moments_clear (m);
+}
+
+/* Clears out a set of moments so that it can be reused for a new
+   set of values.  The moments to be calculated are not changed. */
+void
+moments_clear (struct moments *m) 
+{
+  m->pass = 1;
+  m->w1 = m->w2 = 0.;
+  m->sum = 0.;
+}
+
+/* Creates and returns a data structure for calculating moment
+   MAX_MOMENT and lower moments on a data series.  The user
+   should call moments_pass_one() for each value in the series,
+   then call moments_pass_two() for the same set of values in the
+   same order, then call moments_calculate() to obtain the
+   moments.  The user may ask for the mean at any time during the
+   first pass (using moments_calculate()), but otherwise no
+   statistics may be requested until the end of the second pass.
+   Call moments_destroy() when the moments are no longer
+   needed. */
+struct moments *
+moments_create (enum moment max_moment)
+{
+  struct moments *m = xmalloc (sizeof *m);
+  init_moments (m, max_moment);
+  return m;
+}
+
+/* Adds VALUE with the given WEIGHT to the calculation of
+   moments for the first pass. */
+void
+moments_pass_one (struct moments *m, double value, double weight) 
+{
+  assert (m != NULL);
+  assert (m->pass == 1);
+
+  if (value != SYSMIS && weight > 0.) 
+    {
+      m->sum += value * weight;
+      m->w1 += weight;
+    }
+}
+
+/* Adds VALUE with the given WEIGHT to the calculation of
+   moments for the second pass. */
+void
+moments_pass_two (struct moments *m, double value, double weight) 
+{
+  double d, d_power;
+
+  assert (m != NULL);
+
+  if (m->pass == 1) 
+    {
+      m->pass = 2;
+      m->mean = m->w1 != 0. ? m->sum / m->w1 : 0.;
+      m->d1 = m->d2 = m->d3 = m->d4 = 0.;
+    }
+
+  if (value != SYSMIS && weight >= 0.) 
+    {
+      m->w2 += weight;
+
+      d = d_power = value - m->mean;
+      m->d1 += d_power * weight;
+
+      if (m->max_moment >= MOMENT_VARIANCE) 
+        {
+          d_power *= d;
+          m->d2 += d_power * weight;
+
+          if (m->max_moment >= MOMENT_SKEWNESS)
+            {
+              d_power *= d;
+              m->d3 += d_power * weight;
+
+              if (m->max_moment >= MOMENT_KURTOSIS)
+                {
+                  d_power *= d;
+                  m->d4 += d_power * weight;
+                }
+            }
+        }
+    }
+}
+
+/* Calculates moments based on the input data.  Stores the total
+   weight in *WEIGHT, the mean in *MEAN, the variance in
+   *VARIANCE, the skewness in *SKEWNESS, and the kurtosis in
+   *KURTOSIS.  Any of these result parameters may be null
+   pointers, in which case the values are not calculated.  If any
+   result cannot be calculated, either because they are undefined
+   based on the input data or because their moments are higher
+   than the maximum requested on moments_create(), then SYSMIS is
+   stored into that result. */
+void
+moments_calculate (const struct moments *m,
+                   double *weight,
+                   double *mean, double *variance,
+                   double *skewness, double *kurtosis) 
+{
+  assert (m != NULL);
+
+  if (mean != NULL)
+    *mean = SYSMIS;
+  if (variance != NULL)
+    *variance = SYSMIS;
+  if (skewness != NULL)
+    *skewness = SYSMIS;
+  if (kurtosis != NULL)
+    *kurtosis = SYSMIS;
+
+  if (weight != NULL)
+    *weight = m->w1;
+
+  /* How many passes so far? */
+  if (m->pass == 1) 
+    {
+      /* In the first pass we can only calculate the mean. */
+      if (mean != NULL && m->w1 > 0.)
+        *mean = m->sum / m->w1;
+    }
+  else 
+    {
+      /* After the second pass we can calculate any stat.  We
+         don't support "online" computation during the second
+         pass, so As a simple self-check, the total weight for
+         the passes must agree. */
+      assert (m->pass == 2);
+      assert (m->w1 == m->w2);
+
+      if (m->w2 > 0.) 
+        {
+          if (mean != NULL)
+            *mean = m->mean;
+          calc_moments (m->max_moment,
+                        m->w2, m->d1, m->d2, m->d3, m->d4,
+                        variance, skewness, kurtosis); 
+        }
+    }
+}
+
+/* Destroys a set of moments. */
+void
+moments_destroy (struct moments *m) 
+{
+  free (m);
+}
+
+/* Calculates the requested moments on the CNT values in ARRAY.
+   Each value is given a weight of 1.  The total weight is stored
+   into *WEIGHT (trivially) and the mean, variance, skewness, and
+   kurtosis are stored into *MEAN, *VARIANCE, *SKEWNESS, and
+   *KURTOSIS, respectively.  Any of the result pointers may be
+   null, in which case no value is stored. */
+void
+moments_of_doubles (const double *array, size_t cnt,
+                    double *weight,
+                    double *mean, double *variance,
+                    double *skewness, double *kurtosis) 
+{
+  enum moment max_moment;
+  struct moments m;
+  size_t idx;
+
+  if (kurtosis != NULL)
+    max_moment = MOMENT_KURTOSIS;
+  else if (skewness != NULL)
+    max_moment = MOMENT_SKEWNESS;
+  else if (variance != NULL)
+    max_moment = MOMENT_VARIANCE;
+  else
+    max_moment = MOMENT_MEAN;
+
+  init_moments (&m, max_moment);
+  for (idx = 0; idx < cnt; idx++)
+    moments_pass_one (&m, array[idx], 1.);
+  for (idx = 0; idx < cnt; idx++)
+    moments_pass_two (&m, array[idx], 1.);
+  moments_calculate (&m, weight, mean, variance, skewness, kurtosis);
+}
+
+/* Calculates the requested moments on the CNT numeric values in
+   ARRAY.  Each value is given a weight of 1.  The total weight
+   is stored into *WEIGHT (trivially) and the mean, variance,
+   skewness, and kurtosis are stored into *MEAN, *VARIANCE,
+   *SKEWNESS, and *KURTOSIS, respectively.  Any of the result
+   pointers may be null, in which case no value is stored. */
+void
+moments_of_values (const union value *array, size_t cnt,
+                   double *weight,
+                   double *mean, double *variance,
+                   double *skewness, double *kurtosis) 
+{
+  enum moment max_moment;
+  struct moments m;
+  size_t idx;
+
+  if (kurtosis != NULL)
+    max_moment = MOMENT_KURTOSIS;
+  else if (skewness != NULL)
+    max_moment = MOMENT_SKEWNESS;
+  else if (variance != NULL)
+    max_moment = MOMENT_VARIANCE;
+  else
+    max_moment = MOMENT_MEAN;
+
+  init_moments (&m, max_moment);
+  for (idx = 0; idx < cnt; idx++)
+    moments_pass_one (&m, array[idx].f, 1.);
+  for (idx = 0; idx < cnt; idx++)
+    moments_pass_two (&m, array[idx].f, 1.);
+  moments_calculate (&m, weight, mean, variance, skewness, kurtosis);
+}
+\f
+/* One-pass moments. */
+
+/* A set of one-pass moments. */
+struct moments1 
+  {
+    enum moment max_moment;     /* Highest-order moment we're computing. */
+    double w;                   /* Total weight so far. */
+    double d1;                  /* Sum of deviations from the mean. */
+    double d2;                  /* Sum of squared deviations from the mean. */
+    double d3;                  /* Sum of cubed deviations from the mean. */
+    double d4;                  /* Sum of (deviations from the mean)**4. */
+  };
+
+/* Initializes one-pass moments M for calculating moment
+   MAX_MOMENT and lower moments. */
+static void
+init_moments1 (struct moments1 *m, enum moment max_moment)
+{
+  assert (m != NULL);
+  assert (max_moment == MOMENT_MEAN || max_moment == MOMENT_VARIANCE
+          || max_moment == MOMENT_SKEWNESS || max_moment == MOMENT_KURTOSIS);
+  m->max_moment = max_moment;
+  moments1_clear (m);
+}
+
+/* Clears out a set of one-pass moments so that it can be reused
+   for a new set of values.  The moments to be calculated are not
+   changed. */
+void
+moments1_clear (struct moments1 *m) 
+{
+  m->w = 0.;
+  m->d1 = m->d2 = m->d3 = m->d4 = 0.;
+}
+
+/* Creates and returns a data structure for calculating moment
+   MAX_MOMENT and lower moments on a data series in a single
+   pass.  The user should call moments1_add() for each value in
+   the series.  The user may call moments1_calculate() to obtain
+   the current moments at any time.  Call moments1_destroy() when
+   the moments are no longer needed. 
+
+   One-pass moments should only be used when two passes over the
+   data are impractical. */
+struct moments1 *
+moments1_create (enum moment max_moment) 
+{
+  struct moments1 *m = xmalloc (sizeof *m);
+  init_moments1 (m, max_moment);
+  return m;
+}
+
+/* Adds VALUE with the given WEIGHT to the calculation of
+   one-pass moments. */
+void
+moments1_add (struct moments1 *m, double value, double weight) 
+{
+  assert (m != NULL);
+
+  if (value != SYSMIS && weight > 0.) 
+    {
+      double prev_w, v1;
+
+      prev_w = m->w;
+      m->w += weight;
+      v1 = (weight / m->w) * (value - m->d1);
+      m->d1 += v1;
+
+      if (m->max_moment >= MOMENT_VARIANCE) 
+        {
+          double v2 = v1 * v1;
+          double w_prev_w = m->w * prev_w;
+          double prev_m2 = m->d2;
+          
+          m->d2 += w_prev_w / weight * v2;
+          if (m->max_moment >= MOMENT_SKEWNESS) 
+            {
+              double w2 = weight * weight;
+              double v3 = v2 * v1;
+              double prev_m3 = m->d3;
+
+              m->d3 += (-3. * v1 * prev_m2
+                         + w_prev_w / w2 * (m->w - 2. * weight) * v3);
+              if (m->max_moment >= MOMENT_KURTOSIS) 
+                {
+                  double w3 = w2 * weight;
+                  double v4 = v2 * v2;
+
+                  m->d4 += (-4. * v1 * prev_m3
+                             + 6. * v2 * prev_m2
+                             + ((pow2 (m->w) - 3. * weight * prev_w)
+                                * v4 * w_prev_w / w3));
+                }
+            }
+        }
+    }
+}
+
+/* Calculates one-pass moments based on the input data.  Stores
+   the total weight in *WEIGHT, the mean in *MEAN, the variance
+   in *VARIANCE, the skewness in *SKEWNESS, and the kurtosis in
+   *KURTOSIS.  Any of these result parameters may be null
+   pointers, in which case the values are not calculated.  If any
+   result cannot be calculated, either because they are undefined
+   based on the input data or because their moments are higher
+   than the maximum requested on moments_create(), then SYSMIS is
+   stored into that result. */
+void
+moments1_calculate (const struct moments1 *m,
+                    double *weight,
+                    double *mean, double *variance,
+                    double *skewness, double *kurtosis) 
+{
+  assert (m != NULL);
+
+  if (mean != NULL)
+    *mean = SYSMIS;
+  if (variance != NULL)
+    *variance = SYSMIS;
+  if (skewness != NULL)
+    *skewness = SYSMIS;
+  if (kurtosis != NULL)
+    *kurtosis = SYSMIS;
+
+  if (weight != NULL)
+    *weight = m->w;
+
+  if (m->w > 0.) 
+    {
+      if (mean != NULL)
+        *mean = m->d1;
+
+      calc_moments (m->max_moment,
+                    m->w, 0., m->d2, m->d3, m->d4,
+                    variance, skewness, kurtosis);
+    }
+}
+
+/* Destroy one-pass moments M. */
+void
+moments1_destroy (struct moments1 *m) 
+{
+  free (m);
+}
+\f
+/* Returns the standard error of the skewness for the given total
+   weight W.
+
+   From _SPSS Statistical Algorithms, 2nd ed., 0-918469-89-9,
+   section "DESCRIPTIVES". */
+double
+calc_seskew (double W)
+{
+  return sqrt ((6. * W * (W - 1.)) / ((W - 2.) * (W + 1.) * (W + 3.)));
+}
+
+/* Returns the standard error of the kurtosis for the given total
+   weight W.
+
+   From _SPSS Statistical Algorithms, 2nd ed., 0-918469-89-9,
+   section "DESCRIPTIVES", except that the sqrt symbol is omitted
+   there. */
+double
+calc_sekurt (double W)
+{
+  return sqrt ((4. * (pow2 (W) - 1.) * pow2 (calc_seskew (W)))
+               / ((W - 3.) * (W + 5.)));
+}
diff --git a/src/math/moments.h b/src/math/moments.h
new file mode 100644 (file)
index 0000000..d872280
--- /dev/null
@@ -0,0 +1,75 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef HEADER_MOMENTS
+#define HEADER_MOMENTS
+
+#include <stddef.h>
+#include "value.h"
+
+/* Moments of the mean.
+   Higher-order moments have higher values. */
+enum moment 
+  {
+    MOMENT_NONE,                /* No moments. */
+    MOMENT_MEAN,                /* First-order moment. */
+    MOMENT_VARIANCE,            /* Second-order moment. */
+    MOMENT_SKEWNESS,            /* Third-order moment. */
+    MOMENT_KURTOSIS             /* Fourth-order moment. */
+  };
+
+struct moments;
+
+/* Two-pass moments. */
+struct moments *moments_create (enum moment max_moment);
+void moments_clear (struct moments *);
+void moments_pass_one (struct moments *, double value, double weight);
+void moments_pass_two (struct moments *, double value, double weight);
+void moments_calculate (const struct moments *,
+                        double *weight,
+                        double *mean, double *variance,
+                        double *skewness, double *kurtosis);
+void moments_destroy (struct moments *);
+
+/* Convenience functions for two-pass moments. */
+void moments_of_doubles (const double *array, size_t cnt,
+                         double *weight,
+                         double *mean, double *variance,
+                         double *skewness, double *kurtosis);
+void moments_of_values (const union value *array, size_t cnt,
+                        double *weight,
+                        double *mean, double *variance,
+                        double *skewness, double *kurtosis);
+
+/* One-pass moments.  Use only if two passes are impractical. */
+struct moments1 *moments1_create (enum moment max_moment);
+void moments1_clear (struct moments1 *);
+void moments1_add (struct moments1 *, double value, double weight);
+void moments1_calculate (const struct moments1 *,
+                         double *weight,
+                         double *mean, double *variance,
+                         double *skewness, double *kurtosis);
+void moments1_destroy (struct moments1 *);
+
+/* Standard errors. */
+double calc_semean (double stddev, double weight);
+double calc_seskew (double weight);
+double calc_sekurt (double weight);
+
+#endif /* moments.h */
diff --git a/src/math/percentiles.c b/src/math/percentiles.c
new file mode 100644 (file)
index 0000000..4d7b148
--- /dev/null
@@ -0,0 +1,428 @@
+/* PSPP - A program for statistical analysis . -*-c-*-
+
+Copyright (C) 2004 Free Software Foundation, Inc.
+Author: John Darrington 2004
+
+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., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA. */
+
+#include <config.h>
+#include "factor-stats.h"
+#include "percentiles.h"
+#include "misc.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+#include <assert.h>
+
+
+struct ptile_params
+{
+  double g1, g1_star;
+  double g2, g2_star;
+  int k1, k2;
+};
+
+
+const char *ptile_alg_desc[] = {
+  "",
+  N_("HAverage"),
+  N_("Weighted Average"),
+  N_("Rounded"),
+  N_("Empirical"),
+  N_("Empirical with averaging")
+};
+
+
+
+
+/* Individual Percentile algorithms */
+
+/* Closest observation to tc1 */
+double ptile_round(const struct weighted_value **wv, 
+                  const struct ptile_params *par);
+
+
+/* Weighted average at y_tc2 */
+double ptile_haverage(const struct weighted_value **wv, 
+                  const struct ptile_params *par);
+
+
+/* Weighted average at y_tc1 */
+double ptile_waverage(const struct weighted_value **wv, 
+                  const struct ptile_params *par);
+
+
+/* Empirical distribution function */
+double ptile_empirical(const struct weighted_value **wv, 
+                  const struct ptile_params *par);
+
+
+/* Empirical distribution function with averaging*/
+double ptile_aempirical(const struct weighted_value **wv, 
+                  const struct ptile_params *par);
+
+
+
+
+/* Closest observation to tc1 */
+double
+ptile_round(const struct weighted_value **wv, 
+           const struct ptile_params *par)
+{
+  double x;
+  double a=0;
+
+  if ( par->k1 >= 0 ) 
+    a = wv[par->k1]->v.f;
+
+  if ( wv[par->k1 + 1]->w >= 1 )
+    {
+      if ( par->g1_star < 0.5 ) 
+       x = a;
+      else
+       x = wv[par->k1 + 1]->v.f;
+    }
+  else
+    {
+      if ( par->g1 < 0.5 ) 
+       x = a;
+      else
+       x = wv[par->k1 + 1]->v.f;
+
+    }
+
+  return x;
+}
+
+/* Weighted average at y_tc2 */
+double
+ptile_haverage(const struct weighted_value **wv, 
+              const struct ptile_params *par)
+{
+
+  double a=0;
+
+  if ( par->g2_star >= 1.0 ) 
+      return wv[par->k2 + 1]->v.f ;
+
+  /* Special case  for k2 + 1 >= n_data 
+     (actually it's not a special case, but just avoids indexing errors )
+   */
+  if ( par->g2_star == 0 ) 
+    {
+      assert(par->g2 == 0 );
+      return wv[par->k2]->v.f;
+    }
+
+  /* Ditto for k2 < 0 */
+  if ( par->k2 >= 0 ) 
+    {
+      a = wv[par->k2]->v.f;
+    }
+
+  if ( wv[par->k2 + 1]->w >= 1.0 ) 
+    return ( (1 - par->g2_star) *  a   + 
+            par->g2_star * wv[par->k2 + 1]->v.f);
+  else
+    return ( (1 - par->g2) * a + 
+            par->g2 * wv[par->k2 + 1]->v.f);
+
+}
+
+
+
+/* Weighted average at y_tc1 */
+double 
+ptile_waverage(const struct weighted_value **wv, 
+              const struct ptile_params *par)
+{
+  double a=0;
+
+  if ( par->g1_star >= 1.0 ) 
+      return wv[par->k1 + 1]->v.f ;
+
+  if ( par->k1 >= 0 ) 
+    {
+      a = wv[par->k1]->v.f;
+    }
+
+  if ( wv[par->k1 + 1]->w >= 1.0 ) 
+    return ( (1 - par->g1_star) * a + 
+            par->g1_star * wv[par->k1 + 1]->v.f);
+  else
+    return ( (1 - par->g1) * a + 
+            par->g1 * wv[par->k1 + 1]->v.f);
+}
+
+
+/* Empirical distribution function */
+double 
+ptile_empirical(const struct weighted_value **wv, 
+              const struct ptile_params *par)
+{
+  if ( par->g1_star > 0 ) 
+    return wv[par->k1 + 1]->v.f;
+  else
+    return wv[par->k1]->v.f;
+}
+
+
+
+/* Empirical distribution function with averageing */
+double 
+ptile_aempirical(const struct weighted_value **wv, 
+              const struct ptile_params *par)
+{
+  if ( par->g1_star > 0 ) 
+    return wv[par->k1 + 1]->v.f;
+  else
+    return (wv[par->k1]->v.f + wv[par->k1 + 1]->v.f ) / 2.0 ;
+}
+
+
+
+/* Compute the percentile p */
+double ptile(double p, 
+            const struct weighted_value **wv,
+            int n_data,
+            double w,
+            enum pc_alg algorithm);
+
+
+
+double 
+ptile(double p, 
+      const struct weighted_value **wv,
+      int n_data,
+      double w,
+      enum pc_alg algorithm)
+{
+  int i;
+  double tc1, tc2;
+  double result;
+
+  struct ptile_params pp;
+
+  assert( p <= 1.0);
+
+  tc1 = w * p ;
+  tc2 = (w + 1) * p ;
+
+  pp.k1 = -1;
+  pp.k2 = -1;
+
+  for ( i = 0 ; i < n_data ; ++i ) 
+    {
+      if ( wv[i]->cc <= tc1 ) 
+       pp.k1 = i;
+
+      if ( wv[i]->cc <= tc2 ) 
+       pp.k2 = i;
+      
+    }
+
+
+  if ( pp.k1 >= 0 ) 
+    {
+      pp.g1 = ( tc1 - wv[pp.k1]->cc ) / wv[pp.k1 + 1]->w;
+      pp.g1_star = tc1 -  wv[pp.k1]->cc ; 
+    }
+  else
+    {
+      pp.g1 = tc1 / wv[pp.k1 + 1]->w;
+      pp.g1_star = tc1 ;
+    }
+
+
+  if ( pp.k2  + 1 >= n_data ) 
+    {
+      pp.g2 = 0 ;
+      pp.g2_star = 0;
+    }
+  else 
+    {
+      if ( pp.k2 >= 0 ) 
+       {
+         pp.g2 = ( tc2 - wv[pp.k2]->cc ) / wv[pp.k2 + 1]->w;
+         pp.g2_star = tc2 -  wv[pp.k2]->cc ; 
+       }
+      else
+       {
+         pp.g2 = tc2 / wv[pp.k2 + 1]->w;
+         pp.g2_star = tc2 ;
+       }
+    }
+
+  switch ( algorithm ) 
+    {
+    case PC_HAVERAGE:
+      result = ptile_haverage(wv, &pp);
+      break;
+    case PC_WAVERAGE:
+      result = ptile_waverage(wv, &pp);
+      break;
+    case PC_ROUND:
+      result = ptile_round(wv, &pp);
+      break;
+    case PC_EMPIRICAL:
+      result = ptile_empirical(wv, &pp);
+      break;
+    case PC_AEMPIRICAL:
+      result = ptile_aempirical(wv, &pp);
+      break;
+    default:
+      result = SYSMIS;
+    }
+
+  return result;
+}
+
+
+/* 
+   Calculate the values of the percentiles in pc_hash.
+   wv is  a sorted array of weighted values of the data set.
+*/
+void 
+ptiles(struct hsh_table *pc_hash,
+       const struct weighted_value **wv,
+       int n_data,
+       double w,
+       enum pc_alg algorithm)
+{
+  struct hsh_iterator hi;
+  struct percentile *p;
+
+  if ( !pc_hash ) 
+    return ;
+  for ( p = hsh_first(pc_hash, &hi);
+       p != 0 ;
+       p = hsh_next(pc_hash, &hi))
+    {
+      p->v = ptile(p->p/100.0 , wv, n_data, w, algorithm);
+    }
+  
+}
+
+
+/* Calculate Tukey's Hinges */
+void
+tukey_hinges(const struct weighted_value **wv,
+            int n_data, 
+            double w,
+            double hinge[3]
+            )
+{
+  int i;
+  double c_star = DBL_MAX;
+  double d;
+  double l[3];
+  int h[3];
+  double a, a_star;
+  
+  for ( i = 0 ; i < n_data ; ++i ) 
+    {
+      c_star = min(c_star, wv[i]->w);
+    }
+
+  if ( c_star > 1 ) c_star = 1;
+
+  d = floor((w/c_star + 3 ) / 2.0)/ 2.0;
+
+  l[0] = d*c_star;
+  l[1] = w/2.0 + c_star/2.0;
+  l[2] = w + c_star - d*c_star;
+
+  h[0]=-1;
+  h[1]=-1;
+  h[2]=-1;
+
+  for ( i = 0 ; i < n_data ; ++i ) 
+    {
+      if ( l[0] >= wv[i]->cc ) h[0] = i ;
+      if ( l[1] >= wv[i]->cc ) h[1] = i ;
+      if ( l[2] >= wv[i]->cc ) h[2] = i ;
+    }
+
+  for ( i = 0 ; i < 3 ; i++ )
+    {
+
+      if ( h[i] >= 0 ) 
+       a_star = l[i] - wv[h[i]]->cc ;
+      else
+       a_star = l[i];
+
+      if ( h[i] + 1 >= n_data )
+      {
+             assert( a_star < 1 ) ;
+             hinge[i] = (1 - a_star) * wv[h[i]]->v.f;
+             continue;
+      }
+      else 
+      {
+             a = a_star / ( wv[h[i] + 1]->cc ) ; 
+      }
+
+      if ( a_star >= 1.0 ) 
+       {
+         hinge[i] = wv[h[i] + 1]->v.f ;
+         continue;
+       }
+
+      if ( wv[h[i] + 1]->w >= 1)
+       {
+         hinge[i] = ( 1 - a_star) * wv[h[i]]->v.f
+           + a_star * wv[h[i] + 1]->v.f;
+
+         continue;
+       }
+
+      hinge[i] = (1 - a) * wv[h[i]]->v.f + a * wv[h[i] + 1]->v.f;
+      
+    }
+
+  assert(hinge[0] <= hinge[1]);
+  assert(hinge[1] <= hinge[2]);
+
+}
+
+
+int
+ptile_compare(const struct percentile *p1, 
+                  const struct percentile *p2, 
+                  void *aux UNUSED)
+{
+
+  int cmp;
+  
+  if ( p1->p == p2->p) 
+    cmp = 0 ;
+  else if (p1->p < p2->p)
+    cmp = -1 ; 
+  else 
+    cmp = +1;
+
+  return cmp;
+}
+
+unsigned
+ptile_hash(const struct percentile *p, void *aux UNUSED)
+{
+  return hsh_hash_double(p->p);
+}
+
+
diff --git a/src/math/percentiles.h b/src/math/percentiles.h
new file mode 100644 (file)
index 0000000..8f4271f
--- /dev/null
@@ -0,0 +1,83 @@
+/* PSPP - A program for statistical analysis . -*-c-*-
+
+Copyright (C) 2004 Free Software Foundation, Inc.
+Author: John Darrington 2004
+
+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., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA. */
+
+#ifndef PERCENTILES_H
+#define PERCENTILES_H
+
+
+#include "hash.h"
+
+struct weighted_value ;
+
+/* The algorithm used to calculate percentiles */
+enum pc_alg {
+  PC_NONE=0, 
+  PC_HAVERAGE, 
+  PC_WAVERAGE, 
+  PC_ROUND, 
+  PC_EMPIRICAL, 
+  PC_AEMPIRICAL
+} ;
+
+
+
+extern  const char *ptile_alg_desc[];
+
+
+
+
+struct percentile {
+
+  /* The break point of the percentile */
+  double p;
+
+  /* The value of the percentile */
+  double v;
+};
+
+
+/* Calculate the percentiles of the break points in pc_bp,
+   placing the values in pc_val.
+   wv is  a sorted array of weighted values of the data set.
+*/
+void ptiles(struct hsh_table *pc_hash,
+           const struct weighted_value **wv,
+           int n_data,
+           double w,
+           enum pc_alg algorithm);
+
+
+/* Calculate Tukey's Hinges and the Whiskers for the box plot*/
+void tukey_hinges(const struct weighted_value **wv,
+                 int n_data, 
+                 double w,
+                 double hinges[3]);
+
+
+
+/* Hash utility functions */
+int ptile_compare(const struct percentile *p1, 
+                  const struct percentile *p2, 
+                  void *aux);
+
+unsigned ptile_hash(const struct percentile *p, void *aux);
+
+
+#endif
diff --git a/src/math/random.c b/src/math/random.c
new file mode 100644 (file)
index 0000000..7420a82
--- /dev/null
@@ -0,0 +1,57 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "random.h"
+#include <time.h>
+#include "xalloc.h"
+
+static gsl_rng *rng;
+
+void
+random_init (void) 
+{
+}
+
+void
+random_done (void) 
+{
+  if (rng != NULL) 
+    gsl_rng_free (rng);
+}
+
+/* Returns the current random number generator. */
+gsl_rng *
+get_rng (void)
+{
+  if (rng == NULL)
+    set_rng (time (0));
+  return rng;
+}
+
+/* Initializes or reinitializes the random number generator with
+   the given SEED. */
+void
+set_rng (unsigned long seed) 
+{
+  rng = gsl_rng_alloc (gsl_rng_mt19937);
+  if (rng == NULL)
+    xalloc_die ();
+  gsl_rng_set (rng, seed);
+}
diff --git a/src/math/random.h b/src/math/random.h
new file mode 100644 (file)
index 0000000..8595967
--- /dev/null
@@ -0,0 +1,31 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef RANDOM_H
+#define RANDOM_H 1
+
+#include <gsl/gsl_rng.h>
+
+void random_init (void);
+void random_done (void);
+
+gsl_rng *get_rng (void);
+void set_rng (unsigned long seed);
+
+#endif /* random.h */
diff --git a/src/math/sort.c b/src/math/sort.c
new file mode 100644 (file)
index 0000000..46fcb60
--- /dev/null
@@ -0,0 +1,710 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "sort.h"
+#include "message.h"
+#include "alloc.h"
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include "array.h"
+#include <stdbool.h>
+#include "case.h"
+#include "casefile.h"
+#include "message.h"
+
+#include "misc.h"
+#include "settings.h"
+#include "str.h"
+#include "variable.h"
+#include "procedure.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+
+/* These should only be changed for testing purposes. */
+int min_buffers = 64;
+int max_buffers = INT_MAX;
+bool allow_internal_sort = true;
+
+static int compare_record (const struct ccase *, const struct ccase *,
+                           const struct sort_criteria *);
+static struct casefile *do_internal_sort (struct casereader *,
+                                          const struct sort_criteria *);
+static struct casefile *do_external_sort (struct casereader *,
+                                          const struct sort_criteria *);
+
+/* Gets ready to sort the active file, either in-place or to a
+   separate casefile. */
+static bool
+prepare_to_sort_active_file (void) 
+{
+  bool ok;
+  
+  /* Cancel temporary transformations and PROCESS IF. */
+  if (temporary != 0)
+    cancel_temporary (); 
+  expr_free (process_if_expr);
+  process_if_expr = NULL;
+
+  /* Make sure source cases are in a storage source. */
+  ok = procedure (NULL, NULL);
+  assert (case_source_is_class (vfm_source, &storage_source_class));
+
+  return ok;
+}
+
+/* Sorts the active file in-place according to CRITERIA.
+   Returns nonzero if successful. */
+int
+sort_active_file_in_place (const struct sort_criteria *criteria) 
+{
+  struct casefile *src, *dst;
+  
+  if (!prepare_to_sort_active_file ())
+    return 0;
+
+  src = storage_source_get_casefile (vfm_source);
+  dst = sort_execute (casefile_get_destructive_reader (src), criteria);
+  free_case_source (vfm_source);
+  vfm_source = NULL;
+
+  if (dst == NULL) 
+    return 0;
+
+  vfm_source = storage_source_create (dst);
+  return 1;
+}
+
+/* Sorts the active file to a separate casefile.  If successful,
+   returns the sorted casefile.  Returns a null pointer on
+   failure. */
+struct casefile *
+sort_active_file_to_casefile (const struct sort_criteria *criteria) 
+{
+  struct casefile *src;
+  
+  if (!prepare_to_sort_active_file ())
+    return NULL;
+
+  src = storage_source_get_casefile (vfm_source);
+  return sort_execute (casefile_get_reader (src), criteria);
+}
+
+
+/* Reads all the cases from READER, which is destroyed.  Sorts
+   the cases according to CRITERIA.  Returns the sorted cases in
+   a newly created casefile. */
+struct casefile *
+sort_execute (struct casereader *reader, const struct sort_criteria *criteria)
+{
+  struct casefile *output = do_internal_sort (reader, criteria);
+  if (output == NULL)
+    output = do_external_sort (reader, criteria);
+  casereader_destroy (reader);
+  return output;
+}
+\f
+/* A case and its index. */
+struct indexed_case 
+  {
+    struct ccase c;     /* Case. */
+    unsigned long idx;  /* Index to allow for stable sorting. */
+  };
+
+static int compare_indexed_cases (const void *, const void *, void *);
+
+/* If the data is in memory, do an internal sort and return a new
+   casefile for the data.  Otherwise, return a null pointer. */
+static struct casefile *
+do_internal_sort (struct casereader *reader,
+                  const struct sort_criteria *criteria)
+{
+  const struct casefile *src;
+  struct casefile *dst;
+  unsigned long case_cnt;
+
+  if (!allow_internal_sort)
+    return NULL;
+
+  src = casereader_get_casefile (reader);
+  if (casefile_get_case_cnt (src) > 1 && !casefile_in_core (src))
+    return NULL;
+      
+  case_cnt = casefile_get_case_cnt (src);
+  dst = casefile_create (casefile_get_value_cnt (src));
+  if (case_cnt != 0) 
+    {
+      struct indexed_case *cases = nmalloc (sizeof *cases, case_cnt);
+      if (cases != NULL) 
+        {
+          unsigned long i;
+          
+          for (i = 0; i < case_cnt; i++)
+            {
+              bool ok = casereader_read_xfer (reader, &cases[i].c);
+              if (!ok)
+                abort ();
+              cases[i].idx = i;
+            }
+
+          sort (cases, case_cnt, sizeof *cases, compare_indexed_cases,
+                (void *) criteria);
+      
+          for (i = 0; i < case_cnt; i++)
+            casefile_append_xfer (dst, &cases[i].c);
+          if (casefile_error (dst))
+            abort ();
+
+          free (cases);
+        }
+      else 
+        {
+          /* Failure. */
+          casefile_destroy (dst);
+          dst = NULL;
+        }
+    }
+
+  return dst;
+}
+
+/* Compares the variables specified by CRITERIA between the cases
+   at A and B, with a "last resort" comparison for stability, and
+   returns a strcmp()-type result. */
+static int
+compare_indexed_cases (const void *a_, const void *b_, void *criteria_)
+{
+  struct sort_criteria *criteria = criteria_;
+  const struct indexed_case *a = a_;
+  const struct indexed_case *b = b_;
+  int result = compare_record (&a->c, &b->c, criteria);
+  if (result == 0)
+    result = a->idx < b->idx ? -1 : a->idx > b->idx;
+  return result;
+}
+\f
+/* External sort. */
+
+/* Maximum order of merge (external sort only).  The maximum
+   reasonable value is about 7.  Above that, it would be a good
+   idea to use a heap in merge_once() to select the minimum. */
+#define MAX_MERGE_ORDER 7
+
+/* Results of an external sort. */
+struct external_sort 
+  {
+    const struct sort_criteria *criteria; /* Sort criteria. */
+    size_t value_cnt;                 /* Size of data in `union value's. */
+    struct casefile **runs;           /* Array of initial runs. */
+    size_t run_cnt, run_cap;          /* Number of runs, allocated capacity. */
+  };
+
+/* Prototypes for helper functions. */
+static int write_runs (struct external_sort *, struct casereader *);
+static struct casefile *merge (struct external_sort *);
+static void destroy_external_sort (struct external_sort *);
+
+/* Performs a stable external sort of the active file according
+   to the specification in SCP.  Forms initial runs using a heap
+   as a reservoir.  Merges the initial runs according to a
+   pattern that assures stability. */
+static struct casefile *
+do_external_sort (struct casereader *reader,
+                  const struct sort_criteria *criteria)
+{
+  struct external_sort *xsrt;
+
+  if (!casefile_to_disk (casereader_get_casefile (reader)))
+    return NULL;
+
+  xsrt = xmalloc (sizeof *xsrt);
+  xsrt->criteria = criteria;
+  xsrt->value_cnt = casefile_get_value_cnt (casereader_get_casefile (reader));
+  xsrt->run_cap = 512;
+  xsrt->run_cnt = 0;
+  xsrt->runs = xnmalloc (xsrt->run_cap, sizeof *xsrt->runs);
+  if (write_runs (xsrt, reader))
+    {
+      struct casefile *output = merge (xsrt);
+      destroy_external_sort (xsrt);
+      return output;
+    }
+  else
+    {
+      destroy_external_sort (xsrt);
+      return NULL;
+    }
+}
+
+/* Destroys XSRT. */
+static void
+destroy_external_sort (struct external_sort *xsrt) 
+{
+  if (xsrt != NULL) 
+    {
+      int i;
+      
+      for (i = 0; i < xsrt->run_cnt; i++)
+        casefile_destroy (xsrt->runs[i]);
+      free (xsrt->runs);
+      free (xsrt);
+    }
+}
+\f
+/* Replacement selection. */
+
+/* Pairs a record with a run number. */
+struct record_run
+  {
+    int run;                    /* Run number of case. */
+    struct ccase record;        /* Case data. */
+    size_t idx;                 /* Case number (for stability). */
+  };
+
+/* Represents a set of initial runs during an external sort. */
+struct initial_run_state 
+  {
+    struct external_sort *xsrt;
+
+    /* Reservoir. */
+    struct record_run *records; /* Records arranged as a heap. */
+    size_t record_cnt;          /* Current number of records. */
+    size_t record_cap;          /* Capacity for records. */
+    
+    /* Run currently being output. */
+    int run;                    /* Run number. */
+    size_t case_cnt;            /* Number of cases so far. */
+    struct casefile *casefile;  /* Output file. */
+    struct ccase last_output;   /* Record last output. */
+
+    int okay;                   /* Zero if an error has been encountered. */
+  };
+
+static const struct case_sink_class sort_sink_class;
+
+static bool destroy_initial_run_state (struct initial_run_state *);
+static void process_case (struct initial_run_state *, const struct ccase *,
+                          size_t);
+static int allocate_cases (struct initial_run_state *);
+static void output_record (struct initial_run_state *);
+static void start_run (struct initial_run_state *);
+static void end_run (struct initial_run_state *);
+static int compare_record_run (const struct record_run *,
+                               const struct record_run *,
+                               struct initial_run_state *);
+static int compare_record_run_minheap (const void *, const void *, void *);
+
+/* Reads cases from READER and composes initial runs in XSRT. */
+static int
+write_runs (struct external_sort *xsrt, struct casereader *reader)
+{
+  struct initial_run_state *irs;
+  struct ccase c;
+  size_t idx = 0;
+  int success = 0;
+
+  /* Allocate memory for cases. */
+  irs = xmalloc (sizeof *irs);
+  irs->xsrt = xsrt;
+  irs->records = NULL;
+  irs->record_cnt = irs->record_cap = 0;
+  irs->run = 0;
+  irs->case_cnt = 0;
+  irs->casefile = NULL;
+  case_nullify (&irs->last_output);
+  irs->okay = 1;
+  if (!allocate_cases (irs)) 
+    goto done;
+
+  /* Create initial runs. */
+  start_run (irs);
+  for (; irs->okay && casereader_read (reader, &c); case_destroy (&c))
+    process_case (irs, &c, idx++);
+  while (irs->okay && irs->record_cnt > 0)
+    output_record (irs);
+  end_run (irs);
+
+  success = irs->okay;
+
+ done:
+  if (!destroy_initial_run_state (irs))
+    success = false;
+
+  return success;
+}
+
+/* Add a single case to an initial run. */
+static void
+process_case (struct initial_run_state *irs, const struct ccase *c, size_t idx)
+{
+  struct record_run *rr;
+
+  /* Compose record_run for this run and add to heap. */
+  assert (irs->record_cnt < irs->record_cap - 1);
+  rr = irs->records + irs->record_cnt++;
+  case_copy (&rr->record, 0, c, 0, irs->xsrt->value_cnt);
+  rr->run = irs->run;
+  rr->idx = idx;
+  if (!case_is_null (&irs->last_output)
+      && compare_record (c, &irs->last_output, irs->xsrt->criteria) < 0)
+    rr->run = irs->run + 1;
+  push_heap (irs->records, irs->record_cnt, sizeof *irs->records,
+             compare_record_run_minheap, irs);
+
+  /* Output a record if the reservoir is full. */
+  if (irs->record_cnt == irs->record_cap - 1 && irs->okay)
+    output_record (irs);
+}
+
+/* Destroys the initial run state represented by IRS.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+destroy_initial_run_state (struct initial_run_state *irs) 
+{
+  int i;
+  bool ok = true;
+
+  if (irs == NULL)
+    return true;
+
+  for (i = 0; i < irs->record_cap; i++)
+    case_destroy (&irs->records[i].record);
+  free (irs->records);
+
+  if (irs->casefile != NULL)
+    ok = casefile_sleep (irs->casefile);
+
+  free (irs);
+  return ok;
+}
+
+/* Allocates room for lots of cases as a buffer. */
+static int
+allocate_cases (struct initial_run_state *irs)
+{
+  int approx_case_cost; /* Approximate memory cost of one case in bytes. */
+  int max_cases;        /* Maximum number of cases to allocate. */
+  int i;
+
+  /* Allocate as many cases as we can within the workspace
+     limit. */
+  approx_case_cost = (sizeof *irs->records
+                      + irs->xsrt->value_cnt * sizeof (union value)
+                      + 4 * sizeof (void *));
+  max_cases = get_workspace() / approx_case_cost;
+  if (max_cases > max_buffers)
+    max_cases = max_buffers;
+  irs->records = nmalloc (sizeof *irs->records, max_cases);
+  if (irs->records != NULL)
+    for (i = 0; i < max_cases; i++)
+      if (!case_try_create (&irs->records[i].record, irs->xsrt->value_cnt))
+        {
+          max_cases = i;
+          break;
+        }
+  irs->record_cap = max_cases;
+
+  /* Fail if we didn't allocate an acceptable number of cases. */
+  if (irs->records == NULL || max_cases < min_buffers)
+    {
+      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_buffers, approx_case_cost, get_workspace() / 1024);
+      return 0;
+    }
+  return 1;
+}
+
+/* Compares the VAR_CNT variables in VARS[] between the `value's at
+   A and B, and returns a strcmp()-type result. */
+static int
+compare_record (const struct ccase *a, const struct ccase *b,
+                const struct sort_criteria *criteria)
+{
+  int i;
+
+  assert (a != NULL);
+  assert (b != NULL);
+  
+  for (i = 0; i < criteria->crit_cnt; i++)
+    {
+      const struct sort_criterion *c = &criteria->crits[i];
+      int result;
+      
+      if (c->width == 0)
+        {
+          double af = case_num (a, c->fv);
+          double bf = case_num (b, c->fv);
+          
+          result = af < bf ? -1 : af > bf;
+        }
+      else
+        result = memcmp (case_str (a, c->fv), case_str (b, c->fv), c->width);
+
+      if (result != 0)
+        return c->dir == SRT_ASCEND ? result : -result;
+    }
+
+  return 0;
+}
+
+/* Compares record-run tuples A and B on run number first, then
+   on record, then on case index. */
+static int
+compare_record_run (const struct record_run *a,
+                    const struct record_run *b,
+                    struct initial_run_state *irs)
+{
+  int result = a->run < b->run ? -1 : a->run > b->run;
+  if (result == 0)
+    result = compare_record (&a->record, &b->record, irs->xsrt->criteria);
+  if (result == 0)
+    result = a->idx < b->idx ? -1 : a->idx > b->idx;
+  return result;
+}
+
+/* Compares record-run tuples A and B on run number first, then
+   on the current record according to SCP, but in descending
+   order. */
+static int
+compare_record_run_minheap (const void *a, const void *b, void *irs) 
+{
+  return -compare_record_run (a, b, irs);
+}
+
+/* Begins a new initial run, specifically its output file. */
+static void
+start_run (struct initial_run_state *irs)
+{
+  irs->run++;
+  irs->case_cnt = 0;
+  irs->casefile = casefile_create (irs->xsrt->value_cnt);
+  casefile_to_disk (irs->casefile);
+  case_nullify (&irs->last_output); 
+}
+
+/* Ends the current initial run.  */
+static void
+end_run (struct initial_run_state *irs)
+{
+  struct external_sort *xsrt = irs->xsrt;
+
+  /* Record initial run. */
+  if (irs->casefile != NULL) 
+    {
+      casefile_sleep (irs->casefile);
+      if (xsrt->run_cnt >= xsrt->run_cap) 
+        {
+          xsrt->run_cap *= 2;
+          xsrt->runs = xnrealloc (xsrt->runs,
+                                  xsrt->run_cap, sizeof *xsrt->runs);
+        }
+      xsrt->runs[xsrt->run_cnt++] = irs->casefile;
+      if (casefile_error (irs->casefile))
+        irs->okay = false;
+      irs->casefile = NULL; 
+    }
+}
+
+/* Writes a record to the current initial run. */
+static void
+output_record (struct initial_run_state *irs)
+{
+  struct record_run *record_run;
+  struct ccase case_tmp;
+  
+  /* Extract minimum case from heap. */
+  assert (irs->record_cnt > 0);
+  pop_heap (irs->records, irs->record_cnt--, sizeof *irs->records,
+            compare_record_run_minheap, irs);
+  record_run = irs->records + irs->record_cnt;
+
+  /* Bail if an error has occurred. */
+  if (!irs->okay)
+    return;
+
+  /* Start new run if necessary. */
+  assert (record_run->run == irs->run
+          || record_run->run == irs->run + 1);
+  if (record_run->run != irs->run)
+    {
+      end_run (irs);
+      start_run (irs);
+    }
+  assert (record_run->run == irs->run);
+  irs->case_cnt++;
+
+  /* Write to disk. */
+  if (irs->casefile != NULL)
+    casefile_append (irs->casefile, &record_run->record);
+
+  /* This record becomes last_output. */
+  irs->last_output = case_tmp = record_run->record;
+  record_run->record = irs->records[irs->record_cap - 1].record;
+  irs->records[irs->record_cap - 1].record = case_tmp;
+}
+\f
+/* Merging. */
+
+static int choose_merge (struct casefile *runs[], int run_cnt, int order);
+static struct casefile *merge_once (struct external_sort *,
+                                    struct casefile *[], size_t);
+
+/* Repeatedly merges run until only one is left,
+   and returns the final casefile.
+   Returns a null pointer if an I/O error occurs. */
+static struct casefile *
+merge (struct external_sort *xsrt)
+{
+  while (xsrt->run_cnt > 1)
+    {
+      int order = min (MAX_MERGE_ORDER, xsrt->run_cnt);
+      int idx = choose_merge (xsrt->runs, xsrt->run_cnt, order);
+      xsrt->runs[idx] = merge_once (xsrt, xsrt->runs + idx, order);
+      remove_range (xsrt->runs, xsrt->run_cnt, sizeof *xsrt->runs,
+                    idx + 1, order - 1);
+      xsrt->run_cnt -= order - 1;
+
+      if (xsrt->runs[idx] == NULL)
+        return NULL;
+    }
+  assert (xsrt->run_cnt == 1);
+  xsrt->run_cnt = 0;
+  return xsrt->runs[0];
+}
+
+/* Chooses ORDER runs out of the RUN_CNT runs in RUNS to merge,
+   and returns the index of the first one.
+
+   For stability, we must merge only consecutive runs.  For
+   efficiency, we choose the shortest consecutive sequence of
+   runs. */
+static int
+choose_merge (struct casefile *runs[], int run_cnt, int order) 
+{
+  int min_idx, min_sum;
+  int cur_idx, cur_sum;
+  int i;
+
+  /* Sum up the length of the first ORDER runs. */
+  cur_sum = 0;
+  for (i = 0; i < order; i++)
+    cur_sum += casefile_get_case_cnt (runs[i]);
+
+  /* Find the shortest group of ORDER runs,
+     using a running total for efficiency. */
+  min_idx = 0;
+  min_sum = cur_sum;
+  for (cur_idx = 1; cur_idx + order <= run_cnt; cur_idx++)
+    {
+      cur_sum -= casefile_get_case_cnt (runs[cur_idx - 1]);
+      cur_sum += casefile_get_case_cnt (runs[cur_idx + order - 1]);
+      if (cur_sum < min_sum)
+        {
+          min_sum = cur_sum;
+          min_idx = cur_idx;
+        }
+    }
+
+  return min_idx;
+}
+
+/* Merges the RUN_CNT initial runs specified in INPUT_FILES into a
+   new run, and returns the new run.
+   Returns a null pointer if an I/O error occurs. */
+static struct casefile *
+merge_once (struct external_sort *xsrt,
+            struct casefile **const input_files,
+            size_t run_cnt)
+{
+  struct run
+    {
+      struct casefile *file;
+      struct casereader *reader;
+      struct ccase ccase;
+    }
+  *runs;
+
+  struct casefile *output = NULL;
+  int i;
+
+  /* Open input files. */
+  runs = xnmalloc (run_cnt, sizeof *runs);
+  for (i = 0; i < run_cnt; i++) 
+    {
+      struct run *r = &runs[i];
+      r->file = input_files[i];
+      r->reader = casefile_get_destructive_reader (r->file);
+      if (!casereader_read_xfer (r->reader, &r->ccase))
+        {
+          run_cnt--;
+          i--;
+        }
+    }
+
+  /* Create output file. */
+  output = casefile_create (xsrt->value_cnt);
+  casefile_to_disk (output);
+
+  /* Merge. */
+  while (run_cnt > 0) 
+    {
+      struct run *min_run, *run;
+      
+      /* Find minimum. */
+      min_run = runs;
+      for (run = runs + 1; run < runs + run_cnt; run++)
+       if (compare_record (&run->ccase, &min_run->ccase, xsrt->criteria) < 0)
+          min_run = run;
+
+      /* Write minimum to output file. */
+      casefile_append_xfer (output, &min_run->ccase);
+
+      /* Read another case from minimum run. */
+      if (!casereader_read_xfer (min_run->reader, &min_run->ccase))
+        {
+          if (casefile_error (min_run->file) || casefile_error (output))
+            goto error;
+          casereader_destroy (min_run->reader);
+          casefile_destroy (min_run->file);
+
+          remove_element (runs, run_cnt, sizeof *runs, min_run - runs);
+          run_cnt--;
+        } 
+    }
+
+  if (!casefile_sleep (output))
+    goto error;
+  free (runs);
+
+  return output;
+
+ error:
+  for (i = 0; i < run_cnt; i++) 
+    casefile_destroy (runs[i].file);
+  casefile_destroy (output);
+  free (runs);
+  return NULL;
+}
diff --git a/src/math/sort.h b/src/math/sort.h
new file mode 100644 (file)
index 0000000..267384b
--- /dev/null
@@ -0,0 +1,67 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !sort_h
+#define sort_h 1
+
+#include <stddef.h>
+#include <stdbool.h>
+
+struct casereader;
+struct dictionary;
+struct variable;
+
+extern int min_buffers ;
+extern int max_buffers ;
+extern bool allow_internal_sort ;
+
+
+/* Sort direction. */
+enum sort_direction
+  {
+    SRT_ASCEND,                        /* A, B, C, ..., X, Y, Z. */
+    SRT_DESCEND                        /* Z, Y, X, ..., C, B, A. */
+  };
+
+/* A sort criterion. */
+struct sort_criterion
+  {
+    int fv;                     /* Variable data index. */
+    int width;                  /* 0=numeric, otherwise string width. */
+    enum sort_direction dir;    /* Sort direction. */
+  };
+
+/* A set of sort criteria. */
+struct sort_criteria 
+  {
+    struct sort_criterion *crits;
+    size_t crit_cnt;
+  };
+
+
+void sort_destroy_criteria (struct sort_criteria *);
+
+struct casefile *sort_execute (struct casereader *,
+                               const struct sort_criteria *);
+
+int sort_active_file_in_place (const struct sort_criteria *);
+
+struct casefile *sort_active_file_to_casefile (const struct sort_criteria *);
+
+#endif /* !sort_h */
diff --git a/src/message.c b/src/message.c
new file mode 100644 (file)
index 0000000..b77e7b6
--- /dev/null
@@ -0,0 +1,458 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "filename.h"
+#include "line-buffer.h"
+#include "lexer.h"
+#include "settings.h"
+#include "read-line.h"
+#include "version.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+int err_error_count;
+int err_warning_count;
+
+int err_already_flagged;
+
+int err_verbosity;
+
+static char *command_name;
+\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, ...)
+{
+  struct error e;
+  va_list args;
+
+  e.class = class;
+  err_location (&e.where);
+  e.title = title;
+
+  va_start (args, format);
+  err_vmsg (&e, format, args);
+  va_end (args);
+}
+
+/* Writes error message in CLASS, with text FORMAT, formatted with
+   printf, to the standard places. */
+void
+msg (int class, const char *format, ...)
+{
+  struct error e;
+  va_list args;
+
+  e.class = class;
+  err_location (&e.where);
+  e.title = NULL;
+
+  va_start (args, format);
+  err_vmsg (&e, format, args);
+  va_end (args);
+}
+
+/* Checks whether we've had so many errors that it's time to quit
+   processing this syntax file. */
+void
+err_check_count (void)
+{
+  if (get_errorbreak() && err_error_count)
+    msg (MM, _("Terminating execution of syntax file due to error."));
+  else if (err_error_count > get_mxerrs() )
+    msg (MM, _("Errors (%d) exceeds limit (%d)."),
+        err_error_count, get_mxerrs());
+  else if (err_error_count + err_warning_count > get_mxwarns() )
+    msg (MM, _("Warnings (%d) exceed limit (%d)."),
+        err_error_count + err_warning_count, get_mxwarns() );
+  else
+    return;
+
+  getl_abort_noninteractive ();
+}
+
+/* Some machines are broken.  Compensate. */
+#ifndef EXIT_SUCCESS
+#define EXIT_SUCCESS 0
+#endif
+
+#ifndef EXIT_FAILURE
+#define EXIT_FAILURE 1
+#endif
+
+static void puts_stdout (const char *s);
+static void dump_message (char *errbuf, unsigned indent,
+                         void (*func) (const char *), unsigned width);
+
+void
+err_done (void) 
+{
+  lex_done();
+  getl_uninitialize ();
+  readln_uninitialize();
+}
+
+void
+err_vmsg (const struct error *e, const char *format, va_list args)
+{
+  /* 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 ERR_*. */
+      int *count;              /* Counting category. */
+      const char *banner;      /* Banner. */
+    };
+
+  static const struct error_class error_classes[ERR_CLASS_COUNT] =
+    {
+      {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, &err_error_count, N_("error")},      /* DE */
+      {2, &err_warning_count, N_("warning")},  /* DW */
+
+      {0, &err_error_count, N_("error")},      /* ME */
+      {0, &err_warning_count, 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 (format != NULL);
+  
+  ds_init (&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_putc (&msg, ' ');
+    }
+
+  ds_printf (&msg, "%s: ", gettext (error_classes[class].banner));
+  
+  {
+    int *count = error_classes[class].count;
+    if (count)
+      (*count)++;
+  }
+  
+  if (command_name != NULL && (error_classes[class].flags & ERR_IN_PROCEDURE))
+    ds_printf (&msg, "%s: ", command_name);
+
+  if (e->title)
+    ds_puts (&msg, e->title);
+
+  ds_vprintf (&msg, format, args);
+
+  /* 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_c_str (&msg), 8, puts_stdout, get_viewwidth());
+
+  ds_destroy (&msg);
+}
+\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 the line must be broken here */
+static int
+compulsory_break(int c)
+{
+  return ( c == '\n' );
+}
+
+/* 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 + 2);
+
+  /* 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 && 
+        ! compulsory_break(*cp); 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 (;;)
+    {
+      static int hard_break=0;
+
+      int idx=0;
+      char *cp2;
+
+      /* Advance past whitespace. */
+      if (! hard_break ) 
+       while ( isspace ((unsigned char) *cp) )
+         cp++;
+      else
+       cp++;
+
+      if (*cp == 0)
+         break; 
+
+
+      /* Advance WIDTH - INDENT characters. */
+      for (cp2 = cp; (unsigned) (cp2 - cp) < width - indent && 
+            *cp2 && !compulsory_break(*cp2);  cp2++)
+       if (*cp2 == '"')
+         quote ^= 1;
+      
+      if ( compulsory_break(*cp2) )
+       hard_break = 1;
+      else
+       hard_break = 0;
+
+
+      /* Back up if this isn't a breakpoint. */
+      {
+       unsigned w = cp2 - cp;
+       if (*cp2 && ! compulsory_break(*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 + idx + cp2 - cp] = '\0';
+      func (buf);
+      cp = cp2;
+    }
+
+  local_free (buf);
+}
+
+/* Sets COMMAND_NAME as the command name included in some kinds
+   of error messages. */
+void
+err_set_command_name (const char *command_name_) 
+{
+  free (command_name);
+  command_name = command_name_ ? xstrdup (command_name_) : NULL;
+}
+
+void 
+request_bug_report_and_abort(const char *msg )
+{
+  fprintf(stderr,
+         "******************************************************************\n"
+         "You have discovered a bug in PSPP.\n\n"
+         "  Please report this, by sending "
+         "an email to " PACKAGE_BUGREPORT ",\n"
+         "explaining what you were doing when this happened, and including\n"
+         "a sample of your input file which caused it.\n");
+
+  fprintf(stderr,
+         "Also, please copy the following lines into your bug report:\n\n"
+         "bare_version:        %s\n" 
+         "version:             %s\n"
+         "stat_version:        %s\n"
+         "host_system:         %s\n"
+         "build_system:        %s\n"
+         "default_config_path: %s\n"
+         "include_path:        %s\n"
+         "groff_font_path:     %s\n"
+         "locale_dir:          %s\n"
+         "compiler version:    %s\n"
+         ,
+
+         bare_version,         
+         version,
+         stat_version,
+         host_system,        
+         build_system,
+         default_config_path,
+         include_path, 
+         groff_font_path,
+         locale_dir,
+#ifdef __VERSION__
+         __VERSION__
+#else
+         "Unknown"
+#endif
+         );     
+
+  if ( msg )
+    fprintf(stderr,"Diagnosis: %s\n",msg);
+
+  fprintf(stderr,
+    "******************************************************************\n");
+
+  abort();
+}
+
+void 
+err_assert_fail(const char *expr, const char *file, int line)
+{
+  char msg[256];
+  snprintf(msg,256,"Assertion failed: %s:%d; (%s)",file,line,expr);
+  request_bug_report_and_abort( msg );
+}
+
diff --git a/src/output/ChangeLog b/src/output/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/output/Makefile b/src/output/Makefile
new file mode 100644 (file)
index 0000000..c1a052e
--- /dev/null
@@ -0,0 +1,2 @@
+all:
+       $(MAKE) -C /home/res/jmd/PSPP/pspp 
diff --git a/src/output/ascii.c b/src/output/ascii.c
new file mode 100644 (file)
index 0000000..ffe0c04
--- /dev/null
@@ -0,0 +1,1691 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "message.h"
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "message.h"
+#include "chart.h"
+#include "filename.h"
+#include "misc.h"
+#include "output.h"
+#include "pool.h"
+#include "start-date.h"
+#include "version.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* 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                       
+   squeeze=off|on               Squeeze multiple newlines into exactly one.
+
+   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. */
+  };
+
+/* A line of text. */
+struct line 
+  {
+    unsigned short *chars;      /* Characters and attributes. */
+    int char_cnt;               /* Length. */
+    int char_cap;               /* Allocated bytes. */
+  };
+
+/* 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 fixed_string ops[OPS_COUNT]; /* Basic output strings. */
+    struct fixed_string box[LNS_COUNT]; /* Line & box drawing characters. */
+    struct fixed_string fonts[FSTY_COUNT]; /* Font styles; NULL=overstrike. */
+    int overstrike_style;      /* OVS_SINGLE or OVS_LINE. */
+    int carriage_return_style; /* Carriage return style. */
+    int squeeze_blank_lines;    /* 1=squeeze multiple blank lines into one. */
+
+    /* Internal state. */
+    struct file_ext file;      /* Output file. */
+    int page_number;           /* Current page number. */
+    struct line *lines;         /* Page content. */
+    int lines_cap;              /* Number of lines allocated. */
+    int w, l;                  /* Actual width & length w/o margins, etc. */
+    int cur_font;              /* Current font by OUTP_F_*. */
+#if GLOBAL_DEBUGGING
+    int debug;                 /* Set by som_text_draw(). */
+#endif
+  };
+
+static int postopen (struct file_ext *);
+static int preclose (struct file_ext *);
+
+static struct outp_option_info *option_info;
+
+static int
+ascii_open_global (struct outp_class *this UNUSED)
+{
+  option_info = xmalloc (sizeof *option_info);
+  option_info->initial = 0;
+  option_info->options = 0;
+  return 1;
+}
+
+
+static char *s;
+static int
+ascii_close_global (struct outp_class *this UNUSED)
+{
+  free(option_info->initial);
+  free(option_info->options);
+  free(option_info);
+  free(s);
+  return 1;
+}
+
+static 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;
+}
+
+static 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 *x);
+  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->squeeze_blank_lines = 0;
+  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->lines = NULL;
+  x->lines_cap = 0;
+  x->cur_font = OUTP_F_R;
+#if GLOBAL_DEBUGGING
+  x->debug = 0;
+#endif
+  return 1;
+}
+
+static 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 (&x->ops[OPS_FORMFEED], "\f");
+  if (ls_null_p (&x->ops[OPS_NEWLINE])
+      || !strcmp (ls_c_str (&x->ops[OPS_NEWLINE]), "default"))
+    {
+      ls_create (&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 (&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;
+}
+
+static int
+ascii_close_driver (struct outp_driver *this)
+{
+  struct ascii_driver_ext *x = this->ext;
+  int i;
+  
+  assert (this->driver_open == 1);
+  msg (VM (2), _("%s: Beginning closing..."), this->name);
+  
+  x = this->ext;
+  for (i = 0; i < OPS_COUNT; i++)
+    ls_destroy (&x->ops[i]);
+  for (i = 0; i < LNS_COUNT; i++)
+    ls_destroy (&x->box[i]);
+  for (i = 0; i < FSTY_COUNT; i++)
+    ls_destroy (&x->fonts[i]);
+  if (x->lines != NULL) 
+    {
+      int line;
+      
+      for (line = 0; line < x->lines_cap; line++) 
+        free (x->lines[line].chars);
+      free (x->lines); 
+    }
+  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},
+    {"squeeze", boolean_arg, 2},
+    {"", 0, 0},
+  };
+
+static 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_c_str (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 (&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 fixed_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);
+            abort ();
+         }
+       ls_create (s, value);
+      }
+      break;
+    case font_string_arg:
+      {
+       if (!strcmp (value, "overstrike"))
+         {
+           ls_destroy (&x->fonts[subcat]);
+           return;
+         }
+       ls_create (&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 = setting;
+           break;
+         case 1:
+           x->paginate = setting;
+           break;
+          case 2:
+            x->squeeze_blank_lines = setting;
+            break;
+         default:
+           assert (0);
+         }
+      }
+      break;
+    default:
+      assert (0);
+    }
+}
+
+int
+postopen (struct file_ext *f)
+{
+  struct ascii_driver_ext *x = f->param;
+  struct fixed_string *s = &x->ops[OPS_INIT];
+
+  if (!ls_empty_p (s) && fwrite (ls_c_str (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 fixed_string *d = &x->ops[OPS_DONE];
+
+  if (!ls_empty_p (d) && fwrite (ls_c_str (d), ls_length (d), 1, f->file) < 1)
+    {
+      msg (ME, _("ASCII output driver: %s: %s"),
+          f->filename, strerror (errno));
+      return 0;
+    }
+  return 1;
+}
+
+static int
+ascii_open_page (struct outp_driver *this)
+{
+  struct ascii_driver_ext *x = this->ext;
+  int i;
+
+  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;
+    }
+
+  if (x->l > x->lines_cap)
+    {
+      x->lines = xnrealloc (x->lines, x->l, sizeof *x->lines);
+      for (i = x->lines_cap; i < x->l; i++) 
+        {
+          struct line *line = &x->lines[i];
+          line->chars = NULL;
+          line->char_cap = 0;
+        }
+      x->lines_cap = x->l;
+    }
+
+  for (i = 0; i < x->l; i++)
+    x->lines[i].char_cnt = 0;
+
+  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)
+{
+  struct line *line;
+  int j;
+
+  assert (i < x->lines_cap);
+  line = &x->lines[i];
+  if (l > line->char_cap) 
+    {
+      line->char_cap = l * 2;
+      line->chars = xnrealloc (line->chars,
+                               line->char_cap, sizeof *line->chars); 
+    }
+  for (j = line->char_cnt; j < l; j++)
+    line->chars[j] = ' ';
+  line->char_cnt = 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->lines[K].chars[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)
+
+static 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->lines[y1].char_cnt < x2)
+    expand_line (ext, y1, x2);
+
+  for (x = x1; x < x2; x++)
+    draw_line (x, y1, (style << LNS_LEFT) | (style << LNS_RIGHT));
+}
+
+static 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->lines[y].char_cnt <= x1)
+      expand_line (ext, y, x1 + 1);
+
+  for (y = y1; y < y2; y++)
+    draw_line (x1, y, (style << LNS_TOP) | (style << LNS_BOTTOM));
+}
+
+static 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->lines[y].char_cnt <= x)
+    expand_line (ext, y, x + 1);
+  draw_line (x, y, l);
+}
+
+/* FIXME: Later we could set this up so that for certain devices it
+   performs shading? */
+static 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. */
+static void
+ascii_polyline_begin (struct outp_driver *this UNUSED, const struct color *c UNUSED)
+{
+  assert (this->driver_open && this->page_open);
+}
+static void
+ascii_polyline_point (struct outp_driver *this UNUSED, int x UNUSED, int y UNUSED)
+{
+  assert (this->driver_open && this->page_open);
+}
+static void
+ascii_polyline_end (struct outp_driver *this UNUSED)
+{
+  assert (this->driver_open && this->page_open);
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static void
+ascii_text_set_font_by_family (struct outp_driver *this UNUSED, const char *s UNUSED)
+{
+  assert (this->driver_open && this->page_open);
+}
+
+static 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 ();
+}
+
+static const char *
+ascii_text_get_font_family (struct outp_driver *this UNUSED)
+{
+  assert (this->driver_open && this->page_open);
+  return "";
+}
+
+static int
+ascii_text_set_size (struct outp_driver *this, int size)
+{
+  assert (this->driver_open && this->page_open);
+  return size == this->vert;
+}
+
+static 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_c_str (&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;
+}
+
+static 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);
+}
+
+static 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;
+
+  char *s = ls_c_str (&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->lines[t->y].char_cnt < 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->lines[y].chars[x++] = *s++ | attr;
+  }
+}
+\f
+/* ascii_close_page () and support routines. */
+
+#define LINE_BUF_SIZE 1024
+static char *line_buf;
+static 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 fixed_string *box = &ext->box[*bp & 0xff];
+         size_t len = ls_length (box);
+
+         if (remaining >= len)
+           {
+             memcpy (line_p, ls_c_str (box), len);
+             line_p += len;
+             remaining -= len;
+           }
+         else
+           {
+             if (!commit_line_buf (this))
+               return 0;
+             output_string (this, ls_c_str (box), ls_end (box));
+             remaining = LINE_BUF_SIZE - (line_p - line_buf);
+           }
+       }
+      else if (*bp & 0x0300)
+       {
+         struct fixed_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);
+              abort ();
+           }
+         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);
+                    abort ();
+                 }
+             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, char 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);
+      abort ();
+    }
+}
+
+/* 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;
+  int line_num;
+
+  struct fixed_string *newline = &ext->ops[OPS_NEWLINE];
+
+  int n_chars;
+  int n_passes;
+
+  if (NULL == ext->file.file)
+    return;
+
+  /* Iterate over all the lines to be output. */
+  for (line_num = first; line_num < first + count; line_num++)
+    {
+      struct line *line = &ext->lines[line_num];
+      unsigned short *p = line->chars;
+      unsigned short *end_p = p + line->char_cnt;
+      unsigned short *bp, *ep;
+      unsigned short attr = 0;
+
+      assert (end_p >= p);
+
+      /* Squeeze multiple blank lines into a single blank line if
+         requested. */
+      if (ext->squeeze_blank_lines
+          && line_num > first
+          && ext->lines[line_num].char_cnt == 0
+          && ext->lines[line_num - 1].char_cnt == 0)
+        continue;
+
+      /* 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 fixed_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);
+                  abort ();
+               }
+             if (off)
+               output_string (this, ls_c_str (off), ls_end (off));
+           }
+
+         /* Turn on new font. */
+         attr = (*bp & 0x0300);
+         if (attr != (OUTP_F_R << 8))
+           {
+             struct fixed_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);
+                  abort ();
+               }
+             if (on)
+               output_string (this, ls_c_str (on), ls_end (on));
+           }
+
+         ep = bp + 1;
+       }
+      if (n_passes > 1)
+       {
+         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;
+                default:
+                  assert (0);
+                  abort ();
+               }
+             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;
+           }
+       }
+
+      output_string (this, ls_c_str (newline), ls_end (newline));
+    }
+}
+
+
+static int
+ascii_close_page (struct outp_driver *this)
+{
+  static int s_len;
+
+  struct ascii_driver_ext *x = this->ext;
+  int nl_len, ff_len, total_len;
+  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_c_str (&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"), get_start_date (),
+                  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_c_str (&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_c_str (&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, 0, x->l);
+
+  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_c_str (&x->ops[OPS_NEWLINE]), nl_len);
+      cp += nl_len;
+    }
+  memcpy (cp, ls_c_str (&x->ops[OPS_FORMFEED]), ff_len);
+  if ( x->paginate ) 
+         output_string (this, s, &s[total_len]);
+
+  if (line_p != line_buf && !commit_line_buf (this))
+    return 0;
+
+  this->page_open = 0;
+  return 1;
+}
+
+
+
+static void
+ascii_chart_initialise(struct outp_driver *d UNUSED, struct chart *ch )
+{
+  msg(MW, _("Charts are unsupported with ascii drivers."));
+  ch->lp = 0;
+}
+
+static void 
+ascii_chart_finalise(struct outp_driver *d UNUSED, struct chart *ch UNUSED)
+{
+  
+}
+
+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,
+
+  ascii_chart_initialise,
+  ascii_chart_finalise
+};
diff --git a/src/output/chart.c b/src/output/chart.c
new file mode 100644 (file)
index 0000000..958695a
--- /dev/null
@@ -0,0 +1,122 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <plot.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+#include <assert.h>
+#include <math.h>
+
+#include "chart.h"
+#include "str.h"
+#include "alloc.h"
+#include "manager.h"
+#include "output.h"
+
+extern struct som_table_class tab_table_class;
+
+struct chart *
+chart_create(void)
+{
+  struct chart *chart;
+  struct outp_driver *d;
+
+  d = outp_drivers (NULL);
+  if (d == NULL)
+    return NULL;
+  
+  chart = xmalloc (sizeof *chart);
+  d->class->initialise_chart(d, chart);
+  if (!chart->lp) 
+    {
+      free (chart);
+      return NULL; 
+    }
+
+  if (pl_openpl_r (chart->lp) < 0)      /* open Plotter */
+    return NULL;
+  
+  pl_fspace_r (chart->lp, 0.0, 0.0, 1000.0, 1000.0); /* set coordinate system */
+  pl_flinewidth_r (chart->lp, 0.25);    /* set line thickness */
+  pl_pencolorname_r (chart->lp, "black"); 
+
+  pl_erase_r (chart->lp);               /* erase graphics display */
+  pl_filltype_r(chart->lp,0);
+
+  pl_savestate_r(chart->lp);
+
+  /* Set default chartetry */
+  chart->data_top =   900;
+  chart->data_right = 800;
+  chart->data_bottom = 120;
+  chart->data_left = 150;
+  chart->abscissa_top = 70;
+  chart->ordinate_right = 120;
+  chart->title_bottom = 920;
+  chart->legend_left = 810;
+  chart->legend_right = 1000;
+  chart->font_size = 0;
+  strcpy(chart->fill_colour,"red");
+
+  /* Get default font size */
+  if ( !chart->font_size) 
+    chart->font_size = pl_fontsize_r(chart->lp, -1);
+
+  /* Draw the data area */
+  pl_box_r(chart->lp, 
+          chart->data_left, chart->data_bottom, 
+          chart->data_right, chart->data_top);
+
+  return chart;
+}
+
+void
+chart_submit(struct chart *chart)
+{
+  struct som_entity s;
+  struct outp_driver *d;
+
+  if ( ! chart ) 
+     return ;
+
+  pl_restorestate_r(chart->lp);
+
+  s.class = &tab_table_class;
+  s.ext = chart;
+  s.type = SOM_CHART;
+  som_submit (&s);
+  
+  if (pl_closepl_r (chart->lp) < 0)     /* close Plotter */
+    {
+      fprintf (stderr, "Couldn't close Plotter\n");
+    }
+
+  pl_deletepl_r(chart->lp);
+
+  pl_deleteplparams(chart->pl_params);
+
+  d = outp_drivers (NULL);
+  d->class->finalise_chart(d, chart);
+  free(chart);
+}
+
diff --git a/src/output/chart.h b/src/output/chart.h
new file mode 100644 (file)
index 0000000..546c6c7
--- /dev/null
@@ -0,0 +1,91 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+#include <float.h>
+#include <assert.h>
+#include <math.h>
+
+#include "chart-geometry.h"
+#include "str.h"
+#include "alloc.h"
+#include "manager.h"
+#include "output.h"
+
+#ifndef CHART_H
+#define CHART_H
+
+#ifndef NO_CHARTS
+#include <plot.h>
+#endif
+
+struct chart {
+
+#ifndef NO_CHARTS
+  plPlotter *lp ;
+  plPlotterParams *pl_params;
+#else
+  void *lp;
+#endif
+  char *filename;
+  FILE *file;
+
+  /* The geometry of the chart 
+     See diagram at the foot of this file.
+   */
+  
+  int data_top   ;
+  int data_right ;
+  int data_bottom;
+  int data_left  ;
+
+  int abscissa_top;
+
+  int ordinate_right ;
+
+  int title_bottom ;
+
+  int legend_left ;
+  int legend_right ;
+
+  
+  /* Default font size for the plot (if zero, then use plotter default) */
+  int font_size; 
+
+  char fill_colour[10];
+
+  /* Stuff Particular to Cartesians (and Boxplots ) */
+  double ordinate_scale;
+  double abscissa_scale;
+  double x_min;
+  double x_max;
+  double y_min;
+  double y_max;
+};
+
+
+
+struct chart * chart_create(void);
+void chart_submit(struct chart *ch);
+
+#endif
diff --git a/src/output/dummy-chart.c b/src/output/dummy-chart.c
new file mode 100644 (file)
index 0000000..9e9a814
--- /dev/null
@@ -0,0 +1,35 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2004 Free Software Foundation, Inc.
+   Written by John Darrington <john@darrington.wattle.id.au>
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+
+#include <stdio.h>
+
+
+struct chart *
+chart_create(void)
+{
+  return NULL;
+}
+
+void
+chart_submit(struct chart *chart)
+{
+}
+
diff --git a/src/output/font.h b/src/output/font.h
new file mode 100644 (file)
index 0000000..a709667
--- /dev/null
@@ -0,0 +1,142 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, 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 man page. */
+    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);
+void groff_done (void);
+
+struct font_desc *default_font (void);
+
+#endif /* font_h */
diff --git a/src/output/groff-font.c b/src/output/groff-font.c
new file mode 100644 (file)
index 0000000..b08ee63
--- /dev/null
@@ -0,0 +1,1030 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "font.h"
+#include "message.h"
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdarg.h>
+#include "alloc.h"
+#include "message.h"
+#include "filename.h"
+#include "getline.h"
+#include "hash.h"
+#include "pool.h"
+#include "str.h"
+#include "version.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+int font_number_to_index (int);
+
+int space_index;
+
+static int font_msg (int, const char *,...)
+     PRINTF_FORMAT (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";
+
+/* Some notes on the groff_font 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 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 = 0;
+
+  /* Index for previous line. */
+  int prev_index = -1;
+
+  /* Current location in file, used for error reporting. */
+  struct file_locator where;
+
+#ifdef 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 = 8;
+  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);
+#ifdef 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:
+  if (f != NULL)
+    fclose (f);
+  pool_destroy (font_pool);
+#ifdef 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,...)
+{
+  struct error error;
+  va_list args;
+
+  error.class = class;
+  err_location (&error.where);
+  error.title = _("installation error: Groff font error: ");
+
+  va_start (args, format);
+  err_vmsg (&error, format, args);
+  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)
+{
+  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++) 
+    {
+      int c = (unsigned char) *cp;
+      if (badchars[c >> 3] & (1 << (c & 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 (must be power of 2). */
+    int used;                  /* Number of full entries. */
+    int next_index;            /* Next index to allocate. */
+    struct index_hash *tab;    /* Hash table proper. */
+    struct pool *ar;           /* Pool for names. */
+  }
+hash;
+
+void
+groff_init (void)
+{
+  space_index = font_char_name_to_index ("space");
+}
+
+void
+groff_done (void)
+{
+  free (hash.tab) ;
+  pool_destroy(hash.ar);
+}
+
+
+/* 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 = 128;
+      hash.used = 0;
+      hash.next_index = 256;
+      hash.tab = xnmalloc (hash.size, sizeof *hash.tab);
+      hash.ar = pool_create ();
+      for (i = 0; i < hash.size; i++)
+       hash.tab[i].name = NULL;
+    }
+
+  for (i = hsh_hash_string (name) & (hash.size - 1); 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.size / 2)
+    {
+      struct index_hash *old_tab = hash.tab;
+      int old_size = hash.size;
+      int i, j;
+
+      hash.size *= 2;
+      hash.tab = xnmalloc (hash.size, sizeof *hash.tab);
+      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 = hsh_hash_string (old_tab[i].name) & (hash.size - 1);
+                 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_nrealloc (font->owner, font->deref,
+                                   font->deref_size, sizeof *font->deref);
+      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_nrealloc (font->owner, font->metric,
+                                    font->metric_size, sizeof *font->metric);
+    }
+  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 *= 2;
+      font->kern_max_used = font->kern_size / 2;
+      font->kern = pool_nmalloc (font->owner,
+                                 font->kern_size, sizeof *font->kern);
+      for (i = 0; i < font->kern_size; i++)
+       font->kern[i].ch1 = -1;
+
+      if (old_kern)
+        {
+          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 - 1));
+              while (font->kern[j].ch1 != -1)
+                if (0 == j--)
+                  j = font->kern_size - 1;
+              font->kern[j] = old_kern[i];
+            }
+          pool_free (font->owner, old_kern);
+        }
+    }
+
+  for (i = hash_kern (ch1, ch2) & (font->kern_size - 1);
+       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 = xnrealloc (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 - 1);
+       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 = 8;
+  font->kern_used = 0;
+  font->kern_max_used = 0;
+  return font;
+}
diff --git a/src/output/html.c b/src/output/html.c
new file mode 100644 (file)
index 0000000..901c98a
--- /dev/null
@@ -0,0 +1,652 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+/* This #if encloses the rest of the file. */
+#if !NO_HTML
+
+#include <config.h>
+#include "chart.h"
+#include "htmlP.h"
+#include "message.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 "message.h"
+#include "filename.h"
+#include "getline.h"
+#include "getlogin_r.h"
+#include "output.h"
+#include "manager.h"
+#include "table.h"
+#include "version.h"
+#include "make-file.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* Prototypes. */
+static int postopen (struct file_ext *);
+static int preclose (struct file_ext *);
+
+static int
+html_open_global (struct outp_class *this UNUSED)
+{
+  return 1;
+}
+
+static int
+html_close_global (struct outp_class *this UNUSED)
+{
+  return 1;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+
+/* Link the image contained in FILENAME to the 
+   HTML stream in file F. */
+static int
+link_image (struct file_ext *f, char *filename)
+{
+  fprintf (f->file,
+          "<IMG SRC=\"%s\"/>", filename);
+
+  if (ferror (f->file))
+    return 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;
+
+static 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_c_str (val));
+      break;
+    case string_arg:
+      {
+       char **dest;
+       switch (subcat)
+         {
+         case 0:
+           dest = &x->prologue_fn;
+           break;
+         default:
+           assert (0);
+            abort ();
+         }
+       if (*dest)
+         free (*dest);
+       *dest = xstrdup (ds_c_str (val));
+      }
+      break;
+    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},
+      {0, 0},
+    };
+  char login[128], host[128];
+  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;
+  size_t 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;
+  }
+
+  if (getenv ("LOGNAME") != NULL)
+    str_copy_rpad (login, sizeof login, getenv ("LOGNAME"));
+  else if (getlogin_r (login, sizeof login))
+    strcpy (login, _("nobody"));
+  dict[2].value = login;
+
+#ifdef HAVE_UNISTD_H
+  if (gethostname (host, 128) == -1)
+    {
+      if (errno == ENAMETOOLONG)
+       host[127] = 0;
+      else
+       strcpy (host, _("nowhere"));
+    }
+#else
+  strcpy (host, _("nowhere"));
+#endif
+  dict[3].value = host;
+
+  dict[4].value = outp_title ? outp_title : "";
+  dict[5].value = outp_subtitle ? outp_subtitle : "";
+
+  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;
+}
+
+static 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);
+}
+
+static 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 *);
+
+static void
+html_submit (struct outp_driver *this, struct som_entity *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;
+    }
+
+  assert ( s->class == &tab_table_class ) ;
+
+  switch (s->type) 
+    {
+    case SOM_TABLE:
+      output_tab_table ( this, (struct tab_table *) s->ext);
+      break;
+    case SOM_CHART:
+      link_image( &x->file, ((struct chart *)s->ext)->filename);
+      break;
+    default:
+      assert(0);
+      break;
+    }
+
+}
+
+/* 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;
+
+  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);
+         }
+    }
+}
+  
+/* 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;
+  
+  if (t->nr == 1 && t->nc == 1)
+    {
+      fputs ("<P>", x->file.file);
+      if (!ls_empty_p (t->cc))
+       escape_string (x->file.file, ls_c_str (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_c_str (&t->title),
+                    ls_length (&t->title));
+      fputs ("</TH>\n  </TR>\n", x->file.file);
+    }
+  
+  {
+    int r;
+    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++, ct++)
+         {
+            struct fixed_string *cc;
+           int tag;
+           char header[128];
+           char *cp;
+            struct tab_joined_cell *j = NULL;
+
+            cc = t->cc + c + r * t->nc;
+           if (*ct & TAB_JOIN) 
+              {
+                j = (struct tab_joined_cell *) ls_c_str (cc);
+                cc = &j->contents;
+                if (j->x1 != c || j->y1 != r)
+                  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)
+             {
+               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);
+
+                cc = &j->contents;
+             }
+           
+           strcpy (cp, ">");
+           fputs (header, x->file.file);
+           
+           if ( ! (*ct & TAB_EMPTY)  ) 
+             {
+               char *s = ls_c_str (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);
+}
+
+static void
+html_initialise_chart(struct outp_driver *d UNUSED, struct chart *ch)
+{
+
+  FILE  *fp;
+
+  make_unique_file_stream(&fp, &ch->filename);
+
+#ifdef NO_CHARTS
+  ch->lp = 0;
+#else
+  ch->pl_params = pl_newplparams();
+  ch->lp = pl_newpl_r ("png", 0, fp, stderr, ch->pl_params);
+#endif
+
+}
+
+static void 
+html_finalise_chart(struct outp_driver *d UNUSED, struct chart *ch)
+{
+  free(ch->filename);
+}
+
+
+
+/* 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,
+
+  html_initialise_chart,
+  html_finalise_chart
+
+};
+
+#endif /* !NO_HTML */
+
diff --git a/src/output/htmlP.h b/src/output/htmlP.h
new file mode 100644 (file)
index 0000000..ee86141
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, 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/output/manager.c b/src/output/manager.c
new file mode 100644 (file)
index 0000000..3647abb
--- /dev/null
@@ -0,0 +1,297 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "manager.h"
+#include "message.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "output.h"
+#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. */
+static struct outp_driver *d=0;
+
+/* Table. */
+static struct som_entity *t=0;
+
+/* 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_entity (struct outp_driver *, struct som_entity *);
+
+/* Output table T to appropriate output devices. */
+void
+som_submit (struct som_entity *t)
+{
+#if GLOBAL_DEBUGGING
+  static int entry;
+  
+  assert (entry++ == 0);
+#endif
+
+  if ( t->type == SOM_TABLE) 
+    {
+      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_entity (d, t);
+
+  }
+  
+#if GLOBAL_DEBUGGING
+  assert (--entry == 0);
+#endif
+}
+
+/* Output entity ENTITY to driver DRIVER. */
+static void
+output_entity (struct outp_driver *driver, struct som_entity *entity)
+{
+  bool fits_width, fits_length;
+  d = driver;
+
+  assert (d->driver_open);
+  if (!d->page_open && !d->class->open_page (d))
+    {
+      d->device = OUTP_DEV_DISABLED;
+      return;
+    }
+  
+  if (d->class->special || entity->type == SOM_CHART)
+    {
+      driver->class->submit (d, entity);
+      return;
+    }
+
+  t = entity;
+  
+  t->class->driver (d);
+  t->class->area (&tw, &th);
+  fits_width = t->class->fits_width (d->width);
+  fits_length = t->class->fits_length (d->length);
+  if (!fits_width || !fits_length) 
+    {
+      int tl, tr, tt, tb;
+      tl = fits_width ? hl : 0;
+      tr = fits_width ? hr : 0;
+      tt = fits_length ? ht : 0;
+      tb = fits_length ? hb : 0;
+      t->class->set_headers (tl, tr, tt, tb);
+      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 ();
+
+  t->class->set_headers (hl, hr, ht, hb);
+}
+
+/* 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/output/manager.h b/src/output/manager.h
new file mode 100644 (file)
index 0000000..1568dca
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, 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.  */
+
+#include <stdbool.h>
+
+enum som_type
+  {
+    SOM_TABLE,
+    SOM_CHART
+  } ;
+
+/* Entity (Table or Chart) . */
+struct som_entity
+  {
+    struct som_table_class *class;     /* Table class. */
+    enum som_type type;                 /* Table or Chart */ 
+    void *ext;                         /* Owned by */
+  };
+
+/* 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_entity *);
+    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 *);
+    bool (*fits_width) (int width);
+    bool (*fits_length) (int length);
+
+    /* Set columns and rows. */
+    void (*set_width) (int column, int width);         /* ? */
+    void (*set_height) (int row, int height);          /* ? */
+    void (*set_headers) (int l, int r, int t, int b);
+
+    /* 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_entity *t);
+
+/* Miscellaneous. */
+void som_eject_page (void);
+void som_blank_line (void);
+
+#endif /* som_h */
diff --git a/src/output/output.c b/src/output/output.c
new file mode 100644 (file)
index 0000000..b1b4046
--- /dev/null
@@ -0,0 +1,1328 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "output.h"
+#include "message.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include "alloc.h"
+#include "message.h"
+#include "filename.h"
+#include "htmlP.h"
+#include "misc.h"
+#include "settings.h"
+#include "str.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* 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. */
+static int disabled_devices;
+
+static void destroy_driver (struct outp_driver *);
+static void configure_driver_line (char *);
+static void configure_driver (const char *, const char *,
+                              const char *, const char *);
+
+/* 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. */
+static 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", get_viewwidth ());
+      return buf;
+    }
+  else if (!strcmp (key, "viewlength"))
+    {
+      sprintf (buf, "%d", get_viewlength ());
+      return buf;
+    }
+  else
+    return getenv (key);
+}
+
+/* Initializes global variables. */
+void
+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);
+}
+
+/* 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);
+    }
+}
+
+static void
+init_default_drivers (void) 
+{
+  msg (MM, _("Using default output driver configuration."));
+  configure_driver ("list-ascii", "ascii", "listing",
+                    "length=66 width=79 char-set=ascii "
+                    "output-file=\"pspp.list\" "
+                    "bold-on=\"\" italic-on=\"\" bold-italic-on=\"\"");
+}
+
+/* Reads the initialization file; initializes
+   outp_driver_list. */
+void
+outp_read_devices (void)
+{
+  int result = 0;
+
+  char *init_fn;
+
+  FILE *f = NULL;
+  struct string line;
+  struct file_locator where;
+
+  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);
+
+  ds_init (&line, 128);
+
+  if (init_fn == NULL)
+    {
+      msg (IE, _("Cannot find output initialization file.  "
+                 "Use `-vvvvv' 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;
+    }
+
+  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_c_str (&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_line (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 (result) 
+    {
+      msg (VM (2), _("Device definition file read successfully."));
+      if (outp_driver_list == NULL) 
+        msg (MW, _("No output drivers are active.")); 
+    }
+  else
+    msg (VM (1), _("Error reading device definition file."));
+
+  if (!result || outp_driver_list == NULL)
+    init_default_drivers ();
+}
+
+/* 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. */
+static 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. */
+void
+outp_done (void)
+{
+  struct outp_driver_class_list *n = outp_class_list ; 
+  destroy_list (&outp_driver_list);
+
+  while (n) 
+    {
+      struct outp_driver_class_list *next = n->next;
+      free(n);
+      n = next;
+    }
+  outp_class_list = NULL;
+
+  free (outp_title);
+  outp_title = NULL;
+  
+  free (outp_subtitle);
+  outp_subtitle = NULL;
+}
+
+/* Display on stdout a list of all registered driver classes. */
+void
+outp_list_classes (void)
+{
+  int width = get_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 = get_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 const 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_putc (&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."));
+                      continue;
+                   }
+                 ds_putc (&op_tokstr, (unsigned char) c);
+               }
+           }
+         prog++;
+       }
+      else
+       while (*prog && !isspace ((unsigned char) *prog) && *prog != '=')
+         ds_putc (&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 (const char *s, struct outp_driver * d)
+{
+  prog = s;
+  op_token = -1;
+
+  ds_init (&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_c_str (&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;
+
+  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 const 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 (const char *driver_name, const char *class_name,
+                  const char *device_type, const char *options)
+{
+  struct outp_driver *d = NULL, *iter;
+  struct outp_driver_class_list *c = NULL;
+
+  d = xmalloc (sizeof *d);
+  d->class = NULL;
+  d->name = xstrdup (driver_name);
+  d->driver_open = 0;
+  d->page_open = 0;
+  d->next = d->prev = NULL;
+  d->device = OUTP_DEV_NONE;
+  d->ext = NULL;
+
+  for (c = outp_class_list; c; c = c->next)
+    if (!strcmp (c->class->name, class_name))
+      break;
+  if (!c)
+    {
+      msg (IS, _("Unknown output driver class `%s'."), class_name);
+      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. */
+  if (device_type != NULL)
+    {
+      char *copy = xstrdup (device_type);
+      char *sp, *type;
+
+      for (type = strtok_r (copy, " \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);
+              free (copy);
+             goto error;
+           }
+       }
+      free (copy);
+    }
+  
+  /* Options. */
+  if (options != NULL)
+    parse_options (options, 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;
+  return;
+
+error:
+  if (d)
+    destroy_driver (d);
+  return;
+}
+
+/* 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_line (char *s)
+{
+  char *cp;
+  const char *driver_name, *class_name, *device_type, *options;
+
+  s = fn_interp_vars (s, find_defn_value);
+
+  /* Driver name. */
+  driver_name = colon_tokenize (s, &cp);
+  class_name = colon_tokenize (NULL, &cp);
+  device_type = colon_tokenize (NULL, &cp);
+  options = colon_tokenize (NULL, &cp);
+  if (driver_name == NULL || class_name == NULL)
+    {
+      msg (IS, _("Driver definition line contains fewer fields "
+                 "than expected"));
+      return;
+    }
+
+  configure_driver (driver_name, class_name, device_type, options);
+}
+
+/* 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 = xnmalloc (cp - s, sizeof *info->options);
+      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 (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 (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 (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);
+  ds_init (&line, 128);
+
+  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;
+    }
+
+  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_c_str (&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;
+    }
+
+  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/output.h b/src/output/output.h
new file mode 100644 (file)
index 0000000..7d6f81c
--- /dev/null
@@ -0,0 +1,270 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !output_h
+#define output_h 1
+
+#include "str.h"
+#include "config.h"
+
+/* A rectangle. */
+struct rect
+  {
+    int x1, y1;                        /* Upper left. */
+    int x2, y2;                        /* Lower right, not part of the rectangle. */
+  };
+
+/* 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 fixed_string s;     /* String. */
+    int h, v;                  /* Horizontal, vertical size. */
+    int x, y;                  /* Position. */
+
+    /* Internal use only. */
+    int w, l;                  /* Width, length. */
+  };
+
+struct som_entity;
+struct outp_driver;
+struct chart;
+
+/* 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 entities for output. */
+    void (*submit) (struct outp_driver *, struct som_entity *);
+    
+    /* 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 *);
+
+    void (*initialise_chart)(struct outp_driver *, struct chart *);
+    void (*finalise_chart)(struct outp_driver *, struct chart *);
+
+  };
+
+/* 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 configured output drivers. */
+extern struct outp_driver *outp_driver_list;
+
+/* Title, subtitle. */
+extern char *outp_title;
+extern char *outp_subtitle;
+
+void outp_init (void);
+void outp_read_devices (void);
+void 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/output/postscript.c b/src/output/postscript.c
new file mode 100644 (file)
index 0000000..fbe8f64
--- /dev/null
@@ -0,0 +1,3047 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+
+/*this #if encloses the remainder of the file. */
+#if !NO_POSTSCRIPT
+
+#include <ctype.h>
+#include "chart.h"
+#include "message.h"
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <time.h>
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "alloc.h"
+#include "bit-vector.h"
+#include "message.h"
+#include "filename.h"
+#include "font.h"
+#include "getline.h"
+#include "hash.h"
+#include "misc.h"
+#include "output.h"
+#include "manager.h"
+#include "start-date.h"
+#include "version.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* 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 done_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. */
+
+static int
+ps_open_global (struct outp_class *this UNUSED)
+{
+  init_fonts ();
+  groff_init ();
+  return 1;
+}
+
+static int
+ps_close_global (struct outp_class *this UNUSED)
+{
+  groff_done ();
+  done_fonts ();
+  return 1;
+}
+
+static 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;
+}
+
+static 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 *x);
+  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;
+}
+
+static 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;
+}
+
+static 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 *fe_, void *foobar UNUSED)
+{
+  const struct font_entry *fe = fe_;
+  return hsh_hash_string (fe->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;
+
+static 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_c_str (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);
+            abort ();
+         }
+       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);
+            abort ();
+         }
+       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;
+    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 hsh_hash_string (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 (&line, 128);
+  ds_init (&buf, 128);
+  for (pe = hsh_first (x->encodings, &iter); pe != NULL;
+       pe = hsh_next (x->encodings, &iter)) 
+    {
+      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; 
+
+             if (buf.length == 0) 
+               continue;
+
+             pschar = strtok_r (ds_c_str (&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_puts (&line, x->eol);
+                 fputs (ds_c_str (&line), x->file.file);
+                 ds_clear (&line);
+               }
+             ds_puts (&line, temp);
+           }
+         ds_puts (&line, x->eol);
+         fputs (ds_c_str (&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, &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 (&line, 128);
+    
+  for (;;)
+    {
+      if (!ds_get_config_line (f, &line, &where))
+       {
+         if (ferror (f))
+           msg (ME, _("Reading %s: %s."), encoding_fn, strerror (errno));
+         break;
+       }
+
+      add_encoding (this, line.string);
+    }
+
+  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},
+    {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;
+  
+  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 ((unsigned char) *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 (*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);
+
+  for (fe = hsh_first (x->loaded, &iter); fe != NULL;
+       fe = hsh_next (x->loaded, &iter)) 
+    {
+      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;
+}
+
+static 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);
+    }
+
+  this->cp_y = 0;
+
+  return !ferror (x->file.file);
+}
+
+static 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);
+}
+
+static void
+ps_submit (struct outp_driver *this UNUSED, struct som_entity *s)
+{
+  switch (s->type) 
+    {
+    case SOM_CHART:
+      break;
+    default:
+      assert(0);
+      break;
+    }
+}
+\f
+/* Lines. */
+
+/* qsort() comparison function for int tuples. */
+static int
+int_2_compare (const void *a_, const void *b_)
+{
+  const int *a = a_;
+  const int *b = b_;
+
+  return *a < *b ? -1 : *a > *b;
+}
+
+/* Hash table comparison function for cached lines. */
+static int
+compare_line (const void *a_, const void *b_, void *foo UNUSED)
+{
+  const struct line_form *a = a_;
+  const struct line_form *b = b_;
+
+  return a->ind < b->ind ? -1 : a->ind > 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;
+  int type;
+
+  for (type = 0; type < n_line_types; type++)
+    {
+      struct line_form *line;
+
+      if (x->lines[type] == NULL) 
+        continue;
+
+      for (line = hsh_first (x->lines[type], &iter); line != NULL;
+           line = hsh_next (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], &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++;
+}
+
+static 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);
+}
+
+static 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)
+
+static 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);
+    }
+}
+
+static 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);
+}
+
+static void 
+ps_polyline_begin (struct outp_driver *this UNUSED,
+                  const struct color *c UNUSED)
+{
+  assert (this->driver_open && this->page_open);
+}
+static void 
+ps_polyline_point (struct outp_driver *this UNUSED, int x UNUSED, int y UNUSED)
+{
+  assert (this->driver_open && this->page_open);
+}
+static 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"), get_start_date (), 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. */
+
+static 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;
+}
+
+static 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);
+}
+
+static 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);
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static 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;
+  unsigned name_hash = hsh_hash_string (a->font->font->internal_name);
+  return name_hash ^ hsh_hash_int (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;
+    default:
+      assert (0);
+      abort ();
+    }
+
+  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 ((char) 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_c_str (&t->s);
+  end = ls_end (&t->s);
+  if (draw)
+    {
+      x = t->x;
+      y = t->y;
+    }
+  else
+    x = y = 0;
+  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;
+  space_buf_loc = NULL;
+  space_width_left = 0;
+  
+
+  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. */
+      local_char_name[0] = *cp;
+      char_name = local_char_name;
+      if (ext->current->font->ligatures && *cp == 'f')
+       {
+         int lig = 0;
+          char_name = NULL;
+
+         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 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;
+       }
+      cp += strlen (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 = xnrealloc (buf, buf_len * 2, sizeof *buf);
+                 buf_loc = buf + buf_len;
+                 buf_end = buf + buf_len * 2;
+               }
+           }
+
+         x += kern_amt;
+         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;
+}
+
+static void
+ps_text_metrics (struct outp_driver *this, struct outp_text *t)
+{
+  assert (this->driver_open && this->page_open);
+  text (this, t, 0);
+}
+
+static 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 *f2f_, void *param UNUSED)
+{
+  const struct filename2font *f2f = f2f_;
+  return hsh_hash_string (f2f->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);
+}
+
+static void
+done_fonts (void)
+{
+ hsh_destroy (ps_fonts);
+}
+
+/* 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;
+}
+
+static void
+ps_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch)
+{
+#ifdef NO_CHARTS
+  ch->lp = NULL;
+#else
+  struct ps_driver_ext *x = this->ext;
+  char page_size[128];
+  int size;
+  int x_origin, y_origin;
+
+  ch->file = tmpfile ();
+  if (ch->file == NULL) 
+    {
+      ch->lp = NULL;
+      return;
+    }
+  
+  size = this->width < this->length ? this->width : this->length;
+  x_origin = x->left_margin + (size - this->width) / 2;
+  y_origin = x->bottom_margin + (size - this->length) / 2;
+
+  snprintf (page_size, sizeof page_size,
+            "a,xsize=%.3f,ysize=%.3f,xorigin=%.3f,yorigin=%.3f",
+            (double) size / PSUS, (double) size / PSUS,
+            (double) x_origin / PSUS, (double) y_origin / PSUS);
+
+  ch->pl_params = pl_newplparams ();
+  pl_setplparam (ch->pl_params, "PAGESIZE", page_size);
+  ch->lp = pl_newpl_r ("ps", NULL, ch->file, stderr, ch->pl_params);
+#endif
+}
+
+static void 
+ps_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED)
+{
+#ifndef NO_CHARTS
+  struct ps_driver_ext *x = this->ext;
+  char buf[BUFSIZ];
+  static int doc_num = 0;
+
+  if (this->page_open) 
+    {
+      this->class->close_page (this);
+      this->page_open = 0; 
+    }
+  this->class->open_page (this);
+  fprintf (x->file.file,
+           "/sp save def%s"
+           "%d %d translate 1000 dup scale%s"
+           "userdict begin%s"
+           "/showpage { } def%s"
+           "0 setgray 0 setlinecap 1 setlinewidth%s"
+           "0 setlinejoin 10 setmiterlimit [ ] 0 setdash newpath clear%s"
+           "%%%%BeginDocument: %d%s",
+           x->eol,
+           -x->left_margin, -x->bottom_margin, x->eol,
+           x->eol,
+           x->eol,
+           x->eol,
+           x->eol,
+           doc_num++, x->eol);
+
+  rewind (ch->file);
+  while (fwrite (buf, 1, fread (buf, 1, sizeof buf, ch->file), x->file.file))
+    continue;
+  fclose (ch->file);
+
+  fprintf (x->file.file,
+           "%%%%EndDocument%s"
+           "end%s"
+           "sp restore%s",
+           x->eol,
+           x->eol,
+           x->eol);
+  this->class->close_page (this);
+  this->page_open = 0;
+#endif
+}
+
+/* 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,
+
+  ps_submit,
+
+  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,
+
+  ps_chart_initialise,
+  ps_chart_finalise
+};
+
+/* 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,
+
+  ps_submit,
+
+  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,
+
+  ps_chart_initialise,
+  ps_chart_finalise
+
+};
+
+#endif /* NO_POSTSCRIPT */
diff --git a/src/output/table.c b/src/output/table.c
new file mode 100644 (file)
index 0000000..45af641
--- /dev/null
@@ -0,0 +1,1446 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "table.h"
+#include <ctype.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <stdlib.h>
+#include "message.h"
+#include "alloc.h"
+#include "format.h"
+#include "magic.h"
+#include "misc.h"
+#include "output.h"
+#include "pool.h"
+#include "manager.h"
+#include "variable.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include "debug-print.h"
+\f
+struct som_table_class tab_table_class;
+static char *command_name;
+
+/* 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 n);
+  void *(*nalloc_func) (struct pool *, size_t n, size_t s);
+
+  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;
+
+  nalloc_func = reallocable ? pool_nmalloc : pool_nalloc;
+  alloc_func = reallocable ? pool_malloc : pool_alloc;
+#if GLOBAL_DEBUGGING
+  t->reallocable = reallocable;
+#endif
+
+  t->cc = nalloc_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 = nalloc_func (t->container, nc, nr + 1);
+  memset (t->rh, 0, nc * (nr + 1));
+
+  t->hrh = nalloc_func (t->container, nr + 1, sizeof *t->hrh);
+  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 = nalloc_func (t->container, nr, nc + 1);
+  memset (t->rv, 0, (nc + 1) * nr);
+
+  t->wrv = nalloc_func (t->container, nc + 1, sizeof *t->wrv);
+  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);
+  t=0;
+}
+
+/* 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 fixed_string *new_cc;
+      unsigned char *new_ct;
+      int r;
+
+      new_cc = pool_nmalloc (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_nrealloc (t->container, t->cc, nr * nc, sizeof *t->cc);
+      t->ct = pool_realloc (t->container, t->ct, nr * nc);
+
+      t->rh = pool_nrealloc (t->container, t->rh, nc, nr + 1);
+      t->rv = pool_nrealloc (t->container, t->rv, nr, nc + 1);
+      t->trh = pool_realloc (t->container, t->trh, nr + 1);
+      t->hrh = pool_nrealloc (t->container, t->hrh, nr + 1, sizeof *t->hrh);
+      
+      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);
+  assert (l < table->nc);
+  assert (r < table->nc);
+  assert (t < table->nr);
+  assert (b < table->nr);
+
+
+  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;
+
+  assert (x  > 0);
+  assert (x  < t->nc);
+  assert (y1 >= 0);
+  assert (y2 >= y1);
+  assert (y2 <=  t->nr);
+
+  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);
+
+  x1 += t->col_ofs;
+  x2 += t->col_ofs;
+  y += t->row_ofs;
+
+  assert (y >= 0);
+  assert (y < t->nr);
+  assert (x2 >= x1 );
+  assert (x1 >= 0 );
+  assert (x2 < t->nc);
+
+  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;
+
+  assert (x2 >= x1);
+  assert (y2 >= y1);
+  assert (x1 >= 0);
+  assert (y1 >= 0);
+  assert (x2 < t->nc);
+  assert (y2 < t->nr);
+
+  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 fixed_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 (s, text, len);
+  pool_register (table->container, free, s->string);
+  
+  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;
+
+  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;
+  
+  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;
+  union value double_value;
+
+  assert (table != NULL && w <= 40);
+  
+  assert (c >= 0);
+  assert (c < table->nc);
+  assert (r >= 0);
+  assert (r < table->nr);
+
+  f = make_output_format (FMT_F, w, 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
+
+  double_value.f = val;
+  data_out (buf, &f, &double_value);
+
+  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);
+
+  assert (c >= 0 );
+  assert (r >= 0 );
+  assert (c < table->nc);
+  assert (r < table->nr);
+  
+
+#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);
+
+  assert (x1 + table->col_ofs >= 0);
+  assert (y1 + table->row_ofs >= 0);
+  assert (y2 >= y1);
+  assert (x2 >= x1);
+  assert (y2 + table->row_ofs < table->nr);
+  assert (x2 + table->col_ofs < table->nc);
+
+#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 fixed_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 fixed_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);
+
+          if (d->class->text_set_font_by_name != NULL)
+            d->class->text_set_font_by_name (d, "FIXED");
+          else 
+            {
+              /* FIXME */
+            }
+       }
+    }
+
+  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))
+        if (d->class->text_set_font_by_name != NULL)
+          d->class->text_set_font_by_name (d, "PROP");
+        else 
+          {
+            /* FIXME */
+          }
+    }
+  
+  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_entity s;
+
+  assert (t != NULL);
+  s.class = &tab_table_class;
+  s.ext = t;
+  s.type = SOM_TABLE;
+  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_entity *table)
+{
+  assert (table != NULL);
+  assert (table->type == SOM_TABLE);
+
+  t = table->ext;
+  tab_offset (t, 0, 0);
+  
+  assert (t->w == NULL && t->h == NULL);
+  t->w = pool_nalloc (t->container, t->nc, sizeof *t->w);
+  t->h = pool_nalloc (t->container, t->nr, sizeof *t->h);
+}
+
+/* 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;
+}
+
+/* Returns true if the table will fit in the given page WIDTH,
+   false otherwise. */
+static bool
+tabi_fits_width (int width) 
+{
+  int i;
+
+  for (i = t->l; i < t->nc - t->r; i++)
+    if (t->wl + t->wr + t->w[i] > width)
+      return false;
+
+  return true;
+}
+
+/* Returns true if the table will fit in the given page LENGTH,
+   false otherwise. */
+static bool
+tabi_fits_length (int length) 
+{
+  int i;
+
+  for (i = t->t; i < t->nr - t->b; i++)
+    if (t->ht + t->hb + t->h[i] > length)
+      return false;
+
+  return true;
+}
+
+/* Sets the number of header rows/columns on the left, right, top,
+   and bottom sides to HL, HR, HT, and HB, respectively. */
+static void
+tabi_set_headers (int hl, int hr, int ht, int hb)
+{
+  t->l = hl;
+  t->r = hr;
+  t->t = ht;
+  t->b = hb;
+}
+
+/* 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 (command_name != NULL)
+    cp = spprintf (cp, " %s", command_name);
+  cp = stpcpy (cp, ".  ");
+  if (!ls_empty_p (&t->title))
+    {
+      memcpy (cp, ls_c_str (&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);
+
+/* 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 i, y;
+  int ranges[3][2];
+  
+  tab_hit++;
+
+  y = d->cp_y;
+  if (!(t->flags & SOMF_NO_TITLE))
+    y += d->font_height;
+
+  /* Top headers. */
+  ranges[0][0] = 0;
+  ranges[0][1] = t->t * 2 + 1;
+
+  /* Requested rows. */
+  ranges[1][0] = y1 * 2 + 1;
+  ranges[1][1] = y2 * 2;
+
+  /* Bottom headers. */
+  ranges[2][0] = (t->nr - t->b) * 2;
+  ranges[2][1] = t->nr * 2 + 1;
+
+  for (i = 0; i < 3; i++) 
+    {
+      int r;
+
+      for (r = ranges[i][0]; r < ranges[i][1]; r++) 
+        {
+          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,
+    tabi_fits_width,
+    tabi_fits_length,
+    
+    NULL,
+    NULL,
+    tabi_set_headers,
+
+    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_c_str (&t->cc[index]);
+
+                 if (j->hit != tab_hit)
+                   {
+                     j->hit = tab_hit;
+
+                     if (j->x1 == c / 2 && j->y1 == r / 2)
+                       {
+                         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 < c2 / 2; 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 < r2 / 2; 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;
+}
+
+/* Sets COMMAND_NAME as the name of the current command,
+   for embedding in output. */
+void
+tab_set_command_name (const char *command_name_) 
+{
+  free (command_name);
+  command_name = command_name_ ? xstrdup (command_name_) : NULL;
+}
diff --git a/src/output/table.h b/src/output/table.h
new file mode 100644 (file)
index 0000000..0d35cc9
--- /dev/null
@@ -0,0 +1,198 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, 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 fixed_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 fixed_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 fixed_string *cc;   /* Cell contents; fixed_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 *, ...)
+     PRINTF_FORMAT (5, 6);
+void tab_joint_text (struct tab_table *, int x1, int y1, int x2, int y2,
+                    unsigned opt, const char *, ...)
+     PRINTF_FORMAT (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 fixed_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, ...)
+     PRINTF_FORMAT (2, 3);
+
+/* Embedding the command name in the output. */
+void tab_set_command_name (const char *);
+
+#endif /* tab_h */
+
diff --git a/src/procedure.c b/src/procedure.c
new file mode 100644 (file)
index 0000000..e7f6193
--- /dev/null
@@ -0,0 +1,1019 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 1997-9, 2000, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "procedure.h"
+#include "message.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 "case.h"
+#include "casefile.h"
+#include "command.h"
+#include "dictionary.h"
+#include "control-stack.h"
+#include "message.h"
+#include "expressions/public.h"
+#include "file-handle-def.h"
+#include "misc.h"
+#include "settings.h"
+#include "manager.h"
+#include "table.h"
+#include "str.h"
+#include "variable.h"
+#include "value-labels.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/*
+   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), processed,
+   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. */
+
+/* Procedure execution data. */
+struct write_case_data
+  {
+    /* Function to call for each case. */
+    bool (*proc_func) (struct ccase *, void *); /* Function. */
+    void *aux;                                 /* Auxiliary data. */ 
+
+    struct ccase trns_case;     /* Case used for transformations. */
+    struct ccase sink_case;     /* Case written to sink, if
+                                   compaction is necessary. */
+    size_t cases_written;       /* Cases output so far. */
+    size_t cases_analyzed;      /* Cases passed to procedure so far. */
+  };
+
+/* The current active file, from which cases are read. */
+struct case_source *vfm_source;
+
+/* The replacement active file, to which cases are written. */
+struct case_sink *vfm_sink;
+
+/* The compactor used to compact a compact, if necessary;
+   otherwise a null pointer. */
+static struct dict_compactor *compactor;
+
+/* Time at which vfm was last invoked. */
+static time_t last_vfm_invocation;
+
+/* 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. */
+
+/* Active transformations. */
+struct transformation *t_trns;
+size_t n_trns, m_trns, f_trns;
+
+static bool internal_procedure (bool (*proc_func) (struct ccase *, void *),
+                                void *aux);
+static void update_last_vfm_invocation (void);
+static void create_trns_case (struct ccase *, struct dictionary *);
+static void open_active_file (void);
+static bool write_case (struct write_case_data *wc_data);
+static int execute_transformations (struct ccase *c,
+                                    struct transformation *trns,
+                                    int first_idx, int last_idx,
+                                    int case_num);
+static int filter_case (const struct ccase *c, int case_num);
+static void lag_case (const struct ccase *c);
+static void clear_case (struct ccase *c);
+static bool close_active_file (void);
+\f
+/* Public functions. */
+
+/* Returns the last time the data was read. */
+time_t
+vfm_last_invocation (void) 
+{
+  if (last_vfm_invocation == 0)
+    update_last_vfm_invocation ();
+  return last_vfm_invocation;
+}
+
+/* Reads the data from the input program and writes it to a new
+   active file.  For each case we read from the input program, we
+   do the following
+
+   1. Execute permanent transformations.  If these drop the case,
+      start the next case from step 1.
+
+   2. N OF CASES.  If we have already written N cases, start the
+      next case from step 1.
+   
+   3. Write case to replacement active file.
+   
+   4. Execute temporary transformations.  If these drop the case,
+      start the next case from step 1.
+      
+   5. FILTER, PROCESS IF.  If these drop the case, start the next
+      case from step 1.
+   
+   6. Post-TEMPORARY N OF CASES.  If we have already analyzed N
+      cases, start the next case from step 1.
+      
+   7. Pass case to PROC_FUNC, passing AUX as auxiliary data.
+
+   Returns true if successful, false if an I/O error occurred. */
+bool
+procedure (bool (*proc_func) (struct ccase *, void *), void *aux)
+{
+  if (proc_func == NULL
+      && case_source_is_class (vfm_source, &storage_source_class)
+      && vfm_sink == NULL
+      && !temporary
+      && n_trns == 0)
+    {
+      /* Nothing to do. */
+      update_last_vfm_invocation ();
+      return true;
+    }
+  else 
+    {
+      bool ok;
+      
+      open_active_file ();
+      ok = internal_procedure (proc_func, aux);
+      if (!close_active_file ())
+        ok = false;
+
+      return ok;
+    }
+}
+
+/* Executes a procedure, as procedure(), except that the caller
+   is responsible for calling open_active_file() and
+   close_active_file().
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+internal_procedure (bool (*proc_func) (struct ccase *, void *), void *aux) 
+{
+  static int recursive_call;
+  struct write_case_data wc_data;
+  bool ok;
+
+  assert (++recursive_call == 1);
+
+  wc_data.proc_func = proc_func;
+  wc_data.aux = aux;
+  create_trns_case (&wc_data.trns_case, default_dict);
+  case_create (&wc_data.sink_case, dict_get_next_value_idx (default_dict));
+  wc_data.cases_written = 0;
+
+  update_last_vfm_invocation ();
+
+  ok = (vfm_source == NULL
+        || vfm_source->class->read (vfm_source,
+                                    &wc_data.trns_case,
+                                    write_case, &wc_data));
+
+  case_destroy (&wc_data.sink_case);
+  case_destroy (&wc_data.trns_case);
+
+  assert (--recursive_call == 0);
+
+  return ok;
+}
+
+/* Updates last_vfm_invocation. */
+static void
+update_last_vfm_invocation (void) 
+{
+  last_vfm_invocation = time (NULL);
+}
+
+/* Creates and returns a case, initializing it 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
+create_trns_case (struct ccase *trns_case, struct dictionary *dict)
+{
+  size_t var_cnt = dict_get_var_cnt (dict);
+  size_t i;
+
+  case_create (trns_case, dict_get_next_value_idx (dict));
+  for (i = 0; i < var_cnt; i++) 
+    {
+      struct variable *v = dict_get_var (dict, i);
+      union value *value = case_data_rw (trns_case, v->fv);
+
+      if (v->type == NUMERIC)
+        value->f = v->reinit ? 0.0 : SYSMIS;
+      else
+        memset (value->s, ' ', v->width);
+    }
+}
+
+/* Makes all preparations for reading from the data source and writing
+   to the data sink. */
+static void
+open_active_file (void)
+{
+  /* Make temp_dict refer to the dictionary right before data
+     reaches the sink */
+  if (!temporary)
+    {
+      temp_trns = n_trns;
+      temp_dict = default_dict;
+    }
+
+  /* Figure out compaction. */
+  compactor = (dict_needs_compaction (temp_dict)
+               ? dict_make_compactor (temp_dict)
+               : NULL);
+
+  /* Prepare sink. */
+  if (vfm_sink == NULL)
+    vfm_sink = create_case_sink (&storage_sink_class, temp_dict, NULL);
+  if (vfm_sink->class->open != NULL)
+    vfm_sink->class->open (vfm_sink);
+
+  /* Allocate memory for lag queue. */
+  if (n_lag > 0)
+    {
+      int i;
+  
+      lag_count = 0;
+      lag_head = 0;
+      lag_queue = xnmalloc (n_lag, sizeof *lag_queue);
+      for (i = 0; i < n_lag; i++)
+        case_nullify (&lag_queue[i]);
+    }
+
+  /* Close any unclosed DO IF or LOOP constructs. */
+  ctl_stack_clear ();
+}
+
+/* Transforms trns_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.  */
+static bool
+write_case (struct write_case_data *wc_data)
+{
+  int retval;
+  
+  /* Execute permanent transformations.  */
+  retval = execute_transformations (&wc_data->trns_case, t_trns, f_trns,
+                                    temp_trns, wc_data->cases_written + 1);
+  if (retval != 1)
+    goto done;
+
+  /* N OF CASES. */
+  if (dict_get_case_limit (default_dict)
+      && wc_data->cases_written >= dict_get_case_limit (default_dict))
+    goto done;
+  wc_data->cases_written++;
+
+  /* Write case to LAG queue. */
+  if (n_lag)
+    lag_case (&wc_data->trns_case);
+
+  /* Write case to replacement active file. */
+  if (vfm_sink->class->write != NULL) 
+    {
+      if (compactor != NULL) 
+        {
+          dict_compactor_compact (compactor, &wc_data->sink_case,
+                                  &wc_data->trns_case);
+          vfm_sink->class->write (vfm_sink, &wc_data->sink_case);
+        }
+      else
+        vfm_sink->class->write (vfm_sink, &wc_data->trns_case);
+    }
+  
+  /* Execute temporary transformations. */
+  retval = execute_transformations (&wc_data->trns_case, t_trns, temp_trns,
+                                    n_trns, wc_data->cases_written);
+  if (retval != 1)
+    goto done;
+  
+  /* FILTER, PROCESS IF, post-TEMPORARY N OF CASES. */
+  if (filter_case (&wc_data->trns_case, wc_data->cases_written)
+      || (dict_get_case_limit (temp_dict)
+          && wc_data->cases_analyzed >= dict_get_case_limit (temp_dict)))
+    goto done;
+  wc_data->cases_analyzed++;
+
+  /* Pass case to procedure. */
+  if (wc_data->proc_func != NULL)
+    if (!wc_data->proc_func (&wc_data->trns_case, wc_data->aux))
+      retval = -1;
+
+ done:
+  clear_case (&wc_data->trns_case);
+  return retval != -1;
+}
+
+/* Transforms case C using the transformations in TRNS[] with
+   indexes FIRST_IDX through LAST_IDX, exclusive.  Case C will
+   become case CASE_NUM (1-based) in the output file.  Returns 1
+   if the case was successfully transformed, 0 if it was filtered
+   out by one of the transformations, or -1 if the procedure
+   should be abandoned due to a fatal error. */
+static int
+execute_transformations (struct ccase *c,
+                         struct transformation *trns,
+                         int first_idx, int last_idx,
+                         int case_num) 
+{
+  int idx;
+
+  for (idx = first_idx; idx != last_idx; )
+    {
+      struct transformation *t = &trns[idx];
+      int retval = t->proc (t->private, c, case_num);
+      switch (retval)
+        {
+        case TRNS_CONTINUE:
+          idx++;
+          break;
+          
+        case TRNS_DROP_CASE:
+          return 0;
+
+        case TRNS_ERROR:
+          return -1;
+
+        case TRNS_NEXT_CASE:
+          abort ();
+
+        case TRNS_END_FILE:
+          abort ();
+          
+        default:
+          idx = retval;
+          break;
+        }
+    }
+
+  return 1;
+}
+
+/* Returns nonzero if case C with case number CASE_NUM should be
+   exclude as specified on FILTER or PROCESS IF, otherwise
+   zero. */
+static int
+filter_case (const struct ccase *c, int case_idx)
+{
+  /* FILTER. */
+  struct variable *filter_var = dict_get_filter (default_dict);
+  if (filter_var != NULL) 
+    {
+      double f = case_num (c, filter_var->fv);
+      if (f == 0.0 || mv_is_num_missing (&filter_var->miss, f))
+        return 1;
+    }
+
+  /* PROCESS IF. */
+  if (process_if_expr != NULL
+      && expr_evaluate_num (process_if_expr, c, case_idx) != 1.0)
+    return 1;
+
+  return 0;
+}
+
+/* Add C to the lag queue. */
+static void
+lag_case (const struct ccase *c)
+{
+  if (lag_count < n_lag)
+    lag_count++;
+  case_destroy (&lag_queue[lag_head]);
+  case_clone (&lag_queue[lag_head], c);
+  if (++lag_head >= n_lag)
+    lag_head = 0;
+}
+
+/* Clears the variables in C that need to be cleared between
+   processing cases.  */
+static void
+clear_case (struct ccase *c)
+{
+  size_t var_cnt = dict_get_var_cnt (default_dict);
+  size_t i;
+  
+  for (i = 0; i < var_cnt; i++) 
+    {
+      struct variable *v = dict_get_var (default_dict, i);
+      if (v->init && v->reinit) 
+        {
+          if (v->type == NUMERIC)
+            case_data_rw (c, v->fv)->f = SYSMIS;
+          else
+            memset (case_data_rw (c, v->fv)->s, ' ', v->width);
+        } 
+    }
+}
+
+/* Closes the active file. */
+static bool
+close_active_file (void)
+{
+  /* Free memory for lag queue, and turn off lagging. */
+  if (n_lag > 0)
+    {
+      int i;
+      
+      for (i = 0; i < n_lag; i++)
+       case_destroy (&lag_queue[i]);
+      free (lag_queue);
+      n_lag = 0;
+    }
+  
+  /* Dictionary from before TEMPORARY becomes permanent.. */
+  if (temporary)
+    {
+      dict_destroy (default_dict);
+      default_dict = temp_dict;
+      temp_dict = NULL;
+    }
+
+  /* Finish compaction. */
+  if (compactor != NULL) 
+    {
+      dict_compactor_destroy (compactor);
+      dict_compact_values (default_dict); 
+    }
+    
+  /* Free data source. */
+  free_case_source (vfm_source);
+  vfm_source = NULL;
+
+  /* Old data sink becomes new data source. */
+  if (vfm_sink->class->make_source != NULL)
+    vfm_source = vfm_sink->class->make_source (vfm_sink);
+  free_case_sink (vfm_sink);
+  vfm_sink = NULL;
+
+  /* Cancel TEMPORARY, PROCESS IF, FILTER, N OF CASES, vectors,
+     and get rid of all the transformations. */
+  cancel_temporary ();
+  expr_free (process_if_expr);
+  process_if_expr = NULL;
+  dict_set_case_limit (default_dict, 0);
+  dict_clear_vectors (default_dict);
+  return cancel_transformations ();
+}
+\f
+/* Storage case stream. */
+
+/* Information about storage sink or source. */
+struct storage_stream_info 
+  {
+    struct casefile *casefile;  /* Storage. */
+  };
+
+/* Initializes a storage sink. */
+static void
+storage_sink_open (struct case_sink *sink)
+{
+  struct storage_stream_info *info;
+
+  sink->aux = info = xmalloc (sizeof *info);
+  info->casefile = casefile_create (sink->value_cnt);
+}
+
+/* Destroys storage stream represented by INFO. */
+static void
+destroy_storage_stream_info (struct storage_stream_info *info) 
+{
+  if (info != NULL) 
+    {
+      casefile_destroy (info->casefile);
+      free (info); 
+    }
+}
+
+/* Writes case C to the storage sink SINK.
+   Returns true if successful, false if an I/O error occurred. */
+static bool
+storage_sink_write (struct case_sink *sink, const struct ccase *c)
+{
+  struct storage_stream_info *info = sink->aux;
+
+  return casefile_append (info->casefile, c);
+}
+
+/* Destroys internal data in SINK. */
+static void
+storage_sink_destroy (struct case_sink *sink)
+{
+  destroy_storage_stream_info (sink->aux);
+}
+
+/* Closes the sink and returns a storage source to read back the
+   written data. */
+static struct case_source *
+storage_sink_make_source (struct case_sink *sink) 
+{
+  struct case_source *source
+    = create_case_source (&storage_source_class, sink->aux);
+  sink->aux = NULL;
+  return source;
+}
+
+/* Storage sink. */
+const struct case_sink_class storage_sink_class = 
+  {
+    "storage",
+    storage_sink_open,
+    storage_sink_write,
+    storage_sink_destroy,
+    storage_sink_make_source,
+  };
+\f
+/* Storage source. */
+
+/* Returns the number of cases that will be read by
+   storage_source_read(). */
+static int
+storage_source_count (const struct case_source *source) 
+{
+  struct storage_stream_info *info = source->aux;
+
+  return casefile_get_case_cnt (info->casefile);
+}
+
+/* Reads all cases from the storage source and passes them one by one to
+   write_case(). */
+static bool
+storage_source_read (struct case_source *source,
+                     struct ccase *output_case,
+                     write_case_func *write_case, write_case_data wc_data)
+{
+  struct storage_stream_info *info = source->aux;
+  struct ccase casefile_case;
+  struct casereader *reader;
+  bool ok = true;
+
+  for (reader = casefile_get_reader (info->casefile);
+       ok && casereader_read (reader, &casefile_case);
+       case_destroy (&casefile_case))
+    {
+      case_copy (output_case, 0,
+                 &casefile_case, 0,
+                 casefile_get_value_cnt (info->casefile));
+      ok = write_case (wc_data);
+    }
+  casereader_destroy (reader);
+
+  return ok;
+}
+
+/* Destroys the source's internal data. */
+static void
+storage_source_destroy (struct case_source *source)
+{
+  destroy_storage_stream_info (source->aux);
+}
+
+/* Storage source. */
+const struct case_source_class storage_source_class = 
+  {
+    "storage",
+    storage_source_count,
+    storage_source_read,
+    storage_source_destroy,
+  };
+
+struct casefile *
+storage_source_get_casefile (struct case_source *source) 
+{
+  struct storage_stream_info *info = source->aux;
+
+  assert (source->class == &storage_source_class);
+  return info->casefile;
+}
+
+struct case_source *
+storage_source_create (struct casefile *cf)
+{
+  struct storage_stream_info *info;
+
+  info = xmalloc (sizeof *info);
+  info->casefile = cf;
+
+  return create_case_source (&storage_source_class, info);
+}
+\f
+/* Null sink.  Used by a few procedures that keep track of output
+   themselves and would throw away anything that the sink
+   contained anyway. */
+
+const struct case_sink_class null_sink_class = 
+  {
+    "null",
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+  };
+\f
+/* 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 >= 1 );
+  assert (n_before <= n_lag);
+
+  if (n_before <= lag_count)
+    {
+      int index = lag_head - n_before;
+      if (index < 0)
+        index += n_lag;
+      return &lag_queue[index];
+    }
+  else
+    return NULL;
+}
+   
+/* 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 (trns_proc_func *proc, trns_free_func *free, void *private)
+{
+  struct transformation *trns;
+  if (n_trns >= m_trns)
+    t_trns = x2nrealloc (t_trns, &m_trns, sizeof *t_trns);
+  trns = &t_trns[n_trns++];
+  trns->proc = proc;
+  trns->free = free;
+  trns->private = private;
+}
+
+/* Returns the index number that the next transformation added by
+   add_transformation() will receive.  A trns_proc_func that
+   returns this index causes control flow to jump to it. */
+size_t
+next_transformation (void) 
+{
+  return n_trns;
+}
+
+/* Cancels all active transformations, including any transformations
+   created by the input program.
+   Returns true if successful, false if an I/O error occurred. */
+bool
+cancel_transformations (void)
+{
+  bool ok = true;
+  size_t i;
+  for (i = 0; i < n_trns; i++)
+    {
+      struct transformation *t = &t_trns[i];
+      if (t->free != NULL) 
+        {
+          if (!t->free (t->private))
+            ok = false; 
+        }
+    }
+  n_trns = f_trns = 0;
+  free (t_trns);
+  t_trns = NULL;
+  m_trns = 0;
+  return ok;
+}
+\f
+/* Creates a case source with class CLASS and auxiliary data AUX
+   and based on dictionary DICT. */
+struct case_source *
+create_case_source (const struct case_source_class *class,
+                    void *aux) 
+{
+  struct case_source *source = xmalloc (sizeof *source);
+  source->class = class;
+  source->aux = aux;
+  return source;
+}
+
+/* Destroys case source SOURCE.  It is the caller's responsible to
+   call the source's destroy function, if any. */
+void
+free_case_source (struct case_source *source) 
+{
+  if (source != NULL) 
+    {
+      if (source->class->destroy != NULL)
+        source->class->destroy (source);
+      free (source);
+    }
+}
+
+/* Returns nonzero if a case source is "complex". */
+int
+case_source_is_complex (const struct case_source *source) 
+{
+  return source != NULL && (source->class == &input_program_source_class
+                            || source->class == &file_type_source_class);
+}
+
+/* Returns nonzero if CLASS is the class of SOURCE. */
+int
+case_source_is_class (const struct case_source *source,
+                      const struct case_source_class *class) 
+{
+  return source != NULL && source->class == class;
+}
+
+/* Creates a case sink to accept cases from the given DICT with
+   class CLASS and auxiliary data AUX. */
+struct case_sink *
+create_case_sink (const struct case_sink_class *class,
+                  const struct dictionary *dict,
+                  void *aux) 
+{
+  struct case_sink *sink = xmalloc (sizeof *sink);
+  sink->class = class;
+  sink->value_cnt = dict_get_compacted_value_cnt (dict);
+  sink->aux = aux;
+  return sink;
+}
+
+/* Destroys case sink SINK.  */
+void
+free_case_sink (struct case_sink *sink) 
+{
+  if (sink != NULL) 
+    {
+      if (sink->class->destroy != NULL)
+        sink->class->destroy (sink);
+      free (sink); 
+    }
+}
+\f
+/* Represents auxiliary data for handling SPLIT FILE. */
+struct split_aux_data 
+  {
+    size_t case_count;          /* Number of cases so far. */
+    struct ccase prev_case;     /* Data in previous case. */
+
+    /* Functions to call... */
+    void (*begin_func) (void *);               /* ...before data. */
+    bool (*proc_func) (struct ccase *, void *); /* ...with data. */
+    void (*end_func) (void *);                 /* ...after data. */
+    void *func_aux;                            /* Auxiliary data. */ 
+  };
+
+static int equal_splits (const struct ccase *, const struct ccase *);
+static bool procedure_with_splits_callback (struct ccase *, void *);
+static void dump_splits (struct ccase *);
+
+/* Like procedure(), but it automatically breaks the case stream
+   into SPLIT FILE break groups.  Before each group of cases with
+   identical SPLIT FILE variable values, BEGIN_FUNC is called.
+   Then PROC_FUNC is called with each case in the group.  
+   END_FUNC is called when the group is finished.  FUNC_AUX is
+   passed to each of the functions as auxiliary data.
+
+   If the active file is empty, none of BEGIN_FUNC, PROC_FUNC,
+   and END_FUNC will be called at all. 
+
+   If SPLIT FILE is not in effect, then there is one break group
+   (if the active file is nonempty), and BEGIN_FUNC and END_FUNC
+   will be called once.
+   
+   Returns true if successful, false if an I/O error occurred. */
+bool
+procedure_with_splits (void (*begin_func) (void *aux),
+                       bool (*proc_func) (struct ccase *, void *aux),
+                       void (*end_func) (void *aux),
+                       void *func_aux) 
+{
+  struct split_aux_data split_aux;
+  bool ok;
+
+  split_aux.case_count = 0;
+  case_nullify (&split_aux.prev_case);
+  split_aux.begin_func = begin_func;
+  split_aux.proc_func = proc_func;
+  split_aux.end_func = end_func;
+  split_aux.func_aux = func_aux;
+
+  open_active_file ();
+  ok = internal_procedure (procedure_with_splits_callback, &split_aux);
+  if (split_aux.case_count > 0 && end_func != NULL)
+    end_func (func_aux);
+  if (!close_active_file ())
+    ok = false;
+
+  case_destroy (&split_aux.prev_case);
+
+  return ok;
+}
+
+/* procedure() callback used by procedure_with_splits(). */
+static bool
+procedure_with_splits_callback (struct ccase *c, void *split_aux_) 
+{
+  struct split_aux_data *split_aux = split_aux_;
+
+  /* Start a new series if needed. */
+  if (split_aux->case_count == 0
+      || !equal_splits (c, &split_aux->prev_case))
+    {
+      if (split_aux->case_count > 0 && split_aux->end_func != NULL)
+        split_aux->end_func (split_aux->func_aux);
+
+      dump_splits (c);
+      case_destroy (&split_aux->prev_case);
+      case_clone (&split_aux->prev_case, c);
+
+      if (split_aux->begin_func != NULL)
+       split_aux->begin_func (split_aux->func_aux);
+    }
+
+  split_aux->case_count++;
+  if (split_aux->proc_func != NULL)
+    return split_aux->proc_func (c, split_aux->func_aux);
+  else
+    return true;
+}
+
+/* Compares the SPLIT FILE variables in cases A and B and returns
+   nonzero only if they differ. */
+static int
+equal_splits (const struct ccase *a, const struct ccase *b) 
+{
+  return case_compare (a, b,
+                       dict_get_split_vars (default_dict),
+                       dict_get_split_cnt (default_dict)) == 0;
+}
+
+/* Dumps out the values of all the split variables for the case C. */
+static void
+dump_splits (struct ccase *c)
+{
+  struct variable *const *split;
+  struct tab_table *t;
+  size_t split_cnt;
+  int i;
+
+  split_cnt = dict_get_split_cnt (default_dict);
+  if (split_cnt == 0)
+    return;
+
+  t = tab_create (3, split_cnt + 1, 0);
+  tab_dim (t, tab_natural_dimensions);
+  tab_vline (t, TAL_1 | TAL_SPACING, 1, 0, split_cnt);
+  tab_vline (t, TAL_1 | TAL_SPACING, 2, 0, split_cnt);
+  tab_text (t, 0, 0, TAB_NONE, _("Variable"));
+  tab_text (t, 1, 0, TAB_LEFT, _("Value"));
+  tab_text (t, 2, 0, TAB_LEFT, _("Label"));
+  split = dict_get_split_vars (default_dict);
+  for (i = 0; i < split_cnt; i++)
+    {
+      struct variable *v = split[i];
+      char temp_buf[80];
+      const char *val_lab;
+
+      assert (v->type == NUMERIC || v->type == ALPHA);
+      tab_text (t, 0, i + 1, TAB_LEFT | TAT_PRINTF, "%s", v->name);
+      
+      data_out (temp_buf, &v->print, case_data (c, v->fv));
+      
+      temp_buf[v->print.w] = 0;
+      tab_text (t, 1, i + 1, TAT_PRINTF, "%.*s", v->print.w, temp_buf);
+
+      val_lab = val_labs_find (v->val_labs, *case_data (c, v->fv));
+      if (val_lab)
+       tab_text (t, 2, i + 1, TAB_LEFT, val_lab);
+    }
+  tab_flags (t, SOMF_NO_TITLE);
+  tab_submit (t);
+}
+\f
+/* Represents auxiliary data for handling SPLIT FILE in a
+   multipass procedure. */
+struct multipass_split_aux_data 
+  {
+    struct ccase prev_case;     /* Data in previous case. */
+    struct casefile *casefile;  /* Accumulates data for a split. */
+
+    /* Function to call with the accumulated data. */
+    bool (*split_func) (const struct casefile *, void *);
+    void *func_aux;                            /* Auxiliary data. */ 
+  };
+
+static bool multipass_split_callback (struct ccase *c, void *aux_);
+static void multipass_split_output (struct multipass_split_aux_data *);
+
+/* Returns true if successful, false if an I/O error occurred. */
+bool
+multipass_procedure_with_splits (bool (*split_func) (const struct casefile *,
+                                                     void *),
+                                 void *func_aux) 
+{
+  struct multipass_split_aux_data aux;
+  bool ok;
+
+  assert (split_func != NULL);
+
+  open_active_file ();
+
+  case_nullify (&aux.prev_case);
+  aux.casefile = NULL;
+  aux.split_func = split_func;
+  aux.func_aux = func_aux;
+
+  ok = internal_procedure (multipass_split_callback, &aux);
+  if (aux.casefile != NULL)
+    multipass_split_output (&aux);
+  case_destroy (&aux.prev_case);
+
+  if (!close_active_file ())
+    ok = false;
+
+  return ok;
+}
+
+/* procedure() callback used by multipass_procedure_with_splits(). */
+static bool
+multipass_split_callback (struct ccase *c, void *aux_)
+{
+  struct multipass_split_aux_data *aux = aux_;
+
+  /* Start a new series if needed. */
+  if (aux->casefile == NULL || !equal_splits (c, &aux->prev_case))
+    {
+      /* Pass any cases to split_func. */
+      if (aux->casefile != NULL)
+        multipass_split_output (aux);
+
+      /* Start a new casefile. */
+      aux->casefile = casefile_create (dict_get_next_value_idx (default_dict));
+
+      /* Record split values. */
+      dump_splits (c);
+      case_destroy (&aux->prev_case);
+      case_clone (&aux->prev_case, c);
+    }
+
+  return casefile_append (aux->casefile, c);
+}
+
+static void
+multipass_split_output (struct multipass_split_aux_data *aux)
+{
+  assert (aux->casefile != NULL);
+  aux->split_func (aux->casefile, aux->func_aux);
+  casefile_destroy (aux->casefile);
+  aux->casefile = NULL;
+}
+
+
+/* Discards all the current state in preparation for a data-input
+   command like DATA LIST or GET. */
+void
+discard_variables (void)
+{
+  dict_clear (default_dict);
+  fh_set_default_handle (NULL);
+
+  n_lag = 0;
+  
+  if (vfm_source != NULL)
+    {
+      free_case_source (vfm_source);
+      vfm_source = NULL;
+    }
+
+  cancel_transformations ();
+
+  ctl_stack_clear ();
+
+  expr_free (process_if_expr);
+  process_if_expr = NULL;
+
+  cancel_temporary ();
+
+  pgm_state = STATE_INIT;
+}
diff --git a/src/procedure.h b/src/procedure.h
new file mode 100644 (file)
index 0000000..ce21684
--- /dev/null
@@ -0,0 +1,135 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !vfm_h
+#define vfm_h 1
+
+#include <time.h>
+#include <stdbool.h>
+
+struct ccase;
+typedef struct write_case_data *write_case_data;
+typedef bool write_case_func (write_case_data);
+\f
+/* The current active file, from which cases are read. */
+extern struct case_source *vfm_source;
+
+/* A case source. */
+struct case_source 
+  {
+    const struct case_source_class *class;      /* Class. */
+    void *aux;          /* Auxiliary data. */
+  };
+
+/* A case source class. */
+struct case_source_class
+  {
+    const char *name;                   /* Identifying name. */
+    
+    /* Returns the exact number of cases that READ will pass to
+       WRITE_CASE, if known, or -1 otherwise. */
+    int (*count) (const struct case_source *);
+
+    /* Reads the cases one by one into C and for each one calls
+       WRITE_CASE passing the given AUX data.
+       Returns true if successful, false if an I/O error occurred. */
+    bool (*read) (struct case_source *,
+                  struct ccase *c,
+                  write_case_func *write_case, write_case_data aux);
+
+    /* Destroys the source. */
+    void (*destroy) (struct case_source *);
+  };
+
+extern const struct case_source_class storage_source_class;
+extern const struct case_source_class file_type_source_class;
+extern const struct case_source_class input_program_source_class;
+
+struct dictionary;
+struct case_source *create_case_source (const struct case_source_class *,
+                                        void *);
+void free_case_source (struct case_source *);
+
+int case_source_is_complex (const struct case_source *);
+int case_source_is_class (const struct case_source *,
+                          const struct case_source_class *);
+
+struct casefile *storage_source_get_casefile (struct case_source *);
+struct case_source *storage_source_create (struct casefile *);
+\f
+/* The replacement active file, to which cases are written. */
+extern struct case_sink *vfm_sink;
+
+/* A case sink. */
+struct case_sink 
+  {
+    const struct case_sink_class *class;        /* Class. */
+    void *aux;          /* Auxiliary data. */
+    size_t value_cnt;   /* Number of `union value's in case. */
+  };
+
+/* A case sink class. */
+struct case_sink_class
+  {
+    const char *name;                   /* Identifying name. */
+    
+    /* Opens the sink for writing. */
+    void (*open) (struct case_sink *);
+                  
+    /* Writes a case to the sink. */
+    bool (*write) (struct case_sink *, const struct ccase *);
+    
+    /* Closes and destroys the sink. */
+    void (*destroy) (struct case_sink *);
+
+    /* Closes the sink and returns a source that can read back
+       the cases that were written, perhaps transformed in some
+       way.  The sink must still be separately destroyed by
+       calling destroy(). */
+    struct case_source *(*make_source) (struct case_sink *);
+  };
+
+extern const struct case_sink_class storage_sink_class;
+extern const struct case_sink_class null_sink_class;
+
+struct case_sink *create_case_sink (const struct case_sink_class *,
+                                    const struct dictionary *,
+                                    void *);
+void case_sink_open (struct case_sink *);
+void case_sink_write (struct case_sink *, const struct ccase *);
+void case_sink_destroy (struct case_sink *);
+void free_case_sink (struct case_sink *);
+\f
+/* Number of cases to lag. */
+extern int n_lag;
+
+bool procedure (bool (*proc_func) (struct ccase *, void *aux), void *aux);
+bool procedure_with_splits (void (*begin_func) (void *aux),
+                            bool (*proc_func) (struct ccase *, void *aux),
+                            void (*end_func) (void *aux),
+                            void *aux);
+struct ccase *lagged_case (int n_before);
+\f
+bool multipass_procedure_with_splits (bool (*) (const struct casefile *,
+                                                void *),
+                                      void *aux);
+\f
+time_t vfm_last_invocation (void);
+
+#endif /* !vfm_h */
diff --git a/src/ui/ChangeLog b/src/ui/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/ui/gui/ChangeLog b/src/ui/gui/ChangeLog
new file mode 100644 (file)
index 0000000..8fc3478
--- /dev/null
@@ -0,0 +1,11 @@
+    * Rename error-dialog.[ch] -> message-dialog.[ch]
+
+    * Moved code from the psppire module.
+       
+Sat Jan 28 16:22:23 WST 2006 John Darrington <john@darrington.wattle.id.au>
+
+    * Separated the data out of the GtkSheet.
+
+Tue Nov  2 19:00:28 WST 2004 John Darrington <john@darrington.wattle.id.au>
+
+    * Very first incarnation.
diff --git a/src/ui/gui/customentry.c b/src/ui/gui/customentry.c
new file mode 100644 (file)
index 0000000..9afe978
--- /dev/null
@@ -0,0 +1,444 @@
+/* 
+   PSPPIRE --- A Graphical User Interface for PSPP
+   Copyright (C) 2005  Free Software Foundation
+   Written by John Darrington
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. 
+*/
+
+/* 
+   This widget is a subclass of GtkEntry.  It's an entry widget with a 
+   button on the right hand side.
+
+   This code is heavily based upon the GtkSpinButton widget.  Therefore
+   the copyright notice of that code is pasted below.
+
+   Please note however,  this code is covered by the GPL, not the LGPL.
+*/
+
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * GtkSpinButton widget for GTK+
+ * Copyright (C) 1998 Lars Hamann and Stefan Jeske
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+
+#include <gtk/gtksignal.h>
+#include <gtk/gtkentry.h>
+#include "customentry.h"
+
+#define P_(A) A
+
+static void psppire_custom_entry_class_init          (PsppireCustomEntryClass *klass);
+static void psppire_custom_entry_init                (PsppireCustomEntry      *ce);
+
+static GtkEntryClass *parent_class = NULL;
+
+/* Signals */
+enum
+{
+  CLICKED,
+  n_SIGNALS
+};
+
+
+static guint custom_entry_signals[n_SIGNALS] = {0};
+
+
+GType
+psppire_custom_entry_get_type (void)
+{
+  static GType ce_type = 0;
+
+  if (!ce_type)
+    {
+      static const GTypeInfo ce_info =
+       {
+         sizeof (PsppireCustomEntryClass),
+         NULL, /* base_init */
+         NULL, /* base_finalize */
+         (GClassInitFunc) psppire_custom_entry_class_init,
+         NULL, /* class_finalize */
+         NULL, /* class_data */
+         sizeof (PsppireCustomEntry),
+         0,
+         (GInstanceInitFunc) psppire_custom_entry_init,
+       };
+
+      ce_type = g_type_register_static (GTK_TYPE_ENTRY, "PsppireCustomEntry", 
+                                       &ce_info, 0);
+    }
+
+  return ce_type;
+}
+
+
+static void
+psppire_custom_entry_map (GtkWidget *widget)
+{
+  if (GTK_WIDGET_REALIZED (widget) && !GTK_WIDGET_MAPPED (widget))
+    {
+      GTK_WIDGET_CLASS (parent_class)->map (widget);
+      gdk_window_show (PSPPIRE_CUSTOM_ENTRY (widget)->panel);
+    }
+}
+
+static void
+psppire_custom_entry_unmap (GtkWidget *widget)
+{
+  if (GTK_WIDGET_MAPPED (widget))
+    {
+      gdk_window_hide (PSPPIRE_CUSTOM_ENTRY (widget)->panel);
+      GTK_WIDGET_CLASS (parent_class)->unmap (widget);
+    }
+}
+
+static gint psppire_custom_entry_get_button_width (PsppireCustomEntry *custom_entry);
+
+static void
+psppire_custom_entry_realize (GtkWidget *widget)
+{
+  PsppireCustomEntry *custom_entry;
+  GdkWindowAttr attributes;
+  gint attributes_mask;
+  guint real_width;
+  gint button_size ;
+
+  custom_entry = PSPPIRE_CUSTOM_ENTRY (widget);
+
+  button_size = psppire_custom_entry_get_button_width (custom_entry);
+
+  real_width = widget->allocation.width;
+  widget->allocation.width -= button_size + 2 * widget->style->xthickness;
+  gtk_widget_set_events (widget, gtk_widget_get_events (widget) |
+                        GDK_KEY_RELEASE_MASK);
+  GTK_WIDGET_CLASS (parent_class)->realize (widget);
+
+  widget->allocation.width = real_width;
+  
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.wclass = GDK_INPUT_OUTPUT;
+  attributes.visual = gtk_widget_get_visual (widget);
+  attributes.colormap = gtk_widget_get_colormap (widget);
+  attributes.event_mask = gtk_widget_get_events (widget);
+  attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK 
+    | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK 
+    | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;
+
+  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+
+  attributes.x = (widget->allocation.x +
+                 widget->allocation.width - button_size -
+                 2 * widget->style->xthickness);
+  attributes.y = widget->allocation.y + (widget->allocation.height -
+                                        widget->requisition.height) / 2;
+  attributes.width = button_size + 2 * widget->style->xthickness;
+  attributes.height = widget->requisition.height;
+  
+  custom_entry->panel = gdk_window_new (gtk_widget_get_parent_window (widget), 
+                                       &attributes, attributes_mask);
+  gdk_window_set_user_data (custom_entry->panel, widget);
+
+  gtk_style_set_background (widget->style, custom_entry->panel, GTK_STATE_NORMAL);
+
+
+  gtk_widget_queue_resize (GTK_WIDGET (custom_entry));
+}
+
+
+#define MIN_BUTTON_WIDTH  6
+
+static gint
+psppire_custom_entry_get_button_width (PsppireCustomEntry *custom_entry)
+{
+  const gint size = pango_font_description_get_size 
+    (GTK_WIDGET (custom_entry)->style->font_desc);
+
+  gint button_width = MAX (PANGO_PIXELS (size), MIN_BUTTON_WIDTH);
+
+  return button_width - button_width % 2; /* force even */
+}
+
+/**
+ * custom_entry_get_shadow_type:
+ * @custom_entry: a #PsppireCustomEntry
+ * 
+ * Convenience function to Get the shadow type from the underlying widget's
+ * style.
+ * 
+ * Return value: the #GtkShadowType
+ **/
+static gint
+psppire_custom_entry_get_shadow_type (PsppireCustomEntry *custom_entry)
+{
+  GtkShadowType rc_shadow_type;
+
+  gtk_widget_style_get (GTK_WIDGET (custom_entry), "shadow_type", &rc_shadow_type, NULL);
+
+  return rc_shadow_type;
+}
+
+
+static void
+psppire_custom_entry_unrealize (GtkWidget *widget)
+{
+  PsppireCustomEntry *ce = PSPPIRE_CUSTOM_ENTRY (widget);
+
+  GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
+
+  if (ce->panel)
+    {
+      gdk_window_set_user_data (ce->panel, NULL);
+      gdk_window_destroy (ce->panel);
+      ce->panel = NULL;
+    }
+}
+
+
+static void
+psppire_custom_entry_redraw (PsppireCustomEntry *custom_entry)
+{
+  GtkWidget *widget;
+
+  widget = GTK_WIDGET (custom_entry);
+
+  if (GTK_WIDGET_DRAWABLE (widget))
+    {
+      gtk_widget_queue_draw (widget);
+
+      /* We must invalidate the panel window ourselves, because it
+       * is not a child of widget->window
+       */
+      gdk_window_invalidate_rect (custom_entry->panel, NULL, TRUE);
+    }
+}        
+
+
+static gint
+psppire_custom_entry_expose (GtkWidget      *widget,
+                    GdkEventExpose *event)
+{
+  g_return_val_if_fail (PSPPIRE_IS_CUSTOM_ENTRY (widget), FALSE);
+  g_return_val_if_fail (event != NULL, FALSE);
+
+  PsppireCustomEntry *ce = PSPPIRE_CUSTOM_ENTRY(widget);
+
+  if (GTK_WIDGET_DRAWABLE (widget))
+    {
+      GtkShadowType shadow_type;
+      GdkRectangle rect;
+
+      rect.x = 0;
+      rect.y = 0;
+
+      if (event->window != ce->panel)
+       GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
+
+      gdk_drawable_get_size (ce->panel, &rect.width, &rect.height);
+
+      gdk_window_begin_paint_rect (ce->panel, &rect);      
+
+
+      shadow_type = psppire_custom_entry_get_shadow_type (ce);
+      
+      if (shadow_type != GTK_SHADOW_NONE)
+       {
+         gtk_paint_box (widget->style, ce->panel,
+                        GTK_STATE_NORMAL, shadow_type,
+                        NULL, widget, "customentry",
+                        rect.x, rect.y, rect.width, rect.height);
+
+       }
+
+      gdk_window_end_paint (ce->panel);
+    }
+
+  return FALSE;
+}
+
+
+static gint
+psppire_custom_entry_button_press (GtkWidget      *widget,
+                          GdkEventButton *event);
+
+static void
+psppire_custom_entry_size_allocate (GtkWidget     *widget,
+                           GtkAllocation *allocation);
+
+
+
+static void
+psppire_custom_entry_class_init (PsppireCustomEntryClass *klass)
+{
+  GObjectClass     *gobject_class = G_OBJECT_CLASS (klass);
+
+  GtkWidgetClass   *widget_class;
+  GtkEntryClass   *entry_class;
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  widget_class   = (GtkWidgetClass*)   klass;
+  entry_class   = (GtkEntryClass*)   klass;
+
+  widget_class->map = psppire_custom_entry_map;
+  widget_class->unmap = psppire_custom_entry_unmap;
+
+  widget_class->realize = psppire_custom_entry_realize;
+  widget_class->unrealize = psppire_custom_entry_unrealize;
+
+  widget_class->expose_event = psppire_custom_entry_expose;
+  widget_class->button_press_event = psppire_custom_entry_button_press;
+
+  widget_class->size_allocate = psppire_custom_entry_size_allocate;
+
+
+  gtk_widget_class_install_style_property_parser 
+    (widget_class,
+     g_param_spec_enum ("shadow_type", 
+                       "Shadow Type", 
+                       P_("Style of bevel around the custom entry button"),
+                       GTK_TYPE_SHADOW_TYPE,
+                       GTK_SHADOW_ETCHED_IN,
+                       G_PARAM_READABLE),
+     gtk_rc_property_parse_enum);
+
+  custom_entry_signals[CLICKED] = 
+    g_signal_new ("clicked",
+                 G_TYPE_FROM_CLASS(gobject_class),
+                 G_SIGNAL_RUN_LAST,
+                 0,
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__VOID,
+                 G_TYPE_NONE, 
+                 0);
+
+
+}
+
+static void
+psppire_custom_entry_init (PsppireCustomEntry *ce)
+{
+}
+
+GtkWidget*
+psppire_custom_entry_new ()
+{
+  return GTK_WIDGET (g_object_new (psppire_custom_entry_get_type (), NULL));
+}
+
+
+
+static gint
+psppire_custom_entry_button_press (GtkWidget *widget,
+                                  GdkEventButton *event)
+{
+  PsppireCustomEntry *ce = PSPPIRE_CUSTOM_ENTRY (widget);
+
+  if (event->window == ce->panel)
+    {
+      if (!GTK_WIDGET_HAS_FOCUS (widget))
+       gtk_widget_grab_focus (widget);
+
+      if ( event->button == 1)
+       g_signal_emit (widget, custom_entry_signals[CLICKED], 0);
+
+    }
+  else
+    return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event);
+
+  return FALSE;
+}
+
+
+
+static void
+psppire_custom_entry_size_allocate (GtkWidget     *widget,
+                           GtkAllocation *allocation)
+{
+  PsppireCustomEntry *ce;
+  GtkAllocation entry_allocation;
+  GtkAllocation panel_allocation;
+  gint button_width;
+  gint panel_width;
+
+  g_return_if_fail (PSPPIRE_IS_CUSTOM_ENTRY (widget));
+  g_return_if_fail (allocation != NULL);
+
+  ce = PSPPIRE_CUSTOM_ENTRY (widget);
+  button_width = psppire_custom_entry_get_button_width(ce);
+  panel_width = button_width + 2 * widget->style->xthickness;
+  
+  widget->allocation = *allocation;
+  
+  entry_allocation = *allocation;
+  entry_allocation.width -= panel_width;
+
+  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+    {
+      entry_allocation.x += panel_width;
+      panel_allocation.x = allocation->x;
+    }
+  else
+    {
+      panel_allocation.x = allocation->x + allocation->width - panel_width;
+    }
+
+  panel_allocation.width = panel_width;
+  panel_allocation.height = MIN (widget->requisition.height, allocation->height);
+
+  panel_allocation.y = allocation->y + (allocation->height -
+                                       panel_allocation.height) / 2;
+
+  GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, &entry_allocation);
+
+  if (GTK_WIDGET_REALIZED (widget))
+    {
+      gdk_window_move_resize (PSPPIRE_CUSTOM_ENTRY (widget)->panel, 
+                             panel_allocation.x,
+                             panel_allocation.y,
+                             panel_allocation.width,
+                             panel_allocation.height); 
+    }
+
+  psppire_custom_entry_redraw (ce);
+}
+
+
+
+
+
diff --git a/src/ui/gui/customentry.h b/src/ui/gui/customentry.h
new file mode 100644 (file)
index 0000000..30a5635
--- /dev/null
@@ -0,0 +1,112 @@
+/* 
+   PSPPIRE --- A Graphical User Interface for PSPP
+   Copyright (C) 2005  Free Software Foundation
+   Written by John Darrington
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. 
+*/
+
+/* 
+   This widget is a subclass of GtkEntry.  It's an entry widget with a 
+   button on the right hand side.
+
+   This code is heavily based upon the GtkSpinButton widget.  Therefore
+   the copyright notice of that code is pasted below.
+
+   Please note however,  this code is covered by the GPL, not the LGPL.
+*/
+
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * GtkSpinButton widget for GTK+
+ * Copyright (C) 1998 Lars Hamann and Stefan Jeske
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+
+
+#ifndef __PSPPIRE_CUSTOM_ENTRY_H__
+#define __PSPPIRE_CUSTOM_ENTRY_H__
+
+
+#include <glib.h>
+#include <glib-object.h>
+
+
+GType psppire_custom_entry_get_type (void);
+
+G_BEGIN_DECLS
+
+#define PSPPIRE_CUSTOM_ENTRY_TYPE (psppire_custom_entry_get_type ())
+
+#define PSPPIRE_CUSTOM_ENTRY(obj)            \
+     (G_TYPE_CHECK_INSTANCE_CAST ((obj),PSPPIRE_CUSTOM_ENTRY_TYPE, PsppireCustomEntry))
+
+#define PSPPIRE_CUSTOM_ENTRY_CLASS(klass)    \
+     (G_TYPE_CHECK_CLASS_CAST ((klass),PSPPIRE_CUSTOM_ENTRY_TYPE, PsppireCustomEntryClass))
+
+#define PSPPIRE_IS_CUSTOM_ENTRY(obj)         \
+     (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PSPPIRE_CUSTOM_ENTRY_TYPE))
+
+#define IS_PSPPIRE_CUSTOM_ENTRY_CLASS(klass) \
+     (G_TYPE_CHECK_CLASS_TYPE ((klass),  PSPPIRE_CUSTOM_ENTRY_TYPE))
+
+
+typedef struct _PsppireCustomEntry       PsppireCustomEntry;
+typedef struct _PsppireCustomEntryClass  PsppireCustomEntryClass;
+
+struct _PsppireCustomEntry
+{
+  GtkEntry entry;
+
+  GdkWindow *panel;
+};
+
+struct _PsppireCustomEntryClass
+{
+  GtkEntryClass parent_class;
+
+  void (*clicked)  (PsppireCustomEntry *spin_button);
+
+};
+
+GType          custom_entry_get_type        (void);
+GtkWidget*     custom_entry_new             (void);
+
+G_END_DECLS
+
+#endif /* __PSPPIRE_CUSTOM_ENTRY_H__ */
diff --git a/src/ui/gui/data-sheet.c b/src/ui/gui/data-sheet.c
new file mode 100644 (file)
index 0000000..1edeb33
--- /dev/null
@@ -0,0 +1,147 @@
+/* 
+   PSPPIRE --- A Graphical User Interface for PSPP
+   Copyright (C) 2004, 2005, 2006  Free Software Foundation
+   Written by John Darrington
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. 
+*/
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include <ctype.h>
+
+#include "gtksheet.h"
+
+#include "gsheet-uniform-row.h"
+
+#include "psppire-dict.h"
+#include "psppire-variable.h"
+#include "psppire-data-store.h"
+#include "helper.h"
+
+#include "value-labels.h"
+#include "case.h"
+#include "data-in.h"
+
+#include "menu-actions.h"
+#include "data-sheet.h"
+
+#define _(A) A
+#define N_(A) A
+
+
+extern GladeXML *xml;
+
+
+static gboolean 
+traverse_callback (GtkSheet * sheet, 
+                       gint row, gint col, 
+                       gint *new_row, gint *new_column
+                       )
+{
+  PsppireDataStore *data_store = PSPPIRE_DATA_STORE(gtk_sheet_get_model(sheet));
+
+  const gint n_vars = psppire_dict_get_var_cnt(data_store->dict);
+
+  if ( *new_column >= n_vars ) 
+    return FALSE;
+
+  return TRUE;
+}
+
+
+
+/* Callback which occurs when the column title is double clicked */
+static gboolean
+click2column(GtkWidget *w, gint col, gpointer data)
+{
+  gint current_row, current_column;
+  select_sheet(PAGE_VAR_SHEET);
+  GtkWidget *var_sheet  = get_widget_assert(xml, "variable_sheet");
+
+  gtk_sheet_get_active_cell(GTK_SHEET(var_sheet), 
+                           &current_row, &current_column);
+
+  gtk_sheet_set_active_cell(GTK_SHEET(var_sheet), col, current_column);
+
+  return FALSE;
+}
+
+
+/* Update the data_ref_entry with the reference of the active cell */
+static gint 
+update_data_ref_entry(GtkSheet *sheet, gint row, gint col)
+{
+  /* The entry where the reference to the current cell is displayed */
+  GtkEntry *cell_ref_entry;
+
+  PsppireDataStore *data_store = PSPPIRE_DATA_STORE(gtk_sheet_get_model(sheet));
+
+
+  if ( !xml) 
+    return FALSE;
+
+  const struct PsppireVariable *pv = 
+    psppire_dict_get_variable(data_store->dict, col);
+
+  gchar *text = g_strdup_printf("%d: %s", row, 
+                               pv ? psppire_variable_get_name(pv) : "");
+  
+  cell_ref_entry = GTK_ENTRY(get_widget_assert(xml, "cell_ref_entry"));
+
+  gtk_entry_set_text(cell_ref_entry, text);
+
+  g_free(text);
+
+  return FALSE;
+}
+
+
+extern PsppireDataStore *data_store ;
+
+
+GtkWidget*
+psppire_data_sheet_create (gchar *widget_name, gchar *string1, gchar *string2,
+                          gint int1, gint int2)
+{
+  GtkWidget *sheet;
+  gint i;
+
+  const gint rows = 10046;
+
+  GObject *row_geometry = g_sheet_uniform_row_new(25, rows); 
+
+  sheet = gtk_sheet_new(G_SHEET_ROW(row_geometry), 
+                       G_SHEET_COLUMN(data_store), "data sheet", 0); 
+
+
+  g_signal_connect (GTK_OBJECT (sheet), "activate",
+                   GTK_SIGNAL_FUNC (update_data_ref_entry),
+                   0);
+
+  g_signal_connect (GTK_OBJECT (sheet), "traverse",
+                   GTK_SIGNAL_FUNC (traverse_callback), 0);
+
+
+  g_signal_connect (GTK_OBJECT (sheet), "double-click-column",
+                   GTK_SIGNAL_FUNC (click2column),
+                   0);
+
+  gtk_widget_show(sheet);
+
+  return sheet;
+}
diff --git a/src/ui/gui/data-sheet.h b/src/ui/gui/data-sheet.h
new file mode 100644 (file)
index 0000000..e894338
--- /dev/null
@@ -0,0 +1,53 @@
+/* 
+   PSPPIRE --- A Graphical User Interface for PSPP
+   Copyright (C) 2004  Free Software Foundation
+   Written by John Darrington
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+
+#ifndef DATA_SHEET_H
+#define DATA_SHEET_H
+
+#include <gtksheet.h>
+#include <psppire-case-array.h>
+#include <psppire-dict.h>
+
+void psppire_data_sheet_clear(GtkSheet *sheet);
+
+#if 0
+void psppire_data_sheet_set_dictionary(GtkSheet *sheet, PsppireDict *d);
+#endif
+
+GtkWidget* psppire_data_sheet_create (gchar *widget_name, 
+                                     gchar *string1, 
+                                     gchar *string2, 
+                                     gint int1, gint int2);
+
+
+void data_sheet_set_cell_value(GtkSheet *sheet, gint row, gint col, 
+                              const GValue *value);
+
+
+void psppire_data_sheet_set_show_labels(GtkSheet *sheet, gboolean show_labels);
+
+/* Repair any damage that may have been done to the data sheet */
+void psppire_data_sheet_redisplay(GtkSheet *sheet);
+
+guint columnWidthToPixels(GtkSheet *sheet, gint column, guint width);
+
+
+#endif
diff --git a/src/ui/gui/helper.c b/src/ui/gui/helper.c
new file mode 100644 (file)
index 0000000..061e4dc
--- /dev/null
@@ -0,0 +1,74 @@
+
+#include "helper.h"
+#include "data-in.h"
+#include "message.h"
+
+#include <ctype.h>
+#include <string.h>
+
+/* Formats a value according to FORMAT 
+   The returned string must be freed when no longer required */
+gchar *
+value_to_text(union value v, struct fmt_spec format)
+{
+  gchar *s = 0;
+
+  s = g_new(gchar, format.w + 1);
+  if ( ! data_out(s, &format, &v) ) 
+    {
+      g_warning("Can't format missing discrete value \n");
+    }
+  s[format.w]='\0';
+  g_strchug(s);
+
+  return s;
+}
+
+
+
+gboolean 
+text_to_value(const gchar *text, union value *v, 
+             struct fmt_spec format)
+{
+  struct data_in di;
+
+  if ( format.type != FMT_A) 
+    {
+      if ( ! text ) return FALSE;
+  
+      const gchar *s = text;
+      while(*s) 
+       {
+         if ( !isspace(*s))
+           break;
+         s++;
+       }
+      if ( !*s) return FALSE;
+    }
+
+  di.s = text;
+  di.e = text + strlen(text);
+  di.v = v;
+  di.flags = DI_IGNORE_ERROR;
+  di.f1 = di.f2 = 0;
+  di.format = format;
+  
+  return data_in(&di);
+
+}
+
+
+GtkWidget *
+get_widget_assert(GladeXML *xml, const gchar *name)
+{
+  g_assert(xml);
+  g_assert(name);
+  GtkWidget * w = glade_xml_get_widget(xml, name);
+
+  if ( !w ) 
+    g_warning("Widget \"%s\" could not be found\n",name);
+
+  return w;
+}
+
diff --git a/src/ui/gui/helper.h b/src/ui/gui/helper.h
new file mode 100644 (file)
index 0000000..6097041
--- /dev/null
@@ -0,0 +1,41 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2004  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+
+#ifndef __MISC_H__
+#define __MISC_H__
+
+#include "value.h"
+#include "format.h"
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+/* Formats a value according to FORMAT 
+   The returned string must be freed when no longer required */
+gchar * value_to_text(union value v, struct fmt_spec format);
+
+
+gboolean text_to_value(const gchar *text, union value *v, 
+                      struct fmt_spec format);
+
+GtkWidget * get_widget_assert(GladeXML *xml, const gchar *name);
+
+#endif
diff --git a/src/ui/gui/menu-actions.c b/src/ui/gui/menu-actions.c
new file mode 100644 (file)
index 0000000..175fd5d
--- /dev/null
@@ -0,0 +1,574 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2004, 2005  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <file-handle-def.h>
+#include <sys-file-reader.h>
+#include <case.h>
+
+#include <glade/glade.h>
+#include <gtk/gtk.h>
+
+#include <gtksheet.h>
+#include "helper.h"
+#include "menu-actions.h"
+#include "psppire-variable.h"
+#include "psppire-dict.h"
+
+#include "var-sheet.h"
+#include "data-sheet.h"
+
+#include "psppire-var-store.h"
+#include "psppire-data-store.h"
+
+#define _(A) A
+#define N_(A) A
+
+
+extern GladeXML *xml;
+
+
+extern PsppireDict *the_dictionary ;
+extern PsppireCaseArray *the_cases ;
+
+
+static struct file_handle *psppire_handle = 0;
+
+static const gchar handle_name[] = "psppire_handle";
+
+static const gchar untitled[] = _("Untitled");
+
+static const gchar window_title[]=_("PSPP Data Editor");
+
+
+static void
+psppire_set_window_title(const gchar *text)
+{
+  GtkWidget *data_editor = get_widget_assert(xml, "data_editor");
+  
+  gchar *title = g_strdup_printf("%s --- %s", text, window_title);
+
+  gtk_window_set_title(GTK_WINDOW(data_editor), title);
+
+  g_free(title);
+}
+
+void
+on_new1_activate                       (GtkMenuItem     *menuitem,
+                                        gpointer         user_data)
+{
+  psppire_dict_clear(the_dictionary);
+  psppire_case_array_clear(the_cases);
+
+  psppire_set_window_title(untitled);
+
+  if (psppire_handle)
+    fh_free(psppire_handle);
+  psppire_handle = 0 ;
+}
+
+static gboolean
+populate_case_from_reader(struct ccase *c, gpointer aux)
+{
+  struct sfm_reader *reader = aux;
+
+  return sfm_read_case(reader, c);
+}
+
+
+void
+on_open1_activate                      (GtkMenuItem     *menuitem,
+                                        gpointer         user_data)
+{
+  GtkWidget *dialog;
+  GtkWidget *data_editor  = get_widget_assert(xml, "data_editor");
+  dialog = gtk_file_chooser_dialog_new (_("Open"),
+                                       GTK_WINDOW(data_editor),
+                                       GTK_FILE_CHOOSER_ACTION_OPEN,
+                                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                                       GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+                                       NULL);
+  GtkFileFilter *filter ;
+
+  filter = gtk_file_filter_new();
+  gtk_file_filter_set_name(filter, _("System Files (*.sav)"));
+  gtk_file_filter_add_pattern(filter, "*.sav");
+  gtk_file_filter_add_pattern(filter, "*.SAV");
+  gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
+
+  filter = gtk_file_filter_new();
+  gtk_file_filter_set_name(filter, _("Portable Files (*.por) "));
+  gtk_file_filter_add_pattern(filter, "*.por");
+  gtk_file_filter_add_pattern(filter, "*.POR");
+  gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
+
+  filter = gtk_file_filter_new();
+  gtk_file_filter_set_name(filter, _("All Files"));
+  gtk_file_filter_add_pattern(filter, "*");
+  gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
+
+
+  bool finished = FALSE;
+  do {
+
+    if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
+      {
+       GtkWidget *data_sheet = get_widget_assert(xml, "data_sheet");
+       g_assert(data_sheet);
+
+       GtkWidget *var_sheet = get_widget_assert(xml, "variable_sheet");
+       g_assert(var_sheet);
+
+       char *filename = gtk_file_chooser_get_filename
+         (GTK_FILE_CHOOSER (dialog));
+
+       if ( psppire_handle ) 
+         fh_free(psppire_handle);
+
+       psppire_handle = 
+         fh_create_file (handle_name, filename, fh_default_properties());
+
+       if ( !psppire_handle ) 
+         {
+           g_warning("Cannot read handle for reading system file \"%s\"\n", 
+                     filename);
+           continue;
+         }
+
+       struct dictionary *new_dict;
+       struct sfm_read_info ri;
+       struct sfm_reader *reader ; 
+
+       reader = sfm_open_reader (psppire_handle, &new_dict, &ri);
+      
+       if ( ! reader ) 
+         continue;
+
+       the_dictionary = psppire_dict_new_from_dict(new_dict);
+
+       PsppireVarStore *var_store = 
+         PSPPIRE_VAR_STORE(gtk_sheet_get_model(GTK_SHEET(var_sheet)));
+       
+       psppire_var_store_set_dictionary(var_store, the_dictionary);
+
+
+       PsppireDataStore *data_store = 
+         PSPPIRE_DATA_STORE(gtk_sheet_get_model(GTK_SHEET(data_sheet)));
+       
+
+       psppire_data_store_set_dictionary(data_store,
+                                         the_dictionary);
+
+       psppire_case_array_clear(data_store->cases);
+
+
+       psppire_set_window_title(basename(filename));
+
+       g_free (filename);
+
+       const int ni = dict_get_next_value_idx(the_dictionary->dict);
+       if ( ni == 0 ) 
+         goto done;
+      
+       gint case_num;
+       for(case_num=0;;case_num++)
+         {
+           if (!psppire_case_array_add_case(the_cases, 
+                                    populate_case_from_reader, reader))
+             break;
+         }
+
+       sfm_close_reader(reader);
+       finished = TRUE;
+      }
+    else
+      {
+       finished = TRUE;
+      }
+  } while ( ! finished ) ;
+
+ done:
+  gtk_widget_destroy (dialog);
+}
+
+
+/* Re initialise HANDLE, by interrogating the user for a new file name */
+static void
+recreate_save_handle(struct file_handle **handle)
+{
+  GtkWidget *dialog;
+
+  GtkWidget *data_editor  = get_widget_assert(xml, "data_editor");
+
+  dialog = gtk_file_chooser_dialog_new (_("Save Data As"),
+                                       GTK_WINDOW(data_editor),
+                                       GTK_FILE_CHOOSER_ACTION_SAVE,
+                                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                                       GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+                                       NULL);
+
+  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
+    {
+      char *filename = gtk_file_chooser_get_filename
+       (GTK_FILE_CHOOSER (dialog));
+
+#if 0
+      if ( *handle ) 
+       destroy_file_handle(*handle, 0);
+#endif
+      *handle = fh_create_file (handle_name, filename, fh_default_properties());
+
+      psppire_set_window_title(basename(filename));
+
+      g_free (filename);
+    }
+
+  gtk_widget_destroy (dialog);
+}
+
+void
+on_save1_activate                      (GtkMenuItem     *menuitem,
+                                        gpointer         user_data)
+{
+  if ( ! psppire_handle ) 
+    recreate_save_handle(&psppire_handle);
+  
+  GtkSheet *data_sheet = GTK_SHEET(get_widget_assert(xml, "data_sheet"));
+  PsppireDataStore *data_store = PSPPIRE_DATA_STORE(gtk_sheet_get_model(data_sheet));
+  
+  if ( psppire_handle ) 
+    psppire_data_store_create_system_file(data_store,
+                                      psppire_handle);
+}
+
+
+void
+on_save_as1_activate                   (GtkMenuItem     *menuitem,
+                                        gpointer         user_data)
+{
+  recreate_save_handle(&psppire_handle);
+  if ( ! psppire_handle ) 
+    return ;
+
+  GtkSheet *data_sheet = GTK_SHEET(get_widget_assert(xml, "data_sheet"));
+  PsppireDataStore *data_store = PSPPIRE_DATA_STORE(gtk_sheet_get_model(data_sheet));
+
+  if ( psppire_handle ) 
+    psppire_data_store_create_system_file(data_store,
+                                      psppire_handle);
+}
+
+
+void
+on_quit1_activate                      (GtkMenuItem     *menuitem,
+                                        gpointer         user_data)
+{
+  gtk_main_quit();
+}
+
+
+void
+on_cut1_activate                       (GtkMenuItem     *menuitem,
+                                        gpointer         user_data)
+{
+
+}
+
+
+void
+on_copy1_activate                      (GtkMenuItem     *menuitem,
+                                        gpointer         user_data)
+{
+
+}
+
+
+void
+on_paste1_activate                     (GtkMenuItem     *menuitem,
+                                        gpointer         user_data)
+{
+
+}
+
+
+void
+on_insert1_activate                    (GtkMenuItem     *menuitem,
+                                        gpointer         user_data)
+{
+  GtkNotebook *notebook = GTK_NOTEBOOK(get_widget_assert(xml, "notebook1"));
+  gint page = -1;
+
+  page = gtk_notebook_get_current_page(notebook);
+
+  switch (page) 
+    {
+    case PAGE_DATA_SHEET:
+      {
+       GtkSheet *data_sheet = GTK_SHEET(get_widget_assert(xml, "data_sheet"));
+       PsppireDataStore *data_store = 
+         PSPPIRE_DATA_STORE(gtk_sheet_get_model(data_sheet));
+
+       psppire_case_array_insert_case(data_store->cases, data_sheet->range.row0);
+      }
+      break;
+    case PAGE_VAR_SHEET:
+      {
+       GtkSheet *var_sheet = 
+         GTK_SHEET(get_widget_assert(xml, "variable_sheet"));
+
+       PsppireVarStore *var_store = 
+         PSPPIRE_VAR_STORE(gtk_sheet_get_model(var_sheet));
+
+       psppire_dict_insert_variable(var_store->dict, var_sheet->range.row0, 0);
+      }
+      break;
+    }
+}
+
+void
+on_delete1_activate                    (GtkMenuItem     *menuitem,
+                                        gpointer         user_data)
+{
+  gint page = -1;
+  GtkWidget *notebook = get_widget_assert(xml, "notebook1");
+
+  page = gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook));
+  switch ( page) 
+    {
+    case PAGE_DATA_SHEET:
+      {
+       GtkSheet *data_sheet = GTK_SHEET(get_widget_assert(xml, "data_sheet"));
+       PsppireDataStore *data_store = 
+         PSPPIRE_DATA_STORE(gtk_sheet_get_model(data_sheet));
+
+       psppire_case_array_delete_cases(data_store->cases, 
+                                   data_sheet->range.row0, 
+                                   1 + data_sheet->range.rowi 
+                                   - data_sheet->range.row0  );
+      }
+      break;
+    case PAGE_VAR_SHEET:
+      {
+       GtkSheet *var_sheet = 
+         GTK_SHEET(get_widget_assert(xml, "variable_sheet"));
+
+       PsppireVarStore *var_store = 
+         PSPPIRE_VAR_STORE(gtk_sheet_get_model(var_sheet));
+
+       psppire_dict_delete_variables(var_store->dict, 
+                                  var_sheet->range.row0,
+                                  1 + var_sheet->range.rowi 
+                                  - var_sheet->range.row0  );
+      }
+      break;
+    }
+}
+
+
+void
+on_about1_activate(GtkMenuItem     *menuitem,
+                  gpointer         user_data)
+{
+  GtkWidget *about =  get_widget_assert(xml, "aboutdialog1");
+  
+  
+  GdkPixbuf *pb  = gdk_pixbuf_new_from_file_at_size( "pspplogo.png", 64, 64, 0);
+
+  gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(about), pb);
+
+  gtk_widget_show(about);
+
+  gtk_window_set_transient_for(GTK_WINDOW(about), 
+                               GTK_WINDOW(get_widget_assert(xml, "data_editor")));
+}
+
+
+
+void
+on_toolbars1_activate
+                     (GtkMenuItem     *menuitem,
+                                        gpointer         user_data)
+{
+
+
+}
+
+void
+on_value_labels1_activate(GtkCheckMenuItem     *menuitem,
+                         gpointer         user_data)
+{
+  GtkSheet *data_sheet = GTK_SHEET(get_widget_assert(xml, "data_sheet"));
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE(gtk_sheet_get_model(data_sheet));
+               
+  psppire_data_store_show_labels(ds, 
+                             gtk_check_menu_item_get_active(menuitem));
+}
+
+void
+on_status_bar1_activate(GtkCheckMenuItem     *menuitem,
+ gpointer         user_data)
+{
+
+  if ( gtk_check_menu_item_get_active(menuitem) ) 
+    gtk_widget_show(get_widget_assert(xml, "statusbar1"));
+  else
+    gtk_widget_hide(get_widget_assert(xml, "statusbar1"));
+}
+
+void
+on_grid_lines1_activate(GtkCheckMenuItem     *menuitem,
+ gpointer         user_data)
+{
+
+  const bool grid_visible = gtk_check_menu_item_get_active(menuitem); 
+
+  gtk_sheet_show_grid(GTK_SHEET(get_widget_assert(xml, "variable_sheet")),
+                     grid_visible);
+
+  gtk_sheet_show_grid(GTK_SHEET(get_widget_assert(xml, "data_sheet")),
+                     grid_visible);
+}
+
+
+void
+on_fonts1_activate(GtkMenuItem     *menuitem,
+ gpointer         user_data)
+{
+  static GtkWidget *dialog = 0 ; 
+  if ( !dialog ) 
+    dialog   = gtk_font_selection_dialog_new(_("Font Selection"));
+
+  gtk_window_set_transient_for(GTK_WINDOW(dialog), 
+                               GTK_WINDOW(get_widget_assert(xml, "data_editor")));
+
+
+  if ( GTK_RESPONSE_OK == gtk_dialog_run(GTK_DIALOG(dialog)) ) 
+    {
+      GtkSheet *data_sheet = 
+       GTK_SHEET(get_widget_assert(xml, "data_sheet"));
+
+      GtkSheet *var_sheet = 
+       GTK_SHEET(get_widget_assert(xml, "variable_sheet"));
+
+      PsppireDataStore *ds = PSPPIRE_DATA_STORE(gtk_sheet_get_model(data_sheet));
+      PsppireVarStore *vs = PSPPIRE_VAR_STORE(gtk_sheet_get_model(var_sheet));
+
+      const gchar *font = gtk_font_selection_dialog_get_font_name 
+       (GTK_FONT_SELECTION_DIALOG(dialog));
+
+      PangoFontDescription* font_desc = 
+       pango_font_description_from_string(font);
+
+      psppire_var_store_set_font(vs, font_desc);
+      psppire_data_store_set_font(ds, font_desc);
+    }
+  
+  gtk_widget_hide(dialog);
+
+}
+
+
+static GtkWidget *menuitems[2];
+static GtkNotebook *notebook = 0;
+
+static void
+switch_menus(gint page)
+{
+  switch (page) 
+    {
+    case PAGE_VAR_SHEET:
+      gtk_widget_hide(menuitems[PAGE_VAR_SHEET]);
+      gtk_widget_show(menuitems[PAGE_DATA_SHEET]);
+      break;
+    case PAGE_DATA_SHEET:
+      gtk_widget_show(menuitems[PAGE_VAR_SHEET]);
+      gtk_widget_hide(menuitems[PAGE_DATA_SHEET]);
+      break;
+    default:
+      g_assert_not_reached();
+      break;
+    }
+}
+
+
+void
+select_sheet(gint page)
+{
+  gtk_notebook_set_current_page(notebook, page);
+  switch_menus(page);
+}
+
+
+
+static void
+data_var_select(GtkNotebook *notebook,
+               GtkNotebookPage *page,
+               guint page_num,
+               gpointer user_data)
+{
+  switch_menus(page_num);
+}
+
+static void
+var_data_selection_init()
+{
+  notebook = GTK_NOTEBOOK(get_widget_assert(xml, "notebook1"));
+  menuitems[PAGE_DATA_SHEET] = get_widget_assert(xml, "data1");
+  menuitems[PAGE_VAR_SHEET] = get_widget_assert(xml, "variables1");
+
+  gtk_notebook_set_current_page(notebook, PAGE_DATA_SHEET);
+  gtk_widget_hide(menuitems[PAGE_DATA_SHEET]);
+  gtk_widget_show(menuitems[PAGE_VAR_SHEET]);
+
+
+  g_signal_connect(G_OBJECT(notebook), "switch-page",
+                  G_CALLBACK(data_var_select), 0);
+
+}
+
+
+void
+on_data1_activate(GtkMenuItem     *menuitem,
+                 gpointer         user_data)
+{
+  select_sheet(PAGE_DATA_SHEET);
+}
+
+
+void
+on_variables1_activate(GtkMenuItem     *menuitem,
+                 gpointer         user_data)
+{
+  select_sheet(PAGE_VAR_SHEET);
+}
+
+
+/* Callback which occurs when gtk_main is entered */
+gboolean
+callbacks_on_init(gpointer data)
+{
+  psppire_set_window_title(untitled);
+
+  var_data_selection_init();
+
+  return FALSE;
+}
diff --git a/src/ui/gui/menu-actions.h b/src/ui/gui/menu-actions.h
new file mode 100644 (file)
index 0000000..2e4e86e
--- /dev/null
@@ -0,0 +1,89 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2004  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+#ifndef MENU_ACTIONS_H
+#define MENU_ACTIONS_H 
+
+#include <gtk/gtk.h>
+
+
+void
+on_new1_activate                       (GtkMenuItem     *menuitem,
+                                        gpointer         user_data);
+
+void
+on_open1_activate                      (GtkMenuItem     *menuitem,
+                                        gpointer         user_data);
+
+void
+on_save1_activate                      (GtkMenuItem     *menuitem,
+                                        gpointer         user_data);
+
+void
+on_save_as1_activate                   (GtkMenuItem     *menuitem,
+                                        gpointer         user_data);
+
+void
+on_quit1_activate                      (GtkMenuItem     *menuitem,
+                                        gpointer         user_data);
+
+void
+on_cut1_activate                       (GtkMenuItem     *menuitem,
+                                        gpointer         user_data);
+
+void
+on_copy1_activate                      (GtkMenuItem     *menuitem,
+                                        gpointer         user_data);
+
+void
+on_paste1_activate                     (GtkMenuItem     *menuitem,
+                                        gpointer         user_data);
+
+void
+on_delete1_activate                    (GtkMenuItem     *menuitem,
+                                        gpointer         user_data);
+
+void
+on_about1_activate                     (GtkMenuItem     *menuitem,
+                                        gpointer         user_data);
+
+GtkWidget*
+psppire_data_sheet_create (gchar *widget_name, gchar *string1, gchar *string2,
+                gint int1, gint int2);
+
+GtkWidget*
+psppire_data_sheet_create (gchar *widget_name, gchar *string1, gchar *string2,
+                gint int1, gint int2);
+
+GtkWidget*
+psppire_variable_sheet_create (gchar *widget_name, gchar *string1, gchar *string2,
+                gint int1, gint int2);
+
+
+gboolean callbacks_on_init(gpointer data) ;
+
+
+/* Switch between the VAR SHEET and the DATA SHEET */
+enum {PAGE_DATA_SHEET = 0, PAGE_VAR_SHEET};
+
+
+#endif
+
+void select_sheet(gint page);
diff --git a/src/ui/gui/message-dialog.c b/src/ui/gui/message-dialog.c
new file mode 100644 (file)
index 0000000..624b414
--- /dev/null
@@ -0,0 +1,167 @@
+/* 
+   PSPPIRE --- A Graphical User Interface for PSPP
+   Copyright (C) 2004,2005  Free Software Foundation
+   Written by John Darrington
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. 
+*/
+
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#include <config.h>
+#include "message.h"
+#include "message-dialog.h"
+
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include "helper.h"
+
+extern GladeXML *xml;
+
+#define _(A) A
+
+
+void 
+vmsg(int klass, const char *fmt, va_list args)
+{
+  gchar *msg = 0;
+  gchar *text = g_strdup_vprintf (fmt, args);
+
+  gint message_type;
+
+  switch (klass)
+    {
+    case SE:
+    case IE:
+    case DE:
+    case ME:
+      message_type = GTK_MESSAGE_ERROR;
+      break;
+    case SW:
+    case DW:
+    case MW:
+      message_type = GTK_MESSAGE_WARNING;
+      break;
+    case SM:
+    case IS:
+    case MM:
+    default:
+      message_type = GTK_MESSAGE_INFO;
+      break;
+    };
+  
+  switch (klass) 
+    {
+    case SE:
+    case SW:
+    case SM:
+      msg = g_strdup(_("Script Error"));
+      break;
+
+    case IE:
+    case IS:
+      msg = g_strdup(_("Installation Error"));
+      break;
+
+    case DE:
+    case DW:
+      msg = g_strdup(_("Data File Error"));
+      break;
+
+    case ME:
+    case MW:
+    case MM:
+    default:
+      msg = g_strdup(_("PSPP Error"));
+      break;
+    };
+  
+  GtkWindow *parent = GTK_WINDOW(get_widget_assert(xml, "data_editor"));
+
+  GtkWidget *dialog  =  gtk_message_dialog_new(parent,
+                                              GTK_DIALOG_MODAL,
+                                              message_type,
+                                              GTK_BUTTONS_CLOSE,
+                                              msg);
+
+  
+  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), text);
+
+  g_free(text);
+  g_free(msg);
+    
+  gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);
+
+  gtk_dialog_run(GTK_DIALOG(dialog));
+
+  gtk_widget_destroy (dialog);
+
+}
+
+
+void 
+msg(int klass, const char *fmt, ...)
+{
+  va_list ap;
+  va_start(ap, fmt);
+  vmsg(klass, fmt, ap);
+  va_end(ap);
+}
+
+
+void
+err_vmsg (const struct error *e, const char *format, va_list args)
+{
+  vmsg(e->class, format, args);
+}
+
+
+void 
+err_assert_fail(const char *expr, const char *file, int line)
+{
+  msg(ME, "Assertion failed: %s:%d; (%s)\n",file,line,expr);
+}
+
+/* The GUI is always interactive.
+   So this function does nothing */
+void 
+err_cond_fail(void)
+{
+}
+
+
+void
+err_failure(void)
+{
+  msg(ME, _("Terminating NOW due to fatal error"));
+  gtk_main_quit();
+}
+
+
+/* FIXME: This is a stub .
+ * A temporary workaround until getl.c is rearranged
+ */
+void
+err_location (struct file_locator *f)
+{
+       f->filename = 0;
+       f->line_number = -1;
+}
+
diff --git a/src/ui/gui/message-dialog.h b/src/ui/gui/message-dialog.h
new file mode 100644 (file)
index 0000000..8a17e8b
--- /dev/null
@@ -0,0 +1,31 @@
+/* 
+   PSPPIRE --- A Graphical User Interface for PSPP
+   Copyright (C) 2004,2005  Free Software Foundation
+   Written by John Darrington
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. 
+*/
+
+#ifndef ERROR_DIALOG_H
+#define ERROR_DIALOG_H
+
+#include "message.h"
+
+void vmsg(int klass, const char *fmt, va_list args);
+void  msg(int klass, const char *fmt, ...);
+
+
+#endif
diff --git a/src/ui/gui/missing-val-dialog.c b/src/ui/gui/missing-val-dialog.c
new file mode 100644 (file)
index 0000000..74bc675
--- /dev/null
@@ -0,0 +1,338 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2005, 2006  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+/*  This module describes the behaviour of the Missing Values dialog box,
+    used for input of the missing values in the variable sheet */
+
+#include "helper.h"
+#include "missing-val-dialog.h"
+#include "missing-values.h"
+#include "variable.h"
+#include "data-in.h"
+#include "psppire-variable.h"
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include <string.h>
+
+#define _(A) A
+
+/* A simple (sub) dialog box for displaying user input errors */
+static void
+err_dialog(const gchar *msg, GtkWindow *window)
+{
+  GtkWidget *label = gtk_label_new (msg);
+
+  GtkWidget *dialog = 
+    gtk_dialog_new_with_buttons ("PSPP",
+                                window,
+                                GTK_DIALOG_MODAL | 
+                                GTK_DIALOG_DESTROY_WITH_PARENT | 
+                                GTK_DIALOG_NO_SEPARATOR,
+                                GTK_STOCK_OK,
+                                GTK_RESPONSE_ACCEPT,
+                                NULL);
+
+
+  GtkWidget *icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_ERROR,
+                                            GTK_ICON_SIZE_DIALOG);
+   
+  g_signal_connect_swapped (dialog,
+                           "response", 
+                           G_CALLBACK (gtk_widget_destroy),
+                           dialog);
+
+  GtkWidget *hbox = gtk_hbox_new(FALSE, 10);
+
+  gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox),
+                    hbox);
+
+  gtk_box_pack_start(GTK_BOX(hbox), icon, TRUE, FALSE, 10);
+  gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 10);
+
+  gtk_widget_show_all (dialog);
+}
+
+
+/* Callback which occurs when the OK button is clicked */
+static void 
+missing_val_dialog_accept(GtkWidget *w, gpointer data)
+{
+  struct missing_val_dialog *dialog = data;
+
+  const struct fmt_spec *write_spec = psppire_variable_get_write_spec(dialog->pv);
+  
+  if ( gtk_toggle_button_get_active(dialog->button_discrete))
+    {
+      gint nvals = 0;
+      gint badvals = 0;
+      gint i;
+      mv_set_type(&dialog->mvl, MV_NONE);
+      for(i = 0 ; i < 3 ; ++i ) 
+       {
+         gchar *text = 
+           g_strdup(gtk_entry_get_text(GTK_ENTRY(dialog->mv[i])));
+
+         union value v;
+         if ( !text || strlen(g_strstrip(text)) == 0 )
+           {
+             g_free(text);
+             continue;
+           }
+
+         if ( text_to_value(text, &v, *write_spec))
+           {
+             nvals++;
+             mv_add_value (&dialog->mvl, &v);
+           }
+         else 
+             badvals++;
+         g_free(text);
+       }
+      if ( nvals == 0 || badvals > 0 ) 
+       {
+         err_dialog(_("Incorrect value for variable type"), 
+                    GTK_WINDOW(dialog->window));
+         return ;
+       }
+    }
+  
+  if (gtk_toggle_button_get_active(dialog->button_range))
+    {
+      
+      union value low_val ; 
+      union value high_val;
+      const gchar *low_text = gtk_entry_get_text(GTK_ENTRY(dialog->low));
+      const gchar *high_text = gtk_entry_get_text(GTK_ENTRY(dialog->high));
+
+      if ( text_to_value(low_text, &low_val, *write_spec)
+          && 
+          text_to_value(high_text, &high_val, *write_spec) ) 
+       {
+         if ( low_val.f > high_val.f ) 
+           {
+             err_dialog(_("Incorrect range specification"),
+                         GTK_WINDOW(dialog->window));
+             return ;
+           }
+       }
+      else
+       {
+         err_dialog(_("Incorrect range specification"),
+                     GTK_WINDOW(dialog->window));
+         return;
+       }
+
+      gchar *discrete_text = 
+       g_strdup(gtk_entry_get_text(GTK_ENTRY(dialog->discrete)));
+
+
+      mv_set_type(&dialog->mvl, MV_NONE);
+      mv_add_num_range(&dialog->mvl, low_val.f, high_val.f);
+      
+      if ( discrete_text && strlen(g_strstrip(discrete_text)) > 0 )
+       {
+         union value discrete_val;
+         if ( !text_to_value(discrete_text, &discrete_val, 
+                             *write_spec))
+           {
+             err_dialog(_("Incorrect value for variable type"),
+                        GTK_WINDOW(dialog->window) );
+             g_free(discrete_text);
+             return;
+           }
+         mv_add_value(&dialog->mvl, &discrete_val);
+       }
+      g_free(discrete_text);
+    }
+
+  
+  if (gtk_toggle_button_get_active(dialog->button_none))
+    mv_set_type(&dialog->mvl, MV_NONE);
+
+  psppire_variable_set_missing(dialog->pv, &dialog->mvl);
+
+  gtk_widget_hide(dialog->window);
+}
+
+
+/* Callback which occurs when the 'discrete' radiobutton is toggled */
+static void 
+discrete(GtkToggleButton *button, gpointer data)
+{
+  gint i;
+  struct missing_val_dialog *dialog = data;
+
+  for(i = 0 ; i < 3 ; ++i ) 
+    {
+      gtk_widget_set_sensitive(dialog->mv[i], 
+                              gtk_toggle_button_get_active(button));
+    }
+}
+
+/* Callback which occurs when the 'range' radiobutton is toggled */
+static void 
+range(GtkToggleButton *button, gpointer data)
+{
+  struct missing_val_dialog *dialog = data;
+  
+  const gboolean active = gtk_toggle_button_get_active (button);
+
+  gtk_widget_set_sensitive(dialog->low, active);      
+  gtk_widget_set_sensitive(dialog->high, active);      
+  gtk_widget_set_sensitive(dialog->discrete, active);   
+}
+
+
+/* Creates the dialog structure from the xml */
+struct missing_val_dialog * 
+missing_val_dialog_create(GladeXML *xml)
+{
+  struct missing_val_dialog *dialog = g_malloc(sizeof(*dialog));
+
+  dialog->window = get_widget_assert(xml, "missing_values_dialog");
+
+  gtk_window_set_transient_for
+    (GTK_WINDOW(dialog->window), 
+     GTK_WINDOW(get_widget_assert(xml, "data_editor")));
+
+
+  g_signal_connect_swapped(get_widget_assert(xml, "missing_val_cancel"),
+                  "clicked", G_CALLBACK(gtk_widget_hide), dialog->window);
+
+  g_signal_connect(get_widget_assert(xml, "missing_val_ok"),
+                  "clicked", G_CALLBACK(missing_val_dialog_accept), dialog);
+
+
+  dialog->mv[0] = get_widget_assert(xml, "mv0");
+  dialog->mv[1] = get_widget_assert(xml, "mv1");
+  dialog->mv[2] = get_widget_assert(xml, "mv2");
+
+  dialog->low = get_widget_assert(xml, "mv-low");
+  dialog->high = get_widget_assert(xml, "mv-high");
+  dialog->discrete = get_widget_assert(xml, "mv-discrete");
+  
+
+  dialog->button_none     =  
+    GTK_TOGGLE_BUTTON(get_widget_assert(xml, "no_missing"));
+
+  dialog->button_discrete =  
+    GTK_TOGGLE_BUTTON(get_widget_assert(xml, "discrete_missing"));
+
+  dialog->button_range    =  
+    GTK_TOGGLE_BUTTON(get_widget_assert(xml, "range_missing"));
+
+
+  g_signal_connect(G_OBJECT(dialog->button_discrete), "toggled", 
+                  G_CALLBACK(discrete), dialog);
+
+  g_signal_connect(G_OBJECT(dialog->button_range), "toggled", 
+                  G_CALLBACK(range), dialog);
+
+  return dialog;
+}
+
+/* Shows the dialog box and sets default values */
+void 
+missing_val_dialog_show(struct missing_val_dialog *dialog)
+{
+  gint i;
+  g_return_if_fail(dialog);
+  g_return_if_fail(dialog->pv);
+
+  mv_copy (&dialog->mvl, psppire_variable_get_missing(dialog->pv));
+
+  const struct fmt_spec *write_spec = psppire_variable_get_write_spec(dialog->pv);
+
+  /* Blank all entry boxes and make them insensitive */
+  gtk_entry_set_text(GTK_ENTRY(dialog->low), "");
+  gtk_entry_set_text(GTK_ENTRY(dialog->high), "");
+  gtk_entry_set_text(GTK_ENTRY(dialog->discrete), "");   
+  gtk_widget_set_sensitive(dialog->low, FALSE);      
+  gtk_widget_set_sensitive(dialog->high, FALSE);      
+  gtk_widget_set_sensitive(dialog->discrete, FALSE);   
+
+  gtk_widget_set_sensitive(GTK_WIDGET(dialog->button_range), 
+                          psppire_variable_get_type(dialog->pv) == NUMERIC);
+
+  for(i = 0 ; i < 3 ; ++i ) 
+    {
+      gtk_entry_set_text(GTK_ENTRY(dialog->mv[i]), "");          
+      gtk_widget_set_sensitive(dialog->mv[i], FALSE);
+    }
+
+  if ( mv_has_range (&dialog->mvl))
+    {
+      union value low, high;
+      gchar *low_text;
+      gchar *high_text;
+      mv_peek_range(&dialog->mvl, &low.f, &high.f);
+
+      low_text = value_to_text(low, *write_spec);
+      high_text = value_to_text(high, *write_spec);
+      
+      gtk_entry_set_text(GTK_ENTRY(dialog->low), low_text);
+      gtk_entry_set_text(GTK_ENTRY(dialog->high), high_text);
+      g_free(low_text);
+      g_free(high_text);
+
+      if ( mv_has_value(&dialog->mvl))
+       {
+         gchar *text;
+         union value value;
+         mv_peek_value(&dialog->mvl, &value, 0);
+         text = value_to_text(value, *write_spec);
+         gtk_entry_set_text(GTK_ENTRY(dialog->discrete), text);
+         g_free(text);
+       }
+      
+      gtk_toggle_button_set_active(dialog->button_range, TRUE);
+      gtk_widget_set_sensitive(dialog->low, TRUE);      
+      gtk_widget_set_sensitive(dialog->high, TRUE);      
+      gtk_widget_set_sensitive(dialog->discrete, TRUE);   
+
+    }
+  else if ( mv_has_value (&dialog->mvl))
+    {
+      const int n = mv_n_values (&dialog->mvl);
+
+      for(i = 0 ; i < 3 ; ++i ) 
+       {
+         if ( i < n)
+           {
+             union value value;
+
+             mv_peek_value(&dialog->mvl, &value, i);
+             gchar *text = value_to_text(value, *write_spec);
+             gtk_entry_set_text(GTK_ENTRY(dialog->mv[i]), text);
+             g_free(text);
+           }
+         gtk_widget_set_sensitive(dialog->mv[i], TRUE);
+       }
+      gtk_toggle_button_set_active(dialog->button_discrete, TRUE);
+    }
+  else if ( mv_is_empty (&dialog->mvl))
+    {
+      gtk_toggle_button_set_active(dialog->button_none, TRUE);
+    }
+
+  gtk_widget_show(dialog->window);
+}
diff --git a/src/ui/gui/missing-val-dialog.h b/src/ui/gui/missing-val-dialog.h
new file mode 100644 (file)
index 0000000..4129d07
--- /dev/null
@@ -0,0 +1,59 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2005  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+#ifndef __PSPPIRE_MISSING_VAL_DIALOG_H
+#define __PSPPIRE_MISSING_VAL_DIALOG_H
+
+/*  This module describes the behaviour of the Missing Values dialog box,
+    used for input of the missing values in the variable sheet */
+
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include "missing-values.h"
+
+struct missing_val_dialog
+{
+  GtkWidget *window;
+
+  /* The variable whose missing values are to be updated */
+  struct PsppireVariable *pv;
+  
+  /* local copy */
+  struct missing_values mvl;
+
+  /* Radio Buttons */
+  GtkToggleButton *button_none;
+  GtkToggleButton *button_discrete;
+  GtkToggleButton *button_range;
+
+  /* Entry boxes */
+  GtkWidget *mv[3];
+  GtkWidget *low;
+  GtkWidget *high;
+  GtkWidget *discrete;
+};
+
+struct missing_val_dialog * missing_val_dialog_create(GladeXML *xml);
+
+void missing_val_dialog_show(struct missing_val_dialog *dialog);
+
+#endif
diff --git a/src/ui/gui/psppicon.png b/src/ui/gui/psppicon.png
new file mode 100644 (file)
index 0000000..f8ed8bb
Binary files /dev/null and b/src/ui/gui/psppicon.png differ
diff --git a/src/ui/gui/psppire-case-array.c b/src/ui/gui/psppire-case-array.c
new file mode 100644 (file)
index 0000000..159b3cc
--- /dev/null
@@ -0,0 +1,305 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2004  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "psppire-object.h"
+#include "psppire-case-array.h"
+#include "gtkextra-marshal.h"
+
+#include "case.h"
+
+/* --- prototypes --- */
+static void psppire_case_array_class_init      (PsppireCaseArrayClass  *class);
+static void psppire_case_array_init    (PsppireCaseArray       *case_array);
+static void psppire_case_array_finalize        (GObject                *object);
+
+
+/* --- variables --- */
+static GObjectClass     *parent_class = NULL;
+
+enum  {CASE_CHANGED, 
+       CASE_INSERTED,
+       CASES_DELETED, 
+       n_SIGNALS};
+
+static guint signal[n_SIGNALS];
+
+
+/* --- functions --- */
+/**
+ * psppire_case_array_get_type:
+ * @returns: the type ID for accelerator groups.
+ */
+GType
+psppire_case_array_get_type (void)
+{
+  static GType object_type = 0;
+
+  if (!object_type)
+    {
+      static const GTypeInfo object_info = {
+       sizeof (PsppireCaseArrayClass),
+       (GBaseInitFunc) NULL,
+       (GBaseFinalizeFunc) NULL,
+       (GClassInitFunc) psppire_case_array_class_init,
+       NULL,   /* class_finalize */
+       NULL,   /* class_data */
+       sizeof (PsppireCaseArray),
+       0,      /* n_preallocs */
+       (GInstanceInitFunc) psppire_case_array_init,
+      };
+
+      object_type = g_type_register_static (G_TYPE_PSPPIRE_OBJECT, "PsppireCaseArray",
+                                           &object_info, 0);
+    }
+
+  return object_type;
+}
+
+
+static void
+psppire_case_array_class_init (PsppireCaseArrayClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  parent_class = g_type_class_peek_parent (class);
+
+  object_class->finalize = psppire_case_array_finalize;
+
+  signal[CASE_CHANGED] =
+    g_signal_new ("case_changed",
+                 G_TYPE_FROM_CLASS(class),
+                 G_SIGNAL_RUN_FIRST,
+                 0,
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__INT,
+                 G_TYPE_NONE, 
+                 1,
+                 G_TYPE_INT);
+
+
+  signal[CASE_INSERTED] =
+    g_signal_new ("case_inserted",
+                 G_TYPE_FROM_CLASS(class),
+                 G_SIGNAL_RUN_FIRST,
+                 0,
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__INT,
+                 G_TYPE_NONE, 
+                 1,
+                 G_TYPE_INT);
+
+
+  signal[CASES_DELETED] =
+    g_signal_new ("cases_deleted",
+                 G_TYPE_FROM_CLASS(class),
+                 G_SIGNAL_RUN_FIRST,
+                 0,
+                 NULL, NULL,
+                 gtkextra_VOID__INT_INT,
+                 G_TYPE_NONE, 
+                 2,
+                 G_TYPE_INT,
+                 G_TYPE_INT);
+}
+
+static void
+psppire_case_array_finalize (GObject *object)
+{
+  PsppireCaseArray *ca = PSPPIRE_CASE_ARRAY (object);
+  
+  gint i;
+  for (i = 0 ; i < ca->size; ++i ) 
+    case_destroy(&ca->cases[i]);
+
+  g_free (ca->cases);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+psppire_case_array_init (PsppireCaseArray *ca)
+{
+  ca->cases = 0;
+  ca->size = 0;
+}
+
+/**
+ * psppire_case_array_new:
+ * @returns: a new #PsppireCaseArray object
+ * 
+ * Creates a new #PsppireCaseArray. 
+ */
+PsppireCaseArray*
+psppire_case_array_new (gint capacity, gint width)
+{
+  PsppireCaseArray *ca = g_object_new (G_TYPE_PSPPIRE_CASE_ARRAY, NULL);
+
+  ca->capacity = capacity;
+  ca->width = width;
+  
+  ca->cases = g_new0(struct ccase, capacity);
+
+  return ca;
+}
+
+
+void
+psppire_case_array_resize(PsppireCaseArray *ca,  gint new_size)
+{                     
+  gint c;
+
+  for (c = 0 ; c < ca->size ; ++c ) 
+    case_resize(&ca->cases[c], ca->width, new_size);
+  
+  ca->width = new_size;
+}
+
+/* FIXME: add_case and insert_case need to be merged/refactored */
+gboolean
+psppire_case_array_add_case(PsppireCaseArray *ca, 
+                        psppire_case_array_fill_case_func fill_case_func,
+                        gpointer aux)
+{
+  g_return_val_if_fail(ca->size < ca->capacity, FALSE);
+
+  case_create(&ca->cases[ca->size], ca->width);
+
+  if ( !fill_case_func(&ca->cases[ca->size], aux))
+    return FALSE;
+
+  ca->size++;
+
+  g_signal_emit(ca, signal[CASE_INSERTED], 0, ca->size - 1);  
+
+  return TRUE;
+}
+
+
+gboolean
+psppire_case_array_iterate_case(PsppireCaseArray *ca, 
+                        psppire_case_array_use_case_func use_case_func,
+                        gpointer aux)
+{
+  gint i;
+  g_return_val_if_fail(ca->size < ca->capacity, FALSE);
+
+  for (i = 0 ; i < ca->size ; ++i ) 
+    {
+      if ( !use_case_func(&ca->cases[i], aux))
+       return FALSE;
+    }
+
+  return TRUE;
+}
+
+
+void
+psppire_case_array_insert_case(PsppireCaseArray *ca, gint posn)
+{
+  g_return_if_fail(posn >= 0);
+  g_return_if_fail(posn <= ca->size);
+
+  g_assert(ca->size + 1 <= ca->capacity);
+
+  gint i;
+
+  for(i = ca->size; i > posn ; --i)
+      case_move(&ca->cases[i], &ca->cases[i - 1]);
+
+  case_create(&ca->cases[posn], ca->width);
+
+  ca->size++;
+  g_signal_emit(ca, signal[CASE_INSERTED], 0, posn);
+}
+
+void
+psppire_case_array_delete_cases(PsppireCaseArray *ca, gint first, gint n_cases)
+{
+  g_return_if_fail(n_cases > 0);
+  g_return_if_fail(first >= 0);
+  g_return_if_fail(first + n_cases < ca->size);
+  
+  gint i;
+
+  /* FIXME: Is this right ?? */
+  for ( i = first; i < first + n_cases ; ++i ) 
+    case_destroy(&ca->cases[i]);
+
+  for ( ; i < ca->size; ++i ) 
+    case_move(&ca->cases[i - n_cases], &ca->cases[i]);
+
+  ca->size -= n_cases;
+  g_signal_emit(ca, signal[CASES_DELETED], 0, first, n_cases);  
+}
+
+
+gint
+psppire_case_array_get_n_cases(const PsppireCaseArray *ca)
+{
+  return ca->size;
+}
+
+/* Clears the contents of CA */
+void 
+psppire_case_array_clear(PsppireCaseArray *ca)
+{
+  gint c;
+  for (c = 0 ; c < ca->size ; ++c ) 
+    case_destroy(&ca->cases[c]);
+
+  ca->size = 0;
+
+  g_signal_emit(ca, signal[CASES_DELETED], 0, 0, c);  
+}
+
+/* Return the IDXth value from case C */
+const union value *
+psppire_case_array_get_value(const PsppireCaseArray *ca, gint c, gint idx)
+{
+  g_return_val_if_fail(c < ca->size, NULL);
+
+  return case_data(&ca->cases[c], idx);
+}
+
+
+/* Set the IDXth value of case C using FF and DATA */
+void
+psppire_case_array_set_value(PsppireCaseArray *ca, gint c, gint idx,
+                         value_fill_func_t ff,
+                         gpointer data)
+{
+  g_return_if_fail(c < ca->size);
+
+  struct ccase *cc = &ca->cases[c];
+
+  union value *val = case_data_rw(cc, idx);
+
+  gboolean changed = ff(val, data);
+
+  case_unshare(cc);
+
+  if ( changed ) 
+    g_signal_emit(ca, signal[CASE_CHANGED], 0, c);
+  
+}
diff --git a/src/ui/gui/psppire-case-array.h b/src/ui/gui/psppire-case-array.h
new file mode 100644 (file)
index 0000000..a405b41
--- /dev/null
@@ -0,0 +1,115 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2004  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+
+#ifndef __CASE_ARRAY_H__
+#define __CASE_ARRAY_H__
+
+
+#include <glib-object.h>
+#include <glib.h>
+
+
+
+G_BEGIN_DECLS
+
+
+/* --- type macros --- */
+#define G_TYPE_PSPPIRE_CASE_ARRAY              (psppire_case_array_get_type ())
+#define PSPPIRE_CASE_ARRAY(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), G_TYPE_PSPPIRE_CASE_ARRAY, PsppireCaseArray))
+#define PSPPIRE_CASE_ARRAY_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), G_TYPE_PSPPIRE_CASE_ARRAY, PsppireCaseArrayClass))
+#define G_IS_PSPPIRE_CASE_ARRAY(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), G_TYPE_PSPPIRE_CASE_ARRAY))
+#define G_IS_PSPPIRE_CASE_ARRAY_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), G_TYPE_PSPPIRE_CASE_ARRAY))
+#define PSPPIRE_CASE_ARRAY_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_PSPPIRE_CASE_ARRAY, PsppireCaseArrayClass))
+
+
+
+
+/* --- typedefs & structures --- */
+typedef struct _PsppireCaseArray          PsppireCaseArray;
+typedef struct _PsppireCaseArrayClass PsppireCaseArrayClass;
+
+struct ccase;
+
+struct _PsppireCaseArray
+{
+  GObject             parent;
+
+  struct ccase *cases;
+  gint capacity;
+  gint width;
+  gint size;
+};
+
+
+struct _PsppireCaseArrayClass
+{
+  GObjectClass parent_class;
+};
+
+
+/* -- PsppireCaseArray --- */
+GType          psppire_case_array_get_type (void);
+
+PsppireCaseArray *psppire_case_array_new (gint capacity, gint width);
+
+void psppire_case_array_resize(PsppireCaseArray *ca,  gint new_size);
+
+void psppire_case_array_insert_case(PsppireCaseArray *ca, gint posn);
+
+void psppire_case_array_delete_cases(PsppireCaseArray *ca, gint first, gint n_cases);
+
+
+typedef gboolean psppire_case_array_fill_case_func(struct ccase *, gpointer aux);
+
+typedef gboolean psppire_case_array_use_case_func(const struct ccase *, gpointer aux);
+
+
+gboolean psppire_case_array_add_case(PsppireCaseArray *ca, 
+                                 psppire_case_array_fill_case_func fill_case_func,
+                                 gpointer aux);
+
+
+gboolean psppire_case_array_iterate_case(PsppireCaseArray *ca, 
+                                 psppire_case_array_use_case_func fill_case_func,
+                                 gpointer aux);
+
+
+
+gint psppire_case_array_get_n_cases(const PsppireCaseArray *ca);
+
+
+/* Clears the contents of CA */
+void psppire_case_array_clear(PsppireCaseArray *ca);
+
+
+const union value * psppire_case_array_get_value(const PsppireCaseArray *ca, 
+                                             gint c, gint idx);
+
+
+typedef gboolean value_fill_func_t(union value *v, gpointer data);
+
+void psppire_case_array_set_value(PsppireCaseArray *ca, gint c, gint idx,
+                              value_fill_func_t ff,
+                              gpointer data);
+
+G_END_DECLS
+
+#endif /* __PSPPIRE_CASE_ARRAY_H__ */
diff --git a/src/ui/gui/psppire-data-store.c b/src/ui/gui/psppire-data-store.c
new file mode 100644 (file)
index 0000000..aa11859
--- /dev/null
@@ -0,0 +1,675 @@
+/* psppire-data-store.c
+   PSPPIRE --- A Graphical User Interface for PSPP
+   Copyright (C) 2006  Free Software Foundation
+   Written by John Darrington
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "gtksheet.h"
+#include "gsheetmodel.h"
+#include "gsheet-column-iface.h"
+
+#include "psppire-variable.h"
+#include "psppire-data-store.h"
+
+#include "dictionary.h"
+#include "missing-values.h"
+#include "value-labels.h"
+#include "data-in.h"
+
+#include "file-handle-def.h"
+#include "sys-file-writer.h"
+
+#define _(A) A
+#define N_(A) A
+
+
+static void psppire_data_store_init            (PsppireDataStore      *data_store);
+static void psppire_data_store_class_init      (PsppireDataStoreClass *class);
+static void psppire_data_store_sheet_model_init (GSheetModelIface *iface);
+static void psppire_data_store_sheet_column_init (GSheetColumnIface *iface);
+static void psppire_data_store_finalize        (GObject           *object);
+
+static const gchar *const psppire_data_store_get_string(GSheetModel *sheet_model, gint row, gint column);
+
+static gboolean psppire_data_store_set_string(GSheetModel *model, 
+                                         const gchar *text, gint row, gint column);
+
+static gboolean psppire_data_store_clear_datum(GSheetModel *model, 
+                                         gint row, gint column);
+
+
+#define MIN_COLUMNS 10
+
+#define max(A,B) ((A>B)?A:B)
+
+static GObjectClass *parent_class = NULL;
+
+inline GType
+psppire_data_store_get_type (void)
+{
+  static GType data_store_type = 0;
+
+  if (!data_store_type)
+    {
+      static const GTypeInfo data_store_info =
+      {
+       sizeof (PsppireDataStoreClass),
+       NULL,           /* base_init */
+       NULL,           /* base_finalize */
+        (GClassInitFunc) psppire_data_store_class_init,
+       NULL,           /* class_finalize */
+       NULL,           /* class_data */
+        sizeof (PsppireDataStore),
+       0,
+        (GInstanceInitFunc) psppire_data_store_init,
+      };
+
+      static const GInterfaceInfo sheet_model_info =
+      {
+       (GInterfaceInitFunc) psppire_data_store_sheet_model_init,
+       NULL,
+       NULL
+      };
+
+      static const GInterfaceInfo sheet_column_info =
+      {
+       (GInterfaceInitFunc) psppire_data_store_sheet_column_init,
+       NULL,
+       NULL
+      };
+
+
+
+      data_store_type = g_type_register_static (G_TYPE_OBJECT, "PsppireDataStore",
+                                               &data_store_info, 0);
+
+      g_type_add_interface_static (data_store_type,
+                                  G_TYPE_SHEET_MODEL,
+                                  &sheet_model_info);
+
+      g_type_add_interface_static (data_store_type,
+                                  G_TYPE_SHEET_COLUMN,
+                                  &sheet_column_info);
+
+    }
+
+  return data_store_type;
+}
+
+static void
+psppire_data_store_class_init (PsppireDataStoreClass *class)
+{
+  GObjectClass *object_class;
+
+  parent_class = g_type_class_peek_parent (class);
+  object_class = (GObjectClass*) class;
+
+  object_class->finalize = psppire_data_store_finalize;
+}
+
+
+static void
+psppire_data_store_init (PsppireDataStore *data_store)
+{
+  data_store->dict = 0;
+  data_store->cases = 0;
+}
+
+const PangoFontDescription *
+psppire_data_store_get_font_desc(GSheetModel *model,
+                             gint row, gint column)
+{
+  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
+  
+  return store->font_desc;
+}
+
+
+static void
+psppire_data_store_sheet_model_init (GSheetModelIface *iface)
+{
+  iface->get_string = psppire_data_store_get_string;
+  iface->set_string = psppire_data_store_set_string;
+  iface->clear_datum = psppire_data_store_clear_datum;
+  iface->is_editable = NULL;
+  iface->is_visible = NULL;
+  iface->get_foreground = NULL;
+  iface->get_background = NULL;
+  iface->get_font_desc = psppire_data_store_get_font_desc;
+  iface->get_cell_border = NULL;
+}
+
+static
+gboolean always_true()
+{
+  return TRUE;
+}
+
+
+
+static void
+delete_cases_callback(GtkWidget *w, gint first, gint n_cases, gpointer data)
+{
+  g_return_if_fail (data);
+
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE(data);
+
+  g_assert(first >= 0);
+
+  g_sheet_model_rows_deleted (G_SHEET_MODEL(store), first, n_cases);
+}
+
+
+static void
+insert_case_callback(GtkWidget *w, gint casenum, gpointer data)
+{
+  g_return_if_fail (data);
+
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE(data);
+  
+  g_sheet_model_range_changed (G_SHEET_MODEL(store),
+                                casenum, -1,
+                                psppire_case_array_get_n_cases(store->cases),
+                                -1);
+}
+
+
+static void
+changed_case_callback(GtkWidget *w, gint casenum, gpointer data)
+{
+  g_return_if_fail (data);
+
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE(data);
+  
+
+  g_sheet_model_range_changed (G_SHEET_MODEL(store),
+                                casenum, -1,
+                                casenum, -1);
+
+}
+
+
+static void
+delete_variables_callback(GtkWidget *w, gint var_num, gint n_vars, gpointer data)
+{
+  g_return_if_fail (data);
+
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE(data);
+
+  g_sheet_column_columns_deleted(G_SHEET_COLUMN(store),
+                                  var_num, n_vars);
+}
+
+
+static void
+insert_variable_callback(GtkWidget *w, gint var_num, gpointer data)
+{
+  g_return_if_fail (data);
+
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE(data);
+  
+  /* 
+  g_sheet_model_range_changed (G_SHEET_MODEL(store),
+                                casenum, -1,
+                                psppire_case_array_get_n_cases(store->cases),
+                                -1);
+  */
+
+  psppire_case_array_resize(store->cases, 
+                        dict_get_next_value_idx (store->dict->dict));
+
+}
+
+
+
+
+/**
+ * psppire_data_store_new:
+ * @dict: The dictionary for this data_store.
+ *
+ *
+ * Return value: a new #PsppireDataStore
+ **/
+PsppireDataStore *
+psppire_data_store_new (PsppireDict *dict, PsppireCaseArray *cases)
+{
+  PsppireDataStore *retval;
+
+  retval = g_object_new (GTK_TYPE_DATA_STORE, NULL);
+
+  retval->cases = cases;
+  g_signal_connect(cases, "cases-deleted", G_CALLBACK(delete_cases_callback), 
+                  retval);
+
+  g_signal_connect(cases, "case-inserted", G_CALLBACK(insert_case_callback), 
+                  retval);
+
+
+  g_signal_connect(cases, "case-changed", G_CALLBACK(changed_case_callback), 
+                  retval);
+
+
+  psppire_data_store_set_dictionary(retval, dict);
+
+
+  return retval;
+}
+
+
+
+/**
+ * psppire_data_store_replace_set_dictionary:
+ * @data_store: The variable store
+ * @dict: The dictionary to set
+ *
+ * If a dictionary is already associated with the data-store, then it will be
+ * destroyed.
+ **/
+void
+psppire_data_store_set_dictionary(PsppireDataStore *data_store, PsppireDict *dict)
+{
+#if 0
+  if ( data_store->dict ) g_object_unref(data_store->dict);
+#endif
+
+  data_store->dict = dict;
+
+  psppire_case_array_resize(data_store->cases, 
+                        dict_get_next_value_idx (data_store->dict->dict));
+
+
+  g_signal_connect(dict, "variable-inserted", 
+                  G_CALLBACK(insert_variable_callback), 
+                  data_store);
+
+  g_signal_connect(dict, "variables-deleted", 
+                  G_CALLBACK(delete_variables_callback), 
+                  data_store);
+
+
+  /* The entire model has changed */
+  g_sheet_model_range_changed (G_SHEET_MODEL(data_store), -1, -1, -1, -1);
+}
+
+static void
+psppire_data_store_finalize (GObject *object)
+{
+
+  /* must chain up */
+  (* parent_class->finalize) (object);
+}
+
+
+static const gchar *const 
+psppire_data_store_get_string(GSheetModel *model, gint row, gint column)
+{
+  static gchar s[255];
+  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
+
+  g_return_val_if_fail(store->dict, NULL);
+  g_return_val_if_fail(store->cases, NULL);
+
+  if (column >= psppire_dict_get_var_cnt(store->dict))
+    return NULL;
+
+  if ( row >= psppire_case_array_get_n_cases(store->cases))
+    return NULL;
+
+  const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, column);
+
+
+  const union value *v = 
+    psppire_case_array_get_value(store->cases, row, 
+                             psppire_variable_get_index(pv));
+
+
+  if ( store->show_labels) 
+    {
+      const struct val_labs * vl = psppire_variable_get_value_labels(pv);
+
+      const gchar *label;
+      if ( (label = val_labs_find(vl, *v)) )
+       {
+         return label;
+       }
+    }
+
+  const struct fmt_spec *fp = psppire_variable_get_write_spec(pv);
+
+  if ( psppire_variable_get_type(pv) == NUMERIC ) 
+    {
+      /* Converts binary value V into printable form in the exactly
+        FP->W character in buffer S according to format specification
+        FP.  No null terminator is appended to the buffer.  */
+      data_out (s, fp, v);
+      s[fp->w] = '\0';
+    }
+  else
+    {
+      const gint len = psppire_variable_get_width(pv);
+      memcpy(s, v->s, len);
+      s[len] = '\0';
+    }
+
+  static gchar buf[255];
+  GError *err = NULL;
+  gchar *text = g_locale_to_utf8(s, fp->w, 0, 0, &err);
+  if ( !err ) 
+    { 
+      g_snprintf(buf, 255, text);
+      g_free(text);
+    }
+  else
+    {
+      g_warning("Cannot convert string \"%s\" to utf-8: %s\n", s, err->message);
+      g_error_free(err);
+      return NULL;
+    }
+
+  return buf ;
+}
+
+
+static gboolean
+set_null_string_value(union value *val, gpointer data)
+{
+  strcpy(val->s, "");
+  return TRUE;
+}
+
+static gboolean
+set_sysmis_value(union value *val, gpointer data)
+{
+  val->f = SYSMIS;
+  return TRUE;
+}
+
+
+static gboolean 
+psppire_data_store_clear_datum(GSheetModel *model, 
+                                         gint row, gint col)
+
+{
+  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
+
+  const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
+
+  const gint index = psppire_variable_get_index(pv) ;
+
+  if ( psppire_variable_get_type(pv) == NUMERIC) 
+    psppire_case_array_set_value(store->cases, row, index, set_sysmis_value,0);
+  else
+    psppire_case_array_set_value(store->cases, row, index, set_null_string_value,0);
+  return TRUE;
+}
+
+
+static gboolean
+fillit(union value *val, gpointer data)
+{
+  struct data_in *d_in = data;
+
+  d_in->v = val;
+
+  if ( ! data_in(d_in) ) 
+    {
+      g_warning("Cant encode string\n");
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+
+/* Attempts to update that part of the variable store which corresponds 
+   to ROW, COL with  the value TEXT.
+   Returns true if anything was updated, false otherwise.
+*/
+static gboolean 
+psppire_data_store_set_string(GSheetModel *model, 
+                         const gchar *text, gint row, gint col)
+{
+  gint r;
+  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
+
+  const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
+
+  for(r = psppire_case_array_get_n_cases(store->cases) ; r <= row ; ++r ) 
+    {
+      gint c;
+      psppire_case_array_insert_case(store->cases, r);
+
+      for (c = 0 ; c < psppire_dict_get_var_cnt(store->dict); ++c ) 
+       psppire_data_store_clear_datum(model, r, c);
+    }
+
+  const gint index = psppire_variable_get_index(pv);
+
+  struct data_in d_in;
+  d_in.s = text;
+  d_in.e = text + strlen(text);
+  d_in.v = 0;
+  d_in.f1 = d_in.f2 = 0;
+  d_in.format = * psppire_variable_get_write_spec(pv);
+  d_in.flags = 0;
+
+  psppire_case_array_set_value(store->cases, row, index, fillit, &d_in);
+
+  return TRUE;
+}
+
+
+void
+psppire_data_store_set_font(PsppireDataStore *store, PangoFontDescription *fd)
+{
+  g_return_if_fail (store);
+  g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
+
+  store->font_desc = fd;
+  g_sheet_model_range_changed (G_SHEET_MODEL(store),
+                                -1, -1, -1, -1);
+}
+
+
+void
+psppire_data_store_show_labels(PsppireDataStore *store, gboolean show_labels)
+{
+  g_return_if_fail (store);
+  g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
+
+  store->show_labels = show_labels;
+
+  g_sheet_model_range_changed (G_SHEET_MODEL(store),
+                                -1, -1, -1, -1);
+}
+
+
+
+static gboolean 
+write_case(const struct ccase *cc, 
+          gpointer aux)
+{
+  struct sfm_writer *writer = aux;
+
+  if ( ! sfm_write_case(writer, cc) )
+    return FALSE;
+
+
+  return TRUE;
+}
+
+void
+psppire_data_store_create_system_file(PsppireDataStore *store,
+                             struct file_handle *handle)
+{
+  g_assert(handle);
+
+  const struct sfm_write_options wo = {
+    true, /* writeable */
+    false, /* dont compress */
+    3 /* version */
+  }; 
+
+  struct sfm_writer *writer = sfm_open_writer(handle, store->dict->dict, wo);
+
+  if ( ! writer) 
+    return;
+
+  psppire_case_array_iterate_case(store->cases, write_case, writer);
+
+  sfm_close_writer(writer);
+}
+
+
+
+/* Column related funcs */
+
+static gint
+geometry_get_column_count(const GSheetColumn *geom)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+
+  return max(MIN_COLUMNS, psppire_dict_get_var_cnt(ds->dict));
+}
+
+/* Return the width that an  'M' character would occupy when typeset at
+   row, col */
+static guint 
+M_width(GtkSheet *sheet, gint row, gint col)
+{
+  GtkSheetCellAttr attributes;
+
+  gtk_sheet_get_attributes(sheet, row, col, &attributes);
+
+  PangoRectangle rect;
+  /* FIXME: make this a member of the data store */
+  static PangoLayout *layout = 0;
+
+  if (! layout ) 
+    layout = gtk_widget_create_pango_layout (GTK_WIDGET(sheet), "M");
+  
+  pango_layout_set_font_description (layout, 
+                                    attributes.font_desc);
+
+  pango_layout_get_extents (layout, NULL, &rect);
+
+#if 0
+  g_object_unref(G_OBJECT(layout));
+#endif
+
+  return PANGO_PIXELS(rect.width);
+}
+
+
+/* Return the number of pixels corresponding to a column of 
+   WIDTH characters */
+static inline guint 
+columnWidthToPixels(GtkSheet *sheet, gint column, guint width)
+{
+  return (M_width(sheet, 0, column) * width);
+}
+
+
+static gint
+geometry_get_width(const GSheetColumn *geom, gint unit, GtkSheet *sheet)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+
+  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
+    return 75;
+
+  /* FIXME: We can optimise this by caching the widths until they're resized */
+  const struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
+
+  return columnWidthToPixels(sheet, unit, psppire_variable_get_columns(pv));
+}
+
+
+
+
+static void
+geometry_set_width(GSheetColumn *geom, gint unit, gint width, GtkSheet *sheet)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+
+  struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
+
+  psppire_variable_set_columns(pv, width / M_width(sheet, 1, unit));
+}
+
+
+
+static GtkJustification
+geometry_get_justification(const GSheetColumn *geom, gint unit)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+
+
+  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
+    return GTK_JUSTIFY_LEFT;
+
+  const struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
+
+  /* Kludge: Happily GtkJustification is defined similarly
+     to enum alignment from pspp/variable.h */
+  return psppire_variable_get_alignment(pv);
+}
+
+
+static const gchar null_var_name[]=_("var");
+static const gchar *
+geometry_get_button_label(const GSheetColumn *geom, gint unit)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+
+  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
+    return null_var_name;
+
+  struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
+
+  return psppire_variable_get_name(pv);
+}
+
+
+static gboolean
+geometry_get_sensitivity(const GSheetColumn *geom, gint unit)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+
+
+  return (unit < psppire_dict_get_var_cnt(ds->dict));
+}
+
+
+static void
+psppire_data_store_sheet_column_init (GSheetColumnIface *iface)
+{
+  iface->get_column_count = geometry_get_column_count;
+  iface->get_width = geometry_get_width;
+  iface->set_width = geometry_set_width;
+  iface->get_visibility = always_true;
+  iface->get_sensitivity = geometry_get_sensitivity;
+  iface->get_justification = geometry_get_justification;
+
+  iface->get_button_label = geometry_get_button_label;
+}
diff --git a/src/ui/gui/psppire-data-store.h b/src/ui/gui/psppire-data-store.h
new file mode 100644 (file)
index 0000000..508f0cd
--- /dev/null
@@ -0,0 +1,101 @@
+/* psppire-data-store.h
+   PSPPIRE --- A Graphical User Interface for PSPP
+   Copyright (C) 2006  Free Software Foundation
+   Written by John Darrington
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef __PSPPIRE_DATA_STORE_H__
+#define __PSPPIRE_DATA_STORE_H__
+
+#include "gsheetmodel.h"
+#include "psppire-dict.h"
+#include "psppire-case-array.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define GTK_TYPE_DATA_STORE           (psppire_data_store_get_type ())
+
+#define PSPPIRE_DATA_STORE(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+                                                                   GTK_TYPE_DATA_STORE, PsppireDataStore))
+
+#define PSPPIRE_DATA_STORE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), \
+                                                                GTK_TYPE_DATA_STORE, \
+                                                                 PsppireDataStoreClass))
+
+#define PSPPIRE_IS_DATA_STORE(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_DATA_STORE))
+
+#define PSPPIRE_IS_DATA_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_DATA_STORE))
+
+#define PSPPIRE_DATA_STORE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+                                                                  GTK_TYPE_DATA_STORE, \
+                                                                  PsppireDataStoreClass))
+
+typedef struct _PsppireDataStore       PsppireDataStore;
+typedef struct _PsppireDataStoreClass  PsppireDataStoreClass;
+
+struct dictionary;
+
+struct _PsppireDataStore
+{
+  GObject parent;
+
+  /*< private >*/
+  PsppireDict *dict;
+  PsppireCaseArray *cases;
+  PangoFontDescription *font_desc;
+  gboolean show_labels;
+
+  /* Geometry */
+  gint margin_width;
+};
+
+struct _PsppireDataStoreClass
+{
+  GObjectClass parent_class;
+
+  /* Padding for future expansion */
+  void (*_gtk_reserved1) (void);
+  void (*_gtk_reserved2) (void);
+  void (*_gtk_reserved3) (void);
+  void (*_gtk_reserved4) (void);
+};
+
+
+inline GType psppire_data_store_get_type (void) G_GNUC_CONST;
+PsppireDataStore *psppire_data_store_new     (PsppireDict *dict, PsppireCaseArray *cases);
+
+void psppire_data_store_set_dictionary(PsppireDataStore *data_store, PsppireDict *dict);
+void psppire_data_store_set_font(PsppireDataStore *store, PangoFontDescription *fd);
+
+void psppire_data_store_show_labels(PsppireDataStore *store, gboolean show_labels);
+
+
+struct file_handle;
+
+void psppire_data_store_create_system_file(PsppireDataStore *store,
+                                  struct file_handle *handle);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __PSPPIRE_DATA_STORE_H__ */
diff --git a/src/ui/gui/psppire-dict.c b/src/ui/gui/psppire-dict.c
new file mode 100644 (file)
index 0000000..d5ea909
--- /dev/null
@@ -0,0 +1,433 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2004, 2006  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "gtkextra-marshal.c"
+
+#include "psppire-object.h"
+#include "psppire-dict.h"
+#include "format.h"
+#include "dictionary.h"
+#include "missing-values.h"
+#include "value-labels.h"
+
+
+#include "message-dialog.h"
+#include "psppire-variable.h"
+
+/* --- prototypes --- */
+static void psppire_dict_class_init    (PsppireDictClass       *class);
+static void psppire_dict_init  (PsppireDict            *dict);
+static void psppire_dict_finalize      (GObject                *object);
+
+
+/* --- variables --- */
+static GObjectClass     *parent_class = NULL;
+
+enum  {VARIABLE_CHANGED, 
+       VARIABLE_INSERTED,
+       VARIABLE_DELETED, 
+       VARIABLES_DELETED, 
+       n_SIGNALS};
+
+static guint signal[n_SIGNALS];
+
+#define CACHE_CHUNK 5
+
+/* --- functions --- */
+/**
+ * psppire_dict_get_type:
+ * @returns: the type ID for accelerator groups.
+ */
+GType
+psppire_dict_get_type (void)
+{
+  static GType object_type = 0;
+
+  if (!object_type)
+    {
+      static const GTypeInfo object_info = {
+       sizeof (PsppireDictClass),
+       (GBaseInitFunc) NULL,
+       (GBaseFinalizeFunc) NULL,
+       (GClassInitFunc) psppire_dict_class_init,
+       NULL,   /* class_finalize */
+       NULL,   /* class_data */
+       sizeof (PsppireDict),
+       0,      /* n_preallocs */
+       (GInstanceInitFunc) psppire_dict_init,
+      };
+
+      object_type = g_type_register_static (G_TYPE_PSPPIRE_OBJECT, "PsppireDict",
+                                           &object_info, 0);
+    }
+
+  return object_type;
+}
+
+
+static void
+psppire_dict_class_init (PsppireDictClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  parent_class = g_type_class_peek_parent (class);
+
+  object_class->finalize = psppire_dict_finalize;
+
+  signal[VARIABLE_CHANGED] =
+    g_signal_new ("variable_changed",
+                 G_TYPE_FROM_CLASS(class),
+                 G_SIGNAL_RUN_FIRST,
+                 0,
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__INT,
+                 G_TYPE_NONE, 
+                 1,
+                 G_TYPE_INT);
+
+
+
+  signal[VARIABLE_INSERTED] =
+    g_signal_new ("variable_inserted",
+                 G_TYPE_FROM_CLASS(class),
+                 G_SIGNAL_RUN_FIRST,
+                 0,
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__INT,
+                 G_TYPE_NONE, 
+                 1,
+                 G_TYPE_INT);
+
+  signal[VARIABLE_DELETED] =
+    g_signal_new ("variable_deleted",
+                 G_TYPE_FROM_CLASS(class),
+                 G_SIGNAL_RUN_FIRST,
+                 0,
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__INT,
+                 G_TYPE_NONE, 
+                 1,
+                 G_TYPE_INT);
+
+
+  signal[VARIABLES_DELETED] =
+    g_signal_new ("variables_deleted",
+                 G_TYPE_FROM_CLASS(class),
+                 G_SIGNAL_RUN_FIRST,
+                 0,
+                 NULL, NULL,
+                 gtkextra_VOID__INT_INT,
+                 G_TYPE_NONE, 
+                 2,
+                 G_TYPE_INT,
+                 G_TYPE_INT);
+
+}
+
+static void
+psppire_dict_finalize (GObject *object)
+{
+  gint v;
+  PsppireDict *d = PSPPIRE_DICT (object);
+  
+
+
+  for (v = 0 ; v < psppire_dict_get_var_cnt(d) ; ++v ) 
+    g_free(d->variables[v]);
+
+  g_free(d->variables);
+  d->cache_size = 0;
+
+  dict_destroy(d->dict);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+psppire_dict_init (PsppireDict *psppire_dict)
+{
+  psppire_dict->dict = dict_create();
+
+  psppire_dict->variables = 0; 
+  psppire_dict->cache_size = 0;
+}
+
+/**
+ * psppire_dict_new:
+ * @returns: a new #PsppireDict object
+ * 
+ * Creates a new #PsppireDict. 
+ */
+PsppireDict*
+psppire_dict_new (void)
+{
+  return g_object_new (G_TYPE_PSPPIRE_DICT, NULL);
+}
+
+
+/**
+ * psppire_dict_new_from_dict:
+ * @returns: a new #PsppireDict object
+ * 
+ * Creates a new #PsppireDict. 
+ */
+PsppireDict*
+psppire_dict_new_from_dict (struct dictionary *d)
+{
+  PsppireDict *new_dict = g_object_new (G_TYPE_PSPPIRE_DICT, NULL);
+  new_dict->dict = d;
+  new_dict->cache_size = dict_get_var_cnt(d);
+  new_dict->variables = g_malloc0(sizeof(struct PsppireVariable *) * 
+                                 new_dict->cache_size);
+
+
+  return new_dict;
+}
+
+
+/* Returns a valid name for a new variable in DICT.
+   The return value is statically allocated */
+static gchar * 
+auto_generate_var_name(PsppireDict *dict)
+{
+  gint d = 0;
+  static gchar name[255];
+
+
+  while (g_snprintf(name, 255, "VAR%05d",d++),
+        psppire_dict_lookup_var(dict, name))
+    ;
+
+  return name;
+}
+
+/* Insert a new variable at posn IDX, with the name NAME.
+   If NAME is null, then a name will be automatically assigned.
+ */
+void
+psppire_dict_insert_variable(PsppireDict *d, gint idx, const gchar *name)
+{
+  gint i;
+  g_return_if_fail(d);
+  g_return_if_fail(G_IS_PSPPIRE_DICT(d));
+
+
+  /* Invalidate the cache from IDX onwards */
+  for ( i = idx ; i < d->cache_size ; ++i ) 
+    {
+      g_free(d->variables[i]);
+      d->variables[i] = 0;
+    }
+
+  /* Ensure that the cache is large enough */
+  if ( dict_get_var_cnt(d->dict) >= d->cache_size ) 
+    {
+      d->variables = g_realloc(d->variables, sizeof(struct PsppireVariable*) * 
+                              (d->cache_size + CACHE_CHUNK));
+      d->cache_size += CACHE_CHUNK;
+    }
+
+  /* Zero the new pointers */
+  for ( ; i < d->cache_size ; ++i ) 
+    {
+      d->variables[i] = 0;
+    }
+
+  if ( ! name ) 
+    name = auto_generate_var_name(d);
+  
+  struct variable *var = 
+    dict_create_var(d->dict, name, 0);
+
+  dict_reorder_var(d->dict, var, idx);
+
+
+  d->variables[idx] = g_malloc(sizeof (struct PsppireVariable));
+  d->variables[idx]->v = var;
+  d->variables[idx]->dict = d;
+
+  g_signal_emit(d, signal[VARIABLE_INSERTED], 0, idx );  
+}
+
+/* Delete N variables beginning at FIRST */
+void
+psppire_dict_delete_variables(PsppireDict *d, gint first, gint n)
+{
+  gint idx;
+  g_return_if_fail(d);
+  g_return_if_fail(d->dict);
+  g_return_if_fail(G_IS_PSPPIRE_DICT(d));
+
+  /* Invalidate all pvs from FIRST onwards */
+  for ( idx = first ; idx < d->cache_size ; ++idx ) 
+    {
+      g_free(d->variables[idx]);
+      d->variables[idx] = 0;
+    }
+
+  for (idx = 0 ; idx < n ; ++idx ) 
+    {
+      struct variable *var;
+
+      /* Do nothing if it's out of bounds */
+      if ( first >= dict_get_var_cnt (d->dict))
+       break; 
+
+      var = dict_get_var(d->dict, first);
+      dict_delete_var (d->dict, var);
+    }
+
+  g_signal_emit(d, signal[VARIABLES_DELETED], 0, first, idx );  
+}
+
+
+void
+psppire_dict_set_name(PsppireDict* d, gint idx, const gchar *name)
+{
+  struct variable *var;
+  g_assert(d);
+  g_assert(G_IS_PSPPIRE_DICT(d));
+
+
+  if ( idx < dict_get_var_cnt(d->dict))
+    {
+      /* This is an existing variable? */
+      var = dict_get_var(d->dict, idx);
+      dict_rename_var(d->dict, var, name);
+      g_signal_emit(d, signal[VARIABLE_CHANGED], 0, idx);
+    }
+  else
+    {
+      /* new variable */
+      dict_create_var(d->dict, name, 0);
+      g_print("Emitting variable-inserted signal\n");
+      g_signal_emit(d, signal[VARIABLE_INSERTED], 0, idx);
+    }
+}
+
+
+
+/* Return the IDXth variable */
+struct PsppireVariable *
+psppire_dict_get_variable(PsppireDict *d, gint idx)
+{
+  g_return_val_if_fail(d, NULL);
+  g_return_val_if_fail(d->dict, NULL);
+  g_return_val_if_fail(d->variables, NULL);
+  
+  if (idx < 0 || idx >= psppire_dict_get_var_cnt(d))
+    return NULL;
+
+  struct PsppireVariable *var = d->variables[idx] ; 
+
+  if (! var ) 
+    {
+      var = g_malloc(sizeof (*var));
+      var->dict = d;
+      var->v = dict_get_var(d->dict, idx);
+      d->variables[idx] = var;
+    }
+    
+  return var;
+}
+
+
+/* Return the number of variables in the dictionary */
+gint 
+psppire_dict_get_var_cnt(const PsppireDict *d)
+{
+  g_return_val_if_fail(d, -1);
+  g_return_val_if_fail(d->dict, -1);
+  
+
+  return dict_get_var_cnt(d->dict);
+}
+
+
+/* Return a variable by name.
+   Return NULL if it doesn't exist
+*/
+struct variable *
+psppire_dict_lookup_var (const PsppireDict *d, const gchar *name)
+{
+  g_return_val_if_fail(d, NULL);
+  g_return_val_if_fail(d->dict, NULL);
+
+  return dict_lookup_var(d->dict, name);
+}
+
+
+void
+psppire_dict_var_changed(PsppireDict *d, gint idx)
+{
+  g_return_if_fail(d);
+
+  g_signal_emit(d, signal[VARIABLE_CHANGED], 0, idx);
+}
+
+
+/* Clears the contents of D */
+void 
+psppire_dict_clear(PsppireDict *d)
+{
+  g_return_if_fail(d);
+  g_return_if_fail(d->dict);
+
+  const gint n_vars = dict_get_var_cnt(d->dict);
+  gint i;
+  
+  dict_clear(d->dict);
+
+  /* Invalidate the entire cache */
+  for ( i = 0 ; i < d->cache_size ; ++i ) 
+    {
+      g_free(d->variables[i]);
+      d->variables[i] = 0;
+    }
+
+  g_signal_emit(d, signal[VARIABLES_DELETED], 0, 0, n_vars );  
+}
+
+
+
+/* Return true is NAME would be a valid name of a variable to add to the 
+   dictionary.  False otherwise. 
+   If REPORT is true, then invalid names will be reported as such as errors
+*/
+gboolean
+psppire_dict_check_name(const PsppireDict *dict, 
+                    const gchar *name, gboolean report)
+{
+  if ( ! var_is_valid_name(name, report ) )
+      return FALSE;
+
+  if (psppire_dict_lookup_var(dict, name))
+    {
+      if ( report ) 
+       msg(ME,"Duplicate variable name.");
+      return FALSE;
+    }
+
+  return TRUE;
+}
diff --git a/src/ui/gui/psppire-dict.h b/src/ui/gui/psppire-dict.h
new file mode 100644 (file)
index 0000000..b36ccf9
--- /dev/null
@@ -0,0 +1,111 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2004  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+
+#ifndef __PSPPIRE_DICT_H__
+#define __PSPPIRE_DICT_H__
+
+
+#include <glib-object.h>
+#include <glib.h>
+
+#include <dictionary.h>
+#include <variable.h>
+
+
+G_BEGIN_DECLS
+
+
+/* --- type macros --- */
+#define G_TYPE_PSPPIRE_DICT              (psppire_dict_get_type ())
+#define PSPPIRE_DICT(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), G_TYPE_PSPPIRE_DICT, PsppireDict))
+#define PSPPIRE_DICT_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), G_TYPE_PSPPIRE_DICT, PsppireDictClass))
+#define G_IS_PSPPIRE_DICT(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), G_TYPE_PSPPIRE_DICT))
+#define G_IS_PSPPIRE_DICT_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), G_TYPE_PSPPIRE_DICT))
+#define PSPPIRE_DICT_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_PSPPIRE_DICT, PsppireDictClass))
+
+
+
+/* --- typedefs & structures --- */
+typedef struct _PsppireDict       PsppireDict;
+typedef struct _PsppireDictClass PsppireDictClass;
+
+struct _PsppireDict
+{
+  GObject             parent;
+  struct dictionary *dict;
+
+  /* Cache of variables */
+  struct PsppireVariable **variables;
+
+  gint cache_size;
+};
+
+struct _PsppireDictClass
+{
+  GObjectClass parent_class;
+
+};
+
+
+/* -- PsppireDict --- */
+GType          psppire_dict_get_type (void);
+PsppireDict*     psppire_dict_new (void);
+PsppireDict*     psppire_dict_new_from_dict (struct dictionary *d);
+void           psppire_dict_set_name (PsppireDict* s, gint idx, const gchar *name);
+void           psppire_dict_delete_var (PsppireDict *s, gint idx);
+
+/* Return the variable indexed by IDX.
+   returns NULL if IDX is not valid.
+*/
+struct variable *psppire_dict_get_var(PsppireDict *d, gint idx);
+
+/* Return the number of variables in the dictionary */
+gint psppire_dict_get_var_cnt(const PsppireDict *d);
+
+/* Return a variable by name.
+   Return NULL if it doesn't exist
+*/
+struct variable * psppire_dict_lookup_var (const PsppireDict *d, const gchar *name);
+
+/* Tell the dictionary that one of its variable has changed */
+void psppire_dict_var_changed(PsppireDict *d, gint idx);
+
+
+/* Clears the contents of D */
+void psppire_dict_clear(PsppireDict *d);
+
+/* Return the IDXth variable */
+
+struct PsppireVariable * psppire_dict_get_variable(PsppireDict *d, gint idx);
+
+/* Delete N variables beginning at FIRST */
+void psppire_dict_delete_variables(PsppireDict *d, gint first, gint n);
+
+/* Insert a new variable at posn IDX */
+void psppire_dict_insert_variable(PsppireDict *d, gint idx, const gchar *name);
+
+gboolean psppire_dict_check_name(const PsppireDict *dict, 
+                             const gchar *name, gboolean report);
+
+
+G_END_DECLS
+
+#endif /* __PSPPIRE_DICT_H__ */
diff --git a/src/ui/gui/psppire-object.c b/src/ui/gui/psppire-object.c
new file mode 100644 (file)
index 0000000..4b74029
--- /dev/null
@@ -0,0 +1,113 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2004  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "psppire-object.h"
+
+
+/* --- prototypes --- */
+static void psppire_object_class_init  (PsppireObjectClass     *class);
+static void psppire_object_init        (PsppireObject          *accel_group);
+static void psppire_object_finalize    (GObject                *object);
+
+
+/* --- variables --- */
+static GObjectClass     *parent_class = NULL;
+
+
+/* --- functions --- */
+/**
+ * psppire_object_get_type:
+ * @returns: the type ID for accelerator groups.
+ */
+GType
+psppire_object_get_type (void)
+{
+  static GType object_type = 0;
+
+  if (!object_type)
+    {
+      static const GTypeInfo object_info = {
+       sizeof (PsppireObjectClass),
+       (GBaseInitFunc) NULL,
+       (GBaseFinalizeFunc) NULL,
+       (GClassInitFunc) psppire_object_class_init,
+       NULL,   /* class_finalize */
+       NULL,   /* class_data */
+       sizeof (PsppireObject),
+       0,      /* n_preallocs */
+       (GInstanceInitFunc) psppire_object_init,
+      };
+
+      object_type = g_type_register_static (G_TYPE_OBJECT, "PsppireObject",
+                                           &object_info, G_TYPE_FLAG_ABSTRACT);
+    }
+
+  return object_type;
+}
+
+static guint signal_changed = 0 ; 
+
+static void
+psppire_object_class_init (PsppireObjectClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  parent_class = g_type_class_peek_parent (class);
+
+  object_class->finalize = psppire_object_finalize;
+
+   signal_changed =
+     g_signal_new ("changed",
+                  G_OBJECT_CLASS_TYPE (class),
+                  G_SIGNAL_RUN_FIRST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+}
+
+static void
+psppire_object_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+psppire_object_init (PsppireObject *psppire_object)
+{
+
+}
+
+/**
+ * psppire_object_new:
+ * @returns: a new #PsppireObject object
+ * 
+ * Creates a new #PsppireObject. 
+ */
+PsppireObject*
+psppire_object_new (void)
+{
+  return g_object_new (G_TYPE_PSPPIRE_OBJECT, NULL);
+}
diff --git a/src/ui/gui/psppire-object.h b/src/ui/gui/psppire-object.h
new file mode 100644 (file)
index 0000000..74c3e8b
--- /dev/null
@@ -0,0 +1,64 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2004  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+
+#ifndef __PSPPIRE_OBJECT_H__
+#define __PSPPIRE_OBJECT_H__
+
+#include <glib-object.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+
+/* --- type macros --- */
+#define G_TYPE_PSPPIRE_OBJECT              (psppire_object_get_type ())
+#define PSPPIRE_OBJECT(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), G_TYPE_PSPPIRE_OBJECT, PsppireObject))
+#define PSPPIRE_OBJECT_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), G_TYPE_PSPPIRE_OBJECT, PsppireObjectClass))
+#define G_IS_PSPPIRE_OBJECT(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), G_TYPE_PSPPIRE_OBJECT))
+#define G_IS_PSPPIRE_OBJECT_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), G_TYPE_PSPPIRE_OBJECT))
+#define PSPPIRE_OBJECT_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_PSPPIRE_OBJECT, PsppireObjectClass))
+
+
+
+/* --- typedefs & structures --- */
+typedef struct _PsppireObject     PsppireObject;
+typedef struct _PsppireObjectClass PsppireObjectClass;
+
+
+struct _PsppireObject
+{
+  GObject             parent;
+};
+
+struct _PsppireObjectClass
+{
+  GObjectClass parent_class;
+
+};
+
+
+/* -- PsppireObject --- */
+GType          psppire_object_get_type(void);
+PsppireObject*      psppire_object_new(void);
+
+G_END_DECLS
+
+#endif /* __PSPPIRE_OBJECT_H__ */
diff --git a/src/ui/gui/psppire-var-store.c b/src/ui/gui/psppire-var-store.c
new file mode 100644 (file)
index 0000000..dfae46b
--- /dev/null
@@ -0,0 +1,630 @@
+/* psppire-var-store.c
+   PSPPIRE --- A Graphical User Interface for PSPP
+   Copyright (C) 2006  Free Software Foundation
+   Written by John Darrington
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <gobject/gvaluecollector.h>
+
+#include "gsheetmodel.h"
+
+#include "psppire-variable.h"
+#include "psppire-var-store.h"
+#include "var-sheet.h"
+
+#include "dictionary.h"
+#include "variable.h"
+#include "missing-values.h"
+
+#include "val-labs-dialog.h"
+#include "missing-val-dialog.h"
+#include "value-labels.h"
+
+#define _(A) A
+#define N_(A) A
+
+
+static void         psppire_var_store_init            (PsppireVarStore      *var_store);
+static void         psppire_var_store_class_init      (PsppireVarStoreClass *class);
+static void         psppire_var_store_sheet_model_init (GSheetModelIface *iface);
+static void         psppire_var_store_finalize        (GObject           *object);
+
+static const gchar *const psppire_var_store_get_string(GSheetModel *sheet_model, gint row, gint column);
+
+static gboolean  psppire_var_store_clear(GSheetModel *model,  gint row, gint col);
+
+
+static gboolean psppire_var_store_set_string(GSheetModel *model, 
+                                         const gchar *text, gint row, gint column);
+
+
+static const gchar *const text_for_column(const struct PsppireVariable *pv, gint c);
+
+
+static GObjectClass *parent_class = NULL;
+
+GType
+psppire_var_store_get_type (void)
+{
+  static GType var_store_type = 0;
+
+  if (!var_store_type)
+    {
+      static const GTypeInfo var_store_info =
+      {
+       sizeof (PsppireVarStoreClass),
+       NULL,           /* base_init */
+       NULL,           /* base_finalize */
+        (GClassInitFunc) psppire_var_store_class_init,
+       NULL,           /* class_finalize */
+       NULL,           /* class_data */
+        sizeof (PsppireVarStore),
+       0,
+        (GInstanceInitFunc) psppire_var_store_init,
+      };
+
+      static const GInterfaceInfo sheet_model_info =
+      {
+       (GInterfaceInitFunc) psppire_var_store_sheet_model_init,
+       NULL,
+       NULL
+      };
+
+      var_store_type = g_type_register_static (G_TYPE_OBJECT, "PsppireVarStore",
+                                               &var_store_info, 0);
+
+      g_type_add_interface_static (var_store_type,
+                                  G_TYPE_SHEET_MODEL,
+                                  &sheet_model_info);
+    }
+
+  return var_store_type;
+}
+
+static void
+psppire_var_store_class_init (PsppireVarStoreClass *class)
+{
+  GObjectClass *object_class;
+
+  parent_class = g_type_class_peek_parent (class);
+  object_class = (GObjectClass*) class;
+
+  object_class->finalize = psppire_var_store_finalize;
+}
+
+
+static void
+psppire_var_store_init (PsppireVarStore *var_store)
+{
+
+  g_assert(gdk_color_parse("gray", &var_store->disabled));
+
+  GdkColormap *colormap = gdk_colormap_get_system();
+
+  gdk_colormap_alloc_color (colormap, &var_store->disabled, FALSE, TRUE);
+
+  var_store->dict = 0;
+}
+
+static gboolean
+psppire_var_store_item_editable(PsppireVarStore *var_store, gint row, gint column)
+{
+
+  struct PsppireVariable *pv = psppire_var_store_get_variable(var_store, row);
+
+  if ( !pv ) 
+    return TRUE;
+
+  if ( ALPHA == psppire_variable_get_type(pv) && column == COL_DECIMALS ) 
+    return FALSE;
+
+  const struct fmt_spec *write_spec = psppire_variable_get_write_spec(pv);
+
+  switch ( write_spec->type ) 
+    {
+    case FMT_DATE:     
+    case FMT_EDATE:    
+    case FMT_SDATE:    
+    case FMT_ADATE:    
+    case FMT_JDATE:    
+    case FMT_QYR:      
+    case FMT_MOYR:     
+    case FMT_WKYR:     
+    case FMT_DATETIME: 
+    case FMT_TIME:     
+    case FMT_DTIME:    
+    case FMT_WKDAY:    
+    case FMT_MONTH:    
+      if ( column == COL_DECIMALS || column == COL_WIDTH)
+       return FALSE;
+      break;
+    default:
+      break;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+psppire_var_store_is_editable(GSheetModel *model, gint row, gint column)
+{
+  PsppireVarStore *store = PSPPIRE_VAR_STORE(model);
+  return psppire_var_store_item_editable(store, row, column);
+}
+
+
+static const GdkColor *
+psppire_var_store_get_foreground(GSheetModel *model, gint row, gint column)
+{
+  PsppireVarStore *store = PSPPIRE_VAR_STORE(model);
+
+  if ( ! psppire_var_store_item_editable(store, row, column) ) 
+    return &store->disabled;
+  
+  return NULL;
+}
+
+
+const PangoFontDescription *
+psppire_var_store_get_font_desc(GSheetModel *model,
+                             gint row, gint column)
+{
+  PsppireVarStore *store = PSPPIRE_VAR_STORE(model);
+  
+  return store->font_desc;
+}
+
+
+
+static void
+psppire_var_store_sheet_model_init (GSheetModelIface *iface)
+{
+  iface->get_string = psppire_var_store_get_string;
+  iface->set_string = psppire_var_store_set_string;
+  iface->clear_datum = psppire_var_store_clear;
+  iface->is_editable = psppire_var_store_is_editable;
+  iface->is_visible = NULL;
+  iface->get_foreground = psppire_var_store_get_foreground;
+  iface->get_background = NULL;
+  iface->get_font_desc = psppire_var_store_get_font_desc;
+  iface->get_cell_border = NULL;
+}
+
+
+
+/**
+ * psppire_var_store_new:
+ * @dict: The dictionary for this var_store.
+ *
+ *
+ * Return value: a new #PsppireVarStore
+ **/
+PsppireVarStore *
+psppire_var_store_new (PsppireDict *dict)
+{
+  PsppireVarStore *retval;
+
+  retval = g_object_new (GTK_TYPE_VAR_STORE, NULL);
+
+  psppire_var_store_set_dictionary(retval, dict);
+
+  return retval;
+}
+
+static void 
+var_change_callback(GtkWidget *w, gint n, gpointer data)
+{
+  GSheetModel *model = G_SHEET_MODEL(data);
+  g_sheet_model_range_changed (model,
+                                n, 0, n, n_COLS);
+}
+
+
+static void 
+var_delete_callback(GtkWidget *w, gint first, gint n, gpointer data)
+{
+  GSheetModel *model = G_SHEET_MODEL(data);
+  
+  g_sheet_model_rows_deleted (model, first, n);
+}
+
+
+
+static void 
+var_insert_callback(GtkWidget *w, gint row, gpointer data)
+{
+  GSheetModel *model = G_SHEET_MODEL(data);
+
+  g_sheet_model_rows_inserted (model, row, 1);
+}
+
+
+
+/**
+ * psppire_var_store_replace_set_dictionary:
+ * @var_store: The variable store
+ * @dict: The dictionary to set
+ *
+ * If a dictionary is already associated with the var-store, then it will be
+ * destroyed.
+ **/
+void
+psppire_var_store_set_dictionary(PsppireVarStore *var_store, PsppireDict *dict)
+{
+  if ( var_store->dict ) g_object_unref(var_store->dict);
+
+  var_store->dict = dict;
+
+  g_signal_connect(dict, "variable-changed", G_CALLBACK(var_change_callback), 
+                  var_store);
+
+  g_signal_connect(dict, "variables-deleted", G_CALLBACK(var_delete_callback), 
+                  var_store);
+
+  g_signal_connect(dict, "variable-inserted", G_CALLBACK(var_insert_callback), 
+                  var_store);
+
+
+  /* The entire model has changed */
+  g_sheet_model_range_changed (G_SHEET_MODEL(var_store), -1, -1, -1, -1);
+}
+
+static void
+psppire_var_store_finalize (GObject *object)
+{
+  /* must chain up */
+  (* parent_class->finalize) (object);
+}
+
+static const gchar *const 
+psppire_var_store_get_string(GSheetModel *model, gint row, gint column)
+{
+  PsppireVarStore *store = PSPPIRE_VAR_STORE(model);
+
+  struct PsppireVariable *pv;
+
+  if ( row >= psppire_dict_get_var_cnt(store->dict))
+    return 0;
+  
+  pv = psppire_dict_get_variable (store->dict, row);
+  
+  const gchar *s = text_for_column(pv, column);
+
+  return s;
+}
+
+
+struct PsppireVariable *
+psppire_var_store_get_variable(PsppireVarStore *store, gint row)
+{
+  g_return_val_if_fail(store, NULL);
+  g_return_val_if_fail(store->dict, NULL);
+
+  if ( row >= psppire_dict_get_var_cnt(store->dict))
+    return 0;
+
+  return psppire_dict_get_variable (store->dict, row);
+}
+
+/* Clears that part of the variable store, if possible, which corresponds 
+   to ROW, COL.
+   Returns true if anything was updated, false otherwise.
+*/
+static gboolean 
+psppire_var_store_clear(GSheetModel *model,  gint row, gint col)
+{
+  PsppireVarStore *var_store = PSPPIRE_VAR_STORE(model);
+
+  if ( row >= psppire_dict_get_var_cnt(var_store->dict))
+      return FALSE;
+
+  struct PsppireVariable *pv = psppire_var_store_get_variable(var_store, row);
+
+  if ( !pv ) 
+    return FALSE;
+
+  switch (col)
+    {
+    case COL_LABEL:
+      psppire_variable_set_label(pv, 0);
+      return TRUE;
+      break;
+    }
+
+  return FALSE;
+}
+
+/* Attempts to update that part of the variable store which corresponds 
+   to ROW, COL with  the value TEXT.
+   Returns true if anything was updated, false otherwise.
+*/
+static gboolean 
+psppire_var_store_set_string(GSheetModel *model, 
+                         const gchar *text, gint row, gint col)
+{
+  PsppireVarStore *var_store = PSPPIRE_VAR_STORE(model);
+
+  if ( row >= psppire_dict_get_var_cnt(var_store->dict))
+      return FALSE;
+
+  struct PsppireVariable *pv = psppire_var_store_get_variable(var_store, row);
+
+  if ( !pv ) 
+    return FALSE;
+
+  switch (col)
+    {
+    case COL_NAME:
+      return psppire_variable_set_name(pv, text);
+      break;
+    case COL_COLUMNS:
+      if ( ! text) return FALSE;
+      return psppire_variable_set_columns(pv, atoi(text));
+      break;
+    case COL_WIDTH:
+      if ( ! text) return FALSE;
+      return psppire_variable_set_width(pv, atoi(text));
+      break;
+    case COL_DECIMALS:
+      if ( ! text) return FALSE;
+      return psppire_variable_set_decimals(pv, atoi(text));
+      break;
+    case COL_LABEL:
+      psppire_variable_set_label(pv, text);
+      return TRUE;
+      break;
+    case COL_TYPE:
+    case COL_VALUES:
+    case COL_MISSING:
+    case COL_ALIGN:
+    case COL_MEASURE:
+      /* These can be modified only by their respective dialog boxes */
+      return FALSE;
+      break;
+    default:
+      g_assert_not_reached();
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+
+#define MAX_CELL_TEXT_LEN 255
+
+static const gchar *const
+text_for_column(const struct PsppireVariable *pv, gint c)
+{
+  static gchar buf[MAX_CELL_TEXT_LEN];
+
+  static gchar none[]=_("None");
+
+  static const gchar *const type_label[] = 
+    {
+      _("Numeric"),
+      _("Comma"),
+      _("Dot"),
+      _("Scientific"),
+      _("Date"),
+      _("Dollar"),
+      _("Custom"),
+      _("String")
+    };
+  enum {VT_NUMERIC, VT_COMMA, VT_DOT, VT_SCIENTIFIC, VT_DATE, VT_DOLLAR, 
+       VT_CUSTOM, VT_STRING};
+
+  const struct fmt_spec *write_spec = psppire_variable_get_write_spec(pv);
+
+  switch (c)
+    {
+    case COL_NAME:
+      return psppire_variable_get_name(pv);
+      break;
+    case COL_TYPE:
+      {
+       switch ( write_spec->type ) 
+         {
+         case FMT_F:
+           return type_label[VT_NUMERIC];
+           break;
+         case FMT_COMMA:
+           return type_label[VT_COMMA];
+           break;
+         case FMT_DOT:
+           return type_label[VT_DOT];
+           break;
+         case FMT_E:
+           return type_label[VT_SCIENTIFIC];
+           break;
+         case FMT_DATE:        
+         case FMT_EDATE:       
+         case FMT_SDATE:       
+         case FMT_ADATE:       
+         case FMT_JDATE:       
+         case FMT_QYR: 
+         case FMT_MOYR:        
+         case FMT_WKYR:        
+         case FMT_DATETIME:    
+         case FMT_TIME:        
+         case FMT_DTIME:       
+         case FMT_WKDAY:       
+         case FMT_MONTH:       
+           return type_label[VT_DATE];
+           break;
+         case FMT_DOLLAR:
+           return type_label[VT_DOLLAR];
+           break;
+         case FMT_CCA:
+         case FMT_CCB:
+         case FMT_CCC:
+         case FMT_CCD:
+         case FMT_CCE:
+           return type_label[VT_CUSTOM];
+           break;
+         case FMT_A:
+           return type_label[VT_STRING];
+           break;
+         default:
+           g_warning("Unknown format: \"%s\"\n", 
+                     fmt_to_string(write_spec));
+           break;
+         }
+      }
+      break;
+    case COL_WIDTH:
+      {
+       g_snprintf(buf, MAX_CELL_TEXT_LEN, "%d", write_spec->w);
+       return buf;
+      }
+      break;
+    case COL_DECIMALS:
+      {
+       g_snprintf(buf, MAX_CELL_TEXT_LEN, "%d", write_spec->d);
+       return buf;
+      }
+      break;
+    case COL_COLUMNS:
+      {
+       g_snprintf(buf, MAX_CELL_TEXT_LEN, 
+                  "%d", psppire_variable_get_columns(pv));
+       return buf;
+      }
+      break;
+    case COL_LABEL:
+      return psppire_variable_get_label(pv);
+      break;
+    case COL_MISSING:
+      {
+      const struct missing_values *miss = psppire_variable_get_missing(pv);
+      if ( mv_is_empty(miss)) 
+       return none;
+      else
+       {
+         if ( ! mv_has_range (miss))
+           {
+             const int n = mv_n_values(miss);
+             gchar *mv[4] = {0,0,0,0};
+             gint i;
+             for(i = 0 ; i < n; ++i ) 
+               {
+                 union value v;
+                 mv_peek_value(miss, &v, i);
+                 mv[i] = value_to_text(v, *write_spec);
+               }
+             g_stpcpy(buf, "");
+             for(i = 0 ; i < n; ++i ) 
+               {
+                 if ( i > 0) 
+                   g_strlcat(buf, ", ", MAX_CELL_TEXT_LEN);
+                 g_strlcat(buf, mv[i], MAX_CELL_TEXT_LEN);
+                 g_free(mv[i]);
+               }
+           }
+         else
+           {
+             gchar *l, *h;
+             union value low, high;
+             mv_peek_range(miss, &low.f, &high.f);
+                 
+             l = value_to_text(low, *write_spec);
+             h = value_to_text(high, *write_spec);
+
+             g_snprintf(buf, MAX_CELL_TEXT_LEN, "%s - %s", l, h);
+             g_free(l);
+             g_free(h);
+
+             if ( mv_has_value(miss)) 
+               {
+                 gchar buf2[MAX_CELL_TEXT_LEN];
+                 gchar *s = 0;
+                 union value v;
+                 mv_peek_value(miss, &v, 0);
+
+                 s = value_to_text(v, *write_spec);
+
+                 g_snprintf(buf2, MAX_CELL_TEXT_LEN, "%s, %s", buf, s);
+                 free(s);
+                 g_stpcpy(buf, buf2);
+               }
+           }
+
+         return buf;
+       }
+      }
+      break;
+    case COL_VALUES:
+      {
+       const struct val_labs *vls = psppire_variable_get_value_labels(pv);
+       if ( ! vls || 0 == val_labs_count(vls)) 
+         return none;
+       else
+         {
+           struct val_labs_iterator *ip=0;
+           struct val_lab *vl = val_labs_first_sorted (vls, &ip);
+
+           g_assert(vl);
+
+           gchar *const vstr = value_to_text(vl->value, *write_spec);
+
+           g_snprintf(buf, MAX_CELL_TEXT_LEN, "{%s,\"%s\"}_", vstr, vl->label);
+
+           g_free(vstr);
+
+           val_labs_done(&ip);
+
+           return buf;
+         }
+      }
+      break;
+    case COL_ALIGN:
+      return alignments[psppire_variable_get_alignment(pv)];
+      break;
+    case COL_MEASURE:
+      return measures[psppire_variable_get_measure(pv)];
+      break;
+    }
+  return 0;
+}
+
+
+
+/* Return the number of variables */
+gint
+psppire_var_store_get_var_cnt(PsppireVarStore  *store)
+{
+  return psppire_dict_get_var_cnt(store->dict);
+}
+
+
+void
+psppire_var_store_set_font(PsppireVarStore *store, PangoFontDescription *fd)
+{
+  g_return_if_fail (store);
+  g_return_if_fail (PSPPIRE_IS_VAR_STORE (store));
+
+  store->font_desc = fd;
+
+  g_sheet_model_range_changed (G_SHEET_MODEL(store), -1, -1, -1, -1);
+}
+
+
+
diff --git a/src/ui/gui/psppire-var-store.h b/src/ui/gui/psppire-var-store.h
new file mode 100644 (file)
index 0000000..b4da027
--- /dev/null
@@ -0,0 +1,97 @@
+/* psppire-var-store.h
+   PSPPIRE --- A Graphical User Interface for PSPP
+   Copyright (C) 2006  Free Software Foundation
+   Written by John Darrington
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef __PSPPIRE_VAR_STORE_H__
+#define __PSPPIRE_VAR_STORE_H__
+
+#include "gsheetmodel.h"
+#include "psppire-dict.h"
+#include <gdk/gdk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define GTK_TYPE_VAR_STORE            (psppire_var_store_get_type ())
+
+#define PSPPIRE_VAR_STORE(obj)        (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+                                                                   GTK_TYPE_VAR_STORE, PsppireVarStore))
+
+#define PSPPIRE_VAR_STORE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), \
+                                                                GTK_TYPE_VAR_STORE, \
+                                                                 PsppireVarStoreClass))
+
+#define PSPPIRE_IS_VAR_STORE(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_VAR_STORE))
+
+#define PSPPIRE_IS_VAR_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_VAR_STORE))
+
+#define PSPPIRE_VAR_STORE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+                                                                  GTK_TYPE_VAR_STORE, \
+                                                                  PsppireVarStoreClass))
+
+typedef struct _PsppireVarStore       PsppireVarStore;
+typedef struct _PsppireVarStoreClass  PsppireVarStoreClass;
+
+struct dictionary;
+
+struct _PsppireVarStore
+{
+  GObject parent;
+
+  /*< private >*/
+  PsppireDict *dict;
+  GdkColor disabled;
+  PangoFontDescription *font_desc;
+};
+
+struct _PsppireVarStoreClass
+{
+  GObjectClass parent_class;
+
+  /* Padding for future expansion */
+  void (*_gtk_reserved1) (void);
+  void (*_gtk_reserved2) (void);
+  void (*_gtk_reserved3) (void);
+  void (*_gtk_reserved4) (void);
+};
+
+
+GType         psppire_var_store_get_type         (void) G_GNUC_CONST;
+PsppireVarStore *psppire_var_store_new              (PsppireDict *dict);
+struct variable * psppire_var_store_get_var (PsppireVarStore *store, gint row);
+struct PsppireVariable * psppire_var_store_get_variable(PsppireVarStore *store, 
+                                                 gint row);
+
+void psppire_var_store_set_dictionary(PsppireVarStore *var_store, PsppireDict *dict);
+
+
+/* Return the number of variables */
+gint psppire_var_store_get_var_cnt(PsppireVarStore      *var_store);
+
+void psppire_var_store_set_font(PsppireVarStore *store, PangoFontDescription *fd);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __PSPPIRE_VAR_STORE_H__ */
diff --git a/src/ui/gui/psppire-variable.c b/src/ui/gui/psppire-variable.c
new file mode 100644 (file)
index 0000000..6443ee9
--- /dev/null
@@ -0,0 +1,353 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2004  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "missing-values.h"
+#include "value-labels.h"
+#include "format.h"
+
+#include "psppire-variable.h"
+#include "psppire-dict.h"
+
+
+
+gboolean
+psppire_variable_set_name(struct PsppireVariable *pv, const gchar *text)
+{
+  g_return_val_if_fail(pv, FALSE);
+  g_return_val_if_fail(pv->dict, FALSE);
+  g_return_val_if_fail(pv->v, FALSE);
+
+  if ( !text) 
+    return FALSE;
+
+  if ( 0 == strcmp(pv->v->name, text))
+    return FALSE;
+
+  if ( ! psppire_dict_check_name(pv->dict, text, TRUE) )
+    return FALSE;
+
+  dict_rename_var(pv->dict->dict, pv->v, text);
+
+  psppire_dict_var_changed(pv->dict, pv->v->index);
+
+  return TRUE;
+}
+
+
+gboolean
+psppire_variable_set_columns(struct PsppireVariable *pv, gint columns)
+{
+  g_return_val_if_fail(pv, FALSE);
+  g_return_val_if_fail(pv->dict, FALSE);
+  g_return_val_if_fail(pv->v, FALSE);
+
+  pv->v->display_width = columns;
+  
+  psppire_dict_var_changed(pv->dict, pv->v->index);
+
+  return TRUE;
+}
+
+gboolean
+psppire_variable_set_label(struct PsppireVariable *pv, const gchar *label)
+{
+  g_return_val_if_fail(pv, FALSE);
+  g_return_val_if_fail(pv->dict, FALSE);
+  g_return_val_if_fail(pv->v, FALSE);
+
+  g_free(pv->v->label);
+  pv->v->label = g_strdup(label);
+
+  psppire_dict_var_changed(pv->dict, pv->v->index);
+
+  return TRUE;
+}
+
+
+gboolean
+psppire_variable_set_decimals(struct PsppireVariable *pv, gint decimals)
+{
+  g_return_val_if_fail(pv, FALSE);
+  g_return_val_if_fail(pv->dict, FALSE);
+  g_return_val_if_fail(pv->v, FALSE);
+
+  struct fmt_spec fmt = pv->v->write;
+
+  fmt.d = decimals;
+
+  return psppire_variable_set_format(pv, &fmt);
+}
+
+
+
+gboolean
+psppire_variable_set_width(struct PsppireVariable *pv, gint width)
+{
+  g_return_val_if_fail(pv, FALSE);
+  g_return_val_if_fail(pv->dict, FALSE);
+  g_return_val_if_fail(pv->v, FALSE);
+
+  struct fmt_spec fmt = pv->v->write;
+
+  fmt.w = width;
+
+  if ( pv->v->type == ALPHA ) 
+    pv->v->width = width;
+
+  return psppire_variable_set_format(pv, &fmt);
+}
+
+
+gboolean
+psppire_variable_set_type(struct PsppireVariable *pv, int type)
+{
+  g_return_val_if_fail(pv, FALSE);
+  g_return_val_if_fail(pv->dict, FALSE);
+  g_return_val_if_fail(pv->v, FALSE);
+
+  pv->v->type = type; 
+
+  if ( type == NUMERIC ) 
+    pv->v->width = 0;
+
+  psppire_dict_var_changed(pv->dict, pv->v->index);
+  return TRUE;
+}
+
+
+gboolean
+psppire_variable_set_format(struct PsppireVariable *pv, struct fmt_spec *fmt)
+{
+  g_return_val_if_fail(pv, FALSE);
+  g_return_val_if_fail(pv->dict, FALSE);
+  g_return_val_if_fail(pv->v, FALSE);
+
+  if ( check_output_specifier(fmt, false) 
+       && 
+       check_specifier_type(fmt, pv->v->type, false)
+       && 
+       check_specifier_width(fmt, pv->v->width, false)
+       ) 
+    {
+      pv->v->write = pv->v->print = *fmt;
+      psppire_dict_var_changed(pv->dict, pv->v->index);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+
+gboolean
+psppire_variable_set_value_labels(const struct PsppireVariable *pv,
+                              const struct val_labs *vls)
+{
+  g_return_val_if_fail(pv, FALSE);
+  g_return_val_if_fail(pv->dict, FALSE);
+  g_return_val_if_fail(pv->v, FALSE);
+
+  val_labs_destroy(pv->v->val_labs);
+  pv->v->val_labs = val_labs_copy(vls);
+
+  psppire_dict_var_changed(pv->dict, pv->v->index);
+  return TRUE;
+}
+
+gboolean 
+psppire_variable_set_missing(const struct PsppireVariable *pv,
+                         const struct missing_values *miss)
+{
+  g_return_val_if_fail(pv, FALSE);
+  g_return_val_if_fail(pv->dict, FALSE);
+  g_return_val_if_fail(pv->v, FALSE);
+
+  mv_copy(&pv->v->miss, miss);
+
+  psppire_dict_var_changed(pv->dict, pv->v->index);
+  return TRUE;
+}
+
+gboolean
+psppire_variable_set_write_spec(const struct PsppireVariable *pv, struct fmt_spec fmt)
+{
+  g_return_val_if_fail(pv, FALSE);
+  g_return_val_if_fail(pv->v, FALSE);
+
+  pv->v->write = fmt;
+
+  psppire_dict_var_changed(pv->dict, pv->v->index);
+  return TRUE;
+}
+
+gboolean
+psppire_variable_set_print_spec(const struct PsppireVariable *pv, struct fmt_spec fmt)
+{
+  g_return_val_if_fail(pv, FALSE);
+  g_return_val_if_fail(pv->v, FALSE);
+
+  pv->v->print = fmt;
+
+  psppire_dict_var_changed(pv->dict, pv->v->index);
+  return TRUE;
+}
+
+
+
+gboolean
+psppire_variable_set_alignment(struct PsppireVariable *pv, gint align)
+{
+  g_return_val_if_fail(pv, FALSE);
+  g_return_val_if_fail(pv->dict, FALSE);
+  g_return_val_if_fail(pv->v, FALSE);
+
+  pv->v->alignment = align;
+
+  psppire_dict_var_changed(pv->dict, pv->v->index);
+  return TRUE;
+}
+
+
+gboolean
+psppire_variable_set_measure(struct PsppireVariable *pv, gint measure)
+{
+  g_return_val_if_fail(pv, FALSE);
+  g_return_val_if_fail(pv->dict, FALSE);
+  g_return_val_if_fail(pv->v, FALSE);
+
+  pv->v->measure = measure + 1;
+
+  psppire_dict_var_changed(pv->dict, pv->v->index);
+  return TRUE;
+}
+
+
+const struct fmt_spec *
+psppire_variable_get_write_spec(const struct PsppireVariable *pv)
+{
+  g_return_val_if_fail(pv, NULL);
+  g_return_val_if_fail(pv->v, NULL);
+
+
+  return &pv->v->write;
+}
+
+
+const gchar *
+psppire_variable_get_name(const struct PsppireVariable *pv)
+{
+  g_return_val_if_fail(pv, NULL);
+  g_return_val_if_fail(pv->v, NULL);
+
+  return pv->v->name;
+}
+
+
+gint
+psppire_variable_get_columns(const struct PsppireVariable *pv)
+{
+  g_return_val_if_fail(pv, -1);
+  g_return_val_if_fail(pv->v, -1);
+
+  return pv->v->display_width;
+}
+
+
+
+const gchar *
+psppire_variable_get_label(const struct PsppireVariable *pv)
+{
+  g_return_val_if_fail(pv, NULL);
+  g_return_val_if_fail(pv->v, NULL);
+
+  return pv->v->label;
+}
+
+
+const struct missing_values *
+psppire_variable_get_missing(const struct PsppireVariable *pv)
+{
+  g_return_val_if_fail(pv, NULL);
+  g_return_val_if_fail(pv->v, NULL);
+
+  return &pv->v->miss;
+}
+
+
+const struct val_labs *
+psppire_variable_get_value_labels(const struct PsppireVariable *pv)
+{
+  g_return_val_if_fail(pv, NULL);
+  g_return_val_if_fail(pv->v, NULL);
+
+  return pv->v->val_labs;
+}
+
+
+gint
+psppire_variable_get_alignment(const struct PsppireVariable *pv)
+{
+  g_return_val_if_fail(pv, -1);
+  g_return_val_if_fail(pv->v, -1);
+
+  return pv->v->alignment;
+}
+
+
+
+gint
+psppire_variable_get_measure(const struct PsppireVariable *pv)
+{
+  g_return_val_if_fail(pv, -1);
+  g_return_val_if_fail(pv->v, -1);
+
+  return pv->v->measure - 1;
+}
+
+gint
+psppire_variable_get_type(const struct PsppireVariable *pv)
+{
+  g_return_val_if_fail(pv, -1);
+  g_return_val_if_fail(pv->v, -1);
+
+  return pv->v->type;
+}
+
+
+gint
+psppire_variable_get_width(const struct PsppireVariable *pv)
+{
+  g_return_val_if_fail(pv, -1);
+  g_return_val_if_fail(pv->v, -1);
+
+  return pv->v->width;
+}
+
+gint
+psppire_variable_get_index(const struct PsppireVariable *pv)
+{
+  g_return_val_if_fail(pv, -1);
+  g_return_val_if_fail(pv->v, -1);
+
+  return pv->v->fv;
+}
+
diff --git a/src/ui/gui/psppire-variable.h b/src/ui/gui/psppire-variable.h
new file mode 100644 (file)
index 0000000..19e7ecc
--- /dev/null
@@ -0,0 +1,93 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2006  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+
+#ifndef __PSPPIRE_VARIABLE_H__
+#define __PSPPIRE_VARIABLE_H__
+
+#include <glib-object.h>
+#include <glib.h>
+
+#include <variable.h>
+#include "psppire-dict.h"
+
+/* Don't use any of these members.
+   Use accessor functions instead.
+*/
+struct PsppireVariable
+{
+  /* The payload */
+  struct variable *v;
+
+  /* The dictionary to which this variable belongs */
+  PsppireDict *dict;
+};
+
+
+
+gboolean psppire_variable_set_name(struct PsppireVariable *pv, const gchar *text);
+
+gboolean psppire_variable_set_columns(struct PsppireVariable *pv, gint columns);
+gboolean psppire_variable_set_label(struct PsppireVariable *pv, const gchar *label);
+gboolean psppire_variable_set_format(struct PsppireVariable *pv, struct fmt_spec *fmt);
+gboolean psppire_variable_set_decimals(struct PsppireVariable *pv, gint decimals);
+gboolean psppire_variable_set_width(struct PsppireVariable *pv, gint width);
+gboolean psppire_variable_set_alignment(struct PsppireVariable *pv, gint align);
+gboolean psppire_variable_set_measure(struct PsppireVariable *pv, gint measure);
+gboolean psppire_variable_set_value_labels(const struct PsppireVariable *pv,
+                                       const struct val_labs *vls);
+
+gboolean psppire_variable_set_missing(const struct PsppireVariable *pv,
+                                  const struct missing_values *miss);
+gboolean psppire_variable_set_print_spec(const struct PsppireVariable *pv, struct fmt_spec fmt);
+gboolean psppire_variable_set_write_spec(const struct PsppireVariable *pv, struct fmt_spec fmt);
+
+gboolean psppire_variable_set_type(struct PsppireVariable *pv, int type);
+
+
+
+const struct fmt_spec *psppire_variable_get_write_spec(const struct PsppireVariable *pv);
+const gchar * psppire_variable_get_name(const struct PsppireVariable *pv);
+
+gint psppire_variable_get_columns(const struct PsppireVariable *pv);
+
+const gchar * psppire_variable_get_label(const struct PsppireVariable *pv);
+
+
+const struct missing_values *psppire_variable_get_missing
+                                     (const struct PsppireVariable *pv);
+
+const struct val_labs * psppire_variable_get_value_labels
+                                     (const struct PsppireVariable *pv);
+
+gint psppire_variable_get_alignment(const struct PsppireVariable *pv);
+
+gint psppire_variable_get_measure(const struct PsppireVariable *pv);
+
+gint psppire_variable_get_index(const struct PsppireVariable *pv);
+
+gint psppire_variable_get_type(const struct PsppireVariable *pv);
+
+gint psppire_variable_get_width(const struct PsppireVariable *pv);
+
+
+
+#endif /* __PSPPIRE_VARIABLE_H__ */
diff --git a/src/ui/gui/psppire.c b/src/ui/gui/psppire.c
new file mode 100644 (file)
index 0000000..849f143
--- /dev/null
@@ -0,0 +1,98 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2004, 2005  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+/*
+ * Initial main.c file generated by Glade. Edit as required.
+ * Glade will not overwrite this file.
+ */
+
+
+#include <gtk/gtk.h>
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include "menu-actions.h"
+#include "psppire-dict.h"
+#include "psppire-var-store.h"
+#include "psppire-data-store.h"
+
+#include "helper.h"
+#include "data-sheet.h"
+#include "var-sheet.h"
+#include "psppire-case-array.h"
+
+GladeXML *xml;
+
+
+PsppireDict *the_dictionary = 0;
+PsppireCaseArray *the_cases = 0;
+
+
+PsppireDataStore *data_store = 0;
+
+
+int 
+main(int argc, char *argv[]) 
+{
+
+  gtk_init(&argc, &argv);
+
+  glade_init();
+
+  the_dictionary = psppire_dict_new();
+
+  /* Create the model for the var_sheet */
+  PsppireVarStore *var_store = psppire_var_store_new(the_dictionary);
+
+  /* Create the model for the data sheet */
+  the_cases = psppire_case_array_new(100, 20);
+
+  data_store = psppire_data_store_new(the_dictionary, the_cases);
+
+
+  /* load the interface */
+  xml = glade_xml_new("psppire.glade", NULL, NULL);
+
+  if ( !xml ) 
+    {
+      g_print("Is psppire.glade in current directory?\n");
+      return 1;
+    }
+
+  GtkWidget *data_editor = get_widget_assert(xml, "data_editor");
+  gtk_window_set_icon_from_file(GTK_WINDOW(data_editor), "psppicon.png",0);
+
+  /* connect the signals in the interface */
+  glade_xml_signal_autoconnect(xml);
+
+  GtkSheet *var_sheet  = GTK_SHEET(get_widget_assert(xml, "variable_sheet"));
+  GtkSheet *data_sheet = GTK_SHEET(get_widget_assert(xml, "data_sheet"));
+
+  gtk_sheet_set_model(var_sheet, G_SHEET_MODEL(var_store));
+  
+  gtk_sheet_set_model(data_sheet, G_SHEET_MODEL(data_store));
+
+
+  gtk_init_add(callbacks_on_init, 0);
+
+  /* start the event loop */
+  gtk_main();
+  return 0;
+}
+
diff --git a/src/ui/gui/psppire.glade b/src/ui/gui/psppire.glade
new file mode 100644 (file)
index 0000000..b41c5d5
--- /dev/null
@@ -0,0 +1,2038 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="data_editor">
+  <property name="visible">True</property>
+  <property name="title" translatable="yes">Psppire</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">False</property>
+  <property name="default_width">975</property>
+  <property name="default_height">480</property>
+  <property name="resizable">True</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+
+  <child>
+    <widget class="GtkVBox" id="vbox1">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">0</property>
+
+      <child>
+       <widget class="GtkMenuBar" id="menubar1">
+         <property name="visible">True</property>
+
+         <child>
+           <widget class="GtkMenuItem" id="menuitem1">
+             <property name="visible">True</property>
+             <property name="label" translatable="yes">_File</property>
+             <property name="use_underline">True</property>
+
+             <child>
+               <widget class="GtkMenu" id="menuitem1_menu">
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="new1">
+                     <property name="visible">True</property>
+                     <property name="label">gtk-new</property>
+                     <property name="use_stock">True</property>
+                     <signal name="activate" handler="on_new1_activate" last_modification_time="Tue, 23 Mar 2004 10:22:51 GMT"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="open1">
+                     <property name="visible">True</property>
+                     <property name="label">gtk-open</property>
+                     <property name="use_stock">True</property>
+                     <signal name="activate" handler="on_open1_activate" last_modification_time="Tue, 23 Mar 2004 10:22:51 GMT"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="save1">
+                     <property name="visible">True</property>
+                     <property name="label">gtk-save</property>
+                     <property name="use_stock">True</property>
+                     <signal name="activate" handler="on_save1_activate" last_modification_time="Tue, 23 Mar 2004 10:22:51 GMT"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="save_as1">
+                     <property name="visible">True</property>
+                     <property name="label">gtk-save-as</property>
+                     <property name="use_stock">True</property>
+                     <signal name="activate" handler="on_save_as1_activate" last_modification_time="Tue, 23 Mar 2004 10:22:51 GMT"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkSeparatorMenuItem" id="separatormenuitem1">
+                     <property name="visible">True</property>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="quit1">
+                     <property name="visible">True</property>
+                     <property name="label">gtk-quit</property>
+                     <property name="use_stock">True</property>
+                     <signal name="activate" handler="on_quit1_activate" last_modification_time="Tue, 23 Mar 2004 10:22:51 GMT"/>
+                   </widget>
+                 </child>
+               </widget>
+             </child>
+           </widget>
+         </child>
+
+         <child>
+           <widget class="GtkMenuItem" id="menuitem2">
+             <property name="visible">True</property>
+             <property name="label" translatable="yes">_Edit</property>
+             <property name="use_underline">True</property>
+
+             <child>
+               <widget class="GtkMenu" id="menuitem2_menu">
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="cut1">
+                     <property name="visible">True</property>
+                     <property name="label">gtk-cut</property>
+                     <property name="use_stock">True</property>
+                     <signal name="activate" handler="on_cut1_activate" last_modification_time="Tue, 23 Mar 2004 10:22:51 GMT"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="copy1">
+                     <property name="visible">True</property>
+                     <property name="label">gtk-copy</property>
+                     <property name="use_stock">True</property>
+                     <signal name="activate" handler="on_copy1_activate" last_modification_time="Tue, 23 Mar 2004 10:22:51 GMT"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="paste1">
+                     <property name="visible">True</property>
+                     <property name="label">gtk-paste</property>
+                     <property name="use_stock">True</property>
+                     <signal name="activate" handler="on_paste1_activate" last_modification_time="Tue, 23 Mar 2004 10:22:51 GMT"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkImageMenuItem" id="delete1">
+                     <property name="visible">True</property>
+                     <property name="label">gtk-delete</property>
+                     <property name="use_stock">True</property>
+                     <signal name="activate" handler="on_delete1_activate" last_modification_time="Tue, 23 Mar 2004 10:22:51 GMT"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkMenuItem" id="insert1">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">_Insert</property>
+                     <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_insert1_activate" last_modification_time="Mon, 02 Jan 2006 12:46:02 GMT"/>
+                   </widget>
+                 </child>
+               </widget>
+             </child>
+           </widget>
+         </child>
+
+         <child>
+           <widget class="GtkMenuItem" id="menuitem3">
+             <property name="visible">True</property>
+             <property name="label" translatable="yes">_View</property>
+             <property name="use_underline">True</property>
+
+             <child>
+               <widget class="GtkMenu" id="menuitem3_menu">
+
+                 <child>
+                   <widget class="GtkCheckMenuItem" id="status_bar1">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">Status Bar</property>
+                     <property name="use_underline">True</property>
+                     <property name="active">True</property>
+                     <signal name="activate" handler="on_status_bar1_activate" last_modification_time="Thu, 24 Nov 2005 10:41:01 GMT"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkMenuItem" id="toolbars1">
+                     <property name="label" translatable="yes">Toolbars</property>
+                     <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_toolbars1_activate" last_modification_time="Thu, 24 Nov 2005 10:41:14 GMT"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkSeparatorMenuItem" id="separator1">
+                     <property name="visible">True</property>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkMenuItem" id="fonts1">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">Fonts</property>
+                     <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_fonts1_activate" last_modification_time="Thu, 24 Nov 2005 10:41:29 GMT"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkCheckMenuItem" id="grid_lines1">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">Grid Lines</property>
+                     <property name="use_underline">True</property>
+                     <property name="active">True</property>
+                     <signal name="activate" handler="on_grid_lines1_activate" last_modification_time="Thu, 24 Nov 2005 10:41:44 GMT"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkCheckMenuItem" id="value_labels1">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">Value Labels</property>
+                     <property name="use_underline">True</property>
+                     <property name="active">False</property>
+                     <signal name="activate" handler="on_value_labels1_activate" last_modification_time="Thu, 24 Nov 2005 10:42:04 GMT"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkSeparatorMenuItem" id="separator3">
+                     <property name="visible">True</property>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkMenuItem" id="data1">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">Data</property>
+                     <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_data1_activate" last_modification_time="Sun, 27 Nov 2005 02:18:20 GMT"/>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkMenuItem" id="variables1">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">Variables</property>
+                     <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_variables1_activate" last_modification_time="Sun, 27 Nov 2005 02:18:20 GMT"/>
+                   </widget>
+                 </child>
+               </widget>
+             </child>
+           </widget>
+         </child>
+
+         <child>
+           <widget class="GtkMenuItem" id="menuitem4">
+             <property name="visible">True</property>
+             <property name="label" translatable="yes">_Help</property>
+             <property name="use_underline">True</property>
+
+             <child>
+               <widget class="GtkMenu" id="menuitem4_menu">
+
+                 <child>
+                   <widget class="GtkMenuItem" id="about1">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">_About</property>
+                     <property name="use_underline">True</property>
+                     <signal name="activate" handler="on_about1_activate" last_modification_time="Fri, 25 Nov 2005 11:54:43 GMT"/>
+                   </widget>
+                 </child>
+               </widget>
+             </child>
+           </widget>
+         </child>
+       </widget>
+       <packing>
+         <property name="padding">0</property>
+         <property name="expand">False</property>
+         <property name="fill">False</property>
+       </packing>
+      </child>
+
+      <child>
+       <widget class="GtkHandleBox" id="handlebox1">
+         <property name="visible">True</property>
+         <property name="shadow_type">GTK_SHADOW_OUT</property>
+         <property name="handle_position">GTK_POS_LEFT</property>
+         <property name="snap_edge">GTK_POS_TOP</property>
+
+         <child>
+           <widget class="GtkToolbar" id="toolbar1">
+             <property name="visible">True</property>
+             <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
+             <property name="toolbar_style">GTK_TOOLBAR_BOTH</property>
+             <property name="tooltips">True</property>
+             <property name="show_arrow">True</property>
+
+             <child>
+               <widget class="GtkToolButton" id="buttonOpen">
+                 <property name="visible">True</property>
+                 <property name="tooltip" translatable="yes">Open</property>
+                 <property name="label" translatable="yes"></property>
+                 <property name="use_underline">True</property>
+                 <property name="stock_id">gtk-open</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+                 <property name="is_important">False</property>
+                 <signal name="clicked" handler="on_open1_activate" last_modification_time="Thu, 10 Nov 2005 12:22:50 GMT"/>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">True</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkToolButton" id="buttonSave">
+                 <property name="visible">True</property>
+                 <property name="tooltip" translatable="yes">Save</property>
+                 <property name="label" translatable="yes"></property>
+                 <property name="use_underline">True</property>
+                 <property name="stock_id">gtk-save</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+                 <property name="is_important">False</property>
+                 <signal name="clicked" handler="on_save1_activate" last_modification_time="Wed, 02 Nov 2005 13:05:43 GMT"/>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">True</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkToolButton" id="buttonPrint">
+                 <property name="visible">True</property>
+                 <property name="tooltip" translatable="yes">Print</property>
+                 <property name="label" translatable="yes"></property>
+                 <property name="use_underline">True</property>
+                 <property name="stock_id">gtk-print</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+                 <property name="is_important">False</property>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">True</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkSeparatorToolItem" id="separatortoolitem1">
+                 <property name="visible">True</property>
+                 <property name="draw">True</property>
+                 <property name="visible_horizontal">True</property>
+                 <property name="visible_vertical">True</property>
+               </widget>
+               <packing>
+                 <property name="expand">False</property>
+                 <property name="homogeneous">False</property>
+               </packing>
+             </child>
+           </widget>
+         </child>
+       </widget>
+       <packing>
+         <property name="padding">0</property>
+         <property name="expand">False</property>
+         <property name="fill">False</property>
+       </packing>
+      </child>
+
+      <child>
+       <widget class="GtkNotebook" id="notebook1">
+         <property name="visible">True</property>
+         <property name="can_focus">True</property>
+         <property name="show_tabs">True</property>
+         <property name="show_border">True</property>
+         <property name="tab_pos">GTK_POS_BOTTOM</property>
+         <property name="scrollable">True</property>
+         <property name="enable_popup">True</property>
+
+         <child>
+           <widget class="GtkVBox" id="vbox3">
+             <property name="visible">True</property>
+             <property name="homogeneous">False</property>
+             <property name="spacing">0</property>
+
+             <child>
+               <widget class="GtkTable" id="table2">
+                 <property name="visible">True</property>
+                 <property name="n_rows">1</property>
+                 <property name="n_columns">2</property>
+                 <property name="homogeneous">False</property>
+                 <property name="row_spacing">0</property>
+                 <property name="column_spacing">0</property>
+
+                 <child>
+                   <widget class="GtkEntry" id="entry4">
+                     <property name="visible">True</property>
+                     <property name="can_focus">True</property>
+                     <property name="editable">True</property>
+                     <property name="visibility">True</property>
+                     <property name="max_length">0</property>
+                     <property name="text" translatable="yes"></property>
+                     <property name="has_frame">True</property>
+                     <property name="invisible_char">*</property>
+                     <property name="activates_default">False</property>
+                   </widget>
+                   <packing>
+                     <property name="left_attach">1</property>
+                     <property name="right_attach">2</property>
+                     <property name="top_attach">0</property>
+                     <property name="bottom_attach">1</property>
+                     <property name="y_options"></property>
+                   </packing>
+                 </child>
+
+                 <child>
+                   <widget class="GtkEntry" id="cell_ref_entry">
+                     <property name="visible">True</property>
+                     <property name="sensitive">False</property>
+                     <property name="editable">False</property>
+                     <property name="visibility">True</property>
+                     <property name="max_length">0</property>
+                     <property name="text" translatable="yes"></property>
+                     <property name="has_frame">True</property>
+                     <property name="invisible_char">*</property>
+                     <property name="activates_default">False</property>
+                     <property name="width_chars">25</property>
+                   </widget>
+                   <packing>
+                     <property name="left_attach">0</property>
+                     <property name="right_attach">1</property>
+                     <property name="top_attach">0</property>
+                     <property name="bottom_attach">1</property>
+                     <property name="x_options">fill</property>
+                     <property name="y_options"></property>
+                   </packing>
+                 </child>
+               </widget>
+               <packing>
+                 <property name="padding">0</property>
+                 <property name="expand">False</property>
+                 <property name="fill">False</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkScrolledWindow" id="scrolledwindow1">
+                 <property name="visible">True</property>
+                 <property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property>
+                 <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+                 <property name="shadow_type">GTK_SHADOW_NONE</property>
+                 <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+                 <child>
+                   <widget class="Custom" id="data_sheet">
+                     <property name="visible">True</property>
+                     <property name="creation_function">psppire_data_sheet_create</property>
+                     <property name="int1">0</property>
+                     <property name="int2">0</property>
+                     <property name="last_modification_time">Sun, 30 Oct 2005 08:53:47 GMT</property>
+                   </widget>
+                 </child>
+               </widget>
+               <packing>
+                 <property name="padding">0</property>
+                 <property name="expand">True</property>
+                 <property name="fill">True</property>
+               </packing>
+             </child>
+           </widget>
+           <packing>
+             <property name="tab_expand">False</property>
+             <property name="tab_fill">True</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkLabel" id="label1">
+             <property name="visible">True</property>
+             <property name="label" translatable="yes">Data View</property>
+             <property name="use_underline">False</property>
+             <property name="use_markup">False</property>
+             <property name="justify">GTK_JUSTIFY_LEFT</property>
+             <property name="wrap">False</property>
+             <property name="selectable">False</property>
+             <property name="xalign">0.5</property>
+             <property name="yalign">0.5</property>
+             <property name="xpad">0</property>
+             <property name="ypad">0</property>
+             <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+             <property name="width_chars">-1</property>
+             <property name="single_line_mode">False</property>
+             <property name="angle">0</property>
+           </widget>
+           <packing>
+             <property name="type">tab</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkScrolledWindow" id="scrolledwindow2">
+             <property name="visible">True</property>
+             <property name="can_focus">True</property>
+             <property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property>
+             <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+             <property name="shadow_type">GTK_SHADOW_NONE</property>
+             <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+             <child>
+               <widget class="Custom" id="variable_sheet">
+                 <property name="visible">True</property>
+                 <property name="creation_function">psppire_variable_sheet_create</property>
+                 <property name="int1">0</property>
+                 <property name="int2">0</property>
+                 <property name="last_modification_time">Sun, 30 Oct 2005 08:54:45 GMT</property>
+               </widget>
+             </child>
+           </widget>
+           <packing>
+             <property name="tab_expand">False</property>
+             <property name="tab_fill">True</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkLabel" id="label2">
+             <property name="visible">True</property>
+             <property name="label" translatable="yes">Variable View</property>
+             <property name="use_underline">False</property>
+             <property name="use_markup">False</property>
+             <property name="justify">GTK_JUSTIFY_LEFT</property>
+             <property name="wrap">False</property>
+             <property name="selectable">False</property>
+             <property name="xalign">0.5</property>
+             <property name="yalign">0.5</property>
+             <property name="xpad">0</property>
+             <property name="ypad">0</property>
+             <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+             <property name="width_chars">-1</property>
+             <property name="single_line_mode">False</property>
+             <property name="angle">0</property>
+           </widget>
+           <packing>
+             <property name="type">tab</property>
+           </packing>
+         </child>
+       </widget>
+       <packing>
+         <property name="padding">0</property>
+         <property name="expand">True</property>
+         <property name="fill">True</property>
+       </packing>
+      </child>
+
+      <child>
+       <widget class="GtkStatusbar" id="statusbar1">
+         <property name="visible">True</property>
+         <property name="has_resize_grip">True</property>
+       </widget>
+       <packing>
+         <property name="padding">0</property>
+         <property name="expand">False</property>
+         <property name="fill">False</property>
+       </packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+<widget class="GtkAboutDialog" id="aboutdialog1">
+  <property name="destroy_with_parent">False</property>
+  <property name="name" translatable="yes">PSPPire</property>
+  <property name="copyright" translatable="yes">Free Software Foundation</property>
+  <property name="comments" translatable="yes">This is pre-alpha software.  It probably won't work.</property>
+  <property name="license" translatable="yes">    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA.
+</property>
+  <property name="website">http://www.gnu.org/software/pspp</property>
+  <property name="authors">John Darrington</property>
+  <property name="artists">Patrick Brunier</property>
+  <property name="translator_credits" translatable="yes" comments="TRANSLATORS: Replace this string with your names, one name per line.">translator-credits</property>
+  <property name="logo">pspplogo.png</property>
+</widget>
+
+<widget class="GtkWindow" id="var_type_dialog">
+  <property name="border_width">6</property>
+  <property name="title" translatable="yes">Variable Type</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">True</property>
+  <property name="default_width">485</property>
+  <property name="resizable">False</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">True</property>
+  <property name="skip_pager_hint">True</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+
+  <child>
+    <widget class="GtkHBox" id="hbox1">
+      <property name="border_width">5</property>
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">5</property>
+
+      <child>
+       <widget class="GtkVBox" id="vbox2">
+         <property name="border_width">13</property>
+         <property name="visible">True</property>
+         <property name="homogeneous">True</property>
+         <property name="spacing">0</property>
+
+         <child>
+           <widget class="GtkRadioButton" id="radiobutton1">
+             <property name="visible">True</property>
+             <property name="can_focus">True</property>
+             <property name="label" translatable="yes">Numeric</property>
+             <property name="use_underline">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+             <property name="active">True</property>
+             <property name="inconsistent">False</property>
+             <property name="draw_indicator">True</property>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">False</property>
+             <property name="fill">False</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkRadioButton" id="radiobutton2">
+             <property name="visible">True</property>
+             <property name="can_focus">True</property>
+             <property name="label" translatable="yes">Comma</property>
+             <property name="use_underline">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+             <property name="active">False</property>
+             <property name="inconsistent">False</property>
+             <property name="draw_indicator">True</property>
+             <property name="group">radiobutton1</property>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">False</property>
+             <property name="fill">False</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkRadioButton" id="radiobutton3">
+             <property name="visible">True</property>
+             <property name="can_focus">True</property>
+             <property name="label" translatable="yes">Dot</property>
+             <property name="use_underline">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+             <property name="active">False</property>
+             <property name="inconsistent">False</property>
+             <property name="draw_indicator">True</property>
+             <property name="group">radiobutton1</property>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">False</property>
+             <property name="fill">False</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkRadioButton" id="radiobutton4">
+             <property name="visible">True</property>
+             <property name="can_focus">True</property>
+             <property name="label" translatable="yes">Scientific notation</property>
+             <property name="use_underline">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+             <property name="active">False</property>
+             <property name="inconsistent">False</property>
+             <property name="draw_indicator">True</property>
+             <property name="group">radiobutton1</property>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">False</property>
+             <property name="fill">False</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkRadioButton" id="radiobutton5">
+             <property name="visible">True</property>
+             <property name="can_focus">True</property>
+             <property name="label" translatable="yes">Date</property>
+             <property name="use_underline">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+             <property name="active">False</property>
+             <property name="inconsistent">False</property>
+             <property name="draw_indicator">True</property>
+             <property name="group">radiobutton1</property>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">False</property>
+             <property name="fill">False</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkRadioButton" id="radiobutton6">
+             <property name="visible">True</property>
+             <property name="can_focus">True</property>
+             <property name="label" translatable="yes">Dollar</property>
+             <property name="use_underline">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+             <property name="active">False</property>
+             <property name="inconsistent">False</property>
+             <property name="draw_indicator">True</property>
+             <property name="group">radiobutton1</property>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">False</property>
+             <property name="fill">False</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkRadioButton" id="radiobutton7">
+             <property name="visible">True</property>
+             <property name="can_focus">True</property>
+             <property name="label" translatable="yes">Custom currency</property>
+             <property name="use_underline">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+             <property name="active">False</property>
+             <property name="inconsistent">False</property>
+             <property name="draw_indicator">True</property>
+             <property name="group">radiobutton1</property>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">False</property>
+             <property name="fill">False</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkRadioButton" id="radiobutton8">
+             <property name="visible">True</property>
+             <property name="can_focus">True</property>
+             <property name="label" translatable="yes">String</property>
+             <property name="use_underline">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+             <property name="active">False</property>
+             <property name="inconsistent">False</property>
+             <property name="draw_indicator">True</property>
+             <property name="group">radiobutton1</property>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">False</property>
+             <property name="fill">False</property>
+           </packing>
+         </child>
+       </widget>
+       <packing>
+         <property name="padding">0</property>
+         <property name="expand">False</property>
+         <property name="fill">False</property>
+       </packing>
+      </child>
+
+      <child>
+       <widget class="GtkVBox" id="middle_box">
+         <property name="visible">True</property>
+         <property name="homogeneous">False</property>
+         <property name="spacing">10</property>
+
+         <child>
+           <widget class="GtkScrolledWindow" id="scrolledwindow4">
+             <property name="width_request">20</property>
+             <property name="height_request">194</property>
+             <property name="can_focus">True</property>
+             <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+             <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+             <property name="shadow_type">GTK_SHADOW_IN</property>
+             <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+             <child>
+               <widget class="GtkTreeView" id="date_format_list_view">
+                 <property name="visible">True</property>
+                 <property name="can_focus">True</property>
+                 <property name="headers_visible">False</property>
+                 <property name="rules_hint">False</property>
+                 <property name="reorderable">False</property>
+                 <property name="enable_search">True</property>
+                 <property name="fixed_height_mode">False</property>
+                 <property name="hover_selection">False</property>
+                 <property name="hover_expand">False</property>
+               </widget>
+             </child>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">False</property>
+             <property name="fill">False</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkHBox" id="custom_currency_hbox">
+             <property name="homogeneous">False</property>
+             <property name="spacing">15</property>
+
+             <child>
+               <widget class="GtkScrolledWindow" id="scrolledwindow5">
+                 <property name="width_request">1</property>
+                 <property name="height_request">120</property>
+                 <property name="visible">True</property>
+                 <property name="can_focus">True</property>
+                 <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+                 <property name="vscrollbar_policy">GTK_POLICY_NEVER</property>
+                 <property name="shadow_type">GTK_SHADOW_IN</property>
+                 <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+                 <child>
+                   <widget class="GtkTreeView" id="custom_treeview">
+                     <property name="visible">True</property>
+                     <property name="can_focus">True</property>
+                     <property name="headers_visible">False</property>
+                     <property name="rules_hint">False</property>
+                     <property name="reorderable">False</property>
+                     <property name="enable_search">True</property>
+                     <property name="fixed_height_mode">False</property>
+                     <property name="hover_selection">False</property>
+                     <property name="hover_expand">False</property>
+                   </widget>
+                 </child>
+               </widget>
+               <packing>
+                 <property name="padding">0</property>
+                 <property name="expand">True</property>
+                 <property name="fill">True</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkFrame" id="Sample">
+                 <property name="visible">True</property>
+                 <property name="label_xalign">0</property>
+                 <property name="label_yalign">0.5</property>
+                 <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+
+                 <child>
+                   <widget class="GtkAlignment" id="alignment2">
+                     <property name="visible">True</property>
+                     <property name="xalign">0.5</property>
+                     <property name="yalign">0.5</property>
+                     <property name="xscale">1</property>
+                     <property name="yscale">1</property>
+                     <property name="top_padding">0</property>
+                     <property name="bottom_padding">0</property>
+                     <property name="left_padding">12</property>
+                     <property name="right_padding">0</property>
+
+                     <child>
+                       <widget class="GtkVBox" id="vbox10">
+                         <property name="visible">True</property>
+                         <property name="homogeneous">True</property>
+                         <property name="spacing">0</property>
+
+                         <child>
+                           <widget class="GtkLabel" id="psample_label">
+                             <property name="visible">True</property>
+                             <property name="label" translatable="yes">positive</property>
+                             <property name="use_underline">False</property>
+                             <property name="use_markup">False</property>
+                             <property name="justify">GTK_JUSTIFY_LEFT</property>
+                             <property name="wrap">False</property>
+                             <property name="selectable">False</property>
+                             <property name="xalign">0.5</property>
+                             <property name="yalign">0.5</property>
+                             <property name="xpad">0</property>
+                             <property name="ypad">0</property>
+                             <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+                             <property name="width_chars">-1</property>
+                             <property name="single_line_mode">False</property>
+                             <property name="angle">0</property>
+                           </widget>
+                           <packing>
+                             <property name="padding">0</property>
+                             <property name="expand">True</property>
+                             <property name="fill">True</property>
+                           </packing>
+                         </child>
+
+                         <child>
+                           <widget class="GtkLabel" id="nsample_label">
+                             <property name="visible">True</property>
+                             <property name="label" translatable="yes">negative</property>
+                             <property name="use_underline">False</property>
+                             <property name="use_markup">False</property>
+                             <property name="justify">GTK_JUSTIFY_LEFT</property>
+                             <property name="wrap">False</property>
+                             <property name="selectable">False</property>
+                             <property name="xalign">0.5</property>
+                             <property name="yalign">0.5</property>
+                             <property name="xpad">0</property>
+                             <property name="ypad">0</property>
+                             <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+                             <property name="width_chars">-1</property>
+                             <property name="single_line_mode">False</property>
+                             <property name="angle">0</property>
+                           </widget>
+                           <packing>
+                             <property name="padding">0</property>
+                             <property name="expand">True</property>
+                             <property name="fill">True</property>
+                           </packing>
+                         </child>
+                       </widget>
+                     </child>
+                   </widget>
+                 </child>
+
+                 <child>
+                   <widget class="GtkLabel" id="label13">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">Sample</property>
+                     <property name="use_underline">False</property>
+                     <property name="use_markup">True</property>
+                     <property name="justify">GTK_JUSTIFY_LEFT</property>
+                     <property name="wrap">False</property>
+                     <property name="selectable">False</property>
+                     <property name="xalign">0.5</property>
+                     <property name="yalign">0.5</property>
+                     <property name="xpad">0</property>
+                     <property name="ypad">0</property>
+                     <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+                     <property name="width_chars">-1</property>
+                     <property name="single_line_mode">False</property>
+                     <property name="angle">0</property>
+                   </widget>
+                   <packing>
+                     <property name="type">label_item</property>
+                   </packing>
+                 </child>
+               </widget>
+               <packing>
+                 <property name="padding">0</property>
+                 <property name="expand">True</property>
+                 <property name="fill">True</property>
+                 <property name="pack_type">GTK_PACK_END</property>
+               </packing>
+             </child>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">True</property>
+             <property name="fill">True</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkScrolledWindow" id="dollar_window">
+             <property name="can_focus">True</property>
+             <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+             <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+             <property name="shadow_type">GTK_SHADOW_IN</property>
+             <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+             <child>
+               <widget class="GtkTreeView" id="dollar_treeview">
+                 <property name="visible">True</property>
+                 <property name="can_focus">True</property>
+                 <property name="headers_visible">False</property>
+                 <property name="rules_hint">False</property>
+                 <property name="reorderable">False</property>
+                 <property name="enable_search">True</property>
+                 <property name="fixed_height_mode">False</property>
+                 <property name="hover_selection">False</property>
+                 <property name="hover_expand">False</property>
+               </widget>
+             </child>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">True</property>
+             <property name="fill">True</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkTable" id="width_decimals">
+             <property name="width_request">100</property>
+             <property name="height_request">50</property>
+             <property name="visible">True</property>
+             <property name="n_rows">2</property>
+             <property name="n_columns">2</property>
+             <property name="homogeneous">False</property>
+             <property name="row_spacing">1</property>
+             <property name="column_spacing">2</property>
+
+             <child>
+               <widget class="GtkLabel" id="decimals_label">
+                 <property name="visible">True</property>
+                 <property name="label" translatable="yes">Decimal Places:</property>
+                 <property name="use_underline">False</property>
+                 <property name="use_markup">False</property>
+                 <property name="justify">GTK_JUSTIFY_RIGHT</property>
+                 <property name="wrap">False</property>
+                 <property name="selectable">False</property>
+                 <property name="xalign">0</property>
+                 <property name="yalign">0.5</property>
+                 <property name="xpad">0</property>
+                 <property name="ypad">0</property>
+                 <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+                 <property name="width_chars">-1</property>
+                 <property name="single_line_mode">False</property>
+                 <property name="angle">0</property>
+               </widget>
+               <packing>
+                 <property name="left_attach">0</property>
+                 <property name="right_attach">1</property>
+                 <property name="top_attach">1</property>
+                 <property name="bottom_attach">2</property>
+                 <property name="x_options">fill</property>
+                 <property name="y_options"></property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkEntry" id="width_entry">
+                 <property name="width_request">25</property>
+                 <property name="can_focus">True</property>
+                 <property name="editable">True</property>
+                 <property name="visibility">True</property>
+                 <property name="max_length">0</property>
+                 <property name="text" translatable="yes"></property>
+                 <property name="has_frame">True</property>
+                 <property name="invisible_char">*</property>
+                 <property name="activates_default">False</property>
+               </widget>
+               <packing>
+                 <property name="left_attach">1</property>
+                 <property name="right_attach">2</property>
+                 <property name="top_attach">0</property>
+                 <property name="bottom_attach">1</property>
+                 <property name="y_options"></property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkEntry" id="decimals_entry">
+                 <property name="width_request">25</property>
+                 <property name="visible">True</property>
+                 <property name="can_focus">True</property>
+                 <property name="editable">True</property>
+                 <property name="visibility">True</property>
+                 <property name="max_length">0</property>
+                 <property name="text" translatable="yes"></property>
+                 <property name="has_frame">True</property>
+                 <property name="invisible_char">*</property>
+                 <property name="activates_default">False</property>
+               </widget>
+               <packing>
+                 <property name="left_attach">1</property>
+                 <property name="right_attach">2</property>
+                 <property name="top_attach">1</property>
+                 <property name="bottom_attach">2</property>
+                 <property name="y_options"></property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkHBox" id="hbox2">
+                 <property name="visible">True</property>
+                 <property name="homogeneous">False</property>
+                 <property name="spacing">0</property>
+
+                 <child>
+                   <widget class="GtkLabel" id="width_label">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">Width:</property>
+                     <property name="use_underline">False</property>
+                     <property name="use_markup">False</property>
+                     <property name="justify">GTK_JUSTIFY_RIGHT</property>
+                     <property name="wrap">False</property>
+                     <property name="selectable">False</property>
+                     <property name="xalign">0.5</property>
+                     <property name="yalign">0.5</property>
+                     <property name="xpad">0</property>
+                     <property name="ypad">0</property>
+                     <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+                     <property name="width_chars">-1</property>
+                     <property name="single_line_mode">False</property>
+                     <property name="angle">0</property>
+                   </widget>
+                   <packing>
+                     <property name="padding">0</property>
+                     <property name="expand">False</property>
+                     <property name="fill">False</property>
+                     <property name="pack_type">GTK_PACK_END</property>
+                   </packing>
+                 </child>
+               </widget>
+               <packing>
+                 <property name="left_attach">0</property>
+                 <property name="right_attach">1</property>
+                 <property name="top_attach">0</property>
+                 <property name="bottom_attach">1</property>
+                 <property name="x_options">fill</property>
+                 <property name="y_options">fill</property>
+               </packing>
+             </child>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">True</property>
+             <property name="fill">True</property>
+           </packing>
+         </child>
+       </widget>
+       <packing>
+         <property name="padding">0</property>
+         <property name="expand">True</property>
+         <property name="fill">False</property>
+       </packing>
+      </child>
+
+      <child>
+       <widget class="GtkVButtonBox" id="vbuttonbox6">
+         <property name="visible">True</property>
+         <property name="layout_style">GTK_BUTTONBOX_START</property>
+         <property name="spacing">5</property>
+
+         <child>
+           <widget class="GtkButton" id="var_type_ok">
+             <property name="visible">True</property>
+             <property name="can_default">True</property>
+             <property name="can_focus">True</property>
+             <property name="label">gtk-ok</property>
+             <property name="use_stock">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+           </widget>
+         </child>
+
+         <child>
+           <widget class="GtkButton" id="var_type_cancel">
+             <property name="visible">True</property>
+             <property name="can_default">True</property>
+             <property name="can_focus">True</property>
+             <property name="label">gtk-cancel</property>
+             <property name="use_stock">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+             <signal name="clicked" handler="on_var_type_cancel_clicked" object="var_type_dialog" last_modification_time="Mon, 31 Oct 2005 09:49:33 GMT"/>
+           </widget>
+         </child>
+
+         <child>
+           <widget class="GtkButton" id="var_type_help">
+             <property name="visible">True</property>
+             <property name="can_default">True</property>
+             <property name="can_focus">True</property>
+             <property name="label">gtk-help</property>
+             <property name="use_stock">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+           </widget>
+         </child>
+       </widget>
+       <packing>
+         <property name="padding">0</property>
+         <property name="expand">True</property>
+         <property name="fill">True</property>
+       </packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+<widget class="GtkWindow" id="val_labs_dialog">
+  <property name="title" translatable="yes">Value Labels</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">True</property>
+  <property name="resizable">False</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">True</property>
+  <property name="skip_pager_hint">True</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+
+  <child>
+    <widget class="GtkHBox" id="hbox3">
+      <property name="border_width">5</property>
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">0</property>
+
+      <child>
+       <widget class="GtkFrame" id="frame1">
+         <property name="visible">True</property>
+         <property name="label_xalign">0</property>
+         <property name="label_yalign">0.5</property>
+         <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+
+         <child>
+           <widget class="GtkAlignment" id="alignment1">
+             <property name="border_width">8</property>
+             <property name="visible">True</property>
+             <property name="xalign">0.5</property>
+             <property name="yalign">0.5</property>
+             <property name="xscale">1</property>
+             <property name="yscale">1</property>
+             <property name="top_padding">0</property>
+             <property name="bottom_padding">0</property>
+             <property name="left_padding">12</property>
+             <property name="right_padding">0</property>
+
+             <child>
+               <widget class="GtkTable" id="table3">
+                 <property name="visible">True</property>
+                 <property name="n_rows">2</property>
+                 <property name="n_columns">2</property>
+                 <property name="homogeneous">False</property>
+                 <property name="row_spacing">5</property>
+                 <property name="column_spacing">0</property>
+
+                 <child>
+                   <widget class="GtkVButtonBox" id="vbuttonbox2">
+                     <property name="border_width">5</property>
+                     <property name="visible">True</property>
+                     <property name="layout_style">GTK_BUTTONBOX_DEFAULT_STYLE</property>
+                     <property name="spacing">0</property>
+
+                     <child>
+                       <widget class="GtkButton" id="val_labs_add">
+                         <property name="visible">True</property>
+                         <property name="sensitive">False</property>
+                         <property name="can_default">True</property>
+                         <property name="can_focus">True</property>
+                         <property name="label">gtk-add</property>
+                         <property name="use_stock">True</property>
+                         <property name="relief">GTK_RELIEF_NORMAL</property>
+                         <property name="focus_on_click">True</property>
+                       </widget>
+                     </child>
+
+                     <child>
+                       <widget class="GtkButton" id="val_labs_change">
+                         <property name="visible">True</property>
+                         <property name="sensitive">False</property>
+                         <property name="can_default">True</property>
+                         <property name="can_focus">True</property>
+                         <property name="label">gtk-apply</property>
+                         <property name="use_stock">True</property>
+                         <property name="relief">GTK_RELIEF_NORMAL</property>
+                         <property name="focus_on_click">True</property>
+                       </widget>
+                     </child>
+
+                     <child>
+                       <widget class="GtkButton" id="val_labs_remove">
+                         <property name="visible">True</property>
+                         <property name="sensitive">False</property>
+                         <property name="can_default">True</property>
+                         <property name="can_focus">True</property>
+                         <property name="label">gtk-remove</property>
+                         <property name="use_stock">True</property>
+                         <property name="relief">GTK_RELIEF_NORMAL</property>
+                         <property name="focus_on_click">True</property>
+                       </widget>
+                     </child>
+                   </widget>
+                   <packing>
+                     <property name="left_attach">0</property>
+                     <property name="right_attach">1</property>
+                     <property name="top_attach">1</property>
+                     <property name="bottom_attach">2</property>
+                     <property name="x_options">fill</property>
+                   </packing>
+                 </child>
+
+                 <child>
+                   <widget class="GtkTable" id="table4">
+                     <property name="border_width">5</property>
+                     <property name="visible">True</property>
+                     <property name="n_rows">2</property>
+                     <property name="n_columns">2</property>
+                     <property name="homogeneous">False</property>
+                     <property name="row_spacing">4</property>
+                     <property name="column_spacing">5</property>
+
+                     <child>
+                       <widget class="GtkLabel" id="label5">
+                         <property name="visible">True</property>
+                         <property name="label" translatable="yes">Value:</property>
+                         <property name="use_underline">False</property>
+                         <property name="use_markup">False</property>
+                         <property name="justify">GTK_JUSTIFY_LEFT</property>
+                         <property name="wrap">False</property>
+                         <property name="selectable">False</property>
+                         <property name="xalign">0</property>
+                         <property name="yalign">0.5</property>
+                         <property name="xpad">0</property>
+                         <property name="ypad">0</property>
+                         <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+                         <property name="width_chars">-1</property>
+                         <property name="single_line_mode">False</property>
+                         <property name="angle">0</property>
+                       </widget>
+                       <packing>
+                         <property name="left_attach">0</property>
+                         <property name="right_attach">1</property>
+                         <property name="top_attach">0</property>
+                         <property name="bottom_attach">1</property>
+                         <property name="x_options">fill</property>
+                         <property name="y_options"></property>
+                       </packing>
+                     </child>
+
+                     <child>
+                       <widget class="GtkLabel" id="label6">
+                         <property name="visible">True</property>
+                         <property name="label" translatable="yes">Value Label:</property>
+                         <property name="use_underline">False</property>
+                         <property name="use_markup">False</property>
+                         <property name="justify">GTK_JUSTIFY_LEFT</property>
+                         <property name="wrap">False</property>
+                         <property name="selectable">False</property>
+                         <property name="xalign">0</property>
+                         <property name="yalign">0.5</property>
+                         <property name="xpad">0</property>
+                         <property name="ypad">0</property>
+                         <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+                         <property name="width_chars">-1</property>
+                         <property name="single_line_mode">False</property>
+                         <property name="angle">0</property>
+                       </widget>
+                       <packing>
+                         <property name="left_attach">0</property>
+                         <property name="right_attach">1</property>
+                         <property name="top_attach">1</property>
+                         <property name="bottom_attach">2</property>
+                         <property name="x_options">fill</property>
+                         <property name="y_options"></property>
+                       </packing>
+                     </child>
+
+                     <child>
+                       <widget class="GtkEntry" id="label_entry">
+                         <property name="visible">True</property>
+                         <property name="can_focus">True</property>
+                         <property name="editable">True</property>
+                         <property name="visibility">True</property>
+                         <property name="max_length">0</property>
+                         <property name="text" translatable="yes"></property>
+                         <property name="has_frame">True</property>
+                         <property name="invisible_char">*</property>
+                         <property name="activates_default">False</property>
+                       </widget>
+                       <packing>
+                         <property name="left_attach">1</property>
+                         <property name="right_attach">2</property>
+                         <property name="top_attach">1</property>
+                         <property name="bottom_attach">2</property>
+                         <property name="y_options"></property>
+                       </packing>
+                     </child>
+
+                     <child>
+                       <widget class="GtkHBox" id="hbox4">
+                         <property name="visible">True</property>
+                         <property name="homogeneous">False</property>
+                         <property name="spacing">0</property>
+
+                         <child>
+                           <widget class="GtkEntry" id="value_entry">
+                             <property name="width_request">85</property>
+                             <property name="visible">True</property>
+                             <property name="can_focus">True</property>
+                             <property name="editable">True</property>
+                             <property name="visibility">True</property>
+                             <property name="max_length">0</property>
+                             <property name="text" translatable="yes"></property>
+                             <property name="has_frame">True</property>
+                             <property name="invisible_char">*</property>
+                             <property name="activates_default">False</property>
+                           </widget>
+                           <packing>
+                             <property name="padding">1</property>
+                             <property name="expand">False</property>
+                             <property name="fill">False</property>
+                           </packing>
+                         </child>
+                       </widget>
+                       <packing>
+                         <property name="left_attach">1</property>
+                         <property name="right_attach">2</property>
+                         <property name="top_attach">0</property>
+                         <property name="bottom_attach">1</property>
+                         <property name="x_options">fill</property>
+                         <property name="y_options">fill</property>
+                       </packing>
+                     </child>
+                   </widget>
+                   <packing>
+                     <property name="left_attach">0</property>
+                     <property name="right_attach">2</property>
+                     <property name="top_attach">0</property>
+                     <property name="bottom_attach">1</property>
+                     <property name="x_options">fill</property>
+                   </packing>
+                 </child>
+
+                 <child>
+                   <widget class="GtkScrolledWindow" id="scrolledwindow3">
+                     <property name="visible">True</property>
+                     <property name="can_focus">True</property>
+                     <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+                     <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+                     <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+                     <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+                     <child>
+                       <widget class="GtkTreeView" id="treeview1">
+                         <property name="visible">True</property>
+                         <property name="can_focus">True</property>
+                         <property name="headers_visible">False</property>
+                         <property name="rules_hint">False</property>
+                         <property name="reorderable">False</property>
+                         <property name="enable_search">False</property>
+                         <property name="fixed_height_mode">False</property>
+                         <property name="hover_selection">False</property>
+                         <property name="hover_expand">False</property>
+                       </widget>
+                     </child>
+                   </widget>
+                   <packing>
+                     <property name="left_attach">1</property>
+                     <property name="right_attach">2</property>
+                     <property name="top_attach">1</property>
+                     <property name="bottom_attach">2</property>
+                     <property name="x_options">fill</property>
+                     <property name="y_options">fill</property>
+                   </packing>
+                 </child>
+               </widget>
+             </child>
+           </widget>
+         </child>
+
+         <child>
+           <widget class="GtkLabel" id="label7">
+             <property name="visible">True</property>
+             <property name="label" translatable="yes">Value Labels</property>
+             <property name="use_underline">False</property>
+             <property name="use_markup">True</property>
+             <property name="justify">GTK_JUSTIFY_LEFT</property>
+             <property name="wrap">False</property>
+             <property name="selectable">False</property>
+             <property name="xalign">0.5</property>
+             <property name="yalign">0.5</property>
+             <property name="xpad">0</property>
+             <property name="ypad">0</property>
+             <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+             <property name="width_chars">-1</property>
+             <property name="single_line_mode">False</property>
+             <property name="angle">0</property>
+           </widget>
+           <packing>
+             <property name="type">label_item</property>
+           </packing>
+         </child>
+       </widget>
+       <packing>
+         <property name="padding">10</property>
+         <property name="expand">True</property>
+         <property name="fill">True</property>
+       </packing>
+      </child>
+
+      <child>
+       <widget class="GtkVButtonBox" id="vbuttonbox3">
+         <property name="border_width">5</property>
+         <property name="visible">True</property>
+         <property name="layout_style">GTK_BUTTONBOX_START</property>
+         <property name="spacing">5</property>
+
+         <child>
+           <widget class="GtkButton" id="val_labs_ok">
+             <property name="visible">True</property>
+             <property name="can_default">True</property>
+             <property name="can_focus">True</property>
+             <property name="label">gtk-ok</property>
+             <property name="use_stock">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+             <signal name="clicked" handler="gtk_widget_hide" object="val_labs_dialog" last_modification_time="Fri, 25 Nov 2005 23:15:33 GMT"/>
+           </widget>
+         </child>
+
+         <child>
+           <widget class="GtkButton" id="val_labs_cancel">
+             <property name="visible">True</property>
+             <property name="can_default">True</property>
+             <property name="can_focus">True</property>
+             <property name="label">gtk-cancel</property>
+             <property name="use_stock">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+             <signal name="clicked" handler="gtk_widget_hide" object="val_labs_dialog" last_modification_time="Fri, 25 Nov 2005 23:15:44 GMT"/>
+           </widget>
+         </child>
+
+         <child>
+           <widget class="GtkButton" id="val_labs_help">
+             <property name="visible">True</property>
+             <property name="can_default">True</property>
+             <property name="can_focus">True</property>
+             <property name="label">gtk-help</property>
+             <property name="use_stock">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+           </widget>
+         </child>
+       </widget>
+       <packing>
+         <property name="padding">0</property>
+         <property name="expand">False</property>
+         <property name="fill">False</property>
+         <property name="pack_type">GTK_PACK_END</property>
+       </packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+<widget class="GtkWindow" id="missing_values_dialog">
+  <property name="border_width">10</property>
+  <property name="title" translatable="yes">Missing Values</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">True</property>
+  <property name="resizable">False</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">True</property>
+  <property name="skip_pager_hint">True</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+
+  <child>
+    <widget class="GtkTable" id="table6">
+      <property name="visible">True</property>
+      <property name="n_rows">2</property>
+      <property name="n_columns">2</property>
+      <property name="homogeneous">False</property>
+      <property name="row_spacing">0</property>
+      <property name="column_spacing">0</property>
+
+      <child>
+       <widget class="GtkVButtonBox" id="vbuttonbox5">
+         <property name="border_width">5</property>
+         <property name="visible">True</property>
+         <property name="layout_style">GTK_BUTTONBOX_START</property>
+         <property name="spacing">5</property>
+
+         <child>
+           <widget class="GtkButton" id="missing_val_ok">
+             <property name="visible">True</property>
+             <property name="can_default">True</property>
+             <property name="can_focus">True</property>
+             <property name="label">gtk-ok</property>
+             <property name="use_stock">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+             <signal name="clicked" handler="gtk_widget_hide" object="val_labs_dialog" last_modification_time="Fri, 25 Nov 2005 23:15:33 GMT"/>
+           </widget>
+         </child>
+
+         <child>
+           <widget class="GtkButton" id="missing_val_cancel">
+             <property name="visible">True</property>
+             <property name="can_default">True</property>
+             <property name="can_focus">True</property>
+             <property name="label">gtk-cancel</property>
+             <property name="use_stock">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+             <signal name="clicked" handler="gtk_widget_hide" object="val_labs_dialog" last_modification_time="Fri, 25 Nov 2005 23:15:44 GMT"/>
+           </widget>
+         </child>
+
+         <child>
+           <widget class="GtkButton" id="missing_val_help">
+             <property name="visible">True</property>
+             <property name="can_default">True</property>
+             <property name="can_focus">True</property>
+             <property name="label">gtk-help</property>
+             <property name="use_stock">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+           </widget>
+         </child>
+       </widget>
+       <packing>
+         <property name="left_attach">1</property>
+         <property name="right_attach">2</property>
+         <property name="top_attach">0</property>
+         <property name="bottom_attach">1</property>
+       </packing>
+      </child>
+
+      <child>
+       <widget class="GtkVBox" id="vbox5">
+         <property name="visible">True</property>
+         <property name="homogeneous">False</property>
+         <property name="spacing">12</property>
+
+         <child>
+           <widget class="GtkRadioButton" id="no_missing">
+             <property name="visible">True</property>
+             <property name="can_focus">True</property>
+             <property name="label" translatable="yes">_No missing values</property>
+             <property name="use_underline">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">True</property>
+             <property name="active">True</property>
+             <property name="inconsistent">False</property>
+             <property name="draw_indicator">True</property>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">False</property>
+             <property name="fill">False</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkVBox" id="vbox6">
+             <property name="visible">True</property>
+             <property name="homogeneous">False</property>
+             <property name="spacing">0</property>
+
+             <child>
+               <widget class="GtkRadioButton" id="discrete_missing">
+                 <property name="visible">True</property>
+                 <property name="can_focus">True</property>
+                 <property name="label" translatable="yes">_Discrete missing values</property>
+                 <property name="use_underline">True</property>
+                 <property name="relief">GTK_RELIEF_NORMAL</property>
+                 <property name="focus_on_click">False</property>
+                 <property name="active">False</property>
+                 <property name="inconsistent">False</property>
+                 <property name="draw_indicator">True</property>
+                 <property name="group">no_missing</property>
+               </widget>
+               <packing>
+                 <property name="padding">0</property>
+                 <property name="expand">False</property>
+                 <property name="fill">False</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkHBox" id="hbox10">
+                 <property name="visible">True</property>
+                 <property name="homogeneous">False</property>
+                 <property name="spacing">0</property>
+
+                 <child>
+                   <widget class="GtkHBox" id="hbox5">
+                     <property name="border_width">5</property>
+                     <property name="visible">True</property>
+                     <property name="homogeneous">True</property>
+                     <property name="spacing">5</property>
+
+                     <child>
+                       <widget class="GtkEntry" id="mv0">
+                         <property name="width_request">75</property>
+                         <property name="visible">True</property>
+                         <property name="can_focus">True</property>
+                         <property name="editable">True</property>
+                         <property name="visibility">True</property>
+                         <property name="max_length">0</property>
+                         <property name="text" translatable="yes"></property>
+                         <property name="has_frame">True</property>
+                         <property name="invisible_char">*</property>
+                         <property name="activates_default">False</property>
+                       </widget>
+                       <packing>
+                         <property name="padding">0</property>
+                         <property name="expand">False</property>
+                         <property name="fill">False</property>
+                       </packing>
+                     </child>
+
+                     <child>
+                       <widget class="GtkEntry" id="mv1">
+                         <property name="width_request">75</property>
+                         <property name="visible">True</property>
+                         <property name="can_focus">True</property>
+                         <property name="editable">True</property>
+                         <property name="visibility">True</property>
+                         <property name="max_length">0</property>
+                         <property name="text" translatable="yes"></property>
+                         <property name="has_frame">True</property>
+                         <property name="invisible_char">*</property>
+                         <property name="activates_default">False</property>
+                       </widget>
+                       <packing>
+                         <property name="padding">0</property>
+                         <property name="expand">False</property>
+                         <property name="fill">False</property>
+                       </packing>
+                     </child>
+
+                     <child>
+                       <widget class="GtkEntry" id="mv2">
+                         <property name="width_request">75</property>
+                         <property name="visible">True</property>
+                         <property name="can_focus">True</property>
+                         <property name="editable">True</property>
+                         <property name="visibility">True</property>
+                         <property name="max_length">0</property>
+                         <property name="text" translatable="yes"></property>
+                         <property name="has_frame">True</property>
+                         <property name="invisible_char">*</property>
+                         <property name="activates_default">False</property>
+                       </widget>
+                       <packing>
+                         <property name="padding">0</property>
+                         <property name="expand">False</property>
+                         <property name="fill">False</property>
+                       </packing>
+                     </child>
+                   </widget>
+                   <packing>
+                     <property name="padding">20</property>
+                     <property name="expand">True</property>
+                     <property name="fill">True</property>
+                   </packing>
+                 </child>
+               </widget>
+               <packing>
+                 <property name="padding">0</property>
+                 <property name="expand">True</property>
+                 <property name="fill">True</property>
+               </packing>
+             </child>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">False</property>
+             <property name="fill">False</property>
+           </packing>
+         </child>
+       </widget>
+       <packing>
+         <property name="left_attach">0</property>
+         <property name="right_attach">1</property>
+         <property name="top_attach">0</property>
+         <property name="bottom_attach">1</property>
+         <property name="y_options">fill</property>
+       </packing>
+      </child>
+
+      <child>
+       <widget class="GtkVBox" id="vbox7">
+         <property name="visible">True</property>
+         <property name="homogeneous">False</property>
+         <property name="spacing">0</property>
+
+         <child>
+           <widget class="GtkRadioButton" id="range_missing">
+             <property name="visible">True</property>
+             <property name="can_focus">True</property>
+             <property name="label" translatable="yes">_Range plus one optional discrete missing value</property>
+             <property name="use_underline">True</property>
+             <property name="relief">GTK_RELIEF_NORMAL</property>
+             <property name="focus_on_click">False</property>
+             <property name="active">False</property>
+             <property name="inconsistent">False</property>
+             <property name="draw_indicator">True</property>
+             <property name="group">no_missing</property>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">False</property>
+             <property name="fill">False</property>
+           </packing>
+         </child>
+
+         <child>
+           <widget class="GtkVBox" id="vbox8">
+             <property name="visible">True</property>
+             <property name="homogeneous">False</property>
+             <property name="spacing">5</property>
+
+             <child>
+               <widget class="GtkHBox" id="hbox7">
+                 <property name="visible">True</property>
+                 <property name="homogeneous">False</property>
+                 <property name="spacing">0</property>
+
+                 <child>
+                   <widget class="GtkHBox" id="hbox8">
+                     <property name="visible">True</property>
+                     <property name="homogeneous">False</property>
+                     <property name="spacing">0</property>
+
+                     <child>
+                       <widget class="GtkLabel" id="label11">
+                         <property name="visible">True</property>
+                         <property name="label" translatable="yes">_Low:</property>
+                         <property name="use_underline">True</property>
+                         <property name="use_markup">False</property>
+                         <property name="justify">GTK_JUSTIFY_LEFT</property>
+                         <property name="wrap">False</property>
+                         <property name="selectable">False</property>
+                         <property name="xalign">0.5</property>
+                         <property name="yalign">0.5</property>
+                         <property name="xpad">0</property>
+                         <property name="ypad">0</property>
+                         <property name="mnemonic_widget">mv-low</property>
+                         <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+                         <property name="width_chars">-1</property>
+                         <property name="single_line_mode">False</property>
+                         <property name="angle">0</property>
+                       </widget>
+                       <packing>
+                         <property name="padding">20</property>
+                         <property name="expand">False</property>
+                         <property name="fill">False</property>
+                       </packing>
+                     </child>
+
+                     <child>
+                       <widget class="GtkEntry" id="mv-low">
+                         <property name="width_request">75</property>
+                         <property name="visible">True</property>
+                         <property name="can_focus">True</property>
+                         <property name="editable">True</property>
+                         <property name="visibility">True</property>
+                         <property name="max_length">0</property>
+                         <property name="text" translatable="yes"></property>
+                         <property name="has_frame">True</property>
+                         <property name="invisible_char">*</property>
+                         <property name="activates_default">False</property>
+                       </widget>
+                       <packing>
+                         <property name="padding">0</property>
+                         <property name="expand">False</property>
+                         <property name="fill">True</property>
+                       </packing>
+                     </child>
+                   </widget>
+                   <packing>
+                     <property name="padding">0</property>
+                     <property name="expand">True</property>
+                     <property name="fill">True</property>
+                   </packing>
+                 </child>
+
+                 <child>
+                   <widget class="GtkHBox" id="hbox9">
+                     <property name="visible">True</property>
+                     <property name="homogeneous">False</property>
+                     <property name="spacing">0</property>
+
+                     <child>
+                       <widget class="GtkLabel" id="label12">
+                         <property name="visible">True</property>
+                         <property name="label" translatable="yes">_High:</property>
+                         <property name="use_underline">True</property>
+                         <property name="use_markup">False</property>
+                         <property name="justify">GTK_JUSTIFY_LEFT</property>
+                         <property name="wrap">False</property>
+                         <property name="selectable">False</property>
+                         <property name="xalign">0.5</property>
+                         <property name="yalign">0.5</property>
+                         <property name="xpad">0</property>
+                         <property name="ypad">0</property>
+                         <property name="mnemonic_widget">mv-high</property>
+                         <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+                         <property name="width_chars">-1</property>
+                         <property name="single_line_mode">False</property>
+                         <property name="angle">0</property>
+                       </widget>
+                       <packing>
+                         <property name="padding">0</property>
+                         <property name="expand">False</property>
+                         <property name="fill">False</property>
+                       </packing>
+                     </child>
+
+                     <child>
+                       <widget class="GtkEntry" id="mv-high">
+                         <property name="width_request">75</property>
+                         <property name="visible">True</property>
+                         <property name="can_focus">True</property>
+                         <property name="editable">True</property>
+                         <property name="visibility">True</property>
+                         <property name="max_length">0</property>
+                         <property name="text" translatable="yes"></property>
+                         <property name="has_frame">True</property>
+                         <property name="invisible_char">*</property>
+                         <property name="activates_default">False</property>
+                       </widget>
+                       <packing>
+                         <property name="padding">5</property>
+                         <property name="expand">True</property>
+                         <property name="fill">True</property>
+                       </packing>
+                     </child>
+                   </widget>
+                   <packing>
+                     <property name="padding">20</property>
+                     <property name="expand">True</property>
+                     <property name="fill">False</property>
+                   </packing>
+                 </child>
+
+                 <child>
+                   <placeholder/>
+                 </child>
+
+                 <child>
+                   <placeholder/>
+                 </child>
+               </widget>
+               <packing>
+                 <property name="padding">0</property>
+                 <property name="expand">True</property>
+                 <property name="fill">True</property>
+               </packing>
+             </child>
+
+             <child>
+               <widget class="GtkHBox" id="hbox6">
+                 <property name="visible">True</property>
+                 <property name="homogeneous">False</property>
+                 <property name="spacing">0</property>
+
+                 <child>
+                   <widget class="GtkLabel" id="label10">
+                     <property name="visible">True</property>
+                     <property name="label" translatable="yes">Di_screte value:</property>
+                     <property name="use_underline">True</property>
+                     <property name="use_markup">False</property>
+                     <property name="justify">GTK_JUSTIFY_LEFT</property>
+                     <property name="wrap">False</property>
+                     <property name="selectable">False</property>
+                     <property name="xalign">0.5</property>
+                     <property name="yalign">0.5</property>
+                     <property name="xpad">0</property>
+                     <property name="ypad">0</property>
+                     <property name="mnemonic_widget">mv-discrete</property>
+                     <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+                     <property name="width_chars">-1</property>
+                     <property name="single_line_mode">False</property>
+                     <property name="angle">0</property>
+                   </widget>
+                   <packing>
+                     <property name="padding">20</property>
+                     <property name="expand">False</property>
+                     <property name="fill">False</property>
+                   </packing>
+                 </child>
+
+                 <child>
+                   <widget class="GtkEntry" id="mv-discrete">
+                     <property name="width_request">75</property>
+                     <property name="visible">True</property>
+                     <property name="can_focus">True</property>
+                     <property name="editable">True</property>
+                     <property name="visibility">True</property>
+                     <property name="max_length">0</property>
+                     <property name="text" translatable="yes"></property>
+                     <property name="has_frame">True</property>
+                     <property name="invisible_char">*</property>
+                     <property name="activates_default">False</property>
+                   </widget>
+                   <packing>
+                     <property name="padding">0</property>
+                     <property name="expand">False</property>
+                     <property name="fill">True</property>
+                   </packing>
+                 </child>
+               </widget>
+               <packing>
+                 <property name="padding">0</property>
+                 <property name="expand">True</property>
+                 <property name="fill">True</property>
+               </packing>
+             </child>
+           </widget>
+           <packing>
+             <property name="padding">0</property>
+             <property name="expand">True</property>
+             <property name="fill">True</property>
+           </packing>
+         </child>
+       </widget>
+       <packing>
+         <property name="left_attach">0</property>
+         <property name="right_attach">2</property>
+         <property name="top_attach">1</property>
+         <property name="bottom_attach">2</property>
+         <property name="x_options">fill</property>
+       </packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+</glade-interface>
diff --git a/src/ui/gui/pspplogo.png b/src/ui/gui/pspplogo.png
new file mode 100644 (file)
index 0000000..0f32b67
Binary files /dev/null and b/src/ui/gui/pspplogo.png differ
diff --git a/src/ui/gui/val-labs-dialog.c b/src/ui/gui/val-labs-dialog.c
new file mode 100644 (file)
index 0000000..d5a8256
--- /dev/null
@@ -0,0 +1,452 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2005  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+
+/*  This module describes the behaviour of the Value Labels dialog box,
+    used for input of the value labels in the variable sheet */
+
+#include <string.h>
+
+#include "helper.h"
+#include "val-labs-dialog.h"
+#include "value-labels.h"
+#include "psppire-variable.h"
+
+/* This callback occurs when the text in the label entry box 
+   is changed */
+static void
+on_label_entry_change(GtkEntry *entry, gpointer data)
+{
+  struct val_labs_dialog *dialog = data;
+  g_assert(dialog->labs);
+
+  union value v;
+  const gchar *text = gtk_entry_get_text(GTK_ENTRY(dialog->value_entry));
+
+  text_to_value(text, &v, 
+               *psppire_variable_get_write_spec(dialog->pv));
+
+
+  if ( val_labs_find (dialog->labs, v) ) 
+    {
+      gtk_widget_set_sensitive(dialog->change_button, TRUE);      
+      gtk_widget_set_sensitive(dialog->add_button, FALSE);      
+    }
+  else
+    {
+      gtk_widget_set_sensitive(dialog->change_button, FALSE);     
+      gtk_widget_set_sensitive(dialog->add_button, TRUE);       
+    }
+}
+
+
+/* Set the TREEVIEW list cursor to the item which has the value VAL */
+static void
+select_treeview_from_value(GtkTreeView *treeview, union value *val)
+{
+  /*
+    We do this with a linear search through the model --- hardly 
+    efficient, but the list is short ... */
+  GtkTreeIter iter;
+
+  GtkTreeModel * model  = gtk_tree_view_get_model(treeview);
+
+  gboolean success;
+  for (success = gtk_tree_model_get_iter_first(model, &iter);
+       success;
+       success = gtk_tree_model_iter_next(model, &iter))
+    {
+      GValue gvalue = {0};
+
+      gtk_tree_model_get_value(model, &iter, 1, &gvalue);
+         
+      union value v;
+      v.f = g_value_get_double(&gvalue);
+
+      if ( 0 == memcmp(&v, val, sizeof (union value)))
+       {
+         break;
+       }
+    }
+       
+  GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
+  if ( path ) 
+    {
+      gtk_tree_view_set_cursor(treeview, path, 0, 0);
+      gtk_tree_path_free(path);
+    }
+
+}
+
+
+/* This callback occurs when the text in the value entry box is
+   changed */
+static void
+on_value_entry_change(GtkEntry *entry, gpointer data)
+{
+  struct val_labs_dialog *dialog = data;
+
+  const gchar *text = gtk_entry_get_text(GTK_ENTRY(dialog->value_entry));
+
+  union value v;
+  text_to_value(text, &v, 
+               *psppire_variable_get_write_spec(dialog->pv));
+
+
+  g_signal_handler_block(GTK_ENTRY(dialog->label_entry), 
+                        dialog->change_handler_id);
+
+  gtk_entry_set_text(GTK_ENTRY(dialog->label_entry),"");
+
+
+  char *s;
+  if ( (s = val_labs_find (dialog->labs, v)) ) 
+    {
+      gtk_entry_set_text(GTK_ENTRY(dialog->label_entry), s);
+      gtk_widget_set_sensitive(dialog->add_button, FALSE);
+      gtk_widget_set_sensitive(dialog->remove_button, TRUE);
+      select_treeview_from_value(GTK_TREE_VIEW(dialog->treeview), &v);
+    }
+  else
+    {
+      gtk_widget_set_sensitive(dialog->remove_button, FALSE);
+      gtk_widget_set_sensitive(dialog->add_button, TRUE);
+    }
+  
+  g_signal_handler_unblock(GTK_ENTRY(dialog->label_entry), 
+                        dialog->change_handler_id);
+}
+
+
+/* Callback for when the Value Labels dialog is closed using 
+   the OK button.*/
+static gint
+val_labs_ok(GtkWidget *w, gpointer data)
+{
+  struct val_labs_dialog *dialog = data;
+
+  psppire_variable_set_value_labels(dialog->pv, dialog->labs);
+
+  val_labs_destroy (dialog->labs);
+  dialog->labs = 0;
+
+  return FALSE;
+}
+
+/* Callback for when the Value Labels dialog is closed using 
+   the Cancel button.*/
+static gint
+val_labs_cancel(GtkWidget *w, gpointer data)
+{
+  struct val_labs_dialog *dialog = data;
+
+  val_labs_destroy (dialog->labs);
+  dialog->labs = 0;
+  
+  return FALSE;
+}
+
+
+/* Return the value-label pair currently selected in the dialog box  */
+static struct val_lab *
+get_selected_tuple(struct val_labs_dialog *dialog)
+{
+  GtkTreeView *treeview = GTK_TREE_VIEW(dialog->treeview);
+  static struct val_lab vl;
+  
+  GtkTreeIter iter ;
+  GValue the_value = {0}; 
+
+  GtkTreeSelection* sel =  gtk_tree_view_get_selection(treeview);
+
+  GtkTreeModel * model  = gtk_tree_view_get_model(treeview);
+
+  gtk_tree_selection_get_selected (sel, &model, &iter);
+
+  gtk_tree_model_get_value(model, &iter, 1, &the_value);
+
+  vl.value.f = g_value_get_double(&the_value);
+  g_value_unset(&the_value);
+
+  vl.label = val_labs_find (dialog->labs, vl.value);
+  
+  return &vl;
+}
+
+
+static void repopulate_dialog(struct val_labs_dialog *dialog);
+
+/* Callback which occurs when the "Change" button is clicked */
+static gint
+on_change(GtkWidget *w, gpointer data)
+{
+  struct val_labs_dialog *dialog = data;
+  
+  const gchar *val_text = gtk_entry_get_text(GTK_ENTRY(dialog->value_entry));
+  
+  union value v;
+  
+  text_to_value(val_text, &v, 
+               *psppire_variable_get_write_spec(dialog->pv));
+
+  val_labs_replace (dialog->labs, v,
+                   gtk_entry_get_text(GTK_ENTRY(dialog->label_entry)));
+
+  gtk_widget_set_sensitive(dialog->change_button, FALSE);
+
+  repopulate_dialog(dialog);
+
+  return FALSE;
+}
+
+/* Callback which occurs when the "Add" button is clicked */
+static gint
+on_add(GtkWidget *w, gpointer data)
+{
+  struct val_labs_dialog *dialog = data;
+
+  union value v;
+
+  const gchar *text = gtk_entry_get_text(GTK_ENTRY(dialog->value_entry));
+
+  text_to_value(text, &v, 
+               *psppire_variable_get_write_spec(dialog->pv));
+
+
+  if ( ! val_labs_add (dialog->labs, v,
+                      gtk_entry_get_text(GTK_ENTRY(dialog->label_entry)) ) )
+    return FALSE;
+
+
+  gtk_widget_set_sensitive(dialog->add_button, FALSE);
+
+  repopulate_dialog(dialog);
+
+  return FALSE;
+}
+
+/* Callback which occurs when the "Remove" button is clicked */
+static gint
+on_remove(GtkWidget *w, gpointer data)
+{
+  struct val_labs_dialog *dialog = data;
+
+  struct val_lab *vl = get_selected_tuple(dialog);
+
+  val_labs_remove (dialog->labs, vl->value);
+  
+  repopulate_dialog(dialog);
+
+  gtk_widget_set_sensitive(dialog->remove_button, FALSE);
+
+  return FALSE;
+}
+
+
+
+/* Callback which occurs when a line item is selected in the list of 
+   value--label pairs.*/
+static void        
+on_select_row                  (GtkTreeView *treeview,
+                               gpointer data)
+{
+  struct val_labs_dialog *dialog = data;
+
+  struct val_lab * vl  = get_selected_tuple(dialog);
+
+  gchar *const text = value_to_text(vl->value, 
+                                   *psppire_variable_get_write_spec(dialog->pv));
+
+  g_signal_handler_block(GTK_ENTRY(dialog->value_entry), 
+                        dialog->value_handler_id);
+
+  gtk_entry_set_text(GTK_ENTRY(dialog->value_entry), text);
+
+  g_signal_handler_unblock(GTK_ENTRY(dialog->value_entry), 
+                        dialog->value_handler_id);
+  g_free(text);
+
+  g_signal_handler_block(GTK_ENTRY(dialog->label_entry), 
+                        dialog->change_handler_id);
+
+  gtk_entry_set_text(GTK_ENTRY(dialog->label_entry),
+                    vl->label);
+
+  g_signal_handler_unblock(GTK_ENTRY(dialog->label_entry), 
+                        dialog->change_handler_id);
+
+  gtk_widget_set_sensitive(dialog->remove_button, TRUE);
+  gtk_widget_set_sensitive(dialog->change_button, FALSE);
+}
+
+
+/* Create a new dialog box 
+   (there should  normally be only one)*/
+struct val_labs_dialog *
+val_labs_dialog_create(GladeXML *xml)
+{
+  struct val_labs_dialog *dialog = g_malloc(sizeof(*dialog));
+
+  dialog->window = get_widget_assert(xml,"val_labs_dialog");
+  dialog->value_entry = get_widget_assert(xml,"value_entry");
+  dialog->label_entry = get_widget_assert(xml,"label_entry");
+
+  gtk_window_set_transient_for
+    (GTK_WINDOW(dialog->window), 
+     GTK_WINDOW(get_widget_assert(xml, "data_editor")));
+
+  dialog->ok = get_widget_assert(xml, "val_labs_ok");
+  dialog->add_button = get_widget_assert(xml, "val_labs_add");
+  dialog->remove_button = get_widget_assert(xml, "val_labs_remove");
+  dialog->change_button = get_widget_assert(xml, "val_labs_change");
+
+  dialog->treeview = get_widget_assert(xml,"treeview1");
+
+  gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(dialog->treeview), FALSE);
+
+  GtkTreeViewColumn *column;
+
+  GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+  
+  column = gtk_tree_view_column_new_with_attributes ("Title",
+                                                    renderer,
+                                                    "text",
+                                                    0,
+                                                    NULL);
+
+  gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->treeview), column);
+
+  g_signal_connect(GTK_OBJECT(get_widget_assert(xml, "val_labs_cancel")),
+                  "clicked", 
+                  GTK_SIGNAL_FUNC(val_labs_cancel), dialog);
+
+  dialog->change_handler_id = 
+    g_signal_connect(GTK_OBJECT(dialog->label_entry), 
+                    "changed",
+                    GTK_SIGNAL_FUNC(on_label_entry_change), dialog);
+
+  dialog->value_handler_id  = 
+    g_signal_connect(GTK_OBJECT(dialog->value_entry), 
+                    "changed",
+                    GTK_SIGNAL_FUNC(on_value_entry_change), dialog);
+
+  g_signal_connect(GTK_OBJECT(dialog->change_button), 
+                  "clicked",
+                  GTK_SIGNAL_FUNC(on_change), dialog);
+
+
+  g_signal_connect(GTK_OBJECT(get_widget_assert(xml, "val_labs_ok")),
+                  "clicked", 
+                  GTK_SIGNAL_FUNC(val_labs_ok), dialog);
+
+
+  g_signal_connect(GTK_OBJECT(dialog->treeview), "cursor-changed",
+                  GTK_SIGNAL_FUNC(on_select_row), dialog);
+
+
+  g_signal_connect(GTK_OBJECT(dialog->remove_button), "clicked",
+                  GTK_SIGNAL_FUNC(on_remove), dialog);
+
+
+  g_signal_connect(GTK_OBJECT(dialog->add_button), "clicked",
+                  GTK_SIGNAL_FUNC(on_add), dialog);
+
+  dialog->labs = 0;
+
+  return dialog;
+}
+
+
+/* Populate the components of the dialog box, from the 'labs' member
+   variable */
+static void 
+repopulate_dialog(struct val_labs_dialog *dialog)
+{
+  struct val_labs_iterator *vli = 0;
+  struct val_lab *vl; 
+
+  GtkTreeIter iter;
+
+  GtkListStore *list_store = gtk_list_store_new (2, 
+                                                G_TYPE_STRING, 
+                                                G_TYPE_DOUBLE);
+
+  g_signal_handler_block(GTK_ENTRY(dialog->label_entry), 
+                        dialog->change_handler_id);
+  g_signal_handler_block(GTK_ENTRY(dialog->value_entry), 
+                        dialog->value_handler_id);
+
+  gtk_entry_set_text(GTK_ENTRY(dialog->value_entry), "");
+  gtk_entry_set_text(GTK_ENTRY(dialog->label_entry), "");
+
+  g_signal_handler_unblock(GTK_ENTRY(dialog->value_entry), 
+                        dialog->value_handler_id);
+  g_signal_handler_unblock(GTK_ENTRY(dialog->label_entry), 
+                          dialog->change_handler_id);
+
+
+  for(vl = val_labs_first_sorted (dialog->labs, &vli);
+      vl;
+      vl = val_labs_next(dialog->labs, &vli))
+    {
+      gchar *const vstr  = 
+       value_to_text(vl->value, 
+                     *psppire_variable_get_write_spec(dialog->pv));
+
+                                          
+      
+      gchar *const text = g_strdup_printf("%s = \"%s\"",
+                                         vstr, vl->label);
+      gtk_list_store_append (list_store, &iter);
+      gtk_list_store_set (list_store, &iter,
+                          0, text, 
+                         1, vl->value.f,
+                         -1);
+
+      g_free(text); 
+      g_free(vstr);
+    }
+
+  gtk_tree_view_set_model(GTK_TREE_VIEW(dialog->treeview), 
+                         GTK_TREE_MODEL(list_store));
+
+  g_object_unref(list_store);
+
+}
+
+/* Initialise and display the dialog box */
+void 
+val_labs_dialog_show(struct val_labs_dialog *dialog)
+{
+  g_assert(!dialog->labs);
+  dialog->labs = val_labs_copy(
+                              psppire_variable_get_value_labels(dialog->pv)
+                              );
+
+
+  gtk_widget_set_sensitive(dialog->remove_button, FALSE);
+  gtk_widget_set_sensitive(dialog->change_button, FALSE);
+  gtk_widget_set_sensitive(dialog->add_button, FALSE);
+
+  repopulate_dialog(dialog);
+  gtk_widget_show(dialog->window);
+}
+
diff --git a/src/ui/gui/val-labs-dialog.h b/src/ui/gui/val-labs-dialog.h
new file mode 100644 (file)
index 0000000..85f1a6d
--- /dev/null
@@ -0,0 +1,73 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2005  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+
+
+#ifndef __PSPPIRE_VAL_LABS_DIALOG_H
+#define __PSPPIRE_VAL_LABS_DIALOG_H
+
+
+/*  This module describes the behaviour of the Value Labels dialog box,
+    used for input of the value labels in the variable sheet */
+
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+
+struct val_labs;
+
+struct val_labs_dialog
+{
+  GtkWidget *window;
+
+
+  /* The variable to be updated */
+  struct PsppireVariable *pv;
+
+  /* Local copy of labels */
+  struct val_labs *labs;
+
+  /* Actions */
+  GtkWidget *ok;
+  GtkWidget *add_button;
+  GtkWidget *remove_button;
+  GtkWidget *change_button;
+
+  /* Entry Boxes */
+  GtkWidget *value_entry;
+  GtkWidget *label_entry;
+
+  /* Signal handler ids */
+  gint change_handler_id;
+  gint value_handler_id;
+
+  GtkWidget *treeview;
+};
+
+
+
+
+struct val_labs_dialog * val_labs_dialog_create(GladeXML *xml);
+
+void val_labs_dialog_show(struct val_labs_dialog *dialog);
+
+
+#endif
diff --git a/src/ui/gui/var-sheet.c b/src/ui/gui/var-sheet.c
new file mode 100644 (file)
index 0000000..8a2ba98
--- /dev/null
@@ -0,0 +1,451 @@
+/* 
+   PSPPIRE --- A Graphical User Interface for PSPP
+   Copyright (C) 2004, 2005, 2006  Free Software Foundation
+   Written by John Darrington
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+
+/* This module creates the Variable Sheet used for inputing the
+   variables in the  dictonary */
+
+#include <value-labels.h>
+
+#include <glade/glade.h>
+#include <gtk/gtk.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#define min(A,B) ((A < B)?A:B)
+
+#include "gtksheet.h"
+#include "gsheet-hetero-column.h"
+#include "gsheet-uniform-row.h"
+#include "psppire-var-store.h"
+
+#include "helper.h"
+#include "menu-actions.h"
+#include "psppire-dict.h"
+#include "psppire-variable.h"
+#include "var-type-dialog.h"
+#include "var-sheet.h"
+#include "customentry.h"
+
+#include "val-labs-dialog.h"
+#include "missing-val-dialog.h"
+
+#define _(A) A
+#define N_(A) A
+
+
+static const gint n_initial_rows = 40;
+
+
+extern GladeXML *xml;
+
+struct column_parameters
+{
+  gchar label[20];  
+  gint width ;
+};
+
+static const struct column_parameters column_def[] = {
+  {N_("Name"),    80},
+  {N_("Type"),    100},
+  {N_("Width"),   57}, 
+  {N_("Decimals"),91}, 
+  {N_("Label"),   95}, 
+  {N_("Values"),  103},
+  {N_("Missing"), 95}, 
+  {N_("Columns"), 80}, 
+  {N_("Align"),   69}, 
+  {N_("Measure"), 99}, 
+};
+
+
+static gboolean
+click2row(GtkWidget *w, gint row, gpointer data)
+{
+  gint current_row, current_column;
+
+  select_sheet(PAGE_DATA_SHEET);
+  GtkWidget *data_sheet  = get_widget_assert(xml, "data_sheet");
+
+  gtk_sheet_get_active_cell(GTK_SHEET(data_sheet), 
+                           &current_row, &current_column);
+
+  gtk_sheet_set_active_cell(GTK_SHEET(data_sheet), current_row, row);
+
+  return FALSE;
+}
+
+
+
+const gchar *alignments[]={
+  _("Left"),
+  _("Right"),
+  _("Centre"),
+  0
+};
+
+const gchar *measures[]={
+  _("Nominal"),
+  _("Ordinal"),
+  _("Scale"),
+  0
+};
+
+static GtkListStore *
+create_label_list(const gchar **labels)
+{
+  gint i = 0;
+  GtkTreeIter iter;
+
+  GtkListStore *list_store;
+  list_store = gtk_list_store_new (1, G_TYPE_STRING);
+
+  const gchar *s;
+  while ( (s = labels[i++]))
+    {
+      gtk_list_store_append (list_store, &iter);
+      gtk_list_store_set (list_store, &iter,
+                         0, s,
+                         -1);
+    }
+       
+  return list_store;
+}
+
+/* Callback for when the alignment combo box 
+   item is selected */
+static void        
+change_alignment(GtkComboBox *cb,
+    gpointer user_data)
+{
+  struct PsppireVariable *pv = user_data;
+  gint active_item = gtk_combo_box_get_active(cb);
+
+  if ( active_item < 0 ) return ;
+
+  psppire_variable_set_alignment(pv, active_item);
+}
+
+
+
+/* Callback for when the measure combo box 
+   item is selected */
+static void        
+change_measure(GtkComboBox *cb,
+    gpointer user_data)
+{
+  struct PsppireVariable *pv = user_data;
+  gint active_item = gtk_combo_box_get_active(cb);
+
+  if ( active_item < 0 ) return ;
+
+  psppire_variable_set_measure(pv, active_item);
+}
+
+
+
+static gboolean 
+traverse_cell_callback (GtkSheet * sheet, 
+                       gint row, gint column, 
+                       gint *new_row, gint *new_column
+                       )
+{
+  PsppireVarStore *var_store = PSPPIRE_VAR_STORE(gtk_sheet_get_model(sheet));
+
+  gint n_vars = psppire_var_store_get_var_cnt(var_store);
+
+  if ( row == n_vars && *new_row >= n_vars)
+    {
+      GtkEntry *entry = GTK_ENTRY(gtk_sheet_get_entry(sheet));
+
+      const gchar *name = gtk_entry_get_text(entry);
+
+      if (! psppire_dict_check_name(var_store->dict, name, TRUE))
+       return FALSE;
+      
+      psppire_dict_insert_variable(var_store->dict, row, name);
+
+      return TRUE;
+    }
+
+  /* If the destination cell is outside the current  variables, then
+     accept the destination only as the name column of the first blank row
+  */
+  if ( *new_row > n_vars) 
+    return FALSE;
+  
+  if ( *new_row >= n_vars && *new_column != COL_NAME) 
+    return FALSE;
+
+  return TRUE;
+}
+
+
+/* Callback whenever the cell on the var sheet is entered or left.
+   It sets the entry box type appropriately.
+*/
+static gboolean 
+var_sheet_cell_change_entry (GtkSheet * sheet, gint row, gint column, 
+                            gpointer leaving)
+{
+  g_return_val_if_fail(sheet != NULL, FALSE);
+
+  PsppireVarStore *var_store = PSPPIRE_VAR_STORE(gtk_sheet_get_model(sheet));
+
+  if ( row >= psppire_var_store_get_var_cnt(var_store))
+    return TRUE;
+
+  if ( leaving ) 
+    {
+      gtk_sheet_change_entry(sheet, GTK_TYPE_ENTRY);
+      return TRUE;
+    }
+
+  GtkSheetCellAttr attributes;
+  gtk_sheet_get_attributes(sheet, row, column, &attributes);
+
+  struct PsppireVariable *pv = psppire_var_store_get_variable(var_store, row);
+
+  switch (column)
+    {
+    case COL_ALIGN:
+      {
+       static GtkListStore *list_store = 0;
+       GtkComboBoxEntry *cbe;
+       gtk_sheet_change_entry(sheet, GTK_TYPE_COMBO_BOX_ENTRY);
+       cbe = 
+         GTK_COMBO_BOX_ENTRY(gtk_sheet_get_entry(sheet)->parent);
+
+
+       if ( ! list_store) list_store = create_label_list(alignments);
+
+       gtk_combo_box_set_model(GTK_COMBO_BOX(cbe), 
+                               GTK_TREE_MODEL(list_store));
+
+       gtk_combo_box_entry_set_text_column (cbe, 0);
+
+
+       g_signal_connect(G_OBJECT(cbe),"changed", 
+                        G_CALLBACK(change_alignment), pv);
+      }
+      break;
+    case COL_MEASURE:
+      {
+       static GtkListStore *list_store = 0;
+       GtkComboBoxEntry *cbe;
+       gtk_sheet_change_entry(sheet, GTK_TYPE_COMBO_BOX_ENTRY);
+       cbe = 
+         GTK_COMBO_BOX_ENTRY(gtk_sheet_get_entry(sheet)->parent);
+
+
+       if ( ! list_store) list_store = create_label_list(measures);
+
+       gtk_combo_box_set_model(GTK_COMBO_BOX(cbe), 
+                               GTK_TREE_MODEL(list_store));
+
+       gtk_combo_box_entry_set_text_column (cbe, 0);
+
+       g_signal_connect(G_OBJECT(cbe),"changed", 
+                        G_CALLBACK(change_measure), pv);
+      }
+      break;
+
+    case COL_VALUES:
+      {
+       static struct val_labs_dialog *val_labs_dialog = 0;
+
+       PsppireCustomEntry *customEntry;
+
+       gtk_sheet_change_entry(sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
+
+       customEntry = 
+         PSPPIRE_CUSTOM_ENTRY(gtk_sheet_get_entry(sheet));
+
+
+       if (!val_labs_dialog ) 
+           val_labs_dialog = val_labs_dialog_create(xml);
+
+       val_labs_dialog->pv = pv;
+
+       g_signal_connect_swapped(GTK_OBJECT(customEntry),
+                                "clicked",
+                                GTK_SIGNAL_FUNC(val_labs_dialog_show),
+                                val_labs_dialog);
+      }
+      break;
+    case COL_MISSING:
+      {
+       static struct missing_val_dialog *missing_val_dialog = 0;
+       PsppireCustomEntry *customEntry;
+       
+       gtk_sheet_change_entry(sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
+
+       customEntry = 
+         PSPPIRE_CUSTOM_ENTRY(gtk_sheet_get_entry(sheet));
+
+       if (!missing_val_dialog ) 
+           missing_val_dialog = missing_val_dialog_create(xml);
+
+       missing_val_dialog->pv = psppire_var_store_get_variable(var_store, row);
+
+       g_signal_connect_swapped(GTK_OBJECT(customEntry),
+                                "clicked",
+                                GTK_SIGNAL_FUNC(missing_val_dialog_show),
+                                missing_val_dialog);
+      }
+      break;
+
+    case COL_TYPE:
+      {
+       static struct var_type_dialog *var_type_dialog = 0;
+
+       PsppireCustomEntry *customEntry;
+
+       gtk_sheet_change_entry(sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
+
+       customEntry = 
+         PSPPIRE_CUSTOM_ENTRY(gtk_sheet_get_entry(sheet));
+
+
+       /* Popup the Variable Type dialog box */
+       if (!var_type_dialog ) 
+           var_type_dialog = var_type_dialog_create(xml);
+
+
+       var_type_dialog->pv = pv;
+
+       g_signal_connect_swapped(GTK_OBJECT(customEntry),
+                                "clicked",
+                                GTK_SIGNAL_FUNC(var_type_dialog_show),
+                                var_type_dialog);
+      }
+      break;
+    case COL_WIDTH:
+    case COL_DECIMALS:
+    case COL_COLUMNS:
+      {
+       if ( attributes.is_editable) 
+         {
+           gint r_min, r_max;
+
+           const gchar *s = gtk_sheet_cell_get_text(sheet, row, column);
+
+           if (!s) 
+             return FALSE;
+
+           const gint current_value  = atoi(s);
+
+           const struct fmt_spec *fmt = psppire_variable_get_write_spec(pv);
+           switch (column) 
+             {
+             case COL_WIDTH:
+               r_min = fmt->d + 1;
+               r_max = (psppire_variable_get_type(pv) == ALPHA) ? 255 : 40;
+               break;
+             case COL_DECIMALS:
+               r_min = 0 ; 
+               r_max = min(fmt->w - 1, 16);
+               break;
+             case COL_COLUMNS:
+               r_min = 1;
+               r_max = 255 ; /* Is this a sensible value ? */
+               break;
+             default:
+               g_assert_not_reached();
+             }
+
+           GtkObject *adj =  
+             gtk_adjustment_new(current_value,
+                                r_min, r_max,
+                                1.0, 1.0, 1.0 /* steps */
+                                );
+
+           gtk_sheet_change_entry(sheet, GTK_TYPE_SPIN_BUTTON);
+
+           GtkSpinButton *spinButton = 
+             GTK_SPIN_BUTTON(gtk_sheet_get_entry(sheet));
+
+           gtk_spin_button_set_adjustment(spinButton, GTK_ADJUSTMENT(adj));
+           gtk_spin_button_set_digits(spinButton, 0);
+         }
+      }
+      break; 
+
+    default:
+      gtk_sheet_change_entry(sheet, GTK_TYPE_ENTRY);
+      break;
+    }
+
+  return TRUE;
+}
+
+
+
+/* Create the var sheet */
+GtkWidget*
+psppire_variable_sheet_create (gchar *widget_name, 
+                              gchar *string1, 
+                              gchar *string2,
+                              gint int1, gint int2)
+{
+  gint i;
+  GtkWidget *sheet;
+
+  GObject *geo = g_sheet_hetero_column_new(75, n_COLS);
+  GObject *row_geometry = g_sheet_uniform_row_new(25, n_initial_rows); 
+
+
+
+  sheet = gtk_sheet_new(G_SHEET_ROW(row_geometry),
+                       G_SHEET_COLUMN(geo), 
+                       "variable sheet", 0); 
+
+  g_signal_connect (GTK_OBJECT (sheet), "activate",
+                   GTK_SIGNAL_FUNC (var_sheet_cell_change_entry),
+                   0);
+
+  g_signal_connect (GTK_OBJECT (sheet), "deactivate",
+                   GTK_SIGNAL_FUNC (var_sheet_cell_change_entry),
+                   (void *) 1);
+
+  g_signal_connect (GTK_OBJECT (sheet), "traverse",
+                   GTK_SIGNAL_FUNC (traverse_cell_callback), 0);
+
+
+  g_signal_connect (GTK_OBJECT (sheet), "double-click-row",
+                   GTK_SIGNAL_FUNC (click2row),
+                   sheet);
+
+  for (i = 0 ; i < n_COLS ; ++i ) 
+    {
+      g_sheet_hetero_column_set_button_label(G_SHEET_HETERO_COLUMN(geo), i, 
+                                              column_def[i].label);
+
+      g_sheet_hetero_column_set_width(G_SHEET_HETERO_COLUMN(geo), i, 
+                                              column_def[i].width);
+    }
+
+  gtk_widget_show(sheet);
+
+  return sheet;
+}
+
+
diff --git a/src/ui/gui/var-sheet.h b/src/ui/gui/var-sheet.h
new file mode 100644 (file)
index 0000000..09bcb72
--- /dev/null
@@ -0,0 +1,59 @@
+/* 
+   PSPPIRE --- A Graphical User Interface for PSPP
+   Copyright (C) 2004, 2005, 2006  Free Software Foundation
+   Written by John Darrington
+
+   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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+
+#ifndef VAR_SHEET_H
+#define VAR_SHEET_H
+
+#include "gtksheet.h"
+
+#include "psppire-dict.h"
+
+enum {COL_NAME, 
+      COL_TYPE, 
+      COL_WIDTH, 
+      COL_DECIMALS, 
+      COL_LABEL, 
+      COL_VALUES, 
+      COL_MISSING, 
+      COL_COLUMNS, 
+      COL_ALIGN, 
+      COL_MEASURE, 
+      n_COLS};
+
+
+void var_sheet_range_set_editable(GtkSheet *sheet, 
+                                 const GtkSheetRange *urange, 
+                                 gboolean editable);
+
+
+/* Create the var sheet */
+GtkWidget* psppire_variable_sheet_create (gchar *widget_name, 
+                                         gchar *string1, 
+                                         gchar *string2,
+                                         gint int1, gint int2);
+
+
+extern const gchar *alignments[];
+
+extern const gchar *measures[];
+
+
+#endif
diff --git a/src/ui/gui/var-type-dialog.c b/src/ui/gui/var-type-dialog.c
new file mode 100644 (file)
index 0000000..d1569f8
--- /dev/null
@@ -0,0 +1,849 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2005  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+
+/*  This module describes the behaviour of the Variable Type dialog box used
+    for inputing the variable type in the var sheet */
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "var-type-dialog.h"
+
+#include "psppire-variable.h"
+
+#include "variable.h"
+#include "settings.h"
+
+
+struct tgs
+{
+  struct var_type_dialog *dialog;
+  gint button;
+};
+
+
+struct format_opt {
+  gchar desc[18];
+  struct fmt_spec spec;
+};
+
+
+static const struct format_opt format_option[] =
+  {
+    { "dd-mmm-yyyy", {FMT_DATE,  11, 0} },
+    { "dd-mmm-yy",   {FMT_DATE,   9, 0} },
+    { "mm/dd/yyyy",  {FMT_ADATE, 10, 0} },
+    { "mm/dd/yy",    {FMT_ADATE, 8, 0} },
+    { "dd.mm.yyyy",  {FMT_EDATE, 10, 0} },
+    { "dd.mm.yy",    {FMT_EDATE, 8, 0} },
+    { "yyyy/mm/dd",  {FMT_SDATE, 10, 0} },
+    { "yy/mm/dd",    {FMT_SDATE, 8, 0} },
+    { "yyddd",       {FMT_JDATE, 5, 0} },
+    { "yyyyddd",     {FMT_JDATE, 7, 0} },
+    { "q Q yyyy",    {FMT_QYR, 8, 0} },
+    { "q Q yy",      {FMT_QYR, 6, 0} },
+    { "mmm yyyy",    {FMT_MOYR, 8, 0} },
+    { "mmm yy",      {FMT_MOYR, 6, 0} },
+    { "dd WK yyyy",  {FMT_WKYR, 10, 0} },
+    { "dd WK yy",    {FMT_WKYR, 8, 0} },
+    { "dd-mmm-yyyy HH:MM", {FMT_DATETIME, 17, 0}}
+  };
+
+
+static const struct fmt_spec dollar_format[] = 
+  {
+    {FMT_DOLLAR, 2, 0},
+    {FMT_DOLLAR, 3, 0},
+    {FMT_DOLLAR, 4, 0},
+    {FMT_DOLLAR, 7, 2},
+    {FMT_DOLLAR, 6, 0},
+    {FMT_DOLLAR, 9, 2},
+    {FMT_DOLLAR, 8, 0},
+    {FMT_DOLLAR, 11, 2},
+    {FMT_DOLLAR, 12, 0},
+    {FMT_DOLLAR, 15, 2},
+    {FMT_DOLLAR, 16, 0},
+    {FMT_DOLLAR, 19, 2}
+  };
+
+static const int cc_format[] = 
+  {
+    FMT_CCA, 
+    FMT_CCB, 
+    FMT_CCC, 
+    FMT_CCD, 
+    FMT_CCE, 
+  };
+
+
+static void select_treeview_from_format
+ (GtkTreeView *treeview, const struct fmt_spec *fmt);
+
+static void select_treeview_from_format_type(GtkTreeView *treeview, 
+                                            const int fmt_type);
+
+
+/* callback for when any of the radio buttons are toggled */
+static void        
+on_toggle_1(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  struct tgs *tgs = user_data;
+
+  if ( gtk_toggle_button_get_active(togglebutton) == FALSE) 
+    return ;
+
+  tgs->dialog->active_button = tgs->button;
+}
+
+static void update_width_decimals(const struct var_type_dialog *dialog);
+
+#define force_max(x, val) if (x > val) x = val
+
+/* 
+   Set the local format from the variable
+   and force them to have sensible values */
+static void
+set_local_width_decimals(struct var_type_dialog *dialog)
+{
+  dialog->fmt_l = * psppire_variable_get_write_spec(dialog->pv);
+
+  switch (dialog->active_button) 
+    {
+    case BUTTON_STRING:
+      force_max( dialog->fmt_l.w, 255);
+      break;
+    default:
+      force_max( dialog->fmt_l.w, 40);
+      force_max( dialog->fmt_l.d, 16);
+      break;
+    }
+}
+
+
+/* callback for when any of the radio buttons are toggled */
+static void        
+on_toggle_2(GtkToggleButton *togglebutton, gpointer user_data)
+{
+  struct var_type_dialog *dialog = user_data;
+  if ( gtk_toggle_button_get_active(togglebutton) == FALSE) 
+    {
+      switch (dialog->active_button) 
+       {
+       case BUTTON_DATE:
+         gtk_widget_hide(dialog->date_format_list);
+         break;
+       case BUTTON_CUSTOM:
+         gtk_widget_hide(dialog->custom_currency_hbox);
+         break;
+       case BUTTON_DOLLAR:
+         gtk_widget_hide(dialog->dollar_window);
+         break;
+       case BUTTON_STRING:
+         gtk_widget_show(dialog->label_decimals);
+         gtk_widget_show(dialog->entry_decimals);
+         break;
+       }
+      return ;
+    }
+
+  set_local_width_decimals(dialog);
+  update_width_decimals(dialog);
+
+  switch (dialog->active_button) 
+    {
+    case BUTTON_STRING:
+      gtk_widget_show(dialog->width_decimals);
+      gtk_widget_hide(dialog->label_decimals);
+      gtk_widget_hide(dialog->entry_decimals);
+      break;
+    case BUTTON_DATE:
+      select_treeview_from_format(dialog->date_format_treeview,
+                                 &format_option[0].spec);
+      gtk_widget_hide(dialog->width_decimals);
+      gtk_widget_show(dialog->date_format_list);
+      break;
+    case BUTTON_DOLLAR:
+      select_treeview_from_format(dialog->dollar_treeview,
+                                 &dollar_format[0]);
+      gtk_widget_show(dialog->dollar_window);
+      gtk_widget_show_all(dialog->width_decimals);
+      break;
+    case BUTTON_CUSTOM:
+      select_treeview_from_format_type(dialog->custom_treeview,
+                                 cc_format[0]);
+
+      gtk_widget_show(dialog->width_decimals);
+      gtk_widget_show(dialog->custom_currency_hbox);
+      break;
+    default:
+      gtk_widget_show_all(dialog->width_decimals);
+      break;
+    }
+}
+
+
+
+static gint on_var_type_ok_clicked(GtkWidget *w, gpointer data);
+
+#define LEN 20
+
+/* return a string of the form "$#,###.##" according to FMT. 
+   FMT must be of type FMT_DOLLAR
+ */
+static const gchar *
+dollar_format_template(const struct fmt_spec *fmt)
+{
+  static gchar buf[LEN];
+  g_assert( fmt->type == FMT_DOLLAR);
+
+  gint int_part = fmt->w - fmt->d;
+  if ( fmt->d > 0 ) --int_part;
+  g_assert(int_part > 0);
+
+  g_strlcpy(buf, "$", LEN);
+
+  gint c = int_part - 1;
+  while(c > 0)
+    {
+      g_strlcat(buf, "#", LEN);
+      if(--c % 4 == 0 && c > 0 ) 
+      {
+       g_strlcat(buf, ",", LEN);
+       --c;
+      }
+    }
+  if ( fmt->d > 0 ) 
+    {
+      g_strlcat(buf, ".", LEN);
+      for ( c = 0 ; c < fmt->d ; ++c ) 
+       g_strlcat(buf, "#", LEN);
+    }
+
+  return buf;
+}
+
+static void
+add_to_group(GtkWidget *w, gpointer data)
+{
+  GtkSizeGroup *sg = data;
+  
+  gtk_size_group_add_widget(sg, w);
+}
+
+/* Set the local width and decimals entry boxes to reflec the local format */
+static void
+update_width_decimals(const struct var_type_dialog *dialog)
+{
+  g_assert(dialog);
+
+  gchar *text = g_strdup_printf("%d", dialog->fmt_l.w);
+  gtk_entry_set_text(GTK_ENTRY(dialog->entry_width), text);
+  g_free(text);
+
+  text = g_strdup_printf("%d", dialog->fmt_l.d);
+  gtk_entry_set_text(GTK_ENTRY(dialog->entry_decimals), text);
+  g_free(text);
+}
+
+/* Callback for when the custom treeview row is changed.
+   It sets dialog box to reflect the selected format */
+static void
+preview_custom(GtkWidget *w, gpointer data)
+{
+  struct var_type_dialog *dialog = data;
+
+  if ( dialog->active_button != BUTTON_CUSTOM ) 
+    return;
+
+  const gchar *text = gtk_entry_get_text(GTK_ENTRY(dialog->entry_decimals));
+  dialog->fmt_l.d = atoi(text);
+
+  text = gtk_entry_get_text(GTK_ENTRY(dialog->entry_width));
+  dialog->fmt_l.w = atoi(text);
+
+  if ( ! check_output_specifier(&dialog->fmt_l, 0))
+    {
+      gtk_label_set_text(GTK_LABEL(dialog->label_psample), "---");
+      gtk_label_set_text(GTK_LABEL(dialog->label_nsample), "---");
+    }
+  else
+    {
+      gchar *sample_text;
+      union value v;
+      v.f = 1234.56;
+
+      sample_text = value_to_text(v, dialog->fmt_l);
+      gtk_label_set_text(GTK_LABEL(dialog->label_psample), sample_text);
+      g_free(sample_text);
+
+      v.f = -v.f;
+      sample_text = value_to_text(v, dialog->fmt_l);
+      gtk_label_set_text(GTK_LABEL(dialog->label_nsample), sample_text);
+      g_free(sample_text);
+    }
+}
+
+/* Callback for when a treeview row is changed.
+   It sets the fmt_l_spec to reflect the selected format */
+static void
+set_format_from_treeview(GtkTreeView *treeview, gpointer data)
+{
+  struct var_type_dialog *dialog = data;
+  GtkTreeIter iter ;
+  GValue the_value = {0}; 
+
+  GtkTreeSelection* sel =  gtk_tree_view_get_selection(treeview);
+
+  GtkTreeModel * model  = gtk_tree_view_get_model(treeview);
+
+  gtk_tree_selection_get_selected (sel, &model, &iter);
+
+  gtk_tree_model_get_value(model, &iter, 1, &the_value);
+
+  dialog->fmt_l = *(struct fmt_spec *) g_value_get_pointer(&the_value);
+}
+
+
+/* Callback for when a treeview row is changed.
+   It sets the type of the fmt_l to reflect the selected type */
+static void
+set_format_type_from_treeview(GtkTreeView *treeview, gpointer data)
+{
+  static struct fmt_spec custom_format = {0,0,0};
+  struct var_type_dialog *dialog = data;
+  GtkTreeIter iter ;
+  GValue the_value = {0}; 
+
+  GtkTreeSelection* sel =  gtk_tree_view_get_selection(treeview);
+
+  GtkTreeModel * model  = gtk_tree_view_get_model(treeview);
+
+  gtk_tree_selection_get_selected (sel, &model, &iter);
+
+  gtk_tree_model_get_value(model, &iter, 1, &the_value);
+
+  dialog->fmt_l = custom_format;
+  dialog->fmt_l.type = *(int*) g_value_get_pointer(&the_value);
+
+}
+
+
+
+
+/* Create the structure from the XML definitions */
+struct var_type_dialog *
+var_type_dialog_create(GladeXML *xml)
+{
+  gint i;
+  g_assert(xml);
+
+  struct var_type_dialog *dialog = g_malloc(sizeof(struct var_type_dialog));
+
+  dialog->window = get_widget_assert(xml,"var_type_dialog");
+
+  gtk_window_set_transient_for(GTK_WINDOW(dialog->window), 
+                              GTK_WINDOW(get_widget_assert(xml, "data_editor")));
+
+  dialog->radioButton[BUTTON_NUMERIC] = 
+    get_widget_assert(xml,"radiobutton1");
+  dialog->radioButton[BUTTON_COMMA] =   
+    get_widget_assert(xml,"radiobutton2");
+  dialog->radioButton[BUTTON_DOT] =     
+    get_widget_assert(xml,"radiobutton3");
+  dialog->radioButton[BUTTON_SCIENTIFIC] = 
+    get_widget_assert(xml,"radiobutton4");
+  dialog->radioButton[BUTTON_DATE] =   
+    get_widget_assert(xml,"radiobutton5");
+  dialog->radioButton[BUTTON_DOLLAR] = 
+    get_widget_assert(xml,"radiobutton6");
+  dialog->radioButton[BUTTON_CUSTOM] = 
+    get_widget_assert(xml,"radiobutton7");
+  dialog->radioButton[BUTTON_STRING] = 
+    get_widget_assert(xml,"radiobutton8");
+
+
+  dialog->date_format_list = get_widget_assert(xml, "scrolledwindow4");
+  dialog->width_decimals = get_widget_assert(xml, "width_decimals");
+  dialog->label_decimals = get_widget_assert(xml, "decimals_label");
+  dialog->entry_decimals = get_widget_assert(xml, "decimals_entry");
+
+  dialog->label_psample = get_widget_assert(xml, "psample_label");
+  dialog->label_nsample = get_widget_assert(xml, "nsample_label");
+
+
+  dialog->entry_width = get_widget_assert(xml,"width_entry");
+
+  dialog->custom_currency_hbox = get_widget_assert(xml,
+                                                  "custom_currency_hbox");
+
+  dialog->dollar_window = get_widget_assert(xml, "dollar_window");
+  dialog->dollar_treeview = 
+    GTK_TREE_VIEW(get_widget_assert(xml, "dollar_treeview"));
+
+  dialog->custom_treeview = 
+    GTK_TREE_VIEW(get_widget_assert(xml, "custom_treeview"));
+
+
+  dialog->ok = get_widget_assert(xml,"var_type_ok");
+
+
+  /* The "middle_box" is a vbox with serveral children.
+     However only one child is ever shown at a time.
+     We need to make sure that they all have the same width, to avoid
+     upleasant resizing effects */
+  GtkSizeGroup *sizeGroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+  gtk_container_foreach(GTK_CONTAINER(get_widget_assert(xml, "middle_box")), 
+                       add_to_group, sizeGroup);
+
+  
+  static struct tgs tgs[num_BUTTONS];
+  for (i = 0 ; i < num_BUTTONS; ++i ) 
+    {
+      tgs[i].dialog = dialog;
+      tgs[i].button = i;
+      g_signal_connect(dialog->radioButton[i], "toggled", 
+                      G_CALLBACK(on_toggle_1), &tgs[i]);
+
+      g_signal_connect(dialog->radioButton[i], "toggled", 
+                      G_CALLBACK(on_toggle_2), dialog);
+    }
+
+  /* Populate the date format tree view */
+  dialog->date_format_treeview = GTK_TREE_VIEW(get_widget_assert(xml, 
+                                             "date_format_list_view"));
+
+  GtkTreeViewColumn *column;
+
+  GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+  
+  column = gtk_tree_view_column_new_with_attributes ("Title",
+                                                    renderer,
+                                                    "text",
+                                                    0,
+                                                    NULL);
+
+  gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->date_format_treeview), 
+                              column);
+
+
+  GtkTreeIter iter;
+  GtkListStore *list_store = gtk_list_store_new (2, G_TYPE_STRING, 
+                                                G_TYPE_POINTER);
+
+  for ( i = 0 ; i < sizeof(format_option) / sizeof(format_option[0]) ; ++i ) 
+    {
+      gtk_list_store_append (list_store, &iter);
+      gtk_list_store_set (list_store, &iter,
+                          0, format_option[i].desc,
+                         1, &format_option[i].spec,
+                         -1);
+    }
+
+  gtk_tree_view_set_model(GTK_TREE_VIEW(dialog->date_format_treeview), 
+                         GTK_TREE_MODEL(list_store));
+
+  g_object_unref(list_store);
+
+  g_signal_connect(GTK_OBJECT(dialog->date_format_treeview), "cursor-changed",
+                  GTK_SIGNAL_FUNC(set_format_from_treeview), dialog);
+
+
+  /* populate the dollar treeview */
+
+  renderer = gtk_cell_renderer_text_new();
+  
+  column = gtk_tree_view_column_new_with_attributes ("Title",
+                                                    renderer,
+                                                    "text",
+                                                    0,
+                                                    NULL);
+
+  gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->dollar_treeview), 
+                              column);
+
+
+  list_store = gtk_list_store_new (2, G_TYPE_STRING, 
+                                                G_TYPE_POINTER);
+
+  for ( i = 0 ; i < sizeof(dollar_format)/sizeof(dollar_format[0]) ; ++i ) 
+    {
+      gtk_list_store_append (list_store, &iter);
+      gtk_list_store_set (list_store, &iter,
+                          0, dollar_format_template(&dollar_format[i]),
+                         1, &dollar_format[i],
+                         -1);
+    }
+
+  gtk_tree_view_set_model(GTK_TREE_VIEW(dialog->dollar_treeview), 
+                         GTK_TREE_MODEL(list_store));
+
+  g_object_unref(list_store);
+
+  g_signal_connect(GTK_OBJECT(dialog->dollar_treeview), 
+                  "cursor-changed",
+                  GTK_SIGNAL_FUNC(set_format_from_treeview), dialog);
+
+  g_signal_connect_swapped(GTK_OBJECT(dialog->dollar_treeview), 
+                  "cursor-changed",
+                  GTK_SIGNAL_FUNC(update_width_decimals), dialog);
+
+
+  /* populate the custom treeview */
+
+  renderer = gtk_cell_renderer_text_new();
+  
+  column = gtk_tree_view_column_new_with_attributes ("Title",
+                                                    renderer,
+                                                    "text",
+                                                    0,
+                                                    NULL);
+
+  gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->custom_treeview), 
+                              column);
+
+
+  list_store = gtk_list_store_new (2, G_TYPE_STRING, 
+                                                G_TYPE_POINTER);
+
+  for ( i = 0 ; i < CC_CNT ; ++i ) 
+    {
+      gchar text[4];
+      g_snprintf(text, 4, "CC%c", 'A' + i);
+      gtk_list_store_append (list_store, &iter);
+      gtk_list_store_set (list_store, &iter,
+                          0, text,
+                         1, &cc_format[i],
+                         -1);
+    }
+
+  gtk_tree_view_set_model(GTK_TREE_VIEW(dialog->custom_treeview), 
+                         GTK_TREE_MODEL(list_store));
+
+  g_object_unref(list_store);
+
+
+  g_signal_connect(GTK_OBJECT(dialog->custom_treeview), 
+                  "cursor-changed",
+                  GTK_SIGNAL_FUNC(set_format_type_from_treeview), dialog);
+
+
+  g_signal_connect(GTK_OBJECT(dialog->custom_treeview), 
+                  "cursor-changed",
+                  GTK_SIGNAL_FUNC(preview_custom), dialog);
+
+
+  g_signal_connect(GTK_OBJECT(dialog->entry_width), 
+                  "changed",
+                  GTK_SIGNAL_FUNC(preview_custom), dialog);
+
+
+  g_signal_connect(GTK_OBJECT(dialog->entry_decimals), 
+                  "changed",
+                  GTK_SIGNAL_FUNC(preview_custom), dialog);
+
+
+  /* Connect the OK button */
+  g_signal_connect(dialog->ok, "clicked", G_CALLBACK(on_var_type_ok_clicked), 
+                  dialog);
+
+  return dialog;
+}
+
+
+/* Set a particular button to be active */
+void
+var_type_dialog_set_active_button(struct var_type_dialog *dialog, gint b)
+{
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->radioButton[b]),
+                              TRUE);
+  dialog->active_button = b;
+}
+
+
+
+/* Set the TREEVIEW list cursor to the item described by FMT */
+static void
+select_treeview_from_format(GtkTreeView *treeview, const struct fmt_spec *fmt)
+{
+  /*
+    We do this with a linear search through the model --- hardly 
+    efficient, but the list is short ... */
+  GtkTreeIter iter;
+
+  GtkTreeModel * model  = gtk_tree_view_get_model(treeview);
+
+  gboolean success;
+  for (success = gtk_tree_model_get_iter_first(model, &iter);
+       success;
+       success = gtk_tree_model_iter_next(model, &iter))
+    {
+      GValue value = {0};
+
+      gtk_tree_model_get_value(model, &iter, 1, &value);
+         
+      const struct fmt_spec *spec = g_value_get_pointer(&value);
+
+      if ( 0 == memcmp(spec, fmt, sizeof (struct fmt_spec)))
+       {
+         break;
+       }
+    }
+       
+  GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
+  if ( path ) 
+    {
+      gtk_tree_view_set_cursor(treeview, path, 0, 0);
+      gtk_tree_path_free(path);
+    }
+  else
+    g_warning("Unusual date format: %s\n", fmt_to_string(fmt));
+
+}
+
+
+/* Set the TREEVIEW list cursor to the item described by FMT_TYPE */
+static void
+select_treeview_from_format_type(GtkTreeView *treeview, 
+                                const int fmt_type)
+{
+  /*
+    We do this with a linear search through the model --- hardly 
+    efficient, but the list is short ... */
+  GtkTreeIter iter;
+
+  GtkTreeModel * model  = gtk_tree_view_get_model(treeview);
+
+  gboolean success;
+  for (success = gtk_tree_model_get_iter_first(model, &iter);
+       success;
+       success = gtk_tree_model_iter_next(model, &iter))
+    {
+      GValue value = {0};
+
+      gtk_tree_model_get_value(model, &iter, 1, &value);
+         
+      const int spec = * ((int *) g_value_get_pointer(&value));
+
+      if ( spec == fmt_type)
+       break;
+    }
+       
+  GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
+  if ( path ) 
+    {
+      gtk_tree_view_set_cursor(treeview, path, 0, 0);
+      gtk_tree_path_free(path);
+    }
+  else
+    g_warning("Unknown custom type  %d\n", fmt_type);
+
+}
+
+/* Set up the state of the dialog box to match the variable VAR */
+static void
+var_type_dialog_set_state(struct var_type_dialog *dialog)
+{
+  g_assert(dialog);
+  g_assert(dialog->pv);
+
+  /* Populate width and decimals */
+  const struct fmt_spec *write_spec = psppire_variable_get_write_spec(dialog->pv);
+
+  GString *str = g_string_new("");
+  
+  g_string_printf(str, "%d", write_spec->d);
+
+  gtk_entry_set_text(GTK_ENTRY(dialog->entry_decimals), 
+                    str->str);
+
+  g_string_printf(str, "%d", write_spec->w);
+
+  gtk_entry_set_text(GTK_ENTRY(dialog->entry_width), 
+                    str->str);
+
+  g_string_free(str, TRUE);
+
+  /* Populate the radio button states */
+  switch (write_spec->type)
+    {
+    case FMT_F:
+      var_type_dialog_set_active_button(dialog, BUTTON_NUMERIC);
+      gtk_widget_show_all(dialog->width_decimals);
+      break;
+    case FMT_A:
+      var_type_dialog_set_active_button(dialog, BUTTON_STRING);
+      gtk_widget_hide(dialog->label_decimals);
+      gtk_widget_hide(dialog->entry_decimals);
+      break;
+    case FMT_COMMA:
+      var_type_dialog_set_active_button(dialog, BUTTON_COMMA);
+      gtk_widget_show_all(dialog->width_decimals);
+      break;
+    case FMT_DOT:
+      var_type_dialog_set_active_button(dialog, BUTTON_DOT);
+      gtk_widget_show_all(dialog->width_decimals);
+      break;
+    case FMT_DOLLAR:
+      var_type_dialog_set_active_button(dialog, BUTTON_DOLLAR);
+      gtk_widget_show_all(dialog->width_decimals);
+      
+      select_treeview_from_format(dialog->dollar_treeview, write_spec);
+      break;
+    case FMT_DATE:     
+    case FMT_EDATE:    
+    case FMT_SDATE:    
+    case FMT_ADATE:    
+    case FMT_JDATE:    
+    case FMT_QYR:      
+    case FMT_MOYR:     
+    case FMT_WKYR:     
+    case FMT_DATETIME: 
+    case FMT_TIME:     
+    case FMT_DTIME:    
+    case FMT_WKDAY:    
+    case FMT_MONTH:    
+      var_type_dialog_set_active_button(dialog, BUTTON_DATE);
+      gtk_widget_hide(dialog->width_decimals);
+      gtk_widget_show(dialog->date_format_list);
+      select_treeview_from_format(dialog->date_format_treeview, write_spec);
+      break;
+    case FMT_CCA:
+    case FMT_CCB:
+    case FMT_CCC:
+    case FMT_CCD:
+    case FMT_CCE:
+      var_type_dialog_set_active_button(dialog, BUTTON_CUSTOM);
+      select_treeview_from_format_type(dialog->custom_treeview, 
+                                      write_spec->type);
+      gtk_widget_show_all(dialog->width_decimals);
+      break;
+    default:
+      gtk_widget_show_all(dialog->width_decimals);
+      break;
+    }
+}
+
+
+/* Popup the dialog box */
+void 
+var_type_dialog_show(struct var_type_dialog *dialog)
+{
+  var_type_dialog_set_state(dialog);
+
+  gtk_widget_show(dialog->window);
+}
+
+/* Fills F with an output format specification with type TYPE, width
+   W, and D decimals. Iff it's a valid format, then return true.
+*/
+static bool
+make_output_format_try (struct fmt_spec *f, int type, int w, int d)
+{
+  f->type = type;
+  f->w = w;
+  f->d = d;
+  return check_output_specifier (f, true);
+}
+
+
+
+
+/* Callbacks for the Variable Type Dialog Box */
+
+/* Callback for when the var type dialog is closed using the OK button. 
+   It sets the appropriate variable accordingly. */
+static gint
+on_var_type_ok_clicked(GtkWidget *w, gpointer data)
+{
+  struct var_type_dialog *dialog = data;
+
+  g_assert(dialog);
+  g_assert(dialog->pv);
+
+  gint width = atoi(gtk_entry_get_text
+              (GTK_ENTRY(dialog->entry_width)));
+
+  gint decimals = atoi(gtk_entry_get_text
+                (GTK_ENTRY(dialog->entry_decimals)));
+
+  gint new_type = NUMERIC;
+  gint new_width = 0;
+  bool result = false;
+  struct fmt_spec spec;
+  switch (dialog->active_button) 
+    {
+    case BUTTON_STRING:
+      new_type = ALPHA;
+      new_width = width;
+      result = make_output_format_try(&spec, FMT_A, width, 0);
+      break;
+    case BUTTON_NUMERIC:
+      result = make_output_format_try(&spec, FMT_F, width, decimals);
+      break;
+    case BUTTON_COMMA:
+      result = make_output_format_try(&spec, FMT_COMMA, width, decimals);
+      break;
+    case BUTTON_DOT:
+      result = make_output_format_try(&spec, FMT_DOT, width, decimals);
+      break;
+    case BUTTON_SCIENTIFIC:
+      result = make_output_format_try(&spec, FMT_E, width, decimals);
+      break;
+    case BUTTON_DATE:
+    case BUTTON_CUSTOM:
+      g_assert(check_output_specifier(&dialog->fmt_l, TRUE));
+      result = memcpy(&spec, &dialog->fmt_l, sizeof(struct fmt_spec));
+      break;
+    case BUTTON_DOLLAR:
+      result = make_output_format_try(&spec, FMT_DOLLAR, width, decimals);
+      break;
+    default:
+      g_print("Unknown variable type: %d\n", dialog->active_button) ;
+      result = false;
+      break;
+    }
+
+  if ( result == true ) 
+    {
+      psppire_variable_set_type(dialog->pv, new_type);
+      psppire_variable_set_width(dialog->pv, new_width);
+      psppire_variable_set_write_spec(dialog->pv, spec);
+      psppire_variable_set_print_spec(dialog->pv, spec);
+    }
+
+  gtk_widget_hide(dialog->window);
+
+  return FALSE;
+}
+
+
+
+gint
+on_var_type_cancel_clicked(GtkWidget *w,  gpointer data)
+{
+  gtk_widget_hide(w);
+
+  return FALSE;
+}
+
diff --git a/src/ui/gui/var-type-dialog.h b/src/ui/gui/var-type-dialog.h
new file mode 100644 (file)
index 0000000..28e7d8b
--- /dev/null
@@ -0,0 +1,108 @@
+/* 
+    PSPPIRE --- A Graphical User Interface for PSPP
+    Copyright (C) 2005  Free Software Foundation
+    Written by John Darrington
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA
+    02110-1301, USA. */
+
+
+#ifndef __PSPPIRE_VAR_TYPE_DIALOG_H
+#define __PSPPIRE_VAR_TYPE_DIALOG_H
+
+
+/*  This module describes the behaviour of the Variable Type dialog box,
+    used for input of the variable type parameter in the var sheet */
+
+#include "format.h"
+
+enum 
+  {
+    BUTTON_NUMERIC,
+    BUTTON_COMMA,
+    BUTTON_DOT,
+    BUTTON_SCIENTIFIC,
+    BUTTON_DATE,
+    BUTTON_DOLLAR,
+    BUTTON_CUSTOM,
+    BUTTON_STRING,
+    num_BUTTONS
+  };
+
+struct variable;
+
+typedef void (*variable_changed_func)(struct variable *var);
+
+struct var_type_dialog
+{
+  GtkWidget *window;
+
+  /* Variable to be updated */
+  struct PsppireVariable *pv;
+#if 0
+  struct variable *var;
+
+  /* Function to be run when the dialog changes a variable */
+  variable_changed_func var_change_func;
+#endif
+
+  /* Local copy of format specifier */
+  struct fmt_spec fmt_l;
+
+  /* Toggle Buttons */
+  GtkWidget *radioButton[num_BUTTONS];
+
+  /* Decimals */
+  GtkWidget *label_decimals;
+  GtkWidget *entry_decimals;
+
+  /* Width */
+  GtkWidget *entry_width;
+
+  /* Container for width/decimals entry/labels */
+  GtkWidget *width_decimals;
+
+  /* Date */
+  GtkWidget *date_format_list;
+  GtkTreeView *date_format_treeview;
+
+  /* Dollar */
+  GtkWidget *dollar_window;
+  GtkTreeView *dollar_treeview;
+
+  /* Custom Currency */
+  GtkWidget *custom_currency_hbox;
+  GtkTreeView *custom_treeview;
+  GtkWidget *label_psample;
+  GtkWidget *label_nsample;
+
+  /* Actions */
+  GtkWidget *ok;
+
+  gint active_button;
+};
+
+
+struct var_type_dialog * var_type_dialog_create(GladeXML *xml);
+
+
+void var_type_dialog_set_variable(struct var_type_dialog *dialog, 
+                                 variable_changed_func set_variable_changed,
+                                 struct variable *var);
+
+void var_type_dialog_show(struct var_type_dialog *dialog);
+
+
+#endif
diff --git a/src/ui/terminal/ChangeLog b/src/ui/terminal/ChangeLog
new file mode 100644 (file)
index 0000000..4a96aeb
--- /dev/null
@@ -0,0 +1,3 @@
+Thu Mar  2 08:40:33 WST 2006 John Darrington <john@darrington.wattle.id.au>
+       
+       * Moved files from src directory
diff --git a/src/ui/terminal/command-line.c b/src/ui/terminal/command-line.c
new file mode 100644 (file)
index 0000000..39e17e3
--- /dev/null
@@ -0,0 +1,258 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include "command-line.h"
+#include "message.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include "alloc.h"
+#include "copyleft.h"
+#include "message.h"
+#include "line-buffer.h"
+#include "progname.h"
+#include "settings.h"
+#include "read-line.h"
+#include "output.h"
+#include "filename.h"
+#include "str.h"
+#include "version.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+void welcome (void);
+static void usage (void);
+
+char *subst_vars (char *);
+
+/* Parses the command line specified by ARGC and ARGV as received by
+   main().  Returns true if normal execution should proceed,
+   false if the command-line indicates that PSPP should exit. */
+bool
+parse_command_line (int argc, char **argv)
+{
+  static struct option long_options[] =
+  {
+    {"algorithm", required_argument, NULL, 'a'},
+    {"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'},
+    {"syntax", required_argument, NULL, 'x'},
+    {"testing-mode", no_argument, NULL, 'T'},
+    {"verbose", no_argument, NULL, 'v'},
+    {"version", no_argument, NULL, 'V'},
+    {0, 0, 0, 0},
+  };
+
+  int c, i;
+
+  bool cleared_device_defaults = false;
+  bool process_statrc = true;
+  bool interactive_mode = false;
+  int syntax_files = 0;
+
+  for (;;)
+    {
+      c = getopt_long (argc, argv, "a:x:B:c:f:hiI:lno:prsvV", long_options, NULL);
+      if (c == -1)
+       break;
+
+      switch (c)
+       {
+         /* Compatibility options */
+        case 'a':
+         if ( 0 == strcmp(optarg,"compatible") )
+             set_algorithm(COMPATIBLE);
+         else if ( 0 == strcmp(optarg,"enhanced"))
+             set_algorithm(ENHANCED);
+         else
+           {
+             usage ();
+              return false;
+           }
+         break;
+
+       case 'x':         
+         if ( 0 == strcmp(optarg,"compatible") )
+           set_syntax(COMPATIBLE);
+         else if ( 0 == strcmp(optarg,"enhanced"))
+           set_syntax(ENHANCED);
+         else
+           {
+             usage ();
+              return false;
+           }
+         break;
+
+       case 'B':
+         config_path = optarg;
+         break;
+       case 'f':
+         printf (_("%s is not yet implemented."), "-f");
+          putchar('\n');
+         break;
+       case 'h':
+         usage ();
+          return false;
+       case 'i':
+         interactive_mode = true;
+         break;
+       case 'I':
+         if (optarg == NULL || !strcmp (optarg, "-"))
+           getl_clear_include_path ();
+         else
+           getl_add_include_dir (optarg);
+         break;
+       case 'l':
+         outp_list_classes ();
+          return false;
+       case 'n':
+         printf (_("%s is not yet implemented."),"-n");
+          putchar('\n');
+         break;
+       case 'o':
+         if (!cleared_device_defaults)
+           {
+             outp_configure_clear ();
+             cleared_device_defaults = true;
+           }
+         outp_configure_add (optarg);
+         break;
+       case 'p':
+         printf (_("%s is not yet implemented."),"-p");
+          putchar('\n');
+         break;
+       case 'r':
+         process_statrc = false;
+         break;
+       case 's':
+         set_safer_mode ();
+         break;
+       case 'v':
+         err_verbosity++;
+         break;
+       case 'V':
+         puts (version);
+         puts (legal);
+         return false;
+        case 'T':
+          force_long_view ();
+          set_testing_mode (true);
+          break;
+       case '?':
+         usage ();
+          return false;
+       case 0:
+         break;
+       default:
+         assert (0);
+       }
+    }
+
+  if (process_statrc)
+    {
+      char *pspprc_fn = fn_search_path ("rc", config_path, NULL);
+      if (pspprc_fn != NULL) 
+        {
+          getl_append_syntax_file (pspprc_fn);
+          free (pspprc_fn); 
+        }
+    }
+
+  for (i = optind; i < argc; i++)
+    if (strchr (argv[i], '='))
+      outp_configure_macro (argv[i]);
+    else 
+      {
+        getl_append_syntax_file (argv[i]);
+        syntax_files++;
+      }
+
+  if (!syntax_files || interactive_mode)
+    getl_append_interactive (readln_read);
+
+  return true;
+}
+
+/* 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"
+"  -a, --algorithm={compatible|enhanced}\n"
+"                            set to `compatible' if you want output\n"
+"                            calculated from broken algorithms\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"
+"\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"
+"  -x, --syntax={compatible|enhanced}\n"
+"                            set to `compatible' if you want only to accept\n"
+"                            spss compatible syntax\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"
+" FILE                       syntax file to execute\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 <%s>.\n");
+
+/* Writes a syntax description to stdout. */
+static void
+usage (void)
+{
+  printf (gettext (pre_syntax_message), program_name);
+  outp_list_classes ();
+  printf (gettext (post_syntax_message), PACKAGE_BUGREPORT);
+}
diff --git a/src/ui/terminal/command-line.h b/src/ui/terminal/command-line.h
new file mode 100644 (file)
index 0000000..c103b69
--- /dev/null
@@ -0,0 +1,27 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#if !INCLUDED_CMDLINE_H
+#define INCLUDED_CMDLINE_H 1
+
+#include <stdbool.h>
+
+bool parse_command_line (int argc, char **argv);
+
+#endif /* cmdline.h */
diff --git a/src/ui/terminal/main.c b/src/ui/terminal/main.c
new file mode 100644 (file)
index 0000000..0a09cda
--- /dev/null
@@ -0,0 +1,270 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+#include <gsl/gsl_errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include "command-line.h"
+#include "command.h"
+#include "dictionary.h"
+#include "message.h"
+#include "file-handle-def.h"
+#include "filename.h"
+#include "line-buffer.h"
+#include "lexer.h"
+#include "output.h"
+#include "progname.h"
+#include "random.h"
+#include "read-line.h"
+#include "settings.h"
+#include "variable.h"
+#include "version.h"
+
+#if HAVE_FPU_CONTROL_H
+#include <fpu_control.h>
+#endif
+
+#if HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#if HAVE_FENV_H
+#include <fenv.h>
+#endif
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+#include <stdlib.h>
+
+#include "debug-print.h"
+
+static void i18n_init (void);
+static void fpu_init (void);
+static void handle_error (int code);
+static int execute_command (void);
+
+/* If a segfault happens, issue a message to that effect and halt */
+void bug_handler(int sig);
+
+/* Handle quit/term/int signals */
+void interrupt_handler(int sig);
+
+void terminate (bool success);
+
+/* Program entry point. */
+int
+main (int argc, char **argv)
+{
+  signal (SIGSEGV, bug_handler);
+  signal (SIGFPE, bug_handler);
+  signal (SIGINT, interrupt_handler);
+
+  set_program_name ("pspp");
+  i18n_init ();
+  fpu_init ();
+  gsl_set_error_handler_off ();
+
+  outp_init ();
+  fn_init ();
+  fh_init ();
+  getl_initialize ();
+  readln_initialize ();
+  settings_init ();
+  random_init ();
+
+  default_dict = dict_create ();
+
+  if (parse_command_line (argc, argv)) 
+    {
+      outp_read_devices ();
+      lex_init ();
+
+      for (;;)
+        {
+          int retval;
+
+          err_check_count ();
+
+          retval = execute_command ();
+          if (retval == CMD_EOF)
+            break;
+          if (retval != CMD_SUCCESS)
+            handle_error (retval);
+        }
+    }
+  
+  terminate (err_error_count == 0);
+  abort ();
+}
+
+/* Parse and execute a command, returning its return code. */
+static int
+execute_command (void)
+{
+  int result;
+  
+  /* Read the command's first token.  
+     The first token is part of the first line of the command. */
+  getl_set_prompt_style (GETL_PROMPT_FIRST);
+  lex_get ();
+  if (token == T_STOP)
+    return CMD_EOF;
+
+  /* Parse the command.
+     Any lines read after the first token must be continuation
+     lines. */
+  getl_set_prompt_style (GETL_PROMPT_LATER);
+  result = cmd_parse ();
+  /* Unset the /ALGORITHM subcommand if it was used */
+  unset_cmd_algorithm ();
+
+  /* Clear any auxiliary data from the dictionary. */
+  dict_clear_aux (default_dict);
+
+  return result;
+}
+
+/* Print an error message corresponding to the command return code
+   CODE. */
+static void
+handle_error (int code)
+{
+  if (code == CMD_CASCADING_FAILURE && !getl_is_interactive ()) 
+    {
+      msg (SW, _("This command not executed.  Stopping here "
+                 "to avoid cascading failures."));
+      getl_abort_noninteractive ();
+      return;
+    }
+
+  switch (code)
+    {
+    case CMD_FAILURE:
+    case CMD_CASCADING_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:
+      abort ();
+    }
+
+  if (!getl_is_interactive ())
+    {
+      while (token != T_STOP && token != '.')
+       lex_get ();
+    }
+  else 
+    {
+      msg (SW, _("The rest of this command has been discarded."));
+      lex_discard_line (); 
+    }
+}
+\f
+static void
+i18n_init (void) 
+{
+#if ENABLE_NLS
+#if HAVE_LC_MESSAGES
+  setlocale (LC_MESSAGES, "");
+#endif
+  setlocale (LC_MONETARY, "");
+  bindtextdomain (PACKAGE, locale_dir);
+  textdomain (PACKAGE);
+#endif /* ENABLE_NLS */
+}
+
+static void
+fpu_init (void) 
+{
+#if HAVE_FEHOLDEXCEPT
+  fenv_t foo;
+  feholdexcept (&foo);
+#elif HAVE___SETFPUCW && defined(_FPU_IEEE)
+  __setfpucw (_FPU_IEEE);
+#endif
+}
+
+/* If a segfault happens, issue a message to that effect and halt */
+void 
+bug_handler(int sig)
+{
+  switch (sig) 
+    {
+    case SIGFPE:
+      request_bug_report_and_abort("Floating Point Exception");
+      break;
+    case SIGSEGV:
+      request_bug_report_and_abort("Segmentation Violation");
+      break;
+    default:
+      request_bug_report_and_abort("");
+      break;
+    }
+}
+
+void 
+interrupt_handler(int sig UNUSED)
+{
+  terminate (false);
+}
+
+
+/* Terminate PSPP.  SUCCESS should be true to exit successfully,
+   false to exit as a failure.  */
+void
+terminate (bool success)
+{
+  static bool terminating = false;
+  if (terminating)
+    return;
+  terminating = true;
+
+  err_done ();
+  outp_done ();
+
+  cancel_transformations ();
+  dict_destroy (default_dict);
+
+  random_done ();
+  settings_done ();
+  fh_done ();
+  lex_done ();
+  getl_uninitialize ();
+
+  exit (success ? EXIT_SUCCESS : EXIT_FAILURE);
+}
diff --git a/src/ui/terminal/read-line.c b/src/ui/terminal/read-line.c
new file mode 100644 (file)
index 0000000..562a6fc
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <errno.h>
+
+#include "read-line.h"
+#include "command.h"
+#include "filename.h"
+#include "version.h"
+#include "str.h"
+#include "table.h"
+#include "message.h"
+#include "filename.h"
+#include "settings.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+
+#if HAVE_READLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+
+static char *history_file;
+#endif /* HAVE_READLINE */
+
+static bool initialised = false;
+
+/* Initialize getl. */
+void
+readln_initialize (void)
+{
+  initialised = true;
+
+#if HAVE_READLINE 
+  rl_completion_entry_function = pspp_completion_function;
+#ifdef unix
+  if (history_file == NULL)
+    {
+      history_file = tilde_expand ("~/.pspp_history");
+      using_history ();
+      read_history (history_file);
+      stifle_history (500);
+    }
+#endif
+#endif
+}
+
+/* Close getl. */
+void
+readln_uninitialize (void)
+{
+  initialised = false;
+
+#if HAVE_READLINE && unix
+  if (history_file != NULL)
+    write_history (history_file);
+#endif
+}
+
+/* Display a welcoming message. */
+static void
+welcome (void)
+{
+  static bool welcomed = false;
+  if (welcomed)
+    return;
+  welcomed = true;
+  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);
+
+#if HAVE_READLINE && unix
+  if (history_file == NULL)
+    {
+      history_file = tilde_expand ("~/.pspp_history");
+      using_history ();
+      read_history (history_file);
+      stifle_history (500);
+    }
+#endif
+}
+
+/* Gets a line from the user and stores it into LINE.
+   Prompts the user with PROMPT.
+   Returns true if successful, false at end of file.
+   Suitable for passing to getl_append_interactive(). */
+bool
+readln_read (struct string *line, const char *prompt)
+{
+#if HAVE_READLINE
+  char *string;
+#endif
+  
+  assert(initialised);
+
+  err_error_count = err_warning_count = 0;
+  err_already_flagged = 0;
+
+  welcome ();
+
+#if HAVE_READLINE
+  string = readline (prompt);
+  if (string == NULL)
+    return false;
+  else 
+    {
+      if (string[0])
+        add_history (string);
+      ds_replace (line, string);
+      free (string);
+      return true; 
+    }
+#else
+  fputs (prompt, stdout);
+  fflush (stdout);
+  if (ds_gets (line, stdin)) 
+    {
+      ds_chomp (line, '\n');
+      return true;
+    }
+  else
+    return false;
+#endif
+}
diff --git a/src/ui/terminal/read-line.h b/src/ui/terminal/read-line.h
new file mode 100644 (file)
index 0000000..0c32dec
--- /dev/null
@@ -0,0 +1,30 @@
+/* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
+
+#ifndef READLN_H
+#define READLN_H
+
+#include "str.h"
+
+void readln_initialize (void);
+void readln_uninitialize (void);
+bool readln_read (struct string *line, const char *prompt);
+
+#endif /* READLN_H */
+
diff --git a/tests/command/do-repeat.sh b/tests/command/do-repeat.sh
new file mode 100755 (executable)
index 0000000..4ec9ff7
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+# This program tests the DO REPEAT command.
+
+TEMPDIR=/tmp/pspp-tst-$$
+TESTFILE=$TEMPDIR/`basename $0`.sps
+
+# ensure that top_builddir  are absolute
+if [ -z "$top_builddir" ] ; then top_builddir=. ; fi
+if [ -z "$top_srcdir" ] ; then top_srcdir=. ; fi
+top_builddir=`cd $top_builddir; pwd`
+
+# ensure that top_srcdir is absolute
+top_srcdir=`cd $top_srcdir; pwd`
+
+STAT_CONFIG_PATH=$top_srcdir/config
+export STAT_CONFIG_PATH
+
+
+cleanup()
+{
+     cd /
+     rm -rf $TEMPDIR
+}
+
+
+fail()
+{
+    echo $activity
+    echo FAILED
+    cleanup;
+    exit 1;
+}
+
+
+no_result()
+{
+    echo $activity
+    echo NO RESULT;
+    cleanup;
+    exit 2;
+}
+
+pass()
+{
+    cleanup;
+    exit 0;
+}
+
+mkdir -p $TEMPDIR
+
+cd $TEMPDIR
+
+
+activity="create program"
+cat > $TESTFILE << EOF
+DATA LIST NOTABLE /a 1.
+BEGIN DATA.
+0
+END DATA.
+
+DO REPEAT h = h0 TO h3 / x = 0 1 2 3 / y = 8 TO 5.
+       COMPUTE h = x + y.
+END REPEAT.
+
+VECTOR v(6).
+COMPUTE #idx = 0.
+DO REPEAT a = 1 TO 2.
+       DO REPEAT b = 3 TO 5.
+               COMPUTE #x = a + b.
+               COMPUTE #idx = #idx + 1.
+               COMPUTE v(#idx) = #x.
+       END REPEAT.
+END REPEAT.
+
+LIST.
+
+EOF
+if [ $? -ne 0 ] ; then no_result ; fi
+
+activity="run program"
+$SUPERVISOR $top_builddir/src/pspp --testing-mode -o raw-ascii $TESTFILE >/dev/null 2>&1
+if [ $? -ne 0 ] ; then no_result ; fi
+
+activity="compare results"
+perl -pi -e 's/^\s*$//g' $TEMPDIR/pspp.list
+diff -b  $TEMPDIR/pspp.list - <<EOF
+a       h0       h1       h2       h3       v1       v2       v3       v4       v5       v6
+- -------- -------- -------- -------- -------- -------- -------- -------- -------- --------
+0     8.00     8.00     8.00     8.00     4.00     5.00     6.00     5.00     6.00     7.00 
+EOF
+if [ $? -ne 0 ] ; then fail ; fi
+
+
+pass