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