Show location for backsights out of tolerance
[survex.git] / src / commands.c
blob01d2b1e2bf2bd4319ee19a8aac236e525d5b2f9c
1 /* commands.c
2 * Code for directives
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 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
24 #include <assert.h>
25 #include <limits.h>
26 #include <stddef.h> /* for offsetof */
27 #include <string.h>
29 #include <proj.h>
31 #include "cavern.h"
32 #include "commands.h"
33 #include "datain.h"
34 #include "date.h"
35 #include "debug.h"
36 #include "filename.h"
37 #include "message.h"
38 #include "netbits.h"
39 #include "netskel.h"
40 #include "out.h"
41 #include "readval.h"
42 #include "str.h"
44 #define WGS84_DATUM_STRING "EPSG:4326"
46 static void
47 default_grade(settings *s)
49 /* Values correspond to those in bcra5.svx */
50 s->Var[Q_POS] = (real)sqrd(0.05);
51 s->Var[Q_LENGTH] = (real)sqrd(0.05);
52 s->Var[Q_BACKLENGTH] = (real)sqrd(0.05);
53 s->Var[Q_COUNT] = (real)sqrd(0.05);
54 s->Var[Q_DX] = s->Var[Q_DY] = s->Var[Q_DZ] = (real)sqrd(0.05);
55 s->Var[Q_BEARING] = (real)sqrd(rad(0.5));
56 s->Var[Q_GRADIENT] = (real)sqrd(rad(0.5));
57 s->Var[Q_BACKBEARING] = (real)sqrd(rad(0.5));
58 s->Var[Q_BACKGRADIENT] = (real)sqrd(rad(0.5));
59 /* SD of plumbed legs (0.25 degrees?) */
60 s->Var[Q_PLUMB] = (real)sqrd(rad(0.25));
61 /* SD of level legs (0.25 degrees?) */
62 s->Var[Q_LEVEL] = (real)sqrd(rad(0.25));
63 s->Var[Q_DEPTH] = (real)sqrd(0.05);
66 static void
67 default_truncate(settings *s)
69 s->Truncate = INT_MAX;
72 static void
73 default_case(settings *s)
75 s->Case = LOWER;
78 static reading default_order[] = { Fr, To, Tape, Comp, Clino, End };
80 static void
81 default_style(settings *s)
83 s->recorded_style = s->style = STYLE_NORMAL;
84 s->ordering = default_order;
85 s->dash_for_anon_wall_station = false;
88 static void
89 default_prefix(settings *s)
91 s->Prefix = root;
94 static void
95 init_default_translate_map(short * t)
97 int i;
98 for (i = '0'; i <= '9'; i++) t[i] |= SPECIAL_NAMES;
99 for (i = 'A'; i <= 'Z'; i++) t[i] |= SPECIAL_NAMES;
100 for (i = 'a'; i <= 'z'; i++) t[i] |= SPECIAL_NAMES;
102 t['\t'] |= SPECIAL_BLANK;
103 t[' '] |= SPECIAL_BLANK;
104 t[','] |= SPECIAL_BLANK;
105 t[';'] |= SPECIAL_COMMENT;
106 t['\032'] |= SPECIAL_EOL; /* Ctrl-Z, so olde DOS text files are handled ok */
107 t['\n'] |= SPECIAL_EOL;
108 t['\r'] |= SPECIAL_EOL;
109 t['*'] |= SPECIAL_KEYWORD;
110 t['-'] |= SPECIAL_OMIT;
111 t['\\'] |= SPECIAL_ROOT;
112 t['.'] |= SPECIAL_SEPARATOR;
113 t['_'] |= SPECIAL_NAMES;
114 t['-'] |= SPECIAL_NAMES; /* Added in 0.97 prerelease 4 */
115 t['.'] |= SPECIAL_DECIMAL;
116 t['-'] |= SPECIAL_MINUS;
117 t['+'] |= SPECIAL_PLUS;
118 #if 0 /* FIXME */
119 t['{'] |= SPECIAL_OPEN;
120 t['}'] |= SPECIAL_CLOSE;
121 #endif
124 static void
125 default_translate(settings *s)
127 if (s->next && s->next->Translate == s->Translate) {
128 /* We're currently using the same character translation map as our parent
129 * scope so allocate a new one before we modify it.
131 s->Translate = ((short*)osmalloc(ossizeof(short) * 257)) + 1;
132 } else {
133 /* SVX_ASSERT(EOF==-1);*/ /* important, since we rely on this */
135 s->Translate[EOF] = SPECIAL_EOL;
136 memset(s->Translate, 0, sizeof(short) * 256);
137 init_default_translate_map(s->Translate);
140 /* Flag anything used in SPECIAL_* cumulatively to help us pick a suitable
141 * separator to use in the .3d file. */
142 static short separator_map[256];
144 void
145 scan_compass_station_name(prefix *stn)
147 /* We only need to scan the leaf station name - any survey hierarchy above
148 * that must have been set up in .svx files for which we update
149 * separator_map via cmd_set() plus adding the defaults in
150 * find_output_separator().
152 for (const char *p = stn->ident; *p; ++p) {
153 separator_map[(unsigned char)*p] |= SPECIAL_NAMES;
157 static char
158 find_output_separator(void)
160 // Fast path to handle most common cases where we'd pick '.'.
161 if ((separator_map['.'] & SPECIAL_NAMES) == 0) {
162 return '.';
165 static bool added_defaults = false;
166 if (!added_defaults) {
167 /* Add the default settings to separator_map. */
168 init_default_translate_map(separator_map);
169 added_defaults = true;
172 /* 30 punctuation characters plus space to try arranged in a sensible order
173 * of decreasing preference (these are all the ASCII punctuation characters
174 * excluding '_' and '-' since those are allowed in names by default so are
175 * poor choices for the separator).
177 int best = -1;
178 for (const char *p = "./:;,!|\\ ~+*^='`\"#$%&?@<>()[]{}"; *p; ++p) {
179 unsigned char candidate = *p;
180 int mask = separator_map[candidate];
181 switch (mask & (SPECIAL_SEPARATOR|SPECIAL_NAMES)) {
182 case SPECIAL_SEPARATOR:
183 /* A character which is set as a separator character at some
184 * point but never set as a name character is perfect.
186 return candidate;
187 case 0:
188 /* A character which is never set as either a separator
189 * character or a name character is a reasonable option.
191 if (best < 0) best = candidate;
192 break;
195 if (best < 0) {
196 /* Argh, no plausible choice! Just return the default for now. */
197 return '.';
199 return best;
202 void
203 default_units(settings *s)
205 int quantity;
206 for (quantity = 0; quantity < Q_MAC; quantity++) {
207 if (TSTBIT(ANG_QMASK, quantity))
208 s->units[quantity] = (real)(M_PI / 180.0); /* degrees */
209 else
210 s->units[quantity] = (real)1.0; /* metres */
212 s->f_clino_percent = s->f_backclino_percent = false;
213 s->f_bearing_quadrants = s->f_backbearing_quadrants = false;
216 void
217 default_calib(settings *s)
219 int quantity;
220 for (quantity = 0; quantity < Q_MAC; quantity++) {
221 s->z[quantity] = (real)0.0;
222 s->sc[quantity] = (real)1.0;
226 static void
227 default_flags(settings *s)
229 s->flags = 0;
232 extern void
233 default_all(settings *s)
235 default_truncate(s);
236 s->infer = 0;
237 default_case(s);
238 default_style(s);
239 default_prefix(s);
240 default_translate(s);
241 default_grade(s);
242 default_units(s);
243 default_calib(s);
244 default_flags(s);
247 string token = S_INIT;
249 static string uctoken = S_INIT;
251 /* read token */
252 extern void
253 get_token(void)
255 skipblanks();
256 get_token_no_blanks();
259 extern void
260 get_token_no_blanks(void)
262 s_clear(&token);
263 s_clear(&uctoken);
264 while (isalpha(ch)) {
265 s_catchar(&token, ch);
266 s_catchar(&uctoken, toupper(ch));
267 nextch();
270 #if 0
271 printf("get_token_no_blanks() got “%s”\n", s_str(&token));
272 #endif
275 static string word = S_INIT;
277 /* read word */
278 static void
279 get_word(void)
281 s_clear(&word);
282 skipblanks();
283 while (!isBlank(ch) && !isEol(ch)) {
284 s_catchar(&word, ch);
285 nextch();
287 #if 0
288 printf("get_word() got “%s”\n", s_str(&word));
289 #endif
292 /* match_tok() now uses binary chop
293 * tab argument should be alphabetically sorted (ascending)
295 extern int
296 match_tok(const sztok *tab, int tab_size)
298 int a = 0, b = tab_size - 1, c;
299 int r;
300 const char* tok = s_str(&uctoken);
301 assert(tab_size > 0); /* catch empty table */
302 /* printf("[%d,%d]",a,b); */
303 while (a <= b) {
304 c = (unsigned)(a + b) / 2;
305 /* printf(" %d",c); */
306 r = strcmp(tab[c].sz, tok);
307 if (r == 0) return tab[c].tok; /* match */
308 if (r < 0)
309 a = c + 1;
310 else
311 b = c - 1;
313 return tab[tab_size].tok; /* no match */
316 typedef enum {
317 CMD_NULL = -1, CMD_ALIAS, CMD_BEGIN, CMD_CALIBRATE, CMD_CASE, CMD_COPYRIGHT,
318 CMD_CS, CMD_DATA, CMD_DATE, CMD_DECLINATION, CMD_DEFAULT, CMD_END,
319 CMD_ENTRANCE, CMD_EQUATE, CMD_EXPORT, CMD_FIX, CMD_FLAGS, CMD_INCLUDE,
320 CMD_INFER, CMD_INSTRUMENT, CMD_PREFIX, CMD_REF, CMD_REQUIRE, CMD_SD,
321 CMD_SET, CMD_SOLVE, CMD_TEAM, CMD_TITLE, CMD_TRUNCATE, CMD_UNITS
322 } cmds;
324 static const sztok cmd_tab[] = {
325 {"ALIAS", CMD_ALIAS},
326 {"BEGIN", CMD_BEGIN},
327 {"CALIBRATE", CMD_CALIBRATE},
328 {"CASE", CMD_CASE},
329 {"COPYRIGHT", CMD_COPYRIGHT},
330 {"CS", CMD_CS},
331 {"DATA", CMD_DATA},
332 {"DATE", CMD_DATE},
333 {"DECLINATION", CMD_DECLINATION},
334 #ifndef NO_DEPRECATED
335 {"DEFAULT", CMD_DEFAULT},
336 #endif
337 {"END", CMD_END},
338 {"ENTRANCE", CMD_ENTRANCE},
339 {"EQUATE", CMD_EQUATE},
340 {"EXPORT", CMD_EXPORT},
341 {"FIX", CMD_FIX},
342 {"FLAGS", CMD_FLAGS},
343 {"INCLUDE", CMD_INCLUDE},
344 {"INFER", CMD_INFER},
345 {"INSTRUMENT",CMD_INSTRUMENT},
346 #ifndef NO_DEPRECATED
347 {"PREFIX", CMD_PREFIX},
348 #endif
349 {"REF", CMD_REF},
350 {"REQUIRE", CMD_REQUIRE},
351 {"SD", CMD_SD},
352 {"SET", CMD_SET},
353 {"SOLVE", CMD_SOLVE},
354 {"TEAM", CMD_TEAM},
355 {"TITLE", CMD_TITLE},
356 {"TRUNCATE", CMD_TRUNCATE},
357 {"UNITS", CMD_UNITS},
358 {NULL, CMD_NULL}
361 /* masks for units which are length and angles respectively */
362 #define LEN_UMASK (BIT(UNITS_METRES) | BIT(UNITS_FEET) | BIT(UNITS_YARDS))
363 #define ANG_UMASK (BIT(UNITS_DEGS) | BIT(UNITS_GRADS) | BIT(UNITS_MINUTES))
365 /* ordering must be the same as the units enum */
366 const real factor_tab[] = {
367 1.0, METRES_PER_FOOT, (METRES_PER_FOOT*3.0),
368 (M_PI/180.0), (M_PI/180.0), (M_PI/200.0), 0.01, (M_PI/180.0/60.0)
371 const int units_to_msgno[] = {
372 /*m*/424,
373 /*ft*/428,
374 -1, /* yards */
375 /*°*/344, /* quadrants */
376 /*°*/344,
377 /*ᵍ*/345,
378 /*%*/96,
379 -1 /* minutes */
382 int get_length_units(int quantity) {
383 double factor = pcs->units[quantity];
384 if (fabs(factor - METRES_PER_FOOT) <= REAL_EPSILON ||
385 fabs(factor - METRES_PER_FOOT * 3.0) <= REAL_EPSILON) {
386 return UNITS_FEET;
388 return UNITS_METRES;
391 int get_angle_units(int quantity) {
392 double factor = pcs->units[quantity];
393 if (fabs(factor - M_PI / 200.0) <= REAL_EPSILON) {
394 return UNITS_GRADS;
396 return UNITS_DEGS;
399 static int
400 get_units(unsigned long qmask, bool percent_ok)
402 static const sztok utab[] = {
403 {"DEGREES", UNITS_DEGS },
404 {"DEGS", UNITS_DEGS },
405 {"FEET", UNITS_FEET },
406 {"GRADS", UNITS_GRADS },
407 {"METERS", UNITS_METRES },
408 {"METRES", UNITS_METRES },
409 {"METRIC", UNITS_METRES },
410 {"MILS", UNITS_DEPRECATED_ALIAS_FOR_GRADS },
411 {"MINUTES", UNITS_MINUTES },
412 {"PERCENT", UNITS_PERCENT },
413 {"PERCENTAGE", UNITS_PERCENT },
414 {"QUADRANTS", UNITS_QUADRANTS },
415 {"QUADS", UNITS_QUADRANTS },
416 {"YARDS", UNITS_YARDS },
417 {NULL, UNITS_NULL }
419 int units;
420 get_token();
421 units = match_tok(utab, TABSIZE(utab));
422 if (units == UNITS_NULL) {
423 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown units “%s”*/35,
424 s_str(&token));
425 return UNITS_NULL;
427 /* Survex has long misdefined "mils" as an alias for "grads", of which
428 * there are 400 in a circle. There are several definitions of "mils"
429 * with a circle containing 2000π SI milliradians, 6400 NATO mils, 6000
430 * Warsaw Pact mils, and 6300 Swedish streck, and they aren't in common
431 * use by cave surveyors, so we now just warn if mils are used.
433 if (units == UNITS_DEPRECATED_ALIAS_FOR_GRADS) {
434 compile_diagnostic(DIAG_WARN|DIAG_TOKEN|DIAG_SKIP,
435 /*Units “%s” are deprecated, assuming “grads” - see manual for details*/479,
436 s_str(&token));
437 units = UNITS_GRADS;
439 if (units == UNITS_PERCENT && percent_ok &&
440 !(qmask & ~(BIT(Q_GRADIENT)|BIT(Q_BACKGRADIENT)))) {
441 return units;
443 if (units == UNITS_QUADRANTS &&
444 !(qmask & ~(BIT(Q_BEARING)|BIT(Q_BACKBEARING)))) {
445 return units;
447 if (((qmask & LEN_QMASK) && !TSTBIT(LEN_UMASK, units)) ||
448 ((qmask & ANG_QMASK) && !TSTBIT(ANG_UMASK, units))) {
449 /* TRANSLATORS: Note: In English you talk about the *units* of a single
450 * measurement, but the correct term in other languages may be singular.
452 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Invalid units “%s” for quantity*/37, s_str(&token));
453 return UNITS_NULL;
455 return units;
458 /* returns mask with bit x set to indicate quantity x specified */
459 static unsigned long
460 get_qlist(unsigned long mask_bad)
462 static const sztok qtab[] = {
463 {"ALTITUDE", Q_DZ },
464 {"BACKBEARING", Q_BACKBEARING },
465 {"BACKCLINO", Q_BACKGRADIENT }, /* alternative name */
466 {"BACKCOMPASS", Q_BACKBEARING }, /* alternative name */
467 {"BACKGRADIENT", Q_BACKGRADIENT },
468 {"BACKLENGTH", Q_BACKLENGTH },
469 {"BACKTAPE", Q_BACKLENGTH }, /* alternative name */
470 {"BEARING", Q_BEARING },
471 {"CEILING", Q_UP }, /* alternative name */
472 {"CLINO", Q_GRADIENT }, /* alternative name */
473 {"COMPASS", Q_BEARING }, /* alternative name */
474 {"COUNT", Q_COUNT },
475 {"COUNTER", Q_COUNT }, /* alternative name */
476 {"DECLINATION", Q_DECLINATION },
477 {"DEFAULT", Q_DEFAULT }, /* not a real quantity... */
478 {"DEPTH", Q_DEPTH },
479 {"DOWN", Q_DOWN },
480 {"DX", Q_DX }, /* alternative name */
481 {"DY", Q_DY }, /* alternative name */
482 {"DZ", Q_DZ }, /* alternative name */
483 {"EASTING", Q_DX },
484 {"FLOOR", Q_DOWN }, /* alternative name */
485 {"GRADIENT", Q_GRADIENT },
486 {"LEFT", Q_LEFT },
487 {"LENGTH", Q_LENGTH },
488 {"LEVEL", Q_LEVEL},
489 {"NORTHING", Q_DY },
490 {"PLUMB", Q_PLUMB},
491 {"POSITION", Q_POS },
492 {"RIGHT", Q_RIGHT },
493 {"TAPE", Q_LENGTH }, /* alternative name */
494 {"UP", Q_UP },
495 {NULL, Q_NULL }
497 unsigned long qmask = 0;
498 int tok;
499 filepos fp;
501 while (1) {
502 get_pos(&fp);
503 get_token();
504 tok = match_tok(qtab, TABSIZE(qtab));
505 if (tok == Q_DEFAULT && !(mask_bad & BIT(Q_DEFAULT))) {
506 /* Only recognise DEFAULT if it is the first quantity, and then don't
507 * look for any more. */
508 if (qmask == 0)
509 return BIT(Q_DEFAULT);
510 break;
512 /* bail out if we reach the table end with no match */
513 if (tok == Q_NULL) break;
514 qmask |= BIT(tok);
515 if (qmask & mask_bad) {
516 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown instrument “%s”*/39, s_str(&token));
517 return 0;
521 if (qmask == 0) {
522 /* TRANSLATORS: A "quantity" is something measured like "LENGTH",
523 * "BEARING", "ALTITUDE", etc. */
524 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown quantity “%s”*/34, s_str(&token));
525 } else {
526 set_pos(&fp);
529 return qmask;
532 #define SPECIAL_UNKNOWN 0
533 static void
534 cmd_set(void)
536 static const sztok chartab[] = {
537 {"BLANK", SPECIAL_BLANK },
538 /*FIXME {"CLOSE", SPECIAL_CLOSE }, */
539 {"COMMENT", SPECIAL_COMMENT },
540 {"DECIMAL", SPECIAL_DECIMAL },
541 {"EOL", SPECIAL_EOL }, /* EOL won't work well */
542 {"KEYWORD", SPECIAL_KEYWORD },
543 {"MINUS", SPECIAL_MINUS },
544 {"NAMES", SPECIAL_NAMES },
545 {"OMIT", SPECIAL_OMIT },
546 /*FIXME {"OPEN", SPECIAL_OPEN }, */
547 {"PLUS", SPECIAL_PLUS },
548 #ifndef NO_DEPRECATED
549 {"ROOT", SPECIAL_ROOT },
550 #endif
551 {"SEPARATOR", SPECIAL_SEPARATOR },
552 {NULL, SPECIAL_UNKNOWN }
554 int mask;
555 int i;
557 get_token();
558 mask = match_tok(chartab, TABSIZE(chartab));
560 if (mask == SPECIAL_UNKNOWN) {
561 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown character class “%s”*/42,
562 s_str(&token));
563 return;
566 #ifndef NO_DEPRECATED
567 if (mask == SPECIAL_ROOT) {
568 if (root_depr_count < 5) {
569 /* TRANSLATORS: Use of the ROOT character (which is "\" by default) is
570 * deprecated, so this error would be generated by:
572 * *equate \foo.7 1
574 * If you're unsure what "deprecated" means, see:
575 * https://en.wikipedia.org/wiki/Deprecation */
576 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*ROOT is deprecated*/25);
577 if (++root_depr_count == 5)
578 /* TRANSLATORS: If you're unsure what "deprecated" means, see:
579 * https://en.wikipedia.org/wiki/Deprecation */
580 compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
583 #endif
585 /* if we're currently using an inherited translation table, allocate a new
586 * table, and copy old one into it */
587 if (pcs->next && pcs->next->Translate == pcs->Translate) {
588 short *p;
589 p = ((short*)osmalloc(ossizeof(short) * 257)) + 1;
590 memcpy(p - 1, pcs->Translate - 1, sizeof(short) * 257);
591 pcs->Translate = p;
594 skipblanks();
596 /* clear this flag for all non-alphanums */
597 for (i = 0; i < 256; i++)
598 if (!isalnum(i)) pcs->Translate[i] &= ~mask;
600 /* now set this flag for all specified chars */
601 while (!isEol(ch)) {
602 int char_to_set;
603 if (!isalnum(ch)) {
604 char_to_set = ch;
605 } else if (tolower(ch) == 'x') {
606 int hex;
607 filepos fp;
608 get_pos(&fp);
609 nextch();
610 if (!isxdigit(ch)) {
611 set_pos(&fp);
612 break;
614 hex = isdigit(ch) ? ch - '0' : tolower(ch) - 'a';
615 nextch();
616 if (!isxdigit(ch)) {
617 set_pos(&fp);
618 break;
620 hex = hex << 4 | (isdigit(ch) ? ch - '0' : tolower(ch) - 'a');
621 char_to_set = hex;
622 } else {
623 break;
625 pcs->Translate[char_to_set] |= mask;
626 separator_map[char_to_set] |= mask;
627 nextch();
630 output_separator = find_output_separator();
633 void
634 update_output_separator(void)
636 output_separator = find_output_separator();
639 static void
640 check_reentry(prefix *survey, const filepos* fpos_ptr)
642 /* Don't try to check "*prefix \" or "*begin \" */
643 if (!survey->up) return;
644 if (TSTBIT(survey->sflags, SFLAGS_PREFIX_ENTERED)) {
645 static int reenter_depr_count = 0;
646 filepos fp_tmp;
648 if (reenter_depr_count >= 5)
649 return;
651 get_pos(&fp_tmp);
652 set_pos(fpos_ptr);
653 /* TRANSLATORS: The first of two warnings given when a survey which has
654 * already been completed is reentered. This example file crawl.svx:
656 * *begin crawl ; <- second warning here
657 * 1 2 9.45 234 -01
658 * *end crawl
659 * *begin crawl ; <- first warning here
660 * 2 3 7.67 223 -03
661 * *end crawl
663 * Would lead to:
665 * crawl.svx:4:8: Reentering an existing survey is deprecated
666 * crawl.svx:1: Originally entered here
668 * If you're unsure what "deprecated" means, see:
669 * https://en.wikipedia.org/wiki/Deprecation */
670 compile_diagnostic(DIAG_WARN|DIAG_WORD, /*Reentering an existing survey is deprecated*/29);
671 set_pos(&fp_tmp);
672 /* TRANSLATORS: The second of two warnings given when a survey which has
673 * already been completed is reentered. This example file crawl.svx:
675 * *begin crawl ; <- second warning here
676 * 1 2 9.45 234 -01
677 * *end crawl
678 * *begin crawl ; <- first warning here
679 * 2 3 7.67 223 -03
680 * *end crawl
682 * Would lead to:
684 * crawl.svx:4:8: Reentering an existing survey is deprecated
685 * crawl.svx:1: Originally entered here
687 * If you're unsure what "deprecated" means, see:
688 * https://en.wikipedia.org/wiki/Deprecation */
689 compile_diagnostic_pfx(DIAG_WARN, survey, /*Originally entered here*/30);
690 if (++reenter_depr_count == 5) {
691 /* After we've warned about 5 uses of the same deprecated feature, we
692 * give up for the rest of the current processing run.
694 * If you're unsure what "deprecated" means, see:
695 * https://en.wikipedia.org/wiki/Deprecation */
696 compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
698 } else {
699 survey->sflags |= BIT(SFLAGS_PREFIX_ENTERED);
700 survey->filename = file.filename;
701 survey->line = file.line;
705 #ifndef NO_DEPRECATED
706 static void
707 cmd_prefix(void)
709 static int prefix_depr_count = 0;
710 prefix *survey;
711 filepos fp;
712 /* Issue warning first, so "*prefix \" warns first that *prefix is
713 * deprecated and then that ROOT is...
715 if (prefix_depr_count < 5) {
716 /* TRANSLATORS: If you're unsure what "deprecated" means, see:
717 * https://en.wikipedia.org/wiki/Deprecation */
718 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /**prefix is deprecated - use *begin and *end instead*/6);
719 if (++prefix_depr_count == 5)
720 compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
722 get_pos(&fp);
723 survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT);
724 pcs->Prefix = survey;
725 check_reentry(survey, &fp);
727 #endif
729 static void
730 cmd_alias(void)
732 /* Currently only two forms are supported:
733 * *alias station - ..
734 * *alias station -
736 get_token();
737 if (!S_EQ(&uctoken, "STATION")) {
738 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_TOKEN, /*Bad *alias command*/397);
739 return;
741 filepos fp;
742 get_pos(&fp);
743 get_word();
744 if (!S_EQ(&word, "-"))
745 goto bad_word;
746 get_pos(&fp);
747 get_word();
748 if (!s_empty(&word) && !S_EQ(&word, ".."))
749 goto bad_word;
750 pcs->dash_for_anon_wall_station = !s_empty(&word);
751 return;
752 bad_word:
753 set_pos(&fp);
754 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_WORD, /*Bad *alias command*/397);
757 static void
758 cmd_begin(void)
760 settings *pcsNew;
762 pcsNew = osnew(settings);
763 *pcsNew = *pcs; /* copy contents */
764 pcsNew->begin_lineno = file.line;
765 pcsNew->next = pcs;
766 pcs = pcsNew;
768 skipblanks();
769 pcs->begin_survey = NULL;
770 if (!isEol(ch) && !isComm(ch)) {
771 filepos fp;
772 prefix *survey;
773 get_pos(&fp);
774 survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT|PFX_WARN_SEPARATOR);
775 pcs->begin_survey = survey;
776 pcs->Prefix = survey;
777 check_reentry(survey, &fp);
778 f_export_ok = true;
782 void
783 invalidate_pj_cached(void)
785 /* Invalidate the cached PJ. */
786 if (pj_cached) {
787 proj_destroy(pj_cached);
788 pj_cached = NULL;
792 void
793 report_declination(settings *p)
795 if (p->min_declination <= p->max_declination) {
796 int y, m, d;
797 char range[128];
798 const char* deg_sign = msg(/*°*/344);
799 ymd_from_days_since_1900(p->min_declination_days, &y, &m, &d);
800 snprintf(range, sizeof(range),
801 "%.1f%s @ %04d-%02d-%02d",
802 deg(p->min_declination), deg_sign, y, m, d);
803 if (p->min_declination_days != p->max_declination_days) {
804 size_t len = strlen(range);
805 ymd_from_days_since_1900(p->max_declination_days, &y, &m, &d);
806 snprintf(range + len, sizeof(range) - len,
807 " / %.1f%s @ %04d-%02d-%02d",
808 deg(p->max_declination), deg_sign, y, m, d);
810 /* TRANSLATORS: This message gives information about the range of
811 * declination values and the grid convergence value calculated for
812 * each "*declination auto ..." command.
814 * The first %s will be replaced by the declination range (or single
815 * value), and %.1f%s by the grid convergence angle.
817 compile_diagnostic_at(DIAG_INFO|DIAG_COL, p->dec_filename, p->dec_line,
818 /*Declination: %s, grid convergence: %.1f%s*/484,
819 range,
820 deg(p->convergence), deg_sign);
821 PUTC(' ', STDERR);
822 fputs(p->dec_context, STDERR);
823 fputnl(STDERR);
824 free(p->dec_context);
825 p->dec_context = NULL;
829 void
830 set_declination_location(real x, real y, real z, const char *proj_str)
832 /* Convert to WGS84 lat long. */
833 PJ *transform = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
834 proj_str,
835 WGS84_DATUM_STRING,
836 NULL);
837 if (transform) {
838 /* Normalise the output order so x is longitude and y latitude - by
839 * default new PROJ has them switched for EPSG:4326 which just seems
840 * confusing.
842 PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX,
843 transform);
844 proj_destroy(transform);
845 transform = pj_norm;
848 if (proj_angular_input(transform, PJ_FWD)) {
849 /* Input coordinate system expects radians. */
850 x = rad(x);
851 y = rad(y);
854 PJ_COORD coord = {{x, y, z, HUGE_VAL}};
855 coord = proj_trans(transform, PJ_FWD, coord);
856 x = coord.xyzt.x;
857 y = coord.xyzt.y;
858 z = coord.xyzt.z;
860 if (x == HUGE_VAL || y == HUGE_VAL || z == HUGE_VAL) {
861 compile_diagnostic(DIAG_ERR, /*Failed to convert coordinates: %s*/436,
862 proj_errno_string(proj_errno(transform)));
863 /* Set dummy values which are finite. */
864 x = y = z = 0;
866 proj_destroy(transform);
868 report_declination(pcs);
870 double lon = rad(x);
871 double lat = rad(y);
872 pcs->z[Q_DECLINATION] = HUGE_REAL;
873 pcs->dec_lat = lat;
874 pcs->dec_lon = lon;
875 pcs->dec_alt = z;
876 pcs->dec_filename = file.filename;
877 pcs->dec_line = file.line;
878 pcs->dec_context = grab_line();
879 /* Invalidate cached declination. */
880 pcs->declination = HUGE_REAL;
881 /* Invalidate cached grid convergence. */
882 pcs->convergence = HUGE_REAL;
885 void
886 pop_settings(void)
888 settings * p = pcs;
889 pcs = pcs->next;
891 SVX_ASSERT(pcs);
893 if (pcs->dec_lat != p->dec_lat ||
894 pcs->dec_lon != p->dec_lon ||
895 pcs->dec_alt != p->dec_alt) {
896 report_declination(p);
897 } else {
898 pcs->min_declination_days = p->min_declination_days;
899 pcs->max_declination_days = p->max_declination_days;
900 pcs->min_declination = p->min_declination;
901 pcs->max_declination = p->max_declination;
902 pcs->convergence = p->convergence;
905 if (p->proj_str != pcs->proj_str) {
906 if (!p->proj_str || !pcs->proj_str ||
907 strcmp(p->proj_str, pcs->proj_str) != 0) {
908 invalidate_pj_cached();
910 /* free proj_str if not used by parent */
911 osfree(p->proj_str);
914 /* don't free default ordering or ordering used by parent */
915 if (p->ordering != default_order && p->ordering != pcs->ordering)
916 osfree((reading*)p->ordering);
918 /* free Translate if not used by parent */
919 if (p->Translate != pcs->Translate)
920 osfree(p->Translate - 1);
922 /* free meta if not used by parent, or in this block */
923 if (p->meta && p->meta != pcs->meta && p->meta->ref_count == 0)
924 osfree(p->meta);
926 osfree(p);
929 static void
930 cmd_end(void)
932 prefix *survey, *begin_survey;
933 filepos fp;
935 if (pcs->begin_lineno == 0) {
936 if (pcs->next == NULL) {
937 /* more ENDs than BEGINs */
938 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*No matching BEGIN*/192);
939 } else {
940 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*END with no matching BEGIN in this file*/22);
942 return;
945 begin_survey = pcs->begin_survey;
947 pop_settings();
949 /* note need to read using root *before* BEGIN */
950 skipblanks();
951 if (isEol(ch) || isComm(ch)) {
952 survey = NULL;
953 } else {
954 get_pos(&fp);
955 survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT);
958 if (survey != begin_survey) {
959 if (survey) {
960 set_pos(&fp);
961 if (!begin_survey) {
962 /* TRANSLATORS: Used when a BEGIN command has no survey, but the
963 * END command does, e.g.:
965 * *begin
966 * 1 2 10.00 178 -01
967 * *end entrance <--[Message given here] */
968 compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Matching BEGIN command has no survey name*/36);
969 } else {
970 /* TRANSLATORS: *BEGIN <survey> and *END <survey> should have the
971 * same <survey> if it’s given at all */
972 compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Survey name doesn’t match BEGIN*/193);
974 skipline();
975 } else {
976 /* TRANSLATORS: Used when a BEGIN command has a survey name, but the
977 * END command omits it, e.g.:
979 * *begin entrance
980 * 1 2 10.00 178 -01
981 * *end <--[Message given here] */
982 compile_diagnostic(DIAG_WARN|DIAG_COL, /*Survey name omitted from END*/194);
987 static void
988 cmd_entrance(void)
990 prefix *pfx = read_prefix(PFX_STATION);
991 pfx->sflags |= BIT(SFLAGS_ENTRANCE);
994 static const prefix * first_fix_name = NULL;
995 static const char * first_fix_filename;
996 static unsigned first_fix_line;
998 static void
999 cmd_fix(void)
1001 prefix *fix_name;
1002 node *stn = NULL;
1003 static prefix *name_omit_already = NULL;
1004 static const char * name_omit_already_filename = NULL;
1005 static unsigned int name_omit_already_line;
1006 real x, y, z;
1007 filepos fp_stn, fp;
1009 get_pos(&fp_stn);
1010 fix_name = read_prefix(PFX_STATION|PFX_ALLOW_ROOT);
1011 fix_name->sflags |= BIT(SFLAGS_FIXED);
1013 get_pos(&fp);
1014 get_token();
1015 if (S_EQ(&uctoken, "REFERENCE")) {
1016 /* suppress "unused fixed point" warnings for this station */
1017 fix_name->sflags |= BIT(SFLAGS_USED);
1018 } else {
1019 if (!s_empty(&uctoken)) set_pos(&fp);
1022 x = read_numeric(true);
1023 if (x == HUGE_REAL) {
1024 /* If the end of the line isn't blank, read a number after all to
1025 * get a more helpful error message */
1026 if (!isEol(ch) && !isComm(ch)) x = read_numeric(false);
1028 if (x == HUGE_REAL) {
1029 if (pcs->proj_str || proj_str_out) {
1030 compile_diagnostic(DIAG_ERR|DIAG_COL|DIAG_SKIP, /*Coordinates can't be omitted when coordinate system has been specified*/439);
1031 return;
1034 if (fix_name == name_omit_already) {
1035 compile_diagnostic(DIAG_WARN|DIAG_COL, /*Same station fixed twice with no coordinates*/61);
1036 return;
1039 if (name_omit_already) {
1040 /* TRANSLATORS: Emitted after second and subsequent "FIX" command
1041 * with no coordinates.
1043 compile_diagnostic_at(DIAG_ERR|DIAG_COL,
1044 name_omit_already_filename,
1045 name_omit_already_line,
1046 /*Already had FIX command with no coordinates for station “%s”*/441,
1047 sprint_prefix(name_omit_already));
1048 } else {
1049 /* TRANSLATORS: " *fix a " gives this message: */
1050 compile_diagnostic(DIAG_INFO|DIAG_COL, /*FIX command with no coordinates - fixing at (0,0,0)*/54);
1052 name_omit_already = fix_name;
1053 name_omit_already_filename = file.filename;
1054 name_omit_already_line = file.line;
1057 x = y = z = (real)0.0;
1058 } else {
1059 real sdx;
1060 y = read_numeric(false);
1061 z = read_numeric(false);
1063 if (pcs->proj_str && proj_str_out) {
1064 PJ *transform = pj_cached;
1065 if (!transform) {
1066 transform = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
1067 pcs->proj_str,
1068 proj_str_out,
1069 NULL);
1070 if (transform) {
1071 /* Normalise the output order so x is longitude and y latitude - by
1072 * default new PROJ has them switched for EPSG:4326 which just seems
1073 * confusing.
1075 PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX,
1076 transform);
1077 proj_destroy(transform);
1078 transform = pj_norm;
1081 pj_cached = transform;
1084 if (proj_angular_input(transform, PJ_FWD)) {
1085 /* Input coordinate system expects radians. */
1086 x = rad(x);
1087 y = rad(y);
1090 PJ_COORD coord = {{x, y, z, HUGE_VAL}};
1091 coord = proj_trans(transform, PJ_FWD, coord);
1092 x = coord.xyzt.x;
1093 y = coord.xyzt.y;
1094 z = coord.xyzt.z;
1096 if (x == HUGE_VAL || y == HUGE_VAL || z == HUGE_VAL) {
1097 compile_diagnostic(DIAG_ERR, /*Failed to convert coordinates: %s*/436,
1098 proj_errno_string(proj_errno(transform)));
1099 /* Set dummy values which are finite. */
1100 x = y = z = 0;
1102 } else if (pcs->proj_str) {
1103 compile_diagnostic(DIAG_ERR, /*The input projection is set but the output projection isn't*/437);
1104 } else if (proj_str_out) {
1105 compile_diagnostic(DIAG_ERR, /*The output projection is set but the input projection isn't*/438);
1108 get_pos(&fp);
1109 sdx = read_numeric(true);
1110 if (sdx <= 0) {
1111 set_pos(&fp);
1112 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
1113 return;
1115 if (sdx != HUGE_REAL) {
1116 real sdy, sdz;
1117 real cxy = 0, cyz = 0, czx = 0;
1118 get_pos(&fp);
1119 sdy = read_numeric(true);
1120 if (sdy == HUGE_REAL) {
1121 /* only one variance given */
1122 sdy = sdz = sdx;
1123 } else {
1124 if (sdy <= 0) {
1125 set_pos(&fp);
1126 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
1127 return;
1129 get_pos(&fp);
1130 sdz = read_numeric(true);
1131 if (sdz == HUGE_REAL) {
1132 /* two variances given - horizontal & vertical */
1133 sdz = sdy;
1134 sdy = sdx;
1135 } else {
1136 if (sdz <= 0) {
1137 set_pos(&fp);
1138 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
1139 return;
1141 cxy = read_numeric(true);
1142 if (cxy != HUGE_REAL) {
1143 /* covariances given */
1144 cyz = read_numeric(false);
1145 czx = read_numeric(false);
1146 } else {
1147 cxy = 0;
1151 stn = StnFromPfx(fix_name);
1152 if (!fixed(stn)) {
1153 node *fixpt = osnew(node);
1154 prefix *name;
1155 name = osnew(prefix);
1156 name->pos = osnew(pos);
1157 name->ident = NULL;
1158 name->shape = 0;
1159 fixpt->name = name;
1160 name->stn = fixpt;
1161 name->up = NULL;
1162 if (TSTBIT(pcs->infer, INFER_EXPORTS)) {
1163 name->min_export = USHRT_MAX;
1164 } else {
1165 name->min_export = 0;
1167 name->max_export = 0;
1168 name->sflags = 0;
1169 add_stn_to_list(&stnlist, fixpt);
1170 POS(fixpt, 0) = x;
1171 POS(fixpt, 1) = y;
1172 POS(fixpt, 2) = z;
1173 fix(fixpt);
1174 fixpt->leg[0] = fixpt->leg[1] = fixpt->leg[2] = NULL;
1175 addfakeleg(fixpt, stn, 0, 0, 0,
1176 sdx * sdx, sdy * sdy, sdz * sdz
1177 #ifndef NO_COVARIANCES
1178 , cxy, cyz, czx
1179 #endif
1183 if (!first_fix_name) {
1184 /* We track if we've fixed a station yet, and if so what the name
1185 * of the first fix was, so that we can issue an error if the
1186 * output coordinate system is set after fixing a station. */
1187 first_fix_name = fix_name;
1188 first_fix_filename = file.filename;
1189 first_fix_line = file.line;
1192 return;
1196 if (!first_fix_name) {
1197 /* We track if we've fixed a station yet, and if so what the name of the
1198 * first fix was, so that we can issue an error if the output coordinate
1199 * system is set after fixing a station. */
1200 first_fix_name = fix_name;
1201 first_fix_filename = file.filename;
1202 first_fix_line = file.line;
1205 stn = StnFromPfx(fix_name);
1206 if (!fixed(stn)) {
1207 POS(stn, 0) = x;
1208 POS(stn, 1) = y;
1209 POS(stn, 2) = z;
1210 fix(stn);
1211 return;
1214 get_pos(&fp);
1215 set_pos(&fp_stn);
1216 if (x != POS(stn, 0) || y != POS(stn, 1) || z != POS(stn, 2)) {
1217 compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Station already fixed or equated to a fixed point*/46);
1218 } else {
1219 /* TRANSLATORS: *fix a 1 2 3 / *fix a 1 2 3 */
1220 compile_diagnostic(DIAG_WARN|DIAG_WORD, /*Station already fixed at the same coordinates*/55);
1222 set_pos(&fp);
1225 static void
1226 cmd_flags(void)
1228 static const sztok flagtab[] = {
1229 {"DUPLICATE", FLAGS_DUPLICATE },
1230 {"NOT", FLAGS_NOT },
1231 {"SPLAY", FLAGS_SPLAY },
1232 {"SURFACE", FLAGS_SURFACE },
1233 {NULL, FLAGS_UNKNOWN }
1235 bool fNot = false;
1236 bool fEmpty = true;
1237 while (1) {
1238 int flag;
1239 get_token();
1240 /* If token is empty, it could mean end of line, or maybe
1241 * some non-letter junk which is better reported later */
1242 if (s_empty(&token)) break;
1244 fEmpty = false;
1245 flag = match_tok(flagtab, TABSIZE(flagtab));
1246 /* treat the second NOT in "NOT NOT" as an unknown flag */
1247 if (flag == FLAGS_UNKNOWN || (fNot && flag == FLAGS_NOT)) {
1248 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*FLAG “%s” unknown*/68,
1249 s_str(&token));
1250 /* Recover from “*FLAGS NOT BOGUS SURFACE” by ignoring "NOT BOGUS" */
1251 fNot = false;
1252 } else if (flag == FLAGS_NOT) {
1253 fNot = true;
1254 } else if (fNot) {
1255 pcs->flags &= ~BIT(flag);
1256 fNot = false;
1257 } else {
1258 pcs->flags |= BIT(flag);
1262 if (fNot) {
1263 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Expecting “DUPLICATE”, “SPLAY”, or “SURFACE”*/188);
1264 } else if (fEmpty) {
1265 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Expecting “NOT”, “DUPLICATE”, “SPLAY”, or “SURFACE”*/189);
1269 static void
1270 cmd_equate(void)
1272 prefix *name1, *name2;
1273 bool fOnlyOneStn = true; /* to trap eg *equate entrance.6 */
1274 filepos fp;
1276 get_pos(&fp);
1277 name1 = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_SUSPECT_TYPO);
1278 while (true) {
1279 name2 = name1;
1280 skipblanks();
1281 if (isEol(ch) || isComm(ch)) {
1282 if (fOnlyOneStn) {
1283 set_pos(&fp);
1284 /* TRANSLATORS: EQUATE is a command name, so shouldn’t be
1285 * translated.
1287 * Here "station" is a survey station, not a train station.
1289 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_WORD, /*Only one station in EQUATE command*/33);
1291 return;
1294 name1 = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_SUSPECT_TYPO);
1295 process_equate(name1, name2);
1296 fOnlyOneStn = false;
1300 static void
1301 report_missing_export(prefix *pfx, int depth)
1303 char *s;
1304 const char *p;
1305 prefix *survey = pfx;
1306 int i;
1307 for (i = depth + 1; i; i--) {
1308 survey = survey->up;
1309 SVX_ASSERT(survey);
1311 s = osstrdup(sprint_prefix(survey));
1312 p = sprint_prefix(pfx);
1313 if (survey->filename) {
1314 /* TRANSLATORS: A station must be exported out of each level it is in, so
1315 * this would give "Station “\outer.inner.1” not exported from survey
1316 * “\outer”)":
1318 * *equate entrance outer.inner.1
1319 * *begin outer
1320 * *begin inner
1321 * *export 1
1322 * 1 2 1.23 045 -6
1323 * *end inner
1324 * *end outer
1326 * Here "survey" is a "cave map" rather than list of questions - it should be
1327 * translated to the terminology that cavers using the language would use.
1329 compile_diagnostic_pfx(DIAG_ERR, survey,
1330 /*Station “%s” not exported from survey “%s”*/26, p, s);
1331 } else {
1332 compile_diagnostic(DIAG_ERR, /*Station “%s” not exported from survey “%s”*/26, p, s);
1334 osfree(s);
1337 static void
1338 cmd_export(void)
1340 prefix *pfx;
1342 fExportUsed = true;
1343 do {
1344 int depth = 0;
1345 pfx = read_prefix(PFX_STATION|PFX_NEW);
1346 if (pfx == NULL) {
1347 /* The argument was an existing station. */
1348 /* FIXME */
1349 } else {
1350 prefix *p = pfx;
1351 while (p != NULL && p != pcs->Prefix) {
1352 depth++;
1353 p = p->up;
1355 /* Something like: *export \foo, but we've excluded use of root */
1356 SVX_ASSERT(p);
1358 /* *export \ or similar bogus stuff */
1359 SVX_ASSERT(depth);
1360 #if 0
1361 printf("C min %d max %d depth %d pfx %s\n",
1362 pfx->min_export, pfx->max_export, depth, sprint_prefix(pfx));
1363 #endif
1364 if (pfx->min_export == 0) {
1365 /* not encountered *export for this name before */
1366 if (pfx->max_export > depth) report_missing_export(pfx, depth);
1367 pfx->min_export = pfx->max_export = depth;
1368 } else if (pfx->min_export != USHRT_MAX) {
1369 /* FIXME: what to do if a station is marked for inferred exports
1370 * but is then explicitly exported? Currently we just ignore the
1371 * explicit export... */
1372 if (pfx->min_export - 1 > depth) {
1373 report_missing_export(pfx, depth);
1374 } else if (pfx->min_export - 1 < depth) {
1375 /* TRANSLATORS: Here "station" is a survey station, not a train station.
1377 * Exporting a station twice gives this error:
1379 * *begin example
1380 * *export 1
1381 * *export 1
1382 * 1 2 1.24 045 -6
1383 * *end example */
1384 compile_diagnostic(DIAG_ERR, /*Station “%s” already exported*/66,
1385 sprint_prefix(pfx));
1387 pfx->min_export = depth;
1389 skipblanks();
1390 } while (!isEol(ch) && !isComm(ch));
1393 static void
1394 cmd_data(void)
1396 static const sztok dtab[] = {
1397 {"ALTITUDE", Dz },
1398 {"BACKBEARING", BackComp },
1399 {"BACKCLINO", BackClino }, /* alternative name */
1400 {"BACKCOMPASS", BackComp }, /* alternative name */
1401 {"BACKGRADIENT", BackClino },
1402 {"BACKLENGTH", BackTape },
1403 {"BACKTAPE", BackTape }, /* alternative name */
1404 {"BEARING", Comp },
1405 {"CEILING", Up }, /* alternative name */
1406 {"CLINO", Clino }, /* alternative name */
1407 {"COMPASS", Comp }, /* alternative name */
1408 {"COUNT", Count }, /* FrCount&ToCount in multiline */
1409 {"DEPTH", Depth }, /* FrDepth&ToDepth in multiline */
1410 {"DEPTHCHANGE", DepthChange },
1411 {"DIRECTION", Dir },
1412 {"DOWN", Down },
1413 {"DX", Dx },
1414 {"DY", Dy },
1415 {"DZ", Dz },
1416 {"EASTING", Dx },
1417 {"FLOOR", Down }, /* alternative name */
1418 {"FROM", Fr },
1419 {"FROMCOUNT", FrCount },
1420 {"FROMDEPTH", FrDepth },
1421 {"GRADIENT", Clino },
1422 {"IGNORE", Ignore },
1423 {"IGNOREALL", IgnoreAll },
1424 {"LEFT", Left },
1425 {"LENGTH", Tape },
1426 {"NEWLINE", Newline },
1427 {"NORTHING", Dy },
1428 {"RIGHT", Right },
1429 {"STATION", Station }, /* Fr&To in multiline */
1430 {"TAPE", Tape }, /* alternative name */
1431 {"TO", To },
1432 {"TOCOUNT", ToCount },
1433 {"TODEPTH", ToDepth },
1434 {"UP", Up },
1435 {NULL, End }
1438 #define MASK_stns BIT(Fr) | BIT(To) | BIT(Station)
1439 #define MASK_tape BIT(Tape) | BIT(BackTape) | BIT(FrCount) | BIT(ToCount) | BIT(Count)
1440 #define MASK_dpth BIT(FrDepth) | BIT(ToDepth) | BIT(Depth) | BIT(DepthChange)
1441 #define MASK_comp BIT(Comp) | BIT(BackComp)
1442 #define MASK_clin BIT(Clino) | BIT(BackClino)
1444 #define MASK_NORMAL MASK_stns | BIT(Dir) | MASK_tape | MASK_comp | MASK_clin
1445 #define MASK_DIVING MASK_NORMAL | MASK_dpth
1446 #define MASK_CARTESIAN MASK_stns | BIT(Dx) | BIT(Dy) | BIT(Dz)
1447 #define MASK_CYLPOLAR MASK_stns | BIT(Dir) | MASK_tape | MASK_comp | MASK_dpth
1448 #define MASK_NOSURVEY MASK_stns
1449 #define MASK_PASSAGE BIT(Station) | BIT(Left) | BIT(Right) | BIT(Up) | BIT(Down)
1451 /* readings which may be given for each style */
1452 static const unsigned long mask[] = {
1453 MASK_NORMAL, MASK_DIVING, MASK_CARTESIAN, MASK_CYLPOLAR, MASK_NOSURVEY,
1454 MASK_PASSAGE
1457 /* readings which may be omitted for each style */
1458 static const unsigned long mask_optional[] = {
1459 BIT(Dir) | BIT(Clino) | BIT(BackClino),
1460 BIT(Dir) | BIT(Clino) | BIT(BackClino),
1462 BIT(Dir),
1464 0 /* BIT(Left) | BIT(Right) | BIT(Up) | BIT(Down), */
1467 /* all valid readings */
1468 static const unsigned long mask_all[] = {
1469 MASK_NORMAL | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1470 MASK_DIVING | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1471 MASK_CARTESIAN | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1472 MASK_CYLPOLAR | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1473 MASK_NOSURVEY | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1474 MASK_PASSAGE | BIT(Ignore) | BIT(IgnoreAll) | BIT(End)
1476 #define STYLE_DEFAULT -2
1477 #define STYLE_UNKNOWN -1
1479 static const sztok styletab[] = {
1480 {"CARTESIAN", STYLE_CARTESIAN },
1481 {"CYLPOLAR", STYLE_CYLPOLAR },
1482 {"DEFAULT", STYLE_DEFAULT },
1483 {"DIVING", STYLE_DIVING },
1484 {"NORMAL", STYLE_NORMAL },
1485 {"NOSURVEY", STYLE_NOSURVEY },
1486 {"PASSAGE", STYLE_PASSAGE },
1487 {"TOPOFIL", STYLE_NORMAL },
1488 {NULL, STYLE_UNKNOWN }
1491 #define m_multi (BIT(Station) | BIT(Count) | BIT(Depth))
1493 int style, k = 0;
1494 reading d;
1495 unsigned long mUsed = 0;
1496 int old_style = pcs->style;
1498 /* after a bad *data command ignore survey data until the next
1499 * *data command to avoid an avalanche of errors */
1500 pcs->recorded_style = pcs->style = STYLE_IGNORE;
1502 get_token();
1503 style = match_tok(styletab, TABSIZE(styletab));
1505 if (style == STYLE_DEFAULT) {
1506 default_style(pcs);
1507 return;
1510 if (style == STYLE_UNKNOWN) {
1511 if (s_empty(&token)) {
1512 /* "*data" without arguments reinitialises the current style - useful
1513 * when using *data passage as it provides a way to break the passage
1514 * tube without having to repeat the full *data passage command.
1516 pcs->recorded_style = pcs->style = style = old_style;
1517 goto reinit_style;
1519 /* TRANSLATORS: e.g. trying to refer to an invalid FNORD data style */
1520 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Data style “%s” unknown*/65, s_str(&token));
1521 return;
1524 skipblanks();
1525 #ifndef NO_DEPRECATED
1526 /* Olde syntax had optional field for survey grade, so allow an omit
1527 * but issue a warning about it */
1528 if (isOmit(ch)) {
1529 static int data_depr_count = 0;
1530 if (data_depr_count < 5) {
1531 compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*“*data %s %c …” is deprecated - use “*data %s …” instead*/104,
1532 s_str(&token), ch, s_str(&token));
1533 if (++data_depr_count == 5)
1534 compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
1536 nextch();
1538 #endif
1540 int kMac = 6; /* minimum for NORMAL style */
1541 reading *new_order = osmalloc(kMac * sizeof(reading));
1542 char *style_name = s_steal(&token);
1543 do {
1544 filepos fp;
1545 get_pos(&fp);
1546 get_token();
1547 d = match_tok(dtab, TABSIZE(dtab));
1548 /* only token allowed after IGNOREALL is NEWLINE */
1549 if (k && new_order[k - 1] == IgnoreAll && d != Newline) {
1550 set_pos(&fp);
1551 break;
1553 /* Note: an unknown token is reported as trailing garbage */
1554 if (!TSTBIT(mask_all[style], d)) {
1555 /* TRANSLATORS: a data "style" is something like NORMAL, DIVING, etc.
1556 * a "reading" is one of FROM, TO, TAPE, COMPASS, CLINO for NORMAL
1557 * style. Neither "style" nor "reading" is a keyword in the program.
1559 * This error complains about a "DEPTH" gauge reading in "NORMAL"
1560 * style, for example.
1562 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP,
1563 /*Reading “%s” not allowed in data style “%s”*/63,
1564 s_str(&token), style_name);
1565 osfree(style_name);
1566 osfree(new_order);
1567 return;
1569 if (TSTBIT(mUsed, Newline) && TSTBIT(m_multi, d)) {
1570 /* TRANSLATORS: caused by e.g.
1572 * *data diving station newline depth tape compass
1574 * ("depth" needs to occur before "newline"). */
1575 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP,
1576 /*Reading “%s” must occur before NEWLINE*/225, s_str(&token));
1577 osfree(style_name);
1578 osfree(new_order);
1579 return;
1581 /* Check for duplicates unless it's a special reading:
1582 * IGNOREALL,IGNORE (duplicates allowed) ; END (not possible)
1584 if (!((BIT(Ignore) | BIT(End) | BIT(IgnoreAll)) & BIT(d))) {
1585 if (TSTBIT(mUsed, d)) {
1586 /* TRANSLATORS: complains about a situation like trying to define
1587 * two from stations per leg */
1588 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Duplicate reading “%s”*/67, s_str(&token));
1589 osfree(style_name);
1590 osfree(new_order);
1591 return;
1592 } else {
1593 /* Check for previously listed readings which are incompatible
1594 * with this one - e.g. Count vs FrCount */
1595 bool fBad = false;
1596 switch (d) {
1597 case Station:
1598 if (mUsed & (BIT(Fr) | BIT(To))) fBad = true;
1599 break;
1600 case Fr: case To:
1601 if (TSTBIT(mUsed, Station)) fBad = true;
1602 break;
1603 case Count:
1604 if (mUsed & (BIT(FrCount) | BIT(ToCount) | BIT(Tape)))
1605 fBad = true;
1606 break;
1607 case FrCount: case ToCount:
1608 if (mUsed & (BIT(Count) | BIT(Tape)))
1609 fBad = true;
1610 break;
1611 case Depth:
1612 if (mUsed & (BIT(FrDepth) | BIT(ToDepth) | BIT(DepthChange)))
1613 fBad = true;
1614 break;
1615 case FrDepth: case ToDepth:
1616 if (mUsed & (BIT(Depth) | BIT(DepthChange))) fBad = true;
1617 break;
1618 case DepthChange:
1619 if (mUsed & (BIT(FrDepth) | BIT(ToDepth) | BIT(Depth)))
1620 fBad = true;
1621 break;
1622 case Newline:
1623 if (mUsed & ~m_multi) {
1624 /* TRANSLATORS: e.g.
1626 * *data normal from to tape newline compass clino */
1627 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*NEWLINE can only be preceded by STATION, DEPTH, and COUNT*/226);
1628 osfree(style_name);
1629 osfree(new_order);
1630 return;
1632 if (k == 0) {
1633 /* TRANSLATORS: error from:
1635 * *data normal newline from to tape compass clino */
1636 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*NEWLINE can’t be the first reading*/222);
1637 osfree(style_name);
1638 osfree(new_order);
1639 return;
1641 break;
1642 default: /* avoid compiler warnings about unhandled enums */
1643 break;
1645 if (fBad) {
1646 /* Not entirely happy with phrasing this... */
1647 /* TRANSLATORS: This is an error from the *DATA command. It
1648 * means that a reading (which will appear where %s is isn't
1649 * valid as the list of readings has already included the same
1650 * reading, or an equivalent one (e.g. you can't have both
1651 * DEPTH and DEPTHCHANGE together). */
1652 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Reading “%s” duplicates previous reading(s)*/77,
1653 s_str(&token));
1654 osfree(style_name);
1655 osfree(new_order);
1656 return;
1658 mUsed |= BIT(d); /* used to catch duplicates */
1661 if (k && new_order[k - 1] == IgnoreAll) {
1662 SVX_ASSERT(d == Newline);
1663 k--;
1664 d = IgnoreAllAndNewLine;
1666 if (k >= kMac) {
1667 kMac = kMac * 2;
1668 new_order = osrealloc(new_order, kMac * sizeof(reading));
1670 new_order[k++] = d;
1671 } while (d != End);
1673 if (k >= 2 && new_order[k - 2] == Newline) {
1674 /* TRANSLATORS: error from:
1676 * *data normal from to tape compass clino newline */
1677 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*NEWLINE can’t be the last reading*/223);
1678 osfree(style_name);
1679 osfree(new_order);
1680 return;
1683 if (style == STYLE_NOSURVEY) {
1684 if (TSTBIT(mUsed, Station)) {
1685 if (k >= kMac) {
1686 kMac = kMac * 2;
1687 new_order = osrealloc(new_order, kMac * sizeof(reading));
1689 new_order[k - 1] = Newline;
1690 new_order[k++] = End;
1692 } else if (style == STYLE_PASSAGE) {
1693 /* Station doesn't mean "multiline" for STYLE_PASSAGE. */
1694 } else if (!TSTBIT(mUsed, Newline) && (m_multi & mUsed)) {
1695 /* TRANSLATORS: Error given by something like:
1697 * *data normal station tape compass clino
1699 * ("station" signifies interleaved data). */
1700 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Interleaved readings, but no NEWLINE*/224);
1701 osfree(style_name);
1702 osfree(new_order);
1703 return;
1706 #if 0
1707 printf("mUsed = 0x%x\n", mUsed);
1708 #endif
1710 /* Check the supplied readings form a sufficient set. */
1711 if (style != STYLE_PASSAGE) {
1712 if ((mUsed & (BIT(Fr) | BIT(To))) == (BIT(Fr) | BIT(To)))
1713 mUsed |= BIT(Station);
1714 else if (TSTBIT(mUsed, Station))
1715 mUsed |= BIT(Fr) | BIT(To);
1718 if (mUsed & (BIT(Comp) | BIT(BackComp)))
1719 mUsed |= BIT(Comp) | BIT(BackComp);
1721 if (mUsed & (BIT(Clino) | BIT(BackClino)))
1722 mUsed |= BIT(Clino) | BIT(BackClino);
1724 if ((mUsed & (BIT(FrDepth) | BIT(ToDepth))) == (BIT(FrDepth) | BIT(ToDepth)))
1725 mUsed |= BIT(Depth) | BIT(DepthChange);
1726 else if (mUsed & (BIT(Depth) | BIT(DepthChange)))
1727 mUsed |= BIT(FrDepth) | BIT(ToDepth) | BIT(Depth) | BIT(DepthChange);
1729 if ((mUsed & (BIT(FrCount) | BIT(ToCount))) == (BIT(FrCount) | BIT(ToCount)))
1730 mUsed |= BIT(Count) | BIT(Tape) | BIT(BackTape);
1731 else if (mUsed & (BIT(Count) | BIT(Tape) | BIT(BackTape)))
1732 mUsed |= BIT(FrCount) | BIT(ToCount) | BIT(Count) | BIT(Tape) | BIT(BackTape);
1734 #if 0
1735 printf("mUsed = 0x%x, opt = 0x%x, mask = 0x%x\n", mUsed,
1736 mask_optional[style], mask[style]);
1737 #endif
1739 if (((mUsed &~ BIT(Newline)) | mask_optional[style]) != mask[style]) {
1740 /* Test should only fail with too few bits set, not too many */
1741 SVX_ASSERT((((mUsed &~ BIT(Newline)) | mask_optional[style])
1742 &~ mask[style]) == 0);
1743 /* TRANSLATORS: i.e. not enough readings for the style. */
1744 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Too few readings for data style “%s”*/64, style_name);
1745 osfree(style_name);
1746 osfree(new_order);
1747 return;
1750 /* don't free default ordering or ordering used by parent */
1751 if (pcs->ordering != default_order &&
1752 !(pcs->next && pcs->next->ordering == pcs->ordering))
1753 osfree((reading*)pcs->ordering);
1755 pcs->recorded_style = pcs->style = style;
1756 pcs->ordering = new_order;
1758 osfree(style_name);
1760 reinit_style:
1761 if (style == STYLE_PASSAGE) {
1762 lrudlist * new_psg = osnew(lrudlist);
1763 new_psg->tube = NULL;
1764 new_psg->next = model;
1765 model = new_psg;
1766 next_lrud = &(new_psg->tube);
1770 static void
1771 cmd_units(void)
1773 int units, quantity;
1774 unsigned long qmask;
1775 unsigned long m; /* mask with bit x set to indicate quantity x specified */
1776 real factor;
1777 filepos fp;
1779 qmask = get_qlist(BIT(Q_POS)|BIT(Q_PLUMB)|BIT(Q_LEVEL));
1781 if (!qmask) return;
1782 if (qmask == BIT(Q_DEFAULT)) {
1783 default_units(pcs);
1784 return;
1787 get_pos(&fp);
1788 factor = read_numeric(true);
1789 if (factor == 0.0) {
1790 set_pos(&fp);
1791 /* TRANSLATORS: error message given by "*units tape 0 feet" - it’s
1792 * meaningless to say your tape is marked in "0 feet" (but you might
1793 * measure distance by counting knots on a diving line, and tie them
1794 * every "2 feet"). */
1795 compile_diagnostic(DIAG_ERR|DIAG_WORD, /**UNITS factor must be non-zero*/200);
1796 skipline();
1797 return;
1800 units = get_units(qmask, true);
1801 if (units == UNITS_NULL) return;
1802 if (TSTBIT(qmask, Q_GRADIENT))
1803 pcs->f_clino_percent = (units == UNITS_PERCENT);
1804 if (TSTBIT(qmask, Q_BACKGRADIENT))
1805 pcs->f_backclino_percent = (units == UNITS_PERCENT);
1807 if (TSTBIT(qmask, Q_BEARING)) {
1808 pcs->f_bearing_quadrants = (units == UNITS_QUADRANTS);
1810 if (TSTBIT(qmask, Q_BACKBEARING)) {
1811 pcs->f_backbearing_quadrants = (units == UNITS_QUADRANTS);
1814 if (factor == HUGE_REAL) {
1815 factor = factor_tab[units];
1816 } else {
1817 factor *= factor_tab[units];
1820 for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1)
1821 if (qmask & m) pcs->units[quantity] = factor;
1824 static void
1825 cmd_calibrate(void)
1827 real sc, z;
1828 unsigned long qmask, m;
1829 int quantity;
1830 filepos fp;
1832 qmask = get_qlist(BIT(Q_POS)|BIT(Q_PLUMB)|BIT(Q_LEVEL));
1833 if (!qmask) return; /* error already reported */
1835 if (qmask == BIT(Q_DEFAULT)) {
1836 default_calib(pcs);
1837 return;
1840 if (((qmask & LEN_QMASK)) && ((qmask & ANG_QMASK))) {
1841 /* TRANSLATORS: e.g.
1843 * *calibrate tape compass 1 1
1845 compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Can’t calibrate angular and length quantities together*/227);
1846 return;
1849 z = read_numeric(false);
1850 get_pos(&fp);
1851 sc = read_numeric(true);
1852 if (sc == HUGE_REAL) {
1853 if (isalpha(ch)) {
1854 int units = get_units(qmask, false);
1855 if (units == UNITS_NULL) {
1856 return;
1858 z *= factor_tab[units];
1859 sc = read_numeric(true);
1860 if (sc == HUGE_REAL) {
1861 sc = (real)1.0;
1862 } else {
1863 /* Adjustment applied is: (reading - z) * sc
1864 * We want: reading * sc - z
1865 * So divide z by sc so the applied adjustment does what we want.
1867 z /= sc;
1869 } else {
1870 sc = (real)1.0;
1874 if (sc == HUGE_REAL) sc = (real)1.0;
1875 /* check for declination scale */
1876 if (TSTBIT(qmask, Q_DECLINATION) && sc != 1.0) {
1877 set_pos(&fp);
1878 /* TRANSLATORS: DECLINATION is a built-in keyword, so best not to
1879 * translate */
1880 compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Scale factor must be 1.0 for DECLINATION*/40);
1881 skipline();
1882 return;
1884 if (sc == 0.0) {
1885 set_pos(&fp);
1886 /* TRANSLATORS: If the scale factor for an instrument is zero, then any
1887 * reading would be mapped to zero, which doesn't make sense. */
1888 compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Scale factor must be non-zero*/391);
1889 skipline();
1890 return;
1892 for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1) {
1893 if (qmask & m) {
1894 pcs->z[quantity] = pcs->units[quantity] * z;
1895 pcs->sc[quantity] = sc;
1900 static void
1901 cmd_declination(void)
1903 real v = read_numeric(true);
1904 if (v == HUGE_REAL) {
1905 get_token_no_blanks();
1906 if (!S_EQ(&uctoken, "AUTO")) {
1907 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_COL, /*Expected number or “AUTO”*/309);
1908 return;
1910 filepos fp_auto;
1911 get_pos(&fp_auto);
1913 /* *declination auto X Y Z */
1914 real x = read_numeric(false);
1915 real y = read_numeric(false);
1916 real z = read_numeric(false);
1917 if (!pcs->proj_str) {
1918 filepos fp;
1919 get_pos(&fp);
1920 set_pos(&fp_auto);
1921 compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Input coordinate system must be specified for “*DECLINATION AUTO”*/301);
1922 set_pos(&fp);
1923 return;
1925 set_declination_location(x, y, z, pcs->proj_str);
1926 } else {
1927 /* *declination D UNITS */
1928 int units = get_units(BIT(Q_DECLINATION), false);
1929 if (units == UNITS_NULL) {
1930 return;
1932 pcs->z[Q_DECLINATION] = -v * factor_tab[units];
1933 pcs->convergence = 0;
1937 #ifndef NO_DEPRECATED
1938 static void
1939 cmd_default(void)
1941 static const sztok defaulttab[] = {
1942 { "CALIBRATE", CMD_CALIBRATE },
1943 { "DATA", CMD_DATA },
1944 { "UNITS", CMD_UNITS },
1945 { NULL, CMD_NULL }
1947 static int default_depr_count = 0;
1949 if (default_depr_count < 5) {
1950 /* TRANSLATORS: If you're unsure what "deprecated" means, see:
1951 * https://en.wikipedia.org/wiki/Deprecation */
1952 compile_diagnostic(DIAG_WARN|DIAG_COL, /**DEFAULT is deprecated - use *CALIBRATE/DATA/SD/UNITS with argument DEFAULT instead*/20);
1953 if (++default_depr_count == 5)
1954 compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
1957 get_token();
1958 switch (match_tok(defaulttab, TABSIZE(defaulttab))) {
1959 case CMD_CALIBRATE:
1960 default_calib(pcs);
1961 break;
1962 case CMD_DATA:
1963 default_style(pcs);
1964 default_grade(pcs);
1965 break;
1966 case CMD_UNITS:
1967 default_units(pcs);
1968 break;
1969 default:
1970 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown setting “%s”*/41,
1971 s_str(&token));
1974 #endif
1976 static void
1977 cmd_include(void)
1979 char *pth = NULL;
1980 string fnm = S_INIT;
1981 #ifndef NO_DEPRECATED
1982 prefix *root_store;
1983 #endif
1984 int ch_store;
1986 pth = path_from_fnm(file.filename);
1988 read_string(&fnm);
1990 #ifndef NO_DEPRECATED
1991 /* Since *begin / *end nesting cannot cross file boundaries we only
1992 * need to preserve the prefix if the deprecated *prefix command
1993 * can be used */
1994 root_store = root;
1995 root = pcs->Prefix; /* Root for include file is current prefix */
1996 #endif
1997 ch_store = ch;
1999 data_file(pth, s_str(&fnm));
2001 #ifndef NO_DEPRECATED
2002 root = root_store; /* and restore root */
2003 #endif
2004 ch = ch_store;
2006 s_free(&fnm);
2007 osfree(pth);
2010 static void
2011 cmd_sd(void)
2013 real sd, variance;
2014 int units;
2015 unsigned long qmask, m;
2016 int quantity;
2017 qmask = get_qlist(BIT(Q_DECLINATION));
2018 if (!qmask) return; /* no quantities found - error already reported */
2020 if (qmask == BIT(Q_DEFAULT)) {
2021 default_grade(pcs);
2022 return;
2024 sd = read_numeric(false);
2025 if (sd <= (real)0.0) {
2026 compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_COL, /*Standard deviation must be positive*/48);
2027 return;
2029 units = get_units(qmask, false);
2030 if (units == UNITS_NULL) return;
2032 sd *= factor_tab[units];
2033 variance = sqrd(sd);
2035 for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1)
2036 if (qmask & m) pcs->Var[quantity] = variance;
2039 static void
2040 cmd_title(void)
2042 if (!fExplicitTitle && pcs->Prefix == root) {
2043 /* If we don't have an explicit title yet, and we're currently in the
2044 * root prefix, use this title explicitly. */
2045 fExplicitTitle = true;
2046 read_string(&survey_title);
2047 } else {
2048 /* parse and throw away this title (but still check rest of line) */
2049 string s = S_INIT;
2050 read_string(&s);
2051 s_free(&s);
2055 static const sztok case_tab[] = {
2056 {"PRESERVE", OFF},
2057 {"TOLOWER", LOWER},
2058 {"TOUPPER", UPPER},
2059 {NULL, -1}
2062 static void
2063 cmd_case(void)
2065 int setting;
2066 get_token();
2067 setting = match_tok(case_tab, TABSIZE(case_tab));
2068 if (setting != -1) {
2069 pcs->Case = setting;
2070 } else {
2071 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found “%s”, expecting “PRESERVE”, “TOUPPER”, or “TOLOWER”*/10, s_str(&token));
2075 typedef enum {
2076 CS_NONE = -1,
2077 CS_CUSTOM,
2078 CS_EPSG,
2079 CS_ESRI,
2080 CS_EUR,
2081 CS_IJTSK,
2082 CS_JTSK,
2083 CS_LAT,
2084 CS_LOCAL,
2085 CS_LONG,
2086 CS_OSGB,
2087 CS_S_MERC,
2088 CS_UTM
2089 } cs_class;
2091 static const sztok cs_tab[] = {
2092 {"CUSTOM", CS_CUSTOM},
2093 {"EPSG", CS_EPSG}, /* EPSG:<number> */
2094 {"ESRI", CS_ESRI}, /* ESRI:<number> */
2095 {"EUR", CS_EUR}, /* EUR79Z30 */
2096 {"IJTSK", CS_IJTSK}, /* IJTSK or IJTSK03 */
2097 {"JTSK", CS_JTSK}, /* JTSK or JTSK03 */
2098 {"LAT", CS_LAT}, /* LAT-LONG */
2099 {"LOCAL", CS_LOCAL},
2100 {"LONG", CS_LONG}, /* LONG-LAT */
2101 {"OSGB", CS_OSGB}, /* OSGB:<H, N, O, S or T><A-Z except I> */
2102 {"S", CS_S_MERC}, /* S-MERC */
2103 {"UTM", CS_UTM}, /* UTM<zone><N or S or nothing> */
2104 {NULL, CS_NONE}
2107 static void
2108 cmd_cs(void)
2110 char *proj_str = NULL;
2111 cs_class cs;
2112 int cs_sub = INT_MIN;
2113 filepos fp;
2114 bool output = false;
2115 enum { YES, NO, MAYBE } ok_for_output = YES;
2116 static bool had_cs = false;
2118 if (!had_cs) {
2119 had_cs = true;
2120 if (first_fix_name) {
2121 compile_diagnostic_at(DIAG_ERR,
2122 first_fix_filename, first_fix_line,
2123 /*Station “%s” fixed before CS command first used*/442,
2124 sprint_prefix(first_fix_name));
2128 get_pos(&fp);
2129 /* Note get_token() only accepts letters - it'll stop at digits so "UTM12"
2130 * will give token "UTM". */
2131 get_token();
2132 if (S_EQ(&uctoken, "OUT")) {
2133 output = true;
2134 get_pos(&fp);
2135 get_token();
2137 cs = match_tok(cs_tab, TABSIZE(cs_tab));
2138 switch (cs) {
2139 case CS_NONE:
2140 break;
2141 case CS_CUSTOM:
2142 ok_for_output = MAYBE;
2143 get_pos(&fp);
2144 string str = S_INIT;
2145 read_string(&str);
2146 proj_str = s_steal(&str);
2147 cs_sub = 0;
2148 break;
2149 case CS_EPSG: case CS_ESRI:
2150 ok_for_output = MAYBE;
2151 if (ch == ':' && isdigit(nextch())) {
2152 unsigned n = read_uint();
2153 if (n < 1000000) {
2154 cs_sub = (int)n;
2157 break;
2158 case CS_EUR:
2159 if (isdigit(ch) &&
2160 read_uint() == 79 &&
2161 (ch == 'Z' || ch == 'z') &&
2162 isdigit(nextch()) &&
2163 read_uint() == 30) {
2164 cs_sub = 7930;
2166 break;
2167 case CS_JTSK:
2168 ok_for_output = NO;
2169 /* FALLTHRU */
2170 case CS_IJTSK:
2171 if (ch == '0') {
2172 if (nextch() == '3') {
2173 nextch();
2174 cs_sub = 3;
2176 } else {
2177 cs_sub = 0;
2179 break;
2180 case CS_LAT: case CS_LONG:
2181 ok_for_output = NO;
2182 if (ch == '-') {
2183 nextch();
2184 get_token_no_blanks();
2185 cs_class cs2 = match_tok(cs_tab, TABSIZE(cs_tab));
2186 if ((cs ^ cs2) == (CS_LAT ^ CS_LONG)) {
2187 cs_sub = 0;
2190 break;
2191 case CS_LOCAL:
2192 cs_sub = 0;
2193 break;
2194 case CS_OSGB:
2195 if (ch == ':') {
2196 int uch1 = toupper(nextch());
2197 if (strchr("HNOST", uch1)) {
2198 int uch2 = toupper(nextch());
2199 if (uch2 >= 'A' && uch2 <= 'Z' && uch2 != 'I') {
2200 int x, y;
2201 nextch();
2202 if (uch1 > 'I') --uch1;
2203 uch1 -= 'A';
2204 if (uch2 > 'I') --uch2;
2205 uch2 -= 'A';
2206 x = uch1 % 5;
2207 y = uch1 / 5;
2208 x = (x * 5) + uch2 % 5;
2209 y = (y * 5) + uch2 / 5;
2210 cs_sub = y * 25 + x;
2214 break;
2215 case CS_S_MERC:
2216 if (ch == '-') {
2217 nextch();
2218 get_token_no_blanks();
2219 if (S_EQ(&uctoken, "MERC")) {
2220 cs_sub = 0;
2223 break;
2224 case CS_UTM:
2225 if (isdigit(ch)) {
2226 unsigned n = read_uint();
2227 if (n >= 1 && n <= 60) {
2228 int uch = toupper(ch);
2229 cs_sub = (int)n;
2230 if (uch == 'S') {
2231 nextch();
2232 cs_sub = -cs_sub;
2233 } else if (uch == 'N') {
2234 nextch();
2238 break;
2240 if (cs_sub == INT_MIN || isalnum(ch)) {
2241 set_pos(&fp);
2242 compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Unknown coordinate system*/434);
2243 skipline();
2244 return;
2246 /* Actually handle the cs */
2247 switch (cs) {
2248 case CS_NONE:
2249 break;
2250 case CS_CUSTOM:
2251 /* proj_str already set */
2252 break;
2253 case CS_EPSG:
2254 proj_str = osmalloc(32);
2255 snprintf(proj_str, 32, "EPSG:%d", cs_sub);
2256 break;
2257 case CS_ESRI:
2258 proj_str = osmalloc(32);
2259 snprintf(proj_str, 32, "ESRI:%d", cs_sub);
2260 break;
2261 case CS_EUR:
2262 proj_str = osstrdup("+proj=utm +zone=30 +ellps=intl +towgs84=-86,-98,-119,0,0,0,0 +no_defs");
2263 break;
2264 case CS_IJTSK:
2265 if (cs_sub == 0)
2266 proj_str = osstrdup("+proj=krovak +ellps=bessel +towgs84=570.8285,85.6769,462.842,4.9984,1.5867,5.2611,3.5623 +no_defs");
2267 else
2268 proj_str = osstrdup("+proj=krovak +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +no_defs");
2269 break;
2270 case CS_JTSK:
2271 if (cs_sub == 0)
2272 proj_str = osstrdup("+proj=krovak +czech +ellps=bessel +towgs84=570.8285,85.6769,462.842,4.9984,1.5867,5.2611,3.5623 +no_defs");
2273 else
2274 proj_str = osstrdup("+proj=krovak +czech +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +no_defs");
2275 break;
2276 case CS_LAT:
2277 /* FIXME: Requires PROJ >= 4.8.0 for +axis, and the SDs will be
2278 * misapplied, so we may want to swap ourselves. Also, while
2279 * therion supports lat-long, I'm not totally convinced that it is
2280 * sensible to do so - people often say "lat-long", but probably
2281 * don't think that that's actually "Northing, Easting". This
2282 * seems like it'll result in people accidentally getting X and Y
2283 * swapped in their fixed points...
2285 #if 0
2286 proj_str = osstrdup("+proj=longlat +ellps=WGS84 +datum=WGS84 +axis=neu +no_defs");
2287 #endif
2288 break;
2289 case CS_LOCAL:
2290 /* FIXME: Is it useful to be able to explicitly specify this? */
2291 break;
2292 case CS_LONG:
2293 proj_str = osstrdup("EPSG:4326");
2294 break;
2295 case CS_OSGB: {
2296 int x = 14 - (cs_sub % 25);
2297 int y = (cs_sub / 25) - 20;
2298 proj_str = osmalloc(160);
2299 snprintf(proj_str, 160,
2300 "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 "
2301 "+x_0=%d +y_0=%d +ellps=airy +datum=OSGB36 +units=m +no_defs",
2302 x * 100000, y * 100000);
2303 break;
2305 case CS_S_MERC:
2306 proj_str = osstrdup("EPSG:3857");
2307 break;
2308 case CS_UTM:
2309 proj_str = osmalloc(32);
2310 if (cs_sub > 0) {
2311 snprintf(proj_str, 32, "EPSG:%d", 32600 + cs_sub);
2312 } else {
2313 snprintf(proj_str, 32, "EPSG:%d", 32700 - cs_sub);
2315 break;
2318 if (!proj_str) {
2319 /* printf("CS %d:%d\n", (int)cs, cs_sub); */
2320 set_pos(&fp);
2321 compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Unknown coordinate system*/434);
2322 skipline();
2323 return;
2326 if (output) {
2327 if (ok_for_output == NO) {
2328 set_pos(&fp);
2329 compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Coordinate system unsuitable for output*/435);
2330 skipline();
2331 return;
2334 if (proj_str_out && strcmp(proj_str, proj_str_out) == 0) {
2335 /* Same as the output cs that's already set, so nothing to do. */
2336 osfree(proj_str);
2337 return;
2340 if (ok_for_output == MAYBE) {
2341 /* We only actually create the transformation from input to output when
2342 * we need it, but for a custom proj string or EPSG/ESRI code we need
2343 * to check that the specified coordinate system is valid and also if
2344 * it's suitable for output so we need to test creating it here.
2346 PJ* pj = proj_create(PJ_DEFAULT_CTX, proj_str);
2347 if (!pj) {
2348 set_pos(&fp);
2349 compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Invalid coordinate system: %s*/443,
2350 proj_errno_string(proj_context_errno(PJ_DEFAULT_CTX)));
2351 skipline();
2352 osfree(proj_str);
2353 return;
2355 int type = proj_get_type(pj);
2356 if (type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
2357 type == PJ_TYPE_GEOGRAPHIC_3D_CRS) {
2358 set_pos(&fp);
2359 compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Coordinate system unsuitable for output*/435);
2360 skipline();
2361 osfree(proj_str);
2362 return;
2366 if (proj_str_out) {
2367 /* If the output cs is already set, subsequent attempts to set it
2368 * are silently ignored (so you can combine two datasets and set
2369 * the output cs to use before you include either).
2371 osfree(proj_str);
2372 } else {
2373 proj_str_out = proj_str;
2375 } else {
2376 if (proj_str_out && strcmp(proj_str, proj_str_out) == 0) {
2377 /* Same as the current output projection, so valid for input. */
2378 } else if (pcs->proj_str && strcmp(proj_str, pcs->proj_str) == 0) {
2379 /* Same as the current input projection, so nothing to do! */
2380 return;
2381 } else if (ok_for_output == MAYBE) {
2382 /* (ok_for_output == MAYBE) also happens to indicate whether we need
2383 * to check that the coordinate system is valid for input.
2385 PJ* pj = proj_create(PJ_DEFAULT_CTX, proj_str);
2386 if (!pj) {
2387 set_pos(&fp);
2388 compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Invalid coordinate system: %s*/443,
2389 proj_errno_string(proj_context_errno(PJ_DEFAULT_CTX)));
2390 skipline();
2391 return;
2393 proj_destroy(pj);
2396 /* Free current input proj_str if not used by parent. */
2397 settings * p = pcs;
2398 if (!p->next || p->proj_str != p->next->proj_str)
2399 osfree(p->proj_str);
2400 p->proj_str = proj_str;
2401 invalidate_pj_cached();
2405 static const sztok infer_tab[] = {
2406 { "EQUATES", INFER_EQUATES },
2407 { "EXPORTS", INFER_EXPORTS },
2408 { "PLUMBS", INFER_PLUMBS },
2409 #if 0 /* FIXME */
2410 { "SUBSURVEYS", INFER_SUBSURVEYS },
2411 #endif
2412 { NULL, INFER_NULL }
2415 static const sztok onoff_tab[] = {
2416 { "OFF", 0 },
2417 { "ON", 1 },
2418 { NULL, -1 }
2421 static void
2422 cmd_infer(void)
2424 infer_what setting;
2425 int on;
2426 get_token();
2427 setting = match_tok(infer_tab, TABSIZE(infer_tab));
2428 if (setting == INFER_NULL) {
2429 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found “%s”, expecting “EQUATES”, “EXPORTS”, or “PLUMBS”*/31, s_str(&token));
2430 return;
2432 get_token();
2433 on = match_tok(onoff_tab, TABSIZE(onoff_tab));
2434 if (on == -1) {
2435 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found “%s”, expecting “ON” or “OFF”*/32, s_str(&token));
2436 return;
2439 if (on) {
2440 pcs->infer |= BIT(setting);
2441 if (setting == INFER_EXPORTS) fExportUsed = true;
2442 } else {
2443 pcs->infer &= ~BIT(setting);
2447 static void
2448 cmd_truncate(void)
2450 unsigned int truncate_at = 0; /* default is no truncation */
2451 filepos fp;
2453 get_pos(&fp);
2455 get_token();
2456 if (!S_EQ(&uctoken, "OFF")) {
2457 if (!s_empty(&uctoken)) set_pos(&fp);
2458 truncate_at = read_uint();
2460 /* for backward compatibility, "*truncate 0" means "*truncate off" */
2461 pcs->Truncate = (truncate_at == 0) ? INT_MAX : truncate_at;
2464 static void
2465 cmd_ref(void)
2467 /* Just syntax check for now. */
2468 string ref = S_INIT;
2469 read_string(&ref);
2470 s_free(&ref);
2473 static void
2474 cmd_require(void)
2476 const unsigned int version[] = {COMMAVERSION};
2477 const unsigned int *ver = version;
2478 filepos fp;
2480 skipblanks();
2481 get_pos(&fp);
2482 while (1) {
2483 int diff = *ver++ - read_uint();
2484 if (diff > 0) break;
2485 if (diff < 0) {
2486 size_t i, len;
2487 char *v;
2488 filepos fp_tmp;
2490 /* find end of version number */
2491 while (isdigit(ch) || ch == '.') nextch();
2492 get_pos(&fp_tmp);
2493 len = (size_t)(fp_tmp.offset - fp.offset);
2494 v = osmalloc(len + 1);
2495 set_pos(&fp);
2496 for (i = 0; i < len; i++) {
2497 v[i] = ch;
2498 nextch();
2500 v[i] = '\0';
2501 /* TRANSLATORS: Feel free to translate as "or newer" instead of "or
2502 * greater" if that gives a more natural translation. It's
2503 * technically not quite right when there are parallel active release
2504 * series (e.g. Survex 1.0.40 was released *after* 1.2.0), but this
2505 * seems unlikely to confuse users. "Survex" is the name of the
2506 * software, so should not be translated.
2508 * Here "survey" is a "cave map" rather than list of questions - it should be
2509 * translated to the terminology that cavers using the language would use.
2511 fatalerror_in_file(file.filename, file.line, /*Survex version %s or greater required to process this survey data.*/2, v);
2513 if (ch != '.') break;
2514 nextch();
2515 if (!isdigit(ch) || ver == version + sizeof(version) / sizeof(*version))
2516 break;
2518 /* skip rest of version number */
2519 while (isdigit(ch) || ch == '.') nextch();
2522 /* allocate new meta_data if need be */
2523 void
2524 copy_on_write_meta(settings *s)
2526 if (!s->meta || s->meta->ref_count != 0) {
2527 meta_data * meta_new = osnew(meta_data);
2528 if (!s->meta) {
2529 meta_new->days1 = meta_new->days2 = -1;
2530 } else {
2531 *meta_new = *(s->meta);
2533 meta_new->ref_count = 0;
2534 s->meta = meta_new;
2538 static void
2539 cmd_date(void)
2541 int year, month, day;
2542 int days1, days2;
2543 bool implicit_range = false;
2544 filepos fp, fp2;
2546 get_pos(&fp);
2547 read_date(&year, &month, &day);
2548 days1 = days_since_1900(year, month ? month : 1, day ? day : 1);
2550 if (days1 > current_days_since_1900) {
2551 set_pos(&fp);
2552 compile_diagnostic(DIAG_WARN|DIAG_DATE, /*Date is in the future!*/80);
2555 skipblanks();
2556 if (ch == '-') {
2557 nextch();
2558 get_pos(&fp2);
2559 read_date(&year, &month, &day);
2560 } else {
2561 if (month && day) {
2562 days2 = days1;
2563 goto read;
2565 implicit_range = true;
2568 if (month == 0) month = 12;
2569 if (day == 0) day = last_day(year, month);
2570 days2 = days_since_1900(year, month, day);
2572 if (!implicit_range && days2 > current_days_since_1900) {
2573 set_pos(&fp2);
2574 compile_diagnostic(DIAG_WARN|DIAG_DATE, /*Date is in the future!*/80);
2577 if (days2 < days1) {
2578 set_pos(&fp);
2579 compile_diagnostic(DIAG_ERR|DIAG_WORD, /*End of date range is before the start*/81);
2580 int tmp = days1;
2581 days1 = days2;
2582 days2 = tmp;
2585 read:
2586 if (!pcs->meta || pcs->meta->days1 != days1 || pcs->meta->days2 != days2) {
2587 copy_on_write_meta(pcs);
2588 pcs->meta->days1 = days1;
2589 pcs->meta->days2 = days2;
2590 /* Invalidate cached declination. */
2591 pcs->declination = HUGE_REAL;
2595 typedef void (*cmd_fn)(void);
2597 static const cmd_fn cmd_funcs[] = {
2598 cmd_alias,
2599 cmd_begin,
2600 cmd_calibrate,
2601 cmd_case,
2602 skipline, /*cmd_copyright,*/
2603 cmd_cs,
2604 cmd_data,
2605 cmd_date,
2606 cmd_declination,
2607 #ifndef NO_DEPRECATED
2608 cmd_default,
2609 #endif
2610 cmd_end,
2611 cmd_entrance,
2612 cmd_equate,
2613 cmd_export,
2614 cmd_fix,
2615 cmd_flags,
2616 cmd_include,
2617 cmd_infer,
2618 skipline, /*cmd_instrument,*/
2619 #ifndef NO_DEPRECATED
2620 cmd_prefix,
2621 #endif
2622 cmd_ref,
2623 cmd_require,
2624 cmd_sd,
2625 cmd_set,
2626 solve_network,
2627 skipline, /*cmd_team,*/
2628 cmd_title,
2629 cmd_truncate,
2630 cmd_units
2633 extern void
2634 handle_command(void)
2636 int cmdtok;
2637 get_token();
2638 cmdtok = match_tok(cmd_tab, TABSIZE(cmd_tab));
2640 if (cmdtok < 0 || cmdtok >= (int)(sizeof(cmd_funcs) / sizeof(cmd_fn))) {
2641 compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown command “%s”*/12, s_str(&token));
2642 return;
2645 switch (cmdtok) {
2646 case CMD_EXPORT:
2647 if (!f_export_ok)
2648 /* TRANSLATORS: The *EXPORT command is only valid just after *BEGIN
2649 * <SURVEY>, so this would generate this error:
2651 * *begin fred
2652 * 1 2 1.23 045 -6
2653 * *export 2
2654 * *end fred */
2655 compile_diagnostic(DIAG_ERR, /**EXPORT must immediately follow “*BEGIN <SURVEY>”*/57);
2656 break;
2657 case CMD_ALIAS:
2658 case CMD_CALIBRATE:
2659 case CMD_CASE:
2660 case CMD_COPYRIGHT:
2661 case CMD_CS:
2662 case CMD_DATA:
2663 case CMD_DATE:
2664 case CMD_DECLINATION:
2665 case CMD_DEFAULT:
2666 case CMD_FLAGS:
2667 case CMD_INFER:
2668 case CMD_INSTRUMENT:
2669 case CMD_REF:
2670 case CMD_REQUIRE:
2671 case CMD_SD:
2672 case CMD_SET:
2673 case CMD_TEAM:
2674 case CMD_TITLE:
2675 case CMD_TRUNCATE:
2676 case CMD_UNITS:
2677 /* These can occur between *begin and *export */
2678 break;
2679 default:
2680 /* NB: additional handling for "*begin <survey>" in cmd_begin */
2681 f_export_ok = false;
2682 break;
2685 cmd_funcs[cmdtok]();