source: git/src/readval.c @ f0e31b0

debug-ci-sanitisersfaster-cavernloglog-selectwalls-datawalls-data-hanging-as-warningwarn-only-for-hanging-survey
Last change on this file since f0e31b0 was 0532954, checked in by Olly Betts <olly@…>, 7 months ago

Rework str.h

The previous implementation was O(n²) for a loop appending n characters
to the string. In practice the strings are typically very short for
.svx format, but for other formats they may be longer so it seems silly
to have this known inefficiency.

  • Property mode set to 100644
File size: 19.0 KB
Line 
1/* readval.c
2 * Routines to read a prefix or number from the current input file
3 * Copyright (C) 1991-2024 Olly Betts
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program 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 program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include <config.h>
22#endif
23
24#include <limits.h>
25#include <stddef.h> /* for offsetof */
26
27#include "cavern.h"
28#include "commands.h" /* For match_tok(), etc */
29#include "date.h"
30#include "debug.h"
31#include "filename.h"
32#include "message.h"
33#include "readval.h"
34#include "datain.h"
35#include "netbits.h"
36#include "osalloc.h"
37#include "str.h"
38
39#ifdef HAVE_SETJMP_H
40# define LONGJMP(JB) longjmp((JB), 1)
41#else
42# define LONGJMP(JB) exit(1)
43#endif
44
45int root_depr_count = 0;
46
47static prefix *
48new_anon_station(void)
49{
50    prefix *name = osnew(prefix);
51    name->pos = NULL;
52    name->ident = NULL;
53    name->shape = 0;
54    name->stn = NULL;
55    name->up = pcs->Prefix;
56    name->down = NULL;
57    name->filename = file.filename;
58    name->line = file.line;
59    name->min_export = name->max_export = 0;
60    name->sflags = BIT(SFLAGS_ANON);
61    /* Keep linked list of anon stations for node stats. */
62    name->right = anon_list;
63    anon_list = name;
64    return name;
65}
66
67/* if prefix is omitted: if PFX_OPT set return NULL, otherwise use longjmp */
68extern prefix *
69read_prefix(unsigned pfx_flags)
70{
71   bool f_optional = !!(pfx_flags & PFX_OPT);
72   bool fSurvey = !!(pfx_flags & PFX_SURVEY);
73   bool fSuspectTypo = !!(pfx_flags & PFX_SUSPECT_TYPO);
74   prefix *back_ptr, *ptr;
75   char *name;
76   size_t name_len = 32;
77   size_t i;
78   bool fNew;
79   bool fImplicitPrefix = true;
80   int depth = -1;
81   filepos fp_firstsep;
82
83   skipblanks();
84#ifndef NO_DEPRECATED
85   if (isRoot(ch)) {
86      if (!(pfx_flags & PFX_ALLOW_ROOT)) {
87         compile_diagnostic(DIAG_ERR|DIAG_COL, /*ROOT is deprecated*/25);
88         LONGJMP(file.jbSkipLine);
89      }
90      if (root_depr_count < 5) {
91         compile_diagnostic(DIAG_WARN|DIAG_COL, /*ROOT is deprecated*/25);
92         if (++root_depr_count == 5)
93            compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
94      }
95      nextch();
96      ptr = root;
97      if (!isNames(ch)) {
98         if (!isSep(ch)) return ptr;
99         /* Allow optional SEPARATOR after ROOT */
100         get_pos(&fp_firstsep);
101         nextch();
102      }
103      fImplicitPrefix = false;
104#else
105   if (0) {
106#endif
107   } else {
108      if ((pfx_flags & PFX_ANON) &&
109          (isSep(ch) || (pcs->dash_for_anon_wall_station && ch == '-'))) {
110         int first_ch = ch;
111         filepos here;
112         get_pos(&here);
113         nextch();
114         if (isBlank(ch) || isEol(ch)) {
115            if (!isSep(first_ch))
116               goto anon_wall_station;
117            /* A single separator alone ('.' by default) is an anonymous
118             * station which is on a point inside the passage and implies
119             * the leg to it is a splay.
120             */
121            if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
122               set_pos(&here);
123               compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Can't have a leg between two anonymous stations*/3);
124               LONGJMP(file.jbSkipLine);
125            }
126            pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
127            return new_anon_station();
128         }
129         if (isSep(first_ch) && ch == first_ch) {
130            nextch();
131            if (isBlank(ch) || isEol(ch)) {
132               /* A double separator ('..' by default) is an anonymous station
133                * which is on the wall and implies the leg to it is a splay.
134                */
135               prefix * pfx;
136anon_wall_station:
137               if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
138                  set_pos(&here);
139                  compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Can't have a leg between two anonymous stations*/3);
140                  LONGJMP(file.jbSkipLine);
141               }
142               pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
143               pfx = new_anon_station();
144               pfx->sflags |= BIT(SFLAGS_WALL);
145               return pfx;
146            }
147            if (ch == first_ch) {
148               nextch();
149               if (isBlank(ch) || isEol(ch)) {
150                  /* A triple separator ('...' by default) is an anonymous
151                   * station, but otherwise not handled specially (e.g. for
152                   * a single leg down an unexplored side passage to a station
153                   * which isn't refindable).
154                   */
155                  if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
156                     set_pos(&here);
157                     compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Can't have a leg between two anonymous stations*/3);
158                     LONGJMP(file.jbSkipLine);
159                  }
160                  pcs->flags |= BIT(FLAGS_ANON_ONE_END);
161                  return new_anon_station();
162               }
163            }
164         }
165         set_pos(&here);
166      }
167      ptr = pcs->Prefix;
168   }
169
170   i = 0;
171   name = NULL;
172   do {
173      fNew = false;
174      if (name == NULL) {
175         /* Need a new name buffer */
176         name = osmalloc(name_len);
177      }
178      /* i==0 iff this is the first pass */
179      if (i) {
180         i = 0;
181         nextch();
182      }
183      while (isNames(ch)) {
184         if (i < pcs->Truncate) {
185            /* truncate name */
186            name[i++] = (pcs->Case == LOWER ? tolower(ch) :
187                         (pcs->Case == OFF ? ch : toupper(ch)));
188            if (i >= name_len) {
189               name_len = name_len + name_len;
190               name = osrealloc(name, name_len);
191            }
192         }
193         nextch();
194      }
195      if (isSep(ch)) {
196         fImplicitPrefix = false;
197         get_pos(&fp_firstsep);
198      }
199      if (i == 0) {
200         osfree(name);
201         if (!f_optional) {
202            if (isEol(ch)) {
203               if (fSurvey) {
204                  compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting survey name*/89);
205               } else {
206                  compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting station name*/28);
207               }
208            } else {
209               /* TRANSLATORS: Here "station" is a survey station, not a train station. */
210               compile_diagnostic(DIAG_ERR|DIAG_COL, /*Character “%c” not allowed in station name (use *SET NAMES to set allowed characters)*/7, ch);
211            }
212            LONGJMP(file.jbSkipLine);
213         }
214         return (prefix *)NULL;
215      }
216
217      name[i++] = '\0';
218
219      back_ptr = ptr;
220      ptr = ptr->down;
221      if (ptr == NULL) {
222         /* Special case first time around at each level */
223         name = osrealloc(name, i);
224         ptr = osnew(prefix);
225         ptr->ident = name;
226         name = NULL;
227         ptr->right = ptr->down = NULL;
228         ptr->pos = NULL;
229         ptr->shape = 0;
230         ptr->stn = NULL;
231         ptr->up = back_ptr;
232         ptr->filename = file.filename;
233         ptr->line = file.line;
234         ptr->min_export = ptr->max_export = 0;
235         ptr->sflags = BIT(SFLAGS_SURVEY);
236         if (fSuspectTypo && !fImplicitPrefix)
237            ptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
238         back_ptr->down = ptr;
239         fNew = true;
240      } else {
241         /* Use caching to speed up adding an increasing sequence to a
242          * large survey */
243         static prefix *cached_survey = NULL, *cached_station = NULL;
244         prefix *ptrPrev = NULL;
245         int cmp = 1; /* result of strcmp ( -ve for <, 0 for =, +ve for > ) */
246         if (cached_survey == back_ptr) {
247            cmp = strcmp(cached_station->ident, name);
248            if (cmp <= 0) ptr = cached_station;
249         }
250         while (ptr && (cmp = strcmp(ptr->ident, name))<0) {
251            ptrPrev = ptr;
252            ptr = ptr->right;
253         }
254         if (cmp) {
255            /* ie we got to one that was higher, or the end */
256            prefix *newptr;
257            name = osrealloc(name, i);
258            newptr = osnew(prefix);
259            newptr->ident = name;
260            name = NULL;
261            if (ptrPrev == NULL)
262               back_ptr->down = newptr;
263            else
264               ptrPrev->right = newptr;
265            newptr->right = ptr;
266            newptr->down = NULL;
267            newptr->pos = NULL;
268            newptr->shape = 0;
269            newptr->stn = NULL;
270            newptr->up = back_ptr;
271            newptr->filename = file.filename;
272            newptr->line = file.line;
273            newptr->min_export = newptr->max_export = 0;
274            newptr->sflags = BIT(SFLAGS_SURVEY);
275            if (fSuspectTypo && !fImplicitPrefix)
276               newptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
277            ptr = newptr;
278            fNew = true;
279         }
280         cached_survey = back_ptr;
281         cached_station = ptr;
282      }
283      depth++;
284      f_optional = false; /* disallow after first level */
285      if (isSep(ch)) get_pos(&fp_firstsep);
286   } while (isSep(ch));
287   if (name) osfree(name);
288
289   /* don't warn about a station that is referred to twice */
290   if (!fNew) ptr->sflags &= ~BIT(SFLAGS_SUSPECTTYPO);
291
292   if (fNew) {
293      /* fNew means SFLAGS_SURVEY is currently set */
294      SVX_ASSERT(TSTBIT(ptr->sflags, SFLAGS_SURVEY));
295      if (!fSurvey) {
296         ptr->sflags &= ~BIT(SFLAGS_SURVEY);
297         if (TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
298      }
299   } else {
300      /* check that the same name isn't being used for a survey and station */
301      if (fSurvey ^ TSTBIT(ptr->sflags, SFLAGS_SURVEY)) {
302         /* TRANSLATORS: Here "station" is a survey station, not a train station.
303          *
304          * Here "survey" is a "cave map" rather than list of questions - it should be
305          * translated to the terminology that cavers using the language would use.
306          */
307         compile_diagnostic(DIAG_ERR, /*“%s” can’t be both a station and a survey*/27,
308                            sprint_prefix(ptr));
309      }
310      if (!fSurvey && TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
311   }
312
313   /* check the export level */
314#if 0
315   printf("R min %d max %d depth %d pfx %s\n",
316          ptr->min_export, ptr->max_export, depth, sprint_prefix(ptr));
317#endif
318   if (ptr->min_export == 0 || ptr->min_export == USHRT_MAX) {
319      if (depth > ptr->max_export) ptr->max_export = depth;
320   } else if (ptr->max_export < depth) {
321      prefix *survey = ptr;
322      char *s;
323      const char *p;
324      int level;
325      for (level = ptr->max_export + 1; level; level--) {
326         survey = survey->up;
327         SVX_ASSERT(survey);
328      }
329      s = osstrdup(sprint_prefix(survey));
330      p = sprint_prefix(ptr);
331      if (survey->filename) {
332         compile_diagnostic_pfx(DIAG_ERR, survey,
333                                /*Station “%s” not exported from survey “%s”*/26,
334                                p, s);
335      } else {
336         compile_diagnostic(DIAG_ERR, /*Station “%s” not exported from survey “%s”*/26, p, s);
337      }
338      osfree(s);
339#if 0
340      printf(" *** pfx %s warning not exported enough depth %d "
341             "ptr->max_export %d\n", sprint_prefix(ptr),
342             depth, ptr->max_export);
343#endif
344   }
345   if (!fImplicitPrefix && (pfx_flags & PFX_WARN_SEPARATOR)) {
346      filepos fp_tmp;
347      get_pos(&fp_tmp);
348      set_pos(&fp_firstsep);
349      compile_diagnostic(DIAG_WARN|DIAG_COL, /*Separator in survey name*/392);
350      set_pos(&fp_tmp);
351   }
352   return ptr;
353}
354
355/* if numeric expr is omitted: if f_optional return HUGE_REAL, else longjmp */
356static real
357read_number(bool f_optional, bool f_unsigned)
358{
359   bool fPositive = true, fDigits = false;
360   real n = (real)0.0;
361   filepos fp;
362   int ch_old;
363
364   get_pos(&fp);
365   ch_old = ch;
366   if (!f_unsigned) {
367      fPositive = !isMinus(ch);
368      if (isSign(ch)) nextch();
369   }
370
371   while (isdigit(ch)) {
372      n = n * (real)10.0 + (char)(ch - '0');
373      nextch();
374      fDigits = true;
375   }
376
377   if (isDecimal(ch)) {
378      real mult = (real)1.0;
379      nextch();
380      while (isdigit(ch)) {
381         mult *= (real).1;
382         n += (char)(ch - '0') * mult;
383         fDigits = true;
384         nextch();
385      }
386   }
387
388   /* !'fRead' => !fDigits so fDigits => 'fRead' */
389   if (fDigits) return (fPositive ? n : -n);
390
391   /* didn't read a valid number.  If it's optional, reset filepos & return */
392   set_pos(&fp);
393   if (f_optional) {
394      return HUGE_REAL;
395   }
396
397   if (isOmit(ch_old)) {
398      compile_diagnostic(DIAG_ERR|DIAG_COL, /*Field may not be omitted*/8);
399   } else {
400      compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
401   }
402   LONGJMP(file.jbSkipLine);
403   return 0.0; /* for brain-fried compilers */
404}
405
406static real
407read_quadrant(bool f_optional)
408{
409   enum {
410      POINT_N = 0,
411      POINT_E = 1,
412      POINT_S = 2,
413      POINT_W = 3,
414      POINT_NONE = -1
415   };
416   static const sztok pointtab[] = {
417        {"E", POINT_E },
418        {"N", POINT_N },
419        {"S", POINT_S },
420        {"W", POINT_W },
421        {NULL, POINT_NONE }
422   };
423   static const sztok pointewtab[] = {
424        {"E", POINT_E },
425        {"W", POINT_W },
426        {NULL, POINT_NONE }
427   };
428   if (f_optional && isOmit(ch)) {
429      return HUGE_REAL;
430   }
431   const int quad = 90;
432   filepos fp;
433   get_pos(&fp);
434   get_token_no_blanks();
435   int first_point = match_tok(pointtab, TABSIZE(pointtab));
436   if (first_point == POINT_NONE) {
437      set_pos(&fp);
438      if (isOmit(ch)) {
439         compile_diagnostic(DIAG_ERR|DIAG_COL, /*Field may not be omitted*/8);
440      }
441      compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
442      LONGJMP(file.jbSkipLine);
443      return 0.0; /* for brain-fried compilers */
444   }
445   real r = read_number(true, true);
446   if (r == HUGE_REAL) {
447      if (isSign(ch) || isDecimal(ch)) {
448         /* Give better errors for S-0E, N+10W, N.E, etc. */
449         set_pos(&fp);
450         compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
451         LONGJMP(file.jbSkipLine);
452         return 0.0; /* for brain-fried compilers */
453      }
454      /* N, S, E or W. */
455      return first_point * quad;
456   }
457   if (first_point == POINT_E || first_point == POINT_W) {
458      set_pos(&fp);
459      compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
460      LONGJMP(file.jbSkipLine);
461      return 0.0; /* for brain-fried compilers */
462   }
463
464   get_token_no_blanks();
465   int second_point = match_tok(pointewtab, TABSIZE(pointewtab));
466   if (second_point == POINT_NONE) {
467      set_pos(&fp);
468      compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
469      LONGJMP(file.jbSkipLine);
470      return 0.0; /* for brain-fried compilers */
471   }
472
473   if (r > quad) {
474      set_pos(&fp);
475      compile_diagnostic_token_show(DIAG_ERR|DIAG_COL, /*Suspicious compass reading*/59);
476      LONGJMP(file.jbSkipLine);
477      return 0.0; /* for brain-fried compilers */
478   }
479
480   if (first_point == POINT_N) {
481      if (second_point == POINT_W) {
482         r = quad * 4 - r;
483      }
484   } else {
485      if (second_point == POINT_W) {
486         r += quad * 2;
487      } else {
488         r = quad * 2 - r;
489      }
490   }
491   return r;
492}
493
494extern real
495read_numeric(bool f_optional)
496{
497   skipblanks();
498   return read_number(f_optional, false);
499}
500
501extern real
502read_numeric_multi(bool f_optional, bool f_quadrants, int *p_n_readings)
503{
504   size_t n_readings = 0;
505   real tot = (real)0.0;
506
507   skipblanks();
508   if (!isOpen(ch)) {
509      real r = 0;
510      if (!f_quadrants)
511          r = read_number(f_optional, false);
512      else
513          r = read_quadrant(f_optional);
514      if (p_n_readings) *p_n_readings = (r == HUGE_REAL ? 0 : 1);
515      return r;
516   }
517   nextch();
518
519   skipblanks();
520   do {
521      if (!f_quadrants)
522         tot += read_number(false, false);
523      else
524         tot += read_quadrant(false);
525      ++n_readings;
526      skipblanks();
527   } while (!isClose(ch));
528   nextch();
529
530   if (p_n_readings) *p_n_readings = n_readings;
531   /* FIXME: special averaging for bearings ... */
532   /* And for percentage gradient */
533   return tot / n_readings;
534}
535
536/* read numeric expr or omit (return HUGE_REAL); else longjmp */
537extern real
538read_bearing_multi_or_omit(bool f_quadrants, int *p_n_readings)
539{
540   real v;
541   v = read_numeric_multi(true, f_quadrants, p_n_readings);
542   if (v == HUGE_REAL) {
543      if (!isOmit(ch)) {
544         compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
545         LONGJMP(file.jbSkipLine);
546         return 0.0; /* for brain-fried compilers */
547      }
548      nextch();
549   }
550   return v;
551}
552
553/* Don't skip blanks, variable error code */
554static unsigned int
555read_uint_internal(int errmsg, const filepos *fp)
556{
557   unsigned int n = 0;
558   if (!isdigit(ch)) {
559      if (fp) set_pos(fp);
560      compile_diagnostic_token_show(DIAG_ERR, errmsg);
561      LONGJMP(file.jbSkipLine);
562   }
563   while (isdigit(ch)) {
564      n = n * 10 + (char)(ch - '0');
565      nextch();
566   }
567   return n;
568}
569
570extern unsigned int
571read_uint(void)
572{
573   skipblanks();
574   return read_uint_internal(/*Expecting numeric field, found “%s”*/9, NULL);
575}
576
577extern int
578read_int(int min_val, int max_val)
579{
580    bool negated = false;
581    skipblanks();
582    unsigned n = 0;
583    filepos fp;
584
585    get_pos(&fp);
586    negated = isMinus(ch);
587    unsigned limit = negated ? -(unsigned)min_val : (unsigned)max_val;
588    if (isSign(ch)) nextch();
589
590    if (!isdigit(ch)) {
591bad_value:
592        set_pos(&fp);
593        /* TRANSLATORS: The first %d will be replaced by the (inclusive) lower
594         * bound and the second by the (inclusive) upper bound, for example:
595         * Expecting integer in range -60 to 60
596         */
597        compile_diagnostic(DIAG_ERR|DIAG_NUM, /*Expecting integer in range %d to %d*/489);
598        LONGJMP(file.jbSkipLine);
599    }
600
601    while (isdigit(ch)) {
602        unsigned old_n = n;
603        n = n * 10 + (char)(ch - '0');
604        if (n > limit || n < old_n) {
605            goto bad_value;
606        }
607        nextch();
608    }
609    if (isDecimal(ch)) goto bad_value;
610
611    if (negated) {
612        if (n > (unsigned)INT_MAX) {
613            // Avoid unportable casting.
614            return INT_MIN;
615        }
616        return -(int)n;
617    }
618    return (int)n;
619}
620
621extern void
622read_string(string *pstr)
623{
624   s_clear(pstr);
625
626   skipblanks();
627   if (ch == '\"') {
628      /* String quoted in "" */
629      nextch();
630      while (1) {
631         if (isEol(ch)) {
632            compile_diagnostic(DIAG_ERR|DIAG_COL, /*Missing \"*/69);
633            LONGJMP(file.jbSkipLine);
634         }
635
636         if (ch == '\"') break;
637
638         s_catchar(pstr, ch);
639         nextch();
640      }
641      nextch();
642   } else {
643      /* Unquoted string */
644      while (1) {
645         if (isEol(ch) || isComm(ch)) {
646            if (s_empty(pstr)) {
647               compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting string field*/121);
648               LONGJMP(file.jbSkipLine);
649            }
650            return;
651         }
652
653         if (isBlank(ch)) break;
654
655         s_catchar(pstr, ch);
656         nextch();
657      }
658   }
659}
660
661extern void
662read_date(int *py, int *pm, int *pd)
663{
664   unsigned int y = 0, m = 0, d = 0;
665   filepos fp_date;
666
667   skipblanks();
668
669   get_pos(&fp_date);
670   y = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
671   /* Two digit year is 19xx. */
672   if (y < 100) {
673      filepos fp_save;
674      get_pos(&fp_save);
675      y += 1900;
676      set_pos(&fp_date);
677      /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
678      compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Assuming 2 digit year is %d*/76, y);
679      set_pos(&fp_save);
680   }
681   if (y < 1900 || y > 2078) {
682      set_pos(&fp_date);
683      compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year (< 1900 or > 2078)*/58);
684      LONGJMP(file.jbSkipLine);
685      return; /* for brain-fried compilers */
686   }
687   if (ch == '.') {
688      filepos fp;
689      nextch();
690      get_pos(&fp);
691      m = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
692      if (m < 1 || m > 12) {
693         set_pos(&fp);
694         compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
695         LONGJMP(file.jbSkipLine);
696         return; /* for brain-fried compilers */
697      }
698      if (ch == '.') {
699         nextch();
700         get_pos(&fp);
701         d = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
702         if (d < 1 || d > last_day(y, m)) {
703            set_pos(&fp);
704            /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
705            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid day of the month*/87);
706            LONGJMP(file.jbSkipLine);
707            return; /* for brain-fried compilers */
708         }
709      }
710   }
711   if (py) *py = y;
712   if (pm) *pm = m;
713   if (pd) *pd = d;
714}
Note: See TracBrowser for help on using the repository browser.