source: git/src/readval.c @ 9687520

stereo-2025
Last change on this file since 9687520 was 94d9f64, checked in by Olly Betts <olly@…>, 9 months ago

Change str.h API

The string is now lazily nul-terminated which in particular makes
building up a string character by character more efficient.

Methods which append are now named using the term "append" - e.g.
s_appendch(c) seems much clearer than s_catchar(c).

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