home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / tcl / tk3.3b1 / tkTextIndex.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-06-16  |  17.2 KB  |  652 lines

  1. /* 
  2.  * tkTextIndex.c --
  3.  *
  4.  *    This module provides procedures that manipulate indices for
  5.  *    text widgets.
  6.  *
  7.  * Copyright (c) 1992-1993 The Regents of the University of California.
  8.  * All rights reserved.
  9.  *
  10.  * Permission is hereby granted, without written agreement and without
  11.  * license or royalty fees, to use, copy, modify, and distribute this
  12.  * software and its documentation for any purpose, provided that the
  13.  * above copyright notice and the following two paragraphs appear in
  14.  * all copies of this software.
  15.  * 
  16.  * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
  17.  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
  18.  * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
  19.  * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  20.  *
  21.  * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
  22.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  23.  * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
  24.  * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
  25.  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  26.  */
  27.  
  28. #ifndef lint
  29. static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkTextIndex.c,v 1.3 93/06/16 17:16:34 ouster Exp $ SPRITE (Berkeley)";
  30. #endif
  31.  
  32. #include "default.h"
  33. #include "tkConfig.h"
  34. #include "tk.h"
  35. #include "tkText.h"
  36.  
  37. /*
  38.  * Forward declarations for procedures defined later in this file:
  39.  */
  40.  
  41. static void        BackwardChars _ANSI_ARGS_((TkText *textPtr,
  42.                 TkTextLine *linePtr, int *lineIndexPtr,
  43.                 int *chPtr, int count));
  44. static char *        ForwBack _ANSI_ARGS_((TkText *textPtr,
  45.                 char *string, int *lineIndexPtr, int *chPtr));
  46. static void        ForwardChars _ANSI_ARGS_((TkText *textPtr,
  47.                 TkTextLine *linePtr, int *lineIndexPtr,
  48.                 int *chPtr, int count));
  49. static char *        StartEnd _ANSI_ARGS_((TkText *textPtr,
  50.                 char *string, int *lineIndexPtr, int *chPtr));
  51.  
  52. /*
  53.  *----------------------------------------------------------------------
  54.  *
  55.  * TkTextGetIndex --
  56.  *
  57.  *    Given a string, return the line and character indices that
  58.  *    it describes.
  59.  *
  60.  * Results:
  61.  *    The return value is a standard Tcl return result.  If
  62.  *    TCL_OK is returned, then everything went well and information
  63.  *    is stored at *lineIndexPtr and *chPtr;  otherwise TCL_ERROR
  64.  *    is returned and an error message is left in interp->result.
  65.  *
  66.  * Side effects:
  67.  *    None.
  68.  *
  69.  *----------------------------------------------------------------------
  70.  */
  71.  
  72. int
  73. TkTextGetIndex(interp, textPtr, string, lineIndexPtr, chPtr)
  74.     Tcl_Interp *interp;        /* Use this for error reporting. */
  75.     TkText *textPtr;        /* Information about text widget. */
  76.     char *string;        /* Textual description of position. */
  77.     int *lineIndexPtr;        /* Store line number here. */
  78.     int *chPtr;            /* Store character position here. */
  79. {
  80.     register char *p;
  81.     char *end, *endOfBase;
  82.     TkTextLine *linePtr;
  83.     Tcl_HashEntry *hPtr;
  84.     TkAnnotation *markPtr;
  85.     TkTextTag *tagPtr;
  86.     TkTextSearch search;
  87.     int first;
  88.     char c;
  89.  
  90.     /*
  91.      *------------------------------------------------
  92.      * Stage 1: parse the base index.
  93.      *------------------------------------------------
  94.      */
  95.  
  96.     if (string[0] == '@') {
  97.     /*
  98.      * Find character at a given x,y location in the window.
  99.      */
  100.  
  101.     int x, y;
  102.  
  103.     p = string+1;
  104.     x = strtol(p, &end, 0);
  105.     if ((end == p) || (*end != ',')) {
  106.         goto error;
  107.     }
  108.     p = end+1;
  109.     y = strtol(p, &end, 0);
  110.     if (end == p) {
  111.         goto error;
  112.     }
  113.     *lineIndexPtr = TkBTreeLineIndex(TkTextCharAtLoc(textPtr, x,
  114.         y, chPtr));
  115.     endOfBase = end;
  116.     goto gotBase; 
  117.     } else if (isdigit(string[0]) || (string[0] == '-')) {
  118.     /*
  119.      * Base is identified with line and character indices.
  120.      */
  121.  
  122.     *lineIndexPtr = strtol(string, &end, 0) - 1;
  123.     if ((end == string) || (*end != '.')) {
  124.         goto error;
  125.     }
  126.     p = end+1;
  127.     if ((*p == 'e') && (strncmp(p, "end", 3) == 0)) {
  128.         linePtr = TkBTreeFindLine(textPtr->tree, *lineIndexPtr);
  129.         if (linePtr == NULL) {
  130.         Tcl_AppendResult(interp, "bad text index \"", string,
  131.             "\": no such line in text", (char *) NULL);
  132.         return TCL_ERROR;
  133.         }
  134.         *chPtr = linePtr->numBytes - 1;
  135.         endOfBase = p+3;
  136.         goto gotBase;
  137.     } else {
  138.         *chPtr = strtol(p, &end, 0);
  139.         if (end == p) {
  140.         goto error;
  141.         }
  142.         endOfBase = end;
  143.         goto gotBase;
  144.     }
  145.     }
  146.  
  147.     for (p = string; *p != 0; p++) {
  148.     if (isspace(*p) || (*p == '+') || (*p == '-')) {
  149.         break;
  150.     }
  151.     }
  152.     endOfBase = p;
  153.     if ((string[0] == 'e')
  154.         && (strncmp(string, "end", endOfBase-string) == 0)) {
  155.     /*
  156.      * Base position is end of text.
  157.      */
  158.  
  159.     *lineIndexPtr = TkBTreeNumLines(textPtr->tree) - 1;
  160.     linePtr = TkBTreeFindLine(textPtr->tree, *lineIndexPtr);
  161.     *chPtr = linePtr->numBytes - 1;
  162.     goto gotBase;
  163.     } else {
  164.     /*
  165.      * See if the base position is the name of a mark.
  166.      */
  167.  
  168.     c = *endOfBase;
  169.     *endOfBase = 0;
  170.     hPtr = Tcl_FindHashEntry(&textPtr->markTable, string);
  171.     *endOfBase = c;
  172.     if (hPtr != NULL) {
  173.         markPtr = (TkAnnotation *) Tcl_GetHashValue(hPtr);
  174.         *lineIndexPtr = TkBTreeLineIndex(markPtr->linePtr);
  175.         *chPtr = markPtr->ch;
  176.         goto gotBase;
  177.     }
  178.     }
  179.  
  180.     /*
  181.      * Nothing has worked so far.  See if the base has the form
  182.      * "tag.first" or "tag.last" where "tag" is the name of a valid
  183.      * tag.
  184.      */
  185.  
  186.     p = strchr(string, '.');
  187.     if (p == NULL) {
  188.     goto error;
  189.     }
  190.     if ((p[1] == 'f') && (endOfBase == (p+6))
  191.         && (strncmp(p+1, "first", endOfBase - (p+1)) == 0)) {
  192.     first = 1;
  193.     } else if ((p[1] == 'l') && (endOfBase == (p+5))
  194.         && (strncmp(p+1, "last", endOfBase - (p+1)) == 0)) {
  195.     first = 0;
  196.     } else {
  197.     goto error;
  198.     }
  199.     *p = 0;
  200.     hPtr = Tcl_FindHashEntry(&textPtr->tagTable, string);
  201.     *p = '.';
  202.     if (hPtr == NULL) {
  203.     goto error;
  204.     }
  205.     tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr);
  206.     TkBTreeStartSearch(textPtr->tree, 0, 0, TkBTreeNumLines(textPtr->tree),
  207.         0, tagPtr, &search);
  208.     if (!TkBTreeNextTag(&search)) {
  209.     Tcl_AppendResult(interp,
  210.         "text doesn't contain any characters tagged with \"",
  211.         Tcl_GetHashKey(&textPtr->tagTable, hPtr), "\"", (char *) NULL);
  212.     return TCL_ERROR;
  213.     }
  214.     if (first) {
  215.     *lineIndexPtr = search.line1;
  216.     *chPtr = search.ch1;
  217.     } else {
  218.     while (TkBTreeNextTag(&search)) {
  219.         *lineIndexPtr = search.line1;
  220.         *chPtr = search.ch1;
  221.     }
  222.     }
  223.  
  224.     /*
  225.      *-------------------------------------------------------------------
  226.      * Stage 2: process zero or more modifiers.  Each modifier is either
  227.      * a keyword like "wordend" or "linestart", or it has the form
  228.      * "op count units" where op is + or -, count is a number, and units
  229.      * is "chars" or "lines".
  230.      *-------------------------------------------------------------------
  231.      */
  232.  
  233.     gotBase:
  234.     p = endOfBase;
  235.     while (1) {
  236.     while (isspace(*p)) {
  237.         p++;
  238.     }
  239.     if (*p == 0) {
  240.         return TCL_OK;
  241.     }
  242.     
  243.     if ((*p == '+') || (*p == '-')) {
  244.         p = ForwBack(textPtr, p, lineIndexPtr, chPtr);
  245.     } else {
  246.         p = StartEnd(textPtr, p, lineIndexPtr, chPtr);
  247.     }
  248.     if (p == NULL) {
  249.         goto error;
  250.     }
  251.     }
  252.  
  253.     error:
  254.     Tcl_AppendResult(interp, "bad text index \"", string, "\"",
  255.         (char *) NULL);
  256.     return TCL_ERROR;
  257. }
  258.  
  259. /*
  260.  *----------------------------------------------------------------------
  261.  *
  262.  * TkTextPrintIndex --
  263.  *
  264.  *    Given a line number and a character index, this procedure
  265.  *    generates a string description of the position, which is
  266.  *    suitable for reading in again later.
  267.  *
  268.  * Results:
  269.  *    The characters pointed to by string are modified.
  270.  *
  271.  * Side effects:
  272.  *    None.
  273.  *
  274.  *----------------------------------------------------------------------
  275.  */
  276.  
  277. void
  278. TkTextPrintIndex(line, ch, string)
  279.     int line;            /* Line number. */
  280.     int ch;            /* Character position within line. */
  281.     char *string;        /* Place to store the position.  Must have
  282.                  * at least POS_CHARS characters. */
  283. {
  284.     sprintf(string, "%d.%d", line+1, ch);
  285. }
  286.  
  287. /*
  288.  *----------------------------------------------------------------------
  289.  *
  290.  * TkTextRoundIndex --
  291.  *
  292.  *    Given a line index and a character index, this procedure
  293.  *    adjusts those positions if necessary to correspond to the
  294.  *    nearest actual character within the text.
  295.  *
  296.  * Results:
  297.  *    The return value is a pointer to the line structure for
  298.  *    the line of the text's B-tree that contains the indicated
  299.  *    character.  In addition, *lineIndexPtr and *chPtr are
  300.  *    modified if necessary to refer to an existing character
  301.  *    in the file.
  302.  *
  303.  * Side effects:
  304.  *    None.
  305.  *
  306.  *----------------------------------------------------------------------
  307.  */
  308.  
  309.  
  310. TkTextLine *
  311. TkTextRoundIndex(textPtr, lineIndexPtr, chPtr)
  312.     TkText *textPtr;            /* Information about text widget. */
  313.     int *lineIndexPtr;            /* Points to initial line index,
  314.                      * which is overwritten with actual
  315.                      * line index. */
  316.     int *chPtr;                /* Points to initial character index,
  317.                      * which is overwritten with actual
  318.                      * character index. */
  319. {
  320.     int line, ch, lastLine;
  321.     TkTextLine *linePtr;
  322.  
  323.     line = *lineIndexPtr;
  324.     ch = *chPtr;
  325.     if (line < 0) {
  326.     line = 0;
  327.     ch = 0;
  328.     }
  329.     lastLine = TkBTreeNumLines(textPtr->tree) - 1;
  330.     if (line > lastLine) {
  331.     line = lastLine;
  332.     linePtr = TkBTreeFindLine(textPtr->tree, line);
  333.     ch = linePtr->numBytes - 1;
  334.     } else {
  335.     linePtr = TkBTreeFindLine(textPtr->tree, line);
  336.     if (ch < 0) {
  337.         ch = 0;
  338.     }
  339.     if (ch >= linePtr->numBytes) {
  340.         if (line == lastLine) {
  341.         ch = linePtr->numBytes - 1;
  342.         } else {
  343.         line++;
  344.         linePtr = TkBTreeNextLine(linePtr);
  345.         ch = 0;
  346.         }
  347.     }
  348.     }
  349.     *lineIndexPtr = line;
  350.     *chPtr = ch;
  351.     return linePtr;
  352. }
  353.  
  354. /*
  355.  *----------------------------------------------------------------------
  356.  *
  357.  * ForwBack --
  358.  *
  359.  *    This procedure handles +/- modifiers for indices to adjust
  360.  *    the index forwards or backwards.
  361.  *
  362.  * Results:
  363.  *    If the modifier is successfully parsed then the return value
  364.  *    is the address of the first character after the modifier, and
  365.  *    *lineIndexPtr and *chPtr are updated to reflect the modifier.
  366.  *    If there is a syntax error in the modifier then NULL is returned.
  367.  *
  368.  * Side effects:
  369.  *    None.
  370.  *
  371.  *----------------------------------------------------------------------
  372.  */
  373.  
  374. static char *
  375. ForwBack(textPtr, string, lineIndexPtr, chPtr)
  376.     TkText *textPtr;        /* Information about widget that index
  377.                  * refers to. */
  378.     char *string;        /* String to parse for additional info
  379.                  * about modifier (count and units). 
  380.                  * Points to "+" or "-" that starts
  381.                  * modifier. */
  382.     int *lineIndexPtr;        /* Points to current line index, which will
  383.                  * be updated to reflect modifier. */
  384.     int *chPtr;            /* Points to current character index, which
  385.                  * will be updated to reflect modifier. */
  386. {
  387.     register char *p;
  388.     char *end, *units;
  389.     int count, length, lastLine;
  390.     TkTextLine *linePtr;
  391.  
  392.     /*
  393.      * Get the count (how many units forward or backward).
  394.      */
  395.  
  396.     p = string+1;
  397.     while (isspace(*p)) {
  398.     p++;
  399.     }
  400.     count = strtoul(p, &end, 0);
  401.     if (end == p) {
  402.     return NULL;
  403.     }
  404.     p = end;
  405.     while (isspace(*p)) {
  406.     p++;
  407.     }
  408.  
  409.     /*
  410.      * Find the end of this modifier (next space or + or - character),
  411.      * then parse the unit specifier and update the position
  412.      * accordingly.
  413.      */
  414.  
  415.     units = p; 
  416.     while ((*p != 0) && !isspace(*p) && (*p != '+') && (*p != '-')) {
  417.     p++;
  418.     }
  419.     length = p - units;
  420.     if ((*units == 'c') && (strncmp(units, "chars", length) == 0)) {
  421.     linePtr = TkTextRoundIndex(textPtr, lineIndexPtr, chPtr);
  422.     if (*string == '+') {
  423.         ForwardChars(textPtr, linePtr, lineIndexPtr, chPtr, count);
  424.     } else {
  425.         BackwardChars(textPtr, linePtr, lineIndexPtr, chPtr, count);
  426.     }
  427.     } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) {
  428.     if (*string == '+') {
  429.         *lineIndexPtr += count;
  430.         lastLine = TkBTreeNumLines(textPtr->tree) - 1;
  431.         if (*lineIndexPtr > lastLine) {
  432.         *lineIndexPtr = lastLine;
  433.         }
  434.     } else {
  435.         *lineIndexPtr -= count;
  436.         if (*lineIndexPtr < 0) {
  437.         *lineIndexPtr = 0;
  438.         }
  439.     }
  440.     linePtr = TkBTreeFindLine(textPtr->tree, *lineIndexPtr);
  441.     if (*chPtr >= linePtr->numBytes) {
  442.         *chPtr = linePtr->numBytes - 1;
  443.     }
  444.     if (*chPtr < 0) {
  445.         *chPtr = 0;
  446.     }
  447.     } else {
  448.     return NULL;
  449.     }
  450.     return p;
  451. }
  452.  
  453. /*
  454.  *----------------------------------------------------------------------
  455.  *
  456.  * ForwardChars --
  457.  *
  458.  *    Given a position in a text widget, this procedure computes
  459.  *    a new position that is "count" characters ahead of the given
  460.  *    position.
  461.  *
  462.  * Results:
  463.  *    *LineIndexPtr and *chPtr are overwritten with new values
  464.  *    corresponding to the new position.
  465.  *
  466.  * Side effects:
  467.  *    None.
  468.  *
  469.  *----------------------------------------------------------------------
  470.  */
  471.  
  472.     /* ARGSUSED */
  473. static void
  474. ForwardChars(textPtr, linePtr, lineIndexPtr, chPtr, count)
  475.     TkText *textPtr;            /* Information about text widget. */
  476.     register TkTextLine *linePtr;    /* Text line corresponding to
  477.                      * *lineIndexPtr. */
  478.     int *lineIndexPtr;            /* Points to initial line index,
  479.                      * which is overwritten with final
  480.                      * line index. */
  481.     int *chPtr;                /* Points to initial character index,
  482.                      * which is overwritten with final
  483.                      * character index. */
  484.     int count;                /* How many characters forward to
  485.                      * move.  Must not be negative. */
  486. {
  487.     TkTextLine *nextPtr;
  488.     int bytesInLine;
  489.  
  490.     while (count > 0) {
  491.     bytesInLine = linePtr->numBytes - *chPtr;
  492.     if (bytesInLine > count) {
  493.         *chPtr += count;
  494.         return;
  495.     }
  496.     nextPtr = TkBTreeNextLine(linePtr);
  497.     if (nextPtr == NULL) {
  498.         *chPtr = linePtr->numBytes - 1;
  499.         return;
  500.     }
  501.     *chPtr = 0;
  502.     *lineIndexPtr += 1;
  503.     linePtr = nextPtr;
  504.     count -= bytesInLine;
  505.     }
  506. }
  507.  
  508. /*
  509.  *----------------------------------------------------------------------
  510.  *
  511.  * BackwardChars --
  512.  *
  513.  *    Given a position in a text widget, this procedure computes
  514.  *    a new position that is "count" characters earlier than the given
  515.  *    position.
  516.  *
  517.  * Results:
  518.  *    *LineIndexPtr and *chPtr are overwritten with new values
  519.  *    corresponding to the new position.
  520.  *
  521.  * Side effects:
  522.  *    None.
  523.  *
  524.  *----------------------------------------------------------------------
  525.  */
  526.  
  527. static void
  528. BackwardChars(textPtr, linePtr, lineIndexPtr, chPtr, count)
  529.     TkText *textPtr;            /* Information about text widget. */
  530.     register TkTextLine *linePtr;    /* Text line corresponding to
  531.                      * *lineIndexPtr. */
  532.     int *lineIndexPtr;            /* Points to initial line index,
  533.                      * which is overwritten with final
  534.                      * line index. */
  535.     int *chPtr;                /* Points to initial character index,
  536.                      * which is overwritten with final
  537.                      * character index. */
  538.     int count;                /* How many characters backward to
  539.                      * move.  Must not be negative. */
  540. {
  541.     int bytesInLine;
  542.  
  543.     while (count > 0) {
  544.     bytesInLine = *chPtr;
  545.     if (bytesInLine >= count) {
  546.         *chPtr -= count;
  547.         return;
  548.     }
  549.     if (*lineIndexPtr <= 0) {
  550.         *chPtr = 0;
  551.         return;
  552.     }
  553.     *lineIndexPtr -= 1;
  554.     linePtr = TkBTreeFindLine(textPtr->tree, *lineIndexPtr);
  555.     count -= bytesInLine;
  556.     *chPtr = linePtr->numBytes;
  557.     }
  558. }
  559.  
  560. /*
  561.  *----------------------------------------------------------------------
  562.  *
  563.  * StartEnd --
  564.  *
  565.  *    This procedure handles modifiers like "wordstart" and "lineend"
  566.  *    to adjust indices forwards or backwards.
  567.  *
  568.  * Results:
  569.  *    If the modifier is successfully parsed then the return value
  570.  *    is the address of the first character after the modifier, and
  571.  *    *lineIndexPtr and *chPtr are updated to reflect the modifier.
  572.  *    If there is a syntax error in the modifier then NULL is returned.
  573.  *
  574.  * Side effects:
  575.  *    None.
  576.  *
  577.  *----------------------------------------------------------------------
  578.  */
  579.  
  580. static char *
  581. StartEnd(textPtr, string, lineIndexPtr, chPtr)
  582.     TkText *textPtr;        /* Information about widget that index
  583.                  * refers to. */
  584.     char *string;        /* String to parse for additional info
  585.                  * about modifier (count and units). 
  586.                  * Points to first character of modifer
  587.                  * word. */
  588.     int *lineIndexPtr;        /* Points to current line index, which will
  589.                  * be updated to reflect modifier. */
  590.     int *chPtr;            /* Points to current character index, which
  591.                  * will be updated to reflect modifier. */
  592. {
  593.     char *p, c;
  594.     int length;
  595.     register TkTextLine *linePtr;
  596.  
  597.     /*
  598.      * Find the end of the modifier word.
  599.      */
  600.  
  601.     for (p = string; isalnum(*p); p++) {
  602.     /* Empty loop body. */
  603.     }
  604.     length = p-string;
  605.     linePtr = TkTextRoundIndex(textPtr, lineIndexPtr, chPtr);
  606.     if ((*string == 'l') && (strncmp(string, "lineend", length) == 0)
  607.         && (length >= 5)) {
  608.     *chPtr = linePtr->numBytes - 1;
  609.     } else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0)
  610.         && (length >= 5)) {
  611.     *chPtr = 0;
  612.     } else if ((*string == 'w') && (strncmp(string, "wordend", length) == 0)
  613.         && (length >= 5)) {
  614.     c = linePtr->bytes[*chPtr];
  615.     if (!isalnum(c) && (c != '_')) {
  616.         if (*chPtr >= (linePtr->numBytes - 1)) {
  617.         /*
  618.          * End of line:  go to start of next line unless this is the
  619.          * last line in the text.
  620.          */
  621.  
  622.         if (TkBTreeNextLine(linePtr) != NULL) {
  623.             *lineIndexPtr += 1;
  624.             *chPtr = 0;
  625.         }
  626.         } else {
  627.         *chPtr += 1;
  628.         }
  629.     } else {
  630.         do {
  631.         *chPtr += 1;
  632.         c = linePtr->bytes[*chPtr];
  633.         } while (isalnum(c) || (c == '_'));
  634.     }
  635.     } else if ((*string == 'w') && (strncmp(string, "wordstart", length) == 0)
  636.         && (length >= 5)) {
  637.     c = linePtr->bytes[*chPtr];
  638.     if (isalnum(c) || (c == '_')) {
  639.         while (*chPtr > 0) {
  640.         c = linePtr->bytes[(*chPtr) - 1];
  641.         if (!isalnum(c) && (c != '_')) {
  642.             break;
  643.         }
  644.         *chPtr -= 1;
  645.         }
  646.     }
  647.     } else {
  648.     return NULL;
  649.     }
  650.     return p;
  651. }
  652.