texinfo-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[5458] info_node_of_file_buffer_tags: copy tags to returned node


From: Gavin D. Smith
Subject: [5458] info_node_of_file_buffer_tags: copy tags to returned node
Date: Wed, 16 Apr 2014 18:49:01 +0000

Revision: 5458
          http://svn.sv.gnu.org/viewvc/?view=rev&root=texinfo&revision=5458
Author:   gavin
Date:     2014-04-16 18:49:00 +0000 (Wed, 16 Apr 2014)
Log Message:
-----------
info_node_of_file_buffer_tags: copy tags to returned node

Modified Paths:
--------------
    trunk/ChangeLog
    trunk/info/nodes.c
    trunk/info/nodes.h
    trunk/info/search.c
    trunk/info/session.c

Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog     2014-04-16 18:20:05 UTC (rev 5457)
+++ trunk/ChangeLog     2014-04-16 18:49:00 UTC (rev 5458)
@@ -1,5 +1,40 @@
 2014-04-16  Gavin Smith  <address@hidden>
 
+       * nodes.c (init_file_buffer_tag): New function.  Conditonally set
+       N_HasTagsTable and N_TagsIndirect on tag table entries.
+       (get_nodes_of_info_file, get_nodes_of_tag_table): Create tag table
+       entries with info_create_node and init_file_buffer_tag.  Set
+       nodelen to -1 for all tags.
+       * (get_tags_of_indirect_tags_table): Set N_TagsIndirect in flags before
+       calling get_nodes_of_tags_table so it will be set on tag table entries.
+       
+       * nodes.h: (N_WasRewritten): New preprocessor symbol.
+       (NODE.content_cache): Field deleted.
+       
+       * nodes.c (adjust_nodestart): Do not read or write contents field
+       of tag.  Alter nodestart field.  Call find_node_in_binding instead
+       of find_node_separator.  Arguments changed.
+       (set_tag_nodelen): New function, split out from
+       info_node_of_file_buffer_tags.
+       (info_node_of_file_buffer_tags): Do not set N_HasTagsTable or
+       N_TagsIndirect on returned node.  If tags_expand expanded any tags,
+       update contents field of tag and set N_WasRewritten flag.  Set
+       body_start on tag table entry instead of returned node.  Acquire node
+       by copying from tag table.
+       
+       * search.c (find_node_separator): Allow node separator to start
+       anywhere from binding->start inclusive to binding->end exclusive.  Add
+       comment to clarify that we won't read past the end of allocated space.
+       Reformat logical expression.
+       
+       * session.c (free_node_contents): New function.  Free contents of
+       rewritten nodes in tag table.
+       (info_delete_window): Call info_gc_file_buffers.
+       (info_gc_file_buffers): Call free_node_contents.
+       (free_node_tag): Free tag->contents.
+
+2014-04-16  Gavin Smith  <address@hidden>
+
        * nodes.h (NODE): Fields nodestart and content_cache added.
        * nodes.h (TAG): Structure type deleted.  All variables or functions
        declared with this type changed to use NODE instead.

Modified: trunk/info/nodes.c
===================================================================
--- trunk/info/nodes.c  2014-04-16 18:20:05 UTC (rev 5457)
+++ trunk/info/nodes.c  2014-04-16 18:49:00 UTC (rev 5458)
@@ -175,6 +175,21 @@
   return i - binding->start;
 }
 
+/* Set fields on new tag table entry. */
+static void init_file_buffer_tag (FILE_BUFFER *fb, NODE *entry)
+{
+  if (fb->flags & N_HasTagsTable)
+    {
+      entry->flags |= N_HasTagsTable;
+
+      if (fb->flags & N_TagsIndirect)
+        {
+          entry->flags |= N_TagsIndirect;
+          entry->parent = fb->fullpath;
+        }
+    }
+}
+
 /* Search through FILE_BUFFER->contents building an array of NODE *,
    one entry per each node present in the file.  Store the tags in
    FILE_BUFFER->tags, and the number of allocated slots in
@@ -228,25 +243,20 @@
 
       /* Okay, we have isolated the node name, and we know where the
          node starts.  Remember this information. */
-      entry = xmalloc (sizeof (NODE));
-      entry->content_cache = NULL;
+      entry = info_create_node ();
       entry->nodename = xmalloc (1 + (end - start));
       strncpy (entry->nodename, nodeline + start, end - start);
       entry->nodename[end - start] = 0;
       entry->nodestart = nodestart;
+
+      init_file_buffer_tag (file_buffer, entry);
+
       if (anchor)
         entry->nodelen = 0;
       else
-        {
-          SEARCH_BINDING node_body;
+        /* Record that the length is unknown. */
+        entry->nodelen = -1;
 
-          node_body.buffer = binding.buffer + binding.start;
-          node_body.start = 0;
-          node_body.end = binding.end - binding.start;
-          node_body.flags = S_FoldCase;
-          entry->nodelen = get_node_length (&node_body);
-        }
-
       entry->filename = file_buffer->fullpath;
 
       /* Add this tag to the array of tag structures in this FILE_BUFFER. */
@@ -317,9 +327,10 @@
       if (name_offset == -1)
         break;
 
-      entry = xmalloc (sizeof (NODE));
-      entry->content_cache = NULL;
+      entry = info_create_node ();
 
+      init_file_buffer_tag (file_buffer, entry);
+
       /* Find the beginning of the node definition. */
       tmp_search->start += name_offset;
       nodedef = tmp_search->buffer + tmp_search->start;
@@ -372,6 +383,9 @@
   size_t subfiles_index = 0, subfiles_slots = 0;
   NODE *entry;
 
+  /* Remember that tags table was indirect. */
+  file_buffer->flags |= N_TagsIndirect;
+
   /* First get the list of tags from the tags table.  Then lookup the
      associated file in the indirect list for each tag, and update it. */
   get_nodes_of_tags_table (file_buffer, tags_binding);
@@ -510,10 +524,6 @@
           entry->nodestart -= subfiles[i - 1]->first_byte;
           entry->nodestart += header_length;
         }
-
-      /* We have successfully built the tags table.  Remember that it
-         was indirect. */
-      file_buffer->flags |= N_TagsIndirect;
     }
 
   /* Free the structures assigned to SUBFILES.  Free the names as well
@@ -558,8 +568,10 @@
 static void
 free_info_tag (NODE *tag)
 {
+  if (tag->flags & N_WasRewritten)
+    free (tag->contents);
+
   free (tag->nodename);
-  free (tag->content_cache);
   
   /* We don't free tag->filename, because that filename is part of the
      subfiles list for the containing FILE_BUFFER.  free_info_tags ()
@@ -865,17 +877,12 @@
 
 /* Functions for node creation and retrieval. */
 
-/* Magic number that RMS used to decide how much a tags table pointer could
-   be off by.  I feel that it should be much smaller, like 4.  */
-#define DEFAULT_INFO_FUDGE 1000
-
 static int get_filename_and_nodename (int flag, WINDOW *window,
                                       char **filename, char **nodename,
                                       char *filename_in, char *nodename_in);
-static char *adjust_nodestart (NODE *node, int min, int max);
 static void node_set_body_start (NODE *node);
 static NODE *find_node_of_anchor (FILE_BUFFER *file_buffer, NODE *tag);
-static char *adjust_nodestart (NODE *node, int min, int max);
+static int adjust_nodestart (FILE_BUFFER *file_buffer, NODE *tag);
 static NODE *info_node_of_file_buffer_tags (FILE_BUFFER *file_buffer,
     char *nodename);
 
@@ -1133,88 +1140,82 @@
   return node;
 }
 
