source: git/src/readval.c @ 20e064b

RELEASE/1.2debug-cidebug-ci-sanitiserswalls-datawalls-data-hanging-as-warning
Last change on this file since 20e064b was 734f5f4, checked in by Olly Betts <olly@…>, 6 years ago

Warn about 2 digit years being assumed to be 19xx

We can't change the assumption without risking breaking existing
datasets, but the further we get into this century, the more likely such
an assumption is to catch someone out. The warning can easily be
quashed by explicitly adding the assumed "19".

  • Property mode set to 100644
File size: 15.4 KB
Line 
1/* readval.c
2 * Routines to read a prefix or number from the current input file
3 * Copyright (C) 1991-2003,2005,2006,2010,2011,2012,2013,2014,2015,2016,2018 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 "date.h"
29#include "debug.h"
30#include "filename.h"
31#include "message.h"
32#include "readval.h"
33#include "datain.h"
34#include "netbits.h"
35#include "osalloc.h"
36#include "str.h"
37
38#ifdef HAVE_SETJMP_H
39# define LONGJMP(JB) longjmp((JB), 1)
40#else
41# define LONGJMP(JB) exit(1)
42#endif
43
44int root_depr_count = 0;
45
46static prefix *
47new_anon_station(void)
48{
49    prefix *name = osnew(prefix);
50    name->pos = NULL;
51    name->ident = NULL;
52    name->shape = 0;
53    name->stn = NULL;
54    name->up = pcs->Prefix;
55    name->down = NULL;
56    name->filename = file.filename;
57    name->line = file.line;
58    name->min_export = name->max_export = 0;
59    name->sflags = BIT(SFLAGS_ANON);
60    /* Keep linked list of anon stations for node stats. */
61    name->right = anon_list;
62    anon_list = name;
63    return name;
64}
65
66/* if prefix is omitted: if PFX_OPT set return NULL, otherwise use longjmp */
67extern prefix *
68read_prefix(unsigned pfx_flags)
69{
70   bool f_optional = !!(pfx_flags & PFX_OPT);
71   bool fSurvey = !!(pfx_flags & PFX_SURVEY);
72   bool fSuspectTypo = !!(pfx_flags & PFX_SUSPECT_TYPO);
73   prefix *back_ptr, *ptr;
74   char *name;
75   size_t name_len = 32;
76   size_t i;
77   bool fNew;
78   bool fImplicitPrefix = fTrue;
79   int depth = -1;
80   filepos fp_firstsep;
81
82   skipblanks();
83#ifndef NO_DEPRECATED
84   if (isRoot(ch)) {
85      if (!(pfx_flags & PFX_ALLOW_ROOT)) {
86         compile_diagnostic(DIAG_ERR|DIAG_COL, /*ROOT is deprecated*/25);
87         LONGJMP(file.jbSkipLine);
88      }
89      if (root_depr_count < 5) {
90         compile_diagnostic(DIAG_WARN|DIAG_COL, /*ROOT is deprecated*/25);
91         if (++root_depr_count == 5)
92            compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
93      }
94      nextch();
95      ptr = root;
96      if (!isNames(ch)) {
97         if (!isSep(ch)) return ptr;
98         /* Allow optional SEPARATOR after ROOT */
99         get_pos(&fp_firstsep);
100         nextch();
101      }
102      fImplicitPrefix = fFalse;
103#else
104   if (0) {
105#endif
106   } else {
107      if ((pfx_flags & PFX_ANON) &&
108          (isSep(ch) || (pcs->dash_for_anon_wall_station && ch == '-'))) {
109         int first_ch = ch;
110         filepos here;
111         get_pos(&here);
112         nextch();
113         if (isBlank(ch) || isEol(ch)) {
114            if (!isSep(first_ch))
115               goto anon_wall_station;
116            /* A single separator alone ('.' by default) is an anonymous
117             * station which is on a point inside the passage and implies
118             * the leg to it is a splay.
119             */
120            if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
121               set_pos(&here);
122               compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Can't have a leg between two anonymous stations*/3);
123               LONGJMP(file.jbSkipLine);
124            }
125            pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
126            return new_anon_station();
127         }
128         if (isSep(first_ch) && ch == first_ch) {
129            nextch();
130            if (isBlank(ch) || isEol(ch)) {
131               /* A double separator ('..' by default) is an anonymous station
132                * which is on the wall and implies the leg to it is a splay.
133                */
134               prefix * pfx;
135anon_wall_station:
136               if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
137                  set_pos(&here);
138                  compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Can't have a leg between two anonymous stations*/3);
139                  LONGJMP(file.jbSkipLine);
140               }
141               pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
142               pfx = new_anon_station();
143               pfx->sflags |= BIT(SFLAGS_WALL);
144               return pfx;
145            }
146            if (ch == first_ch) {
147               nextch();
148               if (isBlank(ch) || isEol(ch)) {
149                  /* A triple separator ('...' by default) is an anonymous
150                   * station, but otherwise not handled specially (e.g. for
151                   * a single leg down an unexplored side passage to a station
152                   * which isn't refindable).
153                   */
154                  if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
155                     set_pos(&here);
156                     compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Can't have a leg between two anonymous stations*/3);
157                     LONGJMP(file.jbSkipLine);
158                  }
159                  pcs->flags |= BIT(FLAGS_ANON_ONE_END);
160                  return new_anon_station();
161               }
162            }
163         }
164         set_pos(&here);
165      }
166      ptr = pcs->Prefix;
167   }
168
169   i = 0;
170   name = NULL;
171   do {
172      fNew = fFalse;
173      if (name == NULL) {
174         /* Need a new name buffer */
175         name = osmalloc(name_len);
176      }
177      /* i==0 iff this is the first pass */
178      if (i) {
179         i = 0;
180         nextch();
181      }
182      while (isNames(ch)) {
183         if (i < pcs->Truncate) {
184            /* truncate name */
185            name[i++] = (pcs->Case == LOWER ? tolower(ch) :
186                         (pcs->Case == OFF ? ch : toupper(ch)));
187            if (i >= name_len) {
188               name_len = name_len + name_len;
189               name = osrealloc(name, name_len);
190            }
191         }
192         nextch();
193      }
194      if (isSep(ch)) {
195         fImplicitPrefix = fFalse;
196         get_pos(&fp_firstsep);
197      }
198      if (i == 0) {
199         osfree(name);
200         if (!f_optional) {
201            if (isEol(ch)) {
202               if (fSurvey) {
203                  compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting survey name*/89);
204               } else {
205                  compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting station name*/28);
206               }
207            } else {
208               /* TRANSLATORS: Here "station" is a survey station, not a train station. */
209               compile_diagnostic(DIAG_ERR|DIAG_COL, /*Character “%c” not allowed in station name (use *SET NAMES to set allowed characters)*/7, ch);
210            }
211            LONGJMP(file.jbSkipLine);
212         }
213         return (prefix *)NULL;
214      }
215
216      name[i++] = '\0';
217
218      back_ptr = ptr;
219      ptr = ptr->down;
220      if (ptr == NULL) {
221         /* Special case first time around at each level */
222         name = osrealloc(name, i);
223         ptr = osnew(prefix);
224         ptr->ident = name;
225         name = NULL;
226         ptr->right = ptr->down = NULL;
227         ptr->pos = NULL;
228         ptr->shape = 0;
229         ptr->stn = NULL;
230         ptr->up = back_ptr;
231         ptr->filename = file.filename;
232         ptr->line = file.line;
233         ptr->min_export = ptr->max_export = 0;
234         ptr->sflags = BIT(SFLAGS_SURVEY);
235         if (fSuspectTypo && !fImplicitPrefix)
236            ptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
237         back_ptr->down = ptr;
238         fNew = fTrue;
239      } else {
240         /* Use caching to speed up adding an increasing sequence to a
241          * large survey */
242         static prefix *cached_survey = NULL, *cached_station = NULL;
243         prefix *ptrPrev = NULL;
244         int cmp = 1; /* result of strcmp ( -ve for <, 0 for =, +ve for > ) */
245         if (cached_survey == back_ptr) {
246            cmp = strcmp(cached_station->ident, name);
247            if (cmp <= 0) ptr = cached_station;
248         }
249         while (ptr && (cmp = strcmp(ptr->ident, name))<0) {
250            ptrPrev = ptr;
251            ptr = ptr->right;
252         }
253         if (cmp) {
254            /* ie we got to one that was higher, or the end */
255            prefix *newptr;
256            name = osrealloc(name, i);
257            newptr = osnew(prefix);
258            newptr->ident = name;
259            name = NULL;
260            if (ptrPrev == NULL)
261               back_ptr->down = newptr;
262            else
263               ptrPrev->right = newptr;
264            newptr->right = ptr;
265            newptr->down = NULL;
266            newptr->pos = NULL;
267            newptr->shape = 0;
268            newptr->stn = NULL;
269            newptr->up = back_ptr;
270            newptr->filename = file.filename;
271            newptr->line = file.line;
272            newptr->min_export = newptr->max_export = 0;
273            newptr->sflags = BIT(SFLAGS_SURVEY);
274            if (fSuspectTypo && !fImplicitPrefix)
275               newptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
276            ptr = newptr;
277            fNew = fTrue;
278         }
279         cached_survey = back_ptr;
280         cached_station = ptr;
281      }
282      depth++;
283      f_optional = fFalse; /* disallow after first level */
284      if (isSep(ch)) get_pos(&fp_firstsep);
285   } while (isSep(ch));
286   if (name) osfree(name);
287
288   /* don't warn about a station that is referred to twice */
289   if (!fNew) ptr->sflags &= ~BIT(SFLAGS_SUSPECTTYPO);
290
291   if (fNew) {
292      /* fNew means SFLAGS_SURVEY is currently set */
293      SVX_ASSERT(TSTBIT(ptr->sflags, SFLAGS_SURVEY));
294      if (!fSurvey) {
295         ptr->sflags &= ~BIT(SFLAGS_SURVEY);
296         if (TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
297      }
298   } else {
299      /* check that the same name isn't being used for a survey and station */
300      if (fSurvey ^ TSTBIT(ptr->sflags, SFLAGS_SURVEY)) {
301         /* TRANSLATORS: Here "station" is a survey station, not a train station.
302          *
303          * Here "survey" is a "cave map" rather than list of questions - it should be
304          * translated to the terminology that cavers using the language would use.
305          */
306         compile_diagnostic(DIAG_ERR, /*“%s” can’t be both a station and a survey*/27,
307                            sprint_prefix(ptr));
308      }
309      if (!fSurvey && TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
310   }
311
312   /* check the export level */
313#if 0
314   printf("R min %d max %d depth %d pfx %s\n",
315          ptr->min_export, ptr->max_export, depth, sprint_prefix(ptr));
316#endif
317   if (ptr->min_export == 0 || ptr->min_export == USHRT_MAX) {
318      if (depth > ptr->max_export) ptr->max_export = depth;
319   } else if (ptr->max_export < depth) {
320      prefix *survey = ptr;
321      char *s;
322      const char *p;
323      int level;
324      for (level = ptr->max_export + 1; level; level--) {
325         survey = survey->up;
326         SVX_ASSERT(survey);
327      }
328      s = osstrdup(sprint_prefix(survey));
329      p = sprint_prefix(ptr);
330      if (survey->filename) {
331         compile_diagnostic_pfx(DIAG_ERR, survey,
332                                /*Station “%s” not exported from survey “%s”*/26,
333                                p, s);
334      } else {
335         compile_diagnostic(DIAG_ERR, /*Station “%s” not exported from survey “%s”*/26, p, s);
336      }
337      osfree(s);
338#if 0
339      printf(" *** pfx %s warning not exported enough depth %d "
340             "ptr->max_export %d\n", sprint_prefix(ptr),
341             depth, ptr->max_export);
342#endif
343   }
344   if (!fImplicitPrefix && (pfx_flags & PFX_WARN_SEPARATOR)) {
345      filepos fp_tmp;
346      get_pos(&fp_tmp);
347      set_pos(&fp_firstsep);
348      compile_diagnostic(DIAG_WARN|DIAG_COL, /*Separator in survey name*/392);
349      set_pos(&fp_tmp);
350   }
351   return ptr;
352}
353
354/* if numeric expr is omitted: if f_optional return HUGE_REAL, else longjmp */
355static real
356read_number(bool f_optional)
357{
358   bool fPositive, fDigits = fFalse;
359   real n = (real)0.0;
360   filepos fp;
361   int ch_old;
362
363   get_pos(&fp);
364   ch_old = ch;
365   fPositive = !isMinus(ch);
366   if (isSign(ch)) nextch();
367
368   while (isdigit(ch)) {
369      n = n * (real)10.0 + (char)(ch - '0');
370      nextch();
371      fDigits = fTrue;
372   }
373
374   if (isDecimal(ch)) {
375      real mult = (real)1.0;
376      nextch();
377      while (isdigit(ch)) {
378         mult *= (real).1;
379         n += (char)(ch - '0') * mult;
380         fDigits = fTrue;
381         nextch();
382      }
383   }
384
385   /* !'fRead' => !fDigits so fDigits => 'fRead' */
386   if (fDigits) return (fPositive ? n : -n);
387
388   /* didn't read a valid number.  If it's optional, reset filepos & return */
389   set_pos(&fp);
390   if (f_optional) {
391      return HUGE_REAL;
392   }
393
394   if (isOmit(ch_old)) {
395      compile_diagnostic(DIAG_ERR|DIAG_COL, /*Field may not be omitted*/8);
396   } else {
397      compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
398   }
399   LONGJMP(file.jbSkipLine);
400   return 0.0; /* for brain-fried compilers */
401}
402
403extern real
404read_numeric(bool f_optional)
405{
406   skipblanks();
407   return read_number(f_optional);
408}
409
410extern real
411read_numeric_multi(bool f_optional, int *p_n_readings)
412{
413   size_t n_readings = 0;
414   real tot = (real)0.0;
415
416   skipblanks();
417   if (!isOpen(ch)) {
418      real r = read_number(f_optional);
419      if (p_n_readings) *p_n_readings = (r == HUGE_REAL ? 0 : 1);
420      return r;
421   }
422   nextch();
423
424   skipblanks();
425   do {
426      tot += read_number(fFalse);
427      ++n_readings;
428      skipblanks();
429   } while (!isClose(ch));
430   nextch();
431
432   if (p_n_readings) *p_n_readings = n_readings;
433   /* FIXME: special averaging for bearings ... */
434   /* And for percentage gradient */
435   return tot / n_readings;
436}
437
438/* read numeric expr or omit (return HUGE_REAL); else longjmp */
439extern real
440read_numeric_multi_or_omit(int *p_n_readings)
441{
442   real v = read_numeric_multi(fTrue, p_n_readings);
443   if (v == HUGE_REAL) {
444      if (!isOmit(ch)) {
445         compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
446         LONGJMP(file.jbSkipLine);
447         return 0.0; /* for brain-fried compilers */
448      }
449      nextch();
450   }
451   return v;
452}
453
454/* Don't skip blanks, variable error code */
455static unsigned int
456read_uint_internal(int errmsg, const filepos *fp)
457{
458   unsigned int n = 0;
459   if (!isdigit(ch)) {
460      if (fp) set_pos(fp);
461      compile_diagnostic_token_show(DIAG_ERR, errmsg);
462      LONGJMP(file.jbSkipLine);
463   }
464   while (isdigit(ch)) {
465      n = n * 10 + (char)(ch - '0');
466      nextch();
467   }
468   return n;
469}
470
471extern unsigned int
472read_uint(void)
473{
474   skipblanks();
475   return read_uint_internal(/*Expecting numeric field, found “%s”*/9, NULL);
476}
477
478extern void
479read_string(char **pstr, int *plen)
480{
481   s_zero(pstr);
482
483   skipblanks();
484   if (ch == '\"') {
485      /* String quoted in "" */
486      nextch();
487      while (1) {
488         if (isEol(ch)) {
489            compile_diagnostic(DIAG_ERR|DIAG_COL, /*Missing \"*/69);
490            LONGJMP(file.jbSkipLine);
491         }
492
493         if (ch == '\"') break;
494
495         s_catchar(pstr, plen, ch);
496         nextch();
497      }
498      nextch();
499   } else {
500      /* Unquoted string */
501      while (1) {
502         if (isEol(ch) || isComm(ch)) {
503            if (!*pstr || !(*pstr)[0]) {
504               compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting string field*/121);
505               LONGJMP(file.jbSkipLine);
506            }
507            return;
508         }
509
510         if (isBlank(ch)) break;
511
512         s_catchar(pstr, plen, ch);
513         nextch();
514      }
515   }
516}
517
518extern void
519read_date(int *py, int *pm, int *pd)
520{
521   unsigned int y = 0, m = 0, d = 0;
522   filepos fp_date;
523
524   skipblanks();
525
526   get_pos(&fp_date);
527   y = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
528   /* Two digit year is 19xx. */
529   if (y < 100) {
530      filepos fp_save;
531      get_pos(&fp_save);
532      y += 1900;
533      set_pos(&fp_date);
534      /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
535      compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Assuming 2 digit year is %d*/76, y);
536      set_pos(&fp_save);
537   }
538   if (y < 1900 || y > 2078) {
539      set_pos(&fp_date);
540      compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year (< 1900 or > 2078)*/58);
541      LONGJMP(file.jbSkipLine);
542      return; /* for brain-fried compilers */
543   }
544   if (ch == '.') {
545      filepos fp;
546      nextch();
547      get_pos(&fp);
548      m = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
549      if (m < 1 || m > 12) {
550         set_pos(&fp);
551         compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
552         LONGJMP(file.jbSkipLine);
553         return; /* for brain-fried compilers */
554      }
555      if (ch == '.') {
556         nextch();
557         get_pos(&fp);
558         d = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
559         if (d < 1 || d > last_day(y, m)) {
560            set_pos(&fp);
561            /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
562            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid day of the month*/87);
563            LONGJMP(file.jbSkipLine);
564            return; /* for brain-fried compilers */
565         }
566      }
567   }
568   if (py) *py = y;
569   if (pm) *pm = m;
570   if (pd) *pd = d;
571}
Note: See TracBrowser for help on using the repository browser.