]> git.ipfire.org Git - thirdparty/gcc.git/blob - libio/iostream.cc
Initial revision
[thirdparty/gcc.git] / libio / iostream.cc
1 /* This is part of libio/iostream, providing -*- C++ -*- input/output.
2 Copyright (C) 1993 Free Software Foundation
3
4 This file is part of the GNU IO Library. This library is free
5 software; you can redistribute it and/or modify it under the
6 terms of the GNU General Public License as published by the
7 Free Software Foundation; either version 2, or (at your option)
8 any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this library; see the file COPYING. If not, write to the Free
17 Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 As a special exception, if you link this library with files
20 compiled with a GNU compiler to produce an executable, this does not cause
21 the resulting executable to be covered by the GNU General Public License.
22 This exception does not however invalidate any other reasons why
23 the executable file might be covered by the GNU General Public License. */
24
25 /* Written by Per Bothner (bothner@cygnus.com). */
26
27 #ifdef __GNUC__
28 #pragma implementation
29 #endif
30 #define _STREAM_COMPAT
31 #include <iostream.h>
32 #include "libioP.h"
33 #include <stdio.h> /* Needed for sprintf */
34 #include <ctype.h>
35 #include <string.h>
36 #include <limits.h>
37 #include "floatio.h"
38
39 #define BUF (MAXEXP+MAXFRACT+1) /* + decimal point */
40
41 //#define isspace(ch) ((ch)==' ' || (ch)=='\t' || (ch)=='\n')
42
43 istream::istream(streambuf *sb, ostream* tied)
44 {
45 init (sb, tied);
46 _gcount = 0;
47 }
48
49 int skip_ws(streambuf* sb)
50 {
51 int ch;
52 for (;;) {
53 ch = sb->sbumpc();
54 if (ch == EOF || !isspace(ch))
55 return ch;
56 }
57 }
58
59 istream& istream::get(char& c)
60 {
61 if (ipfx1()) {
62 int ch = _strbuf->sbumpc();
63 if (ch == EOF) {
64 set(ios::eofbit|ios::failbit);
65 _gcount = 0;
66 }
67 else {
68 c = (char)ch;
69 _gcount = 1;
70 }
71 }
72 else
73 _gcount = 0;
74 return *this;
75 }
76
77 int istream::peek()
78 {
79 if (!good())
80 return EOF;
81 if (_tie && rdbuf()->in_avail() == 0)
82 _tie->flush();
83 int ch = _strbuf->sgetc();
84 if (ch == EOF)
85 set(ios::eofbit);
86 return ch;
87 }
88
89 istream& istream::ignore(int n /* = 1 */, int delim /* = EOF */)
90 {
91 _gcount = 0;
92 if (ipfx1()) {
93 register streambuf* sb = _strbuf;
94 if (delim == EOF) {
95 _gcount = sb->ignore(n);
96 return *this;
97 }
98 for (;;) {
99 #if 0
100 if (n != MAXINT) // FIXME
101 #endif
102 if (--n < 0)
103 break;
104 int ch = sb->sbumpc();
105 if (ch == EOF) {
106 set(ios::eofbit|ios::failbit);
107 break;
108 }
109 _gcount++;
110 if (ch == delim)
111 break;
112 }
113 }
114 return *this;
115 }
116
117 istream& istream::read(char *s, streamsize n)
118 {
119 if (ipfx1()) {
120 _gcount = _strbuf->sgetn(s, n);
121 if (_gcount != n)
122 set(ios::failbit|ios::eofbit);
123 }
124 else
125 _gcount = 0;
126 return *this;
127 }
128
129 int
130 istream::sync ()
131 {
132 streambuf *sb = rdbuf ();
133 if (sb == NULL)
134 return EOF;
135 if (sb->sync ()) // Later: pubsync
136 {
137 setstate (ios::badbit);
138 return EOF;
139 }
140 else
141 return 0;
142 }
143
144 istream& istream::seekg(streampos pos)
145 {
146 pos = _strbuf->pubseekpos(pos, ios::in);
147 if (pos == streampos(EOF))
148 set(ios::badbit);
149 return *this;
150 }
151
152 istream& istream::seekg(streamoff off, _seek_dir dir)
153 {
154 streampos pos = _IO_seekoff (_strbuf, off, (int) dir, _IOS_INPUT);
155 if (pos == streampos(EOF))
156 set(ios::badbit);
157 return *this;
158 }
159
160 streampos istream::tellg()
161 {
162 #if 0
163 streampos pos = _strbuf->pubseekoff(0, ios::cur, ios::in);
164 #else
165 streampos pos = _IO_seekoff (_strbuf, 0, _IO_seek_cur, _IOS_INPUT);
166 #endif
167 if (pos == streampos(EOF))
168 set(ios::badbit);
169 return pos;
170 }
171
172 istream& istream::operator>>(char& c)
173 {
174 if (ipfx0()) {
175 int ch = _strbuf->sbumpc();
176 if (ch == EOF)
177 set(ios::eofbit|ios::failbit);
178 else
179 c = (char)ch;
180 }
181 return *this;
182 }
183
184 istream&
185 istream::operator>> (char* ptr)
186 {
187 register char *p = ptr;
188 int w = width(0);
189 if (ipfx0())
190 {
191 register streambuf* sb = _strbuf;
192 for (;;)
193 {
194 int ch = sb->sbumpc();
195 if (ch == EOF)
196 {
197 set(ios::eofbit);
198 break;
199 }
200 else if (isspace(ch) || w == 1)
201 {
202 sb->sputbackc(ch);
203 break;
204 }
205 else *p++ = ch;
206 w--;
207 }
208 if (p == ptr)
209 set(ios::failbit);
210 }
211 *p = '\0';
212 return *this;
213 }
214
215 #if defined(__GNUC__) && !defined(__STRICT_ANSI__)
216 #define LONGEST long long
217 #else
218 #define LONGEST long
219 #endif
220
221 static int read_int(istream& stream, unsigned LONGEST& val, int& neg)
222 {
223 if (!stream.ipfx0())
224 return 0;
225 register streambuf* sb = stream.rdbuf();
226 int base = 10;
227 int ndigits = 0;
228 register int ch = skip_ws(sb);
229 if (ch == EOF)
230 goto eof_fail;
231 neg = 0;
232 if (ch == '+') {
233 ch = skip_ws(sb);
234 }
235 else if (ch == '-') {
236 neg = 1;
237 ch = skip_ws(sb);
238 }
239 if (ch == EOF) goto eof_fail;
240 if (!(stream.flags() & ios::basefield)) {
241 if (ch == '0') {
242 ch = sb->sbumpc();
243 if (ch == EOF) {
244 val = 0;
245 return 1;
246 }
247 if (ch == 'x' || ch == 'X') {
248 base = 16;
249 ch = sb->sbumpc();
250 if (ch == EOF) goto eof_fail;
251 }
252 else {
253 sb->sputbackc(ch);
254 base = 8;
255 ch = '0';
256 }
257 }
258 }
259 else if ((stream.flags() & ios::basefield) == ios::hex)
260 base = 16;
261 else if ((stream.flags() & ios::basefield) == ios::oct)
262 base = 8;
263 val = 0;
264 for (;;) {
265 if (ch == EOF)
266 break;
267 int digit;
268 if (ch >= '0' && ch <= '9')
269 digit = ch - '0';
270 else if (ch >= 'A' && ch <= 'F')
271 digit = ch - 'A' + 10;
272 else if (ch >= 'a' && ch <= 'f')
273 digit = ch - 'a' + 10;
274 else
275 digit = 999;
276 if (digit >= base) {
277 sb->sputbackc(ch);
278 if (ndigits == 0)
279 goto fail;
280 else
281 return 1;
282 }
283 ndigits++;
284 val = base * val + digit;
285 ch = sb->sbumpc();
286 }
287 return 1;
288 fail:
289 stream.set(ios::failbit);
290 return 0;
291 eof_fail:
292 stream.set(ios::failbit|ios::eofbit);
293 return 0;
294 }
295
296 #define READ_INT(TYPE) \
297 istream& istream::operator>>(TYPE& i)\
298 {\
299 unsigned LONGEST val; int neg;\
300 if (read_int(*this, val, neg)) {\
301 if (neg) val = -val;\
302 i = (TYPE)val;\
303 }\
304 return *this;\
305 }
306
307 READ_INT(short)
308 READ_INT(unsigned short)
309 READ_INT(int)
310 READ_INT(unsigned int)
311 READ_INT(long)
312 READ_INT(unsigned long)
313 #if defined(__GNUC__) && !defined(__STRICT_ANSI__)
314 READ_INT(long long)
315 READ_INT(unsigned long long)
316 #endif
317 #if _G_HAVE_BOOL
318 READ_INT(bool)
319 #endif
320
321 istream& istream::operator>>(long double& x)
322 {
323 if (ipfx0())
324 scan("%lg", &x);
325 return *this;
326 }
327
328 istream& istream::operator>>(double& x)
329 {
330 if (ipfx0())
331 scan("%lg", &x);
332 return *this;
333 }
334
335 istream& istream::operator>>(float& x)
336 {
337 if (ipfx0())
338 scan("%g", &x);
339 return *this;
340 }
341
342 istream& istream::operator>>(register streambuf* sbuf)
343 {
344 if (ipfx0()) {
345 register streambuf* inbuf = rdbuf();
346 // FIXME: Should optimize!
347 for (;;) {
348 register int ch = inbuf->sbumpc();
349 if (ch == EOF) {
350 set(ios::eofbit);
351 break;
352 }
353 if (sbuf->sputc(ch) == EOF) {
354 set(ios::failbit);
355 break;
356 }
357 }
358 }
359 return *this;
360 }
361
362 ostream& ostream::operator<<(char c)
363 {
364 if (opfx()) {
365 #if 1
366 // This is what the cfront implementation does.
367 if (_strbuf->sputc(c) == EOF)
368 goto failed;
369 #else
370 // This is what cfront documentation and current ANSI drafts say.
371 int w = width(0);
372 char fill_char = fill();
373 register int padding = w > 0 ? w - 1 : 0;
374 register streambuf *sb = _strbuf;
375 if (!(flags() & ios::left) && padding) // Default adjustment.
376 if (_IO_padn(sb, fill_char, padding) < padding)
377 goto failed;
378 if (sb->sputc(c) == EOF)
379 goto failed;
380 if (flags() & ios::left && padding) // Left adjustment.
381 if (_IO_padn(sb, fill_char, padding) < padding)
382 goto failed;
383 #endif
384 osfx();
385 }
386 return *this;
387 failed:
388 set(ios::badbit);
389 osfx();
390 return *this;
391 }
392
393 /* Write VAL on STREAM.
394 If SIGN<0, val is the absolute value of a negative number.
395 If SIGN>0, val is a signed non-negative number.
396 If SIGN==0, val is unsigned. */
397
398 static void write_int(ostream& stream, unsigned LONGEST val, int sign)
399 {
400 #define WRITE_BUF_SIZE (10 + sizeof(unsigned LONGEST) * 3)
401 char buf[WRITE_BUF_SIZE];
402 register char *buf_ptr = buf+WRITE_BUF_SIZE; // End of buf.
403 const char *show_base = "";
404 int show_base_len = 0;
405 int show_pos = 0; // If 1, print a '+'.
406
407 // Now do the actual conversion, placing the result at the *end* of buf.
408 // Note that we use separate code for decimal, octal, and hex,
409 // so we can divide by optimizable constants.
410 if ((stream.flags() & ios::basefield) == ios::oct) { // Octal
411 do {
412 *--buf_ptr = (val & 7) + '0';
413 val = val >> 3;
414 } while (val != 0);
415 if ((stream.flags() & ios::showbase) && (*buf_ptr != '0'))
416 *--buf_ptr = '0';
417 }
418 else if ((stream.flags() & ios::basefield) == ios::hex) { // Hex
419 const char *xdigs = (stream.flags() & ios::uppercase) ? "0123456789ABCDEF0X"
420 : "0123456789abcdef0x";
421 do {
422 *--buf_ptr = xdigs[val & 15];
423 val = val >> 4;
424 } while (val != 0);
425 if ((stream.flags() & ios::showbase)) {
426 show_base = xdigs + 16; // Either "0X" or "0x".
427 show_base_len = 2;
428 }
429 }
430 else { // Decimal
431 #if defined(__GNUC__) && !defined(__STRICT_ANSI__)
432 // Optimization: Only use long long when we need to.
433 while (val > UINT_MAX) {
434 *--buf_ptr = (val % 10) + '0';
435 val /= 10;
436 }
437 // Use more efficient (int) arithmetic for the rest.
438 register unsigned int ival = (unsigned int)val;
439 #else
440 register unsigned LONGEST ival = val;
441 #endif
442 do {
443 *--buf_ptr = (ival % 10) + '0';
444 ival /= 10;
445 } while (ival != 0);
446 if (sign > 0 && (stream.flags() & ios::showpos))
447 show_pos=1;
448 }
449
450 int buf_len = buf+WRITE_BUF_SIZE - buf_ptr;
451 int w = stream.width(0);
452
453 // Calculate padding.
454 int len = buf_len+show_pos;
455 if (sign < 0) len++;
456 len += show_base_len;
457 int padding = len > w ? 0 : w - len;
458
459 // Do actual output.
460 register streambuf* sbuf = stream.rdbuf();
461 ios::fmtflags pad_kind =
462 stream.flags() & (ios::left|ios::right|ios::internal);
463 char fill_char = stream.fill();
464 if (padding > 0
465 && pad_kind != (ios::fmtflags)ios::left
466 && pad_kind != (ios::fmtflags)ios::internal) // Default (right) adjust.
467 if (_IO_padn(sbuf, fill_char, padding) < padding)
468 goto failed;
469 if (sign < 0 || show_pos)
470 {
471 char ch = sign < 0 ? '-' : '+';
472 if (sbuf->sputc(ch) < 0)
473 goto failed;
474 }
475 if (show_base_len)
476 if (_IO_sputn(sbuf, show_base, show_base_len) <= 0)
477 goto failed;
478 if (pad_kind == (ios::fmtflags)ios::internal && padding > 0)
479 if (_IO_padn(sbuf, fill_char, padding) < padding)
480 goto failed;
481 if (_IO_sputn (sbuf, buf_ptr, buf_len) != buf_len)
482 goto failed;
483 if (pad_kind == (ios::fmtflags)ios::left && padding > 0) // Left adjustment
484 if (_IO_padn(sbuf, fill_char, padding) < padding)
485 goto failed;
486 stream.osfx();
487 return;
488 failed:
489 stream.set(ios::badbit);
490 stream.osfx();
491 }
492
493 ostream& ostream::operator<<(int n)
494 {
495 if (opfx()) {
496 int sign = 1;
497 unsigned int abs_n = (unsigned)n;
498 if (n < 0 && (flags() & (ios::oct|ios::hex)) == 0)
499 abs_n = -((unsigned)n), sign = -1;
500 write_int(*this, abs_n, sign);
501 }
502 return *this;
503 }
504
505 ostream& ostream::operator<<(unsigned int n)
506 {
507 if (opfx())
508 write_int(*this, n, 0);
509 return *this;
510 }
511
512
513 ostream& ostream::operator<<(long n)
514 {
515 if (opfx()) {
516 int sign = 1;
517 unsigned long abs_n = (unsigned long)n;
518 if (n < 0 && (flags() & (ios::oct|ios::hex)) == 0)
519 abs_n = -((unsigned long)n), sign = -1;
520 write_int(*this, abs_n, sign);
521 }
522 return *this;
523 }
524
525 ostream& ostream::operator<<(unsigned long n)
526 {
527 if (opfx())
528 write_int(*this, n, 0);
529 return *this;
530 }
531
532 #if defined(__GNUC__) && !defined(__STRICT_ANSI__)
533 ostream& ostream::operator<<(long long n)
534 {
535 if (opfx()) {
536 int sign = 1;
537 unsigned long long abs_n = (unsigned long long)n;
538 if (n < 0 && (flags() & (ios::oct|ios::hex)) == 0)
539 abs_n = -((unsigned long long)n), sign = -1;
540 write_int(*this, abs_n, sign);
541 }
542 return *this;
543 }
544
545
546 ostream& ostream::operator<<(unsigned long long n)
547 {
548 if (opfx())
549 write_int(*this, n, 0);
550 return *this;
551 }
552 #endif /*__GNUC__*/
553
554 ostream& ostream::operator<<(double n)
555 {
556 if (opfx()) {
557 // Uses __cvt_double (renamed from static cvt), in Chris Torek's
558 // stdio implementation. The setup code uses the same logic
559 // as in __vsbprintf.C (also based on Torek's code).
560 int format_char;
561 if ((flags() & ios::floatfield) == ios::fixed)
562 format_char = 'f';
563 else if ((flags() & ios::floatfield) == ios::scientific)
564 format_char = flags() & ios::uppercase ? 'E' : 'e';
565 else
566 format_char = flags() & ios::uppercase ? 'G' : 'g';
567
568 int prec = precision();
569 if (prec <= 0 && !(flags() & ios::fixed))
570 prec = 6; /* default */
571
572 // Do actual conversion.
573 #ifdef _IO_USE_DTOA
574 if (_IO_outfloat(n, rdbuf(), format_char, width(0),
575 prec, flags(),
576 flags() & ios::showpos ? '+' : 0,
577 fill()) < 0)
578 set(ios::badbit|ios::failbit); // ??
579 #else
580 int fpprec = 0; // 'Extra' (suppressed) floating precision.
581 if (prec > MAXFRACT) {
582 if (flags() & (ios::fixed|ios::scientific) & ios::showpos)
583 fpprec = prec - MAXFRACT;
584 prec = MAXFRACT;
585 }
586 int negative;
587 char buf[BUF];
588 int sign = '\0';
589 char *cp = buf;
590 *cp = 0;
591 int size = __cvt_double(n, prec,
592 flags() & ios::showpoint ? 0x80 : 0,
593 &negative,
594 format_char, cp, buf + sizeof(buf));
595 if (negative) sign = '-';
596 else if (flags() & ios::showpos) sign = '+';
597 if (*cp == 0)
598 cp++;
599
600 // Calculate padding.
601 int fieldsize = size + fpprec;
602 if (sign) fieldsize++;
603 int padding = 0;
604 int w = width(0);
605 if (fieldsize < w)
606 padding = w - fieldsize;
607
608 // Do actual output.
609 register streambuf* sbuf = rdbuf();
610 register i;
611 char fill_char = fill();
612 ios::fmtflags pad_kind =
613 flags() & (ios::left|ios::right|ios::internal);
614 if (pad_kind != (ios::fmtflags)ios::left // Default (right) adjust.
615 && pad_kind != (ios::fmtflags)ios::internal)
616 for (i = padding; --i >= 0; ) sbuf->sputc(fill_char);
617 if (sign)
618 sbuf->sputc(sign);
619 if (pad_kind == (ios::fmtflags)ios::internal)
620 for (i = padding; --i >= 0; ) sbuf->sputc(fill_char);
621
622 // Emit the actual concented field, followed by extra zeros.
623 _IO_sputn (sbuf, cp, size);
624 for (i = fpprec; --i >= 0; ) sbuf->sputc('0');
625
626 if (pad_kind == (ios::fmtflags)ios::left) // Left adjustment
627 for (i = padding; --i >= 0; ) sbuf->sputc(fill_char);
628 #endif
629 osfx();
630 }
631 return *this;
632 }
633
634 ostream& ostream::operator<<(const char *s)
635 {
636 if (opfx())
637 {
638 if (s == NULL)
639 s = "(null)";
640 int len = strlen(s);
641 int w = width(0);
642 // FIXME: Should we: if (w && len>w) len = w;
643 char fill_char = fill();
644 register streambuf *sbuf = rdbuf();
645 register int padding = w > len ? w - len : 0;
646 if (!(flags() & ios::left) && padding > 0) // Default adjustment.
647 if (_IO_padn(sbuf, fill_char, padding) != padding)
648 goto failed;
649 if (_IO_sputn (sbuf, s, len) != len)
650 goto failed;
651 if (flags() & ios::left && padding > 0) // Left adjustment.
652 if (_IO_padn(sbuf, fill_char, padding) != padding)
653 goto failed;
654 osfx();
655 }
656 return *this;
657 failed:
658 set(ios::badbit);
659 osfx();
660 return *this;
661 }
662
663 #if 0
664 ostream& ostream::operator<<(const void *p)
665 { Is in osform.cc, to avoid pulling in all of _IO_vfprintf by this file. */ }
666 #endif
667
668 ostream& ostream::operator<<(register streambuf* sbuf)
669 {
670 if (opfx())
671 {
672 char buffer[_IO_BUFSIZ];
673 register streambuf* outbuf = _strbuf;
674 for (;;)
675 {
676 _IO_size_t count = _IO_sgetn(sbuf, buffer, _IO_BUFSIZ);
677 if (count <= 0)
678 break;
679 if (_IO_sputn(outbuf, buffer, count) != count)
680 {
681 set(ios::badbit);
682 break;
683 }
684 }
685 osfx();
686 }
687 return *this;
688 }
689
690 ostream::ostream(streambuf* sb, ostream* tied)
691 {
692 init (sb, tied);
693 }
694
695 ostream& ostream::seekp(streampos pos)
696 {
697 pos = _strbuf->pubseekpos(pos, ios::out);
698 if (pos == streampos(EOF))
699 set(ios::badbit);
700 return *this;
701 }
702
703 ostream& ostream::seekp(streamoff off, _seek_dir dir)
704 {
705 streampos pos = _IO_seekoff (_strbuf, off, (int) dir, _IOS_OUTPUT);
706 if (pos == streampos(EOF))
707 set(ios::badbit);
708 return *this;
709 }
710
711 streampos ostream::tellp()
712 {
713 #if 1
714 streampos pos = _IO_seekoff (_strbuf, 0, _IO_seek_cur, _IOS_OUTPUT);
715 #else
716 streampos pos = _strbuf->pubseekoff(0, ios::cur, ios::out);
717 #endif
718 if (pos == streampos(EOF))
719 set(ios::badbit);
720 return pos;
721 }
722
723 ostream& ostream::flush()
724 {
725 if (_strbuf->sync())
726 set(ios::badbit);
727 return *this;
728 }
729
730 ostream& flush(ostream& outs)
731 {
732 return outs.flush();
733 }
734
735 istream& ws(istream& ins)
736 {
737 if (ins.ipfx1()) {
738 int ch = skip_ws(ins._strbuf);
739 if (ch == EOF)
740 ins.set(ios::eofbit);
741 else
742 ins._strbuf->sputbackc(ch);
743 }
744 return ins;
745 }
746
747 // Skip white-space. Return 0 on failure (EOF), or 1 on success.
748 // Differs from ws() manipulator in that failbit is set on EOF.
749 // Called by ipfx() and ipfx0() if needed.
750
751 int istream::_skip_ws()
752 {
753 int ch = skip_ws(_strbuf);
754 if (ch == EOF) {
755 set(ios::eofbit|ios::failbit);
756 return 0;
757 }
758 else {
759 _strbuf->sputbackc(ch);
760 return 1;
761 }
762 }
763
764 ostream& ends(ostream& outs)
765 {
766 outs.put('\0');
767 return outs;
768 }
769
770 ostream& endl(ostream& outs)
771 {
772 return flush(outs.put('\n'));
773 }
774
775 ostream& ostream::write(const char *s, streamsize n)
776 {
777 if (opfx()) {
778 if (_IO_sputn(_strbuf, s, n) != n)
779 set(ios::failbit);
780 }
781 return *this;
782 }
783
784 void ostream::do_osfx()
785 {
786 if (flags() & ios::unitbuf)
787 flush();
788 if (flags() & ios::stdio) {
789 fflush(stdout);
790 fflush(stderr);
791 }
792 }
793
794 iostream::iostream(streambuf* sb, ostream* tied)
795 {
796 init (sb, tied);
797 }
798
799 // NOTE: extension for compatibility with old libg++.
800 // Not really compatible with fistream::close().
801 #ifdef _STREAM_COMPAT
802 void ios::close()
803 {
804 if (_strbuf->_flags & _IO_IS_FILEBUF)
805 ((struct filebuf*)rdbuf())->close();
806 else if (_strbuf != NULL)
807 rdbuf()->sync();
808 _strbuf = NULL;
809 _state = badbit;
810 }
811
812 int istream::skip(int i)
813 {
814 int old = (_flags & ios::skipws) != 0;
815 if (i)
816 _flags |= ios::skipws;
817 else
818 _flags &= ~ios::skipws;
819 return old;
820 }
821 #endif