source: git/src/readval.c @ d233de0

main
Last change on this file since d233de0 was e096a93, checked in by Olly Betts <olly@…>, 10 days ago

Reject some invalid Walls readings

Multi-component readings shouldn't allow a decimal point in anything but
the final component, and cavern now checks this.

Some examples of invalid readings which now give an error with cavern
are 12.5i3. 12.5:29, 12.5:29:30 and 12:29.5:30.

  • Property mode set to 100644
File size: 27.6 KB
RevLine 
[6974a34]1/* readval.c
[0395657]2 * Routines to read a prefix or number from the current input file
[62be3ee]3 * Copyright (C) 1991-2025 Olly Betts
[846746e]4 *
[89231c4]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.
[846746e]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
[89231c4]12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
[846746e]14 *
[89231c4]15 * You should have received a copy of the GNU General Public License
[0b99107]16 * along with this program; if not, see
17 * <https://www.gnu.org/licenses/>.
[d1b1380]18 */
19
[4c83f84]20#include <config.h>
[d1b1380]21
[c00c74a9]22#include <limits.h>
[be97baf]23#include <stddef.h> /* for offsetof */
[c00c74a9]24
[a420b49]25#include "cavern.h"
[5a0ab6a]26#include "commands.h" /* For match_tok(), etc */
[1ee204e]27#include "date.h"
[dd5b1a8]28#include "debug.h"
[a1706d1]29#include "filename.h"
30#include "message.h"
[d1b1380]31#include "readval.h"
32#include "datain.h"
[fb2e93c]33#include "netbits.h"
[8cf2e04]34#include "osalloc.h"
[76bb277a]35#include "str.h"
[d1b1380]36
[c86cc71]37int root_depr_count = 0;
38
[710ecc1]39static prefix *
[a2c33ae]40new_anon_station(void)
[710ecc1]41{
[a2c33ae]42    prefix *name = osnew(prefix);
43    name->pos = NULL;
[ba84079]44    name->ident.p = NULL;
[a2c33ae]45    name->stn = NULL;
46    name->up = pcs->Prefix;
47    name->down = NULL;
48    name->filename = file.filename;
49    name->line = file.line;
[98b705d]50    name->column = 0;
[5fc783b]51    name->min_export = name->max_export = 0;
[a2c33ae]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;
[710ecc1]57}
58
[ba84079]59static char *id = NULL;
60static size_t id_len = 0;
61
[c458cf7]62/* if prefix is omitted: if PFX_OPT set return NULL, otherwise use longjmp */
63extern prefix *
64read_prefix(unsigned pfx_flags)
[a420b49]65{
[c458cf7]66   bool f_optional = !!(pfx_flags & PFX_OPT);
67   bool fSurvey = !!(pfx_flags & PFX_SURVEY);
68   bool fSuspectTypo = !!(pfx_flags & PFX_SUSPECT_TYPO);
[4a6a094]69   prefix *back_ptr, *ptr;
[eb18f4d]70   size_t i;
[647407d]71   bool fNew;
[63d4f07]72   bool fImplicitPrefix = true;
[932f7e9]73   int depth = -1;
[d3fa693]74   filepos here;
[5b4085b]75   filepos fp_firstsep;
[a420b49]76
77   skipblanks();
[d3fa693]78   get_pos(&here);
[7f1ab95]79#ifndef NO_DEPRECATED
[a420b49]80   if (isRoot(ch)) {
[c458cf7]81      if (!(pfx_flags & PFX_ALLOW_ROOT)) {
[cab0f26]82         compile_diagnostic(DIAG_ERR|DIAG_COL, /*ROOT is deprecated*/25);
[fdd4ed76]83         longjmp(jbSkipLine, 1);
[84c60fc]84      }
[c86cc71]85      if (root_depr_count < 5) {
[cab0f26]86         compile_diagnostic(DIAG_WARN|DIAG_COL, /*ROOT is deprecated*/25);
[c86cc71]87         if (++root_depr_count == 5)
[64544daf]88            compile_diagnostic(DIAG_INFO, /*Further uses of this deprecated feature will not be reported*/95);
[c86cc71]89      }
[d1b1380]90      nextch();
[a420b49]91      ptr = root;
92      if (!isNames(ch)) {
93         if (!isSep(ch)) return ptr;
[27b8b59]94         /* Allow optional SEPARATOR after ROOT */
[5b4085b]95         get_pos(&fp_firstsep);
[a420b49]96         nextch();
97      }
[63d4f07]98      fImplicitPrefix = false;
[710ecc1]99#else
100   if (0) {
101#endif
[a420b49]102   } else {
[dcbcae0]103      if ((pfx_flags & PFX_ANON) &&
104          (isSep(ch) || (pcs->dash_for_anon_wall_station && ch == '-'))) {
105         int first_ch = ch;
[710ecc1]106         nextch();
[1cb22b5]107         if (isBlank(ch) || isComm(ch) || isEol(ch)) {
[dcbcae0]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)) {
[9f55538]115               set_pos(&here);
[7962c9d]116               compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Can't have a leg between two anonymous stations*/47);
[fdd4ed76]117               longjmp(jbSkipLine, 1);
[710ecc1]118            }
[dcbcae0]119            pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
[a2c33ae]120            return new_anon_station();
[710ecc1]121         }
[dcbcae0]122         if (isSep(first_ch) && ch == first_ch) {
123            nextch();
[1cb22b5]124            if (isBlank(ch) || isComm(ch) || isEol(ch)) {
[dcbcae0]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)) {
[9f55538]131                  set_pos(&here);
[7962c9d]132                  compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Can't have a leg between two anonymous stations*/47);
[fdd4ed76]133                  longjmp(jbSkipLine, 1);
[dcbcae0]134               }
[da65891]135               pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
[dcbcae0]136               pfx = new_anon_station();
137               pfx->sflags |= BIT(SFLAGS_WALL);
138               return pfx;
139            }
140            if (ch == first_ch) {
141               nextch();
[1cb22b5]142               if (isBlank(ch) || isComm(ch) || isEol(ch)) {
[dcbcae0]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)) {
[9f55538]149                     set_pos(&here);
[7962c9d]150                     compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Can't have a leg between two anonymous stations*/47);
[fdd4ed76]151                     longjmp(jbSkipLine, 1);
[dcbcae0]152                  }
153                  pcs->flags |= BIT(FLAGS_ANON_ONE_END);
154                  return new_anon_station();
155               }
156            }
157         }
[710ecc1]158         set_pos(&here);
159      }
[a420b49]160      ptr = pcs->Prefix;
161   }
162
163   i = 0;
164   do {
[63d4f07]165      fNew = false;
[ba84079]166      if (id == NULL) {
167         /* Allocate buffer the first time */
168         id_len = 256;
169         id = osmalloc(id_len);
[be97baf]170      }
[a420b49]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 */
[ba84079]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);
[a420b49]184            }
[8cf2e04]185         }
[a420b49]186         nextch();
[8cf2e04]187      }
[5b4085b]188      if (isSep(ch)) {
[63d4f07]189         fImplicitPrefix = false;
[5b4085b]190         get_pos(&fp_firstsep);
191      }
[a420b49]192      if (i == 0) {
[21c226e]193         if (!f_optional) {
[ee1d81f]194            if (isEol(ch)) {
[84c60fc]195               if (fSurvey) {
[cab0f26]196                  compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting survey name*/89);
[84c60fc]197               } else {
[cab0f26]198                  compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting station name*/28);
[84c60fc]199               }
[ee1d81f]200            } else {
[736f7df]201               /* TRANSLATORS: Here "station" is a survey station, not a train station. */
[7962c9d]202               compile_diagnostic(DIAG_ERR|DIAG_COL, /*Character “%c” not allowed in station name (use *SET NAMES to set allowed characters)*/110, ch);
[ee1d81f]203            }
[fdd4ed76]204            longjmp(jbSkipLine, 1);
[647407d]205         }
[a420b49]206         return (prefix *)NULL;
[d1b1380]207      }
[a420b49]208
[ba84079]209      id[i++] = '\0';
[cb3d1e2]210
[a420b49]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);
[ba84079]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         }
[a420b49]225         ptr->right = ptr->down = NULL;
226         ptr->pos = NULL;
227         ptr->stn = NULL;
228         ptr->up = back_ptr;
[d333899]229         ptr->filename = file.filename;
230         ptr->line = file.line;
[98b705d]231         ptr->column = here.offset - file.lpos;
[932f7e9]232         ptr->min_export = ptr->max_export = 0;
[95c3272]233         if (fSuspectTypo && !fImplicitPrefix)
234            ptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
[a420b49]235         back_ptr->down = ptr;
[63d4f07]236         fNew = true;
[a420b49]237      } else {
[be97baf]238         /* Use caching to speed up adding an increasing sequence to a
239          * large survey */
240         static prefix *cached_survey = NULL, *cached_station = NULL;
[a420b49]241         prefix *ptrPrev = NULL;
[76bb277a]242         int cmp = 1; /* result of strcmp ( -ve for <, 0 for =, +ve for > ) */
[be97baf]243         if (cached_survey == back_ptr) {
[ba84079]244            cmp = strcmp(prefix_ident(cached_station), id);
[be97baf]245            if (cmp <= 0) ptr = cached_station;
246         }
[ba84079]247         while (ptr && (cmp = strcmp(prefix_ident(ptr), id)) < 0) {
[a420b49]248            ptrPrev = ptr;
249            ptr = ptr->right;
250         }
251         if (cmp) {
252            /* ie we got to one that was higher, or the end */
[ba84079]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            }
[a420b49]263            if (ptrPrev == NULL)
[647407d]264               back_ptr->down = newptr;
[a420b49]265            else
[647407d]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;
[d333899]272            newptr->filename = file.filename;
273            newptr->line = file.line;
[98b705d]274            newptr->column = here.offset - file.lpos;
[421b7d2]275            newptr->min_export = newptr->max_export = 0;
[95c3272]276            if (fSuspectTypo && !fImplicitPrefix)
277               newptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
[647407d]278            ptr = newptr;
[63d4f07]279            fNew = true;
[a420b49]280         }
[be97baf]281         cached_survey = back_ptr;
282         cached_station = ptr;
[d1b1380]283      }
[fb2e93c]284      depth++;
[63d4f07]285      f_optional = false; /* disallow after first level */
[d3fa693]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      }
[a420b49]298   } while (isSep(ch));
[be97baf]299
[d40f787]300   /* don't warn about a station that is referred to twice */
[95c3272]301   if (!fNew) ptr->sflags &= ~BIT(SFLAGS_SUSPECTTYPO);
[421b7d2]302
[6430f0e]303   if (fNew) {
[95c3272]304      /* fNew means SFLAGS_SURVEY is currently set */
[4c07c51]305      SVX_ASSERT(TSTBIT(ptr->sflags, SFLAGS_SURVEY));
[c00c74a9]306      if (!fSurvey) {
307         ptr->sflags &= ~BIT(SFLAGS_SURVEY);
[27b8b59]308         if (TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
[c00c74a9]309      }
[6430f0e]310   } else {
311      /* check that the same name isn't being used for a survey and station */
[95c3272]312      if (fSurvey ^ TSTBIT(ptr->sflags, SFLAGS_SURVEY)) {
[0b8c321]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          */
[d3fa693]318         compile_diagnostic(DIAG_ERR|DIAG_FROM(here), /*“%s” can’t be both a station and a survey*/27,
[cab0f26]319                            sprint_prefix(ptr));
[6430f0e]320      }
[27b8b59]321      if (!fSurvey && TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
[051a72b]322   }
323
[932f7e9]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
[c00c74a9]329   if (ptr->min_export == 0 || ptr->min_export == USHRT_MAX) {
[932f7e9]330      if (depth > ptr->max_export) ptr->max_export = depth;
331   } else if (ptr->max_export < depth) {
[dd5b1a8]332      prefix *survey = ptr;
333      char *s;
[93e3492]334      const char *p;
[dd5b1a8]335      int level;
336      for (level = ptr->max_export + 1; level; level--) {
337         survey = survey->up;
[4c07c51]338         SVX_ASSERT(survey);
[dd5b1a8]339      }
340      s = osstrdup(sprint_prefix(survey));
[93e3492]341      p = sprint_prefix(ptr);
[016068a]342      if (survey->filename) {
[cab0f26]343         compile_diagnostic_pfx(DIAG_ERR, survey,
344                                /*Station “%s” not exported from survey “%s”*/26,
345                                p, s);
[93e3492]346      } else {
[cab0f26]347         compile_diagnostic(DIAG_ERR, /*Station “%s” not exported from survey “%s”*/26, p, s);
[016068a]348      }
[ae917b96]349      free(s);
[932f7e9]350#if 0
351      printf(" *** pfx %s warning not exported enough depth %d "
[421b7d2]352             "ptr->max_export %d\n", sprint_prefix(ptr),
[932f7e9]353             depth, ptr->max_export);
354#endif
355   }
[0fa7aac]356   if (!fImplicitPrefix && (pfx_flags & PFX_WARN_SEPARATOR)) {
[5b4085b]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);
[0fa7aac]362   }
[a420b49]363   return ptr;
[d1b1380]364}
365
[725d3b1]366char *
367read_walls_prefix(void)
368{
369    string name = S_INIT;
370    skipblanks();
371    if (!isNames(ch))
372        return NULL;
373    do {
[94d9f64]374        s_appendch(&name, ch);
[725d3b1]375        nextch();
376    } while (isNames(ch));
377    return s_steal(&name);
378}
379
380prefix *
[202e357]381read_walls_station(char * const walls_prefix[3], bool anon_allowed, bool *p_new)
[725d3b1]382{
[202e357]383    if (p_new) *p_new = false;
[725d3b1]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.
[7962c9d]416                compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Can't have a leg between two anonymous stations*/47);
[fdd4ed76]417                longjmp(jbSkipLine, 1);
[725d3b1]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);
[202e357]422            // An anonymous station is always new.
423            if (p_new) *p_new = true;
[725d3b1]424            return pfx;
425        }
[94d9f64]426        s_appendn(&component, dashes, '-');
[725d3b1]427    }
428
429    char *w_prefix[3] = { NULL, NULL, NULL };
430    int explicit_prefix_levels = 0;
431    while (true) {
432        while (isNames(ch)) {
[94d9f64]433            s_appendch(&component, ch);
[725d3b1]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);
[ae917b96]444                for (int i = 0; i < 3; ++i) free(w_prefix[i]);
[fdd4ed76]445                longjmp(jbSkipLine, 1);
[725d3b1]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)) {
[1f34f48]460            if (explicit_prefix_levels == 0) {
461                compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting station name*/28);
462                s_free(&component);
[ae917b96]463                for (int i = 0; i < 3; ++i) free(w_prefix[i]);
[fdd4ed76]464                longjmp(jbSkipLine, 1);
[1f34f48]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.
[94d9f64]471            s_append(&component, "empty name");
[99c26d5]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            }
[725d3b1]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) {
[ba84079]498            char *name;
[725d3b1]499            int sflag = BIT(SFLAGS_SURVEY);
500            if (i == 3) {
501                name = p;
[1bb4ba1]502                sflag = 0;
[725d3b1]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 */
[202e357]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;
[725d3b1]533                ptr = osnew(prefix);
[ba84079]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);
[ae917b96]538                    if (i >= 3) free(name);
[ba84079]539                } else {
540                    ptr->ident.p = (i < 3 ? osstrdup(name) : name);
541                }
[725d3b1]542                name = NULL;
543                ptr->right = ptr->down = NULL;
544                ptr->pos = NULL;
545                ptr->stn = NULL;
546                ptr->up = back_ptr;
[98b705d]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
[725d3b1]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) {
[ba84079]561                    cmp = strcmp(prefix_ident(cached_station), name);
[725d3b1]562                    if (cmp <= 0) ptr = cached_station;
563                }
[ba84079]564                while (ptr && (cmp = strcmp(prefix_ident(ptr), name))<0) {
[725d3b1]565                    ptrPrev = ptr;
566                    ptr = ptr->right;
567                }
568                if (cmp) {
569                    /* ie we got to one that was higher, or the end */
[202e357]570                    if (p_new) *p_new = true;
571                    prefix *newptr = osnew(prefix);
[ba84079]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);
[ae917b96]576                        if (i >= 3) free(name);
[ba84079]577                    } else {
578                        newptr->ident.p = (i < 3 ? osstrdup(name) : name);
579                    }
[725d3b1]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;
[98b705d]590                    // FIXME: Or location of #Prefix, etc for it?
591                    newptr->filename = file.filename;
[725d3b1]592                    newptr->line = file.line;
[98b705d]593                    newptr->column = fp.offset - file.lpos;
594
[725d3b1]595                    newptr->min_export = newptr->max_export = 0;
596                    ptr = newptr;
597                } else {
[116b485]598                    ptr->sflags |= sflag;
[725d3b1]599                }
[1bb4ba1]600                if (!TSTBIT(ptr->sflags, SFLAGS_SURVEY)) {
601                    ptr->min_export = USHRT_MAX;
602                }
[725d3b1]603                cached_survey = back_ptr;
604                cached_station = ptr;
605            }
[ae917b96]606            if (name == p) free(p);
[725d3b1]607        }
608
[1bb4ba1]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        }
[725d3b1]615
[ae917b96]616        for (int i = 0; i < 3; ++i) free(w_prefix[i]);
[725d3b1]617
618        return ptr;
619    }
620}
621
[21c226e]622/* if numeric expr is omitted: if f_optional return HUGE_REAL, else longjmp */
[725d3b1]623real
[e096a93]624read_number_or_int(bool f_optional, bool f_unsigned, bool* pf_decimal_point)
[a420b49]625{
[e096a93]626   if (pf_decimal_point) *pf_decimal_point = false;
[63d4f07]627   bool fPositive = true, fDigits = false;
[a420b49]628   real n = (real)0.0;
[c80bd34]629   filepos fp;
630   int ch_old;
[a420b49]631
[c80bd34]632   get_pos(&fp);
633   ch_old = ch;
[5a0ab6a]634   if (!f_unsigned) {
635      fPositive = !isMinus(ch);
636      if (isSign(ch)) nextch();
637   }
[a420b49]638
639   while (isdigit(ch)) {
[6974a34]640      n = n * (real)10.0 + (char)(ch - '0');
[d1b1380]641      nextch();
[63d4f07]642      fDigits = true;
[a420b49]643   }
644
645   if (isDecimal(ch)) {
[e096a93]646      if (pf_decimal_point) *pf_decimal_point = true;
[a420b49]647      real mult = (real)1.0;
648      nextch();
649      while (isdigit(ch)) {
650         mult *= (real).1;
651         n += (char)(ch - '0') * mult;
[63d4f07]652         fDigits = true;
[a420b49]653         nextch();
654      }
655   }
656
657   /* !'fRead' => !fDigits so fDigits => 'fRead' */
658   if (fDigits) return (fPositive ? n : -n);
659
660   /* didn't read a valid number.  If it's optional, reset filepos & return */
[4fb15a1]661   set_pos(&fp);
[21c226e]662   if (f_optional) {
[a420b49]663      return HUGE_REAL;
664   }
665
[c80bd34]666   if (isOmit(ch_old)) {
[7962c9d]667      compile_diagnostic(DIAG_ERR|DIAG_COL, /*Field may not be omitted*/114);
[647407d]668   } else {
[cab0f26]669      compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
[647407d]670   }
[fdd4ed76]671   longjmp(jbSkipLine, 1);
[d1b1380]672}
[647407d]673
[725d3b1]674real
[4ca3219]675read_quadrant(bool f_optional)
676{
[5a0ab6a]677   enum {
678      POINT_N = 0,
679      POINT_E = 1,
680      POINT_S = 2,
681      POINT_W = 3,
682      POINT_NONE = -1
683   };
684   static const sztok pointtab[] = {
685        {"E", POINT_E },
686        {"N", POINT_N },
687        {"S", POINT_S },
688        {"W", POINT_W },
689        {NULL, POINT_NONE }
690   };
691   static const sztok pointewtab[] = {
692        {"E", POINT_E },
693        {"W", POINT_W },
694        {NULL, POINT_NONE }
695   };
696   if (f_optional && isOmit(ch)) {
697      return HUGE_REAL;
698   }
[9d9bee9]699   const int quad = 90;
700   filepos fp;
701   get_pos(&fp);
[e1cbc0d]702   get_token_legacy_no_blanks();
[5a0ab6a]703   int first_point = match_tok(pointtab, TABSIZE(pointtab));
704   if (first_point == POINT_NONE) {
705      set_pos(&fp);
706      if (isOmit(ch)) {
[7962c9d]707         compile_diagnostic(DIAG_ERR|DIAG_COL, /*Field may not be omitted*/114);
[5a0ab6a]708      }
709      compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
[fdd4ed76]710      longjmp(jbSkipLine, 1);
[9d9bee9]711   }
[63d4f07]712   real r = read_number(true, true);
[5a0ab6a]713   if (r == HUGE_REAL) {
714      if (isSign(ch) || isDecimal(ch)) {
715         /* Give better errors for S-0E, N+10W, N.E, etc. */
[9d9bee9]716         set_pos(&fp);
[728874f9]717         compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
[fdd4ed76]718         longjmp(jbSkipLine, 1);
[9d9bee9]719      }
[5a0ab6a]720      /* N, S, E or W. */
721      return first_point * quad;
722   }
723   if (first_point == POINT_E || first_point == POINT_W) {
[9d9bee9]724      set_pos(&fp);
[728874f9]725      compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
[fdd4ed76]726      longjmp(jbSkipLine, 1);
[4ca3219]727   }
[5a0ab6a]728
[e1cbc0d]729   get_token_legacy_no_blanks();
[eb429bf]730   int second_point = match_tok(pointewtab, TABSIZE(pointewtab));
731   if (second_point == POINT_NONE) {
[5a0ab6a]732      set_pos(&fp);
733      compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
[fdd4ed76]734      longjmp(jbSkipLine, 1);
[4ca3219]735   }
[5a0ab6a]736
[eb429bf]737   if (r > quad) {
[5a0ab6a]738      set_pos(&fp);
[501dd27]739      compile_diagnostic_token_show(DIAG_ERR, /*Suspicious compass reading*/59);
[fdd4ed76]740      longjmp(jbSkipLine, 1);
[4ca3219]741   }
[5a0ab6a]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;
[4ca3219]755}
756
[21c226e]757extern real
[d3241e6]758read_numeric(bool f_optional)
759{
760   skipblanks();
[63d4f07]761   return read_number(f_optional, false);
[d3241e6]762}
763
764extern real
[61513b57]765read_numeric_multi(bool f_optional, bool f_quadrants, int *p_n_readings)
[21c226e]766{
767   size_t n_readings = 0;
768   real tot = (real)0.0;
769
770   skipblanks();
771   if (!isOpen(ch)) {
[4699c40]772      real r = 0;
[05b9de76]773      if (!f_quadrants) {
[63d4f07]774          r = read_number(f_optional, false);
[05b9de76]775      } else {
[5a0ab6a]776          r = read_quadrant(f_optional);
[05b9de76]777          if (r != HUGE_REAL)
778              do_legacy_token_warning();
779      }
[21c226e]780      if (p_n_readings) *p_n_readings = (r == HUGE_REAL ? 0 : 1);
781      return r;
782   }
783   nextch();
784
785   skipblanks();
786   do {
[05b9de76]787      if (!f_quadrants) {
[63d4f07]788         tot += read_number(false, false);
[05b9de76]789      } else {
[63d4f07]790         tot += read_quadrant(false);
[05b9de76]791         do_legacy_token_warning();
792      }
[21c226e]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
[2aa2f3f]804/* read numeric expr or omit (return HUGE_REAL); else longjmp */
805extern real
[00b10c1]806read_bearing_multi_or_omit(bool f_quadrants, int *p_n_readings)
[2aa2f3f]807{
[9d9bee9]808   real v;
[63d4f07]809   v = read_numeric_multi(true, f_quadrants, p_n_readings);
[9d9bee9]810   if (v == HUGE_REAL) {
811      if (!isOmit(ch)) {
812         compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
[fdd4ed76]813         longjmp(jbSkipLine, 1);
[2aa2f3f]814      }
815      nextch();
816   }
817   return v;
818}
819
[950a829]820/* Don't skip blanks, variable error code */
[c7c0f93]821unsigned int
[f15c53d9]822read_uint_raw(int diag_type, int errmsg, const filepos *fp)
[647407d]823{
[421b7d2]824   unsigned int n = 0;
[647407d]825   if (!isdigit(ch)) {
[029cc3f]826      if (fp) set_pos(fp);
[f15c53d9]827      if ((diag_type & DIAG_UINT)) {
828          compile_diagnostic(diag_type, errmsg);
829      } else {
830          compile_diagnostic_token_show(diag_type, errmsg);
831      }
[fdd4ed76]832      longjmp(jbSkipLine, 1);
[647407d]833   }
834   while (isdigit(ch)) {
835      n = n * 10 + (char)(ch - '0');
836      nextch();
837   }
838   return n;
839}
[76bb277a]840
[950a829]841extern unsigned int
842read_uint(void)
843{
844   skipblanks();
[f15c53d9]845   return read_uint_raw(DIAG_ERR, /*Expecting numeric field, found “%s”*/9, NULL);
[950a829]846}
847
[5b9b9d4]848extern int
849read_int(int min_val, int max_val)
850{
851    skipblanks();
852    unsigned n = 0;
853    filepos fp;
854
855    get_pos(&fp);
[1000d9d]856    bool negated = isMinus(ch);
857    unsigned limit;
858    if (negated) {
859        limit = (unsigned)(min_val == INT_MIN ? INT_MIN : -min_val);
860    } else {
861        limit = (unsigned)max_val;
862    }
[5b9b9d4]863    if (isSign(ch)) nextch();
864
865    if (!isdigit(ch)) {
866bad_value:
867        set_pos(&fp);
868        /* TRANSLATORS: The first %d will be replaced by the (inclusive) lower
869         * bound and the second by the (inclusive) upper bound, for example:
870         * Expecting integer in range -60 to 60
871         */
872        compile_diagnostic(DIAG_ERR|DIAG_NUM, /*Expecting integer in range %d to %d*/489);
[fdd4ed76]873        longjmp(jbSkipLine, 1);
[5b9b9d4]874    }
875
876    while (isdigit(ch)) {
877        unsigned old_n = n;
878        n = n * 10 + (char)(ch - '0');
879        if (n > limit || n < old_n) {
880            goto bad_value;
881        }
882        nextch();
883    }
884    if (isDecimal(ch)) goto bad_value;
885
886    if (negated) {
887        if (n > (unsigned)INT_MAX) {
888            // Avoid unportable casting.
889            return INT_MIN;
890        }
891        return -(int)n;
892    }
893    return (int)n;
894}
895
[62be3ee]896static bool
897read_string_(string *pstr, int diag_type)
[76bb277a]898{
[0532954]899   s_clear(pstr);
[76bb277a]900
901   skipblanks();
902   if (ch == '\"') {
[1157304]903      /* String quoted in "" */
[76bb277a]904      nextch();
[457a390]905      while (1) {
906         if (isEol(ch)) {
[62be3ee]907            compile_diagnostic(diag_type|DIAG_COL, /*Missing \"*/69);
908            if (diag_type == DIAG_ERR) longjmp(jbSkipLine, 1);
909            return false;
[457a390]910         }
911
912         if (ch == '\"') break;
913
[94d9f64]914         s_appendch(pstr, ch);
[457a390]915         nextch();
916      }
[66f5fc4]917      nextch();
[457a390]918   } else {
[1157304]919      /* Unquoted string */
[457a390]920      while (1) {
921         if (isEol(ch) || isComm(ch)) {
[0532954]922            if (s_empty(pstr)) {
[62be3ee]923               compile_diagnostic(diag_type|DIAG_COL, /*Expecting string field*/121);
924                if (diag_type == DIAG_ERR) longjmp(jbSkipLine, 1);
925                return false;
[b97d134]926            }
[62be3ee]927            return true;
[76bb277a]928         }
929
[457a390]930         if (isBlank(ch)) break;
[76bb277a]931
[94d9f64]932         s_appendch(pstr, ch);
[457a390]933         nextch();
934      }
[76bb277a]935   }
[62be3ee]936   return true;
937}
938
939extern void
940read_string(string *pstr)
941{
942    (void)read_string_(pstr, DIAG_ERR);
943}
944
945extern bool
946read_string_warning(string *pstr)
947{
948    return read_string_(pstr, DIAG_WARN);
[76bb277a]949}
[950a829]950
[725d3b1]951extern void
952read_walls_srv_date(int *py, int *pm, int *pd)
953{
954    skipblanks();
955
956    filepos fp_date;
957    get_pos(&fp_date);
[f15c53d9]958    unsigned y = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date);
[725d3b1]959    int separator = -2;
960    if (ch == '-' || ch == '/') {
961        separator = ch;
962        nextch();
963    }
964    filepos fp_month;
965    get_pos(&fp_month);
[f15c53d9]966    unsigned m = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date);
[725d3b1]967    if (ch == separator) {
968        nextch();
969    }
970    filepos fp_day;
971    get_pos(&fp_day);
[f15c53d9]972    unsigned d = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date);
[725d3b1]973
974    filepos fp_year;
975    if (y < 100) {
976        // Walls recommends ISO 8601 date format (yyyy-mm-dd and seemingly the
977        // non-standard variant yyyy/mm/dd), but also accepts "some date formats
978        // common in the U.S. (mm/dd/yy, mm-dd-yyyy, etc.)"
979        unsigned tmp = y;
980        y = d;
981        fp_year = fp_day;
982        d = m;
983        fp_day = fp_month;
984        m = tmp;
985        fp_month = fp_date;
986
987        if (y < 100) {
988            // FIXME: Are all 2 digit years 19xx?
989            y += 1900;
990
991            filepos fp_save;
992            get_pos(&fp_save);
993            set_pos(&fp_year);
994            /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
995            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Assuming 2 digit year is %d*/76, y);
996            set_pos(&fp_save);
997        }
998    } else {
999        if (y < 1900 || y > 2078) {
1000            set_pos(&fp_date);
1001            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year (< 1900 or > 2078)*/58);
[fdd4ed76]1002            longjmp(jbSkipLine, 1);
[725d3b1]1003        }
1004        fp_year = fp_date;
1005    }
1006
1007    if (m < 1 || m > 12) {
1008        set_pos(&fp_month);
1009        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
[fdd4ed76]1010        longjmp(jbSkipLine, 1);
[725d3b1]1011    }
1012
[c7c0f93]1013    if (d < 1 || d > (unsigned)last_day(y, m)) {
[725d3b1]1014        set_pos(&fp_day);
1015        /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
1016        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid day of the month*/87);
[fdd4ed76]1017        longjmp(jbSkipLine, 1);
[725d3b1]1018    }
1019
1020    if (py) *py = y;
1021    if (pm) *pm = m;
1022    if (pd) *pd = d;
1023}
Note: See TracBrowser for help on using the repository browser.