Finish fixing MATCH FILES (PR 11677).
authorBen Pfaff <blp@gnu.org>
Tue, 26 Apr 2005 06:32:02 +0000 (06:32 +0000)
committerBen Pfaff <blp@gnu.org>
Tue, 26 Apr 2005 06:32:02 +0000 (06:32 +0000)
Kluge to make some variable renaming sort of work.
Needs real fix.
Add functions for comparing sets of variables between cases.

13 files changed:
doc/files.texi
po/en_GB.po
po/pspp.pot
src/ChangeLog
src/aggregate.c
src/case.c
src/case.h
src/dictionary.c
src/get.c
src/vfm.c
tests/ChangeLog
tests/Makefile.am
tests/command/match-files.sh [new file with mode: 0755]

index e1922b27d7228e972c6a962527c76540d954aa03..6f723213ef52447f957f414a37981dd3aaac0fd2 100644 (file)
@@ -164,12 +164,13 @@ data is read later, when a procedure is executed.
 
 @display
 MATCH FILES
-        /BY var_list
         /@{FILE,TABLE@}=@{*,'filename'@}
         /DROP=var_list
         /KEEP=var_list
         /RENAME=(src_names=target_names)@dots{}
         /IN=var_name
+
+        /BY var_list
         /FIRST=var_name
         /LAST=var_name
         /MAP
@@ -185,7 +186,7 @@ variables.  The results of the merge become the new active file.
 The BY subcommand specifies a list of variables that are used to match
 records from each of the system files.  Variables specified must exist
 in all the files specified on FILE and TABLE.  BY should usually be
-specified.  If TABLE is used then BY is required.
+specified.  If TABLE or IN is used then BY is required.
 
 Specify FILE with a system file as a file name string or file handle
 (@pxref{FILE HANDLE}), or with an asterisk (@samp{*}) to
@@ -203,15 +204,22 @@ It is incorrect to have records with duplicate BY values in table lookup
 files.
 
 Any number of FILE and TABLE subcommands may be specified.  Each
-instance of FILE or TABLE can be followed by DROP, KEEP, and/or RENAME
-subcommands.  These take the same form as the corresponding subcommands
-of @cmd{GET} (@pxref{GET}), and perform the same functions.
+instance of FILE or TABLE can be followed by any sequence of DROP,
+KEEP, or RENAME subcommands.  These have the same form and meaning as
+the corresponding subcommands of @cmd{GET} (@pxref{GET}), but apply
+only to variables in the given file.
+
+Each FILE or TABLE may optionally be followed by an IN subcommand,
+which creates a numeric variable with the specified name and format
+F1.0.  The IN variable takes value 1 in a case if the given file
+contributed a row to the merged file, 0 otherwise.  The DROP, KEEP,
+and RENAME subcommands do not affect IN variables.
 
 Variables belonging to files that are not present for the current case
 are set to the system-missing value for numeric variables or spaces for
 string variables.
 
-IN, FIRST, LAST, and MAP are currently not used.
+FIRST, LAST, and MAP are currently ignored.
 
 @cmd{MATCH FILES} may not be specified following @cmd{TEMPORARY}
 (@pxref{TEMPORARY}) if the active file is used as an input source.
index 6411827209852afd62b555b23f0fa869deedfb63..c93f865643c63ffa057bce92aa56aaaf96522e64 100644 (file)
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PSPP 0.3.1\n"
 "Report-Msgid-Bugs-To: pspp-dev@gnu.org\n"
-"POT-Creation-Date: 2005-04-23 15:57+0800\n"
+"POT-Creation-Date: 2005-04-25 22:30-0700\n"
 "PO-Revision-Date: 2004-01-23 13:04+0800\n"
 "Last-Translator: John Darrington <john@darrington.wattle.id.au>\n"
 "Language-Team: John Darrington <john@darrington.wattle.id.au>\n"
@@ -16,62 +16,62 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n!=1);\n"
 
-#: src/aggregate.c:193
+#: src/aggregate.c:195
 msgid "while expecting COLUMNWISE"
 msgstr ""
 
-#: src/aggregate.c:227
+#: src/aggregate.c:229
 msgid "expecting BREAK"
 msgstr ""
 
-#: src/aggregate.c:232
+#: src/aggregate.c:234
 msgid ""
 "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."
 msgstr ""
 
-#: src/aggregate.c:389
+#: src/aggregate.c:390
 msgid "expecting aggregation function"
 msgstr ""
 
-#: src/aggregate.c:405
+#: src/aggregate.c:406
 #, c-format
 msgid "Unknown aggregation function %s."
 msgstr ""
 
-#: src/aggregate.c:420
+#: src/aggregate.c:421
 msgid "expecting `('"
 msgstr ""
 
-#: src/aggregate.c:457
+#: src/aggregate.c:458
 #, c-format
 msgid "Missing argument %d to %s."
 msgstr ""
 
-#: src/aggregate.c:465
+#: src/aggregate.c:466
 #, c-format
 msgid "Arguments to %s must be of same type as source variables."
 msgstr ""
 
-#: src/aggregate.c:475
+#: src/aggregate.c:476
 msgid "expecting `)'"
 msgstr ""
 
-#: src/aggregate.c:487
+#: src/aggregate.c:488
 #, c-format
 msgid ""
 "Number of source variables (%d) does not match number of target variables (%"
 "d)."
 msgstr ""
 
-#: src/aggregate.c:504
+#: src/aggregate.c:505
 #, c-format
 msgid ""
 "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."
 msgstr ""
 
-#: src/aggregate.c:571
+#: src/aggregate.c:572
 #, c-format
 msgid ""
 "Variable name %s is not unique within the aggregate file dictionary, which "
@@ -733,7 +733,7 @@ msgid ""
 msgstr ""
 
 #: src/data-list.c:795 src/data-list.c:923 src/descript.c:880 src/print.c:796
-#: src/sysfile-info.c:134 src/sysfile-info.c:367 src/vfm.c:830
+#: src/sysfile-info.c:134 src/sysfile-info.c:367 src/vfm.c:807
 msgid "Variable"
 msgstr ""
 
@@ -1142,7 +1142,7 @@ msgid ""
 "The entry \"%s\" in the variable name map, has no corresponding variable"
 msgstr ""
 
-#: src/dictionary.c:856
+#: src/dictionary.c:857
 msgid ""
 "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."
@@ -1227,25 +1227,6 @@ msgstr ""
 msgid "installation error"
 msgstr ""
 
