source: git/src/commands.c @ c6c4996

Last change on this file since c6c4996 was 1650ba1, checked in by Olly Betts <olly@…>, 2 months ago

cavern: Show context if proj can't convert coords

Only for .svx files currently as this is more complex to do for
Compass and Walls.

  • Property mode set to 100644
File size: 87.3 KB
Line 
1/* commands.c
2 * Code for directives
3 * Copyright (C) 1991-2025 Olly Betts
4 *
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.
9 *
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.
14 *
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
18 */
19
20#include <config.h>
21
22#include <limits.h>
23#include <stddef.h> /* for offsetof */
24#include <string.h>
25
26#include <proj.h>
27#if PROJ_VERSION_MAJOR < 8
28# define proj_context_errno_string(CTX, ERR) proj_errno_string(ERR)
29#endif
30
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 "osalloc.h"
41#include "out.h"
42#include "readval.h"
43#include "str.h"
44
45static void
46move_to_fixedlist(node *stn, int ignore_dirn)
47{
48    remove_stn_from_list(&stnlist, stn);
49    add_stn_to_list(&fixedlist, stn);
50    pos *p = stn->name->pos;
51    for (int d = 0; d < 3; d++) {
52        if (d == ignore_dirn) continue;
53        linkfor *leg = stn->leg[d];
54        if (!leg) break;
55        node *to = leg->l.to;
56        if (to->name->pos == p) {
57            move_to_fixedlist(to, reverse_leg_dirn(leg));
58        }
59    }
60}
61
62int fix_station(prefix *fix_name, const double* coords) {
63    bool new_stn = (fix_name->stn == NULL &&
64                    !TSTBIT(fix_name->sflags, SFLAGS_SOLVED));
65    fix_name->sflags |= BIT(SFLAGS_FIXED);
66    if (new_stn) fix_name->sflags |= BIT(SFLAGS_UNUSED_FIXED_POINT);
67    node *stn = StnFromPfx(fix_name);
68    if (fixed(stn)) {
69        if (coords[0] != POS(stn, 0) ||
70            coords[1] != POS(stn, 1) ||
71            coords[2] != POS(stn, 2)) {
72            return -1;
73        }
74        return 1;
75    }
76
77    POS(stn, 0) = coords[0];
78    POS(stn, 1) = coords[1];
79    POS(stn, 2) = coords[2];
80
81    if (new_stn) {
82        remove_stn_from_list(&stnlist, stn);
83        add_stn_to_list(&fixedlist, stn);
84    } else {
85        move_to_fixedlist(stn, -1);
86    }
87
88    // Make the station's file:line location reflect where it was fixed.
89    fix_name->filename = file.filename;
90    fix_name->line = file.line;
91    return 0;
92}
93
94void fix_station_with_variance(prefix *fix_name, const double* coords,
95                               real var_x, real var_y, real var_z,
96#ifndef NO_COVARIANCES
97                               real cxy, real cyz, real czx
98#endif
99                              )
100{
101    bool new_stn = (fix_name->stn == NULL &&
102                    !TSTBIT(fix_name->sflags, SFLAGS_SOLVED));
103    if (new_stn) fix_name->sflags |= BIT(SFLAGS_UNUSED_FIXED_POINT);
104
105    node *stn = StnFromPfx(fix_name);
106    if (!fixed(stn)) {
107        node *fixpt = osnew(node);
108        prefix *name;
109        name = osnew(prefix);
110        name->pos = osnew(pos);
111        name->ident.p = NULL;
112        fixpt->name = name;
113        name->stn = fixpt;
114        name->up = NULL;
115        if (TSTBIT(pcs->infer, INFER_EXPORTS)) {
116            name->min_export = USHRT_MAX;
117        } else {
118            name->min_export = 0;
119        }
120        name->max_export = 0;
121        name->sflags = 0;
122        add_stn_to_list(&fixedlist, fixpt);
123        POS(fixpt, 0) = coords[0];
124        POS(fixpt, 1) = coords[1];
125        POS(fixpt, 2) = coords[2];
126        fixpt->leg[0] = fixpt->leg[1] = fixpt->leg[2] = NULL;
127        addfakeleg(fixpt, stn, 0, 0, 0,
128                   var_x, var_y, var_z
129#ifndef NO_COVARIANCES
130                   , cxy, cyz, czx
131#endif
132                  );
133    }
134}
135
136static void
137default_grade(settings *s)
138{
139   /* Values correspond to those in bcra5.svx */
140   s->Var[Q_POS] = (real)sqrd(0.05);
141   s->Var[Q_LENGTH] = (real)sqrd(0.05);
142   s->Var[Q_BACKLENGTH] = (real)sqrd(0.05);
143   s->Var[Q_COUNT] = (real)sqrd(0.05);
144   s->Var[Q_DX] = s->Var[Q_DY] = s->Var[Q_DZ] = (real)sqrd(0.05);
145   s->Var[Q_BEARING] = (real)sqrd(rad(0.5));
146   s->Var[Q_GRADIENT] = (real)sqrd(rad(0.5));
147   s->Var[Q_BACKBEARING] = (real)sqrd(rad(0.5));
148   s->Var[Q_BACKGRADIENT] = (real)sqrd(rad(0.5));
149   /* SD of plumbed legs (0.25 degrees?) */
150   s->Var[Q_PLUMB] = (real)sqrd(rad(0.25));
151   /* SD of level legs (0.25 degrees?) */
152   s->Var[Q_LEVEL] = (real)sqrd(rad(0.25));
153   s->Var[Q_DEPTH] = (real)sqrd(0.05);
154}
155
156static void
157default_truncate(settings *s)
158{
159   s->Truncate = INT_MAX;
160}
161
162static void
163default_case(settings *s)
164{
165   s->Case = LOWER;
166}
167
168static reading default_order[] = { Fr, To, Tape, Comp, Clino, End };
169
170static void
171default_style(settings *s)
172{
173   s->recorded_style = s->style = STYLE_NORMAL;
174   s->ordering = default_order;
175   s->dash_for_anon_wall_station = false;
176}
177
178static void
179default_prefix(settings *s)
180{
181   s->Prefix = root;
182}
183
184static void
185init_default_translate_map(short * t)
186{
187   int i;
188   for (i = '0'; i <= '9'; i++) t[i] |= SPECIAL_NAMES;
189   for (i = 'A'; i <= 'Z'; i++) t[i] |= SPECIAL_NAMES;
190   for (i = 'a'; i <= 'z'; i++) t[i] |= SPECIAL_NAMES;
191
192   t['\t'] |= SPECIAL_BLANK;
193   t[' '] |= SPECIAL_BLANK;
194   t[','] |= SPECIAL_BLANK;
195   t[';'] |= SPECIAL_COMMENT;
196   t['\032'] |= SPECIAL_EOL; /* Ctrl-Z, so olde DOS text files are handled ok */
197   t['\n'] |= SPECIAL_EOL;
198   t['\r'] |= SPECIAL_EOL;
199   t['*'] |= SPECIAL_KEYWORD;
200   t['-'] |= SPECIAL_OMIT;
201   t['\\'] |= SPECIAL_ROOT;
202   t['.'] |= SPECIAL_SEPARATOR;
203   t['_'] |= SPECIAL_NAMES;
204   t['-'] |= SPECIAL_NAMES; /* Added in 0.97 prerelease 4 */
205   t['.'] |= SPECIAL_DECIMAL;
206   t['-'] |= SPECIAL_MINUS;
207   t['+'] |= SPECIAL_PLUS;
208#if 0 /* FIXME */
209   t['{'] |= SPECIAL_OPEN;
210   t['}'] |= SPECIAL_CLOSE;
211#endif
212}
213
214static void
215default_translate(settings *s)
216{
217   if (s->next && s->next->Translate == s->Translate) {
218      /* We're currently using the same character translation map as our parent
219       * scope so allocate a new one before we modify it.
220       */
221      s->Translate = ((short*)osmalloc(sizeof(short) * 257)) + 1;
222   } else {
223/*  SVX_ASSERT(EOF==-1);*/ /* important, since we rely on this */
224   }
225   s->Translate[EOF] = SPECIAL_EOL;
226   memset(s->Translate, 0, sizeof(short) * 256);
227   init_default_translate_map(s->Translate);
228}
229
230/* Flag anything used in SPECIAL_* cumulatively to help us pick a suitable
231 * separator to use in the .3d file. */
232static short separator_map[256];
233
234void
235scan_compass_station_name(prefix *stn)
236{
237    /* We only need to scan the leaf station name - any survey hierarchy above
238     * that must have been set up in .svx files for which we update
239     * separator_map via cmd_set() plus adding the defaults in
240     * find_output_separator().
241     */
242    for (const char *p = prefix_ident(stn); *p; ++p) {
243        separator_map[(unsigned char)*p] |= SPECIAL_NAMES;
244    }
245}
246
247static char
248find_output_separator(void)
249{
250    // Fast path to handle most common cases where we'd pick '.'.
251    if ((separator_map['.'] & SPECIAL_NAMES) == 0) {
252        return '.';
253    }
254
255    static bool added_defaults = false;
256    if (!added_defaults) {
257        /* Add the default settings to separator_map. */
258        init_default_translate_map(separator_map);
259        added_defaults = true;
260    }
261
262    /* 30 punctuation characters plus space to try arranged in a sensible order
263     * of decreasing preference (these are all the ASCII punctuation characters
264     * excluding '_' and '-' since those are allowed in names by default so are
265     * poor choices for the separator).
266     */
267    int best = -1;
268    for (const char *p = "./:;,!|\\ ~+*^='`\"#$%&?@<>()[]{}"; *p; ++p) {
269        unsigned char candidate = *p;
270        int mask = separator_map[candidate];
271        switch (mask & (SPECIAL_SEPARATOR|SPECIAL_NAMES)) {
272            case SPECIAL_SEPARATOR:
273                /* A character which is set as a separator character at some
274                 * point but never set as a name character is perfect.
275                 */
276                return candidate;
277            case 0:
278                /* A character which is never set as either a separator
279                 * character or a name character is a reasonable option.
280                 */
281                if (best < 0) best = candidate;
282                break;
283        }
284    }
285    if (best < 0) {
286        /* Argh, no plausible choice!  Just return the default for now. */
287        return '.';
288    }
289    return best;
290}
291
292void
293default_units(settings *s)
294{
295   int quantity;
296   for (quantity = 0; quantity < Q_MAC; quantity++) {
297      if (TSTBIT(ANG_QMASK, quantity))
298         s->units[quantity] = (real)(M_PI / 180.0); /* degrees */
299      else
300         s->units[quantity] = (real)1.0; /* metres */
301   }
302   s->f_clino_percent = s->f_backclino_percent = false;
303   s->f_bearing_quadrants = s->f_backbearing_quadrants = false;
304}
305
306void
307default_calib(settings *s)
308{
309   int quantity;
310   for (quantity = 0; quantity < Q_MAC; quantity++) {
311      s->z[quantity] = (real)0.0;
312      s->sc[quantity] = (real)1.0;
313   }
314}
315
316static void
317default_flags(settings *s)
318{
319   s->flags = 0;
320}
321
322extern void
323default_all(settings *s)
324{
325   default_truncate(s);
326   s->infer = 0;
327   default_case(s);
328   default_style(s);
329   default_prefix(s);
330   default_translate(s);
331   default_grade(s);
332   default_units(s);
333   default_calib(s);
334   default_flags(s);
335}
336
337string token = S_INIT;
338
339string uctoken = S_INIT;
340
341extern void
342get_token_legacy(void)
343{
344   skipblanks();
345   get_token_legacy_no_blanks();
346}
347
348extern void
349get_token_legacy_no_blanks(void)
350{
351   s_clear(&token);
352   s_clear(&uctoken);
353   while (isalpha(ch)) {
354      s_appendch(&token, ch);
355      s_appendch(&uctoken, toupper(ch));
356      nextch();
357   }
358
359#if 0
360   printf("get_token_legacy_no_blanks() got “%s”\n", s_str(&token));
361#endif
362}
363
364void
365do_legacy_token_warning(void)
366{
367    if (!s_empty(&token)) {
368        if (!isBlank(ch) && !isComm(ch) && !isEol(ch)) {
369            compile_diagnostic(DIAG_WARN|DIAG_COL, /*No blank after token*/74);
370        }
371    }
372}
373
374extern void
375get_token(void)
376{
377    skipblanks();
378    get_token_no_blanks();
379}
380
381extern void
382get_token_no_blanks(void)
383{
384    s_clear(&token);
385    s_clear(&uctoken);
386    if (isalpha(ch)) {
387        do {
388            s_appendch(&token, ch);
389            s_appendch(&uctoken, toupper(ch));
390            nextch();
391        } while (isalnum(ch));
392    }
393}
394
395/* read word */
396void
397get_word(void)
398{
399   s_clear(&token);
400   skipblanks();
401   while (!isBlank(ch) && !isComm(ch) && !isEol(ch)) {
402      s_appendch(&token, ch);
403      nextch();
404   }
405#if 0
406   printf("get_word() got “%s”\n", s_str(&token));
407#endif
408}
409
410/* match_tok() now uses binary chop
411 * tab argument should be alphabetically sorted (ascending)
412 */
413extern int
414match_tok(const sztok *tab, int tab_size)
415{
416   int a = 0, b = tab_size - 1, c;
417   int r;
418   const char* tok = s_str(&uctoken);
419   SVX_ASSERT(tab_size > 0); /* catch empty table */
420/*  printf("[%d,%d]",a,b); */
421   while (a <= b) {
422      c = (unsigned)(a + b) / 2;
423/*     printf(" %d",c); */
424      r = strcmp(tab[c].sz, tok);
425      if (r == 0) return tab[c].tok; /* match */
426      if (r < 0)
427         a = c + 1;
428      else
429         b = c - 1;
430   }
431   return tab[tab_size].tok; /* no match */
432}
433
434typedef enum {
435   CMD_NULL = -1, CMD_ALIAS, CMD_BEGIN, CMD_CALIBRATE, CMD_CARTESIAN, CMD_CASE,
436   CMD_COPYRIGHT, CMD_CS, CMD_DATA, CMD_DATE, CMD_DECLINATION, CMD_DEFAULT,
437   CMD_END, CMD_ENTRANCE, CMD_EQUATE, CMD_EXPORT, CMD_FIX, CMD_FLAGS,
438   CMD_INCLUDE, CMD_INFER, CMD_INSTRUMENT, CMD_PREFIX, CMD_REF, CMD_REQUIRE,
439   CMD_SD, CMD_SET, CMD_SOLVE, CMD_TEAM, CMD_TITLE, CMD_TRUNCATE, CMD_UNITS
440} cmds;
441
442static const sztok cmd_tab[] = {
443     {"ALIAS",     CMD_ALIAS},
444     {"BEGIN",     CMD_BEGIN},
445     {"CALIBRATE", CMD_CALIBRATE},
446     {"CARTESIAN", CMD_CARTESIAN},
447     {"CASE",      CMD_CASE},
448     {"COPYRIGHT", CMD_COPYRIGHT},
449     {"CS",        CMD_CS},
450     {"DATA",      CMD_DATA},
451     {"DATE",      CMD_DATE},
452     {"DECLINATION", CMD_DECLINATION},
453#ifndef NO_DEPRECATED
454     {"DEFAULT",   CMD_DEFAULT},
455#endif
456     {"END",       CMD_END},
457     {"ENTRANCE",  CMD_ENTRANCE},
458     {"EQUATE",    CMD_EQUATE},
459     {"EXPORT",    CMD_EXPORT},
460     {"FIX",       CMD_FIX},
461     {"FLAGS",     CMD_FLAGS},
462     {"INCLUDE",   CMD_INCLUDE},
463     {"INFER",     CMD_INFER},
464     {"INSTRUMENT",CMD_INSTRUMENT},
465#ifndef NO_DEPRECATED
466     {"PREFIX",    CMD_PREFIX},
467#endif
468     {"REF",       CMD_REF},
469     {"REQUIRE",   CMD_REQUIRE},
470     {"SD",        CMD_SD},
471     {"SET",       CMD_SET},
472     {"SOLVE",     CMD_SOLVE},
473     {"TEAM",      CMD_TEAM},
474     {"TITLE",     CMD_TITLE},
475     {"TRUNCATE",  CMD_TRUNCATE},
476     {"UNITS",     CMD_UNITS},
477     {NULL,        CMD_NULL}
478};
479
480/* masks for units which are length and angles respectively */
481#define LEN_UMASK (BIT(UNITS_METRES) | BIT(UNITS_FEET) | BIT(UNITS_YARDS))
482#define ANG_UMASK (BIT(UNITS_DEGS) | BIT(UNITS_GRADS) | BIT(UNITS_MINUTES))
483
484/* ordering must be the same as the units enum */
485const real factor_tab[] = {
486   1.0, METRES_PER_FOOT, (METRES_PER_FOOT*3.0),
487   (M_PI/180.0), (M_PI/180.0), (M_PI/200.0), 0.01, (M_PI/180.0/60.0)
488};
489
490const int units_to_msgno[] = {
491    /*m*/424,
492    /*′*/428,
493    -1, /* yards */
494    /*°*/344, /* quadrants */
495    /*°*/344,
496    /*ᵍ*/345,
497    /*%*/96,
498    -1 /* minutes */
499};
500
501int get_length_units(int quantity) {
502    double factor = pcs->units[quantity];
503    if (fabs(factor - METRES_PER_FOOT) <= REAL_EPSILON ||
504        fabs(factor - METRES_PER_FOOT * 3.0) <= REAL_EPSILON) {
505        return UNITS_FEET;
506    }
507    return UNITS_METRES;
508}
509
510int get_angle_units(int quantity) {
511    double factor = pcs->units[quantity];
512    if (fabs(factor - M_PI / 200.0) <= REAL_EPSILON) {
513        return UNITS_GRADS;
514    }
515    return UNITS_DEGS;
516}
517
518static int
519get_units(unsigned long qmask, bool percent_ok)
520{
521   static const sztok utab[] = {
522        {"DEGREES",       UNITS_DEGS },
523        {"DEGS",          UNITS_DEGS },
524        {"FEET",          UNITS_FEET },
525        {"GRADS",         UNITS_GRADS },
526        {"METERS",        UNITS_METRES },
527        {"METRES",        UNITS_METRES },
528        {"METRIC",        UNITS_METRES },
529        {"MILS",          UNITS_DEPRECATED_ALIAS_FOR_GRADS },
530        {"MINUTES",       UNITS_MINUTES },
531        {"PERCENT",       UNITS_PERCENT },
532        {"PERCENTAGE",    UNITS_PERCENT },
533        {"QUADRANTS",     UNITS_QUADRANTS },
534        {"QUADS",         UNITS_QUADRANTS },
535        {"YARDS",         UNITS_YARDS },
536        {NULL,            UNITS_NULL }
537   };
538   get_token();
539   int units = match_tok(utab, TABSIZE(utab));
540   if (units == UNITS_NULL) {
541      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown units “%s”*/35,
542                         s_str(&token));
543      return UNITS_NULL;
544   }
545   /* Survex has long misdefined "mils" as an alias for "grads", of which
546    * there are 400 in a circle.  There are several definitions of "mils"
547    * with a circle containing 2000π SI milliradians, 6400 NATO mils, 6000
548    * Warsaw Pact mils, and 6300 Swedish streck, and they aren't in common
549    * use by cave surveyors, so we now just warn if mils are used.
550    */
551   if (units == UNITS_DEPRECATED_ALIAS_FOR_GRADS) {
552      compile_diagnostic(DIAG_WARN|DIAG_TOKEN|DIAG_SKIP,
553                         /*Units “%s” are deprecated, assuming “grads” - see manual for details*/479,
554                         s_str(&token));
555      units = UNITS_GRADS;
556   }
557   if (units == UNITS_PERCENT && percent_ok &&
558       !(qmask & ~(BIT(Q_GRADIENT)|BIT(Q_BACKGRADIENT)))) {
559      return units;
560   }
561   if (units == UNITS_QUADRANTS &&
562       !(qmask & ~(BIT(Q_BEARING)|BIT(Q_BACKBEARING)))) {
563      return units;
564   }
565   if (((qmask & LEN_QMASK) && !TSTBIT(LEN_UMASK, units)) ||
566       ((qmask & ANG_QMASK) && !TSTBIT(ANG_UMASK, units))) {
567      /* TRANSLATORS: Note: In English you talk about the *units* of a single
568       * measurement, but the correct term in other languages may be singular.
569       */
570      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Invalid units “%s” for quantity*/37, s_str(&token));
571      return UNITS_NULL;
572   }
573   return units;
574}
575
576/* returns mask with bit x set to indicate quantity x specified */
577static unsigned long
578get_qlist(unsigned long mask_bad)
579{
580   static const sztok qtab[] = {
581        {"ALTITUDE",     Q_DZ },
582        {"BACKBEARING",  Q_BACKBEARING },
583        {"BACKCLINO",    Q_BACKGRADIENT },    /* alternative name */
584        {"BACKCOMPASS",  Q_BACKBEARING },     /* alternative name */
585        {"BACKGRADIENT", Q_BACKGRADIENT },
586        {"BACKLENGTH",   Q_BACKLENGTH },
587        {"BACKTAPE",     Q_BACKLENGTH },    /* alternative name */
588        {"BEARING",      Q_BEARING },
589        {"CEILING",      Q_UP },          /* alternative name */
590        {"CLINO",        Q_GRADIENT },    /* alternative name */
591        {"COMPASS",      Q_BEARING },     /* alternative name */
592        {"COUNT",        Q_COUNT },
593        {"COUNTER",      Q_COUNT },       /* alternative name */
594        {"DECLINATION",  Q_DECLINATION },
595        {"DEFAULT",      Q_DEFAULT }, /* not a real quantity... */
596        {"DEPTH",        Q_DEPTH },
597        {"DOWN",         Q_DOWN },
598        {"DX",           Q_DX },          /* alternative name */
599        {"DY",           Q_DY },          /* alternative name */
600        {"DZ",           Q_DZ },          /* alternative name */
601        {"EASTING",      Q_DX },
602        {"FLOOR",        Q_DOWN },        /* alternative name */
603        {"GRADIENT",     Q_GRADIENT },
604        {"LEFT",         Q_LEFT },
605        {"LENGTH",       Q_LENGTH },
606        {"LEVEL",        Q_LEVEL},
607        {"NORTHING",     Q_DY },
608        {"PLUMB",        Q_PLUMB},
609        {"POSITION",     Q_POS },
610        {"RIGHT",        Q_RIGHT },
611        {"TAPE",         Q_LENGTH },      /* alternative name */
612        {"UP",           Q_UP },
613        {NULL,           Q_NULL }
614   };
615   unsigned long qmask = 0;
616   int tok;
617   filepos fp;
618
619   while (1) {
620      get_pos(&fp);
621      get_token_legacy();
622      tok = match_tok(qtab, TABSIZE(qtab));
623      if (tok == Q_DEFAULT && !(mask_bad & BIT(Q_DEFAULT))) {
624          /* Only recognise DEFAULT if it is the first quantity, and then don't
625           * look for any more. */
626          if (qmask == 0)
627              return BIT(Q_DEFAULT);
628          break;
629      }
630      /* bail out if we reach the table end with no match */
631      if (tok == Q_NULL) break;
632      qmask |= BIT(tok);
633      if (qmask & mask_bad) {
634         compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown instrument “%s”*/39, s_str(&token));
635         return 0;
636      }
637   }
638
639   if (qmask == 0) {
640      /* TRANSLATORS: A "quantity" is something measured like "LENGTH",
641       * "BEARING", "ALTITUDE", etc. */
642      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown quantity “%s”*/34, s_str(&token));
643   } else {
644      set_pos(&fp);
645   }
646
647   return qmask;
648}
649
650#define SPECIAL_UNKNOWN 0
651static void
652cmd_set(void)
653{
654   static const sztok chartab[] = {
655        {"BLANK",     SPECIAL_BLANK },
656/*FIXME {"CLOSE",     SPECIAL_CLOSE }, */
657        {"COMMENT",   SPECIAL_COMMENT },
658        {"DECIMAL",   SPECIAL_DECIMAL },
659        {"EOL",       SPECIAL_EOL }, /* EOL won't work well */
660        {"KEYWORD",   SPECIAL_KEYWORD },
661        {"MINUS",     SPECIAL_MINUS },
662        {"NAMES",     SPECIAL_NAMES },
663        {"OMIT",      SPECIAL_OMIT },
664/*FIXME {"OPEN",      SPECIAL_OPEN }, */
665        {"PLUS",      SPECIAL_PLUS },
666#ifndef NO_DEPRECATED
667        {"ROOT",      SPECIAL_ROOT },
668#endif
669        {"SEPARATOR", SPECIAL_SEPARATOR },
670        {NULL,        SPECIAL_UNKNOWN }
671   };
672   int i;
673
674   get_token();
675   int mask = match_tok(chartab, TABSIZE(chartab));
676
677   if (mask == SPECIAL_UNKNOWN) {
678      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown character class “%s”*/42,
679                         s_str(&token));
680      return;
681   }
682
683#ifndef NO_DEPRECATED
684   if (mask == SPECIAL_ROOT) {
685      if (root_depr_count < 5) {
686         /* TRANSLATORS: Use of the ROOT character (which is "\" by default) is
687          * deprecated, so this error would be generated by:
688          *
689          * *equate \foo.7 1
690          *
691          * If you're unsure what "deprecated" means, see:
692          * https://en.wikipedia.org/wiki/Deprecation */
693         compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*ROOT is deprecated*/25);
694         if (++root_depr_count == 5)
695             /* TRANSLATORS: If you're unsure what "deprecated" means, see:
696              * https://en.wikipedia.org/wiki/Deprecation */
697            compile_diagnostic(DIAG_INFO, /*Further uses of this deprecated feature will not be reported*/95);
698      }
699   }
700#endif
701
702   /* if we're currently using an inherited translation table, allocate a new
703    * table, and copy old one into it */
704   if (pcs->next && pcs->next->Translate == pcs->Translate) {
705      short *p;
706      p = ((short*)osmalloc(sizeof(short) * 257)) + 1;
707      memcpy(p - 1, pcs->Translate - 1, sizeof(short) * 257);
708      pcs->Translate = p;
709   }
710
711   skipblanks();
712
713   /* clear this flag for all non-alphanums */
714   for (i = 0; i < 256; i++)
715      if (!isalnum(i)) pcs->Translate[i] &= ~mask;
716
717   /* now set this flag for all specified chars */
718   while (!isEol(ch)) {
719      int char_to_set;
720      if (!isalnum(ch)) {
721         char_to_set = ch;
722      } else if (tolower(ch) == 'x') {
723         int hex;
724         filepos fp;
725         get_pos(&fp);
726         nextch();
727         if (!isxdigit(ch)) {
728            set_pos(&fp);
729            break;
730         }
731         hex = isdigit(ch) ? ch - '0' : tolower(ch) - 'a';
732         nextch();
733         if (!isxdigit(ch)) {
734            set_pos(&fp);
735            break;
736         }
737         hex = hex << 4 | (isdigit(ch) ? ch - '0' : tolower(ch) - 'a');
738         char_to_set = hex;
739      } else {
740         break;
741      }
742      pcs->Translate[char_to_set] |= mask;
743      separator_map[char_to_set] |= mask;
744      nextch();
745   }
746
747   output_separator = find_output_separator();
748}
749
750void
751update_output_separator(void)
752{
753   output_separator = find_output_separator();
754}
755
756static void
757check_reentry(prefix *survey, const filepos* fpos_ptr)
758{
759   /* Don't try to check "*prefix \" or "*begin \" */
760   if (!survey->up) return;
761   if (TSTBIT(survey->sflags, SFLAGS_PREFIX_ENTERED)) {
762      static int reenter_depr_count = 0;
763      filepos fp_tmp;
764
765      if (reenter_depr_count >= 5)
766         return;
767
768      get_pos(&fp_tmp);
769      set_pos(fpos_ptr);
770      /* TRANSLATORS: The first of two warnings given when a survey which has
771       * already been completed is reentered.  This example file crawl.svx:
772       *
773       * *begin crawl     ; <- second warning here
774       * 1 2 9.45 234 -01
775       * *end crawl
776       * *begin crawl     ; <- first warning here
777       * 2 3 7.67 223 -03
778       * *end crawl
779       *
780       * Would lead to:
781       *
782       * crawl.svx:4:8: warning: Reentering an existing survey is deprecated
783       * crawl.svx:1: info: Originally entered here
784       *
785       * If you're unsure what "deprecated" means, see:
786       * https://en.wikipedia.org/wiki/Deprecation */
787      compile_diagnostic(DIAG_WARN|DIAG_WORD, /*Reentering an existing survey is deprecated*/29);
788      set_pos(&fp_tmp);
789      /* TRANSLATORS: The second of two warnings given when a survey which has
790       * already been completed is reentered.  This example file crawl.svx:
791       *
792       * *begin crawl     ; <- second warning here
793       * 1 2 9.45 234 -01
794       * *end crawl
795       * *begin crawl     ; <- first warning here
796       * 2 3 7.67 223 -03
797       * *end crawl
798       *
799       * Would lead to:
800       *
801       * crawl.svx:4:8: warning: Reentering an existing survey is deprecated
802       * crawl.svx:1: info: Originally entered here
803       *
804       * If you're unsure what "deprecated" means, see:
805       * https://en.wikipedia.org/wiki/Deprecation */
806      compile_diagnostic_pfx(DIAG_INFO, survey, /*Originally entered here*/30);
807      if (++reenter_depr_count == 5) {
808         /* After we've warned about 5 uses of the same deprecated feature, we
809          * give up for the rest of the current processing run.
810          *
811          * If you're unsure what "deprecated" means, see:
812          * https://en.wikipedia.org/wiki/Deprecation */
813         compile_diagnostic(DIAG_INFO, /*Further uses of this deprecated feature will not be reported*/95);
814      }
815   } else {
816      survey->sflags |= BIT(SFLAGS_PREFIX_ENTERED);
817      survey->filename = file.filename;
818      survey->line = file.line;
819   }
820}
821
822#ifndef NO_DEPRECATED
823static void
824cmd_prefix(void)
825{
826   static int prefix_depr_count = 0;
827   prefix *survey;
828   filepos fp;
829   /* Issue warning first, so "*prefix \" warns first that *prefix is
830    * deprecated and then that ROOT is...
831    */
832   if (prefix_depr_count < 5) {
833      /* TRANSLATORS: If you're unsure what "deprecated" means, see:
834       * https://en.wikipedia.org/wiki/Deprecation */
835      compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /**prefix is deprecated - use *begin and *end instead*/109);
836      if (++prefix_depr_count == 5)
837         compile_diagnostic(DIAG_INFO, /*Further uses of this deprecated feature will not be reported*/95);
838   }
839   get_pos(&fp);
840   survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT);
841   pcs->Prefix = survey;
842   check_reentry(survey, &fp);
843}
844#endif
845
846static void
847cmd_alias(void)
848{
849   /* Currently only two forms are supported:
850    * *alias station - ..
851    * *alias station -
852    */
853   get_token();
854   if (!S_EQ(&uctoken, "STATION")) {
855       compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_TOKEN, /*Bad *alias command*/397);
856       return;
857   }
858   get_word();
859   if (!S_EQ(&token, "-"))
860      goto bad_word;
861   get_word();
862   if (!s_empty(&token) && !S_EQ(&token, ".."))
863      goto bad_word;
864   pcs->dash_for_anon_wall_station = !s_empty(&token);
865   return;
866bad_word:
867   compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_TOKEN, /*Bad *alias command*/397);
868}
869
870static void
871cmd_begin(void)
872{
873   settings *pcsNew;
874
875   pcsNew = osnew(settings);
876   *pcsNew = *pcs; /* copy contents */
877   pcsNew->begin_lineno = file.line;
878   pcsNew->begin_lpos = file.lpos;
879   pcsNew->next = pcs;
880   pcs = pcsNew;
881
882   skipblanks();
883   pcs->begin_survey = NULL;
884   pcs->begin_col = 0;
885   if (!isEol(ch) && !isComm(ch)) {
886      filepos fp;
887      prefix *survey;
888      get_pos(&fp);
889      int begin_col = fp.offset - file.lpos;
890      survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT|PFX_WARN_SEPARATOR);
891      // read_prefix() might fail and longjmp() so only set begin_col if
892      // it succeeds.
893      pcs->begin_col = begin_col;
894      pcs->begin_survey = survey;
895      pcs->Prefix = survey;
896      check_reentry(survey, &fp);
897      f_export_ok = true;
898   }
899}
900
901void
902invalidate_pj_cached(void)
903{
904    /* Invalidate the cached PJ. */
905    if (pj_cached) {
906        proj_destroy(pj_cached);
907        pj_cached = NULL;
908    }
909}
910
911void
912report_declination(settings *p)
913{
914    if (p->min_declination <= p->max_declination) {
915        int y, m, d;
916        char range[128];
917        const char* deg_sign = msg(/*°*/344);
918        ymd_from_days_since_1900(p->min_declination_days, &y, &m, &d);
919        snprintf(range, sizeof(range),
920                 "%.1f%s @ %04d-%02d-%02d",
921                 deg(p->min_declination), deg_sign, y, m, d);
922        if (p->min_declination_days != p->max_declination_days) {
923            size_t len = strlen(range);
924            ymd_from_days_since_1900(p->max_declination_days, &y, &m, &d);
925            snprintf(range + len, sizeof(range) - len,
926                     " / %.1f%s @ %04d-%02d-%02d",
927                     deg(p->max_declination), deg_sign, y, m, d);
928        }
929        /* TRANSLATORS: This message gives information about the range of
930         * declination values and the grid convergence value calculated for
931         * each "*declination auto ..." command.
932         *
933         * The first %s will be replaced by the declination range (or single
934         * value), and %.1f%s by the grid convergence angle.
935         */
936        compile_diagnostic_at(DIAG_INFO, p->dec_filename, p->dec_line,
937                              /*Declination: %s, grid convergence: %.1f%s*/484,
938                              range,
939                              deg(p->convergence), deg_sign);
940        PUTC(' ', STDERR);
941        fputs(p->dec_context, STDERR);
942        fputnl(STDERR);
943        if (p->next && p->dec_context != p->next->dec_context)
944            free(p->dec_context);
945        p->dec_context = NULL;
946        p->min_declination = HUGE_VAL;
947        p->max_declination = -HUGE_VAL;
948    }
949}
950
951void
952pop_settings(void)
953{
954    settings * p = pcs;
955    pcs = pcs->next;
956
957    SVX_ASSERT(pcs);
958
959    if (pcs->dec_lat != p->dec_lat ||
960        pcs->dec_lon != p->dec_lon ||
961        pcs->dec_alt != p->dec_alt) {
962        report_declination(p);
963    } else {
964        pcs->min_declination_days = p->min_declination_days;
965        pcs->max_declination_days = p->max_declination_days;
966        pcs->min_declination = p->min_declination;
967        pcs->max_declination = p->max_declination;
968        pcs->convergence = p->convergence;
969    }
970
971    if (p->proj_str != pcs->proj_str) {
972        if (!p->proj_str || !pcs->proj_str ||
973            strcmp(p->proj_str, pcs->proj_str) != 0) {
974            invalidate_pj_cached();
975        }
976        /* free proj_str if not used by parent */
977        free(p->proj_str);
978    }
979
980    /* don't free default ordering or ordering used by parent */
981    if (p->ordering != default_order && p->ordering != pcs->ordering)
982        free((reading*)p->ordering);
983
984    /* free Translate if not used by parent */
985    if (p->Translate != pcs->Translate)
986        free(p->Translate - 1);
987
988    /* free meta if not used by parent, or in this block */
989    if (p->meta && p->meta != pcs->meta && p->meta->ref_count == 0)
990        free(p->meta);
991
992    free(p);
993}
994
995static void
996cmd_end(void)
997{
998   filepos fp;
999
1000   int begin_lineno = pcs->begin_lineno;
1001   if (begin_lineno == 0) {
1002      if (pcs->next == NULL) {
1003         /* more ENDs than BEGINs */
1004         /* TRANSLATORS: %s is replaced with e.g. BEGIN or .BOOK or #[ */
1005         compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*No matching %s*/192, "BEGIN");
1006      } else {
1007         /* TRANSLATORS: %s and %s are replaced with e.g. BEGIN and END
1008          * or END and BEGIN or #[ and #] */
1009         compile_diagnostic(DIAG_ERR|DIAG_SKIP,
1010                            /*%s with no matching %s in this file*/23, "END", "BEGIN");
1011      }
1012      return;
1013   }
1014
1015   prefix *begin_survey = pcs->begin_survey;
1016   long begin_lpos = pcs->begin_lpos;
1017   int begin_col = pcs->begin_col;
1018
1019   pop_settings();
1020
1021   /* note need to read using root *before* BEGIN */
1022   prefix *survey = NULL;
1023   skipblanks();
1024   if (!isEol(ch) && !isComm(ch)) {
1025      get_pos(&fp);
1026      survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT);
1027   }
1028
1029   if (survey != begin_survey) {
1030      filepos fp_save;
1031      get_pos(&fp_save);
1032      if (survey) {
1033         set_pos(&fp);
1034         if (!begin_survey) {
1035            /* TRANSLATORS: Used when a BEGIN command has no survey, but the
1036             * END command does, e.g.:
1037             *
1038             * *begin
1039             * 1 2 10.00 178 -01
1040             * *end entrance      <--[Message given here] */
1041            compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Matching BEGIN command has no survey name*/36);
1042         } else {
1043            /* TRANSLATORS: *BEGIN <survey> and *END <survey> should have the
1044             * same <survey> if it’s given at all */
1045            compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Survey name doesn’t match BEGIN*/193);
1046         }
1047      } else {
1048         /* TRANSLATORS: Used when a BEGIN command has a survey name, but the
1049          * END command omits it, e.g.:
1050          *
1051          * *begin entrance
1052          * 1 2 10.00 178 -01
1053          * *end     <--[Message given here] */
1054         compile_diagnostic(DIAG_WARN|DIAG_COL, /*Survey name omitted from END*/194);
1055      }
1056      parse file_save = file;
1057      file.line = begin_lineno;
1058      file.lpos = begin_lpos;
1059      int word_flag = 0;
1060      if (begin_col) {
1061          word_flag = DIAG_WORD;
1062          fseek(file.fh, begin_lpos + begin_col - 1, SEEK_SET);
1063          nextch();
1064      }
1065      compile_diagnostic(DIAG_INFO|word_flag, /*Corresponding %s was here*/22, "BEGIN");
1066      file = file_save;
1067      set_pos(&fp_save);
1068   }
1069}
1070
1071static void
1072cmd_entrance(void)
1073{
1074   prefix *pfx = read_prefix(PFX_STATION);
1075   pfx->sflags |= BIT(SFLAGS_ENTRANCE);
1076   pfx->sflags &= ~BIT(SFLAGS_UNUSED_FIXED_POINT);
1077}
1078
1079static const prefix * first_fix_name = NULL;
1080static const char * first_fix_filename;
1081static unsigned first_fix_line;
1082
1083static void
1084cmd_fix(void)
1085{
1086   static prefix *name_omit_already = NULL;
1087   static const char * name_omit_already_filename = NULL;
1088   static unsigned int name_omit_already_line;
1089   PJ_COORD coord;
1090   filepos fp_stn, fp;
1091
1092   get_pos(&fp_stn);
1093   prefix *fix_name = read_prefix(PFX_STATION|PFX_ALLOW_ROOT);
1094
1095   get_pos(&fp);
1096   get_token_legacy();
1097   bool reference = S_EQ(&uctoken, "REFERENCE");
1098   if (reference) {
1099      do_legacy_token_warning();
1100   } else {
1101      if (!s_empty(&uctoken)) set_pos(&fp);
1102   }
1103
1104   skipblanks();
1105   get_pos(&fp);
1106
1107   // If `REFERENCE` is specified the coordinates can't be omitted.
1108   coord.v[0] = read_numeric(!reference);
1109   if (coord.v[0] == HUGE_REAL) {
1110      /* If the end of the line isn't blank, read a number after all to
1111       * get a more helpful error message */
1112      if (!isEol(ch) && !isComm(ch)) coord.v[0] = read_numeric(false);
1113   }
1114   if (coord.v[0] == HUGE_REAL) {
1115      if (pcs->proj_str || proj_str_out) {
1116         compile_diagnostic(DIAG_ERR|DIAG_COL|DIAG_SKIP, /*Coordinates can't be omitted when coordinate system has been specified*/439);
1117         return;
1118      }
1119
1120      if (fix_name == name_omit_already) {
1121         compile_diagnostic(DIAG_WARN|DIAG_COL, /*Same station fixed twice with no coordinates*/61);
1122         return;
1123      }
1124
1125      if (name_omit_already) {
1126         /* TRANSLATORS: Emitted after second and subsequent "FIX" command
1127          * with no coordinates.
1128          */
1129         compile_diagnostic_at(DIAG_ERR,
1130                               name_omit_already_filename,
1131                               name_omit_already_line,
1132                               /*Already had FIX command with no coordinates for station “%s”*/441,
1133                               sprint_prefix(name_omit_already));
1134      } else {
1135         /* TRANSLATORS: " *fix a " gives this message: */
1136         compile_diagnostic(DIAG_INFO|DIAG_COL, /*FIX command with no coordinates - fixing at (0,0,0)*/54);
1137
1138         name_omit_already = fix_name;
1139         name_omit_already_filename = file.filename;
1140         name_omit_already_line = file.line;
1141      }
1142
1143      coord.v[0] = coord.v[1] = coord.v[2] = (real)0.0;
1144   } else {
1145      coord.v[1] = read_numeric(false);
1146      coord.v[2] = read_numeric(false);
1147
1148      if (pcs->proj_str && proj_str_out) {
1149         PJ *transform = pj_cached;
1150         if (!transform) {
1151             transform = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
1152                                                pcs->proj_str,
1153                                                proj_str_out,
1154                                                NULL);
1155             if (transform) {
1156                /* Normalise the output order so x is longitude and y latitude - by
1157                 * default new PROJ has them switched for EPSG:4326 which just seems
1158                 * confusing.
1159                 */
1160                PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX,
1161                                                               transform);
1162                proj_destroy(transform);
1163                transform = pj_norm;
1164             }
1165
1166             pj_cached = transform;
1167         }
1168
1169         if (proj_angular_input(transform, PJ_FWD)) {
1170            /* Input coordinate system expects radians. */
1171            coord.v[0] = rad(coord.v[0]);
1172            coord.v[1] = rad(coord.v[1]);
1173         }
1174
1175         coord.v[3] = HUGE_VAL;
1176         coord = proj_trans(transform, PJ_FWD, coord);
1177
1178         if (coord.v[0] == HUGE_VAL ||
1179             coord.v[1] == HUGE_VAL ||
1180             coord.v[2] == HUGE_VAL) {
1181            compile_diagnostic(DIAG_ERR|DIAG_FROM(fp),
1182                               /*Failed to convert coordinates: %s*/436,
1183                               proj_context_errno_string(PJ_DEFAULT_CTX,
1184                                                         proj_errno(transform)));
1185            /* Set dummy values which are finite. */
1186            coord.v[0] = coord.v[1] = coord.v[2] = 0;
1187         }
1188      } else if (pcs->proj_str) {
1189         compile_diagnostic(DIAG_ERR, /*The input projection is set but the output projection isn't*/437);
1190      } else if (proj_str_out) {
1191         compile_diagnostic(DIAG_ERR, /*The output projection is set but the input projection isn't*/438);
1192      }
1193
1194      get_pos(&fp);
1195      real sdx = read_numeric(true);
1196      if (sdx <= 0) {
1197          set_pos(&fp);
1198          compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
1199          return;
1200      }
1201      if (sdx != HUGE_REAL) {
1202         real sdy, sdz;
1203         real cxy = 0, cyz = 0, czx = 0;
1204         get_pos(&fp);
1205         sdy = read_numeric(true);
1206         if (sdy == HUGE_REAL) {
1207            /* only one variance given */
1208            sdy = sdz = sdx;
1209         } else {
1210            if (sdy <= 0) {
1211               set_pos(&fp);
1212               compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
1213               return;
1214            }
1215            get_pos(&fp);
1216            sdz = read_numeric(true);
1217            if (sdz == HUGE_REAL) {
1218               /* two variances given - horizontal & vertical */
1219               sdz = sdy;
1220               sdy = sdx;
1221            } else {
1222               if (sdz <= 0) {
1223                  set_pos(&fp);
1224                  compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
1225                  return;
1226               }
1227               cxy = read_numeric(true);
1228               if (cxy != HUGE_REAL) {
1229                  /* covariances given */
1230                  cyz = read_numeric(false);
1231                  czx = read_numeric(false);
1232               } else {
1233                  cxy = 0;
1234               }
1235            }
1236         }
1237         fix_station_with_variance(fix_name, coord.v,
1238                                   sdx * sdx, sdy * sdy, sdz * sdz
1239#ifndef NO_COVARIANCES
1240                                   , cxy, cyz, czx
1241#endif
1242                                  );
1243
1244         if (reference) {
1245             // `*fix reference` so suppress "unused fixed point" warning.
1246             fix_name->sflags &= ~BIT(SFLAGS_UNUSED_FIXED_POINT);
1247         }
1248         if (!first_fix_name) {
1249            /* We track if we've fixed a station yet, and if so what the name
1250             * of the first fix was, so that we can issue an error if the
1251             * output coordinate system is set after fixing a station. */
1252            first_fix_name = fix_name;
1253            first_fix_filename = file.filename;
1254            first_fix_line = file.line;
1255         }
1256
1257         return;
1258      }
1259   }
1260
1261   if (!first_fix_name) {
1262      /* We track if we've fixed a station yet, and if so what the name of the
1263       * first fix was, so that we can issue an error if the output coordinate
1264       * system is set after fixing a station. */
1265      first_fix_name = fix_name;
1266      first_fix_filename = file.filename;
1267      first_fix_line = file.line;
1268   }
1269
1270   int fix_result = fix_station(fix_name, coord.v);
1271   if (reference) {
1272       // `*fix reference` so suppress "unused fixed point" warning.
1273       fix_name->sflags &= ~BIT(SFLAGS_UNUSED_FIXED_POINT);
1274   }
1275   if (fix_result == 0) {
1276      return;
1277   }
1278
1279   get_pos(&fp);
1280   set_pos(&fp_stn);
1281   if (fix_result < 0) {
1282       compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Station already fixed or equated to a fixed point*/46);
1283   } else {
1284       /* TRANSLATORS: *fix a 1 2 3 / *fix a 1 2 3 */
1285       compile_diagnostic(DIAG_WARN|DIAG_WORD, /*Station already fixed at the same coordinates*/55);
1286   }
1287   compile_diagnostic_pfx(DIAG_INFO, fix_name, /*Previously fixed or equated here*/493);
1288   set_pos(&fp);
1289}
1290
1291static void
1292cmd_flags(void)
1293{
1294   static const sztok flagtab[] = {
1295        {"DUPLICATE", FLAGS_DUPLICATE },
1296        {"NOT",       FLAGS_NOT },
1297        {"SPLAY",     FLAGS_SPLAY },
1298        {"SURFACE",   FLAGS_SURFACE },
1299        {NULL,        FLAGS_UNKNOWN }
1300   };
1301   bool fNot = false;
1302   bool fEmpty = true;
1303   while (1) {
1304      int flag;
1305      get_token();
1306      /* If token is empty, it could mean end of line, or maybe
1307       * some non-alphanumeric junk which is better reported later */
1308      if (s_empty(&token)) break;
1309
1310      fEmpty = false;
1311      flag = match_tok(flagtab, TABSIZE(flagtab));
1312      /* treat the second NOT in "NOT NOT" as an unknown flag */
1313      if (flag == FLAGS_UNKNOWN || (fNot && flag == FLAGS_NOT)) {
1314         compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*FLAG “%s” unknown*/68,
1315                            s_str(&token));
1316         /* Recover from “*FLAGS NOT BOGUS SURFACE” by ignoring "NOT BOGUS" */
1317         fNot = false;
1318      } else if (flag == FLAGS_NOT) {
1319         fNot = true;
1320      } else if (fNot) {
1321         pcs->flags &= ~BIT(flag);
1322         fNot = false;
1323      } else {
1324         pcs->flags |= BIT(flag);
1325      }
1326   }
1327
1328   if (fNot) {
1329      compile_diagnostic(DIAG_ERR|DIAG_TOKEN,
1330                         /*Expecting “%s”, “%s”, or “%s”*/188,
1331                         "DUPLICATE", "SPLAY", "SURFACE");
1332   } else if (fEmpty) {
1333      compile_diagnostic(DIAG_ERR|DIAG_TOKEN,
1334                         /*Expecting “%s”, “%s”, “%s”, or “%s”*/189,
1335                         "NOT", "DUPLICATE", "SPLAY", "SURFACE");
1336   }
1337}
1338
1339static void
1340cmd_equate(void)
1341{
1342   filepos fp;
1343   get_pos(&fp);
1344   prefix *prev_name = NULL;
1345   prefix *name = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_SUSPECT_TYPO);
1346   while (true) {
1347      if (!name->stn || !fixed(name->stn)) {
1348          // If the station isn't already fixed, make its file:line location
1349          // reflect this *equate.
1350          name->filename = file.filename;
1351          name->line = file.line;
1352      }
1353      skipblanks();
1354      if (isEol(ch) || isComm(ch)) {
1355         if (prev_name == NULL) {
1356            /* E.g. *equate entrance.6 */
1357            set_pos(&fp);
1358            /* TRANSLATORS: EQUATE is a command name, so shouldn’t be
1359             * translated.
1360             *
1361             * Here "station" is a survey station, not a train station.
1362             */
1363            compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_WORD, /*Only one station in EQUATE command*/33);
1364         }
1365         return;
1366      }
1367
1368      prev_name = name;
1369      name = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_SUSPECT_TYPO);
1370      process_equate(prev_name, name);
1371   }
1372}
1373
1374static void
1375report_missing_export(prefix *pfx, int depth)
1376{
1377   char *s;
1378   const char *p;
1379   prefix *survey = pfx;
1380   int i;
1381   for (i = depth + 1; i; i--) {
1382      survey = survey->up;
1383      SVX_ASSERT(survey);
1384   }
1385   s = osstrdup(sprint_prefix(survey));
1386   p = sprint_prefix(pfx);
1387   if (survey->filename) {
1388      /* TRANSLATORS: A station must be exported out of each level it is in, so
1389       * this would give "Station “\outer.inner.1” not exported from survey
1390       * “\outer”)":
1391       *
1392       * *equate entrance outer.inner.1
1393       * *begin outer
1394       * *begin inner
1395       * *export 1
1396       * 1 2 1.23 045 -6
1397       * *end inner
1398       * *end outer
1399       *
1400       * Here "survey" is a "cave map" rather than list of questions - it should be
1401       * translated to the terminology that cavers using the language would use.
1402       */
1403      compile_diagnostic_pfx(DIAG_ERR, survey,
1404                             /*Station “%s” not exported from survey “%s”*/26, p, s);
1405   } else {
1406      compile_diagnostic(DIAG_ERR, /*Station “%s” not exported from survey “%s”*/26, p, s);
1407   }
1408   free(s);
1409}
1410
1411static void
1412cmd_export(void)
1413{
1414   prefix *pfx;
1415
1416   fExportUsed = true;
1417   do {
1418      int depth = 0;
1419      pfx = read_prefix(PFX_STATION);
1420      {
1421         prefix *p = pfx;
1422         while (p != NULL && p != pcs->Prefix) {
1423            depth++;
1424            p = p->up;
1425         }
1426         /* Something like: *export \foo, but we've excluded use of root */
1427         SVX_ASSERT(p);
1428      }
1429      /* *export \ or similar bogus stuff */
1430      SVX_ASSERT(depth);
1431#if 0
1432      printf("C min %d max %d depth %d pfx %s\n",
1433             pfx->min_export, pfx->max_export, depth, sprint_prefix(pfx));
1434#endif
1435      if (pfx->min_export == 0) {
1436         /* not encountered *export for this name before */
1437         if (pfx->max_export > depth) report_missing_export(pfx, depth);
1438         pfx->min_export = pfx->max_export = depth;
1439      } else if (pfx->min_export != USHRT_MAX) {
1440         /* FIXME: what to do if a station is marked for inferred exports
1441          * but is then explicitly exported?  Currently we just ignore the
1442          * explicit export... */
1443         if (pfx->min_export - 1 > depth) {
1444            report_missing_export(pfx, depth);
1445         } else if (pfx->min_export - 1 < depth) {
1446            /* TRANSLATORS: Here "station" is a survey station, not a train station.
1447             *
1448             * Exporting a station twice gives this error:
1449             *
1450             * *begin example
1451             * *export 1
1452             * *export 1
1453             * 1 2 1.24 045 -6
1454             * *end example */
1455            compile_diagnostic(DIAG_ERR, /*Station “%s” already exported*/66,
1456                               sprint_prefix(pfx));
1457         }
1458         pfx->min_export = depth;
1459      }
1460      skipblanks();
1461   } while (!isEol(ch) && !isComm(ch));
1462}
1463
1464static void
1465cmd_data(void)
1466{
1467   static const sztok dtab[] = {
1468        {"ALTITUDE",     Dz },
1469        {"BACKBEARING",  BackComp },
1470        {"BACKCLINO",    BackClino }, /* alternative name */
1471        {"BACKCOMPASS",  BackComp }, /* alternative name */
1472        {"BACKGRADIENT", BackClino },
1473        {"BACKLENGTH",   BackTape },
1474        {"BACKTAPE",     BackTape }, /* alternative name */
1475        {"BEARING",      Comp },
1476        {"CEILING",      Up }, /* alternative name */
1477        {"CLINO",        Clino }, /* alternative name */
1478        {"COMPASS",      Comp }, /* alternative name */
1479        {"COUNT",        Count }, /* FrCount&ToCount in multiline */
1480        {"DEPTH",        Depth }, /* FrDepth&ToDepth in multiline */
1481        {"DEPTHCHANGE",  DepthChange },
1482        {"DIRECTION",    Dir },
1483        {"DOWN",         Down },
1484        {"DX",           Dx },
1485        {"DY",           Dy },
1486        {"DZ",           Dz },
1487        {"EASTING",      Dx },
1488        {"FLOOR",        Down }, /* alternative name */
1489        {"FROM",         Fr },
1490        {"FROMCOUNT",    FrCount },
1491        {"FROMDEPTH",    FrDepth },
1492        {"GRADIENT",     Clino },
1493        {"IGNORE",       Ignore },
1494        {"IGNOREALL",    IgnoreAll },
1495        {"LEFT",         Left },
1496        {"LENGTH",       Tape },
1497        {"NEWLINE",      Newline },
1498        {"NORTHING",     Dy },
1499        {"RIGHT",        Right },
1500        {"STATION",      Station }, /* Fr&To in multiline */
1501        {"TAPE",         Tape }, /* alternative name */
1502        {"TO",           To },
1503        {"TOCOUNT",      ToCount },
1504        {"TODEPTH",      ToDepth },
1505        {"UP",           Up },
1506        {NULL,           End }
1507   };
1508
1509#define MASK_stns BIT(Fr) | BIT(To) | BIT(Station)
1510#define MASK_tape BIT(Tape) | BIT(BackTape) | BIT(FrCount) | BIT(ToCount) | BIT(Count)
1511#define MASK_dpth BIT(FrDepth) | BIT(ToDepth) | BIT(Depth) | BIT(DepthChange)
1512#define MASK_comp BIT(Comp) | BIT(BackComp)
1513#define MASK_clin BIT(Clino) | BIT(BackClino)
1514
1515#define MASK_NORMAL MASK_stns | BIT(Dir) | MASK_tape | MASK_comp | MASK_clin
1516#define MASK_DIVING MASK_NORMAL | MASK_dpth
1517#define MASK_CARTESIAN MASK_stns | BIT(Dx) | BIT(Dy) | BIT(Dz)
1518#define MASK_CYLPOLAR  MASK_stns | BIT(Dir) | MASK_tape | MASK_comp | MASK_dpth
1519#define MASK_NOSURVEY MASK_stns
1520#define MASK_PASSAGE BIT(Station) | BIT(Left) | BIT(Right) | BIT(Up) | BIT(Down)
1521#define MASK_IGNORE 0 // No readings in this style.
1522
1523   // readings which may be given for each style (index is STYLE_*)
1524   static const unsigned long mask[] = {
1525      MASK_NORMAL, MASK_DIVING, MASK_CARTESIAN, MASK_CYLPOLAR, MASK_NOSURVEY,
1526      MASK_PASSAGE, MASK_IGNORE
1527   };
1528
1529   // readings which may be omitted for each style (index is STYLE_*)
1530   static const unsigned long mask_optional[] = {
1531      BIT(Dir) | BIT(Clino) | BIT(BackClino),
1532      BIT(Dir) | BIT(Clino) | BIT(BackClino),
1533      0,
1534      BIT(Dir),
1535      0,
1536      0, /* BIT(Left) | BIT(Right) | BIT(Up) | BIT(Down), */
1537      0
1538   };
1539
1540   /* all valid readings */
1541   static const unsigned long mask_all[] = {
1542      MASK_NORMAL | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1543      MASK_DIVING | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1544      MASK_CARTESIAN | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1545      MASK_CYLPOLAR | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1546      MASK_NOSURVEY | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1547      MASK_PASSAGE | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1548      MASK_IGNORE
1549   };
1550#define STYLE_DEFAULT   -2
1551#define STYLE_UNKNOWN   -1
1552
1553   static const sztok styletab[] = {
1554        {"CARTESIAN",    STYLE_CARTESIAN },
1555        {"CYLPOLAR",     STYLE_CYLPOLAR },
1556        {"DEFAULT",      STYLE_DEFAULT },
1557        {"DIVING",       STYLE_DIVING },
1558        {"IGNORE",       STYLE_IGNORE },
1559        {"NORMAL",       STYLE_NORMAL },
1560        {"NOSURVEY",     STYLE_NOSURVEY },
1561        {"PASSAGE",      STYLE_PASSAGE },
1562        {"TOPOFIL",      STYLE_NORMAL },
1563        {NULL,           STYLE_UNKNOWN }
1564   };
1565
1566#define m_multi (BIT(Station) | BIT(Count) | BIT(Depth))
1567
1568   int style, k = 0;
1569   reading d;
1570   unsigned long mUsed = 0;
1571   int old_style = pcs->style;
1572
1573   /* after a bad *data command ignore survey data until the next
1574    * *data command to avoid an avalanche of errors */
1575   pcs->recorded_style = pcs->style = STYLE_IGNORE;
1576
1577   get_token();
1578   style = match_tok(styletab, TABSIZE(styletab));
1579
1580   if (style == STYLE_DEFAULT) {
1581      default_style(pcs);
1582      return;
1583   }
1584
1585   if (style == STYLE_IGNORE) {
1586      return;
1587   }
1588
1589   if (style == STYLE_UNKNOWN) {
1590      if (s_empty(&token)) {
1591         /* "*data" without arguments reinitialises the current style - useful
1592          * when using *data passage as it provides a way to break the passage
1593          * tube without having to repeat the full *data passage command.
1594          */
1595         pcs->recorded_style = pcs->style = style = old_style;
1596         goto reinit_style;
1597      }
1598      /* TRANSLATORS: e.g. trying to refer to an invalid FNORD data style */
1599      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Data style “%s” unknown*/65, s_str(&token));
1600      return;
1601   }
1602
1603   skipblanks();
1604#ifndef NO_DEPRECATED
1605   /* Olde syntax had optional field for survey grade, so allow an omit
1606    * but issue a warning about it */
1607   if (isOmit(ch)) {
1608      static int data_depr_count = 0;
1609      if (data_depr_count < 5) {
1610         compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*“*data %s %c …” is deprecated - use “*data %s …” instead*/104,
1611                            s_str(&token), ch, s_str(&token));
1612         if (++data_depr_count == 5)
1613            compile_diagnostic(DIAG_INFO, /*Further uses of this deprecated feature will not be reported*/95);
1614      }
1615      nextch();
1616   }
1617#endif
1618
1619   int kMac = 6; /* minimum for NORMAL style */
1620   reading *new_order = osmalloc(kMac * sizeof(reading));
1621   char *style_name = s_steal(&token);
1622   do {
1623      filepos fp;
1624      get_pos(&fp);
1625      get_token();
1626      d = match_tok(dtab, TABSIZE(dtab));
1627      if (d == End && !s_empty(&token)) {
1628         compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP,
1629                            /*Reading “%s” not allowed in data style “%s”*/63,
1630                            s_str(&token), style_name);
1631         free(style_name);
1632         free(new_order);
1633         return;
1634      }
1635
1636      /* only token allowed after IGNOREALL is NEWLINE */
1637      if (k && new_order[k - 1] == IgnoreAll && d != Newline) {
1638         set_pos(&fp);
1639         break;
1640      }
1641      /* Note: an unknown token is reported as trailing garbage */
1642      if (!TSTBIT(mask_all[style], d)) {
1643         /* TRANSLATORS: a data "style" is something like NORMAL, DIVING, etc.
1644          * a "reading" is one of FROM, TO, TAPE, COMPASS, CLINO for NORMAL
1645          * style.  Neither "style" nor "reading" is a keyword in the program.
1646          *
1647          * This error complains about a "DEPTH" gauge reading in "NORMAL"
1648          * style, for example.
1649          */
1650         compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP,
1651                            /*Reading “%s” not allowed in data style “%s”*/63,
1652                            s_str(&token), style_name);
1653         free(style_name);
1654         free(new_order);
1655         return;
1656      }
1657      if (TSTBIT(mUsed, Newline) && TSTBIT(m_multi, d)) {
1658         /* TRANSLATORS: caused by e.g.
1659          *
1660          * *data diving station newline depth tape compass
1661          *
1662          * ("depth" needs to occur before "newline"). */
1663         compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP,
1664                            /*Reading “%s” must occur before NEWLINE*/225, s_str(&token));
1665         free(style_name);
1666         free(new_order);
1667         return;
1668      }
1669      /* Check for duplicates unless it's a special reading:
1670       *   IGNOREALL,IGNORE (duplicates allowed) ; END (not possible)
1671       */
1672      if (!((BIT(Ignore) | BIT(End) | BIT(IgnoreAll)) & BIT(d))) {
1673         if (TSTBIT(mUsed, d)) {
1674            /* TRANSLATORS: complains about a situation like trying to define
1675             * two from stations per leg */
1676            compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Duplicate reading “%s”*/67, s_str(&token));
1677            free(style_name);
1678            free(new_order);
1679            return;
1680         } else {
1681            /* Check for previously listed readings which are incompatible
1682             * with this one - e.g. Count vs FrCount */
1683            bool fBad = false;
1684            switch (d) {
1685             case Station:
1686               if (mUsed & (BIT(Fr) | BIT(To))) fBad = true;
1687               break;
1688             case Fr: case To:
1689               if (TSTBIT(mUsed, Station)) fBad = true;
1690               break;
1691             case Count:
1692               if (mUsed & (BIT(FrCount) | BIT(ToCount) | BIT(Tape)))
1693                  fBad = true;
1694               break;
1695             case FrCount: case ToCount:
1696               if (mUsed & (BIT(Count) | BIT(Tape)))
1697                  fBad = true;
1698               break;
1699             case Depth:
1700               if (mUsed & (BIT(FrDepth) | BIT(ToDepth) | BIT(DepthChange)))
1701                  fBad = true;
1702               break;
1703             case FrDepth: case ToDepth:
1704               if (mUsed & (BIT(Depth) | BIT(DepthChange))) fBad = true;
1705               break;
1706             case DepthChange:
1707               if (mUsed & (BIT(FrDepth) | BIT(ToDepth) | BIT(Depth)))
1708                  fBad = true;
1709               break;
1710             case Newline:
1711               if (mUsed & ~m_multi) {
1712                  /* TRANSLATORS: e.g.
1713                   *
1714                   * *data normal from to tape newline compass clino */
1715                  compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*NEWLINE can only be preceded by STATION, DEPTH, and COUNT*/226);
1716                  free(style_name);
1717                  free(new_order);
1718                  return;
1719               }
1720               if (k == 0) {
1721                  /* TRANSLATORS: error from:
1722                   *
1723                   * *data normal newline from to tape compass clino */
1724                  compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*NEWLINE can’t be the first reading*/222);
1725                  free(style_name);
1726                  free(new_order);
1727                  return;
1728               }
1729               break;
1730             default: /* avoid compiler warnings about unhandled enums */
1731               break;
1732            }
1733            if (fBad) {
1734               /* Not entirely happy with phrasing this... */
1735               /* TRANSLATORS: This is an error from the *DATA command.  It
1736                * means that a reading (which will appear where %s is isn't
1737                * valid as the list of readings has already included the same
1738                * reading, or an equivalent one (e.g. you can't have both
1739                * DEPTH and DEPTHCHANGE together). */
1740               compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Reading “%s” duplicates previous reading(s)*/77,
1741                                  s_str(&token));
1742               free(style_name);
1743               free(new_order);
1744               return;
1745            }
1746            mUsed |= BIT(d); /* used to catch duplicates */
1747         }
1748      }
1749      if (k && new_order[k - 1] == IgnoreAll) {
1750         SVX_ASSERT(d == Newline);
1751         k--;
1752         d = IgnoreAllAndNewLine;
1753      }
1754      if (k >= kMac) {
1755         kMac = kMac * 2;
1756         new_order = osrealloc(new_order, kMac * sizeof(reading));
1757      }
1758      new_order[k++] = d;
1759   } while (d != End);
1760
1761   if (k >= 2 && new_order[k - 2] == Newline) {
1762      /* TRANSLATORS: error from:
1763       *
1764       * *data normal from to tape compass clino newline */
1765      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*NEWLINE can’t be the last reading*/223);
1766      free(style_name);
1767      free(new_order);
1768      return;
1769   }
1770
1771   if (style == STYLE_NOSURVEY) {
1772      if (TSTBIT(mUsed, Station)) {
1773         if (k >= kMac) {
1774            kMac = kMac * 2;
1775            new_order = osrealloc(new_order, kMac * sizeof(reading));
1776         }
1777         new_order[k - 1] = Newline;
1778         new_order[k++] = End;
1779      }
1780   } else if (style == STYLE_PASSAGE) {
1781      /* Station doesn't mean "multiline" for STYLE_PASSAGE. */
1782   } else if (!TSTBIT(mUsed, Newline) && (m_multi & mUsed)) {
1783      /* TRANSLATORS: Error given by something like:
1784       *
1785       * *data normal station tape compass clino
1786       *
1787       * ("station" signifies interleaved data). */
1788      compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Interleaved readings, but no NEWLINE*/224);
1789      free(style_name);
1790      free(new_order);
1791      return;
1792   }
1793
1794#if 0
1795   printf("mUsed = 0x%x\n", mUsed);
1796#endif
1797
1798   /* Check the supplied readings form a sufficient set. */
1799   if (style != STYLE_PASSAGE) {
1800       if ((mUsed & (BIT(Fr) | BIT(To))) == (BIT(Fr) | BIT(To)))
1801           mUsed |= BIT(Station);
1802       else if (TSTBIT(mUsed, Station))
1803           mUsed |= BIT(Fr) | BIT(To);
1804   }
1805
1806   if (mUsed & (BIT(Comp) | BIT(BackComp)))
1807      mUsed |= BIT(Comp) | BIT(BackComp);
1808
1809   if (mUsed & (BIT(Clino) | BIT(BackClino)))
1810      mUsed |= BIT(Clino) | BIT(BackClino);
1811
1812   if ((mUsed & (BIT(FrDepth) | BIT(ToDepth))) == (BIT(FrDepth) | BIT(ToDepth)))
1813      mUsed |= BIT(Depth) | BIT(DepthChange);
1814   else if (mUsed & (BIT(Depth) | BIT(DepthChange)))
1815      mUsed |= BIT(FrDepth) | BIT(ToDepth) | BIT(Depth) | BIT(DepthChange);
1816
1817   if ((mUsed & (BIT(FrCount) | BIT(ToCount))) == (BIT(FrCount) | BIT(ToCount)))
1818      mUsed |= BIT(Count) | BIT(Tape) | BIT(BackTape);
1819   else if (mUsed & (BIT(Count) | BIT(Tape) | BIT(BackTape)))
1820      mUsed |= BIT(FrCount) | BIT(ToCount) | BIT(Count) | BIT(Tape) | BIT(BackTape);
1821
1822#if 0
1823   printf("mUsed = 0x%x, opt = 0x%x, mask = 0x%x\n", mUsed,
1824          mask_optional[style], mask[style]);
1825#endif
1826
1827   if (((mUsed &~ BIT(Newline)) | mask_optional[style]) != mask[style]) {
1828      /* Test should only fail with too few bits set, not too many */
1829      SVX_ASSERT((((mUsed &~ BIT(Newline)) | mask_optional[style])
1830              &~ mask[style]) == 0);
1831      /* TRANSLATORS: i.e. not enough readings for the style. */
1832      compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Too few readings for data style “%s”*/64, style_name);
1833      free(style_name);
1834      free(new_order);
1835      return;
1836   }
1837
1838   /* don't free default ordering or ordering used by parent */
1839   if (pcs->ordering != default_order &&
1840       !(pcs->next && pcs->next->ordering == pcs->ordering))
1841      free((reading*)pcs->ordering);
1842
1843   pcs->recorded_style = pcs->style = style;
1844   pcs->ordering = new_order;
1845
1846   free(style_name);
1847
1848reinit_style:
1849   if (style == STYLE_PASSAGE) {
1850      lrudlist * new_psg = osnew(lrudlist);
1851      new_psg->tube = NULL;
1852      new_psg->next = model;
1853      model = new_psg;
1854      next_lrud = &(new_psg->tube);
1855   }
1856}
1857
1858static void
1859cmd_units(void)
1860{
1861   int units, quantity;
1862   unsigned long qmask;
1863   unsigned long m; /* mask with bit x set to indicate quantity x specified */
1864   real factor;
1865   filepos fp;
1866
1867   qmask = get_qlist(BIT(Q_POS)|BIT(Q_PLUMB)|BIT(Q_LEVEL));
1868
1869   if (!qmask) return;
1870   if (qmask == BIT(Q_DEFAULT)) {
1871      default_units(pcs);
1872      return;
1873   }
1874
1875   get_pos(&fp);
1876   factor = read_numeric(true);
1877   if (factor == 0.0) {
1878      set_pos(&fp);
1879      /* TRANSLATORS: error message given by "*units tape 0 feet" - it’s
1880       * meaningless to say your tape is marked in "0 feet" (but you might
1881       * measure distance by counting knots on a diving line, and tie them
1882       * every "2 feet"). */
1883      compile_diagnostic(DIAG_ERR|DIAG_WORD, /**UNITS factor must be non-zero*/200);
1884      skipline();
1885      return;
1886   }
1887
1888   units = get_units(qmask, true);
1889   if (units == UNITS_NULL) return;
1890   if (TSTBIT(qmask, Q_GRADIENT))
1891      pcs->f_clino_percent = (units == UNITS_PERCENT);
1892   if (TSTBIT(qmask, Q_BACKGRADIENT))
1893      pcs->f_backclino_percent = (units == UNITS_PERCENT);
1894
1895   if (TSTBIT(qmask, Q_BEARING)) {
1896      pcs->f_bearing_quadrants = (units == UNITS_QUADRANTS);
1897   }
1898   if (TSTBIT(qmask, Q_BACKBEARING)) {
1899      pcs->f_backbearing_quadrants = (units == UNITS_QUADRANTS);
1900   }
1901
1902   if (factor == HUGE_REAL) {
1903      factor = factor_tab[units];
1904   } else {
1905      factor *= factor_tab[units];
1906   }
1907
1908   for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1)
1909      if (qmask & m) pcs->units[quantity] = factor;
1910}
1911
1912static void
1913cmd_calibrate(void)
1914{
1915   real sc, z;
1916   unsigned long qmask, m;
1917   int quantity;
1918   filepos fp;
1919
1920   qmask = get_qlist(BIT(Q_POS)|BIT(Q_PLUMB)|BIT(Q_LEVEL));
1921   if (!qmask) return; /* error already reported */
1922
1923   if (qmask == BIT(Q_DEFAULT)) {
1924      default_calib(pcs);
1925      return;
1926   }
1927
1928   if (((qmask & LEN_QMASK)) && ((qmask & ANG_QMASK))) {
1929      /* TRANSLATORS: e.g.
1930       *
1931       * *calibrate tape compass 1 1
1932       */
1933      compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Can’t calibrate angular and length quantities together*/227);
1934      return;
1935   }
1936
1937   z = read_numeric(false);
1938   get_pos(&fp);
1939   sc = read_numeric(true);
1940   if (sc == HUGE_REAL) {
1941      if (isalpha(ch)) {
1942         int units = get_units(qmask, false);
1943         if (units == UNITS_NULL) {
1944            return;
1945         }
1946         z *= factor_tab[units];
1947         sc = read_numeric(true);
1948         if (sc == HUGE_REAL) {
1949            sc = (real)1.0;
1950         } else {
1951            /* Adjustment applied is: (reading - z) * sc
1952             * We want: reading * sc - z
1953             * So divide z by sc so the applied adjustment does what we want.
1954             */
1955            z /= sc;
1956         }
1957      } else {
1958         sc = (real)1.0;
1959      }
1960   }
1961
1962   if (sc == HUGE_REAL) sc = (real)1.0;
1963   /* check for declination scale */
1964   if (TSTBIT(qmask, Q_DECLINATION) && sc != 1.0) {
1965      set_pos(&fp);
1966      /* TRANSLATORS: DECLINATION is a built-in keyword, so best not to
1967       * translate */
1968      compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Scale factor must be 1.0 for DECLINATION*/40);
1969      skipline();
1970      return;
1971   }
1972   if (sc == 0.0) {
1973      set_pos(&fp);
1974      /* TRANSLATORS: If the scale factor for an instrument is zero, then any
1975       * reading would be mapped to zero, which doesn't make sense. */
1976      compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Scale factor must be non-zero*/391);
1977      skipline();
1978      return;
1979   }
1980   for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1) {
1981      if (qmask & m) {
1982         pcs->z[quantity] = pcs->units[quantity] * z;
1983         pcs->sc[quantity] = sc;
1984      }
1985   }
1986}
1987
1988static const sztok north_tab[] = {
1989     { "GRID",          GRID_NORTH },
1990     { "MAGNETIC",      MAGNETIC_NORTH },
1991     { "TRUE",          TRUE_NORTH },
1992     { NULL,            -1 }
1993};
1994
1995static void
1996cmd_cartesian(void)
1997{
1998    get_token();
1999    int north = match_tok(north_tab, TABSIZE(north_tab));
2000    if (north < 0) {
2001        compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP,
2002                           /*Expecting “%s”, “%s”, or “%s”*/188,
2003                           "GRID", "MAGNETIC", "TRUE");
2004        return;
2005    }
2006    pcs->cartesian_north = north;
2007    pcs->cartesian_rotation = 0.0;
2008
2009    skipblanks();
2010    if (!isEol(ch) && !isComm(ch)) {
2011        real rotation = read_numeric(false);
2012        // Accept the same units as *declination does.
2013        int units = get_units(BIT(Q_DECLINATION), false);
2014        if (units == UNITS_NULL) {
2015            return;
2016        }
2017        pcs->cartesian_rotation = rotation * factor_tab[units];
2018    }
2019}
2020
2021static void
2022cmd_declination(void)
2023{
2024    real v = read_numeric(true);
2025    if (v == HUGE_REAL) {
2026        get_token_legacy_no_blanks();
2027        if (!S_EQ(&uctoken, "AUTO")) {
2028            compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_COL, /*Expected number or “AUTO”*/309);
2029            return;
2030        }
2031        do_legacy_token_warning();
2032        if (!pcs->proj_str) {
2033            // TRANSLATORS: %s is replaced by the command that requires it, e.g.
2034            // *DECLINATION AUTO
2035            compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_TOKEN,
2036                               /*Input coordinate system must be specified for “%s”*/301,
2037                               "*DECLINATION AUTO");
2038            return;
2039        }
2040
2041        /* *declination auto X Y Z */
2042        filepos fp;
2043        get_pos(&fp);
2044        real x = read_numeric(false);
2045        real y = read_numeric(false);
2046        real z = read_numeric(false);
2047        set_declination_location(x, y, z, pcs->proj_str, &fp);
2048    } else {
2049        /* *declination D UNITS */
2050        int units = get_units(BIT(Q_DECLINATION), false);
2051        if (units == UNITS_NULL) {
2052            return;
2053        }
2054        pcs->z[Q_DECLINATION] = -v * factor_tab[units];
2055        pcs->convergence = 0;
2056    }
2057}
2058
2059#ifndef NO_DEPRECATED
2060static void
2061cmd_default(void)
2062{
2063   static const sztok defaulttab[] = {
2064      { "CALIBRATE", CMD_CALIBRATE },
2065      { "DATA",      CMD_DATA },
2066      { "UNITS",     CMD_UNITS },
2067      { NULL,        CMD_NULL }
2068   };
2069   static int default_depr_count = 0;
2070
2071   if (default_depr_count < 5) {
2072      /* TRANSLATORS: If you're unsure what "deprecated" means, see:
2073       * https://en.wikipedia.org/wiki/Deprecation */
2074      compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /**DEFAULT is deprecated - use *CALIBRATE/DATA/SD/UNITS with argument DEFAULT instead*/20);
2075      if (++default_depr_count == 5)
2076         compile_diagnostic(DIAG_INFO, /*Further uses of this deprecated feature will not be reported*/95);
2077   }
2078
2079   get_token();
2080   switch (match_tok(defaulttab, TABSIZE(defaulttab))) {
2081    case CMD_CALIBRATE:
2082      default_calib(pcs);
2083      break;
2084    case CMD_DATA:
2085      default_style(pcs);
2086      default_grade(pcs);
2087      pcs->cartesian_north = TRUE_NORTH;
2088      pcs->cartesian_rotation = 0.0;
2089      break;
2090    case CMD_UNITS:
2091      default_units(pcs);
2092      break;
2093    default:
2094      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown setting “%s”*/41,
2095                         s_str(&token));
2096   }
2097}
2098#endif
2099
2100static void
2101cmd_include(void)
2102{
2103   char *pth = NULL;
2104   string fnm = S_INIT;
2105#ifndef NO_DEPRECATED
2106   prefix *root_store;
2107#endif
2108   int ch_store;
2109
2110   pth = path_from_fnm(file.filename);
2111
2112   read_string(&fnm);
2113
2114#ifndef NO_DEPRECATED
2115   /* Since *begin / *end nesting cannot cross file boundaries we only
2116    * need to preserve the prefix if the deprecated *prefix command
2117    * can be used */
2118   root_store = root;
2119   root = pcs->Prefix; /* Root for include file is current prefix */
2120#endif
2121   ch_store = ch;
2122
2123   data_file(pth, s_str(&fnm));
2124
2125#ifndef NO_DEPRECATED
2126   root = root_store; /* and restore root */
2127#endif
2128   ch = ch_store;
2129
2130   s_free(&fnm);
2131   free(pth);
2132}
2133
2134static void
2135cmd_sd(void)
2136{
2137   real sd, variance;
2138   int units;
2139   unsigned long qmask, m;
2140   int quantity;
2141   qmask = get_qlist(BIT(Q_DECLINATION));
2142   if (!qmask) return; /* no quantities found - error already reported */
2143
2144   if (qmask == BIT(Q_DEFAULT)) {
2145      default_grade(pcs);
2146      return;
2147   }
2148   sd = read_numeric(false);
2149   if (sd <= (real)0.0) {
2150      compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_COL, /*Standard deviation must be positive*/48);
2151      return;
2152   }
2153   units = get_units(qmask, false);
2154   if (units == UNITS_NULL) return;
2155
2156   sd *= factor_tab[units];
2157   variance = sqrd(sd);
2158
2159   for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1)
2160      if (qmask & m) pcs->Var[quantity] = variance;
2161}
2162
2163enum {
2164    ROLE_BACKTAPE,
2165    ROLE_BACKCOMPASS,
2166    ROLE_BACKCLINO,
2167    ROLE_TAPE,
2168    ROLE_COMPASS,
2169    ROLE_CLINO,
2170    ROLE_COUNTER,
2171    ROLE_DEPTH,
2172    ROLE_STATION,
2173    ROLE_POSITION,
2174    ROLE_NOTES,
2175    ROLE_PICTURES,
2176    ROLE_INSTRUMENTS,
2177    ROLE_ASSISTANT,
2178    ROLE_ALTITUDE,
2179    ROLE_DIMENSIONS,
2180    ROLE_LEFT,
2181    ROLE_RIGHT,
2182    ROLE_UP,
2183    ROLE_DOWN,
2184    ROLE_EXPLORER
2185};
2186
2187static const sztok role_tab[] = {
2188    {"ALTITUDE",        ROLE_ALTITUDE},
2189    {"ASSISTANT",       ROLE_ASSISTANT},
2190    {"BACKBEARING",     ROLE_BACKCOMPASS},
2191    {"BACKCLINO",       ROLE_BACKCLINO},
2192    {"BACKCOMPASS",     ROLE_BACKCOMPASS},
2193    {"BACKGRADIENT",    ROLE_BACKCLINO},
2194    {"BACKLENGTH",      ROLE_BACKTAPE},
2195    {"BACKTAPE",        ROLE_BACKTAPE},
2196    {"BEARING",         ROLE_COMPASS},
2197    {"CEILING",         ROLE_UP},
2198    {"CLINO",           ROLE_CLINO},
2199    {"COMPASS",         ROLE_COMPASS},
2200    {"COUNT",           ROLE_COUNTER},
2201    {"COUNTER",         ROLE_COUNTER},
2202    {"DEPTH",           ROLE_DEPTH},
2203    {"DIMENSIONS",      ROLE_DIMENSIONS},
2204    {"DOG",             ROLE_ASSISTANT},
2205    {"DOWN",            ROLE_DOWN},
2206    {"DZ",              ROLE_ALTITUDE},
2207    {"EXPLORER",        ROLE_EXPLORER},
2208    {"FLOOR",           ROLE_DOWN},
2209    {"GRADIENT",        ROLE_CLINO},
2210    {"INSTRUMENTS",     ROLE_INSTRUMENTS},
2211    {"INSTS",           ROLE_INSTRUMENTS},
2212    {"LEFT",            ROLE_LEFT},
2213    {"LENGTH",          ROLE_TAPE},
2214    {"NOTEBOOK",        ROLE_NOTES},
2215    {"NOTES",           ROLE_NOTES},
2216    {"PICS",            ROLE_PICTURES},
2217    {"PICTURES",        ROLE_PICTURES},
2218    {"POSITION",        ROLE_POSITION},
2219    {"RIGHT",           ROLE_RIGHT},
2220    {"STATION",         ROLE_STATION},
2221    {"TAPE",            ROLE_TAPE},
2222    {"UP",              ROLE_UP},
2223    {NULL,              -1}
2224};
2225
2226static void
2227cmd_team(void)
2228{
2229    string name = S_INIT;
2230    if (!read_string_warning(&name)) {
2231        skipline();
2232        return;
2233    }
2234    s_free(&name);
2235
2236    while (true) {
2237        skipblanks();
2238        if (isComm(ch) || isEol(ch)) return;
2239        get_token();
2240        int role = match_tok(role_tab, TABSIZE(role_tab));
2241        if (role < 0) {
2242            // Skip after a bad role to avoid triggering multiple warnings for
2243            // one *team command in existing data from before this check was
2244            // implemented.
2245            compile_diagnostic(DIAG_WARN|DIAG_TOKEN|DIAG_SKIP, /*Unknown team role “%s”*/532,
2246                               s_str(&token));
2247            return;
2248        }
2249    }
2250}
2251
2252static void
2253cmd_title(void)
2254{
2255   if (!fExplicitTitle && pcs->Prefix == root) {
2256       /* If we don't have an explicit title yet, and we're currently in the
2257        * root prefix, use this title explicitly. */
2258      fExplicitTitle = true;
2259      read_string(&survey_title);
2260   } else {
2261      /* parse and throw away this title (but still check rest of line) */
2262      string s = S_INIT;
2263      read_string(&s);
2264      s_free(&s);
2265   }
2266}
2267
2268static const sztok case_tab[] = {
2269     {"PRESERVE", OFF},
2270     {"TOLOWER",  LOWER},
2271     {"TOUPPER",  UPPER},
2272     {NULL,       -1}
2273};
2274
2275static void
2276cmd_case(void)
2277{
2278   int setting;
2279   get_token();
2280   setting = match_tok(case_tab, TABSIZE(case_tab));
2281   if (setting != -1) {
2282      pcs->Case = setting;
2283   } else {
2284      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found “%s”, expecting “PRESERVE”, “TOUPPER”, or “TOLOWER”*/10, s_str(&token));
2285   }
2286}
2287
2288static void
2289cmd_copyright(void)
2290{
2291    skipblanks();
2292    filepos fp;
2293    get_pos(&fp);
2294    unsigned y1 = read_uint_raw(DIAG_WARN|DIAG_UINT, /*Invalid year*/534, NULL);
2295    if (y1 < 1000) {
2296        set_pos(&fp);
2297        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year*/534, y1);
2298    } else if (y1 > current_year) {
2299        set_pos(&fp);
2300        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Date is in the future!*/80);
2301    }
2302    if (ch == '-') {
2303        nextch();
2304        get_pos(&fp);
2305        unsigned y2 = read_uint_raw(DIAG_WARN|DIAG_UINT, /*Invalid year*/534, NULL);
2306        if (y2 < 1000) {
2307            set_pos(&fp);
2308            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year*/534, y2);
2309        } else if (y2 < y1) {
2310            set_pos(&fp);
2311            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*End of date range is before the start*/81);
2312        } else if (y2 > current_year) {
2313            set_pos(&fp);
2314            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Date is in the future!*/80);
2315        }
2316    }
2317
2318    string text = S_INIT;
2319    if (!read_string_warning(&text)) {
2320        skipline();
2321        return;
2322    }
2323    s_free(&text);
2324
2325    skipblanks();
2326    if (!isComm(ch) && !isEol(ch))
2327        compile_diagnostic(DIAG_WARN|DIAG_TAIL, /*End of line not blank*/15);
2328}
2329
2330typedef enum {
2331    CS_NONE = -1,
2332    CS_CUSTOM,
2333    CS_EPSG,
2334    CS_ESRI,
2335    CS_EUR79Z30,
2336    CS_IJTSK,
2337    CS_IJTSK03,
2338    CS_JTSK,
2339    CS_JTSK03,
2340    CS_LAT,
2341    CS_LOCAL,
2342    CS_LONG,
2343    CS_OSGB,
2344    CS_S_MERC,
2345    CS_UTM
2346} cs_class;
2347
2348static const sztok cs_tab[] = {
2349     {"CUSTOM",   CS_CUSTOM},
2350     {"EPSG",     CS_EPSG},     /* EPSG:<number> */
2351     {"ESRI",     CS_ESRI},     /* ESRI:<number> */
2352     {"EUR79Z30", CS_EUR79Z30},
2353     {"IJTSK",    CS_IJTSK},
2354     {"IJTSK03",  CS_IJTSK03},
2355     {"JTSK",     CS_JTSK},
2356     {"JTSK03",   CS_JTSK03},
2357     {"LAT",      CS_LAT},      /* LAT-LONG */
2358     {"LOCAL",    CS_LOCAL},
2359     {"LONG",     CS_LONG},     /* LONG-LAT */
2360     {"OSGB",     CS_OSGB},     /* OSGB:<H, N, O, S or T><A-Z except I> */
2361     {"S",        CS_S_MERC},   /* S-MERC */
2362     // UTM<zone><N or S or nothing> is handled separately to avoid needing 180
2363     // entries in this lookup table.
2364     {NULL,       CS_NONE}
2365};
2366
2367static void
2368cmd_cs(void)
2369{
2370   char *proj_str = NULL;
2371   cs_class cs;
2372   int cs_sub = INT_MIN;
2373   filepos fp;
2374   bool output = false;
2375   enum { YES, NO, MAYBE } ok_for_output = YES;
2376   static bool had_cs = false;
2377
2378   if (!had_cs) {
2379      had_cs = true;
2380      if (first_fix_name) {
2381         compile_diagnostic_at(DIAG_ERR,
2382                               first_fix_filename, first_fix_line,
2383                               /*Station “%s” fixed before CS command first used*/442,
2384                               sprint_prefix(first_fix_name));
2385      }
2386   }
2387
2388   skipblanks();
2389   get_pos(&fp);
2390   get_token_no_blanks();
2391   if (S_EQ(&uctoken, "OUT")) {
2392      output = true;
2393      skipblanks();
2394      get_pos(&fp);
2395      get_token_no_blanks();
2396   }
2397
2398   if (s_len(&uctoken) > 3 &&
2399       memcmp(s_str(&uctoken), "UTM", 3) == 0 &&
2400       isdigit((unsigned char)s_str(&uctoken)[3])) {
2401       // The token starts "UTM" followed by a digit so handle that separately
2402       // to avoid needing 180 entries for UTM zones in the cs_tab lookup
2403       // table.
2404       cs = CS_UTM;
2405       // Reposition on the digit after "UTM".
2406       set_pos(&fp);
2407       nextch();
2408       nextch();
2409       nextch();
2410       unsigned n = read_uint();
2411       if (n >= 1 && n <= 60) {
2412           int uch = toupper(ch);
2413           cs_sub = (int)n;
2414           if (uch == 'S') {
2415               nextch();
2416               cs_sub = -cs_sub;
2417           } else if (uch == 'N') {
2418               nextch();
2419           }
2420       }
2421   } else {
2422       cs = match_tok(cs_tab, TABSIZE(cs_tab));
2423       switch (cs) {
2424         case CS_NONE:
2425           break;
2426         case CS_CUSTOM:
2427           ok_for_output = MAYBE;
2428           get_pos(&fp);
2429           string str = S_INIT;
2430           read_string(&str);
2431           proj_str = s_steal(&str);
2432           cs_sub = 0;
2433           break;
2434         case CS_EPSG: case CS_ESRI:
2435           ok_for_output = MAYBE;
2436           if (ch == ':' && isdigit(nextch())) {
2437               unsigned n = read_uint();
2438               if (n < 1000000) {
2439                   cs_sub = (int)n;
2440               }
2441           }
2442           break;
2443         case CS_EUR79Z30:
2444           cs_sub = 0;
2445           break;
2446         case CS_JTSK:
2447         case CS_JTSK03:
2448           ok_for_output = NO;
2449           cs_sub = 0;
2450           break;
2451         case CS_IJTSK:
2452         case CS_IJTSK03:
2453           cs_sub = 0;
2454           break;
2455         case CS_LAT: case CS_LONG:
2456           ok_for_output = NO;
2457           if (ch == '-') {
2458               nextch();
2459               get_token_no_blanks();
2460               cs_class cs2 = match_tok(cs_tab, TABSIZE(cs_tab));
2461               if ((cs ^ cs2) == (CS_LAT ^ CS_LONG)) {
2462                   cs_sub = 0;
2463               }
2464           }
2465           break;
2466         case CS_LOCAL:
2467           cs_sub = 0;
2468           break;
2469         case CS_OSGB:
2470           if (ch == ':') {
2471               int uch1 = toupper(nextch());
2472               if (strchr("HNOST", uch1)) {
2473                   int uch2 = toupper(nextch());
2474                   if (uch2 >= 'A' && uch2 <= 'Z' && uch2 != 'I') {
2475                       int x, y;
2476                       nextch();
2477                       if (uch1 > 'I') --uch1;
2478                       uch1 -= 'A';
2479                       if (uch2 > 'I') --uch2;
2480                       uch2 -= 'A';
2481                       x = uch1 % 5;
2482                       y = uch1 / 5;
2483                       x = (x * 5) + uch2 % 5;
2484                       y = (y * 5) + uch2 / 5;
2485                       cs_sub = y * 25 + x;
2486                   }
2487               }
2488           }
2489           break;
2490         case CS_S_MERC:
2491           if (ch == '-') {
2492               nextch();
2493               get_token_no_blanks();
2494               if (S_EQ(&uctoken, "MERC")) {
2495                   cs_sub = 0;
2496               }
2497           }
2498           break;
2499         case CS_UTM:
2500           // Handled outside of this switch, but avoid compiler warning about
2501           // unhandled enumeration value.
2502           break;
2503       }
2504   }
2505   if (cs_sub == INT_MIN || isalnum(ch)) {
2506      set_pos(&fp);
2507      compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Unknown coordinate system*/434);
2508      skipline();
2509      return;
2510   }
2511   /* Actually handle the cs */
2512   switch (cs) {
2513      case CS_NONE:
2514         break;
2515      case CS_CUSTOM:
2516         /* proj_str already set */
2517         break;
2518      case CS_EPSG:
2519         proj_str = osmalloc(32);
2520         snprintf(proj_str, 32, "EPSG:%d", cs_sub);
2521         break;
2522      case CS_ESRI:
2523         proj_str = osmalloc(32);
2524         snprintf(proj_str, 32, "ESRI:%d", cs_sub);
2525         break;
2526      case CS_EUR79Z30:
2527         proj_str = osstrdup("+proj=utm +zone=30 +ellps=intl +towgs84=-86,-98,-119,0,0,0,0 +no_defs");
2528         break;
2529      case CS_IJTSK:
2530         proj_str = osstrdup("+proj=krovak +ellps=bessel +towgs84=570.8285,85.6769,462.842,4.9984,1.5867,5.2611,3.5623 +no_defs");
2531         break;
2532      case CS_IJTSK03:
2533         proj_str = osstrdup("+proj=krovak +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +no_defs");
2534         break;
2535      case CS_JTSK:
2536         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");
2537         break;
2538      case CS_JTSK03:
2539         proj_str = osstrdup("+proj=krovak +czech +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +no_defs");
2540         break;
2541      case CS_LAT:
2542         /* FIXME: Requires PROJ >= 4.8.0 for +axis, and the SDs will be
2543          * misapplied, so we may want to swap ourselves.  Also, while
2544          * therion supports lat-long, I'm not totally convinced that it is
2545          * sensible to do so - people often say "lat-long", but probably
2546          * don't think that that's actually "Northing, Easting".  This
2547          * seems like it'll result in people accidentally getting X and Y
2548          * swapped in their fixed points...
2549          */
2550#if 0
2551         proj_str = osstrdup("+proj=longlat +ellps=WGS84 +datum=WGS84 +axis=neu +no_defs");
2552#endif
2553         break;
2554      case CS_LOCAL:
2555         /* FIXME: Is it useful to be able to explicitly specify this? */
2556         break;
2557      case CS_LONG:
2558         proj_str = osstrdup("EPSG:4326");
2559         break;
2560      case CS_OSGB: {
2561         int x = 14 - (cs_sub % 25);
2562         int y = (cs_sub / 25) - 20;
2563         proj_str = osmalloc(160);
2564         snprintf(proj_str, 160,
2565                  "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 "
2566                  "+x_0=%d +y_0=%d +ellps=airy +datum=OSGB36 +units=m +no_defs",
2567                  x * 100000, y * 100000);
2568         break;
2569      }
2570      case CS_S_MERC:
2571         proj_str = osstrdup("EPSG:3857");
2572         break;
2573      case CS_UTM:
2574         proj_str = osmalloc(32);
2575         if (cs_sub > 0) {
2576            snprintf(proj_str, 32, "EPSG:%d", 32600 + cs_sub);
2577         } else {
2578            snprintf(proj_str, 32, "EPSG:%d", 32700 - cs_sub);
2579         }
2580         break;
2581   }
2582
2583   if (!proj_str) {
2584      /* printf("CS %d:%d\n", (int)cs, cs_sub); */
2585      set_pos(&fp);
2586      compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Unknown coordinate system*/434);
2587      skipline();
2588      return;
2589   }
2590
2591   if (output) {
2592      if (ok_for_output == NO) {
2593         set_pos(&fp);
2594         compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Coordinate system unsuitable for output*/435);
2595         skipline();
2596         return;
2597      }
2598
2599      if (proj_str_out && strcmp(proj_str, proj_str_out) == 0) {
2600          /* Same as the output cs that's already set, so nothing to do. */
2601          free(proj_str);
2602          return;
2603      }
2604
2605      if (ok_for_output == MAYBE) {
2606          /* We only actually create the transformation from input to output when
2607           * we need it, but for a custom proj string or EPSG/ESRI code we need
2608           * to check that the specified coordinate system is valid and also if
2609           * it's suitable for output so we need to test creating it here.
2610           */
2611          proj_errno_reset(NULL);
2612          PJ* pj = proj_create(PJ_DEFAULT_CTX, proj_str);
2613          if (!pj) {
2614              set_pos(&fp);
2615              compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Invalid coordinate system: %s*/443,
2616                                 proj_context_errno_string(PJ_DEFAULT_CTX,
2617                                                           proj_context_errno(PJ_DEFAULT_CTX)));
2618              skipline();
2619              free(proj_str);
2620              return;
2621          }
2622          int type = proj_get_type(pj);
2623          if (type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
2624              type == PJ_TYPE_GEOGRAPHIC_3D_CRS) {
2625              set_pos(&fp);
2626              compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Coordinate system unsuitable for output*/435);
2627              skipline();
2628              free(proj_str);
2629              return;
2630          }
2631      }
2632
2633      if (proj_str_out) {
2634          /* If the output cs is already set, subsequent attempts to set it
2635           * are silently ignored (so you can combine two datasets and set
2636           * the output cs to use before you include either).
2637           */
2638          free(proj_str);
2639      } else {
2640          proj_str_out = proj_str;
2641      }
2642   } else {
2643      if (proj_str_out && strcmp(proj_str, proj_str_out) == 0) {
2644         /* Same as the current output projection, so valid for input. */
2645      } else if (pcs->proj_str && strcmp(proj_str, pcs->proj_str) == 0) {
2646         /* Same as the current input projection, so nothing to do! */
2647         return;
2648      } else if (ok_for_output == MAYBE) {
2649         /* (ok_for_output == MAYBE) also happens to indicate whether we need
2650          * to check that the coordinate system is valid for input.
2651          */
2652         proj_errno_reset(NULL);
2653         PJ* pj = proj_create(PJ_DEFAULT_CTX, proj_str);
2654         if (!pj) {
2655            set_pos(&fp);
2656            compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Invalid coordinate system: %s*/443,
2657                               proj_context_errno_string(PJ_DEFAULT_CTX,
2658                                                         proj_context_errno(PJ_DEFAULT_CTX)));
2659            skipline();
2660            return;
2661         }
2662         proj_destroy(pj);
2663      }
2664
2665      /* Free current input proj_str if not used by parent. */
2666      settings * p = pcs;
2667      if (!p->next || p->proj_str != p->next->proj_str)
2668         free(p->proj_str);
2669      p->proj_str = proj_str;
2670      p->input_convergence = HUGE_REAL;
2671      invalidate_pj_cached();
2672   }
2673}
2674
2675static const sztok infer_tab[] = {
2676     { "EQUATES",       INFER_EQUATES },
2677     { "EXPORTS",       INFER_EXPORTS },
2678     { "PLUMBS",        INFER_PLUMBS },
2679#if 0 /* FIXME */
2680     { "SUBSURVEYS",    INFER_SUBSURVEYS },
2681#endif
2682     { NULL,            INFER_NULL }
2683};
2684
2685static const sztok onoff_tab[] = {
2686     { "OFF", 0 },
2687     { "ON",  1 },
2688     { NULL, -1 }
2689};
2690
2691static void
2692cmd_infer(void)
2693{
2694   infer_what setting;
2695   int on;
2696   get_token();
2697   setting = match_tok(infer_tab, TABSIZE(infer_tab));
2698   if (setting == INFER_NULL) {
2699      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found “%s”, expecting “EQUATES”, “EXPORTS”, or “PLUMBS”*/31, s_str(&token));
2700      return;
2701   }
2702   get_token();
2703   on = match_tok(onoff_tab, TABSIZE(onoff_tab));
2704   if (on == -1) {
2705      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found “%s”, expecting “ON” or “OFF”*/32, s_str(&token));
2706      return;
2707   }
2708
2709   if (on) {
2710      pcs->infer |= BIT(setting);
2711      if (setting == INFER_EXPORTS) fExportUsed = true;
2712   } else {
2713      pcs->infer &= ~BIT(setting);
2714   }
2715}
2716
2717static void
2718cmd_truncate(void)
2719{
2720   unsigned int truncate_at = 0; /* default is no truncation */
2721   filepos fp;
2722
2723   get_pos(&fp);
2724
2725   get_token();
2726   if (!S_EQ(&uctoken, "OFF")) {
2727      if (!s_empty(&uctoken)) set_pos(&fp);
2728      truncate_at = read_uint();
2729   }
2730   /* for backward compatibility, "*truncate 0" means "*truncate off" */
2731   pcs->Truncate = (truncate_at == 0) ? INT_MAX : truncate_at;
2732}
2733
2734static void
2735cmd_ref(void)
2736{
2737   /* Just syntax check for now. */
2738   string ref = S_INIT;
2739   read_string(&ref);
2740   s_free(&ref);
2741}
2742
2743static void
2744cmd_require(void)
2745{
2746    // Add extra 0 so `*require 1.4.10.1` fails with cavern version 1.4.10.
2747    const unsigned version[] = {COMMAVERSION, 0};
2748
2749    skipblanks();
2750    filepos fp;
2751    get_pos(&fp);
2752
2753    // Parse the required version number, storing its components in
2754    // required_version.  We only store at most one more component than
2755    // COMMAVERSION has since more than that can't affect the comparison.
2756    size_t i = 0;
2757    int diff = 0;
2758    while (1) {
2759        unsigned component = read_uint();
2760        if (diff == 0 && i < sizeof(version) / sizeof(version[0])) {
2761            if (diff == 0) {
2762                diff = (int)version[i++] - (int)component;
2763            }
2764        }
2765        if (ch != '.' || isBlank(nextch()) || isComm(ch) || isEol(ch))
2766            break;
2767    }
2768
2769    if (diff < 0) {
2770        // Requirement not satisfied
2771        size_t len = (size_t)(ftell(file.fh) - fp.offset);
2772        char *v = osmalloc(len + 1);
2773        set_pos(&fp);
2774        for (size_t j = 0; j < len; j++) {
2775            v[j] = ch;
2776            nextch();
2777        }
2778        v[len] = '\0';
2779        /* TRANSLATORS: Feel free to translate as "or newer" instead of "or
2780         * greater" if that gives a more natural translation.  It's
2781         * technically not quite right when there are parallel active release
2782         * series (e.g. Survex 1.0.40 was released *after* 1.2.0), but this
2783         * seems unlikely to confuse users.  "Survex" is the name of the
2784         * software, so should not be translated.
2785         *
2786         * Here "survey" is a "cave map" rather than list of questions - it should be
2787         * translated to the terminology that cavers using the language would use.
2788         */
2789        compile_diagnostic(DIAG_FATAL|DIAG_FROM(fp), /*Survex version %s or greater required to process this survey data.*/38, v);
2790        // Does not return so no point freeing v here.
2791    }
2792}
2793
2794/* allocate new meta_data if need be */
2795void
2796copy_on_write_meta(settings *s)
2797{
2798   if (!s->meta || s->meta->ref_count != 0) {
2799       meta_data * meta_new = osnew(meta_data);
2800       if (!s->meta) {
2801           meta_new->days1 = meta_new->days2 = -1;
2802       } else {
2803           *meta_new = *(s->meta);
2804       }
2805       meta_new->ref_count = 0;
2806       s->meta = meta_new;
2807   }
2808}
2809
2810static int
2811read_year(filepos *fp_date_ptr)
2812{
2813    int y = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, fp_date_ptr);
2814    if (y < 100) {
2815        /* Two digit year is 19xx. */
2816        y += 1900;
2817        filepos fp_save;
2818        get_pos(&fp_save);
2819        set_pos(fp_date_ptr);
2820        /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
2821        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Assuming 2 digit year is %d*/76, y);
2822        set_pos(&fp_save);
2823    } else if (y < 1900 || y > 2078) {
2824        set_pos(fp_date_ptr);
2825        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year (< 1900 or > 2078)*/58);
2826        longjmp(jbSkipLine, 1);
2827    }
2828    return y;
2829}
2830
2831static void
2832cmd_date(void)
2833{
2834    enum { DATE_SURVEYED = 1, DATE_EXPLORED = 2 };
2835    unsigned date_flags = 0;
2836    while (get_token(), !s_empty(&uctoken)) {
2837        unsigned new_flag = 0;
2838        if (S_EQ(&uctoken, "SURVEYED")) {
2839            new_flag = DATE_SURVEYED;
2840        } else if (S_EQ(&uctoken, "EXPLORED")) {
2841            new_flag = DATE_EXPLORED;
2842        } else {
2843            compile_diagnostic(DIAG_ERR|DIAG_TOKEN,
2844                               /*Expecting “%s” or “%s”*/103, "SURVEYED", "EXPLORED");
2845            continue;
2846        }
2847
2848        if ((date_flags & new_flag)) {
2849            compile_diagnostic(DIAG_ERR|DIAG_TOKEN,
2850                               /*Duplicate date type “%s”*/416, s_str(&token));
2851        }
2852        date_flags |= new_flag;
2853    }
2854
2855    int date_sep = '-';
2856    if (date_flags == 0) {
2857        // `*date` without qualification sets the surveyed date.
2858        date_flags = DATE_SURVEYED;
2859        // Also allow '.' for compatibility.
2860        date_sep = 0;
2861    }
2862
2863    filepos fp_date1, fp_date2, fp;
2864    get_pos(&fp_date1);
2865    int year = read_year(&fp_date1);
2866    int month = 0, day = 0, year2 = 0, month2 = 0, day2 = 0;
2867
2868    if (ch == '-') {
2869        // Could be ISO-format date (e.g. 2024-10 or 2024-10-21 or 1912-11), or
2870        // a range of old-format dates (e.g. 1973-1975 or 1911-12.02.03) or an
2871        // ambiguous case like 1911-12 which we need to warn about in a `*date`
2872        // command without `surveyed` or `explored` qualifiers.
2873        nextch();
2874        get_pos(&fp_date2);
2875        int v = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date1);
2876        if (date_sep == '-') {
2877            // We're only accepting ISO dates.
2878        } else if (ch == '-') {
2879            // Two `-` so must be an ISO date, e.g. `2024-10-21`.
2880        } else if (v >= 1 && v <= 12) {
2881            // Valid month number so assume it's an ISO date.
2882            if (year < 1900 + v) {
2883                // Warn about ambiguous cases.
2884                compile_diagnostic(DIAG_WARN|DIAG_FROM(fp_date1),
2885                                   /*Interpreting as an ISO-format date - use “*date surveyed %d-%02d” to suppress this warning, or “*date %d %d” if you wanted a date range*/158,
2886                                   year, v, year, 1900 + v);
2887            }
2888        } else {
2889            date_sep = '.';
2890            year2 = v;
2891            if (year2 < 100) {
2892                /* Two digit year is 19xx. */
2893                year2 += 1900;
2894                /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
2895                compile_diagnostic(DIAG_WARN|DIAG_FROM(fp_date2),
2896                                   /*Assuming 2 digit year is %d*/76, year2);
2897            } else if (year2 < 1900 || year2 > 2078) {
2898                compile_diagnostic(DIAG_WARN|DIAG_FROM(fp_date2),
2899                                   /*Invalid year (< 1900 or > 2078)*/58);
2900                longjmp(jbSkipLine, 1);
2901            }
2902            goto process_dates;
2903        }
2904
2905        date_sep = '-';
2906        month = v;
2907        fp = fp_date2;
2908    } else if (ch == '.') {
2909        if (date_sep == '-') {
2910            char date_sep_string[2] = { date_sep, '\0' };
2911            compile_diagnostic(DIAG_ERR|DIAG_COL|DIAG_SKIP,
2912                               /*Expecting “%s”*/497, date_sep_string);
2913            return;
2914        }
2915        date_sep = ch;
2916        nextch();
2917        get_pos(&fp);
2918        month = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date1);
2919    } else {
2920        // Just a year - might be a ISO date range though.
2921        date_sep = '-';
2922        goto try_date2;
2923    }
2924
2925    if (month < 1 || month > 12) {
2926        set_pos(&fp);
2927        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
2928        longjmp(jbSkipLine, 1);
2929    }
2930
2931    if (ch == date_sep) {
2932        nextch();
2933        get_pos(&fp);
2934        day = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date1);
2935        if (day < 1 || day > last_day(year, month)) {
2936            set_pos(&fp);
2937            /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
2938            compile_diagnostic(DIAG_WARN|DIAG_UINT,
2939                               /*Invalid day of the month*/87);
2940            longjmp(jbSkipLine, 1);
2941        }
2942    }
2943
2944try_date2:
2945    if (date_sep == '-') {
2946        skipblanks();
2947        if (!isdigit(ch)) goto process_dates;
2948    } else if (ch == '-') {
2949        nextch();
2950    } else {
2951        goto process_dates;
2952    }
2953    {
2954        get_pos(&fp_date2);
2955        year2 = read_year(&fp_date2);
2956        if (ch == date_sep) {
2957            nextch();
2958            get_pos(&fp);
2959            month2 = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198,
2960                                   &fp_date2);
2961            if (month2 < 1 || month2 > 12) {
2962                set_pos(&fp);
2963                compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
2964                longjmp(jbSkipLine, 1);
2965            }
2966
2967            if (ch == date_sep) {
2968                nextch();
2969                get_pos(&fp);
2970                day2 = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198,
2971                                     &fp_date2);
2972                if (day2 < 1 || day2 > last_day(year2, month2)) {
2973                    set_pos(&fp);
2974                    /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
2975                    compile_diagnostic(DIAG_WARN|DIAG_UINT,
2976                                       /*Invalid day of the month*/87);
2977                    longjmp(jbSkipLine, 1);
2978                }
2979            }
2980        }
2981    }
2982
2983process_dates:;
2984    bool date_range = (year2 != 0);
2985    if (!date_range) {
2986        year2 = year;
2987        month2 = month;
2988        day2 = day;
2989    }
2990
2991    int days1 = days_since_1900(year, month ? month : 1, day ? day : 1);
2992
2993    if (days1 > current_days_since_1900) {
2994        filepos fp_save;
2995        get_pos(&fp_save);
2996        set_pos(&fp_date1);
2997        compile_diagnostic(DIAG_WARN|DIAG_DATE, /*Date is in the future!*/80);
2998        set_pos(&fp_save);
2999    }
3000
3001    if (month2 == 0) {
3002        month2 = 12;
3003        day2 = 31;
3004    } else if (day2 == 0) {
3005        day2 = last_day(year2, month2);
3006    }
3007    int days2 = days_since_1900(year2, month2, day2);
3008    if (date_range && days2 > current_days_since_1900) {
3009        // If !date_range, either we already emitted this warning when
3010        // processing the start date, or the date is partial and for the
3011        // current year or current month, in which case it's not helpful
3012        // to warn that the end is on the future, but it makes sense to
3013        // process for the end date so the result doesn't change as time
3014        // passes.
3015        filepos fp_save;
3016        get_pos(&fp_save);
3017        set_pos(&fp_date2);
3018        compile_diagnostic(DIAG_WARN|DIAG_DATE, /*Date is in the future!*/80);
3019        set_pos(&fp_save);
3020    }
3021
3022    if (days2 < days1) {
3023        filepos fp_save;
3024        get_pos(&fp_save);
3025        set_pos(&fp_date1);
3026        compile_diagnostic(DIAG_ERR|DIAG_WORD, /*End of date range is before the start*/81);
3027        set_pos(&fp_save);
3028        // Swap range ends to put us in a consistent state.
3029        int tmp = days1;
3030        days1 = days2;
3031        days2 = tmp;
3032    }
3033
3034    if ((date_flags & DATE_SURVEYED)) {
3035        if (!pcs->meta || pcs->meta->days1 != days1 || pcs->meta->days2 != days2) {
3036            copy_on_write_meta(pcs);
3037            pcs->meta->days1 = days1;
3038            pcs->meta->days2 = days2;
3039            /* Invalidate cached declination. */
3040            pcs->declination = HUGE_REAL;
3041        }
3042    }
3043
3044    if ((date_flags & DATE_EXPLORED)) {
3045        // FIXME: Need to revise 3d format to allow storing EXPLORED date too.
3046    }
3047}
3048
3049typedef void (*cmd_fn)(void);
3050
3051static const cmd_fn cmd_funcs[] = {
3052   cmd_alias,
3053   cmd_begin,
3054   cmd_calibrate,
3055   cmd_cartesian,
3056   cmd_case,
3057   cmd_copyright,
3058   cmd_cs,
3059   cmd_data,
3060   cmd_date,
3061   cmd_declination,
3062#ifndef NO_DEPRECATED
3063   cmd_default,
3064#endif
3065   cmd_end,
3066   cmd_entrance,
3067   cmd_equate,
3068   cmd_export,
3069   cmd_fix,
3070   cmd_flags,
3071   cmd_include,
3072   cmd_infer,
3073   skipline, /*cmd_instrument,*/
3074#ifndef NO_DEPRECATED
3075   cmd_prefix,
3076#endif
3077   cmd_ref,
3078   cmd_require,
3079   cmd_sd,
3080   cmd_set,
3081   solve_network,
3082   cmd_team,
3083   cmd_title,
3084   cmd_truncate,
3085   cmd_units
3086};
3087
3088extern void
3089handle_command(void)
3090{
3091   filepos fp;
3092   get_pos(&fp);
3093   get_token_legacy();
3094   int cmdtok = match_tok(cmd_tab, TABSIZE(cmd_tab));
3095   if (cmdtok < 0 || cmdtok >= (int)(sizeof(cmd_funcs) / sizeof(cmd_fn))) {
3096      set_pos(&fp);
3097      get_token();
3098      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown command “%s”*/12, s_str(&token));
3099      return;
3100   }
3101
3102   do_legacy_token_warning();
3103
3104   switch (cmdtok) {
3105    case CMD_EXPORT:
3106      if (!f_export_ok)
3107         /* TRANSLATORS: The *EXPORT command is only valid just after *BEGIN
3108          * <SURVEY>, so this would generate this error:
3109          *
3110          * *begin fred
3111          * 1 2 1.23 045 -6
3112          * *export 2
3113          * *end fred */
3114         compile_diagnostic(DIAG_ERR, /**EXPORT must immediately follow “*BEGIN <SURVEY>”*/57);
3115      break;
3116    case CMD_ALIAS:
3117    case CMD_CALIBRATE:
3118    case CMD_CARTESIAN:
3119    case CMD_CASE:
3120    case CMD_COPYRIGHT:
3121    case CMD_CS:
3122    case CMD_DATA:
3123    case CMD_DATE:
3124    case CMD_DECLINATION:
3125    case CMD_DEFAULT:
3126    case CMD_FLAGS:
3127    case CMD_INFER:
3128    case CMD_INSTRUMENT:
3129    case CMD_REF:
3130    case CMD_REQUIRE:
3131    case CMD_SD:
3132    case CMD_SET:
3133    case CMD_TEAM:
3134    case CMD_TITLE:
3135    case CMD_TRUNCATE:
3136    case CMD_UNITS:
3137      /* These can occur between *begin and *export */
3138      break;
3139    default:
3140      /* NB: additional handling for "*begin <survey>" in cmd_begin */
3141      f_export_ok = false;
3142      break;
3143   }
3144
3145   cmd_funcs[cmdtok]();
3146}
Note: See TracBrowser for help on using the repository browser.