Rewrite expression code.
[pspp-builds.git] / src / file-handle.q
index c70945ee381f701cf9a2694cd9e0198289ea714a..00c3fefd76f910d4168130d0d7e38aabebec6682 100644 (file)
 
 #include <config.h>
 #include "file-handle.h"
-#include <assert.h>
+#include "error.h"
 #include <errno.h>
 #include <stdlib.h>
 #include "alloc.h"
 #include "filename.h"
 #include "command.h"
-#include "hash.h"
 #include "lexer.h"
 #include "getline.h"
 #include "error.h"
 #include "magic.h"
 #include "var.h"
-/* (headers) */
+#include "linked-list.h"
 
-#include "debug-print.h"
-
-static struct hsh_table *files;
-struct file_handle *inline_file;
+/* (headers) */
 
-static void init_file_handle (struct file_handle * handle);
+/* File handle. */
+struct file_handle 
+  {
+    struct file_handle *next;   /* Next in global list. */
+    char *name;                 /* File handle identifier. */
+    char *filename;            /* Filename as provided by user. */
+    struct file_identity *identity; /* For checking file identity. */
+    struct file_locator where; /* Used for reporting error messages. */
+    enum file_handle_mode mode;        /* File mode. */
+    size_t length;             /* Length of fixed-format records. */
+    size_t tab_width;           /* Tab width, 0=do not expand tabs. */
+
+    int open_cnt;               /* 0=not open, otherwise # of openers. */
+    const char *type;           /* If open, type of file. */
+    const char *open_mode;      /* "[rw][se]". */
+    void *aux;                  /* Aux data pointer for owner if any. */
+  };
+
+static struct file_handle *file_handles;
+
+static struct file_handle *create_file_handle (const char *handle_name,
+                                               const char *filename);
 
 /* (specification)
    "FILE HANDLE" (fh_):
      name=string;
-     recform=recform:fixed/!variable/spanned;
      lrecl=integer;
-     mode=mode:!character/image/binary/multipunch/_360.
+     tabwidth=integer "x>=0" "%s must be nonnegative";
+     mode=mode:!character/image.
 */
 /* (declarations) */
 /* (functions) */
 
+static struct file_handle *
+get_handle_with_name (const char *handle_name) 
+{
+  struct file_handle *iter;
+
+  for (iter = file_handles; iter != NULL; iter = iter->next)
+    if (!strcmp (handle_name, iter->name))
+      return iter;
+  return NULL;
+}
+
+static struct file_handle *
+get_handle_for_filename (const char *filename)
+{
+  struct file_identity *identity;
+  struct file_handle *iter;
+      
+  /* First check for a file with the same identity. */
+  identity = fn_get_identity (filename);
+  if (identity != NULL) 
+    {
+      for (iter = file_handles; iter != NULL; iter = iter->next)
+        if (iter->identity != NULL
+            && !fn_compare_file_identities (identity, iter->identity))
+          {
+            fn_free_identity (identity);
+            return iter; 
+          }
+      fn_free_identity (identity);
+    }
+
+  /* Then check for a file with the same name. */
+  for (iter = file_handles; iter != NULL; iter = iter->next)
+    if (!strcmp (filename, iter->filename))
+      return iter; 
+
+  return NULL;
+}
+
 int
 cmd_file_handle (void)
 {
   char handle_name[9];
-  char *handle_name_p = handle_name;
 
   struct cmd_file_handle cmd;
-  struct file_handle *fp;
+  struct file_handle *handle;
 
-  lex_get ();
   if (!lex_force_id ())
     return CMD_FAILURE;
   strcpy (handle_name, tokid);
 
-  fp = NULL;
-  if (files)
-    fp = hsh_find (files, &handle_name_p);
-  if (fp)
+  handle = get_handle_with_name (handle_name);
+  if (handle != NULL)
     {
-      msg (SE, _("File handle %s had already been defined to refer to "
-                "file %s.  It is not possible to redefine a file "
-                "handle within a session."),
-          tokid, fp->fn);
+      msg (SE, _("File handle %s already refers to "
+                "file %s.  File handle cannot be redefined within a "
+                 "session."),
+          tokid, handle->filename);
       return CMD_FAILURE;
     }
 
@@ -96,68 +148,38 @@ cmd_file_handle (void)
       goto lossage;
     }
 
