Fix message references for inches and feet
[survex.git] / src / readval.c
blob66c64165362dd21d28d89b3d2a93d36d644da249
1 /* readval.c
2 * Routines to read a prefix or number from the current input file
3 * Copyright (C) 1991-2024 Olly Betts
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.
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.
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
20 #include <config.h>
22 #include <limits.h>
23 #include <stddef.h> /* for offsetof */
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"
37 int root_depr_count = 0;
39 static prefix *
40 new_anon_station(void)
42 prefix *name = osnew(prefix);
43 name->pos = NULL;
44 name->ident.p = NULL;
45 name->shape = 0;
46 name->stn = NULL;
47 name->up = pcs->Prefix;
48 name->down = NULL;
49 name->filename = file.filename;
50 name->line = file.line;
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;
59 static char *id = NULL;
60 static size_t id_len = 0;
62 /* if prefix is omitted: if PFX_OPT set return NULL, otherwise use longjmp */
63 extern prefix *
64 read_prefix(unsigned pfx_flags)
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;
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);
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);
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();
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.
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*/3);
117 longjmp(jbSkipLine, 1);
119 pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
120 return new_anon_station();
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.
128 prefix * pfx;
129 anon_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*/3);
133 longjmp(jbSkipLine, 1);
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;
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).
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*/3);
151 longjmp(jbSkipLine, 1);
153 pcs->flags |= BIT(FLAGS_ANON_ONE_END);
154 return new_anon_station();
158 set_pos(&here);
160 ptr = pcs->Prefix;
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);
171 /* i==0 iff this is the first pass */
172 if (i) {
173 i = 0;
174 nextch();
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);
186 nextch();
188 if (isSep(ch)) {
189 fImplicitPrefix = false;
190 get_pos(&fp_firstsep);
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);
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)*/7, ch);
204 longjmp(jbSkipLine, 1);
206 return (prefix *)NULL;
209 id[i++] = '\0';
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;
225 ptr->right = ptr->down = NULL;
226 ptr->pos = NULL;
227 ptr->shape = 0;
228 ptr->stn = NULL;
229 ptr->up = back_ptr;
230 ptr->filename = file.filename;
231 ptr->line = file.line;
232 ptr->min_export = ptr->max_export = 0;
233 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;
247 while (ptr && (cmp = strcmp(prefix_ident(ptr), id)) < 0) {
248 ptrPrev = ptr;
249 ptr = ptr->right;
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;
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->shape = 0;
271 newptr->stn = NULL;
272 newptr->up = back_ptr;
273 newptr->filename = file.filename;
274 newptr->line = file.line;
275 newptr->min_export = newptr->max_export = 0;
276 if (fSuspectTypo && !fImplicitPrefix)
277 newptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
278 ptr = newptr;
279 fNew = true;
281 cached_survey = back_ptr;
282 cached_station = ptr;
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.
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.
294 compile_diagnostic(DIAG_ERR|DIAG_FROM(here), /*“%s” can’t be both a station and a survey*/27,
295 sprint_prefix(ptr));
298 } while (isSep(ch));
300 /* don't warn about a station that is referred to twice */
301 if (!fNew) ptr->sflags &= ~BIT(SFLAGS_SUSPECTTYPO);
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;
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.
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.
318 compile_diagnostic(DIAG_ERR|DIAG_FROM(here), /*“%s” can’t be both a station and a survey*/27,
319 sprint_prefix(ptr));
321 if (!fSurvey && TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
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);
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);
349 osfree(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
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);
363 return ptr;
366 char *
367 read_walls_prefix(void)
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);
380 prefix *
381 read_walls_station(char * const walls_prefix[3], bool anon_allowed, bool *p_new)
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;
394 filepos fp;
395 get_pos(&fp);
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?
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();
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*/3);
417 longjmp(jbSkipLine, 1);
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;
426 s_appendn(&component, dashes, '-');
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();
436 //printf("component = '%s'\n", s_str(&component));
437 if (ch == ':') {
438 nextch();
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) osfree(w_prefix[i]);
445 longjmp(jbSkipLine, 1);
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);
453 continue;
456 // printf("explicit_prefix_levels=%d %s:%s:%s\n", explicit_prefix_levels, w_prefix[0], w_prefix[1], w_prefix[2]);
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) osfree(w_prefix[i]);
464 longjmp(jbSkipLine, 1);
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");
473 int len = s_len(&component);
474 char *p = s_steal(&component);
475 // Apply case treatment.
476 switch (pcs->Case) {
477 case LOWER:
478 for (int i = 0; i < len; ++i)
479 p[i] = tolower((unsigned char)p[i]);
480 break;
481 case UPPER:
482 for (int i = 0; i < len; ++i)
483 p[i] = toupper((unsigned char)p[i]);
484 break;
485 case OFF:
486 // Avoid unhandled enum warning.
487 break;
490 prefix *ptr = root;
491 for (int i = 0; i < 4; ++i) {
492 char *name;
493 int sflag = BIT(SFLAGS_SURVEY);
494 if (i == 3) {
495 name = p;
496 sflag = 0;
497 } else {
498 if (i < 3 - explicit_prefix_levels) {
499 name = walls_prefix[i];
500 // printf("using walls_prefix[%d] = '%s'\n", 2 - i, name);
501 } else {
502 name = w_prefix[i - (3 - explicit_prefix_levels)]; // FIXME: Could steal wprefix[i].
503 // printf("using w_prefix[%d] = '%s'\n", i - (3 - explicit_prefix_levels), name);
506 if (name == NULL) {
507 // FIXME: This means :X::Y is treated as the same as
508 // ::X:Y but is that right? Walls docs don't really
509 // say. Need to test (and is they're different then
510 // probably use a character not valid in Walls station
511 // names for the empty prefix level (e.g. space or
512 // `#`).
514 // Also, does Walls allow :::X as a station and
515 // ::X:Y which would mean X is a station and survey?
516 // If so, we probably want to keep every empty level.
517 continue;
520 prefix *back_ptr = ptr;
521 ptr = ptr->down;
522 if (ptr == NULL) {
523 /* Special case first time around at each level */
524 /* No need to check if we're at the station level - if the
525 * prefix is new the station must be. */
526 if (p_new) *p_new = true;
527 ptr = osnew(prefix);
528 ptr->sflags = sflag;
529 if (strlen(name) < sizeof(ptr->ident.i)) {
530 strcpy(ptr->ident.i, name);
531 ptr->sflags |= BIT(SFLAGS_IDENT_INLINE);
532 if (i >= 3) osfree(name);
533 } else {
534 ptr->ident.p = (i < 3 ? osstrdup(name) : name);
536 name = NULL;
537 ptr->right = ptr->down = NULL;
538 ptr->pos = NULL;
539 ptr->shape = 0;
540 ptr->stn = NULL;
541 ptr->up = back_ptr;
542 ptr->filename = file.filename; // FIXME: Or location of #Prefix, etc for it?
543 ptr->line = file.line; // FIXME: Or location of #Prefix, etc for it?
544 ptr->min_export = ptr->max_export = 0;
545 back_ptr->down = ptr;
546 } else {
547 /* Use caching to speed up adding an increasing sequence to a
548 * large survey */
549 static prefix *cached_survey = NULL, *cached_station = NULL;
550 prefix *ptrPrev = NULL;
551 int cmp = 1; /* result of strcmp ( -ve for <, 0 for =, +ve for > ) */
552 if (cached_survey == back_ptr) {
553 cmp = strcmp(prefix_ident(cached_station), name);
554 if (cmp <= 0) ptr = cached_station;
556 while (ptr && (cmp = strcmp(prefix_ident(ptr), name))<0) {
557 ptrPrev = ptr;
558 ptr = ptr->right;
560 if (cmp) {
561 /* ie we got to one that was higher, or the end */
562 if (p_new) *p_new = true;
563 prefix *newptr = osnew(prefix);
564 newptr->sflags = sflag;
565 if (strlen(name) < sizeof(newptr->ident.i)) {
566 strcpy(newptr->ident.i, name);
567 newptr->sflags |= BIT(SFLAGS_IDENT_INLINE);
568 if (i >= 3) osfree(name);
569 } else {
570 newptr->ident.p = (i < 3 ? osstrdup(name) : name);
572 name = NULL;
573 if (ptrPrev == NULL)
574 back_ptr->down = newptr;
575 else
576 ptrPrev->right = newptr;
577 newptr->right = ptr;
578 newptr->down = NULL;
579 newptr->pos = NULL;
580 newptr->shape = 0;
581 newptr->stn = NULL;
582 newptr->up = back_ptr;
583 newptr->filename = file.filename; // FIXME
584 newptr->line = file.line;
585 newptr->min_export = newptr->max_export = 0;
586 ptr = newptr;
587 } else {
588 ptr->sflags |= sflag;
590 if (!TSTBIT(ptr->sflags, SFLAGS_SURVEY)) {
591 ptr->min_export = USHRT_MAX;
593 cached_survey = back_ptr;
594 cached_station = ptr;
596 if (name == p) osfree(p);
599 // Do the equivalent of "*infer exports" for Walls stations with an
600 // explicit prefix.
601 if (ptr->min_export == 0 || ptr->min_export == USHRT_MAX) {
602 if (explicit_prefix_levels > ptr->max_export)
603 ptr->max_export = explicit_prefix_levels;
606 for (int i = 0; i < 3; ++i) osfree(w_prefix[i]);
608 return ptr;
612 /* if numeric expr is omitted: if f_optional return HUGE_REAL, else longjmp */
613 real
614 read_number(bool f_optional, bool f_unsigned)
616 bool fPositive = true, fDigits = false;
617 real n = (real)0.0;
618 filepos fp;
619 int ch_old;
621 get_pos(&fp);
622 ch_old = ch;
623 if (!f_unsigned) {
624 fPositive = !isMinus(ch);
625 if (isSign(ch)) nextch();
628 while (isdigit(ch)) {
629 n = n * (real)10.0 + (char)(ch - '0');
630 nextch();
631 fDigits = true;
634 if (isDecimal(ch)) {
635 real mult = (real)1.0;
636 nextch();
637 while (isdigit(ch)) {
638 mult *= (real).1;
639 n += (char)(ch - '0') * mult;
640 fDigits = true;
641 nextch();
645 /* !'fRead' => !fDigits so fDigits => 'fRead' */
646 if (fDigits) return (fPositive ? n : -n);
648 /* didn't read a valid number. If it's optional, reset filepos & return */
649 set_pos(&fp);
650 if (f_optional) {
651 return HUGE_REAL;
654 if (isOmit(ch_old)) {
655 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Field may not be omitted*/8);
656 } else {
657 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
659 longjmp(jbSkipLine, 1);
662 real
663 read_quadrant(bool f_optional)
665 enum {
666 POINT_N = 0,
667 POINT_E = 1,
668 POINT_S = 2,
669 POINT_W = 3,
670 POINT_NONE = -1
672 static const sztok pointtab[] = {
673 {"E", POINT_E },
674 {"N", POINT_N },
675 {"S", POINT_S },
676 {"W", POINT_W },
677 {NULL, POINT_NONE }
679 static const sztok pointewtab[] = {
680 {"E", POINT_E },
681 {"W", POINT_W },
682 {NULL, POINT_NONE }
684 if (f_optional && isOmit(ch)) {
685 return HUGE_REAL;
687 const int quad = 90;
688 filepos fp;
689 get_pos(&fp);
690 get_token_legacy_no_blanks();
691 int first_point = match_tok(pointtab, TABSIZE(pointtab));
692 if (first_point == POINT_NONE) {
693 set_pos(&fp);
694 if (isOmit(ch)) {
695 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Field may not be omitted*/8);
697 compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
698 longjmp(jbSkipLine, 1);
700 real r = read_number(true, true);
701 if (r == HUGE_REAL) {
702 if (isSign(ch) || isDecimal(ch)) {
703 /* Give better errors for S-0E, N+10W, N.E, etc. */
704 set_pos(&fp);
705 compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
706 longjmp(jbSkipLine, 1);
708 /* N, S, E or W. */
709 return first_point * quad;
711 if (first_point == POINT_E || first_point == POINT_W) {
712 set_pos(&fp);
713 compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
714 longjmp(jbSkipLine, 1);
717 get_token_legacy_no_blanks();
718 int second_point = match_tok(pointewtab, TABSIZE(pointewtab));
719 if (second_point == POINT_NONE) {
720 set_pos(&fp);
721 compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
722 longjmp(jbSkipLine, 1);
725 if (r > quad) {
726 set_pos(&fp);
727 compile_diagnostic_token_show(DIAG_ERR, /*Suspicious compass reading*/59);
728 longjmp(jbSkipLine, 1);
731 if (first_point == POINT_N) {
732 if (second_point == POINT_W) {
733 r = quad * 4 - r;
735 } else {
736 if (second_point == POINT_W) {
737 r += quad * 2;
738 } else {
739 r = quad * 2 - r;
742 return r;
745 extern real
746 read_numeric(bool f_optional)
748 skipblanks();
749 return read_number(f_optional, false);
752 extern real
753 read_numeric_multi(bool f_optional, bool f_quadrants, int *p_n_readings)
755 size_t n_readings = 0;
756 real tot = (real)0.0;
758 skipblanks();
759 if (!isOpen(ch)) {
760 real r = 0;
761 if (!f_quadrants) {
762 r = read_number(f_optional, false);
763 } else {
764 r = read_quadrant(f_optional);
765 if (r != HUGE_REAL)
766 do_legacy_token_warning();
768 if (p_n_readings) *p_n_readings = (r == HUGE_REAL ? 0 : 1);
769 return r;
771 nextch();
773 skipblanks();
774 do {
775 if (!f_quadrants) {
776 tot += read_number(false, false);
777 } else {
778 tot += read_quadrant(false);
779 do_legacy_token_warning();
781 ++n_readings;
782 skipblanks();
783 } while (!isClose(ch));
784 nextch();
786 if (p_n_readings) *p_n_readings = n_readings;
787 /* FIXME: special averaging for bearings ... */
788 /* And for percentage gradient */
789 return tot / n_readings;
792 /* read numeric expr or omit (return HUGE_REAL); else longjmp */
793 extern real
794 read_bearing_multi_or_omit(bool f_quadrants, int *p_n_readings)
796 real v;
797 v = read_numeric_multi(true, f_quadrants, p_n_readings);
798 if (v == HUGE_REAL) {
799 if (!isOmit(ch)) {
800 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
801 longjmp(jbSkipLine, 1);
803 nextch();
805 return v;
808 /* Don't skip blanks, variable error code */
809 unsigned int
810 read_uint_raw(int errmsg, const filepos *fp)
812 unsigned int n = 0;
813 if (!isdigit(ch)) {
814 if (fp) set_pos(fp);
815 compile_diagnostic_token_show(DIAG_ERR, errmsg);
816 longjmp(jbSkipLine, 1);
818 while (isdigit(ch)) {
819 n = n * 10 + (char)(ch - '0');
820 nextch();
822 return n;
825 extern unsigned int
826 read_uint(void)
828 skipblanks();
829 return read_uint_raw(/*Expecting numeric field, found “%s”*/9, NULL);
832 extern int
833 read_int(int min_val, int max_val)
835 skipblanks();
836 unsigned n = 0;
837 filepos fp;
839 get_pos(&fp);
840 bool negated = isMinus(ch);
841 unsigned limit;
842 if (negated) {
843 limit = (unsigned)(min_val == INT_MIN ? INT_MIN : -min_val);
844 } else {
845 limit = (unsigned)max_val;
847 if (isSign(ch)) nextch();
849 if (!isdigit(ch)) {
850 bad_value:
851 set_pos(&fp);
852 /* TRANSLATORS: The first %d will be replaced by the (inclusive) lower
853 * bound and the second by the (inclusive) upper bound, for example:
854 * Expecting integer in range -60 to 60
856 compile_diagnostic(DIAG_ERR|DIAG_NUM, /*Expecting integer in range %d to %d*/489);
857 longjmp(jbSkipLine, 1);
860 while (isdigit(ch)) {
861 unsigned old_n = n;
862 n = n * 10 + (char)(ch - '0');
863 if (n > limit || n < old_n) {
864 goto bad_value;
866 nextch();
868 if (isDecimal(ch)) goto bad_value;
870 if (negated) {
871 if (n > (unsigned)INT_MAX) {
872 // Avoid unportable casting.
873 return INT_MIN;
875 return -(int)n;
877 return (int)n;
880 extern void
881 read_string(string *pstr)
883 s_clear(pstr);
885 skipblanks();
886 if (ch == '\"') {
887 /* String quoted in "" */
888 nextch();
889 while (1) {
890 if (isEol(ch)) {
891 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Missing \"*/69);
892 longjmp(jbSkipLine, 1);
895 if (ch == '\"') break;
897 s_appendch(pstr, ch);
898 nextch();
900 nextch();
901 } else {
902 /* Unquoted string */
903 while (1) {
904 if (isEol(ch) || isComm(ch)) {
905 if (s_empty(pstr)) {
906 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting string field*/121);
907 longjmp(jbSkipLine, 1);
909 return;
912 if (isBlank(ch)) break;
914 s_appendch(pstr, ch);
915 nextch();
920 extern void
921 read_walls_srv_date(int *py, int *pm, int *pd)
923 skipblanks();
925 filepos fp_date;
926 get_pos(&fp_date);
927 unsigned y = read_uint_raw(/*Expecting date, found “%s”*/198, &fp_date);
928 int separator = -2;
929 if (ch == '-' || ch == '/') {
930 separator = ch;
931 nextch();
933 filepos fp_month;
934 get_pos(&fp_month);
935 unsigned m = read_uint_raw(/*Expecting date, found “%s”*/198, &fp_date);
936 if (ch == separator) {
937 nextch();
939 filepos fp_day;
940 get_pos(&fp_day);
941 unsigned d = read_uint_raw(/*Expecting date, found “%s”*/198, &fp_date);
943 filepos fp_year;
944 if (y < 100) {
945 // Walls recommends ISO 8601 date format (yyyy-mm-dd and seemingly the
946 // non-standard variant yyyy/mm/dd), but also accepts "some date formats
947 // common in the U.S. (mm/dd/yy, mm-dd-yyyy, etc.)"
948 unsigned tmp = y;
949 y = d;
950 fp_year = fp_day;
951 d = m;
952 fp_day = fp_month;
953 m = tmp;
954 fp_month = fp_date;
956 if (y < 100) {
957 // FIXME: Are all 2 digit years 19xx?
958 y += 1900;
960 filepos fp_save;
961 get_pos(&fp_save);
962 set_pos(&fp_year);
963 /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
964 compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Assuming 2 digit year is %d*/76, y);
965 set_pos(&fp_save);
967 } else {
968 if (y < 1900 || y > 2078) {
969 set_pos(&fp_date);
970 compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year (< 1900 or > 2078)*/58);
971 longjmp(jbSkipLine, 1);
973 fp_year = fp_date;
976 if (m < 1 || m > 12) {
977 set_pos(&fp_month);
978 compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
979 longjmp(jbSkipLine, 1);
982 if (d < 1 || d > (unsigned)last_day(y, m)) {
983 set_pos(&fp_day);
984 /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
985 compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid day of the month*/87);
986 longjmp(jbSkipLine, 1);
989 if (py) *py = y;
990 if (pm) *pm = m;
991 if (pd) *pd = d;