Print this page
5396 gcc 4.8.2 longjmp errors for cscope-fast
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/lib/libcurses/screen/tparm.c
+++ new/usr/src/lib/libcurses/screen/tparm.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License, Version 1.0 only
6 6 * (the "License"). You may not use this file except in compliance
7 7 * with the License.
8 8 *
9 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 10 * or http://www.opensolaris.org/os/licensing.
11 11 * See the License for the specific language governing permissions
12 12 * and limitations under the License.
↓ open down ↓ |
12 lines elided |
↑ open up ↑ |
13 13 *
14 14 * When distributing Covered Code, include this CDDL HEADER in each
15 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 16 * If applicable, add the following below this CDDL HEADER, with the
17 17 * fields enclosed by brackets "[]" replaced with your own identifying
18 18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 19 *
20 20 * CDDL HEADER END
21 21 */
22 22 /*
23 + * Copyright 2015 Gary Mills
23 24 * Copyright (c) 1996-1997 by Sun Microsystems, Inc.
24 25 * All rights reserved.
25 26 */
26 27
27 28 /* Copyright (c) 1988 AT&T */
28 29 /* All Rights Reserved */
29 30
30 -
31 -#pragma ident "%Z%%M% %I% %E% SMI"
32 -
33 31 /* Copyright (c) 1979 Regents of the University of California */
34 32
35 33 /*LINTLIBRARY*/
36 34
37 35 #include "curses_inc.h"
38 36 #include "curshdr.h"
39 37 #include "term.h"
40 38 #include <string.h>
41 39 #include <setjmp.h>
42 40 #include <stdlib.h>
43 41 #include <stdio.h>
44 42
45 43 #ifndef _CHCTRL
46 44 #define _CHCTRL(c) ((c) & 037)
47 45 #endif /* _CHCTRL */
48 46
49 47 char *_branchto(char *, char);
50 48
51 49 /*
52 50 * Routine to perform parameter substitution.
53 51 * instring is a string containing printf type escapes.
54 52 * The whole thing uses a stack, much like an HP 35.
55 53 * The following escapes are defined for substituting row/column:
56 54 *
57 55 * %[:[-+ #0]][0-9][.][0-9][dsoxX]
58 56 * print pop() as in printf(3), as defined in the local
59 57 * sprintf(3), except that a leading + or - must be preceded
60 58 * with a colon (:) to distinguish from the plus/minus operators.
61 59 *
62 60 * %c print pop() like %c in printf(3)
63 61 * %l pop() a string address and push its length.
64 62 * %P[a-z] set dynamic variable a-z
65 63 * %g[a-z] get dynamic variable a-z
66 64 * %P[A-Z] set static variable A-Z
67 65 * %g[A-Z] get static variable A-Z
68 66 *
69 67 * %p[1-0] push ith parm
70 68 * %'c' char constant c
71 69 * %{nn} integer constant nn
72 70 *
73 71 * %+ %- %* %/ %m arithmetic (%m is mod): push(pop() op pop())
74 72 * %& %| %^ bit operations: push(pop() op pop())
75 73 * %= %> %< logical operations: push(pop() op pop())
76 74 * %A %O logical AND, OR push(pop() op pop())
77 75 * %! %~ unary operations push(op pop())
78 76 * %% output %
79 77 * %? expr %t thenpart %e elsepart %;
80 78 * if-then-else, %e elsepart is optional.
81 79 * else-if's are possible ala Algol 68:
82 80 * %? c1 %t %e c2 %t %e c3 %t %e c4 %t %e %;
83 81 * % followed by anything else
84 82 * is not defined, it may output the character,
85 83 * and it may not. This is done so that further
86 84 * enhancements to the format capabilities may
87 85 * be made without worrying about being upwardly
88 86 * compatible from buggy code.
89 87 *
90 88 * all other characters are ``self-inserting''. %% gets % output.
91 89 *
92 90 * The stack structure used here is based on an idea by Joseph Yao.
93 91 */
94 92
95 93 #define MAX 10
96 94 #define MEM_ALLOC_FAIL 1
97 95 #define STACK_UNDERFLOW 2
98 96
99 97 typedef struct {
100 98 long top;
101 99 int stacksize;
102 100 long *stack;
103 101
104 102 }STACK;
105 103
106 104 static jmp_buf env;
107 105
108 106 static long
109 107 tops(STACK *st)
110 108 {
111 109
112 110 if (st->top < 0) {
113 111 longjmp(env, STACK_UNDERFLOW);
114 112 }
115 113 return (st->stack[st->top]);
116 114 }
117 115
118 116 static void
119 117 push(STACK *st, long i)
120 118 {
121 119 if (st->top >= (st->stacksize - 1)) {
122 120 st->stacksize += MAX;
123 121 if ((st->stack = (void *)realloc(st->stack,
124 122 (st->stacksize * sizeof (long)))) == NULL) {
125 123 longjmp(env, MEM_ALLOC_FAIL);
126 124 }
127 125 }
128 126 st->stack[++st->top] = (i);
129 127 }
130 128
131 129 static long
132 130 pop(STACK *st)
133 131 {
134 132 if (st->top < 0) {
135 133 longjmp(env, STACK_UNDERFLOW);
136 134 }
137 135 return (st->stack[st->top--]);
138 136 }
139 137
140 138 /* The following routine was added to make lint shut up about converting from
141 139 * a long to a char *. It is identical to the pop routine, except for the
142 140 * cast on the return statement.
143 141 */
144 142 static char *
145 143 pop_char_p(STACK *st)
146 144 {
147 145 if (st->top < 0) {
148 146 longjmp(env, STACK_UNDERFLOW);
149 147 }
150 148 return ((char *)(st->stack[st->top--]));
151 149 }
152 150
153 151 static void
154 152 init_stack(STACK *st)
155 153 {
156 154 st->top = -1;
157 155 st->stacksize = MAX;
158 156 if ((st->stack = (void *)malloc(MAX * sizeof (long))) == NULL) {
159 157 longjmp(env, MEM_ALLOC_FAIL);
160 158 }
161 159 }
162 160
163 161 static void
164 162 free_stack(STACK *st)
165 163 {
166 164 free(st->stack);
167 165 }
168 166
169 167
170 168 char *
171 169 tparm_p0(char *instring)
172 170 {
173 171 long p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
174 172
175 173 return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
176 174 p[7], p[8]));
177 175 }
178 176
179 177 char *
180 178 tparm_p1(char *instring, long l1)
181 179 {
182 180 long p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
183 181
184 182 p[0] = l1;
185 183
186 184 return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
187 185 p[7], p[8]));
188 186 }
189 187
190 188 char *
191 189 tparm_p2(char *instring, long l1, long l2)
192 190 {
193 191 long p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
194 192
195 193 p[0] = l1;
196 194 p[1] = l2;
197 195
198 196 return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
199 197 p[7], p[8]));
200 198 }
201 199
202 200 char *
203 201 tparm_p3(char *instring, long l1, long l2, long l3)
204 202 {
205 203 long p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
206 204
207 205 p[0] = l1;
208 206 p[1] = l2;
209 207 p[2] = l3;
210 208
211 209 return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
212 210 p[7], p[8]));
213 211 }
214 212
215 213 char *
216 214 tparm_p4(char *instring, long l1, long l2, long l3, long l4)
217 215 {
218 216 long p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
219 217
220 218 p[0] = l1;
221 219 p[1] = l2;
222 220 p[2] = l3;
223 221 p[3] = l4;
224 222
225 223 return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
226 224 p[7], p[8]));
227 225 }
228 226
229 227 char *
230 228 tparm_p7(char *instring, long l1, long l2, long l3, long l4, long l5, long l6,
231 229 long l7)
232 230 {
233 231 long p[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
234 232
235 233 p[0] = l1;
236 234 p[1] = l2;
237 235 p[2] = l3;
238 236 p[3] = l4;
239 237 p[4] = l5;
240 238 p[5] = l6;
241 239 p[6] = l7;
242 240
243 241 return (tparm(instring, p[0], p[1], p[2], p[3], p[4], p[5], p[6],
244 242 p[7], p[8]));
245 243 }
246 244
247 245 /* VARARGS */
248 246 char *
249 247 tparm(char *instring, long fp1, long fp2, long p3, long p4,
250 248 long p5, long p6, long p7, long p8, long p9)
251 249 {
252 250 static char result[512];
↓ open down ↓ |
210 lines elided |
↑ open up ↑ |
253 251 static char added[100];
254 252 long vars[26];
255 253 STACK stk;
256 254 char *cp = instring;
257 255 char *outp = result;
258 256 char c;
259 257 long op;
260 258 long op2;
261 259 int sign;
262 260 int onrow = 0;
263 - long p1 = fp1, p2 = fp2; /* copy in case < 2 actual parms */
261 + volatile long p1 = fp1, p2 = fp2; /* copy in case < 2 actual parms */
264 262 char *xp;
265 263 char formatbuffer[100];
266 264 char *format;
267 265 int looping;
268 266 short *regs = cur_term->_regs;
269 267 int val;
270 268
271 269
272 270 if ((val = setjmp(env)) != 0) {
273 271 #ifdef DEBUG
274 272 switch (val) {
275 273 case MEM_ALLOC_FAIL:
276 274 fprintf(outf, "TPARM: Memory allocation"
277 275 " failure.");
278 276 break;
279 277 case STACK_UNDERFLOW:
280 278 fprintf(outf, "TPARM: Stack underflow.");
281 279 break;
282 280 }
283 281 #endif /* DEBUG */
284 282
285 283 if (val == STACK_UNDERFLOW)
286 284 free_stack(&stk);
287 285 return (NULL);
288 286 }
289 287
290 288 init_stack(&stk);
291 289 push(&stk, 0);
292 290
293 291 if (instring == 0) {
294 292 #ifdef DEBUG
295 293 if (outf)
296 294 fprintf(outf, "TPARM: null arg\n");
297 295 #endif /* DEBUG */
298 296 free_stack(&stk);
299 297 return (NULL);
300 298 }
301 299
302 300 added[0] = 0;
303 301
304 302 while ((c = *cp++) != 0) {
305 303 if (c != '%') {
306 304 *outp++ = c;
307 305 continue;
308 306 }
309 307 op = tops(&stk);
310 308 switch (c = *cp++) {
311 309 /* PRINTING CASES */
312 310 case ':':
313 311 case ' ':
314 312 case '#':
315 313 case '0':
316 314 case '1':
317 315 case '2':
318 316 case '3':
319 317 case '4':
320 318 case '5':
321 319 case '6':
322 320 case '7':
323 321 case '8':
324 322 case '9':
325 323 case '.':
326 324 case 'd':
327 325 case 's':
328 326 case 'o':
329 327 case 'x':
330 328 case 'X':
331 329 format = formatbuffer;
332 330 *format++ = '%';
333 331
334 332 /* leading ':' to allow +/- in format */
335 333 if (c == ':')
336 334 c = *cp++;
337 335
338 336 /* take care of flags, width and precision */
339 337 looping = 1;
340 338 while (c && looping)
341 339 switch (c) {
342 340 case '-':
343 341 case '+':
344 342 case ' ':
345 343 case '#':
346 344 case '0':
347 345 case '1':
348 346 case '2':
349 347 case '3':
350 348 case '4':
351 349 case '5':
352 350 case '6':
353 351 case '7':
354 352 case '8':
355 353 case '9':
356 354 case '.':
357 355 *format++ = c;
358 356 c = *cp++;
359 357 break;
360 358 default:
361 359 looping = 0;
362 360 }
363 361
364 362 /* add in the conversion type */
365 363 switch (c) {
366 364 case 'd':
367 365 case 's':
368 366 case 'o':
369 367 case 'x':
370 368 case 'X':
371 369 *format++ = c;
372 370 break;
373 371 default:
374 372 #ifdef DEBUG
375 373 if (outf)
376 374 fprintf(outf, "TPARM: invalid "
377 375 "conversion type\n");
378 376 #endif /* DEBUG */
379 377 free_stack(&stk);
380 378 return (NULL);
381 379 }
382 380 *format = '\0';
383 381
384 382 /*
385 383 * Pass off the dirty work to sprintf.
386 384 * It's debatable whether we should just pull in
387 385 * the appropriate code here. I decided not to for
388 386 * now.
389 387 */
390 388 if (c == 's')
391 389 (void) sprintf(outp, formatbuffer,
392 390 (char *) op);
393 391 else
394 392 (void) sprintf(outp, formatbuffer, op);
395 393 /*
396 394 * Advance outp past what sprintf just did.
397 395 * sprintf returns an indication of its length on some
398 396 * systems, others the first char, and there's
399 397 * no easy way to tell which. The Sys V on
400 398 * BSD emulations are particularly confusing.
401 399 */
402 400 while (*outp)
403 401 outp++;
404 402 (void) pop(&stk);
405 403
406 404 continue;
407 405
408 406 case 'c':
409 407 /*
410 408 * This code is worth scratching your head at for a
411 409 * while. The idea is that various weird things can
412 410 * happen to nulls, EOT's, tabs, and newlines by the
413 411 * tty driver, arpanet, and so on, so we don't send
414 412 * them if we can help it. So we instead alter the
415 413 * place being addessed and then move the cursor
416 414 * locally using UP or RIGHT.
417 415 *
418 416 * This is a kludge, clearly. It loses if the
419 417 * parameterized string isn't addressing the cursor
420 418 * (but hopefully that is all that %c terminals do
421 419 * with parms). Also, since tab and newline happen
422 420 * to be next to each other in ASCII, if tab were
423 421 * included a loop would be needed. Finally, note
424 422 * that lots of other processing is done here, so
425 423 * this hack won't always work (e.g. the Ann Arbor
426 424 * 4080, which uses %B and then %c.)
427 425 */
428 426 switch (op) {
429 427 /*
430 428 * Null. Problem is that our
431 429 * output is, by convention, null terminated.
432 430 */
433 431 case 0:
434 432 op = 0200; /* Parity should */
435 433 /* be ignored. */
436 434 break;
437 435 /*
438 436 * Control D. Problem is that certain very
439 437 * ancient hardware hangs up on this, so the
440 438 * current(!) UNIX tty driver doesn't xmit
441 439 * control D's.
442 440 */
443 441 case _CHCTRL('d'):
444 442 /*
445 443 * Newline. Problem is that UNIX will expand
446 444 * this to CRLF.
447 445 */
448 446 case '\n':
449 447 xp = (onrow ? cursor_down :
450 448 cursor_right);
451 449 if (onrow && xp && op < lines-1 &&
452 450 cursor_up) {
453 451 op += 2;
454 452 xp = cursor_up;
455 453 }
456 454 if (xp && instring ==
457 455 cursor_address) {
458 456 (void) strcat(added, xp);
459 457 op--;
460 458 }
461 459 break;
462 460 /*
463 461 * Tab used to be in this group too,
464 462 * because UNIX might expand it to blanks.
465 463 * We now require that this tab mode be turned
466 464 * off by any program using this routine,
467 465 * or using termcap in general, since some
468 466 * terminals use tab for other stuff, like
469 467 * nondestructive space. (Filters like ul
470 468 * or vcrt will lose, since they can't stty.)
471 469 * Tab was taken out to get the Ann Arbor
472 470 * 4080 to work.
473 471 */
474 472 }
475 473
476 474 /* LINTED */
477 475 *outp++ = (char)op;
478 476 (void) pop(&stk);
479 477 break;
480 478
481 479 case 'l':
482 480 xp = pop_char_p(&stk);
483 481 push(&stk, strlen(xp));
484 482 break;
485 483
486 484 case '%':
487 485 *outp++ = c;
488 486 break;
489 487
490 488 /*
491 489 * %i: shorthand for increment first two parms.
492 490 * Useful for terminals that start numbering from
493 491 * one instead of zero(like ANSI terminals).
494 492 */
495 493 case 'i':
496 494 p1++;
497 495 p2++;
498 496 break;
499 497
500 498 /* %pi: push the ith parameter */
501 499 case 'p':
502 500 switch (c = *cp++) {
503 501 case '1':
504 502 push(&stk, p1);
505 503 break;
506 504 case '2':
507 505 push(&stk, p2);
508 506 break;
509 507 case '3':
510 508 push(&stk, p3);
511 509 break;
512 510 case '4':
513 511 push(&stk, p4);
514 512 break;
515 513 case '5':
516 514 push(&stk, p5);
517 515 break;
518 516 case '6':
519 517 push(&stk, p6);
520 518 break;
521 519 case '7':
522 520 push(&stk, p7);
523 521 break;
524 522 case '8':
525 523 push(&stk, p8);
526 524 break;
527 525 case '9':
528 526 push(&stk, p9);
529 527 break;
530 528 default:
531 529 #ifdef DEBUG
532 530 if (outf)
533 531 fprintf(outf, "TPARM:"
534 532 " bad parm"
535 533 " number\n");
536 534 #endif /* DEBUG */
537 535 free_stack(&stk);
538 536 return (NULL);
539 537 }
540 538 onrow = (c == '1');
541 539 break;
542 540
543 541 /* %Pi: pop from stack into variable i (a-z) */
544 542 case 'P':
545 543 if (*cp >= 'a' && *cp <= 'z') {
546 544 vars[*cp++ - 'a'] = pop(&stk);
547 545 } else {
548 546 if (*cp >= 'A' && *cp <= 'Z') {
549 547 regs[*cp++ - 'A'] =
550 548 /* LINTED */
551 549 (short) pop(&stk);
552 550 }
553 551 #ifdef DEBUG
554 552 else if (outf) {
555 553 fprintf(outf, "TPARM: bad"
556 554 " register name\n");
557 555 }
558 556 #endif /* DEBUG */
559 557 }
560 558 break;
561 559
562 560 /* %gi: push variable i (a-z) */
563 561 case 'g':
564 562 if (*cp >= 'a' && *cp <= 'z') {
565 563 push(&stk, vars[*cp++ - 'a']);
566 564 } else {
567 565 if (*cp >= 'A' && *cp <= 'Z') {
568 566 push(&stk, regs[*cp++ - 'A']);
569 567 }
570 568 #ifdef DEBUG
571 569 else if (outf) {
572 570 fprintf(outf, "TPARM: bad"
573 571 " register name\n");
574 572
575 573 }
576 574 #endif /* DEBUG */
577 575 }
578 576 break;
579 577
580 578 /* %'c' : character constant */
581 579 case '\'':
582 580 push(&stk, *cp++);
583 581 if (*cp++ != '\'') {
584 582 #ifdef DEBUG
585 583 if (outf)
586 584 fprintf(outf, "TPARM: missing"
587 585 " closing quote\n");
588 586 #endif /* DEBUG */
589 587 free_stack(&stk);
590 588 return (NULL);
591 589 }
592 590 break;
593 591
594 592 /* %{nn} : integer constant. */
595 593 case '{':
596 594 op = 0;
597 595 sign = 1;
598 596 if (*cp == '-') {
599 597 sign = -1;
600 598 cp++;
601 599 } else
602 600 if (*cp == '+')
603 601 cp++;
604 602 while ((c = *cp++) >= '0' && c <= '9') {
605 603 op = 10 * op + c - '0';
606 604 }
607 605 if (c != '}') {
608 606 #ifdef DEBUG
609 607 if (outf)
610 608 fprintf(outf, "TPARM: missing "
611 609 "closing brace\n");
612 610 #endif /* DEBUG */
613 611 free_stack(&stk);
614 612 return (NULL);
615 613 }
616 614 push(&stk, (sign * op));
617 615 break;
618 616
619 617 /* binary operators */
620 618 case '+':
621 619 op2 = pop(&stk);
622 620 op = pop(&stk);
623 621 push(&stk, (op + op2));
624 622 break;
625 623 case '-':
626 624 op2 = pop(&stk);
627 625 op = pop(&stk);
628 626 push(&stk, (op - op2));
629 627 break;
630 628 case '*':
631 629 op2 = pop(&stk);
632 630 op = pop(&stk);
633 631 push(&stk, (op * op2));
634 632 break;
635 633 case '/':
636 634 op2 = pop(&stk);
637 635 op = pop(&stk);
638 636 push(&stk, (op / op2));
639 637 break;
640 638 case 'm':
641 639 op2 = pop(&stk);
642 640 op = pop(&stk);
643 641 push(&stk, (op % op2));
644 642 break; /* %m: mod */
645 643 case '&':
646 644 op2 = pop(&stk);
647 645 op = pop(&stk);
648 646 push(&stk, (op & op2));
649 647 break;
650 648 case '|':
651 649 op2 = pop(&stk);
652 650 op = pop(&stk);
653 651 push(&stk, (op | op2));
654 652 break;
655 653 case '^':
656 654 op2 = pop(&stk);
657 655 op = pop(&stk);
658 656 push(&stk, (op ^ op2));
659 657 break;
660 658 case '=':
661 659 op2 = pop(&stk);
662 660 op = pop(&stk);
663 661 push(&stk, (op == op2));
664 662 break;
665 663 case '>':
666 664 op2 = pop(&stk);
667 665 op = pop(&stk);
668 666 push(&stk, (op > op2));
669 667 break;
670 668 case '<':
671 669 op2 = pop(&stk);
672 670 op = pop(&stk);
673 671 push(&stk, (op < op2));
674 672 break;
675 673 case 'A':
676 674 op2 = pop(&stk);
677 675 op = pop(&stk);
678 676 push(&stk, (op && op2));
679 677 break; /* AND */
680 678 case 'O':
681 679 op2 = pop(&stk);
682 680 op = pop(&stk);
683 681 push(&stk, (op || op2));
684 682 break; /* OR */
685 683
686 684 /* Unary operators. */
687 685 case '!':
688 686 push(&stk, !pop(&stk));
689 687 break;
690 688 case '~':
691 689 push(&stk, ~pop(&stk));
692 690 break;
693 691
694 692 /* Sorry, no unary minus, because minus is binary. */
695 693
696 694 /*
697 695 * If-then-else. Implemented by a low level hack of
698 696 * skipping forward until the match is found, counting
699 697 * nested if-then-elses.
700 698 */
701 699 case '?': /* IF - just a marker */
702 700 break;
703 701
704 702 case 't': /* THEN - branch if false */
705 703 if (!pop(&stk))
706 704 cp = _branchto(cp, 'e');
707 705 break;
708 706
709 707 case 'e': /* ELSE - branch to ENDIF */
710 708 cp = _branchto(cp, ';');
711 709 break;
712 710
713 711 case ';': /* ENDIF - just a marker */
714 712 break;
715 713
716 714 default:
717 715 #ifdef DEBUG
718 716 if (outf)
719 717 fprintf(outf, "TPARM: bad % "
720 718 "sequence\n");
721 719 #endif /* DEBUG */
722 720 free_stack(&stk);
723 721 return (NULL);
724 722 }
725 723 }
726 724 (void) strcpy(outp, added);
727 725 free_stack(&stk);
728 726 return (result);
729 727 }
730 728
731 729 char *
732 730 _branchto(register char *cp, char to)
733 731 {
734 732 register int level = 0;
735 733 register char c;
736 734
737 735 while (c = *cp++) {
738 736 if (c == '%') {
739 737 if ((c = *cp++) == to || c == ';') {
740 738 if (level == 0) {
741 739 return (cp);
742 740 }
743 741 }
744 742 if (c == '?')
745 743 level++;
746 744 if (c == ';')
747 745 level--;
748 746 }
749 747 }
750 748 #ifdef DEBUG
751 749 if (outf)
752 750 fprintf(outf, "TPARM: no matching ENDIF");
753 751 #endif /* DEBUG */
754 752 return (NULL);
755 753 }
↓ open down ↓ |
482 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX