source: git/src/readval.c @ ca7261b

stereo-2025
Last change on this file since ca7261b was 1f34f48, checked in by Olly Betts <olly@…>, 12 months ago

Handle an empty Walls station name

Walls allows a station with an explicit prefix to have an empty name,
e.g. PEP:. The Walls documentation doesn't mention this, though it
also doesn't explicitly say the name can't be empty. This quirk seems
unlikely to be intentionally used and Survex doesn't allow an empty
station name, so we issue a warning and use the name empty name (which
has a space in, so can't collide with a real Walls station name which
can't contain a space) - so PEP: in Walls becomes PEP.empty name in
Survex.

Reported by Erik C. Landgraf.

  • Property mode set to 100644
File size: 27.8 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#include <config.h>
21
22#include <limits.h>
23#include <stddef.h> /* for offsetof */
24
25#include "cavern.h"
26#include "commands.h" /* For match_tok(), etc */
27#include "date.h"
28#include "debug.h"
29#include "filename.h"
30#include "message.h"
31#include "readval.h"
32#include "datain.h"
33#include "netbits.h"
34#include "osalloc.h"
35#include "str.h"
36
37#ifdef HAVE_SETJMP_H
38# define LONGJMP(JB) longjmp((JB), 1)
39#else
40# define LONGJMP(JB) exit(1)
41#endif
42
43int root_depr_count = 0;
44
45static prefix *
46new_anon_station(void)
47{
48    prefix *name = osnew(prefix);
49    name->pos = NULL;
50    name->ident = NULL;
51    name->shape = 0;
52    name->stn = NULL;
53    name->up = pcs->Prefix;
54    name->down = NULL;
55    name->filename = file.filename;
56    name->line = file.line;
57    name->min_export = name->max_export = 0;
58    name->sflags = BIT(SFLAGS_ANON);
59    /* Keep linked list of anon stations for node stats. */
60    name->right = anon_list;
61    anon_list = name;
62    return name;
63}
64
65/* if prefix is omitted: if PFX_OPT set return NULL, otherwise use longjmp */
66extern prefix *
67read_prefix(unsigned pfx_flags)
68{
69   bool f_optional = !!(pfx_flags & PFX_OPT);
70   bool fSurvey = !!(pfx_flags & PFX_SURVEY);
71   bool fSuspectTypo = !!(pfx_flags & PFX_SUSPECT_TYPO);
72   prefix *back_ptr, *ptr;
73   char *name;
74   size_t name_len = 32;
75   size_t i;
76   bool fNew;
77   bool fImplicitPrefix = true;
78   int depth = -1;
79   filepos here;
80   filepos fp_firstsep;
81
82   skipblanks();
83   get_pos(&here);
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_INFO, /*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         nextch();
112         if (isBlank(ch) || isComm(ch) || isEol(ch)) {
113            if (!isSep(first_ch))
114               goto anon_wall_station;
115            /* A single separator alone ('.' by default) is an anonymous
116             * station which is on a point inside the passage and implies
117             * the leg to it is a splay.
118             */
119            if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
120               set_pos(&here);
121               compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Can't have a leg between two anonymous stations*/3);
122               LONGJMP(file.jbSkipLine);
123            }
124            pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
125            return new_anon_station();
126         }
127         if (isSep(first_ch) && ch == first_ch) {
128            nextch();
129            if (isBlank(ch) || isComm(ch) || isEol(ch)) {
130               /* A double separator ('..' by default) is an anonymous station
131                * which is on the wall and implies the leg to it is a splay.
132                */
133               prefix * pfx;
134anon_wall_station:
135               if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
136                  set_pos(&here);
137                  compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Can't have a leg between two anonymous stations*/3);
138                  LONGJMP(file.jbSkipLine);
139               }
140               pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
141               pfx = new_anon_station();
142               pfx->sflags |= BIT(SFLAGS_WALL);
143               return pfx;
144            }
145            if (ch == first_ch) {
146               nextch();
147               if (isBlank(ch) || isComm(ch) || isEol(ch)) {
148                  /* A triple separator ('...' by default) is an anonymous
149                   * station, but otherwise not handled specially (e.g. for
150                   * a single leg down an unexplored side passage to a station
151                   * which isn't refindable).
152                   */
153                  if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
154                     set_pos(&here);
155                     compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Can't have a leg between two anonymous stations*/3);
156                     LONGJMP(file.jbSkipLine);
157                  }
158                  pcs->flags |= BIT(FLAGS_ANON_ONE_END);
159                  return new_anon_station();
160               }
161            }
162         }
163         set_pos(&here);
164      }
165      ptr = pcs->Prefix;
166   }
167
168   i = 0;
169   name = NULL;
170   do {
171      fNew = false;
172      if (name == NULL) {
173         /* Need a new name buffer */
174         name = osmalloc(name_len);
175      }
176      /* i==0 iff this is the first pass */
177      if (i) {
178         i = 0;
179         nextch();
180      }
181      while (isNames(ch)) {
182         if (i < pcs->Truncate) {
183            /* truncate name */
184            name[i++] = (pcs->Case == LOWER ? tolower(ch) :
185                         (pcs->Case == OFF ? ch : toupper(ch)));
186            if (i >= name_len) {
187               name_len = name_len + name_len;
188               name = osrealloc(name, name_len);
189            }
190         }
191         nextch();
192      }
193      if (isSep(ch)) {
194         fImplicitPrefix = false;
195         get_pos(&fp_firstsep);
196      }
197      if (i == 0) {
198         osfree(name);
199         if (!f_optional) {
200            if (isEol(ch)) {
201               if (fSurvey) {
202                  compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting survey name*/89);
203               } else {
204                  compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting station name*/28);
205               }
206            } else {
207               /* TRANSLATORS: Here "station" is a survey station, not a train station. */
208               compile_diagnostic(DIAG_ERR|DIAG_COL, /*Character “%c” not allowed in station name (use *SET NAMES to set allowed characters)*/7, ch);
209            }
210            LONGJMP(file.jbSkipLine);
211         }
212         return (prefix *)NULL;
213      }
214
215      name[i++] = '\0';
216
217      back_ptr = ptr;
218      ptr = ptr->down;
219      if (ptr == NULL) {
220         /* Special case first time around at each level */
221         name = osrealloc(name, i);
222         ptr = osnew(prefix);
223         ptr->ident = name;
224         name = NULL;
225         ptr->right = ptr->down = NULL;
226         ptr->pos = NULL;
227         ptr->shape = 0;
228         ptr->stn = NULL;
229         ptr->up = back_ptr;
230         ptr->filename = file.filename;
231         ptr->line = file.line;
232         ptr->min_export = ptr->max_export = 0;
233         ptr->sflags = BIT(SFLAGS_SURVEY);
234         if (fSuspectTypo && !fImplicitPrefix)
235            ptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
236         back_ptr->down = ptr;
237         fNew = true;
238      } else {
239         /* Use caching to speed up adding an increasing sequence to a
240          * large survey */
241         static prefix *cached_survey = NULL, *cached_station = NULL;
242         prefix *ptrPrev = NULL;
243         int cmp = 1; /* result of strcmp ( -ve for <, 0 for =, +ve for > ) */
244         if (cached_survey == back_ptr) {
245            cmp = strcmp(cached_station->ident, name);
246            if (cmp <= 0) ptr = cached_station;
247         }
248         while (ptr && (cmp = strcmp(ptr->ident, name)) < 0) {
249            ptrPrev = ptr;
250            ptr = ptr->right;
251         }
252         if (cmp) {
253            /* ie we got to one that was higher, or the end */
254            prefix *newptr;
255            name = osrealloc(name, i);
256            newptr = osnew(prefix);
257            newptr->ident = name;
258            name = NULL;
259            if (ptrPrev == NULL)
260               back_ptr->down = newptr;
261            else
262               ptrPrev->right = newptr;
263            newptr->right = ptr;
264            newptr->down = NULL;
265            newptr->pos = NULL;
266            newptr->shape = 0;
267            newptr->stn = NULL;
268            newptr->up = back_ptr;
269            newptr->filename = file.filename;
270            newptr->line = file.line;
271            newptr->min_export = newptr->max_export = 0;
272            newptr->sflags = BIT(SFLAGS_SURVEY);
273            if (fSuspectTypo && !fImplicitPrefix)
274               newptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
275            ptr = newptr;
276            fNew = true;
277         }
278         cached_survey = back_ptr;
279         cached_station = ptr;
280      }
281      depth++;
282      f_optional = false; /* disallow after first level */
283      if (isSep(ch)) {
284         get_pos(&fp_firstsep);
285         if (!TSTBIT(ptr->sflags, SFLAGS_SURVEY)) {
286            /* TRANSLATORS: Here "station" is a survey station, not a train station.
287             *
288             * Here "survey" is a "cave map" rather than list of questions - it should be
289             * translated to the terminology that cavers using the language would use.
290             */
291            compile_diagnostic(DIAG_ERR|DIAG_FROM(here), /*“%s” can’t be both a station and a survey*/27,
292                               sprint_prefix(ptr));
293         }
294      }
295   } while (isSep(ch));
296   if (name) osfree(name);
297
298   /* don't warn about a station that is referred to twice */
299   if (!fNew) ptr->sflags &= ~BIT(SFLAGS_SUSPECTTYPO);
300
301   if (fNew) {
302      /* fNew means SFLAGS_SURVEY is currently set */
303      SVX_ASSERT(TSTBIT(ptr->sflags, SFLAGS_SURVEY));
304      if (!fSurvey) {
305         ptr->sflags &= ~BIT(SFLAGS_SURVEY);
306         if (TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
307      }
308   } else {
309      /* check that the same name isn't being used for a survey and station */
310      if (fSurvey ^ TSTBIT(ptr->sflags, SFLAGS_SURVEY)) {
311         /* TRANSLATORS: Here "station" is a survey station, not a train station.
312          *
313          * Here "survey" is a "cave map" rather than list of questions - it should be
314          * translated to the terminology that cavers using the language would use.
315          */
316         compile_diagnostic(DIAG_ERR|DIAG_FROM(here), /*“%s” can’t be both a station and a survey*/27,
317                            sprint_prefix(ptr));
318      }
319      if (!fSurvey && TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
320   }
321
322   /* check the export level */
323#if 0
324   printf("R min %d max %d depth %d pfx %s\n",
325          ptr->min_export, ptr->max_export, depth, sprint_prefix(ptr));
326#endif
327   if (ptr->min_export == 0 || ptr->min_export == USHRT_MAX) {
328      if (depth > ptr->max_export) ptr->max_export = depth;
329   } else if (ptr->max_export < depth) {
330      prefix *survey = ptr;
331      char *s;
332      const char *p;
333      int level;
334      for (level = ptr->max_export + 1; level; level--) {
335         survey = survey->up;
336         SVX_ASSERT(survey);
337      }
338      s = osstrdup(sprint_prefix(survey));
339      p = sprint_prefix(ptr);
340      if (survey->filename) {
341         compile_diagnostic_pfx(DIAG_ERR, survey,
342                                /*Station “%s” not exported from survey “%s”*/26,
343                                p, s);
344      } else {
345         compile_diagnostic(DIAG_ERR, /*Station “%s” not exported from survey “%s”*/26, p, s);
346      }
347      osfree(s);
348#if 0
349      printf(" *** pfx %s warning not exported enough depth %d "
350             "ptr->max_export %d\n", sprint_prefix(ptr),
351             depth, ptr->max_export);
352#endif
353   }
354   if (!fImplicitPrefix && (pfx_flags & PFX_WARN_SEPARATOR)) {
355      filepos fp_tmp;
356      get_pos(&fp_tmp);
357      set_pos(&fp_firstsep);
358      compile_diagnostic(DIAG_WARN|DIAG_COL, /*Separator in survey name*/392);
359      set_pos(&fp_tmp);
360   }
361   return ptr;
362}
363
364char *
365read_walls_prefix(void)
366{
367    string name = S_INIT;
368    skipblanks();
369    if (!isNames(ch))
370        return NULL;
371    do {
372        s_catchar(&name, ch);
373        nextch();
374    } while (isNames(ch));
375    return s_steal(&name);
376}
377
378prefix *
379read_walls_station(char * const walls_prefix[3], bool anon_allowed)
380{
381//    bool f_optional = false; //!!(pfx_flags & PFX_OPT);
382//    bool fSuspectTypo = false; //!!(pfx_flags & PFX_SUSPECT_TYPO);
383//    prefix *back_ptr, *ptr;
384    string component = S_INIT;
385//    size_t i;
386//    bool fNew;
387//    bool fImplicitPrefix = true;
388//    int depth = -1;
389//    filepos fp_firstsep;
390
391    filepos fp;
392    get_pos(&fp);
393
394    skipblanks();
395    if (anon_allowed && ch == '-') {
396        // - or -- is an anonymous wall point in a shot, but in #Fix they seem
397        // to just be treated as ordinary station names.
398        // FIXME: Issue warning for such a useless station?
399        //
400        // Not yet checked, but you can presumably use - and -- as a prefix
401        // (FIXME check this).
402        nextch();
403        int dashes = 1;
404        if (ch == '-') {
405            ++dashes;
406            nextch();
407        }
408        if (!isNames(ch) && ch != ':') {
409            // An anonymous station implies the leg it is on is a splay.
410            if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
411                set_pos(&fp);
412                // Walls also rejects this case.
413                compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Can't have a leg between two anonymous stations*/3);
414                LONGJMP(file.jbSkipLine);
415            }
416            pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
417            prefix *pfx = new_anon_station();
418            pfx->sflags |= BIT(SFLAGS_WALL);
419            return pfx;
420        }
421        s_catn(&component, dashes, '-');
422    }
423
424    char *w_prefix[3] = { NULL, NULL, NULL };
425    int explicit_prefix_levels = 0;
426    while (true) {
427        while (isNames(ch)) {
428            s_catchar(&component, ch);
429            nextch();
430        }
431        //printf("component = '%s'\n", s_str(&component));
432        if (ch == ':') {
433            nextch();
434
435            if (++explicit_prefix_levels > 3) {
436                // FIXME Make this a proper error
437                printf("too many prefix levels\n");
438                s_free(&component);
439                for (int i = 0; i < 3; ++i) osfree(w_prefix[i]);
440                LONGJMP(file.jbSkipLine);
441            }
442
443            if (!s_empty(&component)) {
444                // printf("w_prefix[%d] = '%s'\n", explicit_prefix_levels - 1, s_str(&component));
445                w_prefix[explicit_prefix_levels - 1] = s_steal(&component);
446            }
447
448            continue;
449        }
450
451        // printf("explicit_prefix_levels=%d %s:%s:%s\n", explicit_prefix_levels, w_prefix[0], w_prefix[1], w_prefix[2]);
452
453        // component is the station name itself.
454        if (s_empty(&component)) {
455            if (explicit_prefix_levels == 0) {
456                compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting station name*/28);
457                s_free(&component);
458                for (int i = 0; i < 3; ++i) osfree(w_prefix[i]);
459                LONGJMP(file.jbSkipLine);
460            }
461            // Walls allows an empty station name if there's an explicit prefix.
462            // This seems unlikely to be intended, so warn about it.
463            compile_diagnostic(DIAG_WARN|DIAG_COL, /*Expecting station name*/28);
464            // Use a name with a space in so it can't collide with a real
465            // Walls station name.
466            s_cat(&component, "empty name");
467        }
468        int len = s_len(&component);
469        char *p = s_steal(&component);
470        // Apply case treatment.
471        switch (pcs->Case) {
472          case LOWER:
473            for (int i = 0; i < len; ++i)
474                p[i] = tolower((unsigned char)p[i]);
475            break;
476          case UPPER:
477            for (int i = 0; i < len; ++i)
478                p[i] = toupper((unsigned char)p[i]);
479            break;
480          case OFF:
481            // Avoid unhandled enum warning.
482            break;
483        }
484
485        prefix *ptr = root;
486        for (int i = 0; i < 4; ++i) {
487            const char *name;
488            int sflag = BIT(SFLAGS_SURVEY);
489            if (i == 3) {
490                name = p;
491                // Mark stations used with explicit prefix as exported.
492                sflag = explicit_prefix_levels ? BIT(SFLAGS_EXPORTED) : 0;
493            } else {
494                if (i < 3 - explicit_prefix_levels) {
495                    name = walls_prefix[i];
496                    // printf("using walls_prefix[%d] = '%s'\n", 2 - i, name);
497                } else {
498                    name = w_prefix[i - (3 - explicit_prefix_levels)]; // FIXME: Could steal wprefix[i].
499                    // printf("using w_prefix[%d] = '%s'\n", i - (3 - explicit_prefix_levels), name);
500                }
501
502                if (name == NULL) {
503                    // FIXME: This means :X::Y is treated as the same as
504                    // ::X:Y but is that right?  Walls docs don't really
505                    // say.  Need to test (and is they're different then
506                    // probably use a character not valid in Walls station
507                    // names for the empty prefix level (e.g. space or
508                    // `#`).
509                    //
510                    // Also, does Walls allow :::X as a station and
511                    // ::X:Y which would mean X is a station and survey?
512                    // If so, we probably want to keep every empty level.
513                    continue;
514                }
515            }
516            prefix *back_ptr = ptr;
517            ptr = ptr->down;
518            if (ptr == NULL) {
519                /* Special case first time around at each level */
520                ptr = osnew(prefix);
521                ptr->ident = (i < 3 ? osstrdup(name) : name);
522                name = NULL;
523                ptr->right = ptr->down = NULL;
524                ptr->pos = NULL;
525                ptr->shape = 0;
526                ptr->stn = NULL;
527                ptr->up = back_ptr;
528                ptr->filename = file.filename; // FIXME: Or location of #Prefix, etc for it?
529                ptr->line = file.line; // FIXME: Or location of #Prefix, etc for it?
530                ptr->min_export = ptr->max_export = 0;
531                ptr->sflags = sflag;
532                back_ptr->down = ptr;
533            } else {
534                /* Use caching to speed up adding an increasing sequence to a
535                 * large survey */
536                static prefix *cached_survey = NULL, *cached_station = NULL;
537                prefix *ptrPrev = NULL;
538                int cmp = 1; /* result of strcmp ( -ve for <, 0 for =, +ve for > ) */
539                if (cached_survey == back_ptr) {
540                    cmp = strcmp(cached_station->ident, name);
541                    if (cmp <= 0) ptr = cached_station;
542                }
543                while (ptr && (cmp = strcmp(ptr->ident, name))<0) {
544                    ptrPrev = ptr;
545                    ptr = ptr->right;
546                }
547                if (cmp) {
548                    /* ie we got to one that was higher, or the end */
549                    prefix *newptr;
550                    newptr = osnew(prefix);
551                    newptr->ident = (i < 3 ? osstrdup(name) : name);
552                    name = NULL;
553                    if (ptrPrev == NULL)
554                        back_ptr->down = newptr;
555                    else
556                        ptrPrev->right = newptr;
557                    newptr->right = ptr;
558                    newptr->down = NULL;
559                    newptr->pos = NULL;
560                    newptr->shape = 0;
561                    newptr->stn = NULL;
562                    newptr->up = back_ptr;
563                    newptr->filename = file.filename; // FIXME
564                    newptr->line = file.line;
565                    newptr->min_export = newptr->max_export = 0;
566                    newptr->sflags = sflag;
567                    ptr = newptr;
568                } else {
569                    ptr->sflags |= sflag;
570                }
571                cached_survey = back_ptr;
572                cached_station = ptr;
573            }
574            if (name == p) osfree(p);
575        }
576
577        // fprint_prefix(stdout, ptr); fputnl(stdout);
578
579        for (int i = 0; i < 3; ++i) osfree(w_prefix[i]);
580
581        return ptr;
582    }
583}
584
585/* if numeric expr is omitted: if f_optional return HUGE_REAL, else longjmp */
586real
587read_number(bool f_optional, bool f_unsigned)
588{
589   bool fPositive = true, fDigits = false;
590   real n = (real)0.0;
591   filepos fp;
592   int ch_old;
593
594   get_pos(&fp);
595   ch_old = ch;
596   if (!f_unsigned) {
597      fPositive = !isMinus(ch);
598      if (isSign(ch)) nextch();
599   }
600
601   while (isdigit(ch)) {
602      n = n * (real)10.0 + (char)(ch - '0');
603      nextch();
604      fDigits = true;
605   }
606
607   if (isDecimal(ch)) {
608      real mult = (real)1.0;
609      nextch();
610      while (isdigit(ch)) {
611         mult *= (real).1;
612         n += (char)(ch - '0') * mult;
613         fDigits = true;
614         nextch();
615      }
616   }
617
618   /* !'fRead' => !fDigits so fDigits => 'fRead' */
619   if (fDigits) return (fPositive ? n : -n);
620
621   /* didn't read a valid number.  If it's optional, reset filepos & return */
622   set_pos(&fp);
623   if (f_optional) {
624      return HUGE_REAL;
625   }
626
627   if (isOmit(ch_old)) {
628      compile_diagnostic(DIAG_ERR|DIAG_COL, /*Field may not be omitted*/8);
629   } else {
630      compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
631   }
632   LONGJMP(file.jbSkipLine);
633   return 0.0; /* for brain-fried compilers */
634}
635
636real
637read_quadrant(bool f_optional)
638{
639   enum {
640      POINT_N = 0,
641      POINT_E = 1,
642      POINT_S = 2,
643      POINT_W = 3,
644      POINT_NONE = -1
645   };
646   static const sztok pointtab[] = {
647        {"E", POINT_E },
648        {"N", POINT_N },
649        {"S", POINT_S },
650        {"W", POINT_W },
651        {NULL, POINT_NONE }
652   };
653   static const sztok pointewtab[] = {
654        {"E", POINT_E },
655        {"W", POINT_W },
656        {NULL, POINT_NONE }
657   };
658   if (f_optional && isOmit(ch)) {
659      return HUGE_REAL;
660   }
661   const int quad = 90;
662   filepos fp;
663   get_pos(&fp);
664   get_token_no_blanks();
665   int first_point = match_tok(pointtab, TABSIZE(pointtab));
666   if (first_point == POINT_NONE) {
667      set_pos(&fp);
668      if (isOmit(ch)) {
669         compile_diagnostic(DIAG_ERR|DIAG_COL, /*Field may not be omitted*/8);
670      }
671      compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
672      LONGJMP(file.jbSkipLine);
673      return 0.0; /* for brain-fried compilers */
674   }
675   real r = read_number(true, true);
676   if (r == HUGE_REAL) {
677      if (isSign(ch) || isDecimal(ch)) {
678         /* Give better errors for S-0E, N+10W, N.E, etc. */
679         set_pos(&fp);
680         compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
681         LONGJMP(file.jbSkipLine);
682         return 0.0; /* for brain-fried compilers */
683      }
684      /* N, S, E or W. */
685      return first_point * quad;
686   }
687   if (first_point == POINT_E || first_point == POINT_W) {
688      set_pos(&fp);
689      compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
690      LONGJMP(file.jbSkipLine);
691      return 0.0; /* for brain-fried compilers */
692   }
693
694   get_token_no_blanks();
695   int second_point = match_tok(pointewtab, TABSIZE(pointewtab));
696   if (second_point == POINT_NONE) {
697      set_pos(&fp);
698      compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
699      LONGJMP(file.jbSkipLine);
700      return 0.0; /* for brain-fried compilers */
701   }
702
703   if (r > quad) {
704      set_pos(&fp);
705      compile_diagnostic_token_show(DIAG_ERR|DIAG_COL, /*Suspicious compass reading*/59);
706      LONGJMP(file.jbSkipLine);
707      return 0.0; /* for brain-fried compilers */
708   }
709
710   if (first_point == POINT_N) {
711      if (second_point == POINT_W) {
712         r = quad * 4 - r;
713      }
714   } else {
715      if (second_point == POINT_W) {
716         r += quad * 2;
717      } else {
718         r = quad * 2 - r;
719      }
720   }
721   return r;
722}
723
724extern real
725read_numeric(bool f_optional)
726{
727   skipblanks();
728   return read_number(f_optional, false);
729}
730
731extern real
732read_numeric_multi(bool f_optional, bool f_quadrants, int *p_n_readings)
733{
734   size_t n_readings = 0;
735   real tot = (real)0.0;
736
737   skipblanks();
738   if (!isOpen(ch)) {
739      real r = 0;
740      if (!f_quadrants)
741          r = read_number(f_optional, false);
742      else
743          r = read_quadrant(f_optional);
744      if (p_n_readings) *p_n_readings = (r == HUGE_REAL ? 0 : 1);
745      return r;
746   }
747   nextch();
748
749   skipblanks();
750   do {
751      if (!f_quadrants)
752         tot += read_number(false, false);
753      else
754         tot += read_quadrant(false);
755      ++n_readings;
756      skipblanks();
757   } while (!isClose(ch));
758   nextch();
759
760   if (p_n_readings) *p_n_readings = n_readings;
761   /* FIXME: special averaging for bearings ... */
762   /* And for percentage gradient */
763   return tot / n_readings;
764}
765
766/* read numeric expr or omit (return HUGE_REAL); else longjmp */
767extern real
768read_bearing_multi_or_omit(bool f_quadrants, int *p_n_readings)
769{
770   real v;
771   v = read_numeric_multi(true, f_quadrants, p_n_readings);
772   if (v == HUGE_REAL) {
773      if (!isOmit(ch)) {
774         compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
775         LONGJMP(file.jbSkipLine);
776         return 0.0; /* for brain-fried compilers */
777      }
778      nextch();
779   }
780   return v;
781}
782
783/* Don't skip blanks, variable error code */
784static unsigned int
785read_uint_internal(int errmsg, const filepos *fp)
786{
787   unsigned int n = 0;
788   if (!isdigit(ch)) {
789      if (fp) set_pos(fp);
790      compile_diagnostic_token_show(DIAG_ERR, errmsg);
791      LONGJMP(file.jbSkipLine);
792   }
793   while (isdigit(ch)) {
794      n = n * 10 + (char)(ch - '0');
795      nextch();
796   }
797   return n;
798}
799
800extern unsigned int
801read_uint(void)
802{
803   skipblanks();
804   return read_uint_internal(/*Expecting numeric field, found “%s”*/9, NULL);
805}
806
807extern int
808read_int(int min_val, int max_val)
809{
810    skipblanks();
811    unsigned n = 0;
812    filepos fp;
813
814    get_pos(&fp);
815    bool negated = isMinus(ch);
816    unsigned limit;
817    if (negated) {
818        limit = (unsigned)(min_val == INT_MIN ? INT_MIN : -min_val);
819    } else {
820        limit = (unsigned)max_val;
821    }
822    if (isSign(ch)) nextch();
823
824    if (!isdigit(ch)) {
825bad_value:
826        set_pos(&fp);
827        /* TRANSLATORS: The first %d will be replaced by the (inclusive) lower
828         * bound and the second by the (inclusive) upper bound, for example:
829         * Expecting integer in range -60 to 60
830         */
831        compile_diagnostic(DIAG_ERR|DIAG_NUM, /*Expecting integer in range %d to %d*/489);
832        LONGJMP(file.jbSkipLine);
833    }
834
835    while (isdigit(ch)) {
836        unsigned old_n = n;
837        n = n * 10 + (char)(ch - '0');
838        if (n > limit || n < old_n) {
839            goto bad_value;
840        }
841        nextch();
842    }
843    if (isDecimal(ch)) goto bad_value;
844
845    if (negated) {
846        if (n > (unsigned)INT_MAX) {
847            // Avoid unportable casting.
848            return INT_MIN;
849        }
850        return -(int)n;
851    }
852    return (int)n;
853}
854
855extern void
856read_string(string *pstr)
857{
858   s_clear(pstr);
859
860   skipblanks();
861   if (ch == '\"') {
862      /* String quoted in "" */
863      nextch();
864      while (1) {
865         if (isEol(ch)) {
866            compile_diagnostic(DIAG_ERR|DIAG_COL, /*Missing \"*/69);
867            LONGJMP(file.jbSkipLine);
868         }
869
870         if (ch == '\"') break;
871
872         s_catchar(pstr, ch);
873         nextch();
874      }
875      nextch();
876   } else {
877      /* Unquoted string */
878      while (1) {
879         if (isEol(ch) || isComm(ch)) {
880            if (s_empty(pstr)) {
881               compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting string field*/121);
882               LONGJMP(file.jbSkipLine);
883            }
884            return;
885         }
886
887         if (isBlank(ch)) break;
888
889         s_catchar(pstr, ch);
890         nextch();
891      }
892   }
893}
894
895extern void
896read_date(int *py, int *pm, int *pd)
897{
898   unsigned int y = 0, m = 0, d = 0;
899   filepos fp_date;
900
901   skipblanks();
902
903   get_pos(&fp_date);
904   y = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
905   /* Two digit year is 19xx. */
906   if (y < 100) {
907      filepos fp_save;
908      get_pos(&fp_save);
909      y += 1900;
910      set_pos(&fp_date);
911      /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
912      compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Assuming 2 digit year is %d*/76, y);
913      set_pos(&fp_save);
914   }
915   if (y < 1900 || y > 2078) {
916      set_pos(&fp_date);
917      compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year (< 1900 or > 2078)*/58);
918      LONGJMP(file.jbSkipLine);
919      return; /* for brain-fried compilers */
920   }
921   if (ch == '.') {
922      filepos fp;
923      nextch();
924      get_pos(&fp);
925      m = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
926      if (m < 1 || m > 12) {
927         set_pos(&fp);
928         compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
929         LONGJMP(file.jbSkipLine);
930         return; /* for brain-fried compilers */
931      }
932      if (ch == '.') {
933         nextch();
934         get_pos(&fp);
935         d = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
936         if (d < 1 || d > last_day(y, m)) {
937            set_pos(&fp);
938            /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
939            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid day of the month*/87);
940            LONGJMP(file.jbSkipLine);
941            return; /* for brain-fried compilers */
942         }
943      }
944   }
945   if (py) *py = y;
946   if (pm) *pm = m;
947   if (pd) *pd = d;
948}
949
950extern void
951read_walls_srv_date(int *py, int *pm, int *pd)
952{
953    skipblanks();
954
955    filepos fp_date;
956    get_pos(&fp_date);
957    unsigned y = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
958    int separator = -2;
959    if (ch == '-' || ch == '/') {
960        separator = ch;
961        nextch();
962    }
963    filepos fp_month;
964    get_pos(&fp_month);
965    unsigned m = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
966    if (ch == separator) {
967        nextch();
968    }
969    filepos fp_day;
970    get_pos(&fp_day);
971    unsigned d = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
972
973    filepos fp_year;
974    if (y < 100) {
975        // Walls recommends ISO 8601 date format (yyyy-mm-dd and seemingly the
976        // non-standard variant yyyy/mm/dd), but also accepts "some date formats
977        // common in the U.S. (mm/dd/yy, mm-dd-yyyy, etc.)"
978        unsigned tmp = y;
979        y = d;
980        fp_year = fp_day;
981        d = m;
982        fp_day = fp_month;
983        m = tmp;
984        fp_month = fp_date;
985
986        if (y < 100) {
987            // FIXME: Are all 2 digit years 19xx?
988            y += 1900;
989
990            filepos fp_save;
991            get_pos(&fp_save);
992            set_pos(&fp_year);
993            /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
994            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Assuming 2 digit year is %d*/76, y);
995            set_pos(&fp_save);
996        }
997    } else {
998        if (y < 1900 || y > 2078) {
999            set_pos(&fp_date);
1000            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year (< 1900 or > 2078)*/58);
1001            LONGJMP(file.jbSkipLine);
1002            return; /* for brain-fried compilers */
1003        }
1004        fp_year = fp_date;
1005    }
1006
1007    if (m < 1 || m > 12) {
1008        set_pos(&fp_month);
1009        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
1010        LONGJMP(file.jbSkipLine);
1011        return; /* for brain-fried compilers */
1012    }
1013
1014    if (d < 1 || d > last_day(y, m)) {
1015        set_pos(&fp_day);
1016        /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
1017        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid day of the month*/87);
1018        LONGJMP(file.jbSkipLine);
1019        return; /* for brain-fried compilers */
1020    }
1021
1022    if (py) *py = y;
1023    if (pm) *pm = m;
1024    if (pd) *pd = d;
1025}
Note: See TracBrowser for help on using the repository browser.