source: git/src/readval.c @ 236cfc4

stereo-2025
Last change on this file since 236cfc4 was 1bb4ba1, checked in by Olly Betts <olly@…>, 11 months ago

Adjust inferring of exports for Walls data

Do this like we do for native data which should mean exports interwork
better.

  • Property mode set to 100644
File size: 28.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#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                sflag = 0;
492            } else {
493                if (i < 3 - explicit_prefix_levels) {
494                    name = walls_prefix[i];
495                    // printf("using walls_prefix[%d] = '%s'\n", 2 - i, name);
496                } else {
497                    name = w_prefix[i - (3 - explicit_prefix_levels)]; // FIXME: Could steal wprefix[i].
498                    // printf("using w_prefix[%d] = '%s'\n", i - (3 - explicit_prefix_levels), name);
499                }
500
501                if (name == NULL) {
502                    // FIXME: This means :X::Y is treated as the same as
503                    // ::X:Y but is that right?  Walls docs don't really
504                    // say.  Need to test (and is they're different then
505                    // probably use a character not valid in Walls station
506                    // names for the empty prefix level (e.g. space or
507                    // `#`).
508                    //
509                    // Also, does Walls allow :::X as a station and
510                    // ::X:Y which would mean X is a station and survey?
511                    // If so, we probably want to keep every empty level.
512                    continue;
513                }
514            }
515            prefix *back_ptr = ptr;
516            ptr = ptr->down;
517            if (ptr == NULL) {
518                /* Special case first time around at each level */
519                ptr = osnew(prefix);
520                ptr->ident = (i < 3 ? osstrdup(name) : name);
521                name = NULL;
522                ptr->right = ptr->down = NULL;
523                ptr->pos = NULL;
524                ptr->shape = 0;
525                ptr->stn = NULL;
526                ptr->up = back_ptr;
527                ptr->filename = file.filename; // FIXME: Or location of #Prefix, etc for it?
528                ptr->line = file.line; // FIXME: Or location of #Prefix, etc for it?
529                ptr->min_export = ptr->max_export = 0;
530                ptr->sflags = sflag;
531                back_ptr->down = ptr;
532            } else {
533                /* Use caching to speed up adding an increasing sequence to a
534                 * large survey */
535                static prefix *cached_survey = NULL, *cached_station = NULL;
536                prefix *ptrPrev = NULL;
537                int cmp = 1; /* result of strcmp ( -ve for <, 0 for =, +ve for > ) */
538                if (cached_survey == back_ptr) {
539                    cmp = strcmp(cached_station->ident, name);
540                    if (cmp <= 0) ptr = cached_station;
541                }
542                while (ptr && (cmp = strcmp(ptr->ident, name))<0) {
543                    ptrPrev = ptr;
544                    ptr = ptr->right;
545                }
546                if (cmp) {
547                    /* ie we got to one that was higher, or the end */
548                    prefix *newptr;
549                    newptr = osnew(prefix);
550                    newptr->ident = (i < 3 ? osstrdup(name) : name);
551                    name = NULL;
552                    if (ptrPrev == NULL)
553                        back_ptr->down = newptr;
554                    else
555                        ptrPrev->right = newptr;
556                    newptr->right = ptr;
557                    newptr->down = NULL;
558                    newptr->pos = NULL;
559                    newptr->shape = 0;
560                    newptr->stn = NULL;
561                    newptr->up = back_ptr;
562                    newptr->filename = file.filename; // FIXME
563                    newptr->line = file.line;
564                    newptr->min_export = newptr->max_export = 0;
565                    newptr->sflags = sflag;
566                    ptr = newptr;
567                } else {
568                    ptr->sflags |= sflag;
569                }
570                if (!TSTBIT(ptr->sflags, SFLAGS_SURVEY)) {
571                    ptr->min_export = USHRT_MAX;
572                }
573                cached_survey = back_ptr;
574                cached_station = ptr;
575            }
576            if (name == p) osfree(p);
577        }
578
579        // Do the equivalent of "*infer exports" for Walls stations with an
580        // explicit prefix.
581        if (ptr->min_export == 0 || ptr->min_export == USHRT_MAX) {
582            if (explicit_prefix_levels > ptr->max_export)
583                ptr->max_export = explicit_prefix_levels;
584        }
585
586        for (int i = 0; i < 3; ++i) osfree(w_prefix[i]);
587
588        return ptr;
589    }
590}
591
592/* if numeric expr is omitted: if f_optional return HUGE_REAL, else longjmp */
593real
594read_number(bool f_optional, bool f_unsigned)
595{
596   bool fPositive = true, fDigits = false;
597   real n = (real)0.0;
598   filepos fp;
599   int ch_old;
600
601   get_pos(&fp);
602   ch_old = ch;
603   if (!f_unsigned) {
604      fPositive = !isMinus(ch);
605      if (isSign(ch)) nextch();
606   }
607
608   while (isdigit(ch)) {
609      n = n * (real)10.0 + (char)(ch - '0');
610      nextch();
611      fDigits = true;
612   }
613
614   if (isDecimal(ch)) {
615      real mult = (real)1.0;
616      nextch();
617      while (isdigit(ch)) {
618         mult *= (real).1;
619         n += (char)(ch - '0') * mult;
620         fDigits = true;
621         nextch();
622      }
623   }
624
625   /* !'fRead' => !fDigits so fDigits => 'fRead' */
626   if (fDigits) return (fPositive ? n : -n);
627
628   /* didn't read a valid number.  If it's optional, reset filepos & return */
629   set_pos(&fp);
630   if (f_optional) {
631      return HUGE_REAL;
632   }
633
634   if (isOmit(ch_old)) {
635      compile_diagnostic(DIAG_ERR|DIAG_COL, /*Field may not be omitted*/8);
636   } else {
637      compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
638   }
639   LONGJMP(file.jbSkipLine);
640   return 0.0; /* for brain-fried compilers */
641}
642
643real
644read_quadrant(bool f_optional)
645{
646   enum {
647      POINT_N = 0,
648      POINT_E = 1,
649      POINT_S = 2,
650      POINT_W = 3,
651      POINT_NONE = -1
652   };
653   static const sztok pointtab[] = {
654        {"E", POINT_E },
655        {"N", POINT_N },
656        {"S", POINT_S },
657        {"W", POINT_W },
658        {NULL, POINT_NONE }
659   };
660   static const sztok pointewtab[] = {
661        {"E", POINT_E },
662        {"W", POINT_W },
663        {NULL, POINT_NONE }
664   };
665   if (f_optional && isOmit(ch)) {
666      return HUGE_REAL;
667   }
668   const int quad = 90;
669   filepos fp;
670   get_pos(&fp);
671   get_token_no_blanks();
672   int first_point = match_tok(pointtab, TABSIZE(pointtab));
673   if (first_point == POINT_NONE) {
674      set_pos(&fp);
675      if (isOmit(ch)) {
676         compile_diagnostic(DIAG_ERR|DIAG_COL, /*Field may not be omitted*/8);
677      }
678      compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
679      LONGJMP(file.jbSkipLine);
680      return 0.0; /* for brain-fried compilers */
681   }
682   real r = read_number(true, true);
683   if (r == HUGE_REAL) {
684      if (isSign(ch) || isDecimal(ch)) {
685         /* Give better errors for S-0E, N+10W, N.E, etc. */
686         set_pos(&fp);
687         compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
688         LONGJMP(file.jbSkipLine);
689         return 0.0; /* for brain-fried compilers */
690      }
691      /* N, S, E or W. */
692      return first_point * quad;
693   }
694   if (first_point == POINT_E || first_point == POINT_W) {
695      set_pos(&fp);
696      compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
697      LONGJMP(file.jbSkipLine);
698      return 0.0; /* for brain-fried compilers */
699   }
700
701   get_token_no_blanks();
702   int second_point = match_tok(pointewtab, TABSIZE(pointewtab));
703   if (second_point == POINT_NONE) {
704      set_pos(&fp);
705      compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
706      LONGJMP(file.jbSkipLine);
707      return 0.0; /* for brain-fried compilers */
708   }
709
710   if (r > quad) {
711      set_pos(&fp);
712      compile_diagnostic_token_show(DIAG_ERR|DIAG_COL, /*Suspicious compass reading*/59);
713      LONGJMP(file.jbSkipLine);
714      return 0.0; /* for brain-fried compilers */
715   }
716
717   if (first_point == POINT_N) {
718      if (second_point == POINT_W) {
719         r = quad * 4 - r;
720      }
721   } else {
722      if (second_point == POINT_W) {
723         r += quad * 2;
724      } else {
725         r = quad * 2 - r;
726      }
727   }
728   return r;
729}
730
731extern real
732read_numeric(bool f_optional)
733{
734   skipblanks();
735   return read_number(f_optional, false);
736}
737
738extern real
739read_numeric_multi(bool f_optional, bool f_quadrants, int *p_n_readings)
740{
741   size_t n_readings = 0;
742   real tot = (real)0.0;
743
744   skipblanks();
745   if (!isOpen(ch)) {
746      real r = 0;
747      if (!f_quadrants)
748          r = read_number(f_optional, false);
749      else
750          r = read_quadrant(f_optional);
751      if (p_n_readings) *p_n_readings = (r == HUGE_REAL ? 0 : 1);
752      return r;
753   }
754   nextch();
755
756   skipblanks();
757   do {
758      if (!f_quadrants)
759         tot += read_number(false, false);
760      else
761         tot += read_quadrant(false);
762      ++n_readings;
763      skipblanks();
764   } while (!isClose(ch));
765   nextch();
766
767   if (p_n_readings) *p_n_readings = n_readings;
768   /* FIXME: special averaging for bearings ... */
769   /* And for percentage gradient */
770   return tot / n_readings;
771}
772
773/* read numeric expr or omit (return HUGE_REAL); else longjmp */
774extern real
775read_bearing_multi_or_omit(bool f_quadrants, int *p_n_readings)
776{
777   real v;
778   v = read_numeric_multi(true, f_quadrants, p_n_readings);
779   if (v == HUGE_REAL) {
780      if (!isOmit(ch)) {
781         compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
782         LONGJMP(file.jbSkipLine);
783         return 0.0; /* for brain-fried compilers */
784      }
785      nextch();
786   }
787   return v;
788}
789
790/* Don't skip blanks, variable error code */
791static unsigned int
792read_uint_internal(int errmsg, const filepos *fp)
793{
794   unsigned int n = 0;
795   if (!isdigit(ch)) {
796      if (fp) set_pos(fp);
797      compile_diagnostic_token_show(DIAG_ERR, errmsg);
798      LONGJMP(file.jbSkipLine);
799   }
800   while (isdigit(ch)) {
801      n = n * 10 + (char)(ch - '0');
802      nextch();
803   }
804   return n;
805}
806
807extern unsigned int
808read_uint(void)
809{
810   skipblanks();
811   return read_uint_internal(/*Expecting numeric field, found “%s”*/9, NULL);
812}
813
814extern int
815read_int(int min_val, int max_val)
816{
817    skipblanks();
818    unsigned n = 0;
819    filepos fp;
820
821    get_pos(&fp);
822    bool negated = isMinus(ch);
823    unsigned limit;
824    if (negated) {
825        limit = (unsigned)(min_val == INT_MIN ? INT_MIN : -min_val);
826    } else {
827        limit = (unsigned)max_val;
828    }
829    if (isSign(ch)) nextch();
830
831    if (!isdigit(ch)) {
832bad_value:
833        set_pos(&fp);
834        /* TRANSLATORS: The first %d will be replaced by the (inclusive) lower
835         * bound and the second by the (inclusive) upper bound, for example:
836         * Expecting integer in range -60 to 60
837         */
838        compile_diagnostic(DIAG_ERR|DIAG_NUM, /*Expecting integer in range %d to %d*/489);
839        LONGJMP(file.jbSkipLine);
840    }
841
842    while (isdigit(ch)) {
843        unsigned old_n = n;
844        n = n * 10 + (char)(ch - '0');
845        if (n > limit || n < old_n) {
846            goto bad_value;
847        }
848        nextch();
849    }
850    if (isDecimal(ch)) goto bad_value;
851
852    if (negated) {
853        if (n > (unsigned)INT_MAX) {
854            // Avoid unportable casting.
855            return INT_MIN;
856        }
857        return -(int)n;
858    }
859    return (int)n;
860}
861
862extern void
863read_string(string *pstr)
864{
865   s_clear(pstr);
866
867   skipblanks();
868   if (ch == '\"') {
869      /* String quoted in "" */
870      nextch();
871      while (1) {
872         if (isEol(ch)) {
873            compile_diagnostic(DIAG_ERR|DIAG_COL, /*Missing \"*/69);
874            LONGJMP(file.jbSkipLine);
875         }
876
877         if (ch == '\"') break;
878
879         s_catchar(pstr, ch);
880         nextch();
881      }
882      nextch();
883   } else {
884      /* Unquoted string */
885      while (1) {
886         if (isEol(ch) || isComm(ch)) {
887            if (s_empty(pstr)) {
888               compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting string field*/121);
889               LONGJMP(file.jbSkipLine);
890            }
891            return;
892         }
893
894         if (isBlank(ch)) break;
895
896         s_catchar(pstr, ch);
897         nextch();
898      }
899   }
900}
901
902extern void
903read_date(int *py, int *pm, int *pd)
904{
905   unsigned int y = 0, m = 0, d = 0;
906   filepos fp_date;
907
908   skipblanks();
909
910   get_pos(&fp_date);
911   y = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
912   /* Two digit year is 19xx. */
913   if (y < 100) {
914      filepos fp_save;
915      get_pos(&fp_save);
916      y += 1900;
917      set_pos(&fp_date);
918      /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
919      compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Assuming 2 digit year is %d*/76, y);
920      set_pos(&fp_save);
921   }
922   if (y < 1900 || y > 2078) {
923      set_pos(&fp_date);
924      compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year (< 1900 or > 2078)*/58);
925      LONGJMP(file.jbSkipLine);
926      return; /* for brain-fried compilers */
927   }
928   if (ch == '.') {
929      filepos fp;
930      nextch();
931      get_pos(&fp);
932      m = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
933      if (m < 1 || m > 12) {
934         set_pos(&fp);
935         compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
936         LONGJMP(file.jbSkipLine);
937         return; /* for brain-fried compilers */
938      }
939      if (ch == '.') {
940         nextch();
941         get_pos(&fp);
942         d = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
943         if (d < 1 || d > last_day(y, m)) {
944            set_pos(&fp);
945            /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
946            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid day of the month*/87);
947            LONGJMP(file.jbSkipLine);
948            return; /* for brain-fried compilers */
949         }
950      }
951   }
952   if (py) *py = y;
953   if (pm) *pm = m;
954   if (pd) *pd = d;
955}
956
957extern void
958read_walls_srv_date(int *py, int *pm, int *pd)
959{
960    skipblanks();
961
962    filepos fp_date;
963    get_pos(&fp_date);
964    unsigned y = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
965    int separator = -2;
966    if (ch == '-' || ch == '/') {
967        separator = ch;
968        nextch();
969    }
970    filepos fp_month;
971    get_pos(&fp_month);
972    unsigned m = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
973    if (ch == separator) {
974        nextch();
975    }
976    filepos fp_day;
977    get_pos(&fp_day);
978    unsigned d = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
979
980    filepos fp_year;
981    if (y < 100) {
982        // Walls recommends ISO 8601 date format (yyyy-mm-dd and seemingly the
983        // non-standard variant yyyy/mm/dd), but also accepts "some date formats
984        // common in the U.S. (mm/dd/yy, mm-dd-yyyy, etc.)"
985        unsigned tmp = y;
986        y = d;
987        fp_year = fp_day;
988        d = m;
989        fp_day = fp_month;
990        m = tmp;
991        fp_month = fp_date;
992
993        if (y < 100) {
994            // FIXME: Are all 2 digit years 19xx?
995            y += 1900;
996
997            filepos fp_save;
998            get_pos(&fp_save);
999            set_pos(&fp_year);
1000            /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
1001            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Assuming 2 digit year is %d*/76, y);
1002            set_pos(&fp_save);
1003        }
1004    } else {
1005        if (y < 1900 || y > 2078) {
1006            set_pos(&fp_date);
1007            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year (< 1900 or > 2078)*/58);
1008            LONGJMP(file.jbSkipLine);
1009            return; /* for brain-fried compilers */
1010        }
1011        fp_year = fp_date;
1012    }
1013
1014    if (m < 1 || m > 12) {
1015        set_pos(&fp_month);
1016        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
1017        LONGJMP(file.jbSkipLine);
1018        return; /* for brain-fried compilers */
1019    }
1020
1021    if (d < 1 || d > last_day(y, m)) {
1022        set_pos(&fp_day);
1023        /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
1024        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid day of the month*/87);
1025        LONGJMP(file.jbSkipLine);
1026        return; /* for brain-fried compilers */
1027    }
1028
1029    if (py) *py = y;
1030    if (pm) *pm = m;
1031    if (pd) *pd = d;
1032}
Note: See TracBrowser for help on using the repository browser.