Annotation of src/usr.bin/mandoc/mandoc.c, Revision 1.29
1.29 ! schwarze 1: /* $Id: mandoc.c,v 1.28 2011/09/18 15:54:48 schwarze Exp $ */
1.1 schwarze 2: /*
1.24 schwarze 3: * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
1.21 schwarze 4: * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
1.1 schwarze 5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
1.21 schwarze 10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1.1 schwarze 11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1.21 schwarze 12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1.1 schwarze 13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
1.2 schwarze 18: #include <sys/types.h>
19:
1.1 schwarze 20: #include <assert.h>
21: #include <ctype.h>
1.26 schwarze 22: #include <errno.h>
23: #include <limits.h>
1.1 schwarze 24: #include <stdlib.h>
1.4 schwarze 25: #include <stdio.h>
26: #include <string.h>
1.5 schwarze 27: #include <time.h>
1.1 schwarze 28:
1.14 schwarze 29: #include "mandoc.h"
1.1 schwarze 30: #include "libmandoc.h"
31:
1.22 schwarze 32: #define DATESIZE 32
33:
1.14 schwarze 34: static int a2time(time_t *, const char *, const char *);
1.22 schwarze 35: static char *time2a(time_t);
1.26 schwarze 36: static int numescape(const char *);
1.5 schwarze 37:
1.26 schwarze 38: /*
39: * Pass over recursive numerical expressions. This context of this
40: * function is important: it's only called within character-terminating
41: * escapes (e.g., \s[xxxyyy]), so all we need to do is handle initial
42: * recursion: we don't care about what's in these blocks.
43: * This returns the number of characters skipped or -1 if an error
44: * occurs (the caller should bail).
45: */
46: static int
47: numescape(const char *start)
1.1 schwarze 48: {
1.26 schwarze 49: int i;
50: size_t sz;
51: const char *cp;
52:
53: i = 0;
54:
55: /* The expression consists of a subexpression. */
56:
57: if ('\\' == start[i]) {
58: cp = &start[++i];
59: /*
60: * Read past the end of the subexpression.
61: * Bail immediately on errors.
62: */
63: if (ESCAPE_ERROR == mandoc_escape(&cp, NULL, NULL))
64: return(-1);
65: return(i + cp - &start[i]);
66: }
67:
68: if ('(' != start[i++])
69: return(0);
1.14 schwarze 70:
1.26 schwarze 71: /*
72: * A parenthesised subexpression. Read until the closing
73: * parenthesis, making sure to handle any nested subexpressions
74: * that might ruin our parse.
75: */
76:
77: while (')' != start[i]) {
78: sz = strcspn(&start[i], ")\\");
79: i += (int)sz;
80:
81: if ('\0' == start[i])
82: return(-1);
83: else if ('\\' != start[i])
84: continue;
85:
86: cp = &start[++i];
87: if (ESCAPE_ERROR == mandoc_escape(&cp, NULL, NULL))
88: return(-1);
89: i += cp - &start[i];
90: }
91:
92: /* Read past the terminating ')'. */
93: return(++i);
94: }
95:
96: enum mandoc_esc
97: mandoc_escape(const char **end, const char **start, int *sz)
98: {
99: char c, term, numeric;
100: int i, lim, ssz, rlim;
101: const char *cp, *rstart;
102: enum mandoc_esc gly;
103:
104: cp = *end;
105: rstart = cp;
106: if (start)
107: *start = rstart;
108: i = lim = 0;
109: gly = ESCAPE_ERROR;
110: term = numeric = '\0';
111:
112: switch ((c = cp[i++])) {
113: /*
114: * First the glyphs. There are several different forms of
115: * these, but each eventually returns a substring of the glyph
116: * name.
117: */
118: case ('('):
119: gly = ESCAPE_SPECIAL;
120: lim = 2;
121: break;
122: case ('['):
123: gly = ESCAPE_SPECIAL;
124: /*
125: * Unicode escapes are defined in groff as \[uXXXX] to
126: * \[u10FFFF], where the contained value must be a valid
127: * Unicode codepoint. Here, however, only check whether
128: * it's not a zero-width escape.
129: */
130: if ('u' == cp[i] && ']' != cp[i + 1])
131: gly = ESCAPE_UNICODE;
132: term = ']';
133: break;
134: case ('C'):
135: if ('\'' != cp[i])
136: return(ESCAPE_ERROR);
137: gly = ESCAPE_SPECIAL;
138: term = '\'';
139: break;
1.1 schwarze 140:
1.26 schwarze 141: /*
142: * Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where
143: * 'X' is the trigger. These have opaque sub-strings.
144: */
145: case ('F'):
1.16 schwarze 146: /* FALLTHROUGH */
1.26 schwarze 147: case ('g'):
1.16 schwarze 148: /* FALLTHROUGH */
1.26 schwarze 149: case ('k'):
1.1 schwarze 150: /* FALLTHROUGH */
1.26 schwarze 151: case ('M'):
1.14 schwarze 152: /* FALLTHROUGH */
1.26 schwarze 153: case ('m'):
1.1 schwarze 154: /* FALLTHROUGH */
1.26 schwarze 155: case ('n'):
1.1 schwarze 156: /* FALLTHROUGH */
1.26 schwarze 157: case ('V'):
1.1 schwarze 158: /* FALLTHROUGH */
1.26 schwarze 159: case ('Y'):
1.29 ! schwarze 160: gly = ESCAPE_IGNORE;
1.1 schwarze 161: /* FALLTHROUGH */
1.26 schwarze 162: case ('f'):
163: if (ESCAPE_ERROR == gly)
164: gly = ESCAPE_FONT;
165:
166: rstart= &cp[i];
167: if (start)
168: *start = rstart;
169:
170: switch (cp[i++]) {
171: case ('('):
172: lim = 2;
173: break;
174: case ('['):
175: term = ']';
176: break;
177: default:
178: lim = 1;
179: i--;
180: break;
181: }
182: break;
183:
184: /*
185: * These escapes are of the form \X'Y', where 'X' is the trigger
186: * and 'Y' is any string. These have opaque sub-strings.
187: */
188: case ('A'):
1.13 schwarze 189: /* FALLTHROUGH */
1.26 schwarze 190: case ('b'):
1.1 schwarze 191: /* FALLTHROUGH */
1.16 schwarze 192: case ('D'):
1.1 schwarze 193: /* FALLTHROUGH */
1.26 schwarze 194: case ('o'):
1.1 schwarze 195: /* FALLTHROUGH */
1.26 schwarze 196: case ('R'):
1.1 schwarze 197: /* FALLTHROUGH */
1.26 schwarze 198: case ('X'):
1.1 schwarze 199: /* FALLTHROUGH */
1.26 schwarze 200: case ('Z'):
201: if ('\'' != cp[i++])
202: return(ESCAPE_ERROR);
203: gly = ESCAPE_IGNORE;
1.16 schwarze 204: term = '\'';
205: break;
1.26 schwarze 206:
207: /*
208: * These escapes are of the form \X'N', where 'X' is the trigger
209: * and 'N' resolves to a numerical expression.
210: */
211: case ('B'):
212: /* FALLTHROUGH */
1.17 schwarze 213: case ('h'):
214: /* FALLTHROUGH */
1.26 schwarze 215: case ('H'):
216: /* FALLTHROUGH */
217: case ('L'):
218: /* FALLTHROUGH */
219: case ('l'):
1.29 ! schwarze 220: gly = ESCAPE_NUMBERED;
1.26 schwarze 221: /* FALLTHROUGH */
222: case ('S'):
223: /* FALLTHROUGH */
1.17 schwarze 224: case ('v'):
225: /* FALLTHROUGH */
1.26 schwarze 226: case ('w'):
227: /* FALLTHROUGH */
228: case ('x'):
229: if (ESCAPE_ERROR == gly)
230: gly = ESCAPE_IGNORE;
231: if ('\'' != cp[i++])
232: return(ESCAPE_ERROR);
233: term = numeric = '\'';
234: break;
1.29 ! schwarze 235:
! 236: /*
! 237: * Special handling for the numbered character escape.
! 238: * XXX Do any other escapes need similar handling?
! 239: */
! 240: case ('N'):
! 241: if ('\0' == cp[i])
! 242: return(ESCAPE_ERROR);
! 243: *end = &cp[++i];
! 244: if (isdigit((unsigned char)cp[i-1]))
! 245: return(ESCAPE_IGNORE);
! 246: while (isdigit((unsigned char)**end))
! 247: (*end)++;
! 248: if (start)
! 249: *start = &cp[i];
! 250: if (sz)
! 251: *sz = *end - &cp[i];
! 252: if ('\0' != **end)
! 253: (*end)++;
! 254: return(ESCAPE_NUMBERED);
1.26 schwarze 255:
256: /*
257: * Sizes get a special category of their own.
258: */
1.6 schwarze 259: case ('s'):
1.26 schwarze 260: gly = ESCAPE_IGNORE;
1.17 schwarze 261:
1.26 schwarze 262: rstart = &cp[i];
263: if (start)
264: *start = rstart;
265:
266: /* See +/- counts as a sign. */
267: c = cp[i];
268: if ('+' == c || '-' == c || ASCII_HYPH == c)
269: ++i;
1.6 schwarze 270:
1.26 schwarze 271: switch (cp[i++]) {
1.16 schwarze 272: case ('('):
1.26 schwarze 273: lim = 2;
1.16 schwarze 274: break;
275: case ('['):
1.26 schwarze 276: term = numeric = ']';
1.16 schwarze 277: break;
278: case ('\''):
1.26 schwarze 279: term = numeric = '\'';
1.16 schwarze 280: break;
281: default:
1.26 schwarze 282: lim = 1;
283: i--;
1.16 schwarze 284: break;
1.6 schwarze 285: }
286:
1.26 schwarze 287: /* See +/- counts as a sign. */
288: c = cp[i];
289: if ('+' == c || '-' == c || ASCII_HYPH == c)
290: ++i;
291:
292: break;
293:
294: /*
295: * Anything else is assumed to be a glyph.
296: */
297: default:
298: gly = ESCAPE_SPECIAL;
299: lim = 1;
300: i--;
301: break;
302: }
303:
304: assert(ESCAPE_ERROR != gly);
305:
306: rstart = &cp[i];
307: if (start)
308: *start = rstart;
309:
310: /*
311: * If a terminating block has been specified, we need to
312: * handle the case of recursion, which could have their
313: * own terminating blocks that mess up our parse. This, by the
314: * way, means that the "start" and "size" values will be
315: * effectively meaningless.
316: */
317:
318: ssz = 0;
319: if (numeric && -1 == (ssz = numescape(&cp[i])))
320: return(ESCAPE_ERROR);
321:
322: i += ssz;
323: rlim = -1;
324:
325: /*
326: * We have a character terminator. Try to read up to that
327: * character. If we can't (i.e., we hit the nil), then return
328: * an error; if we can, calculate our length, read past the
329: * terminating character, and exit.
330: */
331:
332: if ('\0' != term) {
333: *end = strchr(&cp[i], term);
334: if ('\0' == *end)
335: return(ESCAPE_ERROR);
336:
337: rlim = *end - &cp[i];
338: if (sz)
339: *sz = rlim;
340: (*end)++;
341: goto out;
342: }
343:
344: assert(lim > 0);
345:
346: /*
347: * We have a numeric limit. If the string is shorter than that,
348: * stop and return an error. Else adjust our endpoint, length,
349: * and return the current glyph.
350: */
351:
352: if ((size_t)lim > strlen(&cp[i]))
353: return(ESCAPE_ERROR);
354:
355: rlim = lim;
356: if (sz)
357: *sz = rlim;
1.19 schwarze 358:
1.26 schwarze 359: *end = &cp[i] + lim;
360:
361: out:
362: assert(rlim >= 0 && rstart);
1.19 schwarze 363:
1.26 schwarze 364: /* Run post-processors. */
1.19 schwarze 365:
1.26 schwarze 366: switch (gly) {
367: case (ESCAPE_FONT):
368: if (1 != rlim)
369: break;
370: switch (*rstart) {
371: case ('3'):
372: /* FALLTHROUGH */
373: case ('B'):
374: gly = ESCAPE_FONTBOLD;
375: break;
376: case ('2'):
377: /* FALLTHROUGH */
378: case ('I'):
379: gly = ESCAPE_FONTITALIC;
1.16 schwarze 380: break;
1.26 schwarze 381: case ('P'):
382: gly = ESCAPE_FONTPREV;
1.16 schwarze 383: break;
1.26 schwarze 384: case ('1'):
385: /* FALLTHROUGH */
386: case ('R'):
387: gly = ESCAPE_FONTROMAN;
1.1 schwarze 388: break;
389: }
1.16 schwarze 390: break;
1.26 schwarze 391: case (ESCAPE_SPECIAL):
392: if (1 != rlim)
393: break;
394: if ('c' == *rstart)
395: gly = ESCAPE_NOSPACE;
1.16 schwarze 396: break;
1.1 schwarze 397: default:
1.16 schwarze 398: break;
1.1 schwarze 399: }
400:
1.26 schwarze 401: return(gly);
1.1 schwarze 402: }
403:
1.4 schwarze 404: void *
405: mandoc_calloc(size_t num, size_t size)
406: {
407: void *ptr;
408:
409: ptr = calloc(num, size);
410: if (NULL == ptr) {
411: perror(NULL);
1.20 schwarze 412: exit((int)MANDOCLEVEL_SYSERR);
1.4 schwarze 413: }
414:
415: return(ptr);
416: }
417:
418:
419: void *
420: mandoc_malloc(size_t size)
421: {
422: void *ptr;
423:
424: ptr = malloc(size);
425: if (NULL == ptr) {
426: perror(NULL);
1.20 schwarze 427: exit((int)MANDOCLEVEL_SYSERR);
1.4 schwarze 428: }
429:
430: return(ptr);
431: }
432:
433:
434: void *
435: mandoc_realloc(void *ptr, size_t size)
436: {
437:
438: ptr = realloc(ptr, size);
439: if (NULL == ptr) {
440: perror(NULL);
1.20 schwarze 441: exit((int)MANDOCLEVEL_SYSERR);
1.4 schwarze 442: }
443:
444: return(ptr);
445: }
446:
1.27 schwarze 447: char *
448: mandoc_strndup(const char *ptr, size_t sz)
449: {
450: char *p;
451:
452: p = mandoc_malloc(sz + 1);
453: memcpy(p, ptr, sz);
454: p[(int)sz] = '\0';
455: return(p);
456: }
1.4 schwarze 457:
458: char *
459: mandoc_strdup(const char *ptr)
460: {
461: char *p;
462:
463: p = strdup(ptr);
464: if (NULL == p) {
465: perror(NULL);
1.20 schwarze 466: exit((int)MANDOCLEVEL_SYSERR);
1.4 schwarze 467: }
468:
469: return(p);
1.21 schwarze 470: }
471:
472: /*
473: * Parse a quoted or unquoted roff-style request or macro argument.
474: * Return a pointer to the parsed argument, which is either the original
475: * pointer or advanced by one byte in case the argument is quoted.
476: * Null-terminate the argument in place.
477: * Collapse pairs of quotes inside quoted arguments.
478: * Advance the argument pointer to the next argument,
479: * or to the null byte terminating the argument line.
480: */
481: char *
1.25 schwarze 482: mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
1.21 schwarze 483: {
484: char *start, *cp;
485: int quoted, pairs, white;
486:
487: /* Quoting can only start with a new word. */
488: start = *cpp;
1.26 schwarze 489: quoted = 0;
1.21 schwarze 490: if ('"' == *start) {
491: quoted = 1;
492: start++;
1.26 schwarze 493: }
1.21 schwarze 494:
495: pairs = 0;
496: white = 0;
497: for (cp = start; '\0' != *cp; cp++) {
498: /* Move left after quoted quotes and escaped backslashes. */
499: if (pairs)
500: cp[-pairs] = cp[0];
501: if ('\\' == cp[0]) {
502: if ('\\' == cp[1]) {
503: /* Poor man's copy mode. */
504: pairs++;
505: cp++;
506: } else if (0 == quoted && ' ' == cp[1])
507: /* Skip escaped blanks. */
508: cp++;
509: } else if (0 == quoted) {
510: if (' ' == cp[0]) {
511: /* Unescaped blanks end unquoted args. */
512: white = 1;
513: break;
514: }
515: } else if ('"' == cp[0]) {
516: if ('"' == cp[1]) {
517: /* Quoted quotes collapse. */
518: pairs++;
519: cp++;
520: } else {
521: /* Unquoted quotes end quoted args. */
522: quoted = 2;
523: break;
524: }
525: }
526: }
527:
528: /* Quoted argument without a closing quote. */
1.25 schwarze 529: if (1 == quoted)
530: mandoc_msg(MANDOCERR_BADQUOTE, parse, ln, *pos, NULL);
1.21 schwarze 531:
532: /* Null-terminate this argument and move to the next one. */
533: if (pairs)
534: cp[-pairs] = '\0';
535: if ('\0' != *cp) {
536: *cp++ = '\0';
537: while (' ' == *cp)
538: cp++;
539: }
1.24 schwarze 540: *pos += (int)(cp - start) + (quoted ? 1 : 0);
1.21 schwarze 541: *cpp = cp;
542:
1.25 schwarze 543: if ('\0' == *cp && (white || ' ' == cp[-1]))
544: mandoc_msg(MANDOCERR_EOLNSPACE, parse, ln, *pos, NULL);
1.21 schwarze 545:
546: return(start);
1.4 schwarze 547: }
1.5 schwarze 548:
549: static int
550: a2time(time_t *t, const char *fmt, const char *p)
551: {
552: struct tm tm;
553: char *pp;
554:
555: memset(&tm, 0, sizeof(struct tm));
556:
557: pp = strptime(p, fmt, &tm);
558: if (NULL != pp && '\0' == *pp) {
559: *t = mktime(&tm);
560: return(1);
561: }
562:
563: return(0);
564: }
565:
1.22 schwarze 566: static char *
567: time2a(time_t t)
568: {
1.28 schwarze 569: struct tm *tm;
1.23 schwarze 570: char *buf, *p;
571: size_t ssz;
1.22 schwarze 572: int isz;
573:
1.28 schwarze 574: tm = localtime(&t);
1.22 schwarze 575:
1.23 schwarze 576: /*
577: * Reserve space:
578: * up to 9 characters for the month (September) + blank
579: * up to 2 characters for the day + comma + blank
580: * 4 characters for the year and a terminating '\0'
581: */
582: p = buf = mandoc_malloc(10 + 4 + 4 + 1);
583:
1.28 schwarze 584: if (0 == (ssz = strftime(p, 10 + 1, "%B ", tm)))
1.23 schwarze 585: goto fail;
586: p += (int)ssz;
1.22 schwarze 587:
1.28 schwarze 588: if (-1 == (isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday)))
1.23 schwarze 589: goto fail;
1.22 schwarze 590: p += isz;
591:
1.28 schwarze 592: if (0 == strftime(p, 4 + 1, "%Y", tm))
1.23 schwarze 593: goto fail;
594: return(buf);
595:
596: fail:
597: free(buf);
598: return(NULL);
1.22 schwarze 599: }
600:
601: char *
1.25 schwarze 602: mandoc_normdate(struct mparse *parse, char *in, int ln, int pos)
1.5 schwarze 603: {
1.22 schwarze 604: char *out;
1.5 schwarze 605: time_t t;
606:
1.22 schwarze 607: if (NULL == in || '\0' == *in ||
608: 0 == strcmp(in, "$" "Mdocdate$")) {
1.25 schwarze 609: mandoc_msg(MANDOCERR_NODATE, parse, ln, pos, NULL);
1.22 schwarze 610: time(&t);
611: }
612: else if (!a2time(&t, "$" "Mdocdate: %b %d %Y $", in) &&
613: !a2time(&t, "%b %d, %Y", in) &&
614: !a2time(&t, "%Y-%m-%d", in)) {
1.25 schwarze 615: mandoc_msg(MANDOCERR_BADDATE, parse, ln, pos, NULL);
1.22 schwarze 616: t = 0;
1.5 schwarze 617: }
1.22 schwarze 618: out = t ? time2a(t) : NULL;
1.23 schwarze 619: return(out ? out : mandoc_strdup(in));
1.5 schwarze 620: }
621:
1.9 schwarze 622: int
1.15 schwarze 623: mandoc_eos(const char *p, size_t sz, int enclosed)
1.9 schwarze 624: {
1.15 schwarze 625: const char *q;
1.16 schwarze 626: int found;
1.9 schwarze 627:
1.10 schwarze 628: if (0 == sz)
629: return(0);
1.9 schwarze 630:
1.11 schwarze 631: /*
632: * End-of-sentence recognition must include situations where
633: * some symbols, such as `)', allow prior EOS punctuation to
1.26 schwarze 634: * propagate outward.
1.11 schwarze 635: */
636:
1.16 schwarze 637: found = 0;
638: for (q = p + (int)sz - 1; q >= p; q--) {
1.15 schwarze 639: switch (*q) {
1.11 schwarze 640: case ('\"'):
641: /* FALLTHROUGH */
642: case ('\''):
643: /* FALLTHROUGH */
644: case (']'):
645: /* FALLTHROUGH */
646: case (')'):
1.15 schwarze 647: if (0 == found)
648: enclosed = 1;
1.11 schwarze 649: break;
650: case ('.'):
651: /* FALLTHROUGH */
652: case ('!'):
653: /* FALLTHROUGH */
654: case ('?'):
1.15 schwarze 655: found = 1;
656: break;
1.11 schwarze 657: default:
1.20 schwarze 658: return(found && (!enclosed || isalnum((unsigned char)*q)));
1.11 schwarze 659: }
1.9 schwarze 660: }
661:
1.15 schwarze 662: return(found && !enclosed);
1.24 schwarze 663: }
664:
665: /*
1.25 schwarze 666: * Find out whether a line is a macro line or not. If it is, adjust the
667: * current position and return one; if it isn't, return zero and don't
668: * change the current position.
1.24 schwarze 669: */
1.25 schwarze 670: int
671: mandoc_getcontrol(const char *cp, int *ppos)
1.24 schwarze 672: {
1.25 schwarze 673: int pos;
1.24 schwarze 674:
1.25 schwarze 675: pos = *ppos;
1.24 schwarze 676:
1.25 schwarze 677: if ('\\' == cp[pos] && '.' == cp[pos + 1])
678: pos += 2;
679: else if ('.' == cp[pos] || '\'' == cp[pos])
680: pos++;
681: else
682: return(0);
1.24 schwarze 683:
1.25 schwarze 684: while (' ' == cp[pos] || '\t' == cp[pos])
685: pos++;
1.24 schwarze 686:
1.25 schwarze 687: *ppos = pos;
688: return(1);
1.9 schwarze 689: }
1.26 schwarze 690:
691: /*
692: * Convert a string to a long that may not be <0.
693: * If the string is invalid, or is less than 0, return -1.
694: */
695: int
1.27 schwarze 696: mandoc_strntoi(const char *p, size_t sz, int base)
1.26 schwarze 697: {
698: char buf[32];
699: char *ep;
700: long v;
701:
702: if (sz > 31)
703: return(-1);
704:
705: memcpy(buf, p, sz);
706: buf[(int)sz] = '\0';
707:
708: errno = 0;
709: v = strtol(buf, &ep, base);
710:
711: if (buf[0] == '\0' || *ep != '\0')
712: return(-1);
713:
1.27 schwarze 714: if (v > INT_MAX)
715: v = INT_MAX;
716: if (v < INT_MIN)
717: v = INT_MIN;
1.26 schwarze 718:
719: return((int)v);
720: }