Invalid display lists on hidpi change
[survex.git] / src / readval.c
blob91dbfbbbb4528909ed4b4f55772dff3d11e715e2
1 /* readval.c
2 * Routines to read a prefix or number from the current input file
3 * Copyright (C) 1991-2003,2005,2006,2010,2011,2012,2013,2014,2015,2016,2018,2019 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 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
24 #include <limits.h>
25 #include <stddef.h> /* for offsetof */
27 #include "cavern.h"
28 #include "commands.h" /* For match_tok(), etc */
29 #include "date.h"
30 #include "debug.h"
31 #include "filename.h"
32 #include "message.h"
33 #include "readval.h"
34 #include "datain.h"
35 #include "netbits.h"
36 #include "osalloc.h"
37 #include "str.h"
39 #ifdef HAVE_SETJMP_H
40 # define LONGJMP(JB) longjmp((JB), 1)
41 #else
42 # define LONGJMP(JB) exit(1)
43 #endif
45 int root_depr_count = 0;
47 static prefix *
48 new_anon_station(void)
50 prefix *name = osnew(prefix);
51 name->pos = NULL;
52 name->ident = NULL;
53 name->shape = 0;
54 name->stn = NULL;
55 name->up = pcs->Prefix;
56 name->down = NULL;
57 name->filename = file.filename;
58 name->line = file.line;
59 name->min_export = name->max_export = 0;
60 name->sflags = BIT(SFLAGS_ANON);
61 /* Keep linked list of anon stations for node stats. */
62 name->right = anon_list;
63 anon_list = name;
64 return name;
67 /* if prefix is omitted: if PFX_OPT set return NULL, otherwise use longjmp */
68 extern prefix *
69 read_prefix(unsigned pfx_flags)
71 bool f_optional = !!(pfx_flags & PFX_OPT);
72 bool fSurvey = !!(pfx_flags & PFX_SURVEY);
73 bool fSuspectTypo = !!(pfx_flags & PFX_SUSPECT_TYPO);
74 prefix *back_ptr, *ptr;
75 char *name;
76 size_t name_len = 32;
77 size_t i;
78 bool fNew;
79 bool fImplicitPrefix = fTrue;
80 int depth = -1;
81 filepos fp_firstsep;
83 skipblanks();
84 #ifndef NO_DEPRECATED
85 if (isRoot(ch)) {
86 if (!(pfx_flags & PFX_ALLOW_ROOT)) {
87 compile_diagnostic(DIAG_ERR|DIAG_COL, /*ROOT is deprecated*/25);
88 LONGJMP(file.jbSkipLine);
90 if (root_depr_count < 5) {
91 compile_diagnostic(DIAG_WARN|DIAG_COL, /*ROOT is deprecated*/25);
92 if (++root_depr_count == 5)
93 compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
95 nextch();
96 ptr = root;
97 if (!isNames(ch)) {
98 if (!isSep(ch)) return ptr;
99 /* Allow optional SEPARATOR after ROOT */
100 get_pos(&fp_firstsep);
101 nextch();
103 fImplicitPrefix = fFalse;
104 #else
105 if (0) {
106 #endif
107 } else {
108 if ((pfx_flags & PFX_ANON) &&
109 (isSep(ch) || (pcs->dash_for_anon_wall_station && ch == '-'))) {
110 int first_ch = ch;
111 filepos here;
112 get_pos(&here);
113 nextch();
114 if (isBlank(ch) || isEol(ch)) {
115 if (!isSep(first_ch))
116 goto anon_wall_station;
117 /* A single separator alone ('.' by default) is an anonymous
118 * station which is on a point inside the passage and implies
119 * the leg to it is a splay.
121 if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
122 set_pos(&here);
123 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Can't have a leg between two anonymous stations*/3);
124 LONGJMP(file.jbSkipLine);
126 pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
127 return new_anon_station();
129 if (isSep(first_ch) && ch == first_ch) {
130 nextch();
131 if (isBlank(ch) || isEol(ch)) {
132 /* A double separator ('..' by default) is an anonymous station
133 * which is on the wall and implies the leg to it is a splay.
135 prefix * pfx;
136 anon_wall_station:
137 if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
138 set_pos(&here);
139 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Can't have a leg between two anonymous stations*/3);
140 LONGJMP(file.jbSkipLine);
142 pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
143 pfx = new_anon_station();
144 pfx->sflags |= BIT(SFLAGS_WALL);
145 return pfx;
147 if (ch == first_ch) {
148 nextch();
149 if (isBlank(ch) || isEol(ch)) {
150 /* A triple separator ('...' by default) is an anonymous
151 * station, but otherwise not handled specially (e.g. for
152 * a single leg down an unexplored side passage to a station
153 * which isn't refindable).
155 if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
156 set_pos(&here);
157 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Can't have a leg between two anonymous stations*/3);
158 LONGJMP(file.jbSkipLine);
160 pcs->flags |= BIT(FLAGS_ANON_ONE_END);
161 return new_anon_station();
165 set_pos(&here);
167 ptr = pcs->Prefix;
170 i = 0;
171 name = NULL;
172 do {
173 fNew = fFalse;
174 if (name == NULL) {
175 /* Need a new name buffer */
176 name = osmalloc(name_len);
178 /* i==0 iff this is the first pass */
179 if (i) {
180 i = 0;
181 nextch();
183 while (isNames(ch)) {
184 if (i < pcs->Truncate) {
185 /* truncate name */
186 name[i++] = (pcs->Case == LOWER ? tolower(ch) :
187 (pcs->Case == OFF ? ch : toupper(ch)));
188 if (i >= name_len) {
189 name_len = name_len + name_len;
190 name = osrealloc(name, name_len);
193 nextch();
195 if (isSep(ch)) {
196 fImplicitPrefix = fFalse;
197 get_pos(&fp_firstsep);
199 if (i == 0) {
200 osfree(name);
201 if (!f_optional) {
202 if (isEol(ch)) {
203 if (fSurvey) {
204 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting survey name*/89);
205 } else {
206 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting station name*/28);
208 } else {
209 /* TRANSLATORS: Here "station" is a survey station, not a train station. */
210 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Character “%c” not allowed in station name (use *SET NAMES to set allowed characters)*/7, ch);
212 LONGJMP(file.jbSkipLine);
214 return (prefix *)NULL;
217 name[i++] = '\0';
219 back_ptr = ptr;
220 ptr = ptr->down;
221 if (ptr == NULL) {
222 /* Special case first time around at each level */
223 name = osrealloc(name, i);
224 ptr = osnew(prefix);
225 ptr->ident = name;
226 name = NULL;
227 ptr->right = ptr->down = NULL;
228 ptr->pos = NULL;
229 ptr->shape = 0;
230 ptr->stn = NULL;
231 ptr->up = back_ptr;
232 ptr->filename = file.filename;
233 ptr->line = file.line;
234 ptr->min_export = ptr->max_export = 0;
235 ptr->sflags = BIT(SFLAGS_SURVEY);
236 if (fSuspectTypo && !fImplicitPrefix)
237 ptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
238 back_ptr->down = ptr;
239 fNew = fTrue;
240 } else {
241 /* Use caching to speed up adding an increasing sequence to a
242 * large survey */
243 static prefix *cached_survey = NULL, *cached_station = NULL;
244 prefix *ptrPrev = NULL;
245 int cmp = 1; /* result of strcmp ( -ve for <, 0 for =, +ve for > ) */
246 if (cached_survey == back_ptr) {
247 cmp = strcmp(cached_station->ident, name);
248 if (cmp <= 0) ptr = cached_station;
250 while (ptr && (cmp = strcmp(ptr->ident, name))<0) {
251 ptrPrev = ptr;
252 ptr = ptr->right;
254 if (cmp) {
255 /* ie we got to one that was higher, or the end */
256 prefix *newptr;
257 name = osrealloc(name, i);
258 newptr = osnew(prefix);
259 newptr->ident = name;
260 name = NULL;
261 if (ptrPrev == NULL)
262 back_ptr->down = newptr;
263 else
264 ptrPrev->right = newptr;
265 newptr->right = ptr;
266 newptr->down = NULL;
267 newptr->pos = NULL;
268 newptr->shape = 0;
269 newptr->stn = NULL;
270 newptr->up = back_ptr;
271 newptr->filename = file.filename;
272 newptr->line = file.line;
273 newptr->min_export = newptr->max_export = 0;
274 newptr->sflags = BIT(SFLAGS_SURVEY);
275 if (fSuspectTypo && !fImplicitPrefix)
276 newptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
277 ptr = newptr;
278 fNew = fTrue;
280 cached_survey = back_ptr;
281 cached_station = ptr;
283 depth++;
284 f_optional = fFalse; /* disallow after first level */
285 if (isSep(ch)) get_pos(&fp_firstsep);
286 } while (isSep(ch));
287 if (name) osfree(name);
289 /* don't warn about a station that is referred to twice */
290 if (!fNew) ptr->sflags &= ~BIT(SFLAGS_SUSPECTTYPO);
292 if (fNew) {
293 /* fNew means SFLAGS_SURVEY is currently set */
294 SVX_ASSERT(TSTBIT(ptr->sflags, SFLAGS_SURVEY));
295 if (!fSurvey) {
296 ptr->sflags &= ~BIT(SFLAGS_SURVEY);
297 if (TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
299 } else {
300 /* check that the same name isn't being used for a survey and station */
301 if (fSurvey ^ TSTBIT(ptr->sflags, SFLAGS_SURVEY)) {
302 /* TRANSLATORS: Here "station" is a survey station, not a train station.
304 * Here "survey" is a "cave map" rather than list of questions - it should be
305 * translated to the terminology that cavers using the language would use.
307 compile_diagnostic(DIAG_ERR, /*“%s” can’t be both a station and a survey*/27,
308 sprint_prefix(ptr));
310 if (!fSurvey && TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
313 /* check the export level */
314 #if 0
315 printf("R min %d max %d depth %d pfx %s\n",
316 ptr->min_export, ptr->max_export, depth, sprint_prefix(ptr));
317 #endif
318 if (ptr->min_export == 0 || ptr->min_export == USHRT_MAX) {
319 if (depth > ptr->max_export) ptr->max_export = depth;
320 } else if (ptr->max_export < depth) {
321 prefix *survey = ptr;
322 char *s;
323 const char *p;
324 int level;
325 for (level = ptr->max_export + 1; level; level--) {
326 survey = survey->up;
327 SVX_ASSERT(survey);
329 s = osstrdup(sprint_prefix(survey));
330 p = sprint_prefix(ptr);
331 if (survey->filename) {
332 compile_diagnostic_pfx(DIAG_ERR, survey,
333 /*Station “%s” not exported from survey “%s”*/26,
334 p, s);
335 } else {
336 compile_diagnostic(DIAG_ERR, /*Station “%s” not exported from survey “%s”*/26, p, s);
338 osfree(s);
339 #if 0
340 printf(" *** pfx %s warning not exported enough depth %d "
341 "ptr->max_export %d\n", sprint_prefix(ptr),
342 depth, ptr->max_export);
343 #endif
345 if (!fImplicitPrefix && (pfx_flags & PFX_WARN_SEPARATOR)) {
346 filepos fp_tmp;
347 get_pos(&fp_tmp);
348 set_pos(&fp_firstsep);
349 compile_diagnostic(DIAG_WARN|DIAG_COL, /*Separator in survey name*/392);
350 set_pos(&fp_tmp);
352 return ptr;
355 /* if numeric expr is omitted: if f_optional return HUGE_REAL, else longjmp */
356 static real
357 read_number(bool f_optional, bool f_unsigned)
359 bool fPositive = fTrue, fDigits = fFalse;
360 real n = (real)0.0;
361 filepos fp;
362 int ch_old;
364 get_pos(&fp);
365 ch_old = ch;
366 if (!f_unsigned) {
367 fPositive = !isMinus(ch);
368 if (isSign(ch)) nextch();
371 while (isdigit(ch)) {
372 n = n * (real)10.0 + (char)(ch - '0');
373 nextch();
374 fDigits = fTrue;
377 if (isDecimal(ch)) {
378 real mult = (real)1.0;
379 nextch();
380 while (isdigit(ch)) {
381 mult *= (real).1;
382 n += (char)(ch - '0') * mult;
383 fDigits = fTrue;
384 nextch();
388 /* !'fRead' => !fDigits so fDigits => 'fRead' */
389 if (fDigits) return (fPositive ? n : -n);
391 /* didn't read a valid number. If it's optional, reset filepos & return */
392 set_pos(&fp);
393 if (f_optional) {
394 return HUGE_REAL;
397 if (isOmit(ch_old)) {
398 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Field may not be omitted*/8);
399 } else {
400 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
402 LONGJMP(file.jbSkipLine);
403 return 0.0; /* for brain-fried compilers */
406 static real
407 read_quadrant(bool f_optional)
409 enum {
410 POINT_N = 0,
411 POINT_E = 1,
412 POINT_S = 2,
413 POINT_W = 3,
414 POINT_NONE = -1
416 static const sztok pointtab[] = {
417 {"E", POINT_E },
418 {"N", POINT_N },
419 {"S", POINT_S },
420 {"W", POINT_W },
421 {NULL, POINT_NONE }
423 static const sztok pointewtab[] = {
424 {"E", POINT_E },
425 {"W", POINT_W },
426 {NULL, POINT_NONE }
428 if (f_optional && isOmit(ch)) {
429 return HUGE_REAL;
431 const int quad = 90;
432 filepos fp;
433 get_pos(&fp);
434 get_token_no_blanks();
435 int first_point = match_tok(pointtab, TABSIZE(pointtab));
436 if (first_point == POINT_NONE) {
437 set_pos(&fp);
438 if (isOmit(ch)) {
439 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Field may not be omitted*/8);
441 compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
442 LONGJMP(file.jbSkipLine);
443 return 0.0; /* for brain-fried compilers */
445 real r = read_number(fTrue, fTrue);
446 if (r == HUGE_REAL) {
447 if (isSign(ch) || isDecimal(ch)) {
448 /* Give better errors for S-0E, N+10W, N.E, etc. */
449 set_pos(&fp);
450 compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
451 LONGJMP(file.jbSkipLine);
452 return 0.0; /* for brain-fried compilers */
454 /* N, S, E or W. */
455 return first_point * quad;
457 if (first_point == POINT_E || first_point == POINT_W) {
458 set_pos(&fp);
459 compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
460 LONGJMP(file.jbSkipLine);
461 return 0.0; /* for brain-fried compilers */
464 get_token_no_blanks();
465 int second_point = match_tok(pointewtab, TABSIZE(pointewtab));
466 if (second_point == POINT_NONE) {
467 set_pos(&fp);
468 compile_diagnostic_token_show(DIAG_ERR, /*Expecting quadrant bearing, found “%s”*/483);
469 LONGJMP(file.jbSkipLine);
470 return 0.0; /* for brain-fried compilers */
473 if (r > quad) {
474 set_pos(&fp);
475 compile_diagnostic_token_show(DIAG_ERR|DIAG_COL, /*Suspicious compass reading*/59);
476 LONGJMP(file.jbSkipLine);
477 return 0.0; /* for brain-fried compilers */
480 if (first_point == POINT_N) {
481 if (second_point == POINT_W) {
482 r = quad * 4 - r;
484 } else {
485 if (second_point == POINT_W) {
486 r += quad * 2;
487 } else {
488 r = quad * 2 - r;
491 return r;
494 extern real
495 read_numeric(bool f_optional)
497 skipblanks();
498 return read_number(f_optional, fFalse);
501 extern real
502 read_numeric_multi(bool f_optional, bool f_quadrants, int *p_n_readings)
504 size_t n_readings = 0;
505 real tot = (real)0.0;
507 skipblanks();
508 if (!isOpen(ch)) {
509 real r = 0;
510 if (!f_quadrants)
511 r = read_number(f_optional, fFalse);
512 else
513 r = read_quadrant(f_optional);
514 if (p_n_readings) *p_n_readings = (r == HUGE_REAL ? 0 : 1);
515 return r;
517 nextch();
519 skipblanks();
520 do {
521 if (!f_quadrants)
522 tot += read_number(fFalse, fFalse);
523 else
524 tot += read_quadrant(fFalse);
525 ++n_readings;
526 skipblanks();
527 } while (!isClose(ch));
528 nextch();
530 if (p_n_readings) *p_n_readings = n_readings;
531 /* FIXME: special averaging for bearings ... */
532 /* And for percentage gradient */
533 return tot / n_readings;
536 /* read numeric expr or omit (return HUGE_REAL); else longjmp */
537 extern real
538 read_bearing_multi_or_omit(bool f_quadrants, int *p_n_readings)
540 real v;
541 v = read_numeric_multi(fTrue, f_quadrants, p_n_readings);
542 if (v == HUGE_REAL) {
543 if (!isOmit(ch)) {
544 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
545 LONGJMP(file.jbSkipLine);
546 return 0.0; /* for brain-fried compilers */
548 nextch();
550 return v;
553 /* Don't skip blanks, variable error code */
554 static unsigned int
555 read_uint_internal(int errmsg, const filepos *fp)
557 unsigned int n = 0;
558 if (!isdigit(ch)) {
559 if (fp) set_pos(fp);
560 compile_diagnostic_token_show(DIAG_ERR, errmsg);
561 LONGJMP(file.jbSkipLine);
563 while (isdigit(ch)) {
564 n = n * 10 + (char)(ch - '0');
565 nextch();
567 return n;
570 extern unsigned int
571 read_uint(void)
573 skipblanks();
574 return read_uint_internal(/*Expecting numeric field, found “%s”*/9, NULL);
577 extern void
578 read_string(char **pstr, int *plen)
580 s_zero(pstr);
582 skipblanks();
583 if (ch == '\"') {
584 /* String quoted in "" */
585 nextch();
586 while (1) {
587 if (isEol(ch)) {
588 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Missing \"*/69);
589 LONGJMP(file.jbSkipLine);
592 if (ch == '\"') break;
594 s_catchar(pstr, plen, ch);
595 nextch();
597 if (!*pstr) {
598 /* Return empty string for "", not NULL. */
599 s_catchar(pstr, plen, '\0');
601 nextch();
602 } else {
603 /* Unquoted string */
604 while (1) {
605 if (isEol(ch) || isComm(ch)) {
606 if (!*pstr || !(*pstr)[0]) {
607 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting string field*/121);
608 LONGJMP(file.jbSkipLine);
610 return;
613 if (isBlank(ch)) break;
615 s_catchar(pstr, plen, ch);
616 nextch();
621 extern void
622 read_date(int *py, int *pm, int *pd)
624 unsigned int y = 0, m = 0, d = 0;
625 filepos fp_date;
627 skipblanks();
629 get_pos(&fp_date);
630 y = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
631 /* Two digit year is 19xx. */
632 if (y < 100) {
633 filepos fp_save;
634 get_pos(&fp_save);
635 y += 1900;
636 set_pos(&fp_date);
637 /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
638 compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Assuming 2 digit year is %d*/76, y);
639 set_pos(&fp_save);
641 if (y < 1900 || y > 2078) {
642 set_pos(&fp_date);
643 compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year (< 1900 or > 2078)*/58);
644 LONGJMP(file.jbSkipLine);
645 return; /* for brain-fried compilers */
647 if (ch == '.') {
648 filepos fp;
649 nextch();
650 get_pos(&fp);
651 m = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
652 if (m < 1 || m > 12) {
653 set_pos(&fp);
654 compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
655 LONGJMP(file.jbSkipLine);
656 return; /* for brain-fried compilers */
658 if (ch == '.') {
659 nextch();
660 get_pos(&fp);
661 d = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
662 if (d < 1 || d > last_day(y, m)) {
663 set_pos(&fp);
664 /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
665 compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid day of the month*/87);
666 LONGJMP(file.jbSkipLine);
667 return; /* for brain-fried compilers */
671 if (py) *py = y;
672 if (pm) *pm = m;
673 if (pd) *pd = d;