source: git/src/readval.c @ d731218

main
Last change on this file since d731218 was 99c26d5, checked in by Olly Betts <olly@…>, 3 weeks ago

cavern: Pick better 3d file separator for Walls

Walls uses : as a separator between prefixes and between any prefix and
the station name, so now cavern takes into account which characters are
allowed in Walls station names when picking the separator to use in the
.3d file - for a pure Walls dataset, it will pick :, so Walls station
names in the .3d file should match the names as shown in Walls.

If a dataset mixes data in Walls format with Survex and/or Compass data,
then it's possible : is used in a Survex or Compass station name - if
so a different separator will be chosen.

Previously, cavern would pick . for the separator for a pure Walls
dataset, even though if it was used in station names.

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