Tweak code formatting
[survex.git] / src / readval.c
blob826b32294c1fa690ed0d3ff9d420b53b70096281
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 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;
81 skipblanks();
82 #ifndef NO_DEPRECATED
83 if (isRoot(ch)) {
84 if (!(pfx_flags & PFX_ALLOW_ROOT)) {
85 compile_error(-/*ROOT is deprecated*/25);
86 LONGJMP(file.jbSkipLine);
88 if (root_depr_count < 5) {
89 compile_warning(-/*ROOT is deprecated*/25);
90 if (++root_depr_count == 5)
91 compile_warning(/*Further uses of this deprecated feature will not be reported*/95);
93 nextch();
94 ptr = root;
95 if (!isNames(ch)) {
96 if (!isSep(ch)) return ptr;
97 /* Allow optional SEPARATOR after ROOT */
98 nextch();
100 fImplicitPrefix = fFalse;
101 #else
102 if (0) {
103 #endif
104 } else {
105 if ((pfx_flags & PFX_ANON) &&
106 (isSep(ch) || (pcs->dash_for_anon_wall_station && ch == '-'))) {
107 int first_ch = ch;
108 filepos here;
109 get_pos(&here);
110 nextch();
111 if (isBlank(ch) || isEol(ch)) {
112 if (!isSep(first_ch))
113 goto anon_wall_station;
114 /* A single separator alone ('.' by default) is an anonymous
115 * station which is on a point inside the passage and implies
116 * the leg to it is a splay.
118 if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
119 compile_error(-/*Can't have a leg between two anonymous stations*/3);
120 LONGJMP(file.jbSkipLine);
122 pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
123 return new_anon_station();
125 if (isSep(first_ch) && ch == first_ch) {
126 nextch();
127 if (isBlank(ch) || isEol(ch)) {
128 /* A double separator ('..' by default) is an anonymous station
129 * which is on the wall and implies the leg to it is a splay.
131 prefix * pfx;
132 anon_wall_station:
133 if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
134 compile_error(-/*Can't have a leg between two anonymous stations*/3);
135 LONGJMP(file.jbSkipLine);
137 pcs->flags |= BIT(FLAGS_ANON_ONE_END) | BIT(FLAGS_IMPLICIT_SPLAY);
138 pfx = new_anon_station();
139 pfx->sflags |= BIT(SFLAGS_WALL);
140 return pfx;
142 if (ch == first_ch) {
143 nextch();
144 if (isBlank(ch) || isEol(ch)) {
145 /* A triple separator ('...' by default) is an anonymous
146 * station, but otherwise not handled specially (e.g. for
147 * a single leg down an unexplored side passage to a station
148 * which isn't refindable).
150 if (TSTBIT(pcs->flags, FLAGS_ANON_ONE_END)) {
151 compile_error(-/*Can't have a leg between two anonymous stations*/3);
152 LONGJMP(file.jbSkipLine);
154 pcs->flags |= BIT(FLAGS_ANON_ONE_END);
155 return new_anon_station();
159 set_pos(&here);
161 ptr = pcs->Prefix;
164 i = 0;
165 name = NULL;
166 do {
167 fNew = fFalse;
168 if (name == NULL) {
169 /* Need a new name buffer */
170 name = osmalloc(name_len);
172 /* i==0 iff this is the first pass */
173 if (i) {
174 i = 0;
175 nextch();
177 while (isNames(ch)) {
178 if (i < pcs->Truncate) {
179 /* truncate name */
180 name[i++] = (pcs->Case == LOWER ? tolower(ch) :
181 (pcs->Case == OFF ? ch : toupper(ch)));
182 if (i >= name_len) {
183 name_len = name_len + name_len;
184 name = osrealloc(name, name_len);
187 nextch();
189 if (isSep(ch)) fImplicitPrefix = fFalse;
190 if (i == 0) {
191 osfree(name);
192 if (!f_optional) {
193 if (isEol(ch)) {
194 if (fSurvey) {
195 compile_error(-/*Expecting survey name*/89);
196 } else {
197 compile_error(-/*Expecting station name*/28);
199 } else {
200 /* TRANSLATORS: Here "station" is a survey station, not a train station. */
201 compile_error(-/*Character “%c” not allowed in station name (use *SET NAMES to set allowed characters)*/7, ch);
203 LONGJMP(file.jbSkipLine);
205 return (prefix *)NULL;
208 name[i++] = '\0';
210 back_ptr = ptr;
211 ptr = ptr->down;
212 if (ptr == NULL) {
213 /* Special case first time around at each level */
214 name = osrealloc(name, i);
215 ptr = osnew(prefix);
216 ptr->ident = name;
217 name = NULL;
218 ptr->right = ptr->down = NULL;
219 ptr->pos = NULL;
220 ptr->shape = 0;
221 ptr->stn = NULL;
222 ptr->up = back_ptr;
223 ptr->filename = file.filename;
224 ptr->line = file.line;
225 ptr->min_export = ptr->max_export = 0;
226 ptr->sflags = BIT(SFLAGS_SURVEY);
227 if (fSuspectTypo && !fImplicitPrefix)
228 ptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
229 back_ptr->down = ptr;
230 fNew = fTrue;
231 } else {
232 /* Use caching to speed up adding an increasing sequence to a
233 * large survey */
234 static prefix *cached_survey = NULL, *cached_station = NULL;
235 prefix *ptrPrev = NULL;
236 int cmp = 1; /* result of strcmp ( -ve for <, 0 for =, +ve for > ) */
237 if (cached_survey == back_ptr) {
238 cmp = strcmp(cached_station->ident, name);
239 if (cmp <= 0) ptr = cached_station;
241 while (ptr && (cmp = strcmp(ptr->ident, name))<0) {
242 ptrPrev = ptr;
243 ptr = ptr->right;
245 if (cmp) {
246 /* ie we got to one that was higher, or the end */
247 prefix *newptr;
248 name = osrealloc(name, i);
249 newptr = osnew(prefix);
250 newptr->ident = name;
251 name = NULL;
252 if (ptrPrev == NULL)
253 back_ptr->down = newptr;
254 else
255 ptrPrev->right = newptr;
256 newptr->right = ptr;
257 newptr->down = NULL;
258 newptr->pos = NULL;
259 newptr->shape = 0;
260 newptr->stn = NULL;
261 newptr->up = back_ptr;
262 newptr->filename = file.filename;
263 newptr->line = file.line;
264 newptr->min_export = newptr->max_export = 0;
265 newptr->sflags = BIT(SFLAGS_SURVEY);
266 if (fSuspectTypo && !fImplicitPrefix)
267 newptr->sflags |= BIT(SFLAGS_SUSPECTTYPO);
268 ptr = newptr;
269 fNew = fTrue;
271 cached_survey = back_ptr;
272 cached_station = ptr;
274 depth++;
275 f_optional = fFalse; /* disallow after first level */
276 } while (isSep(ch));
277 if (name) osfree(name);
279 /* don't warn about a station that is referred to twice */
280 if (!fNew) ptr->sflags &= ~BIT(SFLAGS_SUSPECTTYPO);
282 if (fNew) {
283 /* fNew means SFLAGS_SURVEY is currently set */
284 SVX_ASSERT(TSTBIT(ptr->sflags, SFLAGS_SURVEY));
285 if (!fSurvey) {
286 ptr->sflags &= ~BIT(SFLAGS_SURVEY);
287 if (TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
289 } else {
290 /* check that the same name isn't being used for a survey and station */
291 if (fSurvey ^ TSTBIT(ptr->sflags, SFLAGS_SURVEY)) {
292 /* TRANSLATORS: Here "station" is a survey station, not a train station.
294 * Here "survey" is a "cave map" rather than list of questions - it should be
295 * translated to the terminology that cavers using the language would use.
297 compile_error(/*“%s” can’t be both a station and a survey*/27,
298 sprint_prefix(ptr));
300 if (!fSurvey && TSTBIT(pcs->infer, INFER_EXPORTS)) ptr->min_export = USHRT_MAX;
303 /* check the export level */
304 #if 0
305 printf("R min %d max %d depth %d pfx %s\n",
306 ptr->min_export, ptr->max_export, depth, sprint_prefix(ptr));
307 #endif
308 if (ptr->min_export == 0 || ptr->min_export == USHRT_MAX) {
309 if (depth > ptr->max_export) ptr->max_export = depth;
310 } else if (ptr->max_export < depth) {
311 prefix *survey = ptr;
312 char *s;
313 const char *p;
314 int level;
315 for (level = ptr->max_export + 1; level; level--) {
316 survey = survey->up;
317 SVX_ASSERT(survey);
319 s = osstrdup(sprint_prefix(survey));
320 p = sprint_prefix(ptr);
321 if (survey->filename) {
322 compile_error_pfx(survey,
323 /*Station “%s” not exported from survey “%s”*/26,
324 p, s);
325 } else {
326 compile_error(/*Station “%s” not exported from survey “%s”*/26, p, s);
328 osfree(s);
329 #if 0
330 printf(" *** pfx %s warning not exported enough depth %d "
331 "ptr->max_export %d\n", sprint_prefix(ptr),
332 depth, ptr->max_export);
333 #endif
335 if (!fImplicitPrefix && (pfx_flags & PFX_WARN_SEPARATOR)) {
336 compile_warning(/*Separator in survey name*/392);
338 return ptr;
341 /* if numeric expr is omitted: if f_optional return HUGE_REAL, else longjmp */
342 static real
343 read_number(bool f_optional)
345 bool fPositive, fDigits = fFalse;
346 real n = (real)0.0;
347 filepos fp;
348 int ch_old;
350 get_pos(&fp);
351 ch_old = ch;
352 fPositive = !isMinus(ch);
353 if (isSign(ch)) nextch();
355 while (isdigit(ch)) {
356 n = n * (real)10.0 + (char)(ch - '0');
357 nextch();
358 fDigits = fTrue;
361 if (isDecimal(ch)) {
362 real mult = (real)1.0;
363 nextch();
364 while (isdigit(ch)) {
365 mult *= (real).1;
366 n += (char)(ch - '0') * mult;
367 fDigits = fTrue;
368 nextch();
372 /* !'fRead' => !fDigits so fDigits => 'fRead' */
373 if (fDigits) return (fPositive ? n : -n);
375 /* didn't read a valid number. If it's optional, reset filepos & return */
376 set_pos(&fp);
377 if (f_optional) {
378 return HUGE_REAL;
381 if (isOmit(ch_old)) {
382 compile_error(-/*Field may not be omitted*/8);
383 } else {
384 compile_error_token(-/*Expecting numeric field, found “%s”*/9);
386 LONGJMP(file.jbSkipLine);
387 return 0.0; /* for brain-fried compilers */
390 extern real
391 read_numeric(bool f_optional)
393 skipblanks();
394 return read_number(f_optional);
397 extern real
398 read_numeric_multi(bool f_optional, int *p_n_readings)
400 size_t n_readings = 0;
401 real tot = (real)0.0;
403 skipblanks();
404 if (!isOpen(ch)) {
405 real r = read_number(f_optional);
406 if (p_n_readings) *p_n_readings = (r == HUGE_REAL ? 0 : 1);
407 return r;
409 nextch();
411 skipblanks();
412 do {
413 tot += read_number(fFalse);
414 ++n_readings;
415 skipblanks();
416 } while (!isClose(ch));
417 nextch();
419 if (p_n_readings) *p_n_readings = n_readings;
420 /* FIXME: special averaging for bearings ... */
421 /* And for percentage gradient */
422 return tot / n_readings;
425 /* read numeric expr or omit (return HUGE_REAL); else longjmp */
426 extern real
427 read_numeric_multi_or_omit(int *p_n_readings)
429 real v = read_numeric_multi(fTrue, p_n_readings);
430 if (v == HUGE_REAL) {
431 if (!isOmit(ch)) {
432 compile_error_token(-/*Expecting numeric field, found “%s”*/9);
433 LONGJMP(file.jbSkipLine);
434 return 0.0; /* for brain-fried compilers */
436 nextch();
438 return v;
441 /* Don't skip blanks, variable error code */
442 static unsigned int
443 read_uint_internal(int errmsg, const filepos *fp)
445 unsigned int n = 0;
446 if (!isdigit(ch)) {
447 if (fp) set_pos(fp);
448 compile_error_token(-errmsg);
449 LONGJMP(file.jbSkipLine);
451 while (isdigit(ch)) {
452 n = n * 10 + (char)(ch - '0');
453 nextch();
455 return n;
458 extern unsigned int
459 read_uint(void)
461 skipblanks();
462 return read_uint_internal(/*Expecting numeric field, found “%s”*/9, NULL);
465 extern void
466 read_string(char **pstr, int *plen)
468 s_zero(pstr);
470 skipblanks();
471 if (ch == '\"') {
472 /* String quoted in "" */
473 nextch();
474 while (1) {
475 if (isEol(ch)) {
476 compile_error(-/*Missing \"*/69);
477 LONGJMP(file.jbSkipLine);
480 if (ch == '\"') break;
482 s_catchar(pstr, plen, ch);
483 nextch();
485 nextch();
486 } else {
487 /* Unquoted string */
488 while (1) {
489 if (isEol(ch) || isComm(ch)) {
490 if (!*pstr || !(*pstr)[0]) {
491 compile_error(-/*Expecting string field*/121);
492 LONGJMP(file.jbSkipLine);
494 return;
497 if (isBlank(ch)) break;
499 s_catchar(pstr, plen, ch);
500 nextch();
505 extern void
506 read_date(int *py, int *pm, int *pd)
508 int y = 0, m = 0, d = 0;
509 filepos fp;
511 skipblanks();
513 get_pos(&fp);
514 y = read_uint_internal(/*Expecting date, found “%s”*/198, &fp);
515 /* Two digit year is 19xx. */
516 if (y < 100) y += 1900;
517 if (y < 1900 || y > 2078) {
518 compile_warning(/*Invalid year (< 1900 or > 2078)*/58);
519 LONGJMP(file.jbSkipLine);
520 return; /* for brain-fried compilers */
522 if (ch == '.') {
523 nextch();
524 m = read_uint_internal(/*Expecting date, found “%s”*/198, &fp);
525 if (m < 1 || m > 12) {
526 compile_warning(/*Invalid month*/86);
527 LONGJMP(file.jbSkipLine);
528 return; /* for brain-fried compilers */
530 if (ch == '.') {
531 nextch();
532 d = read_uint_internal(/*Expecting date, found “%s”*/198, &fp);
533 if (d < 1 || d > last_day(y, m)) {
534 /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
535 compile_warning(/*Invalid day of the month*/87);
536 LONGJMP(file.jbSkipLine);
537 return; /* for brain-fried compilers */
541 if (py) *py = y;
542 if (pm) *pm = m;
543 if (pd) *pd = d;