-#: src/filename.c:221
-#, c-format
-msgid "Searching for `%s'..."
-msgstr ""
-
-#: src/filename.c:229 src/filename.c:261
-msgid "Search unsuccessful!"
-msgstr ""
-
-#: src/filename.c:254
-#, c-format
-msgid "Found `%s'."
-msgstr ""
-
-#: src/filename.c:686
-#, c-format
-msgid "Not opening pipe file `%s' because SAFER option set."
-msgstr ""
-
 #: src/file-type.c:129
 msgid "MIXED, GROUPED, or NESTED expected."
 msgstr ""
@@ -1295,7 +1276,7 @@ msgstr ""
 msgid "YES or NO expected after ORDERED."
 msgstr ""
 
-#: src/file-type.c:248 src/file-type.c:543 src/get.c:438
+#: src/file-type.c:248 src/file-type.c:543
 msgid "while expecting a valid subcommand"
 msgstr ""
 
@@ -1384,6 +1365,25 @@ msgstr ""
 msgid "Unknown record type %g."
 msgstr ""
 
+#: src/filename.c:221
+#, c-format
+msgid "Searching for `%s'..."
+msgstr ""
+
+#: src/filename.c:229 src/filename.c:261
+msgid "Search unsuccessful!"
+msgstr ""
+
+#: src/filename.c:254
+#, c-format
+msgid "Found `%s'."
+msgstr ""
+
+#: src/filename.c:686
+#, c-format
+msgid "Not opening pipe file `%s' because SAFER option set."
+msgstr ""
+
 #: src/flip.c:82
 msgid ""
 "FLIP ignores TEMPORARY.  Temporary transformations will be made permanent."
@@ -1543,11 +1543,11 @@ msgstr ""
 msgid "Unsupported sysfile version: %d. Using version %d instead."
 msgstr ""
 
-#: src/get.c:444
-msgid "All variables deleted from system file dictionary."
+#: src/get.c:412 src/print.c:179
+msgid "expecting a valid subcommand"
 msgstr ""
 
-#: src/get.c:486
+#: src/get.c:457
 #, c-format
 msgid ""
 "Cannot rename %s as %s because there already exists a variable named %s.  To "
@@ -1555,69 +1555,82 @@ msgid ""
 "as \"/RENAME (A=B)(B=C)(C=A)\", or equivalently, \"/RENAME (A B C=B C A)\"."
 msgstr ""
 
-#: src/get.c:511
+#: src/get.c:482
 msgid "`=' expected after variable list."
 msgstr ""
 
-#: src/get.c:518
+#: src/get.c:489
 #, c-format
 msgid ""
 "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."
 msgstr ""
 
-#: src/get.c:531
+#: src/get.c:502
 #, c-format
 msgid "Requested renaming duplicates variable name %s."
 msgstr ""
 
-#: src/get.c:768
+#: src/get.c:532
+msgid "Cannot DROP all variables from dictionary."
+msgstr ""
+
+#: src/get.c:793
 msgid "The active file may not be specified more than once."
 msgstr ""
 
-#: src/get.c:777
+#: src/get.c:802
 msgid "Cannot specify the active file since no active file has been defined."
 msgstr ""
 
-#: src/get.c:785
+#: src/get.c:810
 msgid ""
 "MATCH FILES may not be used after TEMPORARY when the active file is an input "
 "source.  Temporary transformations will be made permanent."
 msgstr ""
 
-#: src/get.c:823
+#: src/get.c:848
 msgid "Multiple IN subcommands for a single FILE or TABLE."
 msgstr ""
 
-#: src/get.c:843
+#: src/get.c:868
 msgid "BY may appear at most once."
 msgstr ""
 
-#: src/get.c:863
+#: src/get.c:888
 #, c-format
 msgid "File %s lacks BY variable %s."
 msgstr ""
 
-#: src/get.c:876
+#: src/get.c:901
 msgid "FIRST may appear at most once."
 msgstr ""
 
-#: src/get.c:890
+#: src/get.c:915
 msgid "LAST may appear at most once."
 msgstr ""
 
-#: src/get.c:919
+#: src/get.c:956
 msgid "BY is required when TABLE is specified."
 msgstr ""
 
-#: src/get.c:1391
+#: src/get.c:961
+msgid "BY is required when IN is specified."
+msgstr ""
+
+#: src/get.c:988
+#, c-format
+msgid "IN variable name %s duplicates an existing variable name."
+msgstr ""
+
+#: src/get.c:1418
 #, c-format
 msgid ""
 "Variable %s in file %s (%s) has different type or width from the same "
 "variable in earlier file (%s)."
 msgstr ""
 
-#: src/get.c:1488
+#: src/get.c:1512
 msgid "expecting COMM or TAPE"
 msgstr ""
 
@@ -2851,10 +2864,6 @@ msgstr ""
 msgid "Charts are currently unsupported with postscript drivers."
 msgstr ""
 
-#: src/print.c:179
-msgid "expecting a valid subcommand"
-msgstr ""
-
 #: src/print.c:365 src/print.c:382
 #, c-format
 msgid "%g is not a valid column location."
@@ -3497,7 +3506,7 @@ msgstr ""
 msgid "Documents in the active file:"
 msgstr ""
 
-#: src/sysfile-info.c:374 src/sysfile-info.c:532 src/vfm.c:832
+#: src/sysfile-info.c:374 src/sysfile-info.c:532 src/vfm.c:809
 msgid "Label"
 msgstr ""
 
@@ -3520,7 +3529,7 @@ msgstr ""
 msgid "Missing Values: "
 msgstr ""
 
-#: src/sysfile-info.c:531 src/vfm.c:831 src/crosstabs.q:1099
+#: src/sysfile-info.c:531 src/vfm.c:808 src/crosstabs.q:1099
 #: src/crosstabs.q:1126 src/crosstabs.q:1146 src/crosstabs.q:1168
 #: src/examine.q:1129 src/frequencies.q:1138 src/frequencies.q:1259
 msgid "Value"
index f6e648bd0d767c53963c616c0932b60f5d36d2cd..f7d3a30987abbbae809abc2232f7e939ba97ade0 100644 (file)
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: pspp-dev@gnu.org\n"
-"POT-Creation-Date: 2005-04-23 15:57+0800\n"
+"POT-Creation-Date: 2005-04-25 22:30-0700\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,62 +17,62 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
 
-#: src/aggregate.c:193
+#: src/aggregate.c:195
 msgid "while expecting COLUMNWISE"
 msgstr ""
 
-#: src/aggregate.c:227
+#: src/aggregate.c:229
 msgid "expecting BREAK"
 msgstr ""
 
-#: src/aggregate.c:232
+#: src/aggregate.c:234
 msgid ""
 "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."
 msgstr ""
 
-#: src/aggregate.c:389
+#: src/aggregate.c:390
 msgid "expecting aggregation function"
 msgstr ""
 