-  fp = xmalloc (sizeof *fp);
-  init_file_handle (fp);
-
-  switch (cmd.recform)
+  handle = create_file_handle (handle_name, cmd.s_name);
+  switch (cmd.mode)
     {
-    case FH_FIXED:
-      if (cmd.n_lrecl == NOT_LONG)
+    case FH_CHARACTER:
+      handle->mode = MODE_TEXT;
+      if (cmd.sbc_tabwidth)
+        handle->tab_width = cmd.n_tabwidth[0];
+      else
+        handle->tab_width = 4;
+      break;
+    case FH_IMAGE:
+      handle->mode = MODE_BINARY;
+      if (cmd.n_lrecl[0] == NOT_LONG)
        {
-         msg (SE, _("Fixed length records were specified on /RECFORM, but "
-              "record length was not specified on /LRECL.  80-character "
-              "records will be assumed."));
-         cmd.n_lrecl = 80;
+         msg (SE, _("Fixed-length records were specified on /RECFORM, but "
+                     "record length was not specified on /LRECL.  "
+                     "Assuming 1024-character records."));
+          handle->length = 1024;
        }
-      else if (cmd.n_lrecl < 1)
+      else if (cmd.n_lrecl[0] < 1)
        {
          msg (SE, _("Record length (%ld) must be at least one byte.  "
-                    "80-character records will be assumed."), cmd.n_lrecl);
-         cmd.n_lrecl = 80;
+                    "1-character records will be assumed."), cmd.n_lrecl);
+          handle->length = 1;
        }
-      fp->recform = FH_RF_FIXED;
-      fp->lrecl = cmd.n_lrecl;
-      break;
-    case FH_VARIABLE:
-      fp->recform = FH_RF_VARIABLE;
-      break;
-    case FH_SPANNED:
-      msg (SE, 
-          _("%s is not implemented, as the author doesn't know what it is supposed to do.  Send a note to %s.") ,
-          "/RECFORM SPANNED",PACKAGE_BUGREPORT);
+      else
+        handle->length = cmd.n_lrecl[0];
       break;
     default:
       assert (0);
     }
 
-  switch (cmd.mode)
-    {
-    case FH_CHARACTER:
-      fp->mode = FH_MD_CHARACTER;
-      break;
-    case FH_IMAGE:
-      msg (SE, 
-          _("%s is not implemented, as the author doesn't know what it is supposed to do.  Send a note to %s.") ,
-          "/MODE IMAGE",PACKAGE_BUGREPORT);
-      break;
-    case FH_BINARY:
-      fp->mode = FH_MD_BINARY;
-      break;
-    case FH_MULTIPUNCH:
-      msg (SE, _("%s is not implemented.  If you care, complain."),"/MODE MULTIPUNCH");
-      break;
-    case FH__360:
-      msg (SE, _("%s is not implemented.  If you care, complain."),"/MODE 360");
-      break;
-    default:
-      assert (0);
-    }
-
-  fp->name = xstrdup (handle_name);
-  fp->norm_fn = fn_normalize (cmd.s_name);
-  fp->where.filename = fp->fn = cmd.s_name;
-  hsh_force_insert (files, fp);
-
   return CMD_SUCCESS;
 
  lossage:
@@ -167,204 +189,238 @@ cmd_file_handle (void)
 \f
 /* File handle functions. */
 