-/* Return the actual starting memory location of NODE, side-effecting
-   NODE->contents.  MIN and MAX are bounds for a search if one is necessary.
-   Because of the way that tags are implemented, the physical nodestart may
-   not actually be where the tag says it is.  If that is the case, but the
-   node was found anyway, set N_UpdateTags in NODE->flags.  If the node is
-   found, return non-zero.  NODE->contents is returned positioned right after
-   the node separator that precedes this node, while the return value is
-   position directly on the separator that precedes this node.  If the node
-   could not be found, return a NULL pointer. */
-static char *
-adjust_nodestart (NODE *node, int min, int max)
+/* Magic number that RMS used to decide how much a tags table pointer could
+   be off by.  I feel that it should be much smaller, like 4.  */
+#define DEFAULT_INFO_FUDGE 1000
+
+/* Find the actual starting memory location of NODE.  Because of the
+   way that tags are implemented, the physical nodestart may
+   not actually be where the tag says it is.  If that is the case,
+   set N_UpdateTags in NODE->flags.  If the node is
+   found, return non-zero.  Set NODE->nodestart directly on the separator
+   that precedes this node.  If the node could not be found, return 0. */
+static int
+adjust_nodestart (FILE_BUFFER *fb, NODE *node)
 {
   long position;
-  SEARCH_BINDING node_body;
+  SEARCH_BINDING s;
 
-  /* Define the node body. */
-  node_body.buffer = node->contents;
-  node_body.start = 0;
-  node_body.end = max;
-  node_body.flags = 0;
-
   /* Try the optimal case first.  Who knows?  This file may actually be
      formatted (mostly) correctly. */
-  if (node_body.buffer[0] != INFO_COOKIE && min > 2)
-    node_body.buffer -= 3;
+  s.buffer = fb->contents;
+  s.start = node->nodestart;
+  s.end = s.start + 1;
 
-  position = find_node_separator (&node_body);
+  /* Check that the given nodestart is in fact inside the file buffer. */
+  if (s.start >= 0 && s.start < fb->filesize)
+    {
+      /* Check for node separator at node->nodestart
+         introducting this node. */
+      position = find_node_in_binding (node->nodename, &s);
+    }
 
-  /* If we found a node start, then check it out. */
-  if (position != -1)
+  if (position == -1) 
     {
-      int sep_len;
+      if (strict_node_location_p)
+        return 0;
 
-      sep_len = skip_node_separator (node->contents);
+      /* Oh well, I guess we have to try to find it in a larger area. */
 
-      /* If we managed to skip a node separator, then check for this node
-         being the right one. */
-      if (sep_len != 0)
-        {
-          char *nodedef, *nodestart;
-          int offset;
+      s.start -= DEFAULT_INFO_FUDGE;
+      s.end += DEFAULT_INFO_FUDGE;
 
-          nodestart = node_body.buffer + position + sep_len;
-          nodedef = nodestart;
-          offset = string_in_line (INFO_NODE_LABEL, nodedef);
+      if (s.start < 0)
+        s.start = 0;
+      if (s.end > fb->filesize)
+        s.end = fb->filesize;
 
-          if (offset != -1)
-            {
-              nodedef += offset;
-              nodedef += skip_whitespace (nodedef);
-              offset = skip_node_characters (nodedef, PARSE_NODE_START);
-              if (((unsigned int) offset == strlen (node->nodename)) &&
-                  (strncmp (node->nodename, nodedef, offset) == 0))
-                {
-                  node->contents = nodestart;
-                 node_set_body_start (node);
-                  return node_body.buffer + position;
-                }
-            }
-        }
+      position = find_node_in_binding (node->nodename, &s);
+
+      /* If the node still couldn't be found, we lose big. */
+      if (position == -1)
+        return 0;
+ 
+      /* Set the flag in NODE->flags to say that the the tags table could
+         need updating (if we used a tag to get here, that is). */
+      if (node->flags & N_HasTagsTable)
+        node->flags |= N_UpdateTags;
     }
 
-  /* Oh well, I guess we have to try to find it in a larger area. */
-  node_body.buffer = node->contents - min;
-  node_body.start = 0;
-  node_body.end = min + max;
-  node_body.flags = 0;
+  node->nodestart = s.buffer + position - fb->contents;
+  return 1;
+}
 
-  position = find_node_in_binding (node->nodename, &node_body);
+/* Calculate the length of the node. */
+static void
+set_tag_nodelen (FILE_BUFFER *subfile, NODE *tag)
+{
+  int min, max;
+  SEARCH_BINDING node_body;
+  char *buff_end;
 
-  /* If the node couldn't be found, we lose big. */
-  if (position == -1)
-    return NULL;
+  buff_end = subfile->contents + subfile->filesize;
 
-  /* Otherwise, the node was found, but the tags table could need updating
-     (if we used a tag to get here, that is).  Set the flag in NODE->flags. */
-  node->contents = node_body.buffer + position;
-  node->contents += skip_node_separator (node->contents);
-  node_set_body_start (node);
-  if (node->flags & N_HasTagsTable)
-    node->flags |= N_UpdateTags;
-  return node_body.buffer + position;
+  node_body.buffer = tag->contents;
+  node_body.start = 0;
+  node_body.end = buff_end - node_body.buffer;
+  node_body.flags = 0;
+  tag->nodelen = get_node_length (&node_body);
 }
 
 /* Return the node from FILE_BUFFER which matches NODENAME by searching
@@ -1232,7 +1233,8 @@
   for (i = 0; (tag = file_buffer->tags[i]); i++)
     if (strcmp (nodename, tag->nodename) == 0)
       {
-       NODE *node;
+        NODE *node;
+        /* If not a split file, subfile == file_buffer */
         FILE_BUFFER *subfile = info_find_file_internal (tag->filename,
                                                         INFO_NO_TAGS);
         if (!subfile)
