Fix coding style
[survex.git] / src / readval.c
blob37d0895ca8af8645cf8e7d9020a3518ff011c96b
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 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 "date.h"
29 #include "debug.h"
30 #include "filename.h"
31 #include "message.h"
32 #include "readval.h"
33 #include "datain.h"
34 #include "netbits.h"
35 #include "osalloc.h"
36 #include "str.h"
38 #ifdef HAVE_SETJMP_H
39 # define LONGJMP(JB) longjmp((JB), 1)
40 #else
41 # define LONGJMP(JB) exit(1)
42 #endif
44 int root_depr_count = 0;
46 static prefix *
47 new_anon_station(void)
49 prefix *name = osnew(prefix);
50 name->pos = NULL;
51 name->ident = NULL;
52 name->shape = 0;
53 name->stn = NULL;
54 name->up = pcs->Prefix;
55 name->down = NULL;
56 name->filename = file.filename;
57 name->line = file.line;
58 name->min_export = name->max_export = 0;
59 name->sflags = BIT(SFLAGS_ANON);
60 /* Keep linked list of anon stations for node stats. */
61 name->right = anon_list;
62 anon_list = name;
63 return name;
66 /* if prefix is omitted: if PFX_OPT set return NULL, otherwise use longjmp */
67 extern prefix *
68 read_prefix(unsigned pfx_flags)
70 bool f_optional = !!(pfx_flags & PFX_OPT);
71 bool fSurvey = !!(pfx_flags & PFX_SURVEY);
72 bool fSuspectTypo = !!(pfx_flags & PFX_SUSPECT_TYPO);
73 prefix *back_ptr, *ptr;
74 char *name;
75 size_t name_len = 32;
76 size_t i;
77 bool fNew;
78 bool fImplicitPrefix = fTrue;
79 int depth = -1;
80 filepos fp_firstsep;
82 skipblanks();
83 #ifndef NO_DEPRECATED
84 if (isRoot(ch)) {
85 if (!(pfx_flags & PFX_ALLOW_ROOT)) {
86 compile_diagnostic(DIAG_ERR|DIAG_COL, /*ROOT is deprecated*/25);
87 LONGJMP(file.jbSkipLine);
89 if (root_depr_count < 5) {
90 compile_diagnostic(DIAG_WARN|DIAG_COL, /*ROOT is deprecated*/25);
91 if (++root_depr_count == 5)
92 compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
94 nextch();
95 ptr = root;
96 if (!isNames(ch)) {
97 if (!isSep(ch)) return ptr;
98 /* Allow optional SEPARATOR after ROOT */
99 get_pos(&fp_firstsep);
100 nextch();
102 fImplicitPrefix = fFalse;
103 #else
104 if (0) {
105 #endif
106 } else {
107 if ((pfx_flags & PFX_ANON) &&
108 (isSep(ch) || (pcs->dash_for_anon_wall_station && ch == '-'))) {
109 int first_ch = ch;
110 filepos here;
111 get_pos(&here);
112 nextch();
113 if (isBlank(ch) || isEol(ch)) {
114 if (!isSep(first_ch))
115 goto anon_wall_station;
116 /* A single separator alone ('.' by default) is an anonymous
117 * station which is on a point inside the passage and implies
118 * the leg to it is a splay.
120 if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
121 set_pos(&here);
122 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Can't have a leg between two anonymous stations*/3);
123 LONGJMP(file.jbSkipLine);
125 pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
126 return new_anon_station();
128 if (isSep(first_ch) && ch == first_ch) {
129 nextch();
130 if (isBlank(ch) || isEol(ch)) {
131 /* A double separator ('..' by default) is an anonymous station
132 * which is on the wall and implies the leg to it is a splay.
134 prefix * pfx;
135 anon_wall_station:
136 if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
137 set_pos(&here);
138 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Can't have a leg between two anonymous stations*/3);
139 LONGJMP(file.jbSkipLine);
141 pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
142 pfx = new_anon_station();
143 pfx->sflags |= BIT(SFLAGS_WALL);
144 return pfx;
146 if (ch == first_ch) {
147 nextch();
148 if (isBlank(ch) || isEol(ch)) {
149 /* A triple separator ('...' by default) is an anonymous
150 * station, but otherwise not handled specially (e.g. for
151 * a single leg down an unexplored side passage to a station
152 * which isn't refindable).
154 if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
155 set_pos(&here);
156 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Can't have a leg between two anonymous stations*/3);
157 LONGJMP(file.jbSkipLine);
159 pcs->flags |= BIT(FLAGS_ANON_ONE_END);
160 return new_anon_station();
164 set_pos(&here);
166 ptr = pcs->Prefix;
169 i = 0;
170 name = NULL;
171 do {
172 fNew = fFalse;
173 if (name == NULL) {
174 /* Need a new name buffer */
175 name = osmalloc(name_len);
177 /* i==0 iff this is the first pass */
178 if (i) {
179 i = 0;
180 nextch();
182 while (isNames(ch)) {
183 if (i < pcs->Truncate) {
184 /* truncate name */
185 name[i++] = (pcs->Case == LOWER ? tolower(ch) :
186 (pcs->Case == OFF ? ch : toupper(ch)));
187 if (i >= name_len) {
188 name_len = name_len + name_len;
189 name = osrealloc(name, name_len);
192 nextch();
194 if (isSep(ch)) {
195 fImplicitPrefix = fFalse;
196 get_pos(&fp_firstsep);
198 if (i == 0) {
199 osfree(name);
200 if (!f_optional) {
201 if (isEol(ch)) {
202 if (fSurvey) {
203 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting survey name*/89);
204 } else {
205 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting station name*/28);
207 } else {
208 /* TRANSLATORS: Here "station" is a survey station, not a train station. */
209 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Character “%c” not allowed in station name (use *SET NAMES to set allowed characters)*/7, ch);
211 LONGJMP(file.jbSkipLine);
213 return (prefix *)NULL;
216 name[i++] = '\0';
218 back_ptr = ptr;
219 ptr = ptr->down;
220 if (ptr == NULL) {
221 /* Special case first time around at each level */
222 name = osrealloc(name, i);
223 ptr = osnew(prefix);
224 ptr->ident = name;
225 name = NULL;
226 ptr->right = ptr->down = NULL;
227 ptr->pos = NULL;
228 ptr->shape = 0;
229 ptr->stn = NULL;
230 ptr->up = back_ptr;
231 ptr->filename = file.filename;
232 ptr->line = file.line;
233 ptr->min_export = ptr->max_export = 0;
234 ptr->sflags = BIT(SFLAGS_SURVEY);
235 if (fSuspectTypo && !fImplicitPrefix)
236 ptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
237 back_ptr->down = ptr;
238 fNew = fTrue;
239 } else {
240 /* Use caching to speed up adding an increasing sequence to a
241 * large survey */
242 static prefix *cached_survey = NULL, *cached_station = NULL;
243 prefix *ptrPrev = NULL;
244 int cmp = 1; /* result of strcmp ( -ve for <, 0 for =, +ve for > ) */
245 if (cached_survey == back_ptr) {
246 cmp = strcmp(cached_station->ident, name);
247 if (cmp <= 0) ptr = cached_station;
249 while (ptr && (cmp = strcmp(ptr->ident, name))<0) {
250 ptrPrev = ptr;
251 ptr = ptr->right;
253 if (cmp) {
254 /* ie we got to one that was higher, or the end */
255 prefix *newptr;
256 name = osrealloc(name, i);
257 newptr = osnew(prefix);
258 newptr->ident = name;
259 name = NULL;
260 if (ptrPrev == NULL)
261 back_ptr->down = newptr;
262 else
263 ptrPrev->right = newptr;
264 newptr->right = ptr;
265 newptr->down = NULL;
266 newptr->pos = NULL;
267 newptr->shape = 0;
268 newptr->stn = NULL;
269 newptr->up = back_ptr;
270 newptr->filename = file.filename;
271 newptr->line = file.line;
272 newptr->min_export = newptr->max_export = 0;
273 newptr->sflags = BIT(SFLAGS_SURVEY);
274 if (fSuspectTypo && !fImplicitPrefix)
275 newptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
276 ptr = newptr;
277 fNew = fTrue;
279 cached_survey = back_ptr;
280 cached_station = ptr;
282 depth++;
283 f_optional = fFalse; /* disallow after first level */
284 if (isSep(ch)) get_pos(&fp_firstsep);
285 } while (isSep(ch));
286 if (name) osfree(name);
288 /* don't warn about a station that is referred to twice */
289 if (!fNew) ptr->sflags &= ~BIT(SFLAGS_SUSPECTTYPO);
291 if (fNew) {
292 /* fNew means SFLAGS_SURVEY is currently set */
293 SVX_ASSERT(TSTBIT(ptr->sflags, SFLAGS_SURVEY));
294 if (!fSurvey) {
295 ptr->sflags &= ~BIT(SFLAGS_SURVEY);
296 if (TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
298 } else {
299 /* check that the same name isn't being used for a survey and station */
300 if (fSurvey ^ TSTBIT(ptr->sflags, SFLAGS_SURVEY)) {
301 /* TRANSLATORS: Here "station" is a survey station, not a train station.
303 * Here "survey" is a "cave map" rather than list of questions - it should be
304 * translated to the terminology that cavers using the language would use.
306 compile_diagnostic(DIAG_ERR, /*“%s” can’t be both a station and a survey*/27,
307 sprint_prefix(ptr));
309 if (!fSurvey && TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
312 /* check the export level */
313 #if 0
314 printf("R min %d max %d depth %d pfx %s\n",
315 ptr->min_export, ptr->max_export, depth, sprint_prefix(ptr));
316 #endif
317 if (ptr->min_export == 0 || ptr->min_export == USHRT_MAX) {
318 if (depth > ptr->max_export) ptr->max_export = depth;
319 } else if (ptr->max_export < depth) {
320 prefix *survey = ptr;
321 char *s;
322 const char *p;
323 int level;
324 for (level = ptr->max_export + 1; level; level--) {
325 survey = survey->up;
326 SVX_ASSERT(survey);
328 s = osstrdup(sprint_prefix(survey));
329 p = sprint_prefix(ptr);
330 if (survey->filename) {
331 compile_diagnostic_pfx(DIAG_ERR, survey,
332 /*Station “%s” not exported from survey “%s”*/26,
333 p, s);
334 } else {
335 compile_diagnostic(DIAG_ERR, /*Station “%s” not exported from survey “%s”*/26, p, s);
337 osfree(s);
338 #if 0
339 printf(" *** pfx %s warning not exported enough depth %d "
340 "ptr->max_export %d\n", sprint_prefix(ptr),
341 depth, ptr->max_export);
342 #endif
344 if (!fImplicitPrefix && (pfx_flags & PFX_WARN_SEPARATOR)) {
345 filepos fp_tmp;
346 get_pos(&fp_tmp);
347 set_pos(&fp_firstsep);
348 compile_diagnostic(DIAG_WARN|DIAG_COL, /*Separator in survey name*/392);
349 set_pos(&fp_tmp);
351 return ptr;
354 /* if numeric expr is omitted: if f_optional return HUGE_REAL, else longjmp */
355 static real
356 read_number(bool f_optional)
358 bool fPositive, fDigits = fFalse;
359 real n = (real)0.0;
360 filepos fp;
361 int ch_old;
363 get_pos(&fp);
364 ch_old = ch;
365 fPositive = !isMinus(ch);
366 if (isSign(ch)) nextch();
368 while (isdigit(ch)) {
369 n = n * (real)10.0 + (char)(ch - '0');
370 nextch();
371 fDigits = fTrue;
374 if (isDecimal(ch)) {
375 real mult = (real)1.0;
376 nextch();
377 while (isdigit(ch)) {
378 mult *= (real).1;
379 n += (char)(ch - '0') * mult;
380 fDigits = fTrue;
381 nextch();
385 /* !'fRead' => !fDigits so fDigits => 'fRead' */
386 if (fDigits) return (fPositive ? n : -n);
388 /* didn't read a valid number. If it's optional, reset filepos & return */
389 set_pos(&fp);
390 if (f_optional) {
391 return HUGE_REAL;
394 if (isOmit(ch_old)) {
395 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Field may not be omitted*/8);
396 } else {
397 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
399 LONGJMP(file.jbSkipLine);
400 return 0.0; /* for brain-fried compilers */
403 extern real
404 read_numeric(bool f_optional)
406 skipblanks();
407 return read_number(f_optional);
410 extern real
411 read_numeric_multi(bool f_optional, int *p_n_readings)
413 size_t n_readings = 0;
414 real tot = (real)0.0;
416 skipblanks();
417 if (!isOpen(ch)) {
418 real r = read_number(f_optional);
419 if (p_n_readings) *p_n_readings = (r == HUGE_REAL ? 0 : 1);
420 return r;
422 nextch();
424 skipblanks();
425 do {
426 tot += read_number(fFalse);
427 ++n_readings;
428 skipblanks();
429 } while (!isClose(ch));
430 nextch();
432 if (p_n_readings) *p_n_readings = n_readings;
433 /* FIXME: special averaging for bearings ... */
434 /* And for percentage gradient */
435 return tot / n_readings;
438 /* read numeric expr or omit (return HUGE_REAL); else longjmp */
439 extern real
440 read_numeric_multi_or_omit(int *p_n_readings)
442 real v = read_numeric_multi(fTrue, p_n_readings);
443 if (v == HUGE_REAL) {
444 if (!isOmit(ch)) {
445 compile_diagnostic_token_show(DIAG_ERR, /*Expecting numeric field, found “%s”*/9);
446 LONGJMP(file.jbSkipLine);
447 return 0.0; /* for brain-fried compilers */
449 nextch();
451 return v;
454 /* Don't skip blanks, variable error code */
455 static unsigned int
456 read_uint_internal(int errmsg, const filepos *fp)
458 unsigned int n = 0;
459 if (!isdigit(ch)) {
460 if (fp) set_pos(fp);
461 compile_diagnostic_token_show(DIAG_ERR, errmsg);
462 LONGJMP(file.jbSkipLine);
464 while (isdigit(ch)) {
465 n = n * 10 + (char)(ch - '0');
466 nextch();
468 return n;
471 extern unsigned int
472 read_uint(void)
474 skipblanks();
475 return read_uint_internal(/*Expecting numeric field, found “%s”*/9, NULL);
478 extern void
479 read_string(char **pstr, int *plen)
481 s_zero(pstr);
483 skipblanks();
484 if (ch == '\"') {
485 /* String quoted in "" */
486 nextch();
487 while (1) {
488 if (isEol(ch)) {
489 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Missing \"*/69);
490 LONGJMP(file.jbSkipLine);
493 if (ch == '\"') break;
495 s_catchar(pstr, plen, ch);
496 nextch();
498 nextch();
499 } else {
500 /* Unquoted string */
501 while (1) {
502 if (isEol(ch) || isComm(ch)) {
503 if (!*pstr || !(*pstr)[0]) {
504 compile_diagnostic(DIAG_ERR|DIAG_COL, /*Expecting string field*/121);
505 LONGJMP(file.jbSkipLine);
507 return;
510 if (isBlank(ch)) break;
512 s_catchar(pstr, plen, ch);
513 nextch();
518 extern void
519 read_date(int *py, int *pm, int *pd)
521 unsigned int y = 0, m = 0, d = 0;
522 filepos fp_date;
524 skipblanks();
526 get_pos(&fp_date);
527 y = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
528 /* Two digit year is 19xx. */
529 if (y < 100) {
530 filepos fp_save;
531 get_pos(&fp_save);
532 y += 1900;
533 set_pos(&fp_date);
534 /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
535 compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Assuming 2 digit year is %d*/76, y);
536 set_pos(&fp_save);
538 if (y < 1900 || y > 2078) {
539 set_pos(&fp_date);
540 compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year (< 1900 or > 2078)*/58);
541 LONGJMP(file.jbSkipLine);
542 return; /* for brain-fried compilers */
544 if (ch == '.') {
545 filepos fp;
546 nextch();
547 get_pos(&fp);
548 m = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
549 if (m < 1 || m > 12) {
550 set_pos(&fp);
551 compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
552 LONGJMP(file.jbSkipLine);
553 return; /* for brain-fried compilers */
555 if (ch == '.') {
556 nextch();
557 get_pos(&fp);
558 d = read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date);
559 if (d < 1 || d > last_day(y, m)) {
560 set_pos(&fp);
561 /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
562 compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid day of the month*/87);
563 LONGJMP(file.jbSkipLine);
564 return; /* for brain-fried compilers */
568 if (py) *py = y;
569 if (pm) *pm = m;
570 if (pd) *pd = d;