-#: src/aggregate.c:405
+#: src/aggregate.c:406
 #, c-format
 msgid "Unknown aggregation function %s."
 msgstr ""
 
-#: src/aggregate.c:420
+#: src/aggregate.c:421
 msgid "expecting `('"
 msgstr ""
 
-#: src/aggregate.c:457
+#: src/aggregate.c:458
 #, c-format
 msgid "Missing argument %d to %s."
 msgstr ""
 
-#: src/aggregate.c:465
+#: src/aggregate.c:466
 #, c-format
 msgid "Arguments to %s must be of same type as source variables."
 msgstr ""
 
-#: src/aggregate.c:475
+#: src/aggregate.c:476
 msgid "expecting `)'"
 msgstr ""
 
-#: src/aggregate.c:487
+#: src/aggregate.c:488
 #, c-format
 msgid ""
 "Number of source variables (%d) does not match number of target variables (%"
 "d)."
 msgstr ""
 
-#: src/aggregate.c:504
+#: src/aggregate.c:505
 #, c-format
 msgid ""
 "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."
 msgstr ""
 
-#: src/aggregate.c:571
+#: src/aggregate.c:572
 #, c-format
 msgid ""
 "Variable name %s is not unique within the aggregate file dictionary, which "
@@ -734,7 +734,7 @@ msgid ""
 msgstr ""
 
 #: src/data-list.c:795 src/data-list.c:923 src/descript.c:880 src/print.c:796
-#: src/sysfile-info.c:134 src/sysfile-info.c:367 src/vfm.c:830
+#: src/sysfile-info.c:134 src/sysfile-info.c:367 src/vfm.c:807
 msgid "Variable"
 msgstr ""
 
@@ -1143,7 +1143,7 @@ msgid ""
 "The entry \"%s\" in the variable name map, has no corresponding variable"
 msgstr ""
 
-#: src/dictionary.c:856
+#: src/dictionary.c:857
 msgid ""
 "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."
@@ -1228,25 +1228,6 @@ msgstr ""
 msgid "installation error"
 msgstr ""
 
-#: src/filename.c:221
-#, c-format
-msgid "Searching for `%s'..."
-msgstr ""
-
-#: src/filename.c:229 src/filename.c:261
-msgid "Search unsuccessful!"
-msgstr ""
-
-#: src/filename.c:254
-#, c-format
-msgid "Found `%s'."
-msgstr ""
-
-#: src/filename.c:686
-#, c-format
-msgid "Not opening pipe file `%s' because SAFER option set."
-msgstr ""
-
 #: src/file-type.c:129
 msgid "MIXED, GROUPED, or NESTED expected."
 msgstr ""
@@ -1296,7 +1277,7 @@ msgstr ""
 msgid "YES or NO expected after ORDERED."
 msgstr ""
 
-#: src/file-type.c:248 src/file-type.c:543 src/get.c:438
+#: src/file-type.c:248 src/file-type.c:543
 msgid "while expecting a valid subcommand"
 msgstr ""
 
@@ -1385,6 +1366,25 @@ msgstr ""
 msgid "Unknown record type %g."
 msgstr ""
 
+#: src/filename.c:221
+#, c-format
+msgid "Searching for `%s'..."
+msgstr ""
+
+#: src/filename.c:229 src/filename.c:261
+msgid "Search unsuccessful!"
+msgstr ""
+
+#: src/filename.c:254
+#, c-format
+msgid "Found `%s'."
+msgstr ""
+
+#: src/filename.c:686
+#, c-format
+msgid "Not opening pipe file `%s' because SAFER option set."
+msgstr ""
+
 #: src/flip.c:82
 msgid ""
 "FLIP ignores TEMPORARY.  Temporary transformations will be made permanent."
@@ -1544,11 +1544,11 @@ msgstr ""
 msgid "Unsupported sysfile version: %d. Using version %d instead."
 msgstr ""
 
-#: src/get.c:444
-msgid "All variables deleted from system file dictionary."
+#: src/get.c:412 src/print.c:179
+msgid "expecting a valid subcommand"
 msgstr ""
 
-#: src/get.c:486
+#: src/get.c:457
 #, c-format
 msgid ""
 "Cannot rename %s as %s because there already exists a variable named %s.  To "
@@ -1556,69 +1556,82 @@ msgid ""
 "as \"/RENAME (A=B)(B=C)(C=A)\", or equivalently, \"/RENAME (A B C=B C A)\"."
 msgstr ""
 
-#: src/get.c:511
+#: src/get.c:482
 msgid "`=' expected after variable list."
 msgstr ""
 
-#: src/get.c:518
+#: src/get.c:489
 #, c-format
 msgid ""
 "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."
 msgstr ""
 
-#: src/get.c:531
+#: src/get.c:502
 #, c-format
 msgid "Requested renaming duplicates variable name %s."
 msgstr ""
 
-#: src/get.c:768
+#: src/get.c:532
+msgid "Cannot DROP all variables from dictionary."
+msgstr ""
+
+#: src/get.c:793
 msgid "The active file may not be specified more than once."
 msgstr ""
 
-#: src/get.c:777
+#: src/get.c:802
 msgid "Cannot specify the active file since no active file has been defined."
 msgstr ""
 
-#: src/get.c:785
+#: src/get.c:810
 msgid ""
 "MATCH FILES may not be used after TEMPORARY when the active file is an input "
 "source.  Temporary transformations will be made permanent."
 msgstr ""
 
-#: src/get.c:823
+#: src/get.c:848
 msgid "Multiple IN subcommands for a single FILE or TABLE."
 msgstr ""
 
-#: src/get.c:843
+#: src/get.c:868
 msgid "BY may appear at most once."
 msgstr ""
 
-#: src/get.c:863
+#: src/get.c:888
 #, c-format
 msgid "File %s lacks BY variable %s."
 msgstr ""
 
-#: src/get.c:876
+#: src/get.c:901
 msgid "FIRST may appear at most once."
 msgstr ""
 
-#: src/get.c:890
+#: src/get.c:915
 msgid "LAST may appear at most once."
 msgstr ""
 
-#: src/get.c:919
+#: src/get.c:956
 msgid "BY is required when TABLE is specified."
 msgstr ""
 
-#: src/get.c:1391
+#: src/get.c:961
+msgid "BY is required when IN is specified."
+msgstr ""
+
+#: src/get.c:988
+#, c-format
+msgid "IN variable name %s duplicates an existing variable name."
+msgstr ""
+
+#: src/get.c:1418
 #, c-format
 msgid ""
 "Variable %s in file %s (%s) has different type or width from the same "
 "variable in earlier file (%s)."
 msgstr ""
 
