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
25 #include <stddef.h> /* for offsetof */
28 #include "commands.h" /* For match_tok(), etc */
40 # define LONGJMP(JB) longjmp((JB), 1)
42 # define LONGJMP(JB) exit(1)
45 int root_depr_count
= 0;
48 new_anon_station(void)
50 prefix
*name
= osnew(prefix
);
55 name
->up
= pcs
->Prefix
;
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
;
67 /* if prefix is omitted: if PFX_OPT set return NULL, otherwise use longjmp */
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
;
79 bool fImplicitPrefix
= fTrue
;
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);
98 if (!isSep(ch
)) return ptr
;
99 /* Allow optional SEPARATOR after ROOT */
100 get_pos(&fp_firstsep
);
103 fImplicitPrefix
= fFalse
;
108 if ((pfx_flags
& PFX_ANON
) &&
109 (isSep(ch
) || (pcs
->dash_for_anon_wall_station
&& ch
== '-'))) {
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
)) {
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
) {
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.
137 if (TSTBIT(pcs
->flags
, FLAGS_ANON_ONE_END
)) {
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
);
147 if (ch
== first_ch
) {
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
)) {
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();
175 /* Need a new name buffer */
176 name
= osmalloc(name_len
);
178 /* i==0 iff this is the first pass */
183 while (isNames(ch
)) {
184 if (i
< pcs
->Truncate
) {
186 name
[i
++] = (pcs
->Case
== LOWER
? tolower(ch
) :
187 (pcs
->Case
== OFF
? ch
: toupper(ch
)));
189 name_len
= name_len
+ name_len
;
190 name
= osrealloc(name
, name_len
);
196 fImplicitPrefix
= fFalse
;
197 get_pos(&fp_firstsep
);
204 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting survey name*/89);
206 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Expecting station name*/28);
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
;
222 /* Special case first time around at each level */
223 name
= osrealloc(name
, i
);
227 ptr
->right
= ptr
->down
= NULL
;
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
;
241 /* Use caching to speed up adding an increasing sequence to a
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) {
255 /* ie we got to one that was higher, or the end */
257 name
= osrealloc(name
, i
);
258 newptr
= osnew(prefix
);
259 newptr
->ident
= name
;
262 back_ptr
->down
= newptr
;
264 ptrPrev
->right
= newptr
;
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
);
280 cached_survey
= back_ptr
;
281 cached_station
= ptr
;
284 f_optional
= fFalse
; /* disallow after first level */
285 if (isSep(ch
)) get_pos(&fp_firstsep
);
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
);
293 /* fNew means SFLAGS_SURVEY is currently set */
294 SVX_ASSERT(TSTBIT(ptr
->sflags
, SFLAGS_SURVEY
));
296 ptr
->sflags
&= ~BIT(SFLAGS_SURVEY
);
297 if (TSTBIT(pcs
->infer
, INFER_EXPORTS
)) ptr
->min_export
= USHRT_MAX
;
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,
310 if (!fSurvey
&& TSTBIT(pcs
->infer
, INFER_EXPORTS
)) ptr
->min_export
= USHRT_MAX
;
313 /* check the export level */
315 printf("R min %d max %d depth %d pfx %s\n",
316 ptr
->min_export
, ptr
->max_export
, depth
, sprint_prefix(ptr
));
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
;
325 for (level
= ptr
->max_export
+ 1; level
; level
--) {
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,
336 compile_diagnostic(DIAG_ERR
, /*Station “%s” not exported from survey “%s”*/26, p
, s
);
340 printf(" *** pfx %s warning not exported enough depth %d "
341 "ptr->max_export %d\n", sprint_prefix(ptr
),
342 depth
, ptr
->max_export
);
345 if (!fImplicitPrefix
&& (pfx_flags
& PFX_WARN_SEPARATOR
)) {
348 set_pos(&fp_firstsep
);
349 compile_diagnostic(DIAG_WARN
|DIAG_COL
, /*Separator in survey name*/392);
355 /* if numeric expr is omitted: if f_optional return HUGE_REAL, else longjmp */
357 read_number(bool f_optional
, bool f_unsigned
)
359 bool fPositive
= fTrue
, fDigits
= fFalse
;
367 fPositive
= !isMinus(ch
);
368 if (isSign(ch
)) nextch();
371 while (isdigit(ch
)) {
372 n
= n
* (real
)10.0 + (char)(ch
- '0');
378 real mult
= (real
)1.0;
380 while (isdigit(ch
)) {
382 n
+= (char)(ch
- '0') * mult
;
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 */
397 if (isOmit(ch_old
)) {
398 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Field may not be omitted*/8);
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 */
407 read_quadrant(bool f_optional
)
416 static const sztok pointtab
[] = {
423 static const sztok pointewtab
[] = {
428 if (f_optional
&& isOmit(ch
)) {
434 get_token_no_blanks();
435 int first_point
= match_tok(pointtab
, TABSIZE(pointtab
));
436 if (first_point
== POINT_NONE
) {
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. */
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 */
455 return first_point
* quad
;
457 if (first_point
== POINT_E
|| first_point
== POINT_W
) {
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
) {
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 */
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
) {
485 if (second_point
== POINT_W
) {
495 read_numeric(bool f_optional
)
498 return read_number(f_optional
, fFalse
);
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;
511 r
= read_number(f_optional
, fFalse
);
513 r
= read_quadrant(f_optional
);
514 if (p_n_readings
) *p_n_readings
= (r
== HUGE_REAL
? 0 : 1);
522 tot
+= read_number(fFalse
, fFalse
);
524 tot
+= read_quadrant(fFalse
);
527 } while (!isClose(ch
));
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 */
538 read_bearing_multi_or_omit(bool f_quadrants
, int *p_n_readings
)
541 v
= read_numeric_multi(fTrue
, f_quadrants
, p_n_readings
);
542 if (v
== HUGE_REAL
) {
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 */
553 /* Don't skip blanks, variable error code */
555 read_uint_internal(int errmsg
, const filepos
*fp
)
560 compile_diagnostic_token_show(DIAG_ERR
, errmsg
);
561 LONGJMP(file
.jbSkipLine
);
563 while (isdigit(ch
)) {
564 n
= n
* 10 + (char)(ch
- '0');
574 return read_uint_internal(/*Expecting numeric field, found “%s”*/9, NULL
);
578 read_string(char **pstr
, int *plen
)
584 /* String quoted in "" */
588 compile_diagnostic(DIAG_ERR
|DIAG_COL
, /*Missing \"*/69);
589 LONGJMP(file
.jbSkipLine
);
592 if (ch
== '\"') break;
594 s_catchar(pstr
, plen
, ch
);
598 /* Return empty string for "", not NULL. */
599 s_catchar(pstr
, plen
, '\0');
603 /* Unquoted string */
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
);
613 if (isBlank(ch
)) break;
615 s_catchar(pstr
, plen
, ch
);
622 read_date(int *py
, int *pm
, int *pd
)
624 unsigned int y
= 0, m
= 0, d
= 0;
630 y
= read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date
);
631 /* Two digit year is 19xx. */
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
);
641 if (y
< 1900 || y
> 2078) {
643 compile_diagnostic(DIAG_WARN
|DIAG_UINT
, /*Invalid year (< 1900 or > 2078)*/58);
644 LONGJMP(file
.jbSkipLine
);
645 return; /* for brain-fried compilers */
651 m
= read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date
);
652 if (m
< 1 || m
> 12) {
654 compile_diagnostic(DIAG_WARN
|DIAG_UINT
, /*Invalid month*/86);
655 LONGJMP(file
.jbSkipLine
);
656 return; /* for brain-fried compilers */
661 d
= read_uint_internal(/*Expecting date, found “%s”*/198, &fp_date
);
662 if (d
< 1 || d
> last_day(y
, m
)) {
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 */