source: git/src/commands.c @ 3fdc759

stereo-2025
Last change on this file since 3fdc759 was f15c53d9, checked in by Olly Betts <olly@…>, 4 months ago

Implement parsing of *copyright

  • Property mode set to 100644
File size: 88.6 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
952set_declination_location(real x, real y, real z, const char *proj_str)
953{
954    /* Convert to WGS84 lat long. */
955    PJ *transform = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
956                                           proj_str,
957                                           WGS84_DATUM_STRING,
958                                           NULL);
959    if (transform) {
960        /* Normalise the output order so x is longitude and y latitude - by
961         * default new PROJ has them switched for EPSG:4326 which just seems
962         * confusing.
963         */
964        PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX,
965                                                       transform);
966        proj_destroy(transform);
967        transform = pj_norm;
968    }
969
970    if (proj_angular_input(transform, PJ_FWD)) {
971        /* Input coordinate system expects radians. */
972        x = rad(x);
973        y = rad(y);
974    }
975
976    PJ_COORD coord = {{x, y, z, HUGE_VAL}};
977    coord = proj_trans(transform, PJ_FWD, coord);
978    x = coord.xyzt.x;
979    y = coord.xyzt.y;
980    z = coord.xyzt.z;
981
982    if (x == HUGE_VAL || y == HUGE_VAL || z == HUGE_VAL) {
983       compile_diagnostic(DIAG_ERR, /*Failed to convert coordinates: %s*/436,
984                          proj_context_errno_string(PJ_DEFAULT_CTX,
985                                                    proj_errno(transform)));
986       /* Set dummy values which are finite. */
987       x = y = z = 0;
988    }
989    proj_destroy(transform);
990
991    report_declination(pcs);
992
993    double lon = rad(x);
994    double lat = rad(y);
995    pcs->z[Q_DECLINATION] = HUGE_REAL;
996    pcs->dec_lat = lat;
997    pcs->dec_lon = lon;
998    pcs->dec_alt = z;
999    pcs->dec_filename = file.filename;
1000    pcs->dec_line = file.line;
1001    pcs->dec_context = grab_line();
1002    /* Invalidate cached declination. */
1003    pcs->declination = HUGE_REAL;
1004    /* Invalidate cached grid convergence values. */
1005    pcs->convergence = HUGE_REAL;
1006    pcs->input_convergence = HUGE_REAL;
1007}
1008
1009void
1010pop_settings(void)
1011{
1012    settings * p = pcs;
1013    pcs = pcs->next;
1014
1015    SVX_ASSERT(pcs);
1016
1017    if (pcs->dec_lat != p->dec_lat ||
1018        pcs->dec_lon != p->dec_lon ||
1019        pcs->dec_alt != p->dec_alt) {
1020        report_declination(p);
1021    } else {
1022        pcs->min_declination_days = p->min_declination_days;
1023        pcs->max_declination_days = p->max_declination_days;
1024        pcs->min_declination = p->min_declination;
1025        pcs->max_declination = p->max_declination;
1026        pcs->convergence = p->convergence;
1027    }
1028
1029    if (p->proj_str != pcs->proj_str) {
1030        if (!p->proj_str || !pcs->proj_str ||
1031            strcmp(p->proj_str, pcs->proj_str) != 0) {
1032            invalidate_pj_cached();
1033        }
1034        /* free proj_str if not used by parent */
1035        free(p->proj_str);
1036    }
1037
1038    /* don't free default ordering or ordering used by parent */
1039    if (p->ordering != default_order && p->ordering != pcs->ordering)
1040        free((reading*)p->ordering);
1041
1042    /* free Translate if not used by parent */
1043    if (p->Translate != pcs->Translate)
1044        free(p->Translate - 1);
1045
1046    /* free meta if not used by parent, or in this block */
1047    if (p->meta && p->meta != pcs->meta && p->meta->ref_count == 0)
1048        free(p->meta);
1049
1050    free(p);
1051}
1052
1053static void
1054cmd_end(void)
1055{
1056   filepos fp;
1057
1058   int begin_lineno = pcs->begin_lineno;
1059   if (begin_lineno == 0) {
1060      if (pcs->next == NULL) {
1061         /* more ENDs than BEGINs */
1062         /* TRANSLATORS: %s is replaced with e.g. BEGIN or .BOOK or #[ */
1063         compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*No matching %s*/192, "BEGIN");
1064      } else {
1065         /* TRANSLATORS: %s and %s are replaced with e.g. BEGIN and END
1066          * or END and BEGIN or #[ and #] */
1067         compile_diagnostic(DIAG_ERR|DIAG_SKIP,
1068                            /*%s with no matching %s in this file*/23, "END", "BEGIN");
1069      }
1070      return;
1071   }
1072
1073   prefix *begin_survey = pcs->begin_survey;
1074   long begin_lpos = pcs->begin_lpos;
1075   int begin_col = pcs->begin_col;
1076
1077   pop_settings();
1078
1079   /* note need to read using root *before* BEGIN */
1080   prefix *survey = NULL;
1081   skipblanks();
1082   if (!isEol(ch) && !isComm(ch)) {
1083      get_pos(&fp);
1084      survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT);
1085   }
1086
1087   if (survey != begin_survey) {
1088      filepos fp_save;
1089      get_pos(&fp_save);
1090      if (survey) {
1091         set_pos(&fp);
1092         if (!begin_survey) {
1093            /* TRANSLATORS: Used when a BEGIN command has no survey, but the
1094             * END command does, e.g.:
1095             *
1096             * *begin
1097             * 1 2 10.00 178 -01
1098             * *end entrance      <--[Message given here] */
1099            compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Matching BEGIN command has no survey name*/36);
1100         } else {
1101            /* TRANSLATORS: *BEGIN <survey> and *END <survey> should have the
1102             * same <survey> if it’s given at all */
1103            compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Survey name doesn’t match BEGIN*/193);
1104         }
1105      } else {
1106         /* TRANSLATORS: Used when a BEGIN command has a survey name, but the
1107          * END command omits it, e.g.:
1108          *
1109          * *begin entrance
1110          * 1 2 10.00 178 -01
1111          * *end     <--[Message given here] */
1112         compile_diagnostic(DIAG_WARN|DIAG_COL, /*Survey name omitted from END*/194);
1113      }
1114      parse file_save = file;
1115      file.line = begin_lineno;
1116      file.lpos = begin_lpos;
1117      int word_flag = 0;
1118      if (begin_col) {
1119          word_flag = DIAG_WORD;
1120          fseek(file.fh, begin_lpos + begin_col - 1, SEEK_SET);
1121          nextch();
1122      }
1123      compile_diagnostic(DIAG_INFO|word_flag, /*Corresponding %s was here*/22, "BEGIN");
1124      file = file_save;
1125      set_pos(&fp_save);
1126   }
1127}
1128
1129static void
1130cmd_entrance(void)
1131{
1132   prefix *pfx = read_prefix(PFX_STATION);
1133   pfx->sflags |= BIT(SFLAGS_ENTRANCE);
1134   pfx->sflags &= ~BIT(SFLAGS_UNUSED_FIXED_POINT);
1135}
1136
1137static const prefix * first_fix_name = NULL;
1138static const char * first_fix_filename;
1139static unsigned first_fix_line;
1140
1141static void
1142cmd_fix(void)
1143{
1144   static prefix *name_omit_already = NULL;
1145   static const char * name_omit_already_filename = NULL;
1146   static unsigned int name_omit_already_line;
1147   PJ_COORD coord;
1148   filepos fp_stn, fp;
1149
1150   get_pos(&fp_stn);
1151   prefix *fix_name = read_prefix(PFX_STATION|PFX_ALLOW_ROOT);
1152
1153   get_pos(&fp);
1154   get_token_legacy();
1155   bool reference = S_EQ(&uctoken, "REFERENCE");
1156   if (reference) {
1157      do_legacy_token_warning();
1158   } else {
1159      if (!s_empty(&uctoken)) set_pos(&fp);
1160   }
1161
1162   // If `REFERENCE` is specified the coordinates can't be omitted.
1163   coord.v[0] = read_numeric(!reference);
1164   if (coord.v[0] == HUGE_REAL) {
1165      /* If the end of the line isn't blank, read a number after all to
1166       * get a more helpful error message */
1167      if (!isEol(ch) && !isComm(ch)) coord.v[0] = read_numeric(false);
1168   }
1169   if (coord.v[0] == HUGE_REAL) {
1170      if (pcs->proj_str || proj_str_out) {
1171         compile_diagnostic(DIAG_ERR|DIAG_COL|DIAG_SKIP, /*Coordinates can't be omitted when coordinate system has been specified*/439);
1172         return;
1173      }
1174
1175      if (fix_name == name_omit_already) {
1176         compile_diagnostic(DIAG_WARN|DIAG_COL, /*Same station fixed twice with no coordinates*/61);
1177         return;
1178      }
1179
1180      if (name_omit_already) {
1181         /* TRANSLATORS: Emitted after second and subsequent "FIX" command
1182          * with no coordinates.
1183          */
1184         compile_diagnostic_at(DIAG_ERR,
1185                               name_omit_already_filename,
1186                               name_omit_already_line,
1187                               /*Already had FIX command with no coordinates for station “%s”*/441,
1188                               sprint_prefix(name_omit_already));
1189      } else {
1190         /* TRANSLATORS: " *fix a " gives this message: */
1191         compile_diagnostic(DIAG_INFO|DIAG_COL, /*FIX command with no coordinates - fixing at (0,0,0)*/54);
1192
1193         name_omit_already = fix_name;
1194         name_omit_already_filename = file.filename;
1195         name_omit_already_line = file.line;
1196      }
1197
1198      coord.v[0] = coord.v[1] = coord.v[2] = (real)0.0;
1199   } else {
1200      coord.v[1] = read_numeric(false);
1201      coord.v[2] = read_numeric(false);
1202
1203      if (pcs->proj_str && proj_str_out) {
1204         PJ *transform = pj_cached;
1205         if (!transform) {
1206             transform = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
1207                                                pcs->proj_str,
1208                                                proj_str_out,
1209                                                NULL);
1210             if (transform) {
1211                /* Normalise the output order so x is longitude and y latitude - by
1212                 * default new PROJ has them switched for EPSG:4326 which just seems
1213                 * confusing.
1214                 */
1215                PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX,
1216                                                               transform);
1217                proj_destroy(transform);
1218                transform = pj_norm;
1219             }
1220
1221             pj_cached = transform;
1222         }
1223
1224         if (proj_angular_input(transform, PJ_FWD)) {
1225            /* Input coordinate system expects radians. */
1226            coord.v[0] = rad(coord.v[0]);
1227            coord.v[1] = rad(coord.v[1]);
1228         }
1229
1230         coord.v[3] = HUGE_VAL;
1231         coord = proj_trans(transform, PJ_FWD, coord);
1232
1233         if (coord.v[0] == HUGE_VAL ||
1234             coord.v[1] == HUGE_VAL ||
1235             coord.v[2] == HUGE_VAL) {
1236            compile_diagnostic(DIAG_ERR, /*Failed to convert coordinates: %s*/436,
1237                               proj_context_errno_string(PJ_DEFAULT_CTX,
1238                                                         proj_errno(transform)));
1239            /* Set dummy values which are finite. */
1240            coord.v[0] = coord.v[1] = coord.v[2] = 0;
1241         }
1242      } else if (pcs->proj_str) {
1243         compile_diagnostic(DIAG_ERR, /*The input projection is set but the output projection isn't*/437);
1244      } else if (proj_str_out) {
1245         compile_diagnostic(DIAG_ERR, /*The output projection is set but the input projection isn't*/438);
1246      }
1247
1248      get_pos(&fp);
1249      real sdx = read_numeric(true);
1250      if (sdx <= 0) {
1251          set_pos(&fp);
1252          compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
1253          return;
1254      }
1255      if (sdx != HUGE_REAL) {
1256         real sdy, sdz;
1257         real cxy = 0, cyz = 0, czx = 0;
1258         get_pos(&fp);
1259         sdy = read_numeric(true);
1260         if (sdy == HUGE_REAL) {
1261            /* only one variance given */
1262            sdy = sdz = sdx;
1263         } else {
1264            if (sdy <= 0) {
1265               set_pos(&fp);
1266               compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
1267               return;
1268            }
1269            get_pos(&fp);
1270            sdz = read_numeric(true);
1271            if (sdz == HUGE_REAL) {
1272               /* two variances given - horizontal & vertical */
1273               sdz = sdy;
1274               sdy = sdx;
1275            } else {
1276               if (sdz <= 0) {
1277                  set_pos(&fp);
1278                  compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
1279                  return;
1280               }
1281               cxy = read_numeric(true);
1282               if (cxy != HUGE_REAL) {
1283                  /* covariances given */
1284                  cyz = read_numeric(false);
1285                  czx = read_numeric(false);
1286               } else {
1287                  cxy = 0;
1288               }
1289            }
1290         }
1291         fix_station_with_variance(fix_name, coord.v,
1292                                   sdx * sdx, sdy * sdy, sdz * sdz
1293#ifndef NO_COVARIANCES
1294                                   , cxy, cyz, czx
1295#endif
1296                                  );
1297
1298         if (reference) {
1299             // `*fix reference` so suppress "unused fixed point" warning.
1300             fix_name->sflags &= ~BIT(SFLAGS_UNUSED_FIXED_POINT);
1301         }
1302         if (!first_fix_name) {
1303            /* We track if we've fixed a station yet, and if so what the name
1304             * of the first fix was, so that we can issue an error if the
1305             * output coordinate system is set after fixing a station. */
1306            first_fix_name = fix_name;
1307            first_fix_filename = file.filename;
1308            first_fix_line = file.line;
1309         }
1310
1311         return;
1312      }
1313   }
1314
1315   if (!first_fix_name) {
1316      /* We track if we've fixed a station yet, and if so what the name of the
1317       * first fix was, so that we can issue an error if the output coordinate
1318       * system is set after fixing a station. */
1319      first_fix_name = fix_name;
1320      first_fix_filename = file.filename;
1321      first_fix_line = file.line;
1322   }
1323
1324   int fix_result = fix_station(fix_name, coord.v);
1325   if (reference) {
1326       // `*fix reference` so suppress "unused fixed point" warning.
1327       fix_name->sflags &= ~BIT(SFLAGS_UNUSED_FIXED_POINT);
1328   }
1329   if (fix_result == 0) {
1330      return;
1331   }
1332
1333   get_pos(&fp);
1334   set_pos(&fp_stn);
1335   if (fix_result < 0) {
1336       compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Station already fixed or equated to a fixed point*/46);
1337   } else {
1338       /* TRANSLATORS: *fix a 1 2 3 / *fix a 1 2 3 */
1339       compile_diagnostic(DIAG_WARN|DIAG_WORD, /*Station already fixed at the same coordinates*/55);
1340   }
1341   compile_diagnostic_pfx(DIAG_INFO, fix_name, /*Previously fixed or equated here*/493);
1342   set_pos(&fp);
1343}
1344
1345static void
1346cmd_flags(void)
1347{
1348   static const sztok flagtab[] = {
1349        {"DUPLICATE", FLAGS_DUPLICATE },
1350        {"NOT",       FLAGS_NOT },
1351        {"SPLAY",     FLAGS_SPLAY },
1352        {"SURFACE",   FLAGS_SURFACE },
1353        {NULL,        FLAGS_UNKNOWN }
1354   };
1355   bool fNot = false;
1356   bool fEmpty = true;
1357   while (1) {
1358      int flag;
1359      get_token();
1360      /* If token is empty, it could mean end of line, or maybe
1361       * some non-alphanumeric junk which is better reported later */
1362      if (s_empty(&token)) break;
1363
1364      fEmpty = false;
1365      flag = match_tok(flagtab, TABSIZE(flagtab));
1366      /* treat the second NOT in "NOT NOT" as an unknown flag */
1367      if (flag == FLAGS_UNKNOWN || (fNot && flag == FLAGS_NOT)) {
1368         compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*FLAG “%s” unknown*/68,
1369                            s_str(&token));
1370         /* Recover from “*FLAGS NOT BOGUS SURFACE” by ignoring "NOT BOGUS" */
1371         fNot = false;
1372      } else if (flag == FLAGS_NOT) {
1373         fNot = true;
1374      } else if (fNot) {
1375         pcs->flags &= ~BIT(flag);
1376         fNot = false;
1377      } else {
1378         pcs->flags |= BIT(flag);
1379      }
1380   }
1381
1382   if (fNot) {
1383      compile_diagnostic(DIAG_ERR|DIAG_TOKEN,
1384                         /*Expecting “%s”, “%s”, or “%s”*/188,
1385                         "DUPLICATE", "SPLAY", "SURFACE");
1386   } else if (fEmpty) {
1387      compile_diagnostic(DIAG_ERR|DIAG_TOKEN,
1388                         /*Expecting “%s”, “%s”, “%s”, or “%s”*/189,
1389                         "NOT", "DUPLICATE", "SPLAY", "SURFACE");
1390   }
1391}
1392
1393static void
1394cmd_equate(void)
1395{
1396   filepos fp;
1397   get_pos(&fp);
1398   prefix *prev_name = NULL;
1399   prefix *name = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_SUSPECT_TYPO);
1400   while (true) {
1401      if (!name->stn || !fixed(name->stn)) {
1402          // If the station isn't already fixed, make its file:line location
1403          // reflect this *equate.
1404          name->filename = file.filename;
1405          name->line = file.line;
1406      }
1407      skipblanks();
1408      if (isEol(ch) || isComm(ch)) {
1409         if (prev_name == NULL) {
1410            /* E.g. *equate entrance.6 */
1411            set_pos(&fp);
1412            /* TRANSLATORS: EQUATE is a command name, so shouldn’t be
1413             * translated.
1414             *
1415             * Here "station" is a survey station, not a train station.
1416             */
1417            compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_WORD, /*Only one station in EQUATE command*/33);
1418         }
1419         return;
1420      }
1421
1422      prev_name = name;
1423      name = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_SUSPECT_TYPO);
1424      process_equate(prev_name, name);
1425   }
1426}
1427
1428static void
1429report_missing_export(prefix *pfx, int depth)
1430{
1431   char *s;
1432   const char *p;
1433   prefix *survey = pfx;
1434   int i;
1435   for (i = depth + 1; i; i--) {
1436      survey = survey->up;
1437      SVX_ASSERT(survey);
1438   }
1439   s = osstrdup(sprint_prefix(survey));
1440   p = sprint_prefix(pfx);
1441   if (survey->filename) {
1442      /* TRANSLATORS: A station must be exported out of each level it is in, so
1443       * this would give "Station “\outer.inner.1” not exported from survey
1444       * “\outer”)":
1445       *
1446       * *equate entrance outer.inner.1
1447       * *begin outer
1448       * *begin inner
1449       * *export 1
1450       * 1 2 1.23 045 -6
1451       * *end inner
1452       * *end outer
1453       *
1454       * Here "survey" is a "cave map" rather than list of questions - it should be
1455       * translated to the terminology that cavers using the language would use.
1456       */
1457      compile_diagnostic_pfx(DIAG_ERR, survey,
1458                             /*Station “%s” not exported from survey “%s”*/26, p, s);
1459   } else {
1460      compile_diagnostic(DIAG_ERR, /*Station “%s” not exported from survey “%s”*/26, p, s);
1461   }
1462   free(s);
1463}
1464
1465static void
1466cmd_export(void)
1467{
1468   prefix *pfx;
1469
1470   fExportUsed = true;
1471   do {
1472      int depth = 0;
1473      pfx = read_prefix(PFX_STATION);
1474      {
1475         prefix *p = pfx;
1476         while (p != NULL && p != pcs->Prefix) {
1477            depth++;
1478            p = p->up;
1479         }
1480         /* Something like: *export \foo, but we've excluded use of root */
1481         SVX_ASSERT(p);
1482      }
1483      /* *export \ or similar bogus stuff */
1484      SVX_ASSERT(depth);
1485#if 0
1486      printf("C min %d max %d depth %d pfx %s\n",
1487             pfx->min_export, pfx->max_export, depth, sprint_prefix(pfx));
1488#endif
1489      if (pfx->min_export == 0) {
1490         /* not encountered *export for this name before */
1491         if (pfx->max_export > depth) report_missing_export(pfx, depth);
1492         pfx->min_export = pfx->max_export = depth;
1493      } else if (pfx->min_export != USHRT_MAX) {
1494         /* FIXME: what to do if a station is marked for inferred exports
1495          * but is then explicitly exported?  Currently we just ignore the
1496          * explicit export... */
1497         if (pfx->min_export - 1 > depth) {
1498            report_missing_export(pfx, depth);
1499         } else if (pfx->min_export - 1 < depth) {
1500            /* TRANSLATORS: Here "station" is a survey station, not a train station.
1501             *
1502             * Exporting a station twice gives this error:
1503             *
1504             * *begin example
1505             * *export 1
1506             * *export 1
1507             * 1 2 1.24 045 -6
1508             * *end example */
1509            compile_diagnostic(DIAG_ERR, /*Station “%s” already exported*/66,
1510                               sprint_prefix(pfx));
1511         }
1512         pfx->min_export = depth;
1513      }
1514      skipblanks();
1515   } while (!isEol(ch) && !isComm(ch));
1516}
1517
1518static void
1519cmd_data(void)
1520{
1521   static const sztok dtab[] = {
1522        {"ALTITUDE",     Dz },
1523        {"BACKBEARING",  BackComp },
1524        {"BACKCLINO",    BackClino }, /* alternative name */
1525        {"BACKCOMPASS",  BackComp }, /* alternative name */
1526        {"BACKGRADIENT", BackClino },
1527        {"BACKLENGTH",   BackTape },
1528        {"BACKTAPE",     BackTape }, /* alternative name */
1529        {"BEARING",      Comp },
1530        {"CEILING",      Up }, /* alternative name */
1531        {"CLINO",        Clino }, /* alternative name */
1532        {"COMPASS",      Comp }, /* alternative name */
1533        {"COUNT",        Count }, /* FrCount&ToCount in multiline */
1534        {"DEPTH",        Depth }, /* FrDepth&ToDepth in multiline */
1535        {"DEPTHCHANGE",  DepthChange },
1536        {"DIRECTION",    Dir },
1537        {"DOWN",         Down },
1538        {"DX",           Dx },
1539        {"DY",           Dy },
1540        {"DZ",           Dz },
1541        {"EASTING",      Dx },
1542        {"FLOOR",        Down }, /* alternative name */
1543        {"FROM",         Fr },
1544        {"FROMCOUNT",    FrCount },
1545        {"FROMDEPTH",    FrDepth },
1546        {"GRADIENT",     Clino },
1547        {"IGNORE",       Ignore },
1548        {"IGNOREALL",    IgnoreAll },
1549        {"LEFT",         Left },
1550        {"LENGTH",       Tape },
1551        {"NEWLINE",      Newline },
1552        {"NORTHING",     Dy },
1553        {"RIGHT",        Right },
1554        {"STATION",      Station }, /* Fr&To in multiline */
1555        {"TAPE",         Tape }, /* alternative name */
1556        {"TO",           To },
1557        {"TOCOUNT",      ToCount },
1558        {"TODEPTH",      ToDepth },
1559        {"UP",           Up },
1560        {NULL,           End }
1561   };
1562
1563#define MASK_stns BIT(Fr) | BIT(To) | BIT(Station)
1564#define MASK_tape BIT(Tape) | BIT(BackTape) | BIT(FrCount) | BIT(ToCount) | BIT(Count)
1565#define MASK_dpth BIT(FrDepth) | BIT(ToDepth) | BIT(Depth) | BIT(DepthChange)
1566#define MASK_comp BIT(Comp) | BIT(BackComp)
1567#define MASK_clin BIT(Clino) | BIT(BackClino)
1568
1569#define MASK_NORMAL MASK_stns | BIT(Dir) | MASK_tape | MASK_comp | MASK_clin
1570#define MASK_DIVING MASK_NORMAL | MASK_dpth
1571#define MASK_CARTESIAN MASK_stns | BIT(Dx) | BIT(Dy) | BIT(Dz)
1572#define MASK_CYLPOLAR  MASK_stns | BIT(Dir) | MASK_tape | MASK_comp | MASK_dpth
1573#define MASK_NOSURVEY MASK_stns
1574#define MASK_PASSAGE BIT(Station) | BIT(Left) | BIT(Right) | BIT(Up) | BIT(Down)
1575#define MASK_IGNORE 0 // No readings in this style.
1576
1577   // readings which may be given for each style (index is STYLE_*)
1578   static const unsigned long mask[] = {
1579      MASK_NORMAL, MASK_DIVING, MASK_CARTESIAN, MASK_CYLPOLAR, MASK_NOSURVEY,
1580      MASK_PASSAGE, MASK_IGNORE
1581   };
1582
1583   // readings which may be omitted for each style (index is STYLE_*)
1584   static const unsigned long mask_optional[] = {
1585      BIT(Dir) | BIT(Clino) | BIT(BackClino),
1586      BIT(Dir) | BIT(Clino) | BIT(BackClino),
1587      0,
1588      BIT(Dir),
1589      0,
1590      0, /* BIT(Left) | BIT(Right) | BIT(Up) | BIT(Down), */
1591      0
1592   };
1593
1594   /* all valid readings */
1595   static const unsigned long mask_all[] = {
1596      MASK_NORMAL | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1597      MASK_DIVING | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1598      MASK_CARTESIAN | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1599      MASK_CYLPOLAR | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1600      MASK_NOSURVEY | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1601      MASK_PASSAGE | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1602      MASK_IGNORE
1603   };
1604#define STYLE_DEFAULT   -2
1605#define STYLE_UNKNOWN   -1
1606
1607   static const sztok styletab[] = {
1608        {"CARTESIAN",    STYLE_CARTESIAN },
1609        {"CYLPOLAR",     STYLE_CYLPOLAR },
1610        {"DEFAULT",      STYLE_DEFAULT },
1611        {"DIVING",       STYLE_DIVING },
1612        {"IGNORE",       STYLE_IGNORE },
1613        {"NORMAL",       STYLE_NORMAL },
1614        {"NOSURVEY",     STYLE_NOSURVEY },
1615        {"PASSAGE",      STYLE_PASSAGE },
1616        {"TOPOFIL",      STYLE_NORMAL },
1617        {NULL,           STYLE_UNKNOWN }
1618   };
1619
1620#define m_multi (BIT(Station) | BIT(Count) | BIT(Depth))
1621
1622   int style, k = 0;
1623   reading d;
1624   unsigned long mUsed = 0;
1625   int old_style = pcs->style;
1626
1627   /* after a bad *data command ignore survey data until the next
1628    * *data command to avoid an avalanche of errors */
1629   pcs->recorded_style = pcs->style = STYLE_IGNORE;
1630
1631   get_token();
1632   style = match_tok(styletab, TABSIZE(styletab));
1633
1634   if (style == STYLE_DEFAULT) {
1635      default_style(pcs);
1636      return;
1637   }
1638
1639   if (style == STYLE_IGNORE) {
1640      return;
1641   }
1642
1643   if (style == STYLE_UNKNOWN) {
1644      if (s_empty(&token)) {
1645         /* "*data" without arguments reinitialises the current style - useful
1646          * when using *data passage as it provides a way to break the passage
1647          * tube without having to repeat the full *data passage command.
1648          */
1649         pcs->recorded_style = pcs->style = style = old_style;
1650         goto reinit_style;
1651      }
1652      /* TRANSLATORS: e.g. trying to refer to an invalid FNORD data style */
1653      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Data style “%s” unknown*/65, s_str(&token));
1654      return;
1655   }
1656
1657   skipblanks();
1658#ifndef NO_DEPRECATED
1659   /* Olde syntax had optional field for survey grade, so allow an omit
1660    * but issue a warning about it */
1661   if (isOmit(ch)) {
1662      static int data_depr_count = 0;
1663      if (data_depr_count < 5) {
1664         compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*“*data %s %c …” is deprecated - use “*data %s …” instead*/104,
1665                            s_str(&token), ch, s_str(&token));
1666         if (++data_depr_count == 5)
1667            compile_diagnostic(DIAG_INFO, /*Further uses of this deprecated feature will not be reported*/95);
1668      }
1669      nextch();
1670   }
1671#endif
1672
1673   int kMac = 6; /* minimum for NORMAL style */
1674   reading *new_order = osmalloc(kMac * sizeof(reading));
1675   char *style_name = s_steal(&token);
1676   do {
1677      filepos fp;
1678      get_pos(&fp);
1679      get_token();
1680      d = match_tok(dtab, TABSIZE(dtab));
1681      if (d == End && !s_empty(&token)) {
1682         compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP,
1683                            /*Reading “%s” not allowed in data style “%s”*/63,
1684                            s_str(&token), style_name);
1685         free(style_name);
1686         free(new_order);
1687         return;
1688      }
1689
1690      /* only token allowed after IGNOREALL is NEWLINE */
1691      if (k && new_order[k - 1] == IgnoreAll && d != Newline) {
1692         set_pos(&fp);
1693         break;
1694      }
1695      /* Note: an unknown token is reported as trailing garbage */
1696      if (!TSTBIT(mask_all[style], d)) {
1697         /* TRANSLATORS: a data "style" is something like NORMAL, DIVING, etc.
1698          * a "reading" is one of FROM, TO, TAPE, COMPASS, CLINO for NORMAL
1699          * style.  Neither "style" nor "reading" is a keyword in the program.
1700          *
1701          * This error complains about a "DEPTH" gauge reading in "NORMAL"
1702          * style, for example.
1703          */
1704         compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP,
1705                            /*Reading “%s” not allowed in data style “%s”*/63,
1706                            s_str(&token), style_name);
1707         free(style_name);
1708         free(new_order);
1709         return;
1710      }
1711      if (TSTBIT(mUsed, Newline) && TSTBIT(m_multi, d)) {
1712         /* TRANSLATORS: caused by e.g.
1713          *
1714          * *data diving station newline depth tape compass
1715          *
1716          * ("depth" needs to occur before "newline"). */
1717         compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP,
1718                            /*Reading “%s” must occur before NEWLINE*/225, s_str(&token));
1719         free(style_name);
1720         free(new_order);
1721         return;
1722      }
1723      /* Check for duplicates unless it's a special reading:
1724       *   IGNOREALL,IGNORE (duplicates allowed) ; END (not possible)
1725       */
1726      if (!((BIT(Ignore) | BIT(End) | BIT(IgnoreAll)) & BIT(d))) {
1727         if (TSTBIT(mUsed, d)) {
1728            /* TRANSLATORS: complains about a situation like trying to define
1729             * two from stations per leg */
1730            compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Duplicate reading “%s”*/67, s_str(&token));
1731            free(style_name);
1732            free(new_order);
1733            return;
1734         } else {
1735            /* Check for previously listed readings which are incompatible
1736             * with this one - e.g. Count vs FrCount */
1737            bool fBad = false;
1738            switch (d) {
1739             case Station:
1740               if (mUsed & (BIT(Fr) | BIT(To))) fBad = true;
1741               break;
1742             case Fr: case To:
1743               if (TSTBIT(mUsed, Station)) fBad = true;
1744               break;
1745             case Count:
1746               if (mUsed & (BIT(FrCount) | BIT(ToCount) | BIT(Tape)))
1747                  fBad = true;
1748               break;
1749             case FrCount: case ToCount:
1750               if (mUsed & (BIT(Count) | BIT(Tape)))
1751                  fBad = true;
1752               break;
1753             case Depth:
1754               if (mUsed & (BIT(FrDepth) | BIT(ToDepth) | BIT(DepthChange)))
1755                  fBad = true;
1756               break;
1757             case FrDepth: case ToDepth:
1758               if (mUsed & (BIT(Depth) | BIT(DepthChange))) fBad = true;
1759               break;
1760             case DepthChange:
1761               if (mUsed & (BIT(FrDepth) | BIT(ToDepth) | BIT(Depth)))
1762                  fBad = true;
1763               break;
1764             case Newline:
1765               if (mUsed & ~m_multi) {
1766                  /* TRANSLATORS: e.g.
1767                   *
1768                   * *data normal from to tape newline compass clino */
1769                  compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*NEWLINE can only be preceded by STATION, DEPTH, and COUNT*/226);
1770                  free(style_name);
1771                  free(new_order);
1772                  return;
1773               }
1774               if (k == 0) {
1775                  /* TRANSLATORS: error from:
1776                   *
1777                   * *data normal newline from to tape compass clino */
1778                  compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*NEWLINE can’t be the first reading*/222);
1779                  free(style_name);
1780                  free(new_order);
1781                  return;
1782               }
1783               break;
1784             default: /* avoid compiler warnings about unhandled enums */
1785               break;
1786            }
1787            if (fBad) {
1788               /* Not entirely happy with phrasing this... */
1789               /* TRANSLATORS: This is an error from the *DATA command.  It
1790                * means that a reading (which will appear where %s is isn't
1791                * valid as the list of readings has already included the same
1792                * reading, or an equivalent one (e.g. you can't have both
1793                * DEPTH and DEPTHCHANGE together). */
1794               compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Reading “%s” duplicates previous reading(s)*/77,
1795                                  s_str(&token));
1796               free(style_name);
1797               free(new_order);
1798               return;
1799            }
1800            mUsed |= BIT(d); /* used to catch duplicates */
1801         }
1802      }
1803      if (k && new_order[k - 1] == IgnoreAll) {
1804         SVX_ASSERT(d == Newline);
1805         k--;
1806         d = IgnoreAllAndNewLine;
1807      }
1808      if (k >= kMac) {
1809         kMac = kMac * 2;
1810         new_order = osrealloc(new_order, kMac * sizeof(reading));
1811      }
1812      new_order[k++] = d;
1813   } while (d != End);
1814
1815   if (k >= 2 && new_order[k - 2] == Newline) {
1816      /* TRANSLATORS: error from:
1817       *
1818       * *data normal from to tape compass clino newline */
1819      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*NEWLINE can’t be the last reading*/223);
1820      free(style_name);
1821      free(new_order);
1822      return;
1823   }
1824
1825   if (style == STYLE_NOSURVEY) {
1826      if (TSTBIT(mUsed, Station)) {
1827         if (k >= kMac) {
1828            kMac = kMac * 2;
1829            new_order = osrealloc(new_order, kMac * sizeof(reading));
1830         }
1831         new_order[k - 1] = Newline;
1832         new_order[k++] = End;
1833      }
1834   } else if (style == STYLE_PASSAGE) {
1835      /* Station doesn't mean "multiline" for STYLE_PASSAGE. */
1836   } else if (!TSTBIT(mUsed, Newline) && (m_multi & mUsed)) {
1837      /* TRANSLATORS: Error given by something like:
1838       *
1839       * *data normal station tape compass clino
1840       *
1841       * ("station" signifies interleaved data). */
1842      compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Interleaved readings, but no NEWLINE*/224);
1843      free(style_name);
1844      free(new_order);
1845      return;
1846   }
1847
1848#if 0
1849   printf("mUsed = 0x%x\n", mUsed);
1850#endif
1851
1852   /* Check the supplied readings form a sufficient set. */
1853   if (style != STYLE_PASSAGE) {
1854       if ((mUsed & (BIT(Fr) | BIT(To))) == (BIT(Fr) | BIT(To)))
1855           mUsed |= BIT(Station);
1856       else if (TSTBIT(mUsed, Station))
1857           mUsed |= BIT(Fr) | BIT(To);
1858   }
1859
1860   if (mUsed & (BIT(Comp) | BIT(BackComp)))
1861      mUsed |= BIT(Comp) | BIT(BackComp);
1862
1863   if (mUsed & (BIT(Clino) | BIT(BackClino)))
1864      mUsed |= BIT(Clino) | BIT(BackClino);
1865
1866   if ((mUsed & (BIT(FrDepth) | BIT(ToDepth))) == (BIT(FrDepth) | BIT(ToDepth)))
1867      mUsed |= BIT(Depth) | BIT(DepthChange);
1868   else if (mUsed & (BIT(Depth) | BIT(DepthChange)))
1869      mUsed |= BIT(FrDepth) | BIT(ToDepth) | BIT(Depth) | BIT(DepthChange);
1870
1871   if ((mUsed & (BIT(FrCount) | BIT(ToCount))) == (BIT(FrCount) | BIT(ToCount)))
1872      mUsed |= BIT(Count) | BIT(Tape) | BIT(BackTape);
1873   else if (mUsed & (BIT(Count) | BIT(Tape) | BIT(BackTape)))
1874      mUsed |= BIT(FrCount) | BIT(ToCount) | BIT(Count) | BIT(Tape) | BIT(BackTape);
1875
1876#if 0
1877   printf("mUsed = 0x%x, opt = 0x%x, mask = 0x%x\n", mUsed,
1878          mask_optional[style], mask[style]);
1879#endif
1880
1881   if (((mUsed &~ BIT(Newline)) | mask_optional[style]) != mask[style]) {
1882      /* Test should only fail with too few bits set, not too many */
1883      SVX_ASSERT((((mUsed &~ BIT(Newline)) | mask_optional[style])
1884              &~ mask[style]) == 0);
1885      /* TRANSLATORS: i.e. not enough readings for the style. */
1886      compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Too few readings for data style “%s”*/64, style_name);
1887      free(style_name);
1888      free(new_order);
1889      return;
1890   }
1891
1892   /* don't free default ordering or ordering used by parent */
1893   if (pcs->ordering != default_order &&
1894       !(pcs->next && pcs->next->ordering == pcs->ordering))
1895      free((reading*)pcs->ordering);
1896
1897   pcs->recorded_style = pcs->style = style;
1898   pcs->ordering = new_order;
1899
1900   free(style_name);
1901
1902reinit_style:
1903   if (style == STYLE_PASSAGE) {
1904      lrudlist * new_psg = osnew(lrudlist);
1905      new_psg->tube = NULL;
1906      new_psg->next = model;
1907      model = new_psg;
1908      next_lrud = &(new_psg->tube);
1909   }
1910}
1911
1912static void
1913cmd_units(void)
1914{
1915   int units, quantity;
1916   unsigned long qmask;
1917   unsigned long m; /* mask with bit x set to indicate quantity x specified */
1918   real factor;
1919   filepos fp;
1920
1921   qmask = get_qlist(BIT(Q_POS)|BIT(Q_PLUMB)|BIT(Q_LEVEL));
1922
1923   if (!qmask) return;
1924   if (qmask == BIT(Q_DEFAULT)) {
1925      default_units(pcs);
1926      return;
1927   }
1928
1929   get_pos(&fp);
1930   factor = read_numeric(true);
1931   if (factor == 0.0) {
1932      set_pos(&fp);
1933      /* TRANSLATORS: error message given by "*units tape 0 feet" - it’s
1934       * meaningless to say your tape is marked in "0 feet" (but you might
1935       * measure distance by counting knots on a diving line, and tie them
1936       * every "2 feet"). */
1937      compile_diagnostic(DIAG_ERR|DIAG_WORD, /**UNITS factor must be non-zero*/200);
1938      skipline();
1939      return;
1940   }
1941
1942   units = get_units(qmask, true);
1943   if (units == UNITS_NULL) return;
1944   if (TSTBIT(qmask, Q_GRADIENT))
1945      pcs->f_clino_percent = (units == UNITS_PERCENT);
1946   if (TSTBIT(qmask, Q_BACKGRADIENT))
1947      pcs->f_backclino_percent = (units == UNITS_PERCENT);
1948
1949   if (TSTBIT(qmask, Q_BEARING)) {
1950      pcs->f_bearing_quadrants = (units == UNITS_QUADRANTS);
1951   }
1952   if (TSTBIT(qmask, Q_BACKBEARING)) {
1953      pcs->f_backbearing_quadrants = (units == UNITS_QUADRANTS);
1954   }
1955
1956   if (factor == HUGE_REAL) {
1957      factor = factor_tab[units];
1958   } else {
1959      factor *= factor_tab[units];
1960   }
1961
1962   for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1)
1963      if (qmask & m) pcs->units[quantity] = factor;
1964}
1965
1966static void
1967cmd_calibrate(void)
1968{
1969   real sc, z;
1970   unsigned long qmask, m;
1971   int quantity;
1972   filepos fp;
1973
1974   qmask = get_qlist(BIT(Q_POS)|BIT(Q_PLUMB)|BIT(Q_LEVEL));
1975   if (!qmask) return; /* error already reported */
1976
1977   if (qmask == BIT(Q_DEFAULT)) {
1978      default_calib(pcs);
1979      return;
1980   }
1981
1982   if (((qmask & LEN_QMASK)) && ((qmask & ANG_QMASK))) {
1983      /* TRANSLATORS: e.g.
1984       *
1985       * *calibrate tape compass 1 1
1986       */
1987      compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Can’t calibrate angular and length quantities together*/227);
1988      return;
1989   }
1990
1991   z = read_numeric(false);
1992   get_pos(&fp);
1993   sc = read_numeric(true);
1994   if (sc == HUGE_REAL) {
1995      if (isalpha(ch)) {
1996         int units = get_units(qmask, false);
1997         if (units == UNITS_NULL) {
1998            return;
1999         }
2000         z *= factor_tab[units];
2001         sc = read_numeric(true);
2002         if (sc == HUGE_REAL) {
2003            sc = (real)1.0;
2004         } else {
2005            /* Adjustment applied is: (reading - z) * sc
2006             * We want: reading * sc - z
2007             * So divide z by sc so the applied adjustment does what we want.
2008             */
2009            z /= sc;
2010         }
2011      } else {
2012         sc = (real)1.0;
2013      }
2014   }
2015
2016   if (sc == HUGE_REAL) sc = (real)1.0;
2017   /* check for declination scale */
2018   if (TSTBIT(qmask, Q_DECLINATION) && sc != 1.0) {
2019      set_pos(&fp);
2020      /* TRANSLATORS: DECLINATION is a built-in keyword, so best not to
2021       * translate */
2022      compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Scale factor must be 1.0 for DECLINATION*/40);
2023      skipline();
2024      return;
2025   }
2026   if (sc == 0.0) {
2027      set_pos(&fp);
2028      /* TRANSLATORS: If the scale factor for an instrument is zero, then any
2029       * reading would be mapped to zero, which doesn't make sense. */
2030      compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Scale factor must be non-zero*/391);
2031      skipline();
2032      return;
2033   }
2034   for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1) {
2035      if (qmask & m) {
2036         pcs->z[quantity] = pcs->units[quantity] * z;
2037         pcs->sc[quantity] = sc;
2038      }
2039   }
2040}
2041
2042static const sztok north_tab[] = {
2043     { "GRID",          GRID_NORTH },
2044     { "MAGNETIC",      MAGNETIC_NORTH },
2045     { "TRUE",          TRUE_NORTH },
2046     { NULL,            -1 }
2047};
2048
2049static void
2050cmd_cartesian(void)
2051{
2052    get_token();
2053    int north = match_tok(north_tab, TABSIZE(north_tab));
2054    if (north < 0) {
2055        compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP,
2056                           /*Expecting “%s”, “%s”, or “%s”*/188,
2057                           "GRID", "MAGNETIC", "TRUE");
2058        return;
2059    }
2060    pcs->cartesian_north = north;
2061    pcs->cartesian_rotation = 0.0;
2062
2063    skipblanks();
2064    if (!isEol(ch) && !isComm(ch)) {
2065        real rotation = read_numeric(false);
2066        // Accept the same units as *declination does.
2067        int units = get_units(BIT(Q_DECLINATION), false);
2068        if (units == UNITS_NULL) {
2069            return;
2070        }
2071        pcs->cartesian_rotation = rotation * factor_tab[units];
2072    }
2073}
2074
2075static void
2076cmd_declination(void)
2077{
2078    real v = read_numeric(true);
2079    if (v == HUGE_REAL) {
2080        get_token_legacy_no_blanks();
2081        if (!S_EQ(&uctoken, "AUTO")) {
2082            compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_COL, /*Expected number or “AUTO”*/309);
2083            return;
2084        }
2085        do_legacy_token_warning();
2086        if (!pcs->proj_str) {
2087            // TRANSLATORS: %s is replaced by the command that requires it, e.g.
2088            // *DECLINATION AUTO
2089            compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_TOKEN,
2090                               /*Input coordinate system must be specified for “%s”*/301,
2091                               "*DECLINATION AUTO");
2092            return;
2093        }
2094
2095        /* *declination auto X Y Z */
2096        real x = read_numeric(false);
2097        real y = read_numeric(false);
2098        real z = read_numeric(false);
2099        set_declination_location(x, y, z, pcs->proj_str);
2100    } else {
2101        /* *declination D UNITS */
2102        int units = get_units(BIT(Q_DECLINATION), false);
2103        if (units == UNITS_NULL) {
2104            return;
2105        }
2106        pcs->z[Q_DECLINATION] = -v * factor_tab[units];
2107        pcs->convergence = 0;
2108    }
2109}
2110
2111#ifndef NO_DEPRECATED
2112static void
2113cmd_default(void)
2114{
2115   static const sztok defaulttab[] = {
2116      { "CALIBRATE", CMD_CALIBRATE },
2117      { "DATA",      CMD_DATA },
2118      { "UNITS",     CMD_UNITS },
2119      { NULL,        CMD_NULL }
2120   };
2121   static int default_depr_count = 0;
2122
2123   if (default_depr_count < 5) {
2124      /* TRANSLATORS: If you're unsure what "deprecated" means, see:
2125       * https://en.wikipedia.org/wiki/Deprecation */
2126      compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /**DEFAULT is deprecated - use *CALIBRATE/DATA/SD/UNITS with argument DEFAULT instead*/20);
2127      if (++default_depr_count == 5)
2128         compile_diagnostic(DIAG_INFO, /*Further uses of this deprecated feature will not be reported*/95);
2129   }
2130
2131   get_token();
2132   switch (match_tok(defaulttab, TABSIZE(defaulttab))) {
2133    case CMD_CALIBRATE:
2134      default_calib(pcs);
2135      break;
2136    case CMD_DATA:
2137      default_style(pcs);
2138      default_grade(pcs);
2139      pcs->cartesian_north = TRUE_NORTH;
2140      pcs->cartesian_rotation = 0.0;
2141      break;
2142    case CMD_UNITS:
2143      default_units(pcs);
2144      break;
2145    default:
2146      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown setting “%s”*/41,
2147                         s_str(&token));
2148   }
2149}
2150#endif
2151
2152static void
2153cmd_include(void)
2154{
2155   char *pth = NULL;
2156   string fnm = S_INIT;
2157#ifndef NO_DEPRECATED
2158   prefix *root_store;
2159#endif
2160   int ch_store;
2161
2162   pth = path_from_fnm(file.filename);
2163
2164   read_string(&fnm);
2165
2166#ifndef NO_DEPRECATED
2167   /* Since *begin / *end nesting cannot cross file boundaries we only
2168    * need to preserve the prefix if the deprecated *prefix command
2169    * can be used */
2170   root_store = root;
2171   root = pcs->Prefix; /* Root for include file is current prefix */
2172#endif
2173   ch_store = ch;
2174
2175   data_file(pth, s_str(&fnm));
2176
2177#ifndef NO_DEPRECATED
2178   root = root_store; /* and restore root */
2179#endif
2180   ch = ch_store;
2181
2182   s_free(&fnm);
2183   free(pth);
2184}
2185
2186static void
2187cmd_sd(void)
2188{
2189   real sd, variance;
2190   int units;
2191   unsigned long qmask, m;
2192   int quantity;
2193   qmask = get_qlist(BIT(Q_DECLINATION));
2194   if (!qmask) return; /* no quantities found - error already reported */
2195
2196   if (qmask == BIT(Q_DEFAULT)) {
2197      default_grade(pcs);
2198      return;
2199   }
2200   sd = read_numeric(false);
2201   if (sd <= (real)0.0) {
2202      compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_COL, /*Standard deviation must be positive*/48);
2203      return;
2204   }
2205   units = get_units(qmask, false);
2206   if (units == UNITS_NULL) return;
2207
2208   sd *= factor_tab[units];
2209   variance = sqrd(sd);
2210
2211   for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1)
2212      if (qmask & m) pcs->Var[quantity] = variance;
2213}
2214
2215enum {
2216    ROLE_BACKTAPE,
2217    ROLE_BACKCOMPASS,
2218    ROLE_BACKCLINO,
2219    ROLE_TAPE,
2220    ROLE_COMPASS,
2221    ROLE_CLINO,
2222    ROLE_COUNTER,
2223    ROLE_DEPTH,
2224    ROLE_STATION,
2225    ROLE_POSITION,
2226    ROLE_NOTES,
2227    ROLE_PICTURES,
2228    ROLE_INSTRUMENTS,
2229    ROLE_ASSISTANT,
2230    ROLE_ALTITUDE,
2231    ROLE_DIMENSIONS,
2232    ROLE_LEFT,
2233    ROLE_RIGHT,
2234    ROLE_UP,
2235    ROLE_DOWN,
2236    ROLE_EXPLORER
2237};
2238
2239static const sztok role_tab[] = {
2240    {"ALTITUDE",        ROLE_ALTITUDE},
2241    {"ASSISTANT",       ROLE_ASSISTANT},
2242    {"BACKBEARING",     ROLE_BACKCOMPASS},
2243    {"BACKCLINO",       ROLE_BACKCLINO},
2244    {"BACKCOMPASS",     ROLE_BACKCOMPASS},
2245    {"BACKGRADIENT",    ROLE_BACKCLINO},
2246    {"BACKLENGTH",      ROLE_BACKTAPE},
2247    {"BACKTAPE",        ROLE_BACKTAPE},
2248    {"BEARING",         ROLE_COMPASS},
2249    {"CEILING",         ROLE_UP},
2250    {"CLINO",           ROLE_CLINO},
2251    {"COMPASS",         ROLE_COMPASS},
2252    {"COUNT",           ROLE_COUNTER},
2253    {"COUNTER",         ROLE_COUNTER},
2254    {"DEPTH",           ROLE_DEPTH},
2255    {"DIMENSIONS",      ROLE_DIMENSIONS},
2256    {"DOG",             ROLE_ASSISTANT},
2257    {"DOWN",            ROLE_DOWN},
2258    {"DZ",              ROLE_ALTITUDE},
2259    {"EXPLORER",        ROLE_EXPLORER},
2260    {"FLOOR",           ROLE_DOWN},
2261    {"GRADIENT",        ROLE_CLINO},
2262    {"INSTRUMENTS",     ROLE_INSTRUMENTS},
2263    {"INSTS",           ROLE_INSTRUMENTS},
2264    {"LEFT",            ROLE_LEFT},
2265    {"LENGTH",          ROLE_TAPE},
2266    {"NOTEBOOK",        ROLE_NOTES},
2267    {"NOTES",           ROLE_NOTES},
2268    {"PICS",            ROLE_PICTURES},
2269    {"PICTURES",        ROLE_PICTURES},
2270    {"POSITION",        ROLE_POSITION},
2271    {"RIGHT",           ROLE_RIGHT},
2272    {"STATION",         ROLE_STATION},
2273    {"TAPE",            ROLE_TAPE},
2274    {"UP",              ROLE_UP},
2275    {NULL,              -1}
2276};
2277
2278static void
2279cmd_team(void)
2280{
2281    string name = S_INIT;
2282    if (!read_string_warning(&name)) {
2283        skipline();
2284        return;
2285    }
2286    s_free(&name);
2287
2288    while (true) {
2289        skipblanks();
2290        if (isComm(ch) || isEol(ch)) return;
2291        get_token();
2292        int role = match_tok(role_tab, TABSIZE(role_tab));
2293        if (role < 0) {
2294            // Skip after a bad role to avoid triggering multiple warnings for
2295            // one *team command in existing data from before this check was
2296            // implemented.
2297            compile_diagnostic(DIAG_WARN|DIAG_TOKEN|DIAG_SKIP, /*Unknown team role “%s”*/532,
2298                               s_str(&token));
2299            return;
2300        }
2301    }
2302}
2303
2304static void
2305cmd_title(void)
2306{
2307   if (!fExplicitTitle && pcs->Prefix == root) {
2308       /* If we don't have an explicit title yet, and we're currently in the
2309        * root prefix, use this title explicitly. */
2310      fExplicitTitle = true;
2311      read_string(&survey_title);
2312   } else {
2313      /* parse and throw away this title (but still check rest of line) */
2314      string s = S_INIT;
2315      read_string(&s);
2316      s_free(&s);
2317   }
2318}
2319
2320static const sztok case_tab[] = {
2321     {"PRESERVE", OFF},
2322     {"TOLOWER",  LOWER},
2323     {"TOUPPER",  UPPER},
2324     {NULL,       -1}
2325};
2326
2327static void
2328cmd_case(void)
2329{
2330   int setting;
2331   get_token();
2332   setting = match_tok(case_tab, TABSIZE(case_tab));
2333   if (setting != -1) {
2334      pcs->Case = setting;
2335   } else {
2336      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found “%s”, expecting “PRESERVE”, “TOUPPER”, or “TOLOWER”*/10, s_str(&token));
2337   }
2338}
2339
2340static void
2341cmd_copyright(void)
2342{
2343    skipblanks();
2344    filepos fp;
2345    get_pos(&fp);
2346    unsigned y1 = read_uint_raw(DIAG_WARN|DIAG_UINT, /*Invalid year*/534, NULL);
2347    if (y1 < 1000) {
2348        set_pos(&fp);
2349        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year*/534, y1);
2350    }
2351    if (ch == '-') {
2352        nextch();
2353        get_pos(&fp);
2354        unsigned y2 = read_uint_raw(DIAG_WARN|DIAG_UINT, /*Invalid year*/534, NULL);
2355        if (y2 < 1000) {
2356            set_pos(&fp);
2357            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year*/534, y2);
2358        } else if (y2 < y1) {
2359            set_pos(&fp);
2360            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*End of date range is before the start*/81);
2361        }
2362    }
2363
2364    string text = S_INIT;
2365    if (!read_string_warning(&text)) {
2366        skipline();
2367        return;
2368    }
2369    s_free(&text);
2370
2371    skipblanks();
2372    if (!isComm(ch) && !isEol(ch))
2373        compile_diagnostic(DIAG_WARN|DIAG_TAIL, /*End of line not blank*/15);
2374}
2375
2376typedef enum {
2377    CS_NONE = -1,
2378    CS_CUSTOM,
2379    CS_EPSG,
2380    CS_ESRI,
2381    CS_EUR79Z30,
2382    CS_IJTSK,
2383    CS_IJTSK03,
2384    CS_JTSK,
2385    CS_JTSK03,
2386    CS_LAT,
2387    CS_LOCAL,
2388    CS_LONG,
2389    CS_OSGB,
2390    CS_S_MERC,
2391    CS_UTM
2392} cs_class;
2393
2394static const sztok cs_tab[] = {
2395     {"CUSTOM",   CS_CUSTOM},
2396     {"EPSG",     CS_EPSG},     /* EPSG:<number> */
2397     {"ESRI",     CS_ESRI},     /* ESRI:<number> */
2398     {"EUR79Z30", CS_EUR79Z30},
2399     {"IJTSK",    CS_IJTSK},
2400     {"IJTSK03",  CS_IJTSK03},
2401     {"JTSK",     CS_JTSK},
2402     {"JTSK03",   CS_JTSK03},
2403     {"LAT",      CS_LAT},      /* LAT-LONG */
2404     {"LOCAL",    CS_LOCAL},
2405     {"LONG",     CS_LONG},     /* LONG-LAT */
2406     {"OSGB",     CS_OSGB},     /* OSGB:<H, N, O, S or T><A-Z except I> */
2407     {"S",        CS_S_MERC},   /* S-MERC */
2408     // UTM<zone><N or S or nothing> is handled separately to avoid needing 180
2409     // entries in this lookup table.
2410     {NULL,       CS_NONE}
2411};
2412
2413static void
2414cmd_cs(void)
2415{
2416   char *proj_str = NULL;
2417   cs_class cs;
2418   int cs_sub = INT_MIN;
2419   filepos fp;
2420   bool output = false;
2421   enum { YES, NO, MAYBE } ok_for_output = YES;
2422   static bool had_cs = false;
2423
2424   if (!had_cs) {
2425      had_cs = true;
2426      if (first_fix_name) {
2427         compile_diagnostic_at(DIAG_ERR,
2428                               first_fix_filename, first_fix_line,
2429                               /*Station “%s” fixed before CS command first used*/442,
2430                               sprint_prefix(first_fix_name));
2431      }
2432   }
2433
2434   skipblanks();
2435   get_pos(&fp);
2436   get_token_no_blanks();
2437   if (S_EQ(&uctoken, "OUT")) {
2438      output = true;
2439      skipblanks();
2440      get_pos(&fp);
2441      get_token_no_blanks();
2442   }
2443
2444   if (s_len(&uctoken) > 3 &&
2445       memcmp(s_str(&uctoken), "UTM", 3) == 0 &&
2446       isdigit((unsigned char)s_str(&uctoken)[3])) {
2447       // The token starts "UTM" followed by a digit so handle that separately
2448       // to avoid needing 180 entries for UTM zones in the cs_tab lookup
2449       // table.
2450       cs = CS_UTM;
2451       // Reposition on the digit after "UTM".
2452       set_pos(&fp);
2453       nextch();
2454       nextch();
2455       nextch();
2456       unsigned n = read_uint();
2457       if (n >= 1 && n <= 60) {
2458           int uch = toupper(ch);
2459           cs_sub = (int)n;
2460           if (uch == 'S') {
2461               nextch();
2462               cs_sub = -cs_sub;
2463           } else if (uch == 'N') {
2464               nextch();
2465           }
2466       }
2467   } else {
2468       cs = match_tok(cs_tab, TABSIZE(cs_tab));
2469       switch (cs) {
2470         case CS_NONE:
2471           break;
2472         case CS_CUSTOM:
2473           ok_for_output = MAYBE;
2474           get_pos(&fp);
2475           string str = S_INIT;
2476           read_string(&str);
2477           proj_str = s_steal(&str);
2478           cs_sub = 0;
2479           break;
2480         case CS_EPSG: case CS_ESRI:
2481           ok_for_output = MAYBE;
2482           if (ch == ':' && isdigit(nextch())) {
2483               unsigned n = read_uint();
2484               if (n < 1000000) {
2485                   cs_sub = (int)n;
2486               }
2487           }
2488           break;
2489         case CS_EUR79Z30:
2490           cs_sub = 0;
2491           break;
2492         case CS_JTSK:
2493         case CS_JTSK03:
2494           ok_for_output = NO;
2495           cs_sub = 0;
2496           break;
2497         case CS_IJTSK:
2498         case CS_IJTSK03:
2499           cs_sub = 0;
2500           break;
2501         case CS_LAT: case CS_LONG:
2502           ok_for_output = NO;
2503           if (ch == '-') {
2504               nextch();
2505               get_token_no_blanks();
2506               cs_class cs2 = match_tok(cs_tab, TABSIZE(cs_tab));
2507               if ((cs ^ cs2) == (CS_LAT ^ CS_LONG)) {
2508                   cs_sub = 0;
2509               }
2510           }
2511           break;
2512         case CS_LOCAL:
2513           cs_sub = 0;
2514           break;
2515         case CS_OSGB:
2516           if (ch == ':') {
2517               int uch1 = toupper(nextch());
2518               if (strchr("HNOST", uch1)) {
2519                   int uch2 = toupper(nextch());
2520                   if (uch2 >= 'A' && uch2 <= 'Z' && uch2 != 'I') {
2521                       int x, y;
2522                       nextch();
2523                       if (uch1 > 'I') --uch1;
2524                       uch1 -= 'A';
2525                       if (uch2 > 'I') --uch2;
2526                       uch2 -= 'A';
2527                       x = uch1 % 5;
2528                       y = uch1 / 5;
2529                       x = (x * 5) + uch2 % 5;
2530                       y = (y * 5) + uch2 / 5;
2531                       cs_sub = y * 25 + x;
2532                   }
2533               }
2534           }
2535           break;
2536         case CS_S_MERC:
2537           if (ch == '-') {
2538               nextch();
2539               get_token_no_blanks();
2540               if (S_EQ(&uctoken, "MERC")) {
2541                   cs_sub = 0;
2542               }
2543           }
2544           break;
2545         case CS_UTM:
2546           // Handled outside of this switch, but avoid compiler warning about
2547           // unhandled enumeration value.
2548           break;
2549       }
2550   }
2551   if (cs_sub == INT_MIN || isalnum(ch)) {
2552      set_pos(&fp);
2553      compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Unknown coordinate system*/434);
2554      skipline();
2555      return;
2556   }
2557   /* Actually handle the cs */
2558   switch (cs) {
2559      case CS_NONE:
2560         break;
2561      case CS_CUSTOM:
2562         /* proj_str already set */
2563         break;
2564      case CS_EPSG:
2565         proj_str = osmalloc(32);
2566         snprintf(proj_str, 32, "EPSG:%d", cs_sub);
2567         break;
2568      case CS_ESRI:
2569         proj_str = osmalloc(32);
2570         snprintf(proj_str, 32, "ESRI:%d", cs_sub);
2571         break;
2572      case CS_EUR79Z30:
2573         proj_str = osstrdup("+proj=utm +zone=30 +ellps=intl +towgs84=-86,-98,-119,0,0,0,0 +no_defs");
2574         break;
2575      case CS_IJTSK:
2576         proj_str = osstrdup("+proj=krovak +ellps=bessel +towgs84=570.8285,85.6769,462.842,4.9984,1.5867,5.2611,3.5623 +no_defs");
2577         break;
2578      case CS_IJTSK03:
2579         proj_str = osstrdup("+proj=krovak +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +no_defs");
2580         break;
2581      case CS_JTSK:
2582         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");
2583         break;
2584      case CS_JTSK03:
2585         proj_str = osstrdup("+proj=krovak +czech +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +no_defs");
2586         break;
2587      case CS_LAT:
2588         /* FIXME: Requires PROJ >= 4.8.0 for +axis, and the SDs will be
2589          * misapplied, so we may want to swap ourselves.  Also, while
2590          * therion supports lat-long, I'm not totally convinced that it is
2591          * sensible to do so - people often say "lat-long", but probably
2592          * don't think that that's actually "Northing, Easting".  This
2593          * seems like it'll result in people accidentally getting X and Y
2594          * swapped in their fixed points...
2595          */
2596#if 0
2597         proj_str = osstrdup("+proj=longlat +ellps=WGS84 +datum=WGS84 +axis=neu +no_defs");
2598#endif
2599         break;
2600      case CS_LOCAL:
2601         /* FIXME: Is it useful to be able to explicitly specify this? */
2602         break;
2603      case CS_LONG:
2604         proj_str = osstrdup("EPSG:4326");
2605         break;
2606      case CS_OSGB: {
2607         int x = 14 - (cs_sub % 25);
2608         int y = (cs_sub / 25) - 20;
2609         proj_str = osmalloc(160);
2610         snprintf(proj_str, 160,
2611                  "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 "
2612                  "+x_0=%d +y_0=%d +ellps=airy +datum=OSGB36 +units=m +no_defs",
2613                  x * 100000, y * 100000);
2614         break;
2615      }
2616      case CS_S_MERC:
2617         proj_str = osstrdup("EPSG:3857");
2618         break;
2619      case CS_UTM:
2620         proj_str = osmalloc(32);
2621         if (cs_sub > 0) {
2622            snprintf(proj_str, 32, "EPSG:%d", 32600 + cs_sub);
2623         } else {
2624            snprintf(proj_str, 32, "EPSG:%d", 32700 - cs_sub);
2625         }
2626         break;
2627   }
2628
2629   if (!proj_str) {
2630      /* printf("CS %d:%d\n", (int)cs, cs_sub); */
2631      set_pos(&fp);
2632      compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Unknown coordinate system*/434);
2633      skipline();
2634      return;
2635   }
2636
2637   if (output) {
2638      if (ok_for_output == NO) {
2639         set_pos(&fp);
2640         compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Coordinate system unsuitable for output*/435);
2641         skipline();
2642         return;
2643      }
2644
2645      if (proj_str_out && strcmp(proj_str, proj_str_out) == 0) {
2646          /* Same as the output cs that's already set, so nothing to do. */
2647          free(proj_str);
2648          return;
2649      }
2650
2651      if (ok_for_output == MAYBE) {
2652          /* We only actually create the transformation from input to output when
2653           * we need it, but for a custom proj string or EPSG/ESRI code we need
2654           * to check that the specified coordinate system is valid and also if
2655           * it's suitable for output so we need to test creating it here.
2656           */
2657          proj_errno_reset(NULL);
2658          PJ* pj = proj_create(PJ_DEFAULT_CTX, proj_str);
2659          if (!pj) {
2660              set_pos(&fp);
2661              compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Invalid coordinate system: %s*/443,
2662                                 proj_context_errno_string(PJ_DEFAULT_CTX,
2663                                                           proj_context_errno(PJ_DEFAULT_CTX)));
2664              skipline();
2665              free(proj_str);
2666              return;
2667          }
2668          int type = proj_get_type(pj);
2669          if (type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
2670              type == PJ_TYPE_GEOGRAPHIC_3D_CRS) {
2671              set_pos(&fp);
2672              compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Coordinate system unsuitable for output*/435);
2673              skipline();
2674              free(proj_str);
2675              return;
2676          }
2677      }
2678
2679      if (proj_str_out) {
2680          /* If the output cs is already set, subsequent attempts to set it
2681           * are silently ignored (so you can combine two datasets and set
2682           * the output cs to use before you include either).
2683           */
2684          free(proj_str);
2685      } else {
2686          proj_str_out = proj_str;
2687      }
2688   } else {
2689      if (proj_str_out && strcmp(proj_str, proj_str_out) == 0) {
2690         /* Same as the current output projection, so valid for input. */
2691      } else if (pcs->proj_str && strcmp(proj_str, pcs->proj_str) == 0) {
2692         /* Same as the current input projection, so nothing to do! */
2693         return;
2694      } else if (ok_for_output == MAYBE) {
2695         /* (ok_for_output == MAYBE) also happens to indicate whether we need
2696          * to check that the coordinate system is valid for input.
2697          */
2698         proj_errno_reset(NULL);
2699         PJ* pj = proj_create(PJ_DEFAULT_CTX, proj_str);
2700         if (!pj) {
2701            set_pos(&fp);
2702            compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Invalid coordinate system: %s*/443,
2703                               proj_context_errno_string(PJ_DEFAULT_CTX,
2704                                                         proj_context_errno(PJ_DEFAULT_CTX)));
2705            skipline();
2706            return;
2707         }
2708         proj_destroy(pj);
2709      }
2710
2711      /* Free current input proj_str if not used by parent. */
2712      settings * p = pcs;
2713      if (!p->next || p->proj_str != p->next->proj_str)
2714         free(p->proj_str);
2715      p->proj_str = proj_str;
2716      p->input_convergence = HUGE_REAL;
2717      invalidate_pj_cached();
2718   }
2719}
2720
2721static const sztok infer_tab[] = {
2722     { "EQUATES",       INFER_EQUATES },
2723     { "EXPORTS",       INFER_EXPORTS },
2724     { "PLUMBS",        INFER_PLUMBS },
2725#if 0 /* FIXME */
2726     { "SUBSURVEYS",    INFER_SUBSURVEYS },
2727#endif
2728     { NULL,            INFER_NULL }
2729};
2730
2731static const sztok onoff_tab[] = {
2732     { "OFF", 0 },
2733     { "ON",  1 },
2734     { NULL, -1 }
2735};
2736
2737static void
2738cmd_infer(void)
2739{
2740   infer_what setting;
2741   int on;
2742   get_token();
2743   setting = match_tok(infer_tab, TABSIZE(infer_tab));
2744   if (setting == INFER_NULL) {
2745      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found “%s”, expecting “EQUATES”, “EXPORTS”, or “PLUMBS”*/31, s_str(&token));
2746      return;
2747   }
2748   get_token();
2749   on = match_tok(onoff_tab, TABSIZE(onoff_tab));
2750   if (on == -1) {
2751      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found “%s”, expecting “ON” or “OFF”*/32, s_str(&token));
2752      return;
2753   }
2754
2755   if (on) {
2756      pcs->infer |= BIT(setting);
2757      if (setting == INFER_EXPORTS) fExportUsed = true;
2758   } else {
2759      pcs->infer &= ~BIT(setting);
2760   }
2761}
2762
2763static void
2764cmd_truncate(void)
2765{
2766   unsigned int truncate_at = 0; /* default is no truncation */
2767   filepos fp;
2768
2769   get_pos(&fp);
2770
2771   get_token();
2772   if (!S_EQ(&uctoken, "OFF")) {
2773      if (!s_empty(&uctoken)) set_pos(&fp);
2774      truncate_at = read_uint();
2775   }
2776   /* for backward compatibility, "*truncate 0" means "*truncate off" */
2777   pcs->Truncate = (truncate_at == 0) ? INT_MAX : truncate_at;
2778}
2779
2780static void
2781cmd_ref(void)
2782{
2783   /* Just syntax check for now. */
2784   string ref = S_INIT;
2785   read_string(&ref);
2786   s_free(&ref);
2787}
2788
2789static void
2790cmd_require(void)
2791{
2792    // Add extra 0 so `*require 1.4.10.1` fails with cavern version 1.4.10.
2793    const unsigned version[] = {COMMAVERSION, 0};
2794
2795    skipblanks();
2796    filepos fp;
2797    get_pos(&fp);
2798
2799    // Parse the required version number, storing its components in
2800    // required_version.  We only store at most one more component than
2801    // COMMAVERSION has since more than that can't affect the comparison.
2802    size_t i = 0;
2803    int diff = 0;
2804    while (1) {
2805        unsigned component = read_uint();
2806        if (diff == 0 && i < sizeof(version) / sizeof(version[0])) {
2807            if (diff == 0) {
2808                diff = (int)version[i++] - (int)component;
2809            }
2810        }
2811        if (ch != '.' || isBlank(nextch()) || isComm(ch) || isEol(ch))
2812            break;
2813    }
2814
2815    if (diff < 0) {
2816        // Requirement not satisfied
2817        size_t len = (size_t)(ftell(file.fh) - fp.offset);
2818        char *v = osmalloc(len + 1);
2819        set_pos(&fp);
2820        for (size_t j = 0; j < len; j++) {
2821            v[j] = ch;
2822            nextch();
2823        }
2824        v[len] = '\0';
2825        /* TRANSLATORS: Feel free to translate as "or newer" instead of "or
2826         * greater" if that gives a more natural translation.  It's
2827         * technically not quite right when there are parallel active release
2828         * series (e.g. Survex 1.0.40 was released *after* 1.2.0), but this
2829         * seems unlikely to confuse users.  "Survex" is the name of the
2830         * software, so should not be translated.
2831         *
2832         * Here "survey" is a "cave map" rather than list of questions - it should be
2833         * translated to the terminology that cavers using the language would use.
2834         */
2835        compile_diagnostic(DIAG_FATAL|DIAG_FROM(fp), /*Survex version %s or greater required to process this survey data.*/38, v);
2836        // Does not return so no point freeing v here.
2837    }
2838}
2839
2840/* allocate new meta_data if need be */
2841void
2842copy_on_write_meta(settings *s)
2843{
2844   if (!s->meta || s->meta->ref_count != 0) {
2845       meta_data * meta_new = osnew(meta_data);
2846       if (!s->meta) {
2847           meta_new->days1 = meta_new->days2 = -1;
2848       } else {
2849           *meta_new = *(s->meta);
2850       }
2851       meta_new->ref_count = 0;
2852       s->meta = meta_new;
2853   }
2854}
2855
2856static int
2857read_year(filepos *fp_date_ptr)
2858{
2859    int y = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, fp_date_ptr);
2860    if (y < 100) {
2861        /* Two digit year is 19xx. */
2862        y += 1900;
2863        filepos fp_save;
2864        get_pos(&fp_save);
2865        set_pos(fp_date_ptr);
2866        /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
2867        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Assuming 2 digit year is %d*/76, y);
2868        set_pos(&fp_save);
2869    } else if (y < 1900 || y > 2078) {
2870        set_pos(fp_date_ptr);
2871        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year (< 1900 or > 2078)*/58);
2872        longjmp(jbSkipLine, 1);
2873    }
2874    return y;
2875}
2876
2877static void
2878cmd_date(void)
2879{
2880    enum { DATE_SURVEYED = 1, DATE_EXPLORED = 2 };
2881    unsigned date_flags = 0;
2882    while (get_token(), !s_empty(&uctoken)) {
2883        unsigned new_flag = 0;
2884        if (S_EQ(&uctoken, "SURVEYED")) {
2885            new_flag = DATE_SURVEYED;
2886        } else if (S_EQ(&uctoken, "EXPLORED")) {
2887            new_flag = DATE_EXPLORED;
2888        } else {
2889            compile_diagnostic(DIAG_ERR|DIAG_TOKEN,
2890                               /*Expecting “%s” or “%s”*/103, "SURVEYED", "EXPLORED");
2891            continue;
2892        }
2893
2894        if ((date_flags & new_flag)) {
2895            compile_diagnostic(DIAG_ERR|DIAG_TOKEN,
2896                               /*Duplicate date type “%s”*/416, s_str(&token));
2897        }
2898        date_flags |= new_flag;
2899    }
2900
2901    int date_sep = '-';
2902    if (date_flags == 0) {
2903        // `*date` without qualification sets the surveyed date.
2904        date_flags = DATE_SURVEYED;
2905        // Also allow '.' for compatibility.
2906        date_sep = 0;
2907    }
2908
2909    filepos fp_date1, fp_date2, fp;
2910    get_pos(&fp_date1);
2911    int year = read_year(&fp_date1);
2912    int month = 0, day = 0, year2 = 0, month2 = 0, day2 = 0;
2913
2914    if (ch == '-') {
2915        // Could be ISO-format date (e.g. 2024-10 or 2024-10-21 or 1912-11), or
2916        // a range of old-format dates (e.g. 1973-1975 or 1911-12.02.03) or an
2917        // ambiguous case like 1911-12 which we need to warn about in a `*date`
2918        // command without `surveyed` or `explored` qualifiers.
2919        nextch();
2920        get_pos(&fp_date2);
2921        int v = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date1);
2922        if (date_sep == '-') {
2923            // We're only accepting ISO dates.
2924        } else if (ch == '-') {
2925            // Two `-` so must be an ISO date, e.g. `2024-10-21`.
2926        } else if (v >= 1 && v <= 12) {
2927            // Valid month number so assume it's an ISO date.
2928            if (year < 1900 + v) {
2929                // Warn about ambiguous cases.
2930                compile_diagnostic(DIAG_WARN|DIAG_FROM(fp_date1),
2931                                   /*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,
2932                                   year, v, year, 1900 + v);
2933            }
2934        } else {
2935            date_sep = '.';
2936            year2 = v;
2937            if (year2 < 100) {
2938                /* Two digit year is 19xx. */
2939                year2 += 1900;
2940                /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
2941                compile_diagnostic(DIAG_WARN|DIAG_FROM(fp_date2),
2942                                   /*Assuming 2 digit year is %d*/76, year2);
2943            } else if (year2 < 1900 || year2 > 2078) {
2944                compile_diagnostic(DIAG_WARN|DIAG_FROM(fp_date2),
2945                                   /*Invalid year (< 1900 or > 2078)*/58);
2946                longjmp(jbSkipLine, 1);
2947            }
2948            goto process_dates;
2949        }
2950
2951        date_sep = '-';
2952        month = v;
2953        fp = fp_date2;
2954    } else if (ch == '.') {
2955        if (date_sep == '-') {
2956            char date_sep_string[2] = { date_sep, '\0' };
2957            compile_diagnostic(DIAG_ERR|DIAG_COL|DIAG_SKIP,
2958                               /*Expecting “%s”*/497, date_sep_string);
2959            return;
2960        }
2961        date_sep = ch;
2962        nextch();
2963        get_pos(&fp);
2964        month = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date1);
2965    } else {
2966        // Just a year - might be a ISO date range though.
2967        date_sep = '-';
2968        goto try_date2;
2969    }
2970
2971    if (month < 1 || month > 12) {
2972        set_pos(&fp);
2973        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
2974        longjmp(jbSkipLine, 1);
2975    }
2976
2977    if (ch == date_sep) {
2978        nextch();
2979        get_pos(&fp);
2980        day = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date1);
2981        if (day < 1 || day > last_day(year, month)) {
2982            set_pos(&fp);
2983            /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
2984            compile_diagnostic(DIAG_WARN|DIAG_UINT,
2985                               /*Invalid day of the month*/87);
2986            longjmp(jbSkipLine, 1);
2987        }
2988    }
2989
2990try_date2:
2991    if (date_sep == '-') {
2992        skipblanks();
2993        if (!isdigit(ch)) goto process_dates;
2994    } else if (ch == '-') {
2995        nextch();
2996    } else {
2997        goto process_dates;
2998    }
2999    {
3000        get_pos(&fp_date2);
3001        year2 = read_year(&fp_date2);
3002        if (ch == date_sep) {
3003            nextch();
3004            get_pos(&fp);
3005            month2 = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198,
3006                                   &fp_date2);
3007            if (month2 < 1 || month2 > 12) {
3008                set_pos(&fp);
3009                compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
3010                longjmp(jbSkipLine, 1);
3011            }
3012
3013            if (ch == date_sep) {
3014                nextch();
3015                get_pos(&fp);
3016                day2 = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198,
3017                                     &fp_date2);
3018                if (day2 < 1 || day2 > last_day(year2, month2)) {
3019                    set_pos(&fp);
3020                    /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
3021                    compile_diagnostic(DIAG_WARN|DIAG_UINT,
3022                                       /*Invalid day of the month*/87);
3023                    longjmp(jbSkipLine, 1);
3024                }
3025            }
3026        }
3027    }
3028
3029process_dates:;
3030    bool date_range = (year2 != 0);
3031    if (!date_range) {
3032        year2 = year;
3033        month2 = month;
3034        day2 = day;
3035    }
3036
3037    int days1 = days_since_1900(year, month ? month : 1, day ? day : 1);
3038
3039    if (days1 > current_days_since_1900) {
3040        filepos fp_save;
3041        get_pos(&fp_save);
3042        set_pos(&fp_date1);
3043        compile_diagnostic(DIAG_WARN|DIAG_DATE, /*Date is in the future!*/80);
3044        set_pos(&fp_save);
3045    }
3046
3047    if (month2 == 0) {
3048        month2 = 12;
3049        day2 = 31;
3050    } else if (day2 == 0) {
3051        day2 = last_day(year2, month2);
3052    }
3053    int days2 = days_since_1900(year2, month2, day2);
3054    if (date_range && days2 > current_days_since_1900) {
3055        // If !date_range, either we already emitted this warning when
3056        // processing the start date, or the date is partial and for the
3057        // current year or current month, in which case it's not helpful
3058        // to warn that the end is on the future, but it makes sense to
3059        // process for the end date so the result doesn't change as time
3060        // passes.
3061        filepos fp_save;
3062        get_pos(&fp_save);
3063        set_pos(&fp_date2);
3064        compile_diagnostic(DIAG_WARN|DIAG_DATE, /*Date is in the future!*/80);
3065        set_pos(&fp_save);
3066    }
3067
3068    if (days2 < days1) {
3069        filepos fp_save;
3070        get_pos(&fp_save);
3071        set_pos(&fp_date1);
3072        compile_diagnostic(DIAG_ERR|DIAG_WORD, /*End of date range is before the start*/81);
3073        set_pos(&fp_save);
3074        // Swap range ends to put us in a consistent state.
3075        int tmp = days1;
3076        days1 = days2;
3077        days2 = tmp;
3078    }
3079
3080    if ((date_flags & DATE_SURVEYED)) {
3081        if (!pcs->meta || pcs->meta->days1 != days1 || pcs->meta->days2 != days2) {
3082            copy_on_write_meta(pcs);
3083            pcs->meta->days1 = days1;
3084            pcs->meta->days2 = days2;
3085            /* Invalidate cached declination. */
3086            pcs->declination = HUGE_REAL;
3087        }
3088    }
3089
3090    if ((date_flags & DATE_EXPLORED)) {
3091        // FIXME: Need to revise 3d format to allow storing EXPLORED date too.
3092    }
3093}
3094
3095typedef void (*cmd_fn)(void);
3096
3097static const cmd_fn cmd_funcs[] = {
3098   cmd_alias,
3099   cmd_begin,
3100   cmd_calibrate,
3101   cmd_cartesian,
3102   cmd_case,
3103   cmd_copyright,
3104   cmd_cs,
3105   cmd_data,
3106   cmd_date,
3107   cmd_declination,
3108#ifndef NO_DEPRECATED
3109   cmd_default,
3110#endif
3111   cmd_end,
3112   cmd_entrance,
3113   cmd_equate,
3114   cmd_export,
3115   cmd_fix,
3116   cmd_flags,
3117   cmd_include,
3118   cmd_infer,
3119   skipline, /*cmd_instrument,*/
3120#ifndef NO_DEPRECATED
3121   cmd_prefix,
3122#endif
3123   cmd_ref,
3124   cmd_require,
3125   cmd_sd,
3126   cmd_set,
3127   solve_network,
3128   cmd_team,
3129   cmd_title,
3130   cmd_truncate,
3131   cmd_units
3132};
3133
3134extern void
3135handle_command(void)
3136{
3137   filepos fp;
3138   get_pos(&fp);
3139   get_token_legacy();
3140   int cmdtok = match_tok(cmd_tab, TABSIZE(cmd_tab));
3141   if (cmdtok < 0 || cmdtok >= (int)(sizeof(cmd_funcs) / sizeof(cmd_fn))) {
3142      set_pos(&fp);
3143      get_token();
3144      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown command “%s”*/12, s_str(&token));
3145      return;
3146   }
3147
3148   do_legacy_token_warning();
3149
3150   switch (cmdtok) {
3151    case CMD_EXPORT:
3152      if (!f_export_ok)
3153         /* TRANSLATORS: The *EXPORT command is only valid just after *BEGIN
3154          * <SURVEY>, so this would generate this error:
3155          *
3156          * *begin fred
3157          * 1 2 1.23 045 -6
3158          * *export 2
3159          * *end fred */
3160         compile_diagnostic(DIAG_ERR, /**EXPORT must immediately follow “*BEGIN <SURVEY>”*/57);
3161      break;
3162    case CMD_ALIAS:
3163    case CMD_CALIBRATE:
3164    case CMD_CARTESIAN:
3165    case CMD_CASE:
3166    case CMD_COPYRIGHT:
3167    case CMD_CS:
3168    case CMD_DATA:
3169    case CMD_DATE:
3170    case CMD_DECLINATION:
3171    case CMD_DEFAULT:
3172    case CMD_FLAGS:
3173    case CMD_INFER:
3174    case CMD_INSTRUMENT:
3175    case CMD_REF:
3176    case CMD_REQUIRE:
3177    case CMD_SD:
3178    case CMD_SET:
3179    case CMD_TEAM:
3180    case CMD_TITLE:
3181    case CMD_TRUNCATE:
3182    case CMD_UNITS:
3183      /* These can occur between *begin and *export */
3184      break;
3185    default:
3186      /* NB: additional handling for "*begin <survey>" in cmd_begin */
3187      f_export_ok = false;
3188      break;
3189   }
3190
3191   cmd_funcs[cmdtok]();
3192}
Note: See TracBrowser for help on using the repository browser.