-#: src/get.c:1488
+#: src/get.c:1512
 msgid "expecting COMM or TAPE"
 msgstr ""
 
@@ -2852,10 +2865,6 @@ msgstr ""
 msgid "Charts are currently unsupported with postscript drivers."
 msgstr ""
 
-#: src/print.c:179
-msgid "expecting a valid subcommand"
-msgstr ""
-
 #: src/print.c:365 src/print.c:382
 #, c-format
 msgid "%g is not a valid column location."
@@ -3498,7 +3507,7 @@ msgstr ""
 msgid "Documents in the active file:"
 msgstr ""
 
-#: src/sysfile-info.c:374 src/sysfile-info.c:532 src/vfm.c:832
+#: src/sysfile-info.c:374 src/sysfile-info.c:532 src/vfm.c:809
 msgid "Label"
 msgstr ""
 
@@ -3521,7 +3530,7 @@ msgstr ""
 msgid "Missing Values: "
 msgstr ""
 
-#: src/sysfile-info.c:531 src/vfm.c:831 src/crosstabs.q:1099
+#: src/sysfile-info.c:531 src/vfm.c:808 src/crosstabs.q:1099
 #: src/crosstabs.q:1126 src/crosstabs.q:1146 src/crosstabs.q:1168
 #: src/examine.q:1129 src/frequencies.q:1138 src/frequencies.q:1259
 msgid "Value"