-/* Sets up some fields in H; caller should fill in
-   H->{NAME,NORM_FN,FN}. */
-static void
-init_file_handle (struct file_handle *h)
+/* Creates and returns a new file handle with the given values
+   and defaults for other values.  Adds the created file handle
+   to the global list. */
+static struct file_handle *
+create_file_handle (const char *handle_name, const char *filename)
 {
-  h->recform = FH_RF_VARIABLE;
-  h->mode = FH_MD_CHARACTER;
-  h->ext = NULL;
-  h->class = NULL;
+  struct file_handle *handle;
+
+  /* Create and initialize file handle. */
+  handle = xmalloc (sizeof *handle);
+  handle->next = file_handles;
+  handle->name = xstrdup (handle_name);
+  handle->filename = xstrdup (filename);
+  handle->identity = fn_get_identity (filename);
+  handle->where.filename = handle->filename;
+  handle->where.line_number = 0;
+  handle->mode = MODE_TEXT;
+  handle->length = 1024;
+  handle->tab_width = 4;
+  handle->open_cnt = 0;
+  handle->type = NULL;
+  handle->open_mode = NULL;
+  handle->aux = NULL;
+  file_handles = handle;
+
+  return handle;
 }
 
-/* Returns the handle corresponding to FILENAME.  Creates the handle
-   if no handle exists for that file.  All filenames are normalized
-   first, so different filenames referring to the same file will
-   return the same file handle. */
-struct file_handle *
-fh_get_handle_by_filename (const char *filename)
+static void
+destroy_file_handle(void *fh_, void *aux UNUSED)
 {
-  struct file_handle f, *fp;
-  char *fn;
-  char *name;
-  int len;
-
-  /* Get filename. */
-  fn = fn_normalize (filename);
-  len = strlen (fn);
-
-  /* Create handle name with invalid identifier character to prevent
-     conflicts with handles created with FILE HANDLE. */
-  name = xmalloc (len + 2);
-  name[0] = '*';
-  strcpy (&name[1], fn);
-
-  f.name = name;
-  fp = hsh_find (files, &f);
-  if (!fp)
-    {
-      fp = xmalloc (sizeof *fp);
-      init_file_handle (fp);
-      fp->name = name;
-      fp->norm_fn = fn;
-      fp->where.filename = fp->fn = xstrdup (filename);
-      hsh_force_insert (files, fp);
-    }
-  else
-    {
-      free (fn);
-      free (name);
-    }
-  return fp;
+  struct file_handle *fh = fh_;
+  free (fh->name);
+  free (fh->filename);
+  fn_free_identity (fh->identity);
+  free (fh);
 }
 
-/* Returns the handle with identifier NAME, if it exists; otherwise
-   reports error to user and returns NULL. */
-struct file_handle *
-fh_get_handle_by_name (const char name[9])
+static const char *
+mode_name (const char *mode) 
 {
-  struct file_handle f, *fp;
-  f.name = (char *) name;
-  fp = hsh_find (files, &f);
-
-  if (!fp)
-    msg (SE, _("File handle `%s' has not been previously declared on "
-        "FILE HANDLE."), name);
-  return fp;
+  assert (mode != NULL);
+  assert (mode[0] == 'r' || mode[0] == 'w');
+
+  return mode[0] == 'r' ? "reading" : "writing";
 }
 
