X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fget.c;h=057b50725b792ca5f92b50c0a600af29340eb953;hb=7dc203206d3f3172474a4ec0f4dcab5364f4ce26;hp=df6e207cbb8b41862d178863563731e854599cd1;hpb=77cbf2d7d1e5712d4ef952f265ed5c2917fc3f68;p=pspp-builds.git diff --git a/src/get.c b/src/get.c index df6e207c..057b5072 100644 --- a/src/get.c +++ b/src/get.c @@ -18,18 +18,22 @@ 02111-1307, USA. */ #include -#include +#include "error.h" #include #include "alloc.h" +#include "case.h" #include "command.h" +#include "dictionary.h" #include "error.h" #include "file-handle.h" #include "hash.h" #include "lexer.h" #include "misc.h" -#include "pfm.h" +#include "pfm-read.h" +#include "pfm-write.h" #include "settings.h" -#include "sfm.h" +#include "sfm-read.h" +#include "sfm-write.h" #include "str.h" #include "value-labels.h" #include "var.h" @@ -38,223 +42,238 @@ #include "debug-print.h" -/* GET or IMPORT input program. */ -struct get_pgm +/* 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 *); + +/* Operation type. */ +enum operation { - struct file_handle *handle; /* File to GET or IMPORT from. */ - size_t case_size; /* Case size in bytes. */ + OP_READ, /* GET or IMPORT. */ + OP_SAVE, /* SAVE or XSAVE. */ + OP_EXPORT, /* EXPORT. */ + OP_MATCH /* MATCH FILES. */ }; -/* XSAVE transformation (and related SAVE, EXPORT procedures). */ -struct save_trns +static int trim_dictionary (struct dictionary *, + enum operation, int *compress); + +/* GET input program. */ +struct get_pgm { - struct trns_header h; - struct file_handle *f; /* Associated system file. */ - int nvar; /* Number of variables. */ - struct variable **var; /* Variables. */ - flt64 *case_buf; /* Case transfer buffer. */ + struct sfm_reader *reader; /* System file reader. */ + struct case_map *map; /* Map from system file to active file dict. */ + struct ccase bounce; /* Bounce buffer. */ }; -/* Options bits set by trim_dictionary(). */ -#define GTSV_OPT_COMPRESSED 001 /* Compression; (X)SAVE only. */ -#define GTSV_OPT_SAVE 002 /* The SAVE/XSAVE/EXPORT procedures. */ -#define GTSV_OPT_MATCH_FILES 004 /* The MATCH FILES procedure. */ -#define GTSV_OPT_NONE 0 - -static int trim_dictionary (struct dictionary * dict, int *options); -static int save_write_case_func (struct ccase *, void *); -static trns_proc_func save_trns_proc; -static trns_free_func save_trns_free; - -#if DEBUGGING -void dump_dict_variables (struct dictionary *); -#endif +static void get_pgm_free (struct get_pgm *); /* Parses the GET command. */ int cmd_get (void) { - struct file_handle *handle; - struct dictionary *dict; - struct get_pgm *pgm; - int options = GTSV_OPT_NONE; + struct get_pgm *pgm = NULL; + struct file_handle *fh; + struct dictionary *dict = NULL; + + pgm = xmalloc (sizeof *pgm); + pgm->reader = NULL; + pgm->map = NULL; + case_nullify (&pgm->bounce); - lex_match_id ("GET"); discard_variables (); lex_match ('/'); if (lex_match_id ("FILE")) lex_match ('='); + fh = fh_parse (); + if (fh == NULL) + goto error; - handle = fh_parse_file_handle (); - if (handle == NULL) - return CMD_FAILURE; + pgm->reader = sfm_open_reader (fh, &dict, NULL); + if (pgm->reader == NULL) + goto error; + case_create (&pgm->bounce, dict_get_next_value_idx (dict)); - dict = sfm_read_dictionary (handle, NULL); - if (dict == NULL) - return CMD_FAILURE; + start_case_map (dict); + if (!trim_dictionary (dict, OP_READ, NULL)) + goto error; + pgm->map = finish_case_map (dict); -#if DEBUGGING - dump_dict_variables (dict); -#endif - if (0 == trim_dictionary (dict, &options)) - { - fh_close_handle (handle); - return CMD_FAILURE; - } -#if DEBUGGING - dump_dict_variables (dict); -#endif + dict_destroy (default_dict); + default_dict = dict; - dict_compact_values (dict); + vfm_source = create_case_source (&get_source_class, pgm); -#if DEBUGGING - printf (_("GET translation table from file to memory:\n")); - for (i = 0; i < dict->nvar; i++) - { - struct variable *v = dict->var[i]; + return CMD_SUCCESS; + + error: + get_pgm_free (pgm); + if (dict != NULL) + dict_destroy (dict); + return CMD_FAILURE; +} - printf (_(" %8s from %3d,%3d to %3d,%3d\n"), v->name, - v->get.fv, v->get.nv, v->fv, v->nv); +/* Frees a struct get_pgm. */ +static void +get_pgm_free (struct get_pgm *pgm) +{ + if (pgm != NULL) + { + sfm_close_reader (pgm->reader); + destroy_case_map (pgm->map); + case_destroy (&pgm->bounce); + free (pgm); } -#endif +} - dict_destroy (default_dict); - default_dict = dict; +/* Clears internal state related to GET input procedure. */ +static void +get_source_destroy (struct case_source *source) +{ + struct get_pgm *pgm = source->aux; + get_pgm_free (pgm); +} - pgm = xmalloc (sizeof *pgm); - pgm->handle = handle; - pgm->case_size = dict_get_case_size (default_dict); - vfm_source = create_case_source (&get_source_class, default_dict, pgm); +/* Reads all the cases from the data file into C and passes them + to WRITE_CASE one by one, passing WC_DATA. */ +static void +get_source_read (struct case_source *source, + struct ccase *c, + write_case_func *write_case, write_case_data wc_data) +{ + struct get_pgm *pgm = source->aux; + int ok; - return CMD_SUCCESS; + do + { + if (pgm->map == NULL) + ok = sfm_read_case (pgm->reader, c); + else + { + ok = sfm_read_case (pgm->reader, &pgm->bounce); + if (ok) + map_case (pgm->map, &pgm->bounce, c); + } + + if (ok) + ok = write_case (wc_data); + } + while (ok); } -/* SAVE or XSAVE command? */ -enum save_cmd +const struct case_source_class get_source_class = { - CMD_SAVE, - CMD_XSAVE + "GET", + NULL, + get_source_read, + get_source_destroy, + }; + +/* XSAVE transformation and SAVE procedure. */ +struct save_trns + { + struct trns_header h; + struct sfm_writer *writer; /* System file writer. */ + struct case_map *map; /* Map from active file to system file dict. */ + struct ccase bounce; /* Bounce buffer. */ }; -/* Parses the SAVE and XSAVE commands. */ -static int -cmd_save_internal (enum save_cmd save_cmd) -{ - struct file_handle *handle; - struct dictionary *dict; - int options = GTSV_OPT_SAVE; - - struct save_trns *t; - struct sfm_write_info inf; - - int i; +static int save_write_case_func (struct ccase *, void *); +static trns_proc_func save_trns_proc; +static trns_free_func save_trns_free; - lex_match_id ("SAVE"); +/* Parses the SAVE or XSAVE command + and returns the parsed transformation. */ +static struct save_trns * +cmd_save_internal (void) +{ + struct file_handle *fh; + struct dictionary *dict = NULL; + struct save_trns *t = NULL; + int compress = get_scompression (); + t = xmalloc (sizeof *t); + t->h.proc = save_trns_proc; + t->h.free = save_trns_free; + t->writer = NULL; + t->map = NULL; + case_nullify (&t->bounce); + lex_match ('/'); if (lex_match_id ("OUTFILE")) lex_match ('='); - - handle = fh_parse_file_handle (); - if (handle == NULL) - return CMD_FAILURE; + fh = fh_parse (); + if (fh == NULL) + goto error; dict = dict_clone (default_dict); -#if DEBUGGING - dump_dict_variables (dict); -#endif - for (i = 0; i < dict_get_var_cnt (dict); i++) - dict_get_var (dict, i)->aux = dict_get_var (default_dict, i); - if (0 == trim_dictionary (dict, &options)) - { - fh_close_handle (handle); - return CMD_FAILURE; - } + start_case_map (dict); + if (!trim_dictionary (dict, OP_SAVE, &compress)) + goto error; + t->map = finish_case_map (dict); + if (t->map != NULL) + case_create (&t->bounce, dict_get_next_value_idx (dict)); -#if DEBUGGING - dump_dict_variables (dict); -#endif + t->writer = sfm_open_writer (fh, dict, compress); + if (t->writer == NULL) + goto error; - /* Write dictionary. */ - inf.h = handle; - inf.dict = dict; - inf.compress = !!(options & GTSV_OPT_COMPRESSED); - if (!sfm_write_dictionary (&inf)) - { - dict_destroy (dict); - fh_close_handle (handle); - return CMD_FAILURE; - } - - /* Fill in transformation structure. */ - t = xmalloc (sizeof *t); - t->h.proc = save_trns_proc; - t->h.free = save_trns_free; - t->f = handle; - t->nvar = dict_get_var_cnt (dict); - t->var = xmalloc (sizeof *t->var * t->nvar); - for (i = 0; i < t->nvar; i++) - t->var[i] = dict_get_var (dict, i)->aux; - t->case_buf = xmalloc (sizeof *t->case_buf * inf.case_size); dict_destroy (dict); - if (save_cmd == CMD_SAVE) - { - procedure (NULL, save_write_case_func, NULL, t); - save_trns_free (&t->h); - } - else - { - assert (save_cmd == CMD_XSAVE); - add_transformation (&t->h); - } + return t; - return CMD_SUCCESS; + error: + assert (t != NULL); + dict_destroy (dict); + save_trns_free (&t->h); + return NULL; } /* Parses and performs the SAVE procedure. */ int cmd_save (void) { - return cmd_save_internal (CMD_SAVE); + struct save_trns *t = cmd_save_internal (); + if (t != NULL) + { + procedure (save_write_case_func, t); + save_trns_free (&t->h); + free(t); + return CMD_SUCCESS; + } + else + return CMD_FAILURE; } /* Parses the XSAVE transformation command. */ int cmd_xsave (void) { - return cmd_save_internal (CMD_XSAVE); + struct save_trns *t = cmd_save_internal (); + if (t != NULL) + { + add_transformation (&t->h); + return CMD_SUCCESS; + } + else + return CMD_FAILURE; } /* Writes the given C to the file specified by T. */ static void do_write_case (struct save_trns *t, struct ccase *c) { - flt64 *p = t->case_buf; - int i; - - for (i = 0; i < t->nvar; i++) + if (t->map == NULL) + sfm_write_case (t->writer, c); + else { - struct variable *v = t->var[i]; - if (v->type == NUMERIC) - { - double src = c->data[v->fv].f; - if (src == SYSMIS) - *p++ = -FLT64_MAX; - else - *p++ = src; - } - else - { - memcpy (p, c->data[v->fv].s, v->width); - memset (&((char *) p)[v->width], ' ', - REM_RND_UP (v->width, sizeof *p)); - p += DIV_RND_UP (v->width, sizeof *p); - } + map_case (t->map, c, &t->bounce); + sfm_write_case (t->writer, &t->bounce); } - - sfm_write_case (t->f, t->case_buf, p - t->case_buf); } /* Writes case C to the system file specified on SAVE. */ @@ -276,33 +295,39 @@ save_trns_proc (struct trns_header *h, struct ccase *c, int case_num UNUSED) /* Frees a SAVE transformation. */ static void -save_trns_free (struct trns_header *pt) +save_trns_free (struct trns_header *t_) { - struct save_trns *t = (struct save_trns *) pt; + struct save_trns *t = (struct save_trns *) t_; - fh_close_handle (t->f); - free (t->var); - free (t->case_buf); - free (t); + if (t != NULL) + { + sfm_close_writer (t->writer); + destroy_case_map (t->map); + case_destroy (&t->bounce); + } } -static int rename_variables (struct dictionary * dict); +static int rename_variables (struct dictionary *dict); -/* The GET and SAVE commands have a common structure after the - FILE/OUTFILE subcommand. This function parses this structure and - returns nonzero on success, zero on failure. It both reads - *OPTIONS, for the GTSV_OPT_SAVE bit, and writes it, for the - GTSV_OPT_COMPRESSED bit. */ +/* 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. + + OP is the operation being performed. For operations that + write a system file, *COMPRESS is set to 1 if the system file + should be compressed, 0 otherwise. + + Returns nonzero on success, zero on failure. */ /* FIXME: IN, FIRST, LAST, MAP. */ -/* FIXME? Should we call dict_compact_values() on dict as a - final step? */ static int -trim_dictionary (struct dictionary *dict, int *options) +trim_dictionary (struct dictionary *dict, enum operation op, int *compress) { - if (set_scompression) - *options |= GTSV_OPT_COMPRESSED; + assert ((compress != NULL) == (op == OP_SAVE)); + if (get_scompression()) + *compress = 1; - if (*options & GTSV_OPT_SAVE) + if (op == OP_SAVE || op == OP_EXPORT) { /* Delete all the scratch variables. */ struct variable **v; @@ -318,12 +343,12 @@ trim_dictionary (struct dictionary *dict, int *options) free (v); } - while ((*options & GTSV_OPT_MATCH_FILES) || lex_match ('/')) + while (op == OP_MATCH || lex_match ('/')) { - if (!(*options & GTSV_OPT_MATCH_FILES) && lex_match_id ("COMPRESSED")) - *options |= GTSV_OPT_COMPRESSED; - else if (!(*options & GTSV_OPT_MATCH_FILES) && lex_match_id ("UNCOMPRESSED")) - *options &= ~GTSV_OPT_COMPRESSED; + 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; @@ -372,8 +397,8 @@ trim_dictionary (struct dictionary *dict, int *options) return 0; } - if (*options & GTSV_OPT_MATCH_FILES) - return 1; + if (op == OP_MATCH) + goto success; } if (token != '.') @@ -381,13 +406,16 @@ trim_dictionary (struct dictionary *dict, int *options) lex_error (_("expecting end of command")); return 0; } - + + success: + if (op != OP_MATCH) + dict_compact_values (dict); return 1; } /* Parses and performs the RENAME subcommand of GET and SAVE. */ static int -rename_variables (struct dictionary * dict) +rename_variables (struct dictionary *dict) { int i; @@ -473,54 +501,91 @@ done: return success; } + +/* EXPORT procedure. */ +struct export_proc + { + struct pfm_writer *writer; /* System file writer. */ + struct case_map *map; /* Map from active file to system file dict. */ + struct ccase bounce; /* Bounce buffer. */ + }; -#if DEBUGGING -void -dump_dict_variables (struct dictionary * dict) +static int export_write_case_func (struct ccase *, void *); +static void export_proc_free (struct export_proc *); + +/* Parses the EXPORT command. */ +/* FIXME: same as cmd_save_internal(). */ +int +cmd_export (void) { - int i; + struct file_handle *fh; + struct dictionary *dict; + struct export_proc *proc; + + proc = xmalloc (sizeof *proc); + proc->writer = NULL; + proc->map = NULL; + case_nullify (&proc->bounce); + + lex_match ('/'); + if (lex_match_id ("OUTFILE")) + lex_match ('='); + fh = fh_parse (); + if (fh == NULL) + return CMD_FAILURE; + + dict = dict_clone (default_dict); + start_case_map (dict); + if (!trim_dictionary (dict, OP_EXPORT, NULL)) + goto error; + proc->map = finish_case_map (dict); + if (proc->map != NULL) + case_create (&proc->bounce, dict_get_next_value_idx (dict)); + + proc->writer = pfm_open_writer (fh, dict); + if (proc->writer == NULL) + goto error; + + dict_destroy (dict); + + procedure (export_write_case_func, proc); + export_proc_free (proc); + free (proc); + + return CMD_SUCCESS; - printf (_("\nVariables in dictionary:\n")); - for (i = 0; i < dict->nvar; i++) - printf ("%s, ", dict->var[i]->name); - printf ("\n"); + error: + dict_destroy (dict); + export_proc_free (proc); + free (proc); + return CMD_FAILURE; } -#endif - -/* Clears internal state related to GET input procedure. */ -static void -get_source_destroy (struct case_source *source) -{ - struct get_pgm *pgm = source->aux; - /* It is not necessary to destroy the dictionary because if we get - to this point then the dictionary is default_dict. */ - fh_close_handle (pgm->handle); - free (pgm); +/* Writes case C to the EXPORT file. */ +static int +export_write_case_func (struct ccase *c, void *aux) +{ + struct export_proc *proc = aux; + if (proc->map == NULL) + pfm_write_case (proc->writer, c); + else + { + map_case (proc->map, c, &proc->bounce); + pfm_write_case (proc->writer, &proc->bounce); + } + return 1; } -/* Reads all the cases from the data file into C and passes them - to WRITE_CASE one by one, passing WC_DATA. */ static void -get_source_read (struct case_source *source, - struct ccase *c, - write_case_func *write_case, write_case_data wc_data) +export_proc_free (struct export_proc *proc) { - struct get_pgm *pgm = source->aux; - - while (sfm_read_case (pgm->handle, c->data, default_dict) - && write_case (wc_data)) - ; + if (proc != NULL) + { + pfm_close_writer (proc->writer); + destroy_case_map (proc->map); + case_destroy (&proc->bounce); + } } - -const struct case_source_class get_source_class = - { - "GET", - NULL, - get_source_read, - get_source_destroy, - }; - /* MATCH FILES. */ @@ -542,34 +607,35 @@ struct mtf_file int type; /* One of MTF_*. */ struct variable **by; /* List of BY variables for this file. */ - struct file_handle *handle; /* File handle for the file. */ + struct file_handle *handle; /* File handle. */ + struct sfm_reader *reader; /* System file reader. */ struct dictionary *dict; /* Dictionary from system file. */ char in[9]; /* Name of the variable from IN=. */ char first[9], last[9]; /* Name of the variables from FIRST=, LAST=. */ - union value *input; /* Input record. */ + struct ccase input; /* Input record. */ }; -/* All the files mentioned on FILE or TABLE. */ -static struct mtf_file *mtf_head, *mtf_tail; - -/* Variables on the BY subcommand. */ -static struct variable **mtf_by; -static int mtf_n_by; - -/* Master dictionary. */ -static struct dictionary *mtf_master; +/* 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. */ + + struct variable **by; /* Variables on the BY subcommand. */ + size_t by_cnt; /* Number of variables on BY subcommand. */ -/* Used to determine whether we've already initialized this - variable. */ -static unsigned mtf_seq_num; + struct dictionary *dict; /* Dictionary of output file. */ + struct case_sink *sink; /* Sink to receive output. */ + struct ccase *mtf_case; /* Case used for output. */ -/* Sequence numbers for each variable in mtf_master. */ -static unsigned *mtf_seq_nums; + unsigned seq_num; /* Have we initialized this variable? */ + unsigned *seq_nums; /* Sequence numbers for each var in dict. */ + }; -static void mtf_free (void); -static void mtf_free_file (struct mtf_file *file); -static int mtf_merge_dictionary (struct mtf_file *f); -static void mtf_delete_file_in_place (struct mtf_file **file); +static void mtf_free (struct mtf_proc *); +static void mtf_free_file (struct mtf_file *); +static int mtf_merge_dictionary (struct dictionary *const, struct mtf_file *); +static void mtf_delete_file_in_place (struct mtf_proc *, struct mtf_file **); static void mtf_read_nonactive_records (void *); static void mtf_processing_finish (void *); @@ -577,24 +643,28 @@ static int 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; int seen = 0; - lex_match_id ("MATCH"); - lex_match_id ("FILES"); - - mtf_head = mtf_tail = NULL; - mtf_by = NULL; - mtf_n_by = 0; - mtf_master = dict_create (); - mtf_seq_num = 0; - mtf_seq_nums = NULL; - dict_set_case_limit (mtf_master, dict_get_case_limit (default_dict)); + mtf.head = mtf.tail = NULL; + mtf.by = NULL; + mtf.by_cnt = 0; + mtf.dict = dict_create (); + mtf.sink = NULL; + mtf.mtf_case = NULL; + mtf.seq_num = 0; + mtf.seq_nums = NULL; + dict_set_case_limit (mtf.dict, dict_get_case_limit (default_dict)); do { @@ -605,29 +675,24 @@ cmd_match_files (void) if (seen & 1) { msg (SE, _("The BY subcommand may be given once at most.")); - goto lossage; + goto error; } seen |= 1; lex_match ('='); - if (!parse_variables (mtf_master, &mtf_by, &mtf_n_by, + if (!parse_variables (mtf.dict, &mtf.by, &mtf.by_cnt, PV_NO_DUPLICATE | PV_NO_SCRATCH)) - goto lossage; + goto error; } else if (token != T_ID) { lex_error (NULL); - goto lossage; + goto error; } else if (lex_id_match ("FILE", tokid) || lex_id_match ("TABLE", tokid)) { struct mtf_file *file = xmalloc (sizeof *file); - file->in[0] = file->first[0] = file->last[0] = '\0'; - file->dict = NULL; - file->by = NULL; - file->input = NULL; - if (lex_match_id ("FILE")) file->type = MTF_FILE; else if (lex_match_id ("TABLE")) @@ -638,16 +703,25 @@ cmd_match_files (void) else assert (0); + file->by = NULL; + file->handle = NULL; + file->reader = NULL; + file->dict = NULL; + file->in[0] = '\0'; + file->first[0] = '\0'; + file->last[0] = '\0'; + 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; + 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; } @@ -659,7 +733,7 @@ cmd_match_files (void) if (first_table->prev) first_table->prev->next = file; else - mtf_head = file; + mtf.head = file; first_table->prev = file; } @@ -667,13 +741,14 @@ cmd_match_files (void) if (lex_match ('*')) { - file->handle = NULL; - + file->handle = NULL; + file->reader = NULL; + if (seen & 2) { msg (SE, _("The active file may not be specified more " "than once.")); - goto lossage; + goto error; } seen |= 2; @@ -682,26 +757,32 @@ cmd_match_files (void) { msg (SE, _("Cannot specify the active file since no active " "file has been defined.")); - goto lossage; + 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_file_handle (); - if (!file->handle) - goto lossage; - } + file->handle = fh_parse (); + if (file->handle == NULL) + goto error; - if (file->handle) - { - file->dict = sfm_read_dictionary (file->handle, NULL); - if (!file->dict) - goto lossage; + file->reader = sfm_open_reader (file->handle, &file->dict, NULL); + if (file->reader == NULL) + goto error; + + case_create (&file->input, dict_get_next_value_idx (file->dict)); } - else - file->dict = default_dict; - if (!mtf_merge_dictionary (file)) - goto lossage; } else if (lex_id_match ("IN", tokid) || lex_id_match ("FIRST", tokid) @@ -710,36 +791,39 @@ cmd_match_files (void) const char *sbc; char *name; - if (mtf_tail == NULL) + if (mtf.tail == NULL) { msg (SE, _("IN, FIRST, and LAST subcommands may not occur " "before the first FILE or TABLE.")); - goto lossage; + goto error; } if (lex_match_id ("IN")) { - name = mtf_tail->in; + name = mtf.tail->in; sbc = "IN"; } else if (lex_match_id ("FIRST")) { - name = mtf_tail->first; + name = mtf.tail->first; sbc = "FIRST"; } else if (lex_match_id ("LAST")) { - name = mtf_tail->last; + name = mtf.tail->last; sbc = "LAST"; } - else - assert (0); + else + { + assert (0); + abort (); + } lex_match ('='); if (token != T_ID) { lex_error (NULL); - goto lossage; + goto error; } if (*name) @@ -747,34 +831,32 @@ cmd_match_files (void) msg (SE, _("Multiple %s subcommands for a single FILE or " "TABLE."), sbc); - goto lossage; + goto error; } strcpy (name, tokid); lex_get (); - if (!dict_create_var (mtf_master, name, 0)) + if (!dict_create_var (mtf.dict, name, 0)) { msg (SE, _("Duplicate variable name %s while creating %s " "variable."), name, sbc); - goto lossage; + goto error; } } else if (lex_id_match ("RENAME", tokid) || lex_id_match ("KEEP", tokid) || lex_id_match ("DROP", tokid)) { - int options = GTSV_OPT_MATCH_FILES; - - if (mtf_tail == NULL) + if (mtf.tail == NULL) { msg (SE, _("RENAME, KEEP, and DROP subcommands may not occur " "before the first FILE or TABLE.")); - goto lossage; + goto error; } - if (!trim_dictionary (mtf_tail->dict, &options)) - goto lossage; + if (!trim_dictionary (mtf.tail->dict, OP_MATCH, NULL)) + goto error; } else if (lex_match_id ("MAP")) { @@ -783,54 +865,46 @@ cmd_match_files (void) else { lex_error (NULL); - goto lossage; + goto error; } } while (token != '.'); + for (iter = mtf.head; iter != NULL; iter = iter->next) + mtf_merge_dictionary (mtf.dict, iter); + if (seen & 4) { if (!(seen & 1)) { msg (SE, _("The BY subcommand is required when a TABLE subcommand " "is given.")); - goto lossage; + goto error; } } if (seen & 1) { - struct mtf_file *iter; - - for (iter = mtf_head; iter; iter = iter->next) + for (iter = mtf.head; iter != NULL; iter = iter->next) { int i; - iter->by = xmalloc (sizeof *iter->by * mtf_n_by); + iter->by = xmalloc (sizeof *iter->by * mtf.by_cnt); - for (i = 0; i < mtf_n_by; i++) + for (i = 0; i < mtf.by_cnt; i++) { - iter->by[i] = dict_lookup_var (iter->dict, mtf_by[i]->name); + iter->by[i] = dict_lookup_var (iter->dict, mtf.by[i]->name); if (iter->by[i] == NULL) { msg (SE, _("File %s lacks BY variable %s."), - iter->handle ? fh_handle_name (iter->handle) : "*", - mtf_by[i]->name); - goto lossage; + iter->handle ? handle_get_name (iter->handle) : "*", + mtf.by[i]->name); + goto error; } } } } -#if DEBUGGING - { - /* From sfm-read.c. */ - extern void dump_dictionary (struct dictionary *); - - dump_dictionary (mtf_master); - } -#endif - /* MATCH FILES performs an n-way merge on all its input files. Abstract algorithm: @@ -869,45 +943,50 @@ cmd_match_files (void) if (!(seen & 2)) discard_variables (); - temporary = 2; - temp_dict = mtf_master; - temp_trns = n_trns; + mtf.sink = create_case_sink (&storage_sink_class, mtf.dict, NULL); + + 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.mtf_case = xmalloc (dict_get_case_size (mtf.dict)); - mtf_seq_nums = xmalloc (dict_get_var_cnt (mtf_master) - * sizeof *mtf_seq_nums); - memset (mtf_seq_nums, 0, - dict_get_var_cnt (mtf_master) * sizeof *mtf_seq_nums); + mtf_read_nonactive_records (NULL); + if (seen & 2) + procedure (mtf_processing, NULL); + mtf_processing_finish (NULL); - process_active_file (mtf_read_nonactive_records, mtf_processing, - mtf_processing_finish, NULL); - mtf_master = 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); - mtf_free (); + mtf_free (&mtf); return CMD_SUCCESS; -lossage: - mtf_free (); +error: + mtf_free (&mtf); return CMD_FAILURE; } /* Repeats 2...8 an arbitrary number of times. */ static void -mtf_processing_finish (void *aux UNUSED) +mtf_processing_finish (void *mtf_) { + struct mtf_proc *mtf = mtf_; + struct mtf_file *iter; + /* Find the active file and delete it. */ - { - struct mtf_file *iter; - - for (iter = mtf_head; iter; iter = iter->next) - if (iter->handle == NULL) - { - mtf_delete_file_in_place (&iter); - break; - } - } + for (iter = mtf->head; iter; iter = iter->next) + if (iter->handle == NULL) + { + mtf_delete_file_in_place (mtf, &iter); + break; + } - while (mtf_head && mtf_head->type == MTF_FILE) - if (!mtf_processing (NULL, NULL)) + while (mtf->head && mtf->head->type == MTF_FILE) + if (!mtf_processing (NULL, mtf)) break; } @@ -937,38 +1016,37 @@ var_type_description (struct variable *v) static void mtf_free_file (struct mtf_file *file) { - fh_close_handle (file->handle); - if (file->dict != NULL && file->dict != default_dict) - dict_destroy (file->dict); free (file->by); - if (file->handle) - free (file->input); + sfm_close_reader (file->reader); + if (file->dict != default_dict) + dict_destroy (file->dict); + case_destroy (&file->input); free (file); } /* Free all the data for the MATCH FILES procedure. */ static void -mtf_free (void) +mtf_free (struct mtf_proc *mtf) { struct mtf_file *iter, *next; - for (iter = mtf_head; iter; iter = next) + for (iter = mtf->head; iter; iter = next) { next = iter->next; mtf_free_file (iter); } - free (mtf_by); - if (mtf_master) - dict_destroy (mtf_master); - free (mtf_seq_nums); + free (mtf->by); + if (mtf->dict) + dict_destroy (mtf->dict); + free (mtf->seq_nums); } /* Remove *FILE from the mtf_file chain. Make *FILE point to the next file in the chain, or to NULL if was the last in the chain. */ static void -mtf_delete_file_in_place (struct mtf_file **file) +mtf_delete_file_in_place (struct mtf_proc *mtf, struct mtf_file **file) { struct mtf_file *f = *file; @@ -976,10 +1054,10 @@ mtf_delete_file_in_place (struct mtf_file **file) 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; + if (f == mtf->head) + mtf->head = f->next; + if (f == mtf->tail) + mtf->tail = f->prev; *file = f->next; { @@ -988,33 +1066,31 @@ mtf_delete_file_in_place (struct mtf_file **file) 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 (v->type == NUMERIC) - compaction_case->data[v->p.mtf.master->fv].f = SYSMIS; + out->f = SYSMIS; else - memset (compaction_case->data[v->p.mtf.master->fv].s, ' ', - v->width); + memset (out->s, ' ', v->width); } } - + mtf_free_file (f); } /* Read a record from every input file except the active file. */ static void -mtf_read_nonactive_records (void *aux UNUSED) +mtf_read_nonactive_records (void *mtf_ UNUSED) { + struct mtf_proc *mtf = mtf_; struct mtf_file *iter; - for (iter = mtf_head; iter; ) + for (iter = mtf->head; iter; ) { if (iter->handle) { - assert (iter->input == NULL); - iter->input = xmalloc (dict_get_case_size (iter->dict)); - - if (!sfm_read_case (iter->handle, iter->input, iter->dict)) - mtf_delete_file_in_place (&iter); + if (!sfm_read_case (iter->reader, &iter->input)) + mtf_delete_file_in_place (mtf, &iter); else iter = iter->next; } @@ -1026,24 +1102,25 @@ mtf_read_nonactive_records (void *aux UNUSED) /* Compare the BY variables for files A and B; return -1 if A < B, 0 if A == B, 1 if A > B. */ static inline int -mtf_compare_BY_values (struct mtf_file *a, struct mtf_file *b, +mtf_compare_BY_values (struct mtf_proc *mtf, + struct mtf_file *a, struct mtf_file *b, struct ccase *c) { - union value *a_input, *b_input; + struct ccase *a_input, *b_input; int i; assert ((a == NULL) + (b == NULL) + (c == NULL) <= 1); - a_input = a->input != NULL ? a->input : c->data; - b_input = b->input != NULL ? b->input : c->data; - for (i = 0; i < mtf_n_by; i++) + 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 = a_input[a->by[i]->fv].f; - double bf = b_input[b->by[i]->fv].f; + 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; @@ -1055,8 +1132,8 @@ mtf_compare_BY_values (struct mtf_file *a, struct mtf_file *b, int result; assert (a->by[i]->type == ALPHA); - result = memcmp (a_input[a->by[i]->fv].s, - b_input[b->by[i]->fv].s, + 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; @@ -1069,16 +1146,12 @@ mtf_compare_BY_values (struct mtf_file *a, struct mtf_file *b, /* Perform one iteration of steps 3...7 above. */ static int -mtf_processing (struct ccase *c, void *aux UNUSED) +mtf_processing (struct ccase *c, void *mtf_ UNUSED) { - /* List of files with minimum BY values. */ - struct mtf_file *min_head, *min_tail; - - /* List of files with non-minimum BY values. */ - struct mtf_file *max_head, *max_tail; - - /* Iterator. */ - struct mtf_file *iter; + 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 (;;) { @@ -1086,7 +1159,7 @@ mtf_processing (struct ccase *c, void *aux UNUSED) return because that would cause a record to be skipped. */ int advance = 1; - if (mtf_head->type == MTF_TABLE) + if (mtf->head->type == MTF_TABLE) return 0; /* 3. Find the FILE input record with minimum BY values. Store @@ -1095,11 +1168,11 @@ mtf_processing (struct ccase *c, void *aux UNUSED) 4. Find all the FILE input records with BY values identical to the minimums. Store all the values from these input records into the output record. */ - min_head = min_tail = mtf_head; + min_head = min_tail = mtf->head; max_head = max_tail = NULL; - for (iter = mtf_head->next; iter && iter->type == MTF_FILE; + for (iter = mtf->head->next; iter && iter->type == MTF_FILE; iter = iter->next) - switch (mtf_compare_BY_values (min_head, iter, c)) + switch (mtf_compare_BY_values (mtf, min_head, iter, c)) { case -1: if (max_head) @@ -1144,7 +1217,7 @@ mtf_processing (struct ccase *c, void *aux UNUSED) advance = 0; again: - switch (mtf_compare_BY_values (min_head, iter, c)) + switch (mtf_compare_BY_values (mtf, min_head, iter, c)) { case -1: if (max_head) @@ -1160,9 +1233,9 @@ mtf_processing (struct ccase *c, void *aux UNUSED) case 1: if (iter->handle == NULL) return 1; - if (sfm_read_case (iter->handle, iter->input, iter->dict)) + if (sfm_read_case (iter->reader, &iter->input)) goto again; - mtf_delete_file_in_place (&iter); + mtf_delete_file_in_place (mtf, &iter); break; default: @@ -1173,7 +1246,7 @@ mtf_processing (struct ccase *c, void *aux UNUSED) } /* Next sequence number. */ - mtf_seq_num++; + mtf->seq_num++; /* Store data to all the records we are using. */ if (min_tail) @@ -1185,20 +1258,21 @@ mtf_processing (struct ccase *c, void *aux UNUSED) for (i = 0; i < dict_get_var_cnt (iter->dict); i++) { struct variable *v = dict_get_var (iter->dict, i); - union value *record; + struct ccase *record; + union value *out; - if (mtf_seq_nums[v->p.mtf.master->index] == mtf_seq_num) + if (mtf->seq_nums[get_master (v)->index] == mtf->seq_num) continue; - mtf_seq_nums[v->p.mtf.master->index] = mtf_seq_num; + mtf->seq_nums[get_master (v)->index] = mtf->seq_num; - record = iter->input != NULL ? iter->input : c->data; + record = case_is_null (&iter->input) ? c : &iter->input; assert (v->type == NUMERIC || v->type == ALPHA); + out = case_data_rw (mtf->mtf_case, get_master (v)->fv); if (v->type == NUMERIC) - compaction_case->data[v->p.mtf.master->fv].f = record[v->fv].f; + out->f = case_num (record, v->fv); else - memcpy (compaction_case->data[v->p.mtf.master->fv].s, - record[v->fv].s, v->width); + memcpy (out->s, case_str (record, v->fv), v->width); } } @@ -1212,22 +1286,17 @@ mtf_processing (struct ccase *c, void *aux UNUSED) for (i = 0; i < dict_get_var_cnt (iter->dict); i++) { struct variable *v = dict_get_var (iter->dict, i); + union value *out; - if (mtf_seq_nums[v->p.mtf.master->index] == mtf_seq_num) + if (mtf->seq_nums[get_master (v)->index] == mtf->seq_num) continue; - mtf_seq_nums[v->p.mtf.master->index] = mtf_seq_num; - -#if 0 - printf ("%s/%s: dest-fv=%d\n", - fh_handle_name (iter->handle), - v->name, - v->p.mtf.master->fv); -#endif + mtf->seq_nums[get_master (v)->index] = mtf->seq_num; + + out = case_data_rw (mtf->mtf_case, get_master (v)->fv); if (v->type == NUMERIC) - compaction_case->data[v->p.mtf.master->fv].f = SYSMIS; + out->f = SYSMIS; else - memset (compaction_case->data[v->p.mtf.master->fv].s, ' ', - v->width); + memset (out->s, ' ', v->width); } if (iter->handle == NULL) @@ -1235,7 +1304,7 @@ mtf_processing (struct ccase *c, void *aux UNUSED) } /* 6. Write the output record. */ - process_active_file_output_case (compaction_case); + mtf->sink->class->write (mtf->sink, mtf->mtf_case); /* 7. Read another record from each input file FILE and TABLE that we stored values from above. If we come to the end of @@ -1245,12 +1314,10 @@ mtf_processing (struct ccase *c, void *aux UNUSED) { struct mtf_file *next = iter->next_min; - if (iter->handle) + if (iter->reader != NULL) { - assert (iter->input != NULL); - - if (!sfm_read_case (iter->handle, iter->input, iter->dict)) - mtf_delete_file_in_place (&iter); + if (!sfm_read_case (iter->reader, &iter->input)) + mtf_delete_file_in_place (mtf, &iter); } iter = next; @@ -1260,15 +1327,13 @@ mtf_processing (struct ccase *c, void *aux UNUSED) break; } - return (mtf_head && mtf_head->type != MTF_TABLE); + return (mtf->head && mtf->head->type != MTF_TABLE); } -/* Merge the dictionary for file F into the master dictionary - mtf_master. */ +/* Merge the dictionary for file F into master dictionary M. */ static int -mtf_merge_dictionary (struct mtf_file *f) +mtf_merge_dictionary (struct dictionary *const m, struct mtf_file *f) { - struct dictionary *const m = mtf_master; struct dictionary *d = f->dict; const char *d_docs, *m_docs; @@ -1328,30 +1393,58 @@ mtf_merge_dictionary (struct mtf_file *f) msg (SE, _("Variable %s in file %s (%s) has different " "type or width from the same variable in " "earlier file (%s)."), - dv->name, fh_handle_name (f->handle), + dv->name, handle_get_name (f->handle), var_type_description (dv), var_type_description (mv)); return 0; } - dv->p.mtf.master = mv; + set_master (dv, mv); } } 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) +{ + assert (v->aux != NULL); + return v->aux; +} /* IMPORT command. */ +/* IMPORT input program. */ +struct import_pgm + { + struct pfm_reader *reader; /* Portable file reader. */ + struct case_map *map; /* Map from system file to active file dict. */ + struct ccase bounce; /* Bounce buffer. */ + }; + +static void import_pgm_free (struct import_pgm *); + /* Parses the IMPORT command. */ int cmd_import (void) { - struct file_handle *handle = NULL; - struct dictionary *dict; - struct get_pgm *pgm; - int options = GTSV_OPT_NONE; + struct import_pgm *pgm = NULL; + struct file_handle *fh = NULL; + struct dictionary *dict = NULL; int type; - lex_match_id ("IMPORT"); + pgm = xmalloc (sizeof *pgm); + pgm->reader = NULL; + pgm->map = NULL; + case_nullify (&pgm->bounce); for (;;) { @@ -1361,8 +1454,8 @@ cmd_import (void) { lex_match ('='); - handle = fh_parse_file_handle (); - if (handle == NULL) + fh = fh_parse (); + if (fh == NULL) return CMD_FAILURE; } else if (lex_match_id ("TYPE")) @@ -1389,58 +1482,76 @@ cmd_import (void) discard_variables (); - dict = pfm_read_dictionary (handle, NULL); - if (dict == NULL) + pgm->reader = pfm_open_reader (fh, &dict, NULL); + if (pgm->reader == NULL) return CMD_FAILURE; + case_create (&pgm->bounce, dict_get_next_value_idx (dict)); + + start_case_map (dict); + if (!trim_dictionary (dict, OP_READ, NULL)) + goto error; + pgm->map = finish_case_map (dict); + + dict_destroy (default_dict); + default_dict = dict; -#if DEBUGGING - dump_dict_variables (dict); -#endif - if (0 == trim_dictionary (dict, &options)) - { - fh_close_handle (handle); - return CMD_FAILURE; - } -#if DEBUGGING - dump_dict_variables (dict); -#endif + vfm_source = create_case_source (&import_source_class, pgm); - dict_compact_values (dict); + return CMD_SUCCESS; -#if DEBUGGING - printf (_("IMPORT translation table from file to memory:\n")); - for (i = 0; i < dict->nvar; i++) - { - struct variable *v = dict->var[i]; + error: + import_pgm_free (pgm); + if (dict != NULL) + dict_destroy (dict); + return CMD_FAILURE; +} - printf (_(" %8s from %3d,%3d to %3d,%3d\n"), v->name, - v->get.fv, v->get.nv, v->fv, v->nv); +/* Frees a struct import_pgm. */ +static void +import_pgm_free (struct import_pgm *pgm) +{ + if (pgm != NULL) + { + pfm_close_reader (pgm->reader); + destroy_case_map (pgm->map); + case_destroy (&pgm->bounce); + free (pgm); } -#endif - - dict_destroy (default_dict); - default_dict = dict; - - pgm = xmalloc (sizeof *pgm); - pgm->handle = handle; - pgm->case_size = dict_get_case_size (default_dict); - vfm_source = create_case_source (&import_source_class, default_dict, pgm); +} - return CMD_SUCCESS; +/* Clears internal state related to IMPORT input procedure. */ +static void +import_source_destroy (struct case_source *source) +{ + struct import_pgm *pgm = source->aux; + import_pgm_free (pgm); } -/* Reads all the cases from the data file and passes them to - write_case(). */ +/* Reads all the cases from the data file into C and passes them + to WRITE_CASE one by one, passing WC_DATA. */ static void import_source_read (struct case_source *source, - struct ccase *c, - write_case_func *write_case, write_case_data wc_data) + struct ccase *c, + write_case_func *write_case, write_case_data wc_data) { - struct get_pgm *pgm = source->aux; - - while (pfm_read_case (pgm->handle, c->data, default_dict)) - if (!write_case (wc_data)) - break; + struct import_pgm *pgm = source->aux; + int ok; + + do + { + if (pgm->map == NULL) + ok = pfm_read_case (pgm->reader, c); + else + { + ok = pfm_read_case (pgm->reader, &pgm->bounce); + if (ok) + map_case (pgm->map, &pgm->bounce, c); + } + + if (ok) + ok = write_case (wc_data); + } + while (ok); } const struct case_source_class import_source_class = @@ -1448,94 +1559,129 @@ const struct case_source_class import_source_class = "IMPORT", NULL, import_source_read, - get_source_destroy, + import_source_destroy, }; - -static int export_write_case_func (struct ccase *c, void *); - -/* Parses the EXPORT command. */ -/* FIXME: same as cmd_save_internal(). */ -int -cmd_export (void) -{ - struct file_handle *handle; - struct dictionary *dict; - int options = GTSV_OPT_SAVE; - struct save_trns *t; - - int i; + +/* Case map. - lex_match_id ("EXPORT"); + 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.) + */ - lex_match ('/'); - if (lex_match_id ("OUTFILE")) - lex_match ('='); +/* 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. */ + }; - handle = fh_parse_file_handle (); - if (handle == NULL) - return CMD_FAILURE; +/* 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. - dict = dict_clone (default_dict); -#if DEBUGGING - dump_dict_variables (dict); -#endif - for (i = 0; i < dict_get_var_cnt (dict); i++) - dict_get_var (dict, i)->aux = dict_get_var (default_dict, i); - if (0 == trim_dictionary (dict, &options)) + Uses D's aux members, which may not otherwise 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++) { - fh_close_handle (handle); - return CMD_FAILURE; + 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(). -#if DEBUGGING - dump_dict_variables (dict); -#endif + Does not retain any reference to D, and clears the aux members + set up by start_case_map(). - /* Write dictionary. */ - if (!pfm_write_dictionary (handle, dict)) + 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 = xmalloc (sizeof *map->map * map->value_cnt); + for (i = 0; i < map->value_cnt; i++) + map->map[i] = -1; + + identity_map = 1; + for (i = 0; i < var_cnt; i++) { - dict_destroy (dict); - fh_close_handle (handle); - return CMD_FAILURE; + 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); } - /* Fill in transformation structure. */ - t = xmalloc (sizeof *t); - t->h.proc = save_trns_proc; - t->h.free = save_trns_free; - t->f = handle; - t->nvar = dict_get_var_cnt (dict); - t->var = xmalloc (sizeof *t->var * t->nvar); - for (i = 0; i < t->nvar; i++) - t->var[i] = dict_get_var (dict, i)->aux; - t->case_buf = xmalloc (sizeof *t->case_buf * t->nvar); - dict_destroy (dict); + if (identity_map) + { + destroy_case_map (map); + return NULL; + } - procedure (NULL, export_write_case_func, NULL, t); - save_trns_free (&t->h); + while (map->value_cnt > 0 && map->map[map->value_cnt - 1] == -1) + map->value_cnt--; - return CMD_SUCCESS; + return map; } -/* Writes case C to the EXPORT file. */ -static int -export_write_case_func (struct ccase *c, void *aux) +/* 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) { - struct save_trns *t = aux; - union value *p = (union value *) t->case_buf; - int i; + size_t dst_idx; - for (i = 0; i < t->nvar; i++) - { - struct variable *v = t->var[i]; + assert (map != NULL); + assert (src != NULL); + assert (dst != NULL); + assert (src != dst); - if (v->type == NUMERIC) - *p++ = c->data[v->fv]; - else - (*p++).c = c->data[v->fv].s; + 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); } +} - pfm_write_case (t->f, (union value *) t->case_buf); - return 1; +/* Destroys case map MAP. */ +static void +destroy_case_map (struct case_map *map) +{ + if (map != NULL) + { + free (map->map); + free (map); + } }