1 /* Buffer-oriented functions
2 
3    Copyright (c) 1997-2014 Free Software Foundation, Inc.
4 
5    This file is part of GNU Zile.
6 
7    GNU Zile is free software; you can redistribute it and/or modify it
8    under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3, or (at your option)
10    any later version.
11 
12    GNU Zile is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with GNU Zile; see the file COPYING.  If not, write to the
19    Free Software Foundation, Fifth Floor, 51 Franklin Street, Boston,
20    MA 02111-1301, USA.  */
21 
22 #include <config.h>
23 
24 #include <assert.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <string.h>
28 #include "dirname.h"
29 
30 #include "main.h"
31 #include "extern.h"
32 #include "memrmem.h"
33 
34 
35 /*
36  * Buffer structure
37  */
38 struct Buffer
39 {
40 #define FIELD(ty, name) ty name;
41 #define FIELD_STR(name) char *name;
42 #include "buffer.h"
43 #undef FIELD
44 #undef FIELD_STR
45   size_t pt;         /* The point. */
46   estr text;         /* The text. */
47   size_t gap;        /* Size of gap after point. */
48 };
49 
50 #define FIELD(ty, field)                         \
51   GETTER (Buffer, buffer, ty, field)             \
52   SETTER (Buffer, buffer, ty, field)
53 
54 #define FIELD_STR(field)                         \
55   GETTER (Buffer, buffer, char *, field)         \
56   STR_SETTER (Buffer, buffer, field)
57 
58 #include "buffer.h"
59 #undef FIELD
60 #undef FIELD_STR
61 
62 /* Buffer methods that know about the gap. */
63 
64 void
set_buffer_text(Buffer bp,estr es)65 set_buffer_text (Buffer bp, estr es)
66 {
67   bp->text = es;
68 }
69 
70 const_astr
get_buffer_pre_point(Buffer bp)71 get_buffer_pre_point (Buffer bp)
72 {
73   return const_astr_new_nstr (astr_cstr (estr_get_as (bp->text)), bp->pt);
74 }
75 
76 const_astr
get_buffer_post_point(Buffer bp)77 get_buffer_post_point (Buffer bp)
78 {
79   size_t post_gap = bp->pt + bp->gap;
80   const_astr as = estr_get_as (bp->text);
81   return const_astr_new_nstr (astr_cstr (as) + post_gap, astr_len (as) - post_gap);
82 }
83 
84 size_t
get_buffer_pt(Buffer bp)85 get_buffer_pt (Buffer bp)
86 {
87   return bp->pt;
88 }
89 
90 static void
set_buffer_pt(Buffer bp,size_t o)91 set_buffer_pt (Buffer bp, size_t o)
92 {
93   if (o < bp->pt)
94     {
95       astr_move (estr_get_as (bp->text), o + bp->gap, o, bp->pt - o);
96       astr_set (estr_get_as (bp->text), o, '\0', MIN (bp->pt - o, bp->gap));
97     }
98   else if (o > bp->pt)
99     {
100       astr_move (estr_get_as (bp->text), bp->pt, bp->pt + bp->gap, o - bp->pt);
101       astr_set (estr_get_as (bp->text), o + bp->gap - MIN (o - bp->pt, bp->gap), '\0', MIN (o - bp->pt, bp->gap));
102     }
103   bp->pt = o;
104 }
105 
106 static inline size_t
realo_to_o(Buffer bp,size_t o)107 realo_to_o (Buffer bp, size_t o)
108 {
109   if (o == SIZE_MAX)
110     return o;
111   else if (o < bp->pt + bp->gap)
112     return MIN (o, bp->pt);
113   else
114     return o - bp->gap;
115 }
116 
117 static inline size_t
o_to_realo(Buffer bp,size_t o)118 o_to_realo (Buffer bp, size_t o)
119 {
120   return o < bp->pt ? o : o + bp->gap;
121 }
122 
123 size_t
get_buffer_size(Buffer bp)124 get_buffer_size (Buffer bp)
125 {
126   return realo_to_o (bp, astr_len (estr_get_as (bp->text)));
127 }
128 
129 size_t
buffer_line_len(Buffer bp,size_t o)130 buffer_line_len (Buffer bp, size_t o)
131 {
132   return realo_to_o (bp, estr_end_of_line (bp->text, o_to_realo (bp, o))) -
133     realo_to_o (bp, estr_start_of_line (bp->text, o_to_realo (bp, o)));
134 }
135 
136 /*
137  * Replace `del' chars after point with `es'.
138  */
139 #define MIN_GAP 1024 /* Minimum gap size after resize. */
140 #define MAX_GAP 4096 /* Maximum permitted gap size. */
141 bool
replace_estr(size_t del,const_estr es)142 replace_estr (size_t del, const_estr es)
143 {
144   if (warn_if_readonly_buffer ())
145     return false;
146 
147   size_t newlen = estr_len (es, get_buffer_eol (cur_bp));
148   undo_save_block (cur_bp->pt, del, newlen);
149 
150   /* Adjust gap. */
151   size_t oldgap = cur_bp->gap;
152   size_t added_gap = oldgap + del < newlen ? MIN_GAP : 0;
153   if (added_gap > 0)
154     { /* If gap would vanish, open it to MIN_GAP. */
155       astr_insert (estr_get_as (cur_bp->text), cur_bp->pt, (newlen + MIN_GAP) - (oldgap + del));
156       cur_bp->gap = MIN_GAP;
157     }
158   else if (oldgap + del > MAX_GAP + newlen)
159     { /* If gap would be larger than MAX_GAP, restrict it to MAX_GAP. */
160       astr_remove (estr_get_as (cur_bp->text), cur_bp->pt + newlen + MAX_GAP, (oldgap + del) - (MAX_GAP + newlen));
161       cur_bp->gap = MAX_GAP;
162     }
163   else
164     cur_bp->gap = oldgap + del - newlen;
165 
166   /* Zero any new bit of gap not produced by astr_insert. */
167   if (MAX (oldgap, newlen) + added_gap < cur_bp->gap + newlen)
168     astr_set (estr_get_as (cur_bp->text), cur_bp->pt + MAX (oldgap, newlen) + added_gap, '\0', newlen + cur_bp->gap - MAX (oldgap, newlen) - added_gap);
169 
170   /* Insert `newlen' chars. */
171   estr_replace_estr (cur_bp->text, cur_bp->pt, es);
172   cur_bp->pt += newlen;
173 
174   /* Adjust markers. */
175   for (Marker m = get_buffer_markers (cur_bp); m != NULL; m = get_marker_next (m))
176     if (get_marker_o (m) > cur_bp->pt - newlen)
177       set_marker_o (m, MAX (cur_bp->pt - newlen, get_marker_o (m) + newlen - del));
178 
179   set_buffer_modified (cur_bp, true);
180   if (estr_next_line (es, 0) != SIZE_MAX)
181     thisflag |= FLAG_NEED_RESYNC;
182   return true;
183 }
184 
185 bool
insert_estr(const_estr es)186 insert_estr (const_estr es)
187 {
188   return replace_estr (0, es);
189 }
190 
191 char
get_buffer_char(Buffer bp,size_t o)192 get_buffer_char (Buffer bp, size_t o)
193 {
194   return astr_get (estr_get_as (bp->text), o_to_realo (bp, o));
195 }
196 
197 size_t
buffer_prev_line(Buffer bp,size_t o)198 buffer_prev_line (Buffer bp, size_t o)
199 {
200   return realo_to_o (bp, estr_prev_line (bp->text, o_to_realo (bp, o)));
201 }
202 
203 size_t
buffer_next_line(Buffer bp,size_t o)204 buffer_next_line (Buffer bp, size_t o)
205 {
206   return realo_to_o (bp, estr_next_line (bp->text, o_to_realo (bp, o)));
207 }
208 
209 size_t
buffer_start_of_line(Buffer bp,size_t o)210 buffer_start_of_line (Buffer bp, size_t o)
211 {
212   return realo_to_o (bp, estr_start_of_line (bp->text, o_to_realo (bp, o)));
213 }
214 
215 size_t
buffer_end_of_line(Buffer bp,size_t o)216 buffer_end_of_line (Buffer bp, size_t o)
217 {
218   return realo_to_o (bp, estr_end_of_line (bp->text, o_to_realo (bp, o)));
219 }
220 
221 size_t
get_buffer_line_o(Buffer bp)222 get_buffer_line_o (Buffer bp)
223 {
224   return realo_to_o (bp, estr_start_of_line (bp->text, o_to_realo (bp, bp->pt)));
225 }
226 
227 
228 /* Buffer methods that don't know about the gap. */
229 
230 const char *
get_buffer_eol(Buffer bp)231 get_buffer_eol (Buffer bp)
232 {
233   return estr_get_eol (bp->text);
234 }
235 
236 /* Get the buffer region as an estr. */
237 estr
get_buffer_region(Buffer bp,Region r)238 get_buffer_region (Buffer bp, Region r)
239 {
240   astr as = astr_new ();
241   if (get_region_start (r) < bp->pt)
242     astr_cat (as, astr_substr (get_buffer_pre_point (bp), get_region_start (r), MIN (get_region_end (r), bp->pt) - get_region_start (r)));
243   if (get_region_end (r) > bp->pt)
244     {
245       size_t from = MAX (get_region_start (r), bp->pt);
246       astr_cat (as, astr_substr (get_buffer_post_point (bp), from - bp->pt, get_region_end (r) - from));
247     }
248   return estr_new (as, get_buffer_eol (bp));
249 }
250 
251 /*
252  * Insert the character `c' at point in the current buffer.
253  */
254 bool
insert_char(int c)255 insert_char (int c)
256 {
257   const char ch = (char) c;
258   return insert_estr (estr_new (const_astr_new_nstr (&ch, 1), coding_eol_lf));
259 }
260 
261 bool
delete_char(void)262 delete_char (void)
263 {
264   deactivate_mark ();
265 
266   if (eobp ())
267     {
268       minibuf_error ("End of buffer");
269       return false;
270     }
271 
272   if (warn_if_readonly_buffer ())
273     return false;
274 
275   if (eolp ())
276     {
277       replace_estr (strlen (get_buffer_eol (cur_bp)), estr_empty);
278       thisflag |= FLAG_NEED_RESYNC;
279     }
280   else
281     replace_estr (1, estr_empty);
282 
283   set_buffer_modified (cur_bp, true);
284 
285   return true;
286 }
287 
288 void
insert_buffer(Buffer bp)289 insert_buffer (Buffer bp)
290 {
291   /* Copy text to avoid problems when bp == cur_bp. */
292   insert_estr (estr_new (get_buffer_pre_point (bp), get_buffer_eol (bp)));
293   insert_estr (estr_new (get_buffer_post_point (bp), get_buffer_eol (bp)));
294 }
295 
296 /*
297  * Allocate a new buffer structure, set the default local
298  * variable values, and insert it into the buffer list.
299  */
300 Buffer
buffer_new(void)301 buffer_new (void)
302 {
303   Buffer bp = (Buffer) XZALLOC (struct Buffer);
304   bp->text = estr_new_astr (astr_new ());
305   bp->dir = agetcwd ();
306 
307   /* Insert into buffer list. */
308   bp->next = head_bp;
309   head_bp = bp;
310 
311   init_buffer (bp);
312 
313   return bp;
314 }
315 
316 /*
317  * Unchain the buffer's markers.
318  */
319 void
destroy_buffer(Buffer bp)320 destroy_buffer (Buffer bp)
321 {
322   while (bp->markers)
323     unchain_marker (bp->markers);
324 }
325 
326 /*
327  * Initialise a buffer
328  */
329 void
init_buffer(Buffer bp)330 init_buffer (Buffer bp)
331 {
332   if (get_variable_bool ("auto-fill-mode"))
333     set_buffer_autofill (bp, true);
334 }
335 
336 /*
337  * Get filename, or buffer name if NULL.
338  */
339 const char *
get_buffer_filename_or_name(Buffer bp)340 get_buffer_filename_or_name (Buffer bp)
341 {
342   const char *fname = get_buffer_filename (bp);
343   return fname ? fname : get_buffer_name (bp);
344 }
345 
346 /*
347  * Set a new filename, and from it a name, for the buffer.
348  */
349 void
set_buffer_names(Buffer bp,const char * filename)350 set_buffer_names (Buffer bp, const char *filename)
351 {
352   if (filename[0] != '/')
353     filename = astr_cstr (astr_fmt ("%s/%s", astr_cstr (agetcwd ()), filename));
354   set_buffer_filename (bp, filename);
355 
356   char *s = base_name (filename);
357   char *name = xstrdup (s);
358   /* Note: there can't be more than SIZE_MAX buffers. */
359   for (size_t i = 2; find_buffer (name) != NULL; i++)
360     name = xasprintf ("%s<%zu>", s, i);
361   set_buffer_name (bp, name);
362 }
363 
364 /*
365  * Search for a buffer named `name'.
366  */
367 Buffer
find_buffer(const char * name)368 find_buffer (const char *name)
369 {
370   for (Buffer bp = head_bp; bp != NULL; bp = bp->next)
371     {
372       const char *bname = get_buffer_name (bp);
373       if (bname && STREQ (bname, name))
374         return bp;
375     }
376 
377   return NULL;
378 }
379 
380 /*
381  * Move the given buffer to head.
382  */
383 static void
move_buffer_to_head(Buffer bp)384 move_buffer_to_head (Buffer bp)
385 {
386   Buffer prev = NULL;
387   for (Buffer it = head_bp; it != bp; prev = it, it = it->next)
388     ;
389   if (prev)
390     {
391       prev->next = bp->next;
392       bp->next = head_bp;
393       head_bp = bp;
394     }
395 }
396 
397 /*
398  * Switch to the specified buffer.
399  */
400 void
switch_to_buffer(Buffer bp)401 switch_to_buffer (Buffer bp)
402 {
403   assert (get_window_bp (cur_wp) == cur_bp);
404 
405   /* The buffer is the current buffer; return safely.  */
406   if (cur_bp == bp)
407     return;
408 
409   /* Set current buffer.  */
410   cur_bp = bp;
411   set_window_bp (cur_wp, cur_bp);
412 
413   /* Move the buffer to head.  */
414   move_buffer_to_head (bp);
415 
416   /* Change to buffer's default directory.  */
417   if (chdir (astr_cstr (bp->dir))) {
418     /* Avoid compiler warning for ignoring return value. */
419   }
420 
421   thisflag |= FLAG_NEED_RESYNC;
422 }
423 
424 /*
425  * Print an error message into the echo area and return true
426  * if the current buffer is readonly; otherwise return false.
427  */
428 bool
warn_if_readonly_buffer(void)429 warn_if_readonly_buffer (void)
430 {
431   if (get_buffer_readonly (cur_bp))
432     {
433       minibuf_error ("Buffer is readonly: %s", get_buffer_name (cur_bp));
434       return true;
435     }
436 
437   return false;
438 }
439 
440 bool
warn_if_no_mark(void)441 warn_if_no_mark (void)
442 {
443   if (!cur_bp->mark)
444     {
445       minibuf_error ("The mark is not set now");
446       return true;
447     }
448   else if (!get_buffer_mark_active (cur_bp))
449     {
450       minibuf_error ("The mark is not active now");
451       return true;
452     }
453   return false;
454 }
455 
456 /*
457  * Set the specified buffer temporary flag and move the buffer
458  * to the end of the buffer list.
459  */
460 void
set_temporary_buffer(Buffer bp)461 set_temporary_buffer (Buffer bp)
462 {
463   Buffer bp0;
464 
465   set_buffer_temporary (bp, true);
466 
467   if (bp == head_bp)
468     {
469       if (head_bp->next == NULL)
470         return;
471       head_bp = head_bp->next;
472     }
473   else if (bp->next == NULL)
474     return;
475 
476   for (bp0 = head_bp; bp0 != NULL; bp0 = bp0->next)
477     if (bp0->next == bp)
478       {
479         bp0->next = bp0->next->next;
480         break;
481       }
482 
483   for (bp0 = head_bp; bp0->next != NULL; bp0 = bp0->next)
484     ;
485 
486   bp0->next = bp;
487   bp->next = NULL;
488 }
489 
490 void
activate_mark(void)491 activate_mark (void)
492 {
493   set_buffer_mark_active (cur_bp, true);
494 }
495 
496 void
deactivate_mark(void)497 deactivate_mark (void)
498 {
499   set_buffer_mark_active (cur_bp, false);
500 }
501 
502 /*
503  * Return a safe tab width for the given buffer.
504  */
505 size_t
tab_width(Buffer bp)506 tab_width (Buffer bp)
507 {
508   long res = 0;
509   lisp_to_number (get_variable_bp (bp, "tab-width"), &res);
510   if (res < 1)
511     res = 8;
512   return res;
513 }
514 
515 Buffer
create_auto_buffer(const char * name)516 create_auto_buffer (const char *name)
517 {
518   Buffer bp = buffer_new ();
519   set_buffer_name (bp, name);
520   set_buffer_needname (bp, true);
521   set_buffer_temporary (bp, true);
522   set_buffer_nosave (bp, true);
523   return bp;
524 }
525 
526 Buffer
create_scratch_buffer(void)527 create_scratch_buffer (void)
528 {
529   return create_auto_buffer ("*scratch*");
530 }
531 
532 /*
533  * Remove the specified buffer from the buffer list and deallocate
534  * its space.  Recreate the scratch buffer when required.
535  */
536 void
kill_buffer(Buffer kill_bp)537 kill_buffer (Buffer kill_bp)
538 {
539   Buffer next_bp;
540 
541   if (get_buffer_next (kill_bp) != NULL)
542     next_bp = get_buffer_next (kill_bp);
543   else
544     next_bp = (head_bp == kill_bp) ? NULL : head_bp;
545 
546   /* Search for windows displaying the buffer to kill. */
547   for (Window wp = head_wp; wp != NULL; wp = get_window_next (wp))
548     if (get_window_bp (wp) == kill_bp)
549       {
550         set_window_bp (wp, next_bp);
551         set_window_topdelta (wp, 0);
552         set_window_saved_pt (wp, NULL);
553       }
554 
555   /* Remove the buffer from the buffer list. */
556   if (cur_bp == kill_bp)
557     cur_bp = next_bp;
558   if (head_bp == kill_bp)
559     head_bp = get_buffer_next (head_bp);
560   for (Buffer bp = head_bp; bp != NULL && get_buffer_next (bp) != NULL; bp = get_buffer_next (bp))
561     if (get_buffer_next (bp) == kill_bp)
562       {
563         set_buffer_next (bp, get_buffer_next (get_buffer_next (bp)));
564         break;
565       }
566 
567   destroy_buffer (kill_bp);
568 
569   /* If no buffers left, recreate scratch buffer and point windows at
570      it. */
571   if (next_bp == NULL)
572     {
573       cur_bp = head_bp = next_bp = create_scratch_buffer ();
574       for (Window wp = head_wp; wp != NULL; wp = get_window_next (wp))
575         set_window_bp (wp, head_bp);
576     }
577 
578   /* Resync windows that need it. */
579   for (Window wp = head_wp; wp != NULL; wp = get_window_next (wp))
580     if (get_window_bp (wp) == next_bp)
581       window_resync (wp);
582 }
583 
584 DEFUN_ARGS ("kill-buffer", kill_buffer,
585             STR_ARG (buf))
586 /*+
587 Kill buffer BUFFER.
588 With a nil argument, kill the current buffer.
589 +*/
590 {
591   Buffer bp;
592 
593   STR_INIT (buf)
594   else
595     {
596       Completion cp = make_buffer_completion ();
597       buf = minibuf_read_completion ("Kill buffer (default %s): ",
598                                      "", cp, NULL, get_buffer_name (cur_bp));
599       if (buf == NULL)
600         ok = FUNCALL (keyboard_quit);
601     }
602 
603   if (buf && astr_len (buf) > 0)
604     {
605       bp = find_buffer (astr_cstr (buf));
606       if (bp == NULL)
607         {
608           minibuf_error ("Buffer `%s' not found", astr_cstr (buf));
609           ok = leNIL;
610         }
611     }
612   else
613     bp = cur_bp;
614 
615   if (ok == leT)
616     {
617       if (!check_modified_buffer (bp))
618         ok = leNIL;
619       else
620         kill_buffer (bp);
621     }
622 }
623 END_DEFUN
624 
625 Completion
make_buffer_completion(void)626 make_buffer_completion (void)
627 {
628   Completion cp = completion_new (false);
629   for (Buffer bp = head_bp; bp != NULL; bp = get_buffer_next (bp))
630     gl_sortedlist_add (get_completion_completions (cp), completion_strcmp,
631                        xstrdup (get_buffer_name (bp)));
632 
633   return cp;
634 }
635 
636 /*
637  * Check if the buffer has been modified.  If so, asks the user if
638  * he/she wants to save the changes.  If the response is positive, return
639  * true, else false.
640  */
641 bool
check_modified_buffer(Buffer bp)642 check_modified_buffer (Buffer bp)
643 {
644   if (get_buffer_modified (bp) && !get_buffer_nosave (bp))
645     for (;;)
646       {
647         int ans = minibuf_read_yesno
648           ("Buffer %s modified; kill anyway? (yes or no) ", get_buffer_name (bp));
649         if (ans == -1)
650           {
651             FUNCALL (keyboard_quit);
652             return false;
653           }
654         else if (!ans)
655           return false;
656         break;
657       }
658 
659   return true;
660 }
661 
662 
663 /* Basic movement routines */
664 
665 bool
move_char(ptrdiff_t offset)666 move_char (ptrdiff_t offset)
667 {
668   int dir = offset >= 0 ? 1 : -1;
669   for (size_t i = 0; i < (size_t) (abs (offset)); i++)
670     {
671       if (dir > 0 ? !eolp () : !bolp ())
672         set_buffer_pt (cur_bp, cur_bp->pt + dir);
673       else if (dir > 0 ? !eobp () : !bobp ())
674         {
675           thisflag |= FLAG_NEED_RESYNC;
676           set_buffer_pt (cur_bp, cur_bp->pt + dir * strlen (get_buffer_eol (cur_bp)));
677           if (dir > 0)
678             FUNCALL (beginning_of_line);
679           else
680             FUNCALL (end_of_line);
681         }
682       else
683         return false;
684     }
685 
686   return true;
687 }
688 
689 /*
690  * Go to the goal column.  Take care of expanding tabulations.
691  */
692 static void
goto_goalc(void)693 goto_goalc (void)
694 {
695   size_t i, col = 0, t = tab_width (cur_bp);
696 
697   for (i = get_buffer_line_o (cur_bp);
698        i < get_buffer_line_o (cur_bp) + buffer_line_len (cur_bp, get_buffer_pt (cur_bp));
699        i++)
700     if (col == get_buffer_goalc (cur_bp))
701       break;
702     else if (get_buffer_char (cur_bp, i) == '\t')
703       for (size_t w = t - col % t; w > 0 && ++col < get_buffer_goalc (cur_bp); w--)
704         ;
705     else
706       ++col;
707 
708   set_buffer_pt (cur_bp, i);
709 }
710 
711 bool
move_line(ptrdiff_t n)712 move_line (ptrdiff_t n)
713 {
714   size_t (*func) (Buffer bp, size_t o) = buffer_next_line;
715   if (n < 0)
716     {
717       n = -n;
718       func = buffer_prev_line;
719     }
720 
721   if (last_command () != F_next_line && last_command () != F_previous_line)
722     set_buffer_goalc (cur_bp, get_goalc ());
723 
724   for (; n > 0; n--)
725     {
726       size_t o = func (cur_bp, cur_bp->pt);
727       if (o == SIZE_MAX)
728         break;
729       set_buffer_pt (cur_bp, o);
730     }
731 
732   goto_goalc ();
733   thisflag |= FLAG_NEED_RESYNC;
734 
735   return n == 0;
736 }
737 
738 size_t
offset_to_line(Buffer bp,size_t offset)739 offset_to_line (Buffer bp, size_t offset)
740 {
741   size_t n = 0;
742   for (size_t o = 0; buffer_end_of_line (bp, o) < offset; o = buffer_next_line (bp, o))
743     n++;
744   return n;
745 }
746 
747 void
goto_offset(size_t o)748 goto_offset (size_t o)
749 {
750   size_t old_lineo = get_buffer_line_o (cur_bp);
751   set_buffer_pt (cur_bp, o);
752   if (get_buffer_line_o (cur_bp) != old_lineo)
753     {
754       set_buffer_goalc (cur_bp, get_goalc ());
755       thisflag |= FLAG_NEED_RESYNC;
756     }
757 }
758