-/* Returns the identifier of file HANDLE.  If HANDLE was created by
-   referring to a filename (i.e., DATA LIST FILE='yyy' instead of FILE
-   HANDLE XXX='yyy'), returns the filename, enclosed in double quotes.
-   Return value is in a static buffer.
 
-   Useful for printing error messages about use of file handles.  */
-const char *
-fh_handle_name (struct file_handle *h)
-{
-  static char *buf = NULL;
+/* Tries to open FILE with the given TYPE and MODE.
 
-  if (buf)
-    {
-      free (buf);
-      buf = NULL;
-    }
-  if (!h)
-    return NULL;
+   TYPE is the sort of file, e.g. "system file".  Only one given
+   type of access is allowed on a given file handle at once.
 
-  if (h->name[0] == '*')
-    {
-      int len = strlen (h->fn);
+   MODE combines the read or write mode with the sharing mode.
+   The first character is 'r' for read, 'w' for write.  The
+   second character is 's' to permit sharing, 'e' to require
+   exclusive access.
 
-      buf = xmalloc (len + 3);
-      strcpy (&buf[1], h->fn);
-      buf[0] = buf[len + 1] = '"';
-      buf[len + 2] = 0;
-      return buf;
-    }
-  return h->name;
-}
+   Returns the address of a void * that the caller can use for
+   data specific to the file handle if successful, or a null
+   pointer on failure.  For exclusive access modes the void *
+   will always be a null pointer at return.  In shared access
+   modes the void * will necessarily be null only if no other
+   sharers are active.
 
-/* Closes the stdio FILE associated with handle H.  Frees internal
-   buffers associated with that file.  Does *not* destroy the file
-   handle H.  (File handles are permanent during a session.)  */
-void
-fh_close_handle (struct file_handle *h)
+   If successful, references to type and mode are retained, so
+   they should probably be string literals. */
+void **
+fh_open (struct file_handle *h, const char *type, const char *mode) 
 {
-  if (h == NULL)
-    return;
-
-  debug_printf (("Closing %s%s.\n", fh_handle_name (h),
-                h->class == NULL ? " (already closed)" : ""));
+  assert (h != NULL);
+  assert (type != NULL);
+  assert (mode != NULL);
+  assert (mode[0] == 'r' || mode[0] == 'w');
+  assert (mode[1] == 's' || mode[1] == 'e');
+  assert (mode[2] == '\0');
+
+  if (h->open_cnt != 0) 
+    {
+      if (strcmp (h->type, type))
+        msg (SE, _("Can't open %s as a %s because it is "
+                   "already open as a %s"),
+             handle_get_name (h), type, h->type);
+      else if (strcmp (h->open_mode, mode))
+        msg (SE, _("Can't open %s as a %s for %s because it is "
+                   "already open for %s"),
+             handle_get_name (h), type,
+             mode_name (mode), mode_name (h->open_mode));
+      else if (h->open_mode[1] == 'e')
+        msg (SE, _("Can't re-open %s as a %s for %s"),
+             handle_get_name (h), type, mode_name (mode));
+    }
+  else 
+    {
+      h->type = type;
+      h->open_mode = mode;
+      assert (h->aux == NULL);
+    }
+  h->open_cnt++;
 
-  if (h->class)
-    h->class->close (h);
-  h->class = NULL;
-  h->ext = NULL;
+  return &h->aux;
 }
 
-/* Hashes the name of file handle H. */
-static unsigned
-hash_file_handle (const void *handle_, void *param unused)
+/* Closes file handle H, which must have been open for the
+   specified TYPE and MODE of access provided to fh_open().
+   Returns zero if the file is now closed, nonzero if it is still
+   open due to another reference. */
+int
+fh_close (struct file_handle *h, const char *type, const char *mode)
 {
-  const struct file_handle *handle = handle_;
-
-  return hsh_hash_string (handle->name);
+  assert (h != NULL);
+  assert (h->open_cnt > 0);
+  assert (type != NULL);
+  assert (!strcmp (type, h->type));
+  assert (mode != NULL);
+  assert (!strcmp (mode, h->open_mode));
+
+  h->open_cnt--;
+  if (h->open_cnt == 0) 
+    {
+      h->type = NULL;
+      h->open_mode = NULL;
+      h->aux = NULL;
+    }
+  return h->open_cnt;
 }
 
-/* Compares names of file handles A and B. */
-static int
-cmp_file_handle (const void *a_, const void *b_, void *foo unused)
-{
-  const struct file_handle *a = a_;
-  const struct file_handle *b = b_;
 
-  return strcmp (a->name, b->name);
-}
+static struct linked_list *handle_list;
 
-/* Initialize the hash of file handles; inserts the "inline file"
-   inline_file. */
-void
-fh_init_files (void)
-{
-  /* Create hash. */
-  files = hsh_create (4, cmp_file_handle, hash_file_handle, NULL, NULL);
-
-  /* Insert inline file. */
-  inline_file = xmalloc (sizeof *inline_file);
-  init_file_handle (inline_file);
-  inline_file->name = "INLINE";
-  inline_file->where.filename
-    = inline_file->fn = inline_file->norm_fn = (char *) _("<Inline File>");
-  inline_file->where.line_number = 0;
-  hsh_force_insert (files, inline_file);
-}
 
 /* Parses a file handle name, which may be a filename as a string or
    a file handle name as an identifier.  Returns the file handle or
    NULL on failure. */
 struct file_handle *
-fh_parse_file_handle (void)
+fh_parse (void)
 {
   struct file_handle *handle;
 
-  if (token == T_ID)
-    handle = fh_get_handle_by_name (tokid);
-  else if (token == T_STRING)
-    handle = fh_get_handle_by_filename (ds_value (&tokstr));
-  else
+  if (token != T_ID && token != T_STRING)
     {
-      lex_error (_("expecting a file name or handle"));
+      lex_error (_("expecting a file name or handle name"));
       return NULL;
     }
 
-  if (!handle)
-    return NULL;
+  /* Check for named handles first, then go by filename. */
+  handle = NULL;
+  if (token == T_ID) 
+    handle = get_handle_with_name (tokid);
+  if (handle == NULL)
+    handle = get_handle_for_filename (ds_c_str (&tokstr));
+  if (handle == NULL) 
+    {
+      char *filename = ds_c_str (&tokstr);
+      char *handle_name = xmalloc (strlen (filename) + 3);
+      sprintf (handle_name, "\"%s\"", filename);
+      handle = create_file_handle (handle_name, filename);
+      ll_push_front(handle_list, handle);
+      free (handle_name);
+    }
+
   lex_get ();
 
+
   return handle;
 }
 
-/* Returns the (normalized) filename associated with file handle H. */
-char *
-fh_handle_filename (struct file_handle * h)
+/* Returns the identifier of file HANDLE.  If HANDLE was created
+   by referring to a filename instead of a handle name, returns
+   the filename, enclosed in double quotes.  Return value is
+   owned by the file handle. 
+
+   Useful for printing error messages about use of file handles.  */
+const char *
+handle_get_name (const struct file_handle *handle)
+{
+  assert (handle != NULL);
+  return handle->name;
+}
+
+/* Returns the name of the file associated with HANDLE. */
+const char *
+handle_get_filename (const struct file_handle *handle) 
+{
+  assert (handle != NULL);
+  return handle->filename;
+}
+
+/* Returns the mode of HANDLE. */
+enum file_handle_mode
+handle_get_mode (const struct file_handle *handle) 
 {
-  return h->norm_fn;
+  assert (handle != NULL);
+  return handle->mode;
 }
 
-/* Returns the width of a logical record on file handle H. */
+/* Returns the width of a logical record on HANDLE.  Applicable
+   only to MODE_BINARY files.  */
 size_t
-fh_record_width (struct file_handle *h)
+handle_get_record_width (const struct file_handle *handle)
 {
-  if (h == inline_file)
-    return 80;
-  else if (h->recform == FH_RF_FIXED)
-    return h->lrecl;
-  else
-    return 1024;
+  assert (handle != NULL);
+  return handle->length;
 }
 
+/* Returns the number of characters per tab stop for HANDLE, or
+   zero if tabs are not to be expanded.  Applicable only to
+   MODE_TEXT files. */
+size_t
+handle_get_tab_width (const struct file_handle *handle) 
+{
+  assert (handle != NULL);
+  return handle->tab_width;
+}
+
+
+void 
+fh_init(void)
+{
+  handle_list = ll_create(destroy_file_handle,0);
+}
+
+void 
+fh_done(void)
+{
+  assert(handle_list);
+  
+  ll_destroy(handle_list);
+  handle_list = 0;
+}
+
+
 /*
    Local variables:
    mode: c