index 248c344e4703ea277799546c223b7d15d2ec5a5c..e31bb4ae4e4a536a96971899c98696fb1635c100 100644 (file)
@@ -1,3 +1,52 @@
+Mon Apr 25 22:55:59 2005  Ben Pfaff  <blp@gnu.org>
+
+       Finish fixing MATCH FILES (PR 11677).
+
+       * get.c: (trim_dictionary) Rewrite in terms of drop_variables(),
+       keep_variables(), rename_variables().
+       (drop_variables) New function.
+       (keep_variables) New function.
+       (struct mtf_file) Rename `in' to `in_name'.  Add `in_var'.
+       (cmd_match_files) Deal with in_var.  Use drop_variables(),
+       keep_variables().  When IN is specified, require BY.  Set master
+       variables after master dictionary is complete.  Add IN variables
+       after master dictionary is complete.
+       (mtf_free_file) Free `in_name'.
+       (mtf_delete_file_in_place) Set in_var value to 0.
+       (mtf_read_nonactive_records)  Rephrase.
+       (mtf_processing) Support IN.  Rephrase.  Fix bugs.
+       (mtf_merge_dictionary) Don't set master variables; we do that
+       later now.
+       (get_master) Don't insist that there's a master variable.
+       
+Mon Apr 25 22:55:22 2005  Ben Pfaff  <blp@gnu.org>
+
+       Kluge to make some variable renaming sort of work.
+       Needs real fix.
+
+       * dictionary.c: (dict_rename_var) Call dict_add_longvar_entry().
+
+Mon Apr 25 22:52:28 2005  Ben Pfaff  <blp@gnu.org>
+
+       Add functions for comparing sets of variables between cases.
+       Use the functions.
+
+       * case.c: (case_compare) New function.
+       (case_compare_2dict) New function.
+       
+       * aggregate.c: (struct agr_proc) Remove `prev_break' member.  Add
+       `break_case'.
+       (cmd_aggregate) Nullify break_case.  Don't call
+       initialize_aggregate_info().
+       (agr_destroy) Destroy break_case.
+       (aggregate_single_case) Rewrite.  Use case_compare().
+       (dump_aggregate_info) Copy from break_case into output.
+       (initialize_aggregate_info) Copy break_case from input.
+
+       * get.c: (mtf_compare_BY_values) Use case_compare_2dict().
+
+       * vfm.c: (equal_splits) Use case_compare().
+
 Sat Apr 23 17:01:04 WST 2005 John Darrington <john@darrington.wattle.id.au>
 
        * dictionary.c vars-prs.c sfm-write.c: Fixed some memory leaks
index 6da6242c670a0be3fad5dfaf321f1db73cfd2495..fb955330a4102e0aea3f13156573f4d9414a2438 100644 (file)
@@ -124,7 +124,7 @@ struct agr_proc
     struct sort_criteria *sort;         /* Sort criteria. */
     struct variable **break_vars;       /* Break variables. */
     size_t break_var_cnt;               /* Number of break variables. */
-    union value *prev_break;            /* Last values 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. */
@@ -133,7 +133,8 @@ struct agr_proc
     struct ccase agr_case;              /* Aggregate case for output. */
   };
 
-static void initialize_aggregate_info (struct agr_proc *);
+static void initialize_aggregate_info (struct agr_proc *,
+                                       const struct ccase *);
 
 /* Prototypes. */
 static int parse_aggregate_functions (struct agr_proc *);
@@ -164,6 +165,7 @@ cmd_aggregate (void)
 
   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));
@@ -248,7 +250,6 @@ cmd_aggregate (void)
   /* Initialize. */
   agr.case_cnt = 0;
   case_create (&agr.agr_case, dict_get_next_value_idx (agr.dict));
-  initialize_aggregate_info (&agr);
 
   /* Output to active file or external file? */
   if (out_file == NULL) 
@@ -655,7 +656,7 @@ agr_destroy (struct agr_proc *agr)
   if (agr->sort != NULL)
     sort_destroy_criteria (agr->sort);
   free (agr->break_vars);
-  free (agr->prev_break);
+  case_destroy (&agr->break_case);
   for (iter = agr->agr_vars; iter; iter = next)
     {
       next = iter->next;
@@ -693,105 +694,21 @@ static int
 aggregate_single_case (struct agr_proc *agr,
                        const struct ccase *input, struct ccase *output)
 {
-  /* The first case always begins a new break group.  We also need to
-     preserve the values of the case for later comparison. */
+  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))
     {
-      int n_elem = 0;
-      
-      {
-       int i;
-
-       for (i = 0; i < agr->break_var_cnt; i++)
-         n_elem += agr->break_vars[i]->nv;
-      }
-      
-      agr->prev_break = xmalloc (sizeof *agr->prev_break * n_elem);
-
-      /* Copy INPUT into prev_break. */
-      {
-       union value *iter = agr->prev_break;
-       int i;
+      dump_aggregate_info (agr, output);
+      finished_group = true;
 
-       for (i = 0; i < agr->break_var_cnt; i++)
-         {
-           struct variable *v = agr->break_vars[i];
-           
-           if (v->type == NUMERIC)
-             (iter++)->f = case_num (input, v->fv);
-           else
-             {
-               memcpy (iter->s, case_str (input, v->fv), v->width);
-               iter += v->nv;
-             }
-         }
-      }
-           
-      accumulate_aggregate_info (agr, input);
-       
-      return 0;
+      initialize_aggregate_info (agr, input);
     }
-      
-  /* Compare the value of each break variable to the values on the
-     previous case. */
-  {
-    union value *iter = agr->prev_break;
-    int i;
-    
-    for (i = 0; i < agr->break_var_cnt; i++)
-      {
-       struct variable *v = agr->break_vars[i];
-      
-       switch (v->type)
-         {
-         case NUMERIC:
-           if (case_num (input, v->fv) != iter->f)
-             goto not_equal;
-           iter++;
-           break;
-         case ALPHA:
-           if (memcmp (case_str (input, v->fv), iter->s, v->width))
-             goto not_equal;
-           iter += v->nv;
-           break;
-         default:
-           assert (0);
-         }
-      }
-  }
-
-  accumulate_aggregate_info (agr, input);
 
-  return 0;
-  
-not_equal:
-  /* The values of the break variable are different from the values on
-     the previous case.  That means that it's time to dump aggregate
-     info. */
-  dump_aggregate_info (agr, output);
-  initialize_aggregate_info (agr);
   accumulate_aggregate_info (agr, input);
-
-  /* Copy INPUT into prev_break. */
-  {
-    union value *iter = agr->prev_break;
-    int i;
-
-    for (i = 0; i < agr->break_var_cnt; i++)
-      {
-       struct variable *v = agr->break_vars[i];
-           
-       if (v->type == NUMERIC)
-         (iter++)->f = case_num (input, v->fv);
-       else
-         {
-           memcpy (iter->s, case_str (input, v->fv), v->width);
-           iter += v->nv;
-         }
-      }
-  }
-  
-  return 1;
+  return finished_group;
 }
 
 /* Accumulates aggregation data from the case INPUT. */
@@ -978,11 +895,11 @@ dump_aggregate_info (struct agr_proc *agr, struct ccase *output)
 
     for (i = 0; i < agr->break_var_cnt; i++) 
       {
-        int nv = agr->break_vars[i]->nv;
+        struct variable *v = agr->break_vars[i];
         memcpy (case_data_rw (output, value_idx),
-                &agr->prev_break[value_idx],
-                sizeof (union value) * nv);
-        value_idx += nv; 
+                case_data (&agr->break_case, v->fv),
+                sizeof (union value) * v->nv);
+        value_idx += v->nv; 
       }
   }
   
@@ -1098,10 +1015,13 @@ dump_aggregate_info (struct agr_proc *agr, struct ccase *output)
 
 /* Resets the state for all the aggregate functions. */
 static void
-initialize_aggregate_info (struct agr_proc *agr)
+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;
index 80478e7d72c36b426c2155e916d531a80df5f0a2..a1c65d6b68362322c038a0e62026b0e6aabb31d6 100644 (file)
@@ -24,6 +24,7 @@
 #include "val.h"
 #include "alloc.h"
 #include "str.h"
+#include "var.h"
 
 #ifdef GLOBAL_DEBUGGING
 #undef NDEBUG
@@ -349,6 +350,75 @@ case_data_rw (struct ccase *c, size_t 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)
+{
+  for (; var_cnt-- > 0; vp++) 
+    {
+      struct variable *v = *vp;
+
+      if (v->width == 0) 
+        {
+          double af = case_num (a, v->fv);
+          double bf = case_num (b, v->fv);
+
+          if (af != bf) 
+            return af > bf ? 1 : -1;
+        }
+      else 
+        {
+          const char *as = case_str (a, v->fv);
+          const char *bs = case_str (b, v->fv);
+          int cmp = memcmp (as, bs, v->width);
+
+          if (cmp != 0)
+            return cmp;
+        }
+    }
+  return 0;
+}
+
+
+/* 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.
 
index c7d7e9fbbc108fdade4f3a6af1f88554ddbfdb24..fc75c74ae4aef1d61ecc3a749b9b2e26e4f619eb 100644 (file)
@@ -21,6 +21,7 @@
 #define HEADER_CASE
 
 #include <stddef.h>
+#include "bool.h"
 #include "val.h"
 
 /* Opaque structure that represents a case.  Use accessor
@@ -76,6 +77,13 @@ 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 *);
 
index 141dd8e1f38f914858832120c362b0173a1c73b7..45d03ebb9a21ec8c8ccd764b421a479213093365 100644 (file)
@@ -566,6 +566,7 @@ dict_rename_var (struct dictionary *d, struct variable *v,
   strncpy (v->name, new_name, sizeof v->name);
   v->name[SHORT_NAME_LEN] = '\0';
   hsh_force_insert (d->name_tab, v);
+  dict_add_longvar_entry (d, new_name, new_name);
 }
 
 /* Returns the variable named NAME in D, or a null pointer if no
index 0f571d90468dcb62b634af6b428a5d1934d9423d..ab9bb0ce78a006341ee2e56215f57fe2ca767ebc 100644 (file)
--- a/src/get.c
+++ b/src/get.c
@@ -356,7 +356,9 @@ save_trns_free (struct trns_header *t_)
     }
 }
 
-static int rename_variables (struct dictionary *dict);
+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
@@ -393,57 +395,26 @@ trim_dictionary (struct dictionary *dict, enum operation op, int *compress)
   
   while (lex_match ('/'))
     {
+      bool ok = true;
+      
       if (op == OP_SAVE && lex_match_id ("COMPRESSED"))
        *compress = 1;
       else if (op == OP_SAVE && lex_match_id ("UNCOMPRESSED"))
        *compress = 0;
       else if (lex_match_id ("DROP"))
-       {
-         struct variable **v;
-         int nv;
-
-         lex_match ('=');
-         if (!parse_variables (dict, &v, &nv, PV_NONE))
-           return false;
-          dict_delete_vars (dict, v, nv);
-          free (v);
-       }
+        ok = drop_variables (dict);
       else if (lex_match_id ("KEEP"))
-       {
-         struct variable **v;
-         int nv;
-          int 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 = xrealloc (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);
-       }
+       ok = keep_variables (dict);
       else if (lex_match_id ("RENAME"))
-       {
-         if (!rename_variables (dict))
-           return false;
-       }
+        ok = rename_variables (dict);
       else
        {
-         lex_error (_("while expecting a valid subcommand"));
-         return false;
+         lex_error (_("expecting a valid subcommand"));
+         ok = false;
        }
 
-      if (dict_get_var_cnt (dict) == 0)
-       {
-         msg (SE, _("All variables deleted from system file dictionary."));
-         return false;
-       }
+      if (!ok)
+        return false;
     }
 
   if (!lex_end_of_command ())
@@ -454,7 +425,7 @@ trim_dictionary (struct dictionary *dict, enum operation op, int *compress)
 }
 
 /* Parses and performs the RENAME subcommand of GET and SAVE. */
-static int
+static bool
 rename_variables (struct dictionary *dict)
 {
   int i;
@@ -541,6 +512,54 @@ done:
 
   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;
+  int 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;
+  int nv;
+  int 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 = xrealloc (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
 /* EXPORT procedure. */
 struct export_proc 
@@ -650,7 +669,10 @@ struct mtf_file
     struct file_handle *handle; /* File handle. */
     struct sfm_reader *reader;  /* System file reader. */
     struct dictionary *dict;   /* Dictionary from system file. */
-    char in[SHORT_NAME_LEN + 1]; /* Name of the variable from IN=. */
+
+    /* IN subcommand. */
+    char *in_name;              /* Variable name. */
+    struct variable *in_var;    /* Variable (in master dictionary). */
 
     struct ccase input;         /* Input record. */
   };
@@ -694,9 +716,11 @@ 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;
   
   mtf.head = mtf.tail = NULL;
   mtf.by_cnt = 0;
@@ -728,7 +752,8 @@ cmd_match_files (void)
       file->handle = NULL;
       file->reader = NULL;
       file->dict = NULL;
-      file->in[0] = '\0';
+      file->in_name = NULL;
+      file->in_var = NULL;
       case_nullify (&file->input);
 
       /* FILEs go first, then TABLEs. */
@@ -818,25 +843,25 @@ cmd_match_files (void)
                 goto error;
               }
 
-            if (file->in[0])
+            if (file->in_name != NULL)
               {
                 msg (SE, _("Multiple IN subcommands for a single FILE or "
                            "TABLE."));
                 goto error;
               }
-            strcpy (file->in, tokid);
+            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;
-          struct mtf_file *iter;
           
          if (mtf.by_cnt)
            {
@@ -901,6 +926,16 @@ cmd_match_files (void)
        {
          /* 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);
@@ -914,12 +949,50 @@ cmd_match_files (void)
         }
     }
 
-  if (mtf.by_cnt == 0 && saw_table)
+  if (mtf.by_cnt == 0)
     {
-      msg (SE, _("BY is required when TABLE is specified."));
-      goto error;
+      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;
+        }
     }
 
+  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);
+        }
+    }
+
+  for (iter = mtf.head; iter != NULL; iter = iter->next) 
+    if (iter->in_name != NULL)
+      {
+        static const struct fmt_spec f1_0 = {FMT_F, 1, 0};
+        
+        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);
+            goto error;
+          }
+        iter->in_var->print = iter->in_var->write = f1_0;
+      }
+    
   /* MATCH FILES performs an n-way merge on all its input files.
      Abstract algorithm:
 
@@ -927,29 +1000,27 @@ cmd_match_files (void)
 
      2. If no FILEs are left, stop.  Otherwise, proceed to step 3.
 
-     3. Find the FILE input record with minimum BY values.  Store all
-     the values from this input record into the output record.
-
-     4. Find all the FILE input records with BY values identical to
-     the minimums.  Store all the values from these input records into
+     3. Find the FILE input record(s) that have minimum BY
+     values.  Store all the values from these input records into
      the output record.
 
-     5. For every TABLE, read another record as long as the BY values
+     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.
 
-     6. Write the output record.
+     5. Write the output record.
 
-     7. Read another record from each input file FILE and TABLE that
+     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.
 
-     8. Repeat from step 2.
+     7. Repeat from step 2.
 
-     Unfortunately, this algorithm can't be directly implemented
-     because there's no function to read a record from the active
-     file; instead, it has to be done using callbacks.
+     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
@@ -958,13 +1029,12 @@ cmd_match_files (void)
   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 = xmalloc (dict_get_var_cnt (mtf.dict) * sizeof *mtf.seq_nums);
-  memset (mtf.seq_nums, 0,
-          dict_get_var_cnt (mtf.dict) * sizeof *mtf.seq_nums);
+  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));
 
   mtf_read_nonactive_records (&mtf);
@@ -986,7 +1056,7 @@ error:
   return CMD_FAILURE;
 }
 
-/* Repeats 2...8 an arbitrary number of times. */
+/* Repeats 2...7 an arbitrary number of times. */
 static void
 mtf_processing_finish (void *mtf_)
 {
@@ -1037,6 +1107,7 @@ mtf_free_file (struct mtf_file *file)
   if (file->dict != default_dict)
     dict_destroy (file->dict);
   case_destroy (&file->input);
+  free (file->in_name);
   free (file);
 }
 
@@ -1049,7 +1120,6 @@ mtf_free (struct mtf_proc *mtf)
   for (iter = mtf->head; iter; iter = next)
     {
       next = iter->next;
-
       mtf_free_file (iter);
     }
   
@@ -1065,6 +1135,7 @@ static void
 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;
@@ -1076,20 +1147,22 @@ mtf_delete_file_in_place (struct mtf_proc *mtf, struct mtf_file **file)
     mtf->tail = f->prev;
   *file = f->next;
 
-  {
-    int i;
-
-    for (i = 0; i < dict_get_var_cnt (f->dict); i++)
-      {
-       struct variable *v = dict_get_var (f->dict, i);
-        union value *out = case_data_rw (&mtf->mtf_case, get_master (v)->fv);
+  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);
-      }
-  }
+          if (v->type == NUMERIC)
+            out->f = SYSMIS;
+          else
+            memset (out->s, ' ', v->width);
+        } 
+    }
 
   mtf_free_file (f);
 }
@@ -1099,19 +1172,13 @@ static void
 mtf_read_nonactive_records (void *mtf_)
 {
   struct mtf_proc *mtf = mtf_;
-  struct mtf_file *iter;
+  struct mtf_file *iter, *next;
 
-  for (iter = mtf->head; iter)
+  for (iter = mtf->head; iter != NULL; iter = next)
     {
-      if (iter->handle)
-       {
-         if (!sfm_read_case (iter->reader, &iter->input))
-           mtf_delete_file_in_place (mtf, &iter);
-         else
-           iter = iter->next;
-       }
-      else
-        iter = iter->next;
+      next = iter->next;
+      if (iter->handle && !sfm_read_case (iter->reader, &iter->input))
+        mtf_delete_file_in_place (mtf, &iter);
     }
 }
 
@@ -1122,42 +1189,10 @@ mtf_compare_BY_values (struct mtf_proc *mtf,
                        struct mtf_file *a, struct mtf_file *b,
                        struct ccase *c)
 {
-  struct ccase *a_input, *b_input;
-  int i;
-
+  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);
-  a_input = case_is_null (&a->input) ? c : &a->input;
-  b_input = case_is_null (&b->input) ? c : &b->input;
-  for (i = 0; i < mtf->by_cnt; i++)
-    {
-      assert (a->by[i]->type == b->by[i]->type);
-      assert (a->by[i]->width == b->by[i]->width);
-      
-      if (a->by[i]->type == NUMERIC)
-       {
-         double af = case_num (a_input, a->by[i]->fv);
-         double bf = case_num (b_input, b->by[i]->fv);
-
-         if (af < bf)
-           return -1;
-         else if (af > bf)
-           return 1;
-       }
-      else 
-       {
-         int result;
-         
-         assert (a->by[i]->type == ALPHA);
-         result = memcmp (case_str (a_input, a->by[i]->fv),
-                          case_str (b_input, b->by[i]->fv),
-                          a->by[i]->width);
-         if (result < 0)
-           return -1;
-         else if (result > 0)
-           return 1;
-       }
-    }
-  return 0;
+  return case_compare_2dict (ca, cb, a->by, b->by, mtf->by_cnt);
 }
 
 /* Perform one iteration of steps 3...7 above. */
@@ -1165,25 +1200,23 @@ static int
 mtf_processing (struct ccase *c, void *mtf_)
 {
   struct mtf_proc *mtf = mtf_;
-  struct mtf_file *min_head, *min_tail; /* Files with minimum BY values. */
-  struct mtf_file *max_head, *max_tail; /* Files with non-minimum BY values. */
-  struct mtf_file *iter;                /* Iterator. */
 
-  for (;;)
+  /* Do we need another record from the active file? */
+  bool read_active_file;
+
+  assert (mtf->head != NULL);
+  assert (mtf->head->type == MTF_FILE);
+  do
     {
-      /* If the active file doesn't have the minimum BY values, don't
-        return because that would cause a record to be skipped. */
-      bool advance = true;
+      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;
 
-      if (mtf->head->type == MTF_TABLE)
-       return 0;
+      read_active_file = false;
       
-      /* 3. Find the FILE input record with minimum BY values.  Store
-        all the values from this input record into the output record.
-
-        4. Find all the FILE input records with BY values identical
-        to the minimums.  Store all the values from these input
-        records into the output record. */
+      /* 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;
@@ -1219,18 +1252,15 @@ mtf_processing (struct ccase *c, void *mtf_)
            assert (0);
          }
 
-      /* 5. For every TABLE, read another record as long as the BY
+      /* 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. */
-      while (iter)
+      for (; iter != NULL; iter = next)
        {
-         struct mtf_file *next = iter->next;
-         
          assert (iter->type == MTF_TABLE);
       
-         if (iter->handle == NULL)
-           advance = false;
+         next = iter->next;
 
        again:
          switch (mtf_compare_BY_values (mtf, min_head, iter, c))
@@ -1257,13 +1287,11 @@ mtf_processing (struct ccase *c, void *mtf_)
            default:
              assert (0);
            }
-
-         iter = next;
        }
 
       /* Next sequence number. */
       mtf->seq_num++;
-  
+
       /* Store data to all the records we are using. */
       if (min_tail)
        min_tail->next_min = NULL;
@@ -1276,7 +1304,7 @@ mtf_processing (struct ccase *c, void *mtf_)
              struct variable *v = dict_get_var (iter->dict, i);
               struct variable *mv = get_master (v);
          
-             if (mtf->seq_nums[mv->index] != mtf->seq_num) 
+             if (mv != NULL && mtf->seq_nums[mv->index] != mtf->seq_num) 
                 {
                   struct ccase *record
                     = case_is_null (&iter->input) ? c : &iter->input;
@@ -1289,9 +1317,15 @@ mtf_processing (struct ccase *c, void *mtf_)
                     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. */
+      /* 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)
@@ -1302,8 +1336,8 @@ mtf_processing (struct ccase *c, void *mtf_)
            {
              struct variable *v = dict_get_var (iter->dict, i);
               struct variable *mv = get_master (v);
-         
-             if (mtf->seq_nums[mv->index] != mtf->seq_num) 
+
+             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;
@@ -1314,36 +1348,29 @@ mtf_processing (struct ccase *c, void *mtf_)
                     memset (out->s, ' ', v->width);
                 }
             }
-
-         if (iter->handle == NULL)
-           advance = false;
+          if (iter->in_var != NULL)
+            case_data_rw (&mtf->mtf_case, iter->in_var->fv)->f = 0.;
        }
 
-      /* 6. Write the output record. */
+      /* 5. Write the output record. */
       mtf->sink->class->write (mtf->sink, &mtf->mtf_case);
 
-      /* 7. Read another record from each input file FILE and TABLE
+      /* 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; )
+      for (iter = min_head; iter && iter->type == MTF_FILE; iter = next)
        {
-         struct mtf_file *next = iter->next_min;
-         
-         if (iter->reader != NULL)
-           {
-             if (!sfm_read_case (iter->reader, &iter->input))
-               mtf_delete_file_in_place (mtf, &iter);
-           }
-
-         iter = next;
+         next = iter->next_min;
+         if (iter->reader != NULL
+              && !sfm_read_case (iter->reader, &iter->input))
+            mtf_delete_file_in_place (mtf, &iter);
        }
-      
-      if (advance)
-       break;
     }
+  while (!read_active_file
+         && mtf->head != NULL && mtf->head->type == MTF_FILE);
 
-  return (mtf->head && mtf->head->type != MTF_TABLE);
+  return mtf->head != NULL && mtf->head->type == MTF_FILE;
 }
 
 /* Merge the dictionary for file F into master dictionary M. */
@@ -1414,8 +1441,6 @@ mtf_merge_dictionary (struct dictionary *const m, struct mtf_file *f)
           mv = dict_clone_var (m, dv, dv->name, dv->longname);
           assert (mv != NULL);
         }
-        
-      set_master (dv, mv);
     }
 
   return 1;
@@ -1433,7 +1458,6 @@ set_master (struct variable *v, struct variable *master)
 static struct variable *
 get_master (struct variable *v) 
 {
-  assert (v->aux != NULL);
   return v->aux;
 }
 \f
index 344f94b9a80224625fdfed08e9ed84fcb7dc9645..49cd4a4ce7a01bccceeec16635a4a1987611beb7 100644 (file)
--- a/src/vfm.c
+++ b/src/vfm.c
@@ -782,32 +782,9 @@ procedure_with_splits_callback (struct ccase *c, void *split_aux_)
 static int
 equal_splits (const struct ccase *a, const struct ccase *b) 
 {
-  struct variable *const *split;
-  size_t split_cnt;
-  size_t i;
-    
-  split = dict_get_split_vars (default_dict);
-  split_cnt = dict_get_split_cnt (default_dict);
-  for (i = 0; i < split_cnt; i++)
-    {
-      struct variable *v = split[i];
-      
-      switch (v->type)
-       {
-       case NUMERIC:
-         if (case_num (a, v->fv) != case_num (b, v->fv))
-            return 0;
-         break;
-       case ALPHA:
-         if (memcmp (case_str (a, v->fv), case_str (b, v->fv), v->width))
-            return 0;
-         break;
-       default:
-         assert (0);
-       }
-    }
-
-  return 1;
+  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. */
index ed4a3a094cd2bedc6b150231c0f0d2aef4272afd..bb353ca4b56cf4d71c5c6535b0746974d03be31a 100644 (file)
@@ -1,3 +1,9 @@
+Mon Apr 25 23:30:17 2005  Ben Pfaff  <blp@gnu.org>
+
+       * commands/match-files.sh: New test.
+
+       * Makefile.am: (TESTS) Add commands/match-files.sh.
+
 Sun Apr 17 16:38:00 2005  Ben Pfaff  <blp@gnu.org>
 
        * crosstabs.stat, data-fmts.stat, do-if.stat, do-repeat.stat,
index 2717d67aa5851f143dcffd5c446b35a84dd4973a..b14643e459796aaa69bd7f40fd422dbbc5b9df56 100644 (file)
@@ -21,6 +21,7 @@ TESTS = \
        command/list.sh \
        command/loop.sh \
        command/longvars.sh \
+       command/match-files.sh \
        command/oneway.sh \
        command/oneway-missing.sh \
        command/oneway-with-splits.sh \
diff --git a/tests/command/match-files.sh b/tests/command/match-files.sh
new file mode 100755 (executable)
index 0000000..092c49e
--- /dev/null
@@ -0,0 +1,208 @@
+#!/bin/sh
+
+# This program tests the MATCH FILES procedure
+
+TEMPDIR=/tmp/pspp-tst-$$
+TESTFILE=$TEMPDIR/match-files.pspp
+
+
+here=`pwd`;
+
+# ensure that top_srcdir is absolute
+cd $top_srcdir; top_srcdir=`pwd`
+
+
+export STAT_CONFIG_PATH=$top_srcdir/config
+
+cleanup()
+{
+    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="data create"
+cat > a.data <<EOF
+0aA
+1aB
+1aC
+2aD
+3aE
+4aF
+5aG
+5aH
+6aI
+7aJ
+7aK
+7aL
+8aM
+EOF
+if [ $? -ne 0 ] ; then no_result ; fi
+cat > b.data <<EOF
+1bN
+3bO
+4bP
+6bQ
+7bR
+9bS
+EOF
+if [ $? -ne 0 ] ; then no_result ; fi
+
+cat > ff.out <<EOF
+A B C D INA INB
+- - - - --- ---
+0 a A     1   0
+1 a B N   1   1
+1 a C     1   0
+2 a D     1   0
+3 a E O   1   1
+4 a F P   1   1
+5 a G     1   0
+5 a H     1   0
+6 a I Q   1   1
+7 a J R   1   1
+7 a K     1   0
+7 a L     1   0
+8 a M     1   0
+9 b   S   0   1
+EOF
+
+cat > ft.out <<EOF
+A B C D INA INB
+- - - - --- ---
+0 a A     1   0
+1 a B N   1   1
+1 a C N   1   1
+2 a D     1   0
+3 a E O   1   1
+4 a F P   1   1
+5 a G     1   0
+5 a H     1   0
+6 a I Q   1   1
+7 a J R   1   1
+7 a K R   1   1
+7 a L R   1   1
+8 a M     1   0
+EOF
+
+# Test nonparallel match and table lookup.
+dla="data list notable file='a.data' /a b c 1-3 (a)."
+sa="save outfile='a.sys'."
+dlb="data list notable file='b.data' /a b c 1-3 (a)."
+sb="save outfile='b.sys'."
+for types in ff ft; do
+    type1=file
+    if [ $types = ff ]; then 
+       type2=file
+    else
+       type2=table
+    fi
+    for sources in ss sa as; do
+       name="$types-$sources"
+       activity="create $name.pspp"
+       {
+           if [ $sources = ss ]; then
+               cat <<EOF
+$dla
+$sa
+$dlb
+$sb
+match files $type1='a.sys' /in=ina /$type2='b.sys' /in=inb /rename c=D /by a.
+EOF
+           elif [ $sources = sa ]; then
+               cat <<EOF
+$dla
+$sa
+$dlb
+match files $type1='a.sys' /in=ina /$type2=* /in=inb /rename c=D /by a.
+EOF
+           elif [ $sources = as ]; then
+               cat <<EOF
+$dlb
+$sb
+$dla
+match files $type1=* /in=ina /$type2='b.sys' /in=inb /rename c=D /by a.
+EOF
+           else
+               activity="internal error"
+               no_result
+           fi
+           echo 'list.'
+        } > $name.pspp
+       if [ $? -ne 0 ] ; then no_result ; fi
+
+       activity="run $name.pspp"
+       $SUPERVISOR $here/../src/pspp -o raw-ascii $name.pspp >/dev/null 2>&1
+       if [ $? -ne 0 ] ; then no_result ; fi
+
+       activity="check $name output"
+       diff -b -w -B pspp.list $types.out
+       if [ $? -ne 0 ] ; then fail ; fi
+    done
+done
+
+# Test parallel match. 
+name="parallel"
+activity="create $name.pspp"
+cat > $name.pspp <<EOF
+$dla
+$sa
+$dlb
+$sb
+match files file='a.sys' /file='b.sys' /rename (a b c=d e f).
+list.
+EOF
+if [ $? -ne 0 ] ; then no_result ; fi
+
+activity="run $name.pspp"
+$SUPERVISOR $here/../src/pspp -o raw-ascii $name.pspp >/dev/null 2>&1
+if [ $? -ne 0 ] ; then no_result ; fi
+
+activity="check $name output"
+diff -b -w -B - pspp.list <<EOF
+A B C D E F
+- - - - - -
+0 a A 1 b N
+1 a B 3 b O
+1 a C 4 b P
+2 a D 6 b Q
+3 a E 7 b R
+4 a F 9 b S
+5 a G
+5 a H
+6 a I
+7 a J
+7 a K
+7 a L
+8 a M
+EOF
+if [ $? -ne 0 ] ; then fail ; fi
+
+pass;