@@ -1247,106 +1249,70 @@
 
         /* If we were able to find this file and load it, then return
            the node within it. */
-       if (!(tag->nodestart >= 0 && tag->nodestart < subfile->filesize))
-         return NULL;
+        if (!(tag->nodestart >= 0 && tag->nodestart < subfile->filesize))
+          return NULL;
 
-       node = info_create_node ();
-       node->filename    = subfile->fullpath;
-       node->nodename    = tag->nodename;
-       
-       if (tag->content_cache)
-         node->contents = tag->content_cache;
-       else
-         node->contents    = subfile->contents + tag->nodestart;
+        node = 0;
 
-       node_set_body_start (node);
-       
-       if (file_buffer->flags & N_HasTagsTable)
-         {
-           node->flags |= N_HasTagsTable;
-           
-           if (file_buffer->flags & N_TagsIndirect)
-             {
-               node->flags |= N_TagsIndirect;
-               node->parent = file_buffer->fullpath;
-             }
-         }
-       
-       if (subfile->flags & N_IsCompressed)
-         node->flags |= N_IsCompressed;
-       
-       /* If TAG->nodelen hasn't been calculated yet, then we aren't
-          in a position to trust the entry pointer.  Adjust things so
-          that ENTRY->nodestart gets the exact address of the start of
-          the node separator which starts this node, and NODE->contents
-          gets the address of the line defining this node.  If we cannot
-          do that, the node isn't really here. */
-       if (tag->nodelen == -1)
-         {
-           int min, max;
-           char *node_sep;
-           SEARCH_BINDING node_body;
-           char *buff_end;
-           
-           min = max = DEFAULT_INFO_FUDGE;
-           
-           if (strict_node_location_p)
-             {
-               min = 0;
-               max = 2;
-             }
-           
-           if (tag->nodestart < min)
-             min = tag->nodestart;
-           
-           if (max >
-               (subfile->filesize - tag->nodestart))
-             max = subfile->filesize - tag->nodestart;
-           
-           /* NODE_SEP gets the address of the separator which defines
-              this node, or NULL if the node wasn't found.
-              NODE->contents is side-effected to point to right after
-              the separator. */
-           node_sep = adjust_nodestart (node, min, max);
-           if (node_sep == NULL)
-             {
-               free (node);
-               return NULL;
-             }
-           /* Readjust tag->nodestart. */
-           tag->nodestart = node_sep - subfile->contents;
-           
-           /* Calculate the length of the current node. */
-           buff_end = subfile->contents + subfile->filesize;
-           
-           node_body.buffer = node->contents;
-           node_body.start = 0;
-           node_body.end = buff_end - node_body.buffer;
-           node_body.flags = 0;
-           tag->nodelen = get_node_length (&node_body);
-           /* Expand eventual \b[...\b] constructs in the contents.
-              If found, update node->contents to point to the resulting
-              buffer. */
-           if (tags_expand (node->contents, tag->nodelen,
-                            &tag->content_cache, &tag->nodelen))
-             node->contents = tag->content_cache;
-           node->nodelen = tag->nodelen;
-         }
-       else if (tag->nodelen == 0) /* anchor, return containing node */
-         {
-           free (node);
-           node = find_node_of_anchor (file_buffer, tag);
-         }
-       else
-         {
-           /* Since we know the length of this node, we have already
-              adjusted tag->nodestart to point to the exact start of
-              it.  Simply skip the node separator. */
-           node->contents += skip_node_separator (node->contents);
-           node->nodelen = tag->nodelen;
-         }
+        /* If not an anchor and contents of node are not available: */
+        if (tag->nodelen != 0 && !tag->contents)
+          {
+            char *new_contents;
+            long new_nodelen;
 
-       return node;
+            /* If TAG->nodelen hasn't been calculated yet, then we aren't
+               in a position to trust the entry pointer.  Adjust things so
+               that ENTRY->nodestart gets the exact address of the start of
+               the node separator which starts this node, and NODE->contents
+               gets the address of the line defining this node.  If we cannot
+               do that, the node isn't really here. */
+            if (tag->nodelen == -1)
+              {
+                char *node_sep;
+
+                if (!adjust_nodestart (subfile, tag))
+                  return NULL; /* Node not found. */
+              }
+
+            /* Right after the separator. */
+            tag->contents = subfile->contents + tag->nodestart;
+            tag->contents += skip_node_separator (tag->contents);
+
+            /* This may be already calculated, but be out of date
+               due to previous calls to tags_expand. */
+            set_tag_nodelen (subfile, tag);
+
+            /* Expand eventual \b[...\b] constructs in the contents.
+               If found, update tag->contents to point to the resulting
+               buffer. */
+            if (tags_expand (tag->contents, tag->nodelen,
+                             &new_contents, &new_nodelen))
+              {  
+                tag->contents = new_contents;
+                tag->nodelen = new_nodelen;
+                tag->flags &= N_WasRewritten;
+              }
+ 
+            node_set_body_start (tag);
+          }
+        else if (tag->nodelen == 0) /* anchor, return containing node */
+          {
+            node = find_node_of_anchor (file_buffer, tag);
+          }
+
+        if (!node)
+          {
+            node = xmalloc (sizeof (NODE));
+            *node = *tag;
+          }
+
+        /* We can't set this when tag table is built, because
+           if file is split, we don't know which of the sub-files
+           are compressed. */
+        if (subfile->flags & N_IsCompressed)
+          node->flags |= N_IsCompressed;
+        
+        return node;
       }
 
   /* There was a tag table for this file, and the node wasn't found.

Modified: trunk/info/nodes.h
===================================================================
--- trunk/info/nodes.h  2014-04-16 18:20:05 UTC (rev 5457)
+++ trunk/info/nodes.h  2014-04-16 18:49:00 UTC (rev 5458)
@@ -39,14 +39,14 @@
   char *parent;                 /* Non-null is the logical file name. */
   char *nodename;               /* The name of this node. */
   char *contents;               /* Characters appearing in this node. */
-  long nodelen;                 /* The length of the CONTENTS member. */
+  long nodelen;                 /* The length of the CONTENTS member.
+                                   nodelen == -1 if length is unknown
+                                   because node hasn't been read yet.
+                                   nodelen == 0 if it is an anchor. */
   unsigned long display_pos;    /* Where to display at, if nonzero.  */
   long body_start;              /* Offset of the actual node body */
   int flags;                    /* See immediately below. */
   long nodestart;               /* The offset of the start of this node. */
-  char *content_cache;          /* Cache of the node contents; used if the
-                                  node contents must be preprocessed before
-                                  displaying it. */
 } NODE;
 
 /* Defines that can appear in NODE->flags.  All informative. */
@@ -58,6 +58,7 @@
 #define N_CannotGC     0x20     /* File buffer cannot be gc'ed. */
 #define N_IsManPage    0x40     /* This node is a manpage. */
 #define N_FromAnchor   0x80     /* Synthesized for an anchor reference. */
+#define N_WasRewritten 0x100    /* NODE->contents can be passed to free(). */ 
 
 /* Internal data structures.  */
 

Modified: trunk/info/search.c
===================================================================
--- trunk/info/search.c 2014-04-16 18:20:05 UTC (rev 5457)
+++ trunk/info/search.c 2014-04-16 18:49:00 UTC (rev 5458)
@@ -577,9 +577,9 @@
 /*                                                                  */
 /* **************************************************************** */
 
-/* Return the absolute position of the first occurence of a node separator in
-   BINDING-buffer.  The search starts at BINDING->start.  Return -1 if no node
-   separator was found. */
+/* Return the absolute position of the first occurence of a node separator
+   starting in BINDING->buffer.  The search starts at BINDING->start.
+   Return -1 if no node separator was found. */
 long
 find_node_separator (SEARCH_BINDING *binding)
 {
@@ -592,14 +592,17 @@
      optional, but the DELETE and NEWLINE are not.  This separator holds
      true for all separated elements in an Info file, including the tags
      table (if present) and the indirect tags table (if present). */
-  for (i = binding->start; i < binding->end - 1; i++)
-    if (((body[i] == INFO_FF && body[i + 1] == INFO_COOKIE) &&
-         (body[i + 2] == '\n' ||
-          (body[i + 2] == INFO_FF && body[i + 3] == '\n'))) ||
-        ((body[i] == INFO_COOKIE) &&
-         (body[i + 1] == '\n' ||
-          (body[i + 1] == INFO_FF && body[i + 2] == '\n'))))
-      return i;
+  for (i = binding->start; i < binding->end; i++)
+      /* Note that bytes are read in order from the buffer, so if at any
+         point a null byte is encountered signifying the end of the buffer,
+         no more bytes will be read past that point. */
+      if (   (body[i] == INFO_FF && body[i + 1] == INFO_COOKIE
+              && (body[i + 2] == '\n'
+                  || (body[i + 2] == INFO_FF && body[i + 3] == '\n')))
+          || (body[i] == INFO_COOKIE
+              && (body[i + 1] == '\n'
+                  || (body[i + 1] == INFO_FF && body[i + 2] == '\n'))))
+          return i;
   return -1;
 }
 

Modified: trunk/info/session.c
===================================================================
--- trunk/info/session.c        2014-04-16 18:20:05 UTC (rev 5457)
+++ trunk/info/session.c        2014-04-16 18:49:00 UTC (rev 5458)
@@ -38,6 +38,8 @@
 #  include "man.h"
 #endif
 
+static void info_gc_file_buffers (void);
+
 static void info_clear_pending_input (void);
 static void info_set_pending_input (unsigned char key);
 static void info_handle_pointer (char *label, WINDOW *window);
@@ -1884,6 +1886,8 @@
 
       if (auto_tiling_p)
         window_tile_windows (DONT_TILE_INTERNALS);
+
+      info_gc_file_buffers ();
     }
 }
 
@@ -4678,6 +4682,26 @@
     window_clear_echo_area ();
 }
 
+/* Find tag table entries for nodes which have been rewritten, and free
+   their contents. */
+static void
+free_node_contents (FILE_BUFFER *fb)
+{
+  NODE **entry;
+
+  if (!fb->tags)
+    return;
+
+  for (entry = fb->tags; *entry; entry++)
+    if ((*entry)->flags & N_WasRewritten)
+      {
+        free ((*entry)->contents);
+        (*entry)->contents = 0;
+        (*entry)->flags &= ~N_WasRewritten;
+      }
+}
+
+
 /* GC some file buffers.  A file buffer can be gc-ed if there we have
    no nodes in INFO_WINDOWS that reference this file buffer's contents.
    Garbage collecting a file buffer means to free the file buffers
@@ -4694,7 +4718,7 @@
 
   for (fb_index = 0; (fb = info_loaded_files[fb_index]); fb_index++)
     {
-      int fb_referenced_p = 0;
+      int fb_referenced_p = 0, parent_referenced_p = 0;
 
       /* If already gc-ed, do nothing. */
       if (!fb->contents)
@@ -4723,12 +4747,24 @@
                   fb_referenced_p = 1;
                   break;
                 }
+
+              /* If any subfile of a split file is referenced, none of
+                 the rewritten nodes in the split file is freed. */
+              if (iw->nodes[i]->parent &&
+                 ((FILENAME_CMP (fb->fullpath, iw->nodes[i]->parent) == 0) ||
+                  (FILENAME_CMP (fb->filename, iw->nodes[i]->parent) == 0)))
+                {
+                  parent_referenced_p = 1;
+                  break;
+                }
             }
         }
 
       /* If this file buffer wasn't referenced, free its contents. */
       if (!fb_referenced_p)
         {
+          if (!parent_referenced_p)
+            free_node_contents (fb);
           free (fb->contents);
           fb->contents = NULL;
         }




reply via email to

[Prev in Thread] Current Thread [Next in Thread]