1 /* SPDX-License-Identifier: BSD-3-Clause
2 * Copyright(c) 2010-2014 Intel Corporation.
3 * Copyright (c) 2009, Olivier MATZ <zer0@droids-corp.org>
4 * All rights reserved.
5 */
6
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include <string.h>
10 #include <errno.h>
11 #include <ctype.h>
12
13 #include "cmdline_cirbuf.h"
14 #include "cmdline_private.h"
15 #include "cmdline_rdline.h"
16
17 static void rdline_puts(struct rdline *rdl, const char *buf);
18 static void rdline_miniprintf(struct rdline *rdl,
19 const char *buf, unsigned int val);
20
21 static void rdline_remove_old_history_item(struct rdline *rdl);
22 static void rdline_remove_first_history_item(struct rdline *rdl);
23 static unsigned int rdline_get_history_size(struct rdline *rdl);
24
25
26 /* isblank() needs _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE, so use our
27 * own. */
28 static int
isblank2(char c)29 isblank2(char c)
30 {
31 if (c == ' ' ||
32 c == '\t' )
33 return 1;
34 return 0;
35 }
36
37 int
rdline_init(struct rdline * rdl,rdline_write_char_t * write_char,rdline_validate_t * validate,rdline_complete_t * complete,void * opaque)38 rdline_init(struct rdline *rdl,
39 rdline_write_char_t *write_char,
40 rdline_validate_t *validate,
41 rdline_complete_t *complete,
42 void *opaque)
43 {
44 if (!rdl || !write_char || !validate || !complete)
45 return -EINVAL;
46 memset(rdl, 0, sizeof(*rdl));
47 rdl->validate = validate;
48 rdl->complete = complete;
49 rdl->write_char = write_char;
50 rdl->opaque = opaque;
51 rdl->status = RDLINE_INIT;
52 return cirbuf_init(&rdl->history, rdl->history_buf, 0, RDLINE_HISTORY_BUF_SIZE);
53 }
54
55 struct rdline *
rdline_new(rdline_write_char_t * write_char,rdline_validate_t * validate,rdline_complete_t * complete,void * opaque)56 rdline_new(rdline_write_char_t *write_char,
57 rdline_validate_t *validate,
58 rdline_complete_t *complete,
59 void *opaque)
60 {
61 struct rdline *rdl;
62
63 rdl = malloc(sizeof(*rdl));
64 if (rdline_init(rdl, write_char, validate, complete, opaque) < 0) {
65 free(rdl);
66 rdl = NULL;
67 }
68 return rdl;
69 }
70
71 void
rdline_free(struct rdline * rdl)72 rdline_free(struct rdline *rdl)
73 {
74 free(rdl);
75 }
76
77 void
rdline_newline(struct rdline * rdl,const char * prompt)78 rdline_newline(struct rdline *rdl, const char *prompt)
79 {
80 unsigned int i;
81
82 if (!rdl || !prompt)
83 return;
84
85 vt100_init(&rdl->vt100);
86 cirbuf_init(&rdl->left, rdl->left_buf, 0, RDLINE_BUF_SIZE);
87 cirbuf_init(&rdl->right, rdl->right_buf, 0, RDLINE_BUF_SIZE);
88
89 rdl->prompt_size = strnlen(prompt, RDLINE_PROMPT_SIZE-1);
90 if (prompt != rdl->prompt)
91 memcpy(rdl->prompt, prompt, rdl->prompt_size);
92 rdl->prompt[RDLINE_PROMPT_SIZE-1] = '\0';
93
94 for (i=0 ; i<rdl->prompt_size ; i++)
95 rdl->write_char(rdl, rdl->prompt[i]);
96 rdl->status = RDLINE_RUNNING;
97
98 rdl->history_cur_line = -1;
99 }
100
101 void
rdline_stop(struct rdline * rdl)102 rdline_stop(struct rdline *rdl)
103 {
104 if (!rdl)
105 return;
106 rdl->status = RDLINE_INIT;
107 }
108
109 void
rdline_quit(struct rdline * rdl)110 rdline_quit(struct rdline *rdl)
111 {
112 if (!rdl)
113 return;
114 rdl->status = RDLINE_EXITED;
115 }
116
117 void
rdline_restart(struct rdline * rdl)118 rdline_restart(struct rdline *rdl)
119 {
120 if (!rdl)
121 return;
122 rdl->status = RDLINE_RUNNING;
123 }
124
125 void
rdline_reset(struct rdline * rdl)126 rdline_reset(struct rdline *rdl)
127 {
128 if (!rdl)
129 return;
130 vt100_init(&rdl->vt100);
131 cirbuf_init(&rdl->left, rdl->left_buf, 0, RDLINE_BUF_SIZE);
132 cirbuf_init(&rdl->right, rdl->right_buf, 0, RDLINE_BUF_SIZE);
133
134 rdl->status = RDLINE_RUNNING;
135
136 rdl->history_cur_line = -1;
137 }
138
139 const char *
rdline_get_buffer(struct rdline * rdl)140 rdline_get_buffer(struct rdline *rdl)
141 {
142 if (!rdl)
143 return NULL;
144 unsigned int len_l, len_r;
145 cirbuf_align_left(&rdl->left);
146 cirbuf_align_left(&rdl->right);
147
148 len_l = CIRBUF_GET_LEN(&rdl->left);
149 len_r = CIRBUF_GET_LEN(&rdl->right);
150 memcpy(rdl->left_buf+len_l, rdl->right_buf, len_r);
151
152 rdl->left_buf[len_l + len_r] = '\n';
153 rdl->left_buf[len_l + len_r + 1] = '\0';
154 return rdl->left_buf;
155 }
156
157 static void
display_right_buffer(struct rdline * rdl,int force)158 display_right_buffer(struct rdline *rdl, int force)
159 {
160 unsigned int i;
161 char tmp;
162
163 if (!force && CIRBUF_IS_EMPTY(&rdl->right))
164 return;
165
166 rdline_puts(rdl, vt100_clear_right);
167 CIRBUF_FOREACH(&rdl->right, i, tmp) {
168 rdl->write_char(rdl, tmp);
169 }
170 if (!CIRBUF_IS_EMPTY(&rdl->right))
171 rdline_miniprintf(rdl, vt100_multi_left,
172 CIRBUF_GET_LEN(&rdl->right));
173 }
174
175 void
rdline_redisplay(struct rdline * rdl)176 rdline_redisplay(struct rdline *rdl)
177 {
178 unsigned int i;
179 char tmp;
180
181 if (!rdl)
182 return;
183
184 rdline_puts(rdl, vt100_home);
185 for (i=0 ; i<rdl->prompt_size ; i++)
186 rdl->write_char(rdl, rdl->prompt[i]);
187 CIRBUF_FOREACH(&rdl->left, i, tmp) {
188 rdl->write_char(rdl, tmp);
189 }
190 display_right_buffer(rdl, 1);
191 }
192
193 int
rdline_char_in(struct rdline * rdl,char c)194 rdline_char_in(struct rdline *rdl, char c)
195 {
196 unsigned int i;
197 int cmd;
198 char tmp;
199 char *buf;
200
201 if (!rdl)
202 return -EINVAL;
203
204 if (rdl->status == RDLINE_EXITED)
205 return RDLINE_RES_EXITED;
206 if (rdl->status != RDLINE_RUNNING)
207 return RDLINE_RES_NOT_RUNNING;
208
209 cmd = vt100_parser(&rdl->vt100, c);
210 if (cmd == -2)
211 return RDLINE_RES_SUCCESS;
212
213 if (cmd >= 0) {
214 switch (cmd) {
215 /* move caret 1 char to the left */
216 case CMDLINE_KEY_CTRL_B:
217 case CMDLINE_KEY_LEFT_ARR:
218 if (CIRBUF_IS_EMPTY(&rdl->left))
219 break;
220 tmp = cirbuf_get_tail(&rdl->left);
221 cirbuf_del_tail(&rdl->left);
222 cirbuf_add_head(&rdl->right, tmp);
223 rdline_puts(rdl, vt100_left_arr);
224 break;
225
226 /* move caret 1 char to the right */
227 case CMDLINE_KEY_CTRL_F:
228 case CMDLINE_KEY_RIGHT_ARR:
229 if (CIRBUF_IS_EMPTY(&rdl->right))
230 break;
231 tmp = cirbuf_get_head(&rdl->right);
232 cirbuf_del_head(&rdl->right);
233 cirbuf_add_tail(&rdl->left, tmp);
234 rdline_puts(rdl, vt100_right_arr);
235 break;
236
237 /* move caret 1 word to the left */
238 /* keyboard equivalent: Alt+B */
239 case CMDLINE_KEY_WLEFT:
240 while (! CIRBUF_IS_EMPTY(&rdl->left) &&
241 (tmp = cirbuf_get_tail(&rdl->left)) &&
242 isblank2(tmp)) {
243 rdline_puts(rdl, vt100_left_arr);
244 cirbuf_del_tail(&rdl->left);
245 cirbuf_add_head(&rdl->right, tmp);
246 }
247 while (! CIRBUF_IS_EMPTY(&rdl->left) &&
248 (tmp = cirbuf_get_tail(&rdl->left)) &&
249 !isblank2(tmp)) {
250 rdline_puts(rdl, vt100_left_arr);
251 cirbuf_del_tail(&rdl->left);
252 cirbuf_add_head(&rdl->right, tmp);
253 }
254 break;
255
256 /* move caret 1 word to the right */
257 /* keyboard equivalent: Alt+F */
258 case CMDLINE_KEY_WRIGHT:
259 while (! CIRBUF_IS_EMPTY(&rdl->right) &&
260 (tmp = cirbuf_get_head(&rdl->right)) &&
261 isblank2(tmp)) {
262 rdline_puts(rdl, vt100_right_arr);
263 cirbuf_del_head(&rdl->right);
264 cirbuf_add_tail(&rdl->left, tmp);
265 }
266 while (! CIRBUF_IS_EMPTY(&rdl->right) &&
267 (tmp = cirbuf_get_head(&rdl->right)) &&
268 !isblank2(tmp)) {
269 rdline_puts(rdl, vt100_right_arr);
270 cirbuf_del_head(&rdl->right);
271 cirbuf_add_tail(&rdl->left, tmp);
272 }
273 break;
274
275 /* move caret to the left */
276 case CMDLINE_KEY_CTRL_A:
277 if (CIRBUF_IS_EMPTY(&rdl->left))
278 break;
279 rdline_miniprintf(rdl, vt100_multi_left,
280 CIRBUF_GET_LEN(&rdl->left));
281 while (! CIRBUF_IS_EMPTY(&rdl->left)) {
282 tmp = cirbuf_get_tail(&rdl->left);
283 cirbuf_del_tail(&rdl->left);
284 cirbuf_add_head(&rdl->right, tmp);
285 }
286 break;
287
288 /* move caret to the right */
289 case CMDLINE_KEY_CTRL_E:
290 if (CIRBUF_IS_EMPTY(&rdl->right))
291 break;
292 rdline_miniprintf(rdl, vt100_multi_right,
293 CIRBUF_GET_LEN(&rdl->right));
294 while (! CIRBUF_IS_EMPTY(&rdl->right)) {
295 tmp = cirbuf_get_head(&rdl->right);
296 cirbuf_del_head(&rdl->right);
297 cirbuf_add_tail(&rdl->left, tmp);
298 }
299 break;
300
301 /* delete 1 char from the left */
302 case CMDLINE_KEY_BKSPACE:
303 case CMDLINE_KEY_BKSPACE2:
304 if(!cirbuf_del_tail_safe(&rdl->left)) {
305 rdline_puts(rdl, vt100_bs);
306 display_right_buffer(rdl, 1);
307 }
308 break;
309
310 /* delete 1 char from the right */
311 case CMDLINE_KEY_SUPPR:
312 case CMDLINE_KEY_CTRL_D:
313 if (cmd == CMDLINE_KEY_CTRL_D &&
314 CIRBUF_IS_EMPTY(&rdl->left) &&
315 CIRBUF_IS_EMPTY(&rdl->right)) {
316 return RDLINE_RES_EOF;
317 }
318 if (!cirbuf_del_head_safe(&rdl->right)) {
319 display_right_buffer(rdl, 1);
320 }
321 break;
322
323 /* delete 1 word from the left */
324 case CMDLINE_KEY_META_BKSPACE:
325 case CMDLINE_KEY_CTRL_W:
326 while (! CIRBUF_IS_EMPTY(&rdl->left) && isblank2(cirbuf_get_tail(&rdl->left))) {
327 rdline_puts(rdl, vt100_bs);
328 cirbuf_del_tail(&rdl->left);
329 }
330 while (! CIRBUF_IS_EMPTY(&rdl->left) && !isblank2(cirbuf_get_tail(&rdl->left))) {
331 rdline_puts(rdl, vt100_bs);
332 cirbuf_del_tail(&rdl->left);
333 }
334 display_right_buffer(rdl, 1);
335 break;
336
337 /* delete 1 word from the right */
338 case CMDLINE_KEY_META_D:
339 while (! CIRBUF_IS_EMPTY(&rdl->right) && isblank2(cirbuf_get_head(&rdl->right)))
340 cirbuf_del_head(&rdl->right);
341 while (! CIRBUF_IS_EMPTY(&rdl->right) && !isblank2(cirbuf_get_head(&rdl->right)))
342 cirbuf_del_head(&rdl->right);
343 display_right_buffer(rdl, 1);
344 break;
345
346 /* set kill buffer to contents on the right side of caret */
347 case CMDLINE_KEY_CTRL_K:
348 cirbuf_get_buf_head(&rdl->right, rdl->kill_buf, RDLINE_BUF_SIZE);
349 rdl->kill_size = CIRBUF_GET_LEN(&rdl->right);
350 cirbuf_del_buf_head(&rdl->right, rdl->kill_size);
351 rdline_puts(rdl, vt100_clear_right);
352 break;
353
354 /* paste contents of kill buffer to the left side of caret */
355 case CMDLINE_KEY_CTRL_Y:
356 i=0;
357 while(CIRBUF_GET_LEN(&rdl->right) + CIRBUF_GET_LEN(&rdl->left) <
358 RDLINE_BUF_SIZE &&
359 i < rdl->kill_size) {
360 cirbuf_add_tail(&rdl->left, rdl->kill_buf[i]);
361 rdl->write_char(rdl, rdl->kill_buf[i]);
362 i++;
363 }
364 display_right_buffer(rdl, 0);
365 break;
366
367 /* clear and newline */
368 case CMDLINE_KEY_CTRL_C:
369 rdline_puts(rdl, "\r\n");
370 rdline_newline(rdl, rdl->prompt);
371 break;
372
373 /* redisplay (helps when prompt is lost in other output) */
374 case CMDLINE_KEY_CTRL_L:
375 rdline_redisplay(rdl);
376 break;
377
378 /* autocomplete */
379 case CMDLINE_KEY_TAB:
380 case CMDLINE_KEY_HELP:
381 cirbuf_align_left(&rdl->left);
382 rdl->left_buf[CIRBUF_GET_LEN(&rdl->left)] = '\0';
383 if (rdl->complete) {
384 char tmp_buf[BUFSIZ];
385 int complete_state;
386 int ret;
387 unsigned int tmp_size;
388
389 if (cmd == CMDLINE_KEY_TAB)
390 complete_state = 0;
391 else
392 complete_state = -1;
393
394 /* see in parse.h for help on complete() */
395 ret = rdl->complete(rdl, rdl->left_buf,
396 tmp_buf, sizeof(tmp_buf),
397 &complete_state);
398 /* no completion or error */
399 if (ret <= 0) {
400 return RDLINE_RES_COMPLETE;
401 }
402
403 tmp_size = strnlen(tmp_buf, sizeof(tmp_buf));
404 /* add chars */
405 if (ret == RDLINE_RES_COMPLETE) {
406 i=0;
407 while(CIRBUF_GET_LEN(&rdl->right) + CIRBUF_GET_LEN(&rdl->left) <
408 RDLINE_BUF_SIZE &&
409 i < tmp_size) {
410 cirbuf_add_tail(&rdl->left, tmp_buf[i]);
411 rdl->write_char(rdl, tmp_buf[i]);
412 i++;
413 }
414 display_right_buffer(rdl, 1);
415 return RDLINE_RES_COMPLETE; /* ?? */
416 }
417
418 /* choice */
419 rdline_puts(rdl, "\r\n");
420 while (ret) {
421 rdl->write_char(rdl, ' ');
422 for (i=0 ; tmp_buf[i] ; i++)
423 rdl->write_char(rdl, tmp_buf[i]);
424 rdline_puts(rdl, "\r\n");
425 ret = rdl->complete(rdl, rdl->left_buf,
426 tmp_buf, sizeof(tmp_buf),
427 &complete_state);
428 }
429
430 rdline_redisplay(rdl);
431 }
432 return RDLINE_RES_COMPLETE;
433
434 /* complete buffer */
435 case CMDLINE_KEY_RETURN:
436 case CMDLINE_KEY_RETURN2:
437 rdline_get_buffer(rdl);
438 rdl->status = RDLINE_INIT;
439 rdline_puts(rdl, "\r\n");
440 if (rdl->history_cur_line != -1)
441 rdline_remove_first_history_item(rdl);
442
443 if (rdl->validate)
444 rdl->validate(rdl, rdl->left_buf, CIRBUF_GET_LEN(&rdl->left)+2);
445 /* user may have stopped rdline */
446 if (rdl->status == RDLINE_EXITED)
447 return RDLINE_RES_EXITED;
448 return RDLINE_RES_VALIDATED;
449
450 /* previous element in history */
451 case CMDLINE_KEY_UP_ARR:
452 case CMDLINE_KEY_CTRL_P:
453 if (rdl->history_cur_line == 0) {
454 rdline_remove_first_history_item(rdl);
455 }
456 if (rdl->history_cur_line <= 0) {
457 rdline_add_history(rdl, rdline_get_buffer(rdl));
458 rdl->history_cur_line = 0;
459 }
460
461 buf = rdline_get_history_item(rdl, rdl->history_cur_line + 1);
462 if (!buf)
463 break;
464
465 rdl->history_cur_line ++;
466 vt100_init(&rdl->vt100);
467 cirbuf_init(&rdl->left, rdl->left_buf, 0, RDLINE_BUF_SIZE);
468 cirbuf_init(&rdl->right, rdl->right_buf, 0, RDLINE_BUF_SIZE);
469 cirbuf_add_buf_tail(&rdl->left, buf, strnlen(buf, RDLINE_BUF_SIZE));
470 rdline_redisplay(rdl);
471 break;
472
473 /* next element in history */
474 case CMDLINE_KEY_DOWN_ARR:
475 case CMDLINE_KEY_CTRL_N:
476 if (rdl->history_cur_line - 1 < 0)
477 break;
478
479 rdl->history_cur_line --;
480 buf = rdline_get_history_item(rdl, rdl->history_cur_line);
481 if (!buf)
482 break;
483 vt100_init(&rdl->vt100);
484 cirbuf_init(&rdl->left, rdl->left_buf, 0, RDLINE_BUF_SIZE);
485 cirbuf_init(&rdl->right, rdl->right_buf, 0, RDLINE_BUF_SIZE);
486 cirbuf_add_buf_tail(&rdl->left, buf, strnlen(buf, RDLINE_BUF_SIZE));
487 rdline_redisplay(rdl);
488
489 break;
490
491
492 default:
493 break;
494 }
495
496 return RDLINE_RES_SUCCESS;
497 }
498
499 if (!isprint((int)c))
500 return RDLINE_RES_SUCCESS;
501
502 /* standard chars */
503 if (CIRBUF_GET_LEN(&rdl->left) + CIRBUF_GET_LEN(&rdl->right) >= RDLINE_BUF_SIZE)
504 return RDLINE_RES_SUCCESS;
505
506 if (cirbuf_add_tail_safe(&rdl->left, c))
507 return RDLINE_RES_SUCCESS;
508
509 rdl->write_char(rdl, c);
510 display_right_buffer(rdl, 0);
511
512 return RDLINE_RES_SUCCESS;
513 }
514
515
516 /* HISTORY */
517
518 static void
rdline_remove_old_history_item(struct rdline * rdl)519 rdline_remove_old_history_item(struct rdline * rdl)
520 {
521 char tmp;
522
523 while (! CIRBUF_IS_EMPTY(&rdl->history) ) {
524 tmp = cirbuf_get_head(&rdl->history);
525 cirbuf_del_head(&rdl->history);
526 if (!tmp)
527 break;
528 }
529 }
530
531 static void
rdline_remove_first_history_item(struct rdline * rdl)532 rdline_remove_first_history_item(struct rdline * rdl)
533 {
534 char tmp;
535
536 if ( CIRBUF_IS_EMPTY(&rdl->history) ) {
537 return;
538 }
539 else {
540 cirbuf_del_tail(&rdl->history);
541 }
542
543 while (! CIRBUF_IS_EMPTY(&rdl->history) ) {
544 tmp = cirbuf_get_tail(&rdl->history);
545 if (!tmp)
546 break;
547 cirbuf_del_tail(&rdl->history);
548 }
549 }
550
551 static unsigned int
rdline_get_history_size(struct rdline * rdl)552 rdline_get_history_size(struct rdline * rdl)
553 {
554 unsigned int i, tmp, ret=0;
555
556 CIRBUF_FOREACH(&rdl->history, i, tmp) {
557 if (tmp == 0)
558 ret ++;
559 }
560
561 return ret;
562 }
563
564 char *
rdline_get_history_item(struct rdline * rdl,unsigned int idx)565 rdline_get_history_item(struct rdline * rdl, unsigned int idx)
566 {
567 unsigned int len, i, tmp;
568
569 if (!rdl)
570 return NULL;
571
572 len = rdline_get_history_size(rdl);
573 if ( idx >= len ) {
574 return NULL;
575 }
576
577 cirbuf_align_left(&rdl->history);
578
579 CIRBUF_FOREACH(&rdl->history, i, tmp) {
580 if ( idx == len - 1) {
581 return rdl->history_buf + i;
582 }
583 if (tmp == 0)
584 len --;
585 }
586
587 return NULL;
588 }
589
590 size_t
rdline_get_history_buffer_size(struct rdline * rdl)591 rdline_get_history_buffer_size(struct rdline *rdl)
592 {
593 return sizeof(rdl->history_buf);
594 }
595
596 void *
rdline_get_opaque(struct rdline * rdl)597 rdline_get_opaque(struct rdline *rdl)
598 {
599 return rdl != NULL ? rdl->opaque : NULL;
600 }
601
602 int
rdline_add_history(struct rdline * rdl,const char * buf)603 rdline_add_history(struct rdline * rdl, const char * buf)
604 {
605 unsigned int len, i;
606
607 if (!rdl || !buf)
608 return -EINVAL;
609
610 len = strnlen(buf, RDLINE_BUF_SIZE);
611 for (i=0; i<len ; i++) {
612 if (buf[i] == '\n') {
613 len = i;
614 break;
615 }
616 }
617
618 if ( len >= RDLINE_HISTORY_BUF_SIZE )
619 return -1;
620
621 while ( len >= CIRBUF_GET_FREELEN(&rdl->history) ) {
622 rdline_remove_old_history_item(rdl);
623 }
624
625 cirbuf_add_buf_tail(&rdl->history, buf, len);
626 cirbuf_add_tail(&rdl->history, 0);
627
628 return 0;
629 }
630
631 void
rdline_clear_history(struct rdline * rdl)632 rdline_clear_history(struct rdline * rdl)
633 {
634 if (!rdl)
635 return;
636 cirbuf_init(&rdl->history, rdl->history_buf, 0, RDLINE_HISTORY_BUF_SIZE);
637 }
638
639
640 /* STATIC USEFUL FUNCS */
641
642 static void
rdline_puts(struct rdline * rdl,const char * buf)643 rdline_puts(struct rdline * rdl, const char * buf)
644 {
645 char c;
646 while ( (c = *(buf++)) != '\0' ) {
647 rdl->write_char(rdl, c);
648 }
649 }
650
651 /* a very very basic printf with one arg and one format 'u' */
652 static void
rdline_miniprintf(struct rdline * rdl,const char * buf,unsigned int val)653 rdline_miniprintf(struct rdline *rdl, const char * buf, unsigned int val)
654 {
655 char c, started=0, div=100;
656
657 while ( (c=*(buf++)) ) {
658 if (c != '%') {
659 rdl->write_char(rdl, c);
660 continue;
661 }
662 c = *(buf++);
663 if (c != 'u') {
664 rdl->write_char(rdl, '%');
665 rdl->write_char(rdl, c);
666 continue;
667 }
668 /* val is never more than 255 */
669 while (div) {
670 c = (char)(val / div);
671 if (c || started) {
672 rdl->write_char(rdl, (char)(c+'0'));
673 started = 1;
674 }
675 val %= div;
676 div /= 10;
677 }
678 }
679 }
680