source: git/src/commands.c @ 9929d60

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

Warn about *copyright date in the future

  • Property mode set to 100644
File size: 88.8 KB
RevLine 
[ff6cfe1]1/* commands.c
[d1b1380]2 * Code for directives
[0f8216c]3 * Copyright (C) 1991-2025 Olly Betts
[846746e]4 *
[89231c4]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.
[846746e]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
[89231c4]12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
[846746e]14 *
[89231c4]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
[d333899]17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
[d1b1380]18 */
19
[a420b49]20#include <config.h>
[d1b1380]21
[a420b49]22#include <limits.h>
[be97baf]23#include <stddef.h> /* for offsetof */
[aeeb3de]24#include <string.h>
[a420b49]25
[b39e24a]26#include <proj.h>
[d9d8f21]27#if PROJ_VERSION_MAJOR < 8
28# define proj_context_errno_string(CTX, ERR) proj_errno_string(ERR)
29#endif
[c092d72]30
[a420b49]31#include "cavern.h"
[d1b1380]32#include "commands.h"
33#include "datain.h"
[1ee204e]34#include "date.h"
[d1b1380]35#include "debug.h"
[5853657]36#include "filename.h"
37#include "message.h"
38#include "netbits.h"
39#include "netskel.h"
[a49a80c0]40#include "osalloc.h"
[5f1e194]41#include "out.h"
[5853657]42#include "readval.h"
[69c920d]43#include "str.h"
[a420b49]44
[bf9faf6]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
[05b93bc0]62int fix_station(prefix *fix_name, const double* coords) {
[e315359]63    bool new_stn = (fix_name->stn == NULL &&
64                    !TSTBIT(fix_name->sflags, SFLAGS_SOLVED));
[725d3b1]65    fix_name->sflags |= BIT(SFLAGS_FIXED);
[e315359]66    if (new_stn) fix_name->sflags |= BIT(SFLAGS_UNUSED_FIXED_POINT);
[725d3b1]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
[bf9faf6]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
[725d3b1]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
[05b93bc0]94void fix_station_with_variance(prefix *fix_name, const double* coords,
[725d3b1]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{
[e315359]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
[725d3b1]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);
[ba84079]111        name->ident.p = NULL;
[725d3b1]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;
[bf9faf6]122        add_stn_to_list(&fixedlist, fixpt);
[725d3b1]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
[a420b49]136static void
137default_grade(settings *s)
138{
[770157e]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);
[4f38f94]142   s->Var[Q_BACKLENGTH] = (real)sqrd(0.05);
[770157e]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));
[a420b49]149   /* SD of plumbed legs (0.25 degrees?) */
150   s->Var[Q_PLUMB] = (real)sqrd(rad(0.25));
[6b7079f]151   /* SD of level legs (0.25 degrees?) */
152   s->Var[Q_LEVEL] = (real)sqrd(rad(0.25));
[770157e]153   s->Var[Q_DEPTH] = (real)sqrd(0.05);
[a420b49]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
[27b8b59]168static reading default_order[] = { Fr, To, Tape, Comp, Clino, End };
[a420b49]169
170static void
171default_style(settings *s)
172{
[fdffa7d]173   s->recorded_style = s->style = STYLE_NORMAL;
[a420b49]174   s->ordering = default_order;
[63d4f07]175   s->dash_for_anon_wall_station = false;
[a420b49]176}
177
178static void
179default_prefix(settings *s)
180{
181   s->Prefix = root;
182}
183
184static void
[5d59477]185init_default_translate_map(short * t)
[a420b49]186{
187   int i;
[5d59477]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;
[a420b49]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;
[f3ac7d4]204   t['-'] |= SPECIAL_NAMES; /* Added in 0.97 prerelease 4 */
[a420b49]205   t['.'] |= SPECIAL_DECIMAL;
206   t['-'] |= SPECIAL_MINUS;
207   t['+'] |= SPECIAL_PLUS;
[3593388]208#if 0 /* FIXME */
[7d40549]209   t['{'] |= SPECIAL_OPEN;
210   t['}'] |= SPECIAL_CLOSE;
[3593388]211#endif
[a420b49]212}
213
[5d59477]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       */
[ae917b96]221      s->Translate = ((short*)osmalloc(sizeof(short) * 257)) + 1;
[5d59477]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     */
[ba84079]242    for (const char *p = prefix_ident(stn); *p; ++p) {
[5d59477]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
[be97baf]292void
[a420b49]293default_units(settings *s)
294{
295   int quantity;
296   for (quantity = 0; quantity < Q_MAC; quantity++) {
[7f08c83]297      if (TSTBIT(ANG_QMASK, quantity))
[bca0071]298         s->units[quantity] = (real)(M_PI / 180.0); /* degrees */
[a420b49]299      else
300         s->units[quantity] = (real)1.0; /* metres */
301   }
[63d4f07]302   s->f_clino_percent = s->f_backclino_percent = false;
303   s->f_bearing_quadrants = s->f_backbearing_quadrants = false;
[a420b49]304}
305
[be97baf]306void
[a420b49]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
[5c3c61a]316static void
317default_flags(settings *s)
318{
319   s->flags = 0;
320}
321
[a420b49]322extern void
323default_all(settings *s)
324{
325   default_truncate(s);
[27b8b59]326   s->infer = 0;
[a420b49]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);
[5c3c61a]334   default_flags(s);
[a420b49]335}
336
[0532954]337string token = S_INIT;
[d1b1380]338
[725d3b1]339string uctoken = S_INIT;
[5f1e194]340
[a420b49]341extern void
[e1cbc0d]342get_token_legacy(void)
[f6bdb01]343{
344   skipblanks();
[e1cbc0d]345   get_token_legacy_no_blanks();
[f6bdb01]346}
347
348extern void
[e1cbc0d]349get_token_legacy_no_blanks(void)
[a420b49]350{
[0532954]351   s_clear(&token);
352   s_clear(&uctoken);
[5f1e194]353   while (isalpha(ch)) {
[94d9f64]354      s_appendch(&token, ch);
355      s_appendch(&uctoken, toupper(ch));
[5f1e194]356      nextch();
357   }
[cfe093e]358
359#if 0
[e1cbc0d]360   printf("get_token_legacy_no_blanks() got “%s”\n", s_str(&token));
[cfe093e]361#endif
[d1b1380]362}
363
[05b9de76]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
[725d3b1]374extern void
[e1cbc0d]375get_token(void)
[725d3b1]376{
[e1cbc0d]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 {
[94d9f64]388            s_appendch(&token, ch);
389            s_appendch(&uctoken, toupper(ch));
[e1cbc0d]390            nextch();
391        } while (isalnum(ch));
392    }
[725d3b1]393}
394
[dcbcae0]395/* read word */
[725d3b1]396void
[dcbcae0]397get_word(void)
398{
[2f246b0]399   s_clear(&token);
[dcbcae0]400   skipblanks();
[2aa37a8]401   while (!isBlank(ch) && !isComm(ch) && !isEol(ch)) {
[94d9f64]402      s_appendch(&token, ch);
[dcbcae0]403      nextch();
404   }
405#if 0
[2f246b0]406   printf("get_word() got “%s”\n", s_str(&token));
[dcbcae0]407#endif
408}
409
[d1b1380]410/* match_tok() now uses binary chop
411 * tab argument should be alphabetically sorted (ascending)
412 */
[a420b49]413extern int
414match_tok(const sztok *tab, int tab_size)
415{
[5f1e194]416   int a = 0, b = tab_size - 1, c;
417   int r;
[0532954]418   const char* tok = s_str(&uctoken);
[cf0560b]419   SVX_ASSERT(tab_size > 0); /* catch empty table */
[d1b1380]420/*  printf("[%d,%d]",a,b); */
[5f1e194]421   while (a <= b) {
422      c = (unsigned)(a + b) / 2;
[d1b1380]423/*     printf(" %d",c); */
[0532954]424      r = strcmp(tab[c].sz, tok);
[5f1e194]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 */
[d1b1380]432}
433
[fb2e93c]434typedef enum {
[abe7192]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
[5f1e194]440} cmds;
[cb3d1e2]441
[82919e07]442static const sztok cmd_tab[] = {
[dcbcae0]443     {"ALIAS",     CMD_ALIAS},
[5f1e194]444     {"BEGIN",     CMD_BEGIN},
445     {"CALIBRATE", CMD_CALIBRATE},
[abe7192]446     {"CARTESIAN", CMD_CARTESIAN},
[a420b49]447     {"CASE",      CMD_CASE},
[13a48f6]448     {"COPYRIGHT", CMD_COPYRIGHT},
[abd0310]449     {"CS",        CMD_CS},
[5f1e194]450     {"DATA",      CMD_DATA},
[13a48f6]451     {"DATE",      CMD_DATE},
[58c7b459]452     {"DECLINATION", CMD_DECLINATION},
[a7d5f1c]453#ifndef NO_DEPRECATED
[5f1e194]454     {"DEFAULT",   CMD_DEFAULT},
[a7d5f1c]455#endif
[5f1e194]456     {"END",       CMD_END},
[dfb4240]457     {"ENTRANCE",  CMD_ENTRANCE},
[5f1e194]458     {"EQUATE",    CMD_EQUATE},
[fb2e93c]459     {"EXPORT",    CMD_EXPORT},
[5f1e194]460     {"FIX",       CMD_FIX},
[5c3c61a]461     {"FLAGS",     CMD_FLAGS},
[5f1e194]462     {"INCLUDE",   CMD_INCLUDE},
[a420b49]463     {"INFER",     CMD_INFER},
[ec6a4b3]464     {"INSTRUMENT",CMD_INSTRUMENT},
[a7d5f1c]465#ifndef NO_DEPRECATED
[5f1e194]466     {"PREFIX",    CMD_PREFIX},
[a7d5f1c]467#endif
[ce15637]468     {"REF",       CMD_REF},
[647407d]469     {"REQUIRE",   CMD_REQUIRE},
[a4ae909]470     {"SD",        CMD_SD},
[5f1e194]471     {"SET",       CMD_SET},
472     {"SOLVE",     CMD_SOLVE},
[13a48f6]473     {"TEAM",      CMD_TEAM},
[a420b49]474     {"TITLE",     CMD_TITLE},
475     {"TRUNCATE",  CMD_TRUNCATE},
[5f1e194]476     {"UNITS",     CMD_UNITS},
[a4ae909]477     {NULL,        CMD_NULL}
[5f1e194]478};
479
[fa42426]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 */
[85c0078]485const real factor_tab[] = {
[fa42426]486   1.0, METRES_PER_FOOT, (METRES_PER_FOOT*3.0),
[00b10c1]487   (M_PI/180.0), (M_PI/180.0), (M_PI/200.0), 0.01, (M_PI/180.0/60.0)
[fa42426]488};
489
[85c0078]490const int units_to_msgno[] = {
491    /*m*/424,
[f516c1e]492    /*′*/428,
[85c0078]493    -1, /* yards */
[e343cfc0]494    /*°*/344, /* quadrants */
[85c0078]495    /*°*/344,
[85dcdcd]496    /*ᵍ*/345,
[85c0078]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
[a420b49]518static int
[fa42426]519get_units(unsigned long qmask, bool percent_ok)
[a420b49]520{
[82919e07]521   static const sztok utab[] = {
[5f1e194]522        {"DEGREES",       UNITS_DEGS },
[a4ae909]523        {"DEGS",          UNITS_DEGS },
524        {"FEET",          UNITS_FEET },
525        {"GRADS",         UNITS_GRADS },
526        {"METERS",        UNITS_METRES },
527        {"METRES",        UNITS_METRES },
528        {"METRIC",        UNITS_METRES },
[70fa970]529        {"MILS",          UNITS_DEPRECATED_ALIAS_FOR_GRADS },
[a4ae909]530        {"MINUTES",       UNITS_MINUTES },
531        {"PERCENT",       UNITS_PERCENT },
[5f1e194]532        {"PERCENTAGE",    UNITS_PERCENT },
[00b10c1]533        {"QUADRANTS",     UNITS_QUADRANTS },
[51951b9]534        {"QUADS",         UNITS_QUADRANTS },
535        {"YARDS",         UNITS_YARDS },
[a4ae909]536        {NULL,            UNITS_NULL }
[5f1e194]537   };
[3a33d12]538   get_token();
[e1cbc0d]539   int units = match_tok(utab, TABSIZE(utab));
[647407d]540   if (units == UNITS_NULL) {
[caae6cd]541      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown units “%s”*/35,
[0532954]542                         s_str(&token));
[fa42426]543      return UNITS_NULL;
544   }
[70fa970]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) {
[caae6cd]552      compile_diagnostic(DIAG_WARN|DIAG_TOKEN|DIAG_SKIP,
[70fa970]553                         /*Units “%s” are deprecated, assuming “grads” - see manual for details*/479,
[0532954]554                         s_str(&token));
[70fa970]555      units = UNITS_GRADS;
556   }
[fa42426]557   if (units == UNITS_PERCENT && percent_ok &&
558       !(qmask & ~(BIT(Q_GRADIENT)|BIT(Q_BACKGRADIENT)))) {
559      return units;
560   }
[00b10c1]561   if (units == UNITS_QUADRANTS &&
562       !(qmask & ~(BIT(Q_BEARING)|BIT(Q_BACKBEARING)))) {
563      return units;
564   }
[fa42426]565   if (((qmask & LEN_QMASK) && !TSTBIT(LEN_UMASK, units)) ||
566       ((qmask & ANG_QMASK) && !TSTBIT(ANG_UMASK, units))) {
[736f7df]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       */
[caae6cd]570      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Invalid units “%s” for quantity*/37, s_str(&token));
[fa42426]571      return UNITS_NULL;
[647407d]572   }
[5f1e194]573   return units;
[d1b1380]574}
575
576/* returns mask with bit x set to indicate quantity x specified */
[67508f0]577static unsigned long
[46cb98f]578get_qlist(unsigned long mask_bad)
[a420b49]579{
[82919e07]580   static const sztok qtab[] = {
[9b5d785]581        {"ALTITUDE",     Q_DZ },
[b14f44f]582        {"BACKBEARING",  Q_BACKBEARING },
583        {"BACKCLINO",    Q_BACKGRADIENT },    /* alternative name */
584        {"BACKCOMPASS",  Q_BACKBEARING },     /* alternative name */
585        {"BACKGRADIENT", Q_BACKGRADIENT },
[4f38f94]586        {"BACKLENGTH",   Q_BACKLENGTH },
587        {"BACKTAPE",     Q_BACKLENGTH },    /* alternative name */
[5f1e194]588        {"BEARING",      Q_BEARING },
[ee05463]589        {"CEILING",      Q_UP },          /* alternative name */
[a4ae909]590        {"CLINO",        Q_GRADIENT },    /* alternative name */
[5f1e194]591        {"COMPASS",      Q_BEARING },     /* alternative name */
[a4ae909]592        {"COUNT",        Q_COUNT },
[5f1e194]593        {"COUNTER",      Q_COUNT },       /* alternative name */
594        {"DECLINATION",  Q_DECLINATION },
[a420b49]595        {"DEFAULT",      Q_DEFAULT }, /* not a real quantity... */
[a4ae909]596        {"DEPTH",        Q_DEPTH },
[ee05463]597        {"DOWN",         Q_DOWN },
[a4ae909]598        {"DX",           Q_DX },          /* alternative name */
599        {"DY",           Q_DY },          /* alternative name */
600        {"DZ",           Q_DZ },          /* alternative name */
601        {"EASTING",      Q_DX },
[ee05463]602        {"FLOOR",        Q_DOWN },        /* alternative name */
[5f1e194]603        {"GRADIENT",     Q_GRADIENT },
[ee05463]604        {"LEFT",         Q_LEFT },
[5f1e194]605        {"LENGTH",       Q_LENGTH },
[a4ae909]606        {"LEVEL",        Q_LEVEL},
[9b5d785]607        {"NORTHING",     Q_DY },
[a4ae909]608        {"PLUMB",        Q_PLUMB},
[5f1e194]609        {"POSITION",     Q_POS },
[ee05463]610        {"RIGHT",        Q_RIGHT },
[a4ae909]611        {"TAPE",         Q_LENGTH },      /* alternative name */
[ee05463]612        {"UP",           Q_UP },
[a4ae909]613        {NULL,           Q_NULL }
[5f1e194]614   };
[67508f0]615   unsigned long qmask = 0;
[5f1e194]616   int tok;
[3a33d12]617   filepos fp;
618
[a420b49]619   while (1) {
[3a33d12]620      get_pos(&fp);
[e1cbc0d]621      get_token_legacy();
[5f1e194]622      tok = match_tok(qtab, TABSIZE(qtab));
[da96015]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      }
[5f1e194]630      /* bail out if we reach the table end with no match */
631      if (tok == Q_NULL) break;
632      qmask |= BIT(tok);
[da96015]633      if (qmask & mask_bad) {
[caae6cd]634         compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown instrument “%s”*/39, s_str(&token));
[46cb98f]635         return 0;
636      }
[5f1e194]637   }
[3a33d12]638
[647407d]639   if (qmask == 0) {
[36efb03]640      /* TRANSLATORS: A "quantity" is something measured like "LENGTH",
641       * "BEARING", "ALTITUDE", etc. */
[caae6cd]642      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown quantity “%s”*/34, s_str(&token));
[3a33d12]643   } else {
644      set_pos(&fp);
[647407d]645   }
[3a33d12]646
[5f1e194]647   return qmask;
[d1b1380]648}
649
650#define SPECIAL_UNKNOWN 0
[a420b49]651static void
[eb18f4d]652cmd_set(void)
[a420b49]653{
[82919e07]654   static const sztok chartab[] = {
[5f1e194]655        {"BLANK",     SPECIAL_BLANK },
[3593388]656/*FIXME {"CLOSE",     SPECIAL_CLOSE }, */
[5f1e194]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 },
[3593388]664/*FIXME {"OPEN",      SPECIAL_OPEN }, */
[5f1e194]665        {"PLUS",      SPECIAL_PLUS },
[7f1ab95]666#ifndef NO_DEPRECATED
[5f1e194]667        {"ROOT",      SPECIAL_ROOT },
[7f1ab95]668#endif
[5f1e194]669        {"SEPARATOR", SPECIAL_SEPARATOR },
[a4ae909]670        {NULL,        SPECIAL_UNKNOWN }
[5f1e194]671   };
672   int i;
[b15eeda]673
[a420b49]674   get_token();
[e1cbc0d]675   int mask = match_tok(chartab, TABSIZE(chartab));
[b15eeda]676
[f74d0cb]677   if (mask == SPECIAL_UNKNOWN) {
[caae6cd]678      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown character class “%s”*/42,
[0532954]679                         s_str(&token));
[5f1e194]680      return;
681   }
[b15eeda]682
[7f1ab95]683#ifndef NO_DEPRECATED
[c86cc71]684   if (mask == SPECIAL_ROOT) {
685      if (root_depr_count < 5) {
[736f7df]686         /* TRANSLATORS: Use of the ROOT character (which is "\" by default) is
687          * deprecated, so this error would be generated by:
[b49ac56]688          *
[736f7df]689          * *equate \foo.7 1
[b49ac56]690          *
[736f7df]691          * If you're unsure what "deprecated" means, see:
[2c1c52e]692          * https://en.wikipedia.org/wiki/Deprecation */
[caae6cd]693         compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*ROOT is deprecated*/25);
[c86cc71]694         if (++root_depr_count == 5)
[736f7df]695             /* TRANSLATORS: If you're unsure what "deprecated" means, see:
[2c1c52e]696              * https://en.wikipedia.org/wiki/Deprecation */
[64544daf]697            compile_diagnostic(DIAG_INFO, /*Further uses of this deprecated feature will not be reported*/95);
[c86cc71]698      }
699   }
[7f1ab95]700#endif
[b15eeda]701
[5f1e194]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;
[ae917b96]706      p = ((short*)osmalloc(sizeof(short) * 257)) + 1;
[5f1e194]707      memcpy(p - 1, pcs->Translate - 1, sizeof(short) * 257);
708      pcs->Translate = p;
709   }
[11f9067]710
711   skipblanks();
712
[5f1e194]713   /* clear this flag for all non-alphanums */
[a420b49]714   for (i = 0; i < 256; i++)
715      if (!isalnum(i)) pcs->Translate[i] &= ~mask;
[11f9067]716
[5f1e194]717   /* now set this flag for all specified chars */
718   while (!isEol(ch)) {
[5d59477]719      int char_to_set;
[11f9067]720      if (!isalnum(ch)) {
[5d59477]721         char_to_set = ch;
[11f9067]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');
[5d59477]738         char_to_set = hex;
[11f9067]739      } else {
740         break;
741      }
[5d59477]742      pcs->Translate[char_to_set] |= mask;
743      separator_map[char_to_set] |= mask;
[5f1e194]744      nextch();
745   }
[5d59477]746
747   output_separator = find_output_separator();
748}
749
750void
751update_output_separator(void)
752{
753   output_separator = find_output_separator();
[d1b1380]754}
755
[4d9eecd]756static void
[23e64c14]757check_reentry(prefix *survey, const filepos* fpos_ptr)
[4d9eecd]758{
[91912b4]759   /* Don't try to check "*prefix \" or "*begin \" */
[613028c]760   if (!survey->up) return;
761   if (TSTBIT(survey->sflags, SFLAGS_PREFIX_ENTERED)) {
[15696f3]762      static int reenter_depr_count = 0;
[23e64c14]763      filepos fp_tmp;
[c86cc71]764
[e1a66da]765      if (reenter_depr_count >= 5)
766         return;
767
[23e64c14]768      get_pos(&fp_tmp);
769      set_pos(fpos_ptr);
[1c507cf]770      /* TRANSLATORS: The first of two warnings given when a survey which has
771       * already been completed is reentered.  This example file crawl.svx:
[e1a66da]772       *
[5a9e47c]773       * *begin crawl     ; <- second warning here
774       * 1 2 9.45 234 -01
[e1a66da]775       * *end crawl
[5a9e47c]776       * *begin crawl     ; <- first warning here
[e1a66da]777       * 2 3 7.67 223 -03
778       * *end crawl
[1c507cf]779       *
780       * Would lead to:
781       *
[b0a2ddb]782       * crawl.svx:4:8: warning: Reentering an existing survey is deprecated
783       * crawl.svx:1: info: Originally entered here
[1c507cf]784       *
[e1a66da]785       * If you're unsure what "deprecated" means, see:
[2c1c52e]786       * https://en.wikipedia.org/wiki/Deprecation */
[d0be687d]787      compile_diagnostic(DIAG_WARN|DIAG_WORD, /*Reentering an existing survey is deprecated*/29);
[23e64c14]788      set_pos(&fp_tmp);
[1c507cf]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       *
[9c25fac]792       * *begin crawl     ; <- second warning here
793       * 1 2 9.45 234 -01
[1c507cf]794       * *end crawl
[9c25fac]795       * *begin crawl     ; <- first warning here
[1c507cf]796       * 2 3 7.67 223 -03
797       * *end crawl
798       *
799       * Would lead to:
800       *
[b0a2ddb]801       * crawl.svx:4:8: warning: Reentering an existing survey is deprecated
802       * crawl.svx:1: info: Originally entered here
[1c507cf]803       *
804       * If you're unsure what "deprecated" means, see:
[2c1c52e]805       * https://en.wikipedia.org/wiki/Deprecation */
[b0a2ddb]806      compile_diagnostic_pfx(DIAG_INFO, survey, /*Originally entered here*/30);
[1c507cf]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:
[2c1c52e]812          * https://en.wikipedia.org/wiki/Deprecation */
[64544daf]813         compile_diagnostic(DIAG_INFO, /*Further uses of this deprecated feature will not be reported*/95);
[1c507cf]814      }
[4d9eecd]815   } else {
[613028c]816      survey->sflags |= BIT(SFLAGS_PREFIX_ENTERED);
817      survey->filename = file.filename;
818      survey->line = file.line;
[4d9eecd]819   }
820}
821
[7f1ab95]822#ifndef NO_DEPRECATED
[a420b49]823static void
[eb18f4d]824cmd_prefix(void)
[a420b49]825{
[c86cc71]826   static int prefix_depr_count = 0;
[613028c]827   prefix *survey;
[23e64c14]828   filepos fp;
[91912b4]829   /* Issue warning first, so "*prefix \" warns first that *prefix is
830    * deprecated and then that ROOT is...
831    */
[c86cc71]832   if (prefix_depr_count < 5) {
[736f7df]833      /* TRANSLATORS: If you're unsure what "deprecated" means, see:
[2c1c52e]834       * https://en.wikipedia.org/wiki/Deprecation */
[7962c9d]835      compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /**prefix is deprecated - use *begin and *end instead*/109);
[c86cc71]836      if (++prefix_depr_count == 5)
[64544daf]837         compile_diagnostic(DIAG_INFO, /*Further uses of this deprecated feature will not be reported*/95);
[c86cc71]838   }
[23e64c14]839   get_pos(&fp);
[613028c]840   survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT);
841   pcs->Prefix = survey;
[23e64c14]842   check_reentry(survey, &fp);
[d1b1380]843}
[7f1ab95]844#endif
[d1b1380]845
[dcbcae0]846static void
847cmd_alias(void)
848{
[45dcea2]849   /* Currently only two forms are supported:
[dcbcae0]850    * *alias station - ..
[45dcea2]851    * *alias station -
[dcbcae0]852    */
853   get_token();
[4772e46]854   if (!S_EQ(&uctoken, "STATION")) {
855       compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_TOKEN, /*Bad *alias command*/397);
856       return;
857   }
[dcbcae0]858   get_word();
[2f246b0]859   if (!S_EQ(&token, "-"))
[4772e46]860      goto bad_word;
[dcbcae0]861   get_word();
[2f246b0]862   if (!s_empty(&token) && !S_EQ(&token, ".."))
[4772e46]863      goto bad_word;
[2f246b0]864   pcs->dash_for_anon_wall_station = !s_empty(&token);
[dcbcae0]865   return;
[4772e46]866bad_word:
[2aa37a8]867   compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_TOKEN, /*Bad *alias command*/397);
[dcbcae0]868}
869
[a420b49]870static void
[eb18f4d]871cmd_begin(void)
[a420b49]872{
[5f1e194]873   settings *pcsNew;
[4be360f]874
[5f1e194]875   pcsNew = osnew(settings);
876   *pcsNew = *pcs; /* copy contents */
[47c7a94]877   pcsNew->begin_lineno = file.line;
[94aeeaf]878   pcsNew->begin_lpos = file.lpos;
[5f1e194]879   pcsNew->next = pcs;
880   pcs = pcsNew;
[4be360f]881
[c230d677]882   skipblanks();
[3be21a9]883   pcs->begin_survey = NULL;
[94aeeaf]884   pcs->begin_col = 0;
[3be21a9]885   if (!isEol(ch) && !isComm(ch)) {
[23e64c14]886      filepos fp;
887      prefix *survey;
888      get_pos(&fp);
[94aeeaf]889      int begin_col = fp.offset - file.lpos;
[23e64c14]890      survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT|PFX_WARN_SEPARATOR);
[94aeeaf]891      // read_prefix() might fail and longjmp() so only set begin_col if
892      // it succeeds.
893      pcs->begin_col = begin_col;
[c230d677]894      pcs->begin_survey = survey;
[613028c]895      pcs->Prefix = survey;
[23e64c14]896      check_reentry(survey, &fp);
[63d4f07]897      f_export_ok = true;
[d53b0f5]898   }
[647407d]899}
900
[d624d86]901void
[da9163b]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
[37d6b84]911void
912report_declination(settings *p)
[da9163b]913{
[37d6b84]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         */
[501dd27]936        compile_diagnostic_at(DIAG_INFO, p->dec_filename, p->dec_line,
[37d6b84]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);
[725d3b1]943        if (p->next && p->dec_context != p->next->dec_context)
944            free(p->dec_context);
[37d6b84]945        p->dec_context = NULL;
[c1fe79a]946        p->min_declination = HUGE_VAL;
947        p->max_declination = -HUGE_VAL;
[37d6b84]948    }
949}
[da9163b]950
[69a9e01]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,
[d9d8f21]984                          proj_context_errno_string(PJ_DEFAULT_CTX,
985                                                    proj_errno(transform)));
[69a9e01]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;
[abe7192]1004    /* Invalidate cached grid convergence values. */
[69a9e01]1005    pcs->convergence = HUGE_REAL;
[abe7192]1006    pcs->input_convergence = HUGE_REAL;
[69a9e01]1007}
1008
[37d6b84]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;
[4bea0f8]1026        pcs->convergence = p->convergence;
[37d6b84]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 */
[ae917b96]1035        free(p->proj_str);
[37d6b84]1036    }
[cb3d1e2]1037
[37d6b84]1038    /* don't free default ordering or ordering used by parent */
1039    if (p->ordering != default_order && p->ordering != pcs->ordering)
[ae917b96]1040        free((reading*)p->ordering);
[647407d]1041
[37d6b84]1042    /* free Translate if not used by parent */
1043    if (p->Translate != pcs->Translate)
[ae917b96]1044        free(p->Translate - 1);
[b5a3219]1045
[37d6b84]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)
[ae917b96]1048        free(p->meta);
[37d6b84]1049
[ae917b96]1050    free(p);
[d1b1380]1051}
1052
[4be360f]1053static void
[eb18f4d]1054cmd_end(void)
[a420b49]1055{
[9f55538]1056   filepos fp;
[4be360f]1057
[94aeeaf]1058   int begin_lineno = pcs->begin_lineno;
1059   if (begin_lineno == 0) {
[da9163b]1060      if (pcs->next == NULL) {
[4be360f]1061         /* more ENDs than BEGINs */
[725d3b1]1062         /* TRANSLATORS: %s is replaced with e.g. BEGIN or .BOOK or #[ */
1063         compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*No matching %s*/192, "BEGIN");
[4be360f]1064      } else {
[944153c]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");
[4be360f]1069      }
1070      return;
1071   }
1072
[94aeeaf]1073   prefix *begin_survey = pcs->begin_survey;
1074   long begin_lpos = pcs->begin_lpos;
1075   int begin_col = pcs->begin_col;
[647407d]1076
[da9163b]1077   pop_settings();
[d1b1380]1078
[84c60fc]1079   /* note need to read using root *before* BEGIN */
[94aeeaf]1080   prefix *survey = NULL;
[3916384]1081   skipblanks();
[94aeeaf]1082   if (!isEol(ch) && !isComm(ch)) {
[9f55538]1083      get_pos(&fp);
[3916384]1084      survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT);
1085   }
1086
[613028c]1087   if (survey != begin_survey) {
[94aeeaf]1088      filepos fp_save;
1089      get_pos(&fp_save);
[613028c]1090      if (survey) {
[9f55538]1091         set_pos(&fp);
[613028c]1092         if (!begin_survey) {
1093            /* TRANSLATORS: Used when a BEGIN command has no survey, but the
1094             * END command does, e.g.:
[736f7df]1095             *
1096             * *begin
1097             * 1 2 10.00 178 -01
1098             * *end entrance      <--[Message given here] */
[d0be687d]1099            compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Matching BEGIN command has no survey name*/36);
[647407d]1100         } else {
[613028c]1101            /* TRANSLATORS: *BEGIN <survey> and *END <survey> should have the
1102             * same <survey> if it’s given at all */
[d0be687d]1103            compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Survey name doesn’t match BEGIN*/193);
[647407d]1104         }
[a420b49]1105      } else {
[613028c]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] */
[cab0f26]1112         compile_diagnostic(DIAG_WARN|DIAG_COL, /*Survey name omitted from END*/194);
[a420b49]1113      }
[94aeeaf]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);
[5f1e194]1126   }
1127}
[d1b1380]1128
[dfb4240]1129static void
1130cmd_entrance(void)
1131{
[c458cf7]1132   prefix *pfx = read_prefix(PFX_STATION);
[e315359]1133   pfx->sflags |= BIT(SFLAGS_ENTRANCE);
1134   pfx->sflags &= ~BIT(SFLAGS_UNUSED_FIXED_POINT);
[dfb4240]1135}
1136
[56db37f]1137static const prefix * first_fix_name = NULL;
1138static const char * first_fix_filename;
1139static unsigned first_fix_line;
[216ada0]1140
[a420b49]1141static void
[eb18f4d]1142cmd_fix(void)
[a420b49]1143{
[216ada0]1144   static prefix *name_omit_already = NULL;
[31699b54]1145   static const char * name_omit_already_filename = NULL;
1146   static unsigned int name_omit_already_line;
[725d3b1]1147   PJ_COORD coord;
[58edecc]1148   filepos fp_stn, fp;
[5f1e194]1149
[58edecc]1150   get_pos(&fp_stn);
[725d3b1]1151   prefix *fix_name = read_prefix(PFX_STATION|PFX_ALLOW_ROOT);
[5f1e194]1152
[c80bd34]1153   get_pos(&fp);
[e1cbc0d]1154   get_token_legacy();
[72f617b]1155   bool reference = S_EQ(&uctoken, "REFERENCE");
1156   if (reference) {
[05b9de76]1157      do_legacy_token_warning();
[c80bd34]1158   } else {
[0532954]1159      if (!s_empty(&uctoken)) set_pos(&fp);
[647407d]1160   }
[c80bd34]1161
[72f617b]1162   // If `REFERENCE` is specified the coordinates can't be omitted.
1163   coord.v[0] = read_numeric(!reference);
[725d3b1]1164   if (coord.v[0] == HUGE_REAL) {
[2aa2f3f]1165      /* If the end of the line isn't blank, read a number after all to
1166       * get a more helpful error message */
[725d3b1]1167      if (!isEol(ch) && !isComm(ch)) coord.v[0] = read_numeric(false);
[2aa2f3f]1168   }
[725d3b1]1169   if (coord.v[0] == HUGE_REAL) {
[b39e24a]1170      if (pcs->proj_str || proj_str_out) {
[301a7d4]1171         compile_diagnostic(DIAG_ERR|DIAG_COL|DIAG_SKIP, /*Coordinates can't be omitted when coordinate system has been specified*/439);
[a4f1d96]1172         return;
1173      }
1174
[6727d64]1175      if (fix_name == name_omit_already) {
[301a7d4]1176         compile_diagnostic(DIAG_WARN|DIAG_COL, /*Same station fixed twice with no coordinates*/61);
[5f1e194]1177         return;
1178      }
[6727d64]1179
1180      if (name_omit_already) {
[0ef9ce4]1181         /* TRANSLATORS: Emitted after second and subsequent "FIX" command
1182          * with no coordinates.
[6727d64]1183          */
[501dd27]1184         compile_diagnostic_at(DIAG_ERR,
[cab0f26]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));
[6727d64]1189      } else {
[0ef9ce4]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
[6727d64]1193         name_omit_already = fix_name;
1194         name_omit_already_filename = file.filename;
1195         name_omit_already_line = file.line;
1196      }
1197
[725d3b1]1198      coord.v[0] = coord.v[1] = coord.v[2] = (real)0.0;
[5f1e194]1199   } else {
[725d3b1]1200      coord.v[1] = read_numeric(false);
1201      coord.v[2] = read_numeric(false);
[702be52]1202
[b39e24a]1203      if (pcs->proj_str && proj_str_out) {
[da9163b]1204         PJ *transform = pj_cached;
1205         if (!transform) {
1206             transform = proj_create_crs_to_crs(PJ_DEFAULT_CTX,
[b39e24a]1207                                                pcs->proj_str,
1208                                                proj_str_out,
1209                                                NULL);
[da9163b]1210             if (transform) {
[998388e]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                 */
[da9163b]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;
[b39e24a]1222         }
1223
1224         if (proj_angular_input(transform, PJ_FWD)) {
1225            /* Input coordinate system expects radians. */
[725d3b1]1226            coord.v[0] = rad(coord.v[0]);
1227            coord.v[1] = rad(coord.v[1]);
[702be52]1228         }
[b39e24a]1229
[1a6fddc]1230         coord.v[3] = HUGE_VAL;
[b39e24a]1231         coord = proj_trans(transform, PJ_FWD, coord);
1232
[725d3b1]1233         if (coord.v[0] == HUGE_VAL ||
1234             coord.v[1] == HUGE_VAL ||
1235             coord.v[2] == HUGE_VAL) {
[b39e24a]1236            compile_diagnostic(DIAG_ERR, /*Failed to convert coordinates: %s*/436,
[d9d8f21]1237                               proj_context_errno_string(PJ_DEFAULT_CTX,
1238                                                         proj_errno(transform)));
[998388e]1239            /* Set dummy values which are finite. */
[725d3b1]1240            coord.v[0] = coord.v[1] = coord.v[2] = 0;
[702be52]1241         }
[b39e24a]1242      } else if (pcs->proj_str) {
[cab0f26]1243         compile_diagnostic(DIAG_ERR, /*The input projection is set but the output projection isn't*/437);
[b39e24a]1244      } else if (proj_str_out) {
[cab0f26]1245         compile_diagnostic(DIAG_ERR, /*The output projection is set but the input projection isn't*/438);
[702be52]1246      }
1247
[c270761]1248      get_pos(&fp);
[725d3b1]1249      real sdx = read_numeric(true);
[2cfcb32]1250      if (sdx <= 0) {
[c270761]1251          set_pos(&fp);
1252          compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
[2cfcb32]1253          return;
1254      }
[647407d]1255      if (sdx != HUGE_REAL) {
1256         real sdy, sdz;
[c80bd34]1257         real cxy = 0, cyz = 0, czx = 0;
[c270761]1258         get_pos(&fp);
[63d4f07]1259         sdy = read_numeric(true);
[647407d]1260         if (sdy == HUGE_REAL) {
1261            /* only one variance given */
1262            sdy = sdz = sdx;
1263         } else {
[2cfcb32]1264            if (sdy <= 0) {
[c270761]1265               set_pos(&fp);
1266               compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
[2cfcb32]1267               return;
1268            }
[c270761]1269            get_pos(&fp);
[63d4f07]1270            sdz = read_numeric(true);
[647407d]1271            if (sdz == HUGE_REAL) {
1272               /* two variances given - horizontal & vertical */
1273               sdz = sdy;
1274               sdy = sdx;
[c80bd34]1275            } else {
[2cfcb32]1276               if (sdz <= 0) {
[c270761]1277                  set_pos(&fp);
1278                  compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
[2cfcb32]1279                  return;
1280               }
[63d4f07]1281               cxy = read_numeric(true);
[c80bd34]1282               if (cxy != HUGE_REAL) {
1283                  /* covariances given */
[63d4f07]1284                  cyz = read_numeric(false);
1285                  czx = read_numeric(false);
[c2211a5]1286               } else {
1287                  cxy = 0;
[c80bd34]1288               }
[647407d]1289            }
1290         }
[725d3b1]1291         fix_station_with_variance(fix_name, coord.v,
1292                                   sdx * sdx, sdy * sdy, sdz * sdz
[647407d]1293#ifndef NO_COVARIANCES
[725d3b1]1294                                   , cxy, cyz, czx
[647407d]1295#endif
[725d3b1]1296                                  );
[a9c640c]1297
[e315359]1298         if (reference) {
1299             // `*fix reference` so suppress "unused fixed point" warning.
1300             fix_name->sflags &= ~BIT(SFLAGS_UNUSED_FIXED_POINT);
1301         }
[a9c640c]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
[cb3d1e2]1311         return;
[647407d]1312      }
[5f1e194]1313   }
1314
[56db37f]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
[725d3b1]1324   int fix_result = fix_station(fix_name, coord.v);
[e315359]1325   if (reference) {
1326       // `*fix reference` so suppress "unused fixed point" warning.
1327       fix_name->sflags &= ~BIT(SFLAGS_UNUSED_FIXED_POINT);
1328   }
[725d3b1]1329   if (fix_result == 0) {
[5f1e194]1330      return;
1331   }
[d1b1380]1332
[58edecc]1333   get_pos(&fp);
1334   set_pos(&fp_stn);
[725d3b1]1335   if (fix_result < 0) {
[58edecc]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);
[5f1e194]1340   }
[bf669f6]1341   compile_diagnostic_pfx(DIAG_INFO, fix_name, /*Previously fixed or equated here*/493);
[58edecc]1342   set_pos(&fp);
[d1b1380]1343}
1344
[5c3c61a]1345static void
1346cmd_flags(void)
1347{
[82919e07]1348   static const sztok flagtab[] = {
[5c3c61a]1349        {"DUPLICATE", FLAGS_DUPLICATE },
1350        {"NOT",       FLAGS_NOT },
[95c3272]1351        {"SPLAY",     FLAGS_SPLAY },
[5c3c61a]1352        {"SURFACE",   FLAGS_SURFACE },
[a4ae909]1353        {NULL,        FLAGS_UNKNOWN }
[5c3c61a]1354   };
[63d4f07]1355   bool fNot = false;
1356   bool fEmpty = true;
[5c3c61a]1357   while (1) {
1358      int flag;
1359      get_token();
[0532954]1360      /* If token is empty, it could mean end of line, or maybe
[e1cbc0d]1361       * some non-alphanumeric junk which is better reported later */
[0532954]1362      if (s_empty(&token)) break;
[62bb4d3]1363
[63d4f07]1364      fEmpty = false;
[5c3c61a]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)) {
[caae6cd]1368         compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*FLAG “%s” unknown*/68,
[0532954]1369                            s_str(&token));
[0804fbe]1370         /* Recover from “*FLAGS NOT BOGUS SURFACE” by ignoring "NOT BOGUS" */
[63d4f07]1371         fNot = false;
[62bb4d3]1372      } else if (flag == FLAGS_NOT) {
[63d4f07]1373         fNot = true;
[5c3c61a]1374      } else if (fNot) {
[421b7d2]1375         pcs->flags &= ~BIT(flag);
[63d4f07]1376         fNot = false;
[5c3c61a]1377      } else {
[421b7d2]1378         pcs->flags |= BIT(flag);
[5c3c61a]1379      }
1380   }
[9881759]1381
1382   if (fNot) {
[725d3b1]1383      compile_diagnostic(DIAG_ERR|DIAG_TOKEN,
1384                         /*Expecting “%s”, “%s”, or “%s”*/188,
1385                         "DUPLICATE", "SPLAY", "SURFACE");
[9881759]1386   } else if (fEmpty) {
[725d3b1]1387      compile_diagnostic(DIAG_ERR|DIAG_TOKEN,
1388                         /*Expecting “%s”, “%s”, “%s”, or “%s”*/189,
1389                         "NOT", "DUPLICATE", "SPLAY", "SURFACE");
[9881759]1390   }
[5c3c61a]1391}
1392
[a420b49]1393static void
[eb18f4d]1394cmd_equate(void)
[a420b49]1395{
[c270761]1396   filepos fp;
1397   get_pos(&fp);
[bf669f6]1398   prefix *prev_name = NULL;
1399   prefix *name = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_SUSPECT_TYPO);
[63d4f07]1400   while (true) {
[bf669f6]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      }
[b9c82c2]1407      skipblanks();
1408      if (isEol(ch) || isComm(ch)) {
[bf669f6]1409         if (prev_name == NULL) {
1410            /* E.g. *equate entrance.6 */
[c270761]1411            set_pos(&fp);
[736f7df]1412            /* TRANSLATORS: EQUATE is a command name, so shouldn’t be
[0b8c321]1413             * translated.
1414             *
1415             * Here "station" is a survey station, not a train station.
1416             */
[d0be687d]1417            compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_WORD, /*Only one station in EQUATE command*/33);
[11f9067]1418         }
[5f1e194]1419         return;
[d1b1380]1420      }
[cb3d1e2]1421
[bf669f6]1422      prev_name = name;
1423      name = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_SUSPECT_TYPO);
[dad5d599]1424      process_equate(prev_name, name);
[5f1e194]1425   }
[d1b1380]1426}
1427
[84c60fc]1428static void
1429report_missing_export(prefix *pfx, int depth)
1430{
1431   char *s;
[93e3492]1432   const char *p;
1433   prefix *survey = pfx;
[84c60fc]1434   int i;
1435   for (i = depth + 1; i; i--) {
1436      survey = survey->up;
[4c07c51]1437      SVX_ASSERT(survey);
[84c60fc]1438   }
1439   s = osstrdup(sprint_prefix(survey));
[93e3492]1440   p = sprint_prefix(pfx);
[84c60fc]1441   if (survey->filename) {
[93e3492]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
[0b8c321]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       */
[cab0f26]1457      compile_diagnostic_pfx(DIAG_ERR, survey,
1458                             /*Station “%s” not exported from survey “%s”*/26, p, s);
[93e3492]1459   } else {
[cab0f26]1460      compile_diagnostic(DIAG_ERR, /*Station “%s” not exported from survey “%s”*/26, p, s);
[84c60fc]1461   }
[ae917b96]1462   free(s);
[84c60fc]1463}
1464
[fb2e93c]1465static void
[d1878c51]1466cmd_export(void)
[fb2e93c]1467{
[d1878c51]1468   prefix *pfx;
1469
[63d4f07]1470   fExportUsed = true;
[932f7e9]1471   do {
[fb2e93c]1472      int depth = 0;
[6327dcf]1473      pfx = read_prefix(PFX_STATION);
1474      {
[421b7d2]1475         prefix *p = pfx;
[fb2e93c]1476         while (p != NULL && p != pcs->Prefix) {
1477            depth++;
1478            p = p->up;
1479         }
[84c60fc]1480         /* Something like: *export \foo, but we've excluded use of root */
[4c07c51]1481         SVX_ASSERT(p);
[fb2e93c]1482      }
[84c60fc]1483      /* *export \ or similar bogus stuff */
[4c07c51]1484      SVX_ASSERT(depth);
[932f7e9]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) {
[421b7d2]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;
[c00c74a9]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... */
[421b7d2]1497         if (pfx->min_export - 1 > depth) {
[84c60fc]1498            report_missing_export(pfx, depth);
[421b7d2]1499         } else if (pfx->min_export - 1 < depth) {
[736f7df]1500            /* TRANSLATORS: Here "station" is a survey station, not a train station.
[b49ac56]1501             *
[736f7df]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 */
[cab0f26]1509            compile_diagnostic(DIAG_ERR, /*Station “%s” already exported*/66,
1510                               sprint_prefix(pfx));
[421b7d2]1511         }
1512         pfx->min_export = depth;
[fb2e93c]1513      }
[b9c82c2]1514      skipblanks();
1515   } while (!isEol(ch) && !isComm(ch));
[fb2e93c]1516}
1517
[a420b49]1518static void
[eb18f4d]1519cmd_data(void)
[a420b49]1520{
[82919e07]1521   static const sztok dtab[] = {
[9b5d785]1522        {"ALTITUDE",     Dz },
[5b7c1b7]1523        {"BACKBEARING",  BackComp },
1524        {"BACKCLINO",    BackClino }, /* alternative name */
1525        {"BACKCOMPASS",  BackComp }, /* alternative name */
1526        {"BACKGRADIENT", BackClino },
[4f38f94]1527        {"BACKLENGTH",   BackTape },
1528        {"BACKTAPE",     BackTape }, /* alternative name */
[5f1e194]1529        {"BEARING",      Comp },
[ee05463]1530        {"CEILING",      Up }, /* alternative name */
[a4ae909]1531        {"CLINO",        Clino }, /* alternative name */
[a420b49]1532        {"COMPASS",      Comp }, /* alternative name */
[a4ae909]1533        {"COUNT",        Count }, /* FrCount&ToCount in multiline */
1534        {"DEPTH",        Depth }, /* FrDepth&ToDepth in multiline */
[6114207]1535        {"DEPTHCHANGE",  DepthChange },
[421b7d2]1536        {"DIRECTION",    Dir },
[ee05463]1537        {"DOWN",         Down },
[647407d]1538        {"DX",           Dx },
1539        {"DY",           Dy },
1540        {"DZ",           Dz },
[9b5d785]1541        {"EASTING",      Dx },
[ee05463]1542        {"FLOOR",        Down }, /* alternative name */
[a4ae909]1543        {"FROM",         Fr },
[647407d]1544        {"FROMCOUNT",    FrCount },
[5f1e194]1545        {"FROMDEPTH",    FrDepth },
1546        {"GRADIENT",     Clino },
1547        {"IGNORE",       Ignore },
1548        {"IGNOREALL",    IgnoreAll },
[ee05463]1549        {"LEFT",         Left },
[5f1e194]1550        {"LENGTH",       Tape },
[a0027a2]1551        {"NEWLINE",      Newline },
[9b5d785]1552        {"NORTHING",     Dy },
[ee05463]1553        {"RIGHT",        Right },
[421b7d2]1554        {"STATION",      Station }, /* Fr&To in multiline */
[a4ae909]1555        {"TAPE",         Tape }, /* alternative name */
1556        {"TO",           To },
[647407d]1557        {"TOCOUNT",      ToCount },
[5f1e194]1558        {"TODEPTH",      ToDepth },
[ee05463]1559        {"UP",           Up },
[a4ae909]1560        {NULL,           End }
[5f1e194]1561   };
1562
[107b8bd]1563#define MASK_stns BIT(Fr) | BIT(To) | BIT(Station)
[4f38f94]1564#define MASK_tape BIT(Tape) | BIT(BackTape) | BIT(FrCount) | BIT(ToCount) | BIT(Count)
[107b8bd]1565#define MASK_dpth BIT(FrDepth) | BIT(ToDepth) | BIT(Depth) | BIT(DepthChange)
[5b7c1b7]1566#define MASK_comp BIT(Comp) | BIT(BackComp)
1567#define MASK_clin BIT(Clino) | BIT(BackClino)
[5f1e194]1568
[5b7c1b7]1569#define MASK_NORMAL MASK_stns | BIT(Dir) | MASK_tape | MASK_comp | MASK_clin
[bd263b36]1570#define MASK_DIVING MASK_NORMAL | MASK_dpth
[107b8bd]1571#define MASK_CARTESIAN MASK_stns | BIT(Dx) | BIT(Dy) | BIT(Dz)
[5b7c1b7]1572#define MASK_CYLPOLAR  MASK_stns | BIT(Dir) | MASK_tape | MASK_comp | MASK_dpth
[107b8bd]1573#define MASK_NOSURVEY MASK_stns
[19bb4772]1574#define MASK_PASSAGE BIT(Station) | BIT(Left) | BIT(Right) | BIT(Up) | BIT(Down)
[63ae487]1575#define MASK_IGNORE 0 // No readings in this style.
[421b7d2]1576
[63ae487]1577   // readings which may be given for each style (index is STYLE_*)
[6114207]1578   static const unsigned long mask[] = {
[200a12c]1579      MASK_NORMAL, MASK_DIVING, MASK_CARTESIAN, MASK_CYLPOLAR, MASK_NOSURVEY,
[63ae487]1580      MASK_PASSAGE, MASK_IGNORE
[5f1e194]1581   };
1582
[63ae487]1583   // readings which may be omitted for each style (index is STYLE_*)
[6114207]1584   static const unsigned long mask_optional[] = {
[5b7c1b7]1585      BIT(Dir) | BIT(Clino) | BIT(BackClino),
[bd263b36]1586      BIT(Dir) | BIT(Clino) | BIT(BackClino),
[86f26e2]1587      0,
[54c4612]1588      BIT(Dir),
[200a12c]1589      0,
[63ae487]1590      0, /* BIT(Left) | BIT(Right) | BIT(Up) | BIT(Down), */
1591      0
[5f1e194]1592   };
1593
[6114207]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),
[200a12c]1600      MASK_NOSURVEY | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
[63ae487]1601      MASK_PASSAGE | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1602      MASK_IGNORE
[6114207]1603   };
[54c4612]1604#define STYLE_DEFAULT   -2
1605#define STYLE_UNKNOWN   -1
1606
[82919e07]1607   static const sztok styletab[] = {
[647407d]1608        {"CARTESIAN",    STYLE_CARTESIAN },
[54c4612]1609        {"CYLPOLAR",     STYLE_CYLPOLAR },
[a420b49]1610        {"DEFAULT",      STYLE_DEFAULT },
[5f1e194]1611        {"DIVING",       STYLE_DIVING },
[63ae487]1612        {"IGNORE",       STYLE_IGNORE },
[5f1e194]1613        {"NORMAL",       STYLE_NORMAL },
[647407d]1614        {"NOSURVEY",     STYLE_NOSURVEY },
[ee05463]1615        {"PASSAGE",      STYLE_PASSAGE },
[107b8bd]1616        {"TOPOFIL",      STYLE_NORMAL },
[a4ae909]1617        {NULL,           STYLE_UNKNOWN }
[5f1e194]1618   };
1619
[ee3a4ed]1620#define m_multi (BIT(Station) | BIT(Count) | BIT(Depth))
1621
[dbe783b]1622   int style, k = 0;
1623   reading d;
[6114207]1624   unsigned long mUsed = 0;
[667e803c]1625   int old_style = pcs->style;
[5f1e194]1626
[50f6901]1627   /* after a bad *data command ignore survey data until the next
1628    * *data command to avoid an avalanche of errors */
[fdffa7d]1629   pcs->recorded_style = pcs->style = STYLE_IGNORE;
[289b7a8]1630
[a420b49]1631   get_token();
[5f1e194]1632   style = match_tok(styletab, TABSIZE(styletab));
1633
[a420b49]1634   if (style == STYLE_DEFAULT) {
1635      default_style(pcs);
1636      return;
1637   }
1638
[63ae487]1639   if (style == STYLE_IGNORE) {
1640      return;
1641   }
1642
[5f1e194]1643   if (style == STYLE_UNKNOWN) {
[0532954]1644      if (s_empty(&token)) {
[b9122e1]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.
[667e803c]1648          */
[fdffa7d]1649         pcs->recorded_style = pcs->style = style = old_style;
[667e803c]1650         goto reinit_style;
1651      }
[736f7df]1652      /* TRANSLATORS: e.g. trying to refer to an invalid FNORD data style */
[caae6cd]1653      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Data style “%s” unknown*/65, s_str(&token));
[5f1e194]1654      return;
1655   }
[a420b49]1656
[5f1e194]1657   skipblanks();
[d6d3576]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) {
[caae6cd]1664         compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*“*data %s %c …” is deprecated - use “*data %s …” instead*/104,
[0532954]1665                            s_str(&token), ch, s_str(&token));
[d6d3576]1666         if (++data_depr_count == 5)
[64544daf]1667            compile_diagnostic(DIAG_INFO, /*Further uses of this deprecated feature will not be reported*/95);
[d6d3576]1668      }
1669      nextch();
1670   }
1671#endif
[d1b1380]1672
[dbe783b]1673   int kMac = 6; /* minimum for NORMAL style */
1674   reading *new_order = osmalloc(kMac * sizeof(reading));
[0532954]1675   char *style_name = s_steal(&token);
[421b7d2]1676   do {
[c80bd34]1677      filepos fp;
1678      get_pos(&fp);
[a420b49]1679      get_token();
[5f1e194]1680      d = match_tok(dtab, TABSIZE(dtab));
[30c81bb]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);
[ae917b96]1685         free(style_name);
1686         free(new_order);
[30c81bb]1687         return;
1688      }
1689
[90bb053f]1690      /* only token allowed after IGNOREALL is NEWLINE */
1691      if (k && new_order[k - 1] == IgnoreAll && d != Newline) {
[c80bd34]1692         set_pos(&fp);
[90bb053f]1693         break;
1694      }
[0395657]1695      /* Note: an unknown token is reported as trailing garbage */
[6114207]1696      if (!TSTBIT(mask_all[style], d)) {
[736f7df]1697         /* TRANSLATORS: a data "style" is something like NORMAL, DIVING, etc.
1698          * a "reading" is one of FROM, TO, TAPE, COMPASS, CLINO for NORMAL
[88f302c]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.
[736f7df]1703          */
[caae6cd]1704         compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP,
[cab0f26]1705                            /*Reading “%s” not allowed in data style “%s”*/63,
[0532954]1706                            s_str(&token), style_name);
[ae917b96]1707         free(style_name);
1708         free(new_order);
[ee3a4ed]1709         return;
1710      }
[6114207]1711      if (TSTBIT(mUsed, Newline) && TSTBIT(m_multi, d)) {
[736f7df]1712         /* TRANSLATORS: caused by e.g.
1713          *
1714          * *data diving station newline depth tape compass
1715          *
1716          * ("depth" needs to occur before "newline"). */
[caae6cd]1717         compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP,
[0532954]1718                            /*Reading “%s” must occur before NEWLINE*/225, s_str(&token));
[ae917b96]1719         free(style_name);
1720         free(new_order);
[5f1e194]1721         return;
1722      }
[0395657]1723      /* Check for duplicates unless it's a special reading:
[ee3a4ed]1724       *   IGNOREALL,IGNORE (duplicates allowed) ; END (not possible)
[5f1e194]1725       */
[ee3a4ed]1726      if (!((BIT(Ignore) | BIT(End) | BIT(IgnoreAll)) & BIT(d))) {
[95c3272]1727         if (TSTBIT(mUsed, d)) {
[736f7df]1728            /* TRANSLATORS: complains about a situation like trying to define
1729             * two from stations per leg */
[caae6cd]1730            compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Duplicate reading “%s”*/67, s_str(&token));
[ae917b96]1731            free(style_name);
1732            free(new_order);
[90bb053f]1733            return;
[62bb4d3]1734         } else {
[5b7c1b7]1735            /* Check for previously listed readings which are incompatible
1736             * with this one - e.g. Count vs FrCount */
[63d4f07]1737            bool fBad = false;
[90bb053f]1738            switch (d) {
1739             case Station:
[63d4f07]1740               if (mUsed & (BIT(Fr) | BIT(To))) fBad = true;
[90bb053f]1741               break;
1742             case Fr: case To:
[63d4f07]1743               if (TSTBIT(mUsed, Station)) fBad = true;
[90bb053f]1744               break;
1745             case Count:
[107b8bd]1746               if (mUsed & (BIT(FrCount) | BIT(ToCount) | BIT(Tape)))
[63d4f07]1747                  fBad = true;
[90bb053f]1748               break;
1749             case FrCount: case ToCount:
[107b8bd]1750               if (mUsed & (BIT(Count) | BIT(Tape)))
[63d4f07]1751                  fBad = true;
[90bb053f]1752               break;
1753             case Depth:
[6114207]1754               if (mUsed & (BIT(FrDepth) | BIT(ToDepth) | BIT(DepthChange)))
[63d4f07]1755                  fBad = true;
[90bb053f]1756               break;
1757             case FrDepth: case ToDepth:
[63d4f07]1758               if (mUsed & (BIT(Depth) | BIT(DepthChange))) fBad = true;
[a186573]1759               break;
[6114207]1760             case DepthChange:
[a186573]1761               if (mUsed & (BIT(FrDepth) | BIT(ToDepth) | BIT(Depth)))
[63d4f07]1762                  fBad = true;
[90bb053f]1763               break;
[ee3a4ed]1764             case Newline:
1765               if (mUsed & ~m_multi) {
[736f7df]1766                  /* TRANSLATORS: e.g.
1767                   *
1768                   * *data normal from to tape newline compass clino */
[caae6cd]1769                  compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*NEWLINE can only be preceded by STATION, DEPTH, and COUNT*/226);
[ae917b96]1770                  free(style_name);
1771                  free(new_order);
[ee3a4ed]1772                  return;
1773               }
[6114207]1774               if (k == 0) {
[736f7df]1775                  /* TRANSLATORS: error from:
1776                   *
1777                   * *data normal newline from to tape compass clino */
[caae6cd]1778                  compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*NEWLINE can’t be the first reading*/222);
[ae917b96]1779                  free(style_name);
1780                  free(new_order);
[6114207]1781                  return;
1782               }
[ee3a4ed]1783               break;
[90bb053f]1784             default: /* avoid compiler warnings about unhandled enums */
1785               break;
[421b7d2]1786            }
[90bb053f]1787            if (fBad) {
[46cb98f]1788               /* Not entirely happy with phrasing this... */
[36efb03]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). */
[caae6cd]1794               compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Reading “%s” duplicates previous reading(s)*/77,
[0532954]1795                                  s_str(&token));
[ae917b96]1796               free(style_name);
1797               free(new_order);
[90bb053f]1798               return;
1799            }
[62bb4d3]1800            mUsed |= BIT(d); /* used to catch duplicates */
[a420b49]1801         }
[d1b1380]1802      }
[90bb053f]1803      if (k && new_order[k - 1] == IgnoreAll) {
[4c07c51]1804         SVX_ASSERT(d == Newline);
[90bb053f]1805         k--;
1806         d = IgnoreAllAndNewLine;
1807      }
[a420b49]1808      if (k >= kMac) {
1809         kMac = kMac * 2;
[0395657]1810         new_order = osrealloc(new_order, kMac * sizeof(reading));
[a420b49]1811      }
1812      new_order[k++] = d;
[90bb053f]1813   } while (d != End);
1814
[6114207]1815   if (k >= 2 && new_order[k - 2] == Newline) {
[736f7df]1816      /* TRANSLATORS: error from:
1817       *
1818       * *data normal from to tape compass clino newline */
[caae6cd]1819      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*NEWLINE can’t be the last reading*/223);
[ae917b96]1820      free(style_name);
1821      free(new_order);
[6114207]1822      return;
1823   }
[421b7d2]1824
[6114207]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      }
[ee05463]1834   } else if (style == STYLE_PASSAGE) {
1835      /* Station doesn't mean "multiline" for STYLE_PASSAGE. */
[6114207]1836   } else if (!TSTBIT(mUsed, Newline) && (m_multi & mUsed)) {
[736f7df]1837      /* TRANSLATORS: Error given by something like:
1838       *
[6114207]1839       * *data normal station tape compass clino
[736f7df]1840       *
1841       * ("station" signifies interleaved data). */
[cab0f26]1842      compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Interleaved readings, but no NEWLINE*/224);
[ae917b96]1843      free(style_name);
1844      free(new_order);
[6114207]1845      return;
1846   }
1847
[a6d094f]1848#if 0
1849   printf("mUsed = 0x%x\n", mUsed);
1850#endif
[90bb053f]1851
[5b7c1b7]1852   /* Check the supplied readings form a sufficient set. */
[ee05463]1853   if (style != STYLE_PASSAGE) {
[4f38f94]1854       if ((mUsed & (BIT(Fr) | BIT(To))) == (BIT(Fr) | BIT(To)))
[ee05463]1855           mUsed |= BIT(Station);
1856       else if (TSTBIT(mUsed, Station))
1857           mUsed |= BIT(Fr) | BIT(To);
1858   }
[a186573]1859
[5b7c1b7]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
[4f38f94]1866   if ((mUsed & (BIT(FrDepth) | BIT(ToDepth))) == (BIT(FrDepth) | BIT(ToDepth)))
[6114207]1867      mUsed |= BIT(Depth) | BIT(DepthChange);
[4f38f94]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);
[90bb053f]1875
[a6d094f]1876#if 0
1877   printf("mUsed = 0x%x, opt = 0x%x, mask = 0x%x\n", mUsed,
1878          mask_optional[style], mask[style]);
1879#endif
[cb3d1e2]1880
[6114207]1881   if (((mUsed &~ BIT(Newline)) | mask_optional[style]) != mask[style]) {
1882      /* Test should only fail with too few bits set, not too many */
[4c07c51]1883      SVX_ASSERT((((mUsed &~ BIT(Newline)) | mask_optional[style])
[6114207]1884              &~ mask[style]) == 0);
[736f7df]1885      /* TRANSLATORS: i.e. not enough readings for the style. */
[cab0f26]1886      compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Too few readings for data style “%s”*/64, style_name);
[ae917b96]1887      free(style_name);
1888      free(new_order);
[a420b49]1889      return;
1890   }
[d1b1380]1891
[a420b49]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))
[ae917b96]1895      free((reading*)pcs->ordering);
[cb3d1e2]1896
[fdffa7d]1897   pcs->recorded_style = pcs->style = style;
[a420b49]1898   pcs->ordering = new_order;
[1b34062]1899
[ae917b96]1900   free(style_name);
[ee05463]1901
[667e803c]1902reinit_style:
[ee05463]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   }
[d1b1380]1910}
1911
[a420b49]1912static void
[eb18f4d]1913cmd_units(void)
[a420b49]1914{
[5f1e194]1915   int units, quantity;
[6114207]1916   unsigned long qmask;
1917   unsigned long m; /* mask with bit x set to indicate quantity x specified */
[5f1e194]1918   real factor;
[9f55538]1919   filepos fp;
[a420b49]1920
[699bf50]1921   qmask = get_qlist(BIT(Q_POS)|BIT(Q_PLUMB)|BIT(Q_LEVEL));
1922
[647407d]1923   if (!qmask) return;
[a420b49]1924   if (qmask == BIT(Q_DEFAULT)) {
1925      default_units(pcs);
[5f1e194]1926      return;
1927   }
[a420b49]1928
[9f55538]1929   get_pos(&fp);
[63d4f07]1930   factor = read_numeric(true);
[fa42426]1931   if (factor == 0.0) {
[9f55538]1932      set_pos(&fp);
[736f7df]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"). */
[d0be687d]1937      compile_diagnostic(DIAG_ERR|DIAG_WORD, /**UNITS factor must be non-zero*/200);
[9f55538]1938      skipline();
[fa42426]1939      return;
1940   }
[a420b49]1941
[63d4f07]1942   units = get_units(qmask, true);
[647407d]1943   if (units == UNITS_NULL) return;
[fa42426]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
[00b10c1]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
[6eb50ab]1956   if (factor == HUGE_REAL) {
1957      factor = factor_tab[units];
1958   } else {
1959      factor *= factor_tab[units];
1960   }
[5f1e194]1961
1962   for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1)
1963      if (qmask & m) pcs->units[quantity] = factor;
[d1b1380]1964}
1965
[a420b49]1966static void
[eb18f4d]1967cmd_calibrate(void)
[a420b49]1968{
[d6d3576]1969   real sc, z;
[67508f0]1970   unsigned long qmask, m;
[a420b49]1971   int quantity;
[9f55538]1972   filepos fp;
[46cb98f]1973
[da96015]1974   qmask = get_qlist(BIT(Q_POS)|BIT(Q_PLUMB)|BIT(Q_LEVEL));
[46cb98f]1975   if (!qmask) return; /* error already reported */
1976
[a420b49]1977   if (qmask == BIT(Q_DEFAULT)) {
1978      default_calib(pcs);
1979      return;
1980   }
[46cb98f]1981
[a420b49]1982   if (((qmask & LEN_QMASK)) && ((qmask & ANG_QMASK))) {
[0b8c321]1983      /* TRANSLATORS: e.g.
1984       *
1985       * *calibrate tape compass 1 1
1986       */
[cab0f26]1987      compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Can’t calibrate angular and length quantities together*/227);
[647407d]1988      return;
[a420b49]1989   }
[46cb98f]1990
[63d4f07]1991   z = read_numeric(false);
[9f55538]1992   get_pos(&fp);
[63d4f07]1993   sc = read_numeric(true);
[88569fe]1994   if (sc == HUGE_REAL) {
1995      if (isalpha(ch)) {
[63d4f07]1996         int units = get_units(qmask, false);
[88569fe]1997         if (units == UNITS_NULL) {
1998            return;
1999         }
2000         z *= factor_tab[units];
[63d4f07]2001         sc = read_numeric(true);
[88569fe]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
[a420b49]2016   if (sc == HUGE_REAL) sc = (real)1.0;
[647407d]2017   /* check for declination scale */
[95c3272]2018   if (TSTBIT(qmask, Q_DECLINATION) && sc != 1.0) {
[9f55538]2019      set_pos(&fp);
[736f7df]2020      /* TRANSLATORS: DECLINATION is a built-in keyword, so best not to
2021       * translate */
[d0be687d]2022      compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Scale factor must be 1.0 for DECLINATION*/40);
[9f55538]2023      skipline();
[647407d]2024      return;
2025   }
[4b14118]2026   if (sc == 0.0) {
[9f55538]2027      set_pos(&fp);
[736f7df]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. */
[d0be687d]2030      compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Scale factor must be non-zero*/391);
[9f55538]2031      skipline();
[4b14118]2032      return;
2033   }
[647407d]2034   for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1) {
[a420b49]2035      if (qmask & m) {
[647407d]2036         pcs->z[quantity] = pcs->units[quantity] * z;
[a420b49]2037         pcs->sc[quantity] = sc;
2038      }
[647407d]2039   }
[d1b1380]2040}
2041
[abe7192]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
[58c7b459]2075static void
2076cmd_declination(void)
2077{
[63d4f07]2078    real v = read_numeric(true);
[58c7b459]2079    if (v == HUGE_REAL) {
[e1cbc0d]2080        get_token_legacy_no_blanks();
[0532954]2081        if (!S_EQ(&uctoken, "AUTO")) {
[d72396e]2082            compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_COL, /*Expected number or “AUTO”*/309);
[58c7b459]2083            return;
2084        }
[d655496]2085        do_legacy_token_warning();
[b39e24a]2086        if (!pcs->proj_str) {
[abe7192]2087            // TRANSLATORS: %s is replaced by the command that requires it, e.g.
2088            // *DECLINATION AUTO
[d655496]2089            compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_TOKEN,
2090                               /*Input coordinate system must be specified for “%s”*/301,
[abe7192]2091                               "*DECLINATION AUTO");
[58c7b459]2092            return;
2093        }
[d655496]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);
[69a9e01]2099        set_declination_location(x, y, z, pcs->proj_str);
[58c7b459]2100    } else {
2101        /* *declination D UNITS */
[63d4f07]2102        int units = get_units(BIT(Q_DECLINATION), false);
[58c7b459]2103        if (units == UNITS_NULL) {
2104            return;
2105        }
[85d8e0c]2106        pcs->z[Q_DECLINATION] = -v * factor_tab[units];
[2c17123e]2107        pcs->convergence = 0;
[58c7b459]2108    }
2109}
2110
[7f1ab95]2111#ifndef NO_DEPRECATED
[a420b49]2112static void
[eb18f4d]2113cmd_default(void)
[a420b49]2114{
[82919e07]2115   static const sztok defaulttab[] = {
[c0563da]2116      { "CALIBRATE", CMD_CALIBRATE },
2117      { "DATA",      CMD_DATA },
2118      { "UNITS",     CMD_UNITS },
[a4ae909]2119      { NULL,        CMD_NULL }
[c0563da]2120   };
[c86cc71]2121   static int default_depr_count = 0;
2122
2123   if (default_depr_count < 5) {
[736f7df]2124      /* TRANSLATORS: If you're unsure what "deprecated" means, see:
[2c1c52e]2125       * https://en.wikipedia.org/wiki/Deprecation */
[68f7ba4]2126      compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /**DEFAULT is deprecated - use *CALIBRATE/DATA/SD/UNITS with argument DEFAULT instead*/20);
[c86cc71]2127      if (++default_depr_count == 5)
[64544daf]2128         compile_diagnostic(DIAG_INFO, /*Further uses of this deprecated feature will not be reported*/95);
[c86cc71]2129   }
2130
[cb3d1e2]2131   get_token();
[c0563da]2132   switch (match_tok(defaulttab, TABSIZE(defaulttab))) {
2133    case CMD_CALIBRATE:
[5f1e194]2134      default_calib(pcs);
[c0563da]2135      break;
2136    case CMD_DATA:
[5f1e194]2137      default_style(pcs);
2138      default_grade(pcs);
[abe7192]2139      pcs->cartesian_north = TRUE_NORTH;
2140      pcs->cartesian_rotation = 0.0;
[c0563da]2141      break;
2142    case CMD_UNITS:
[5f1e194]2143      default_units(pcs);
[c0563da]2144      break;
2145    default:
[caae6cd]2146      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown setting “%s”*/41,
[0532954]2147                         s_str(&token));
[5f1e194]2148   }
[d1b1380]2149}
[7f1ab95]2150#endif
[d1b1380]2151
[a420b49]2152static void
[eb18f4d]2153cmd_include(void)
[a420b49]2154{
[0532954]2155   char *pth = NULL;
2156   string fnm = S_INIT;
[a882316]2157#ifndef NO_DEPRECATED
[5f1e194]2158   prefix *root_store;
[a882316]2159#endif
[5f1e194]2160   int ch_store;
2161
[f97076a]2162   pth = path_from_fnm(file.filename);
2163
[0532954]2164   read_string(&fnm);
[d1b1380]2165
[a882316]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 */
[5f1e194]2170   root_store = root;
2171   root = pcs->Prefix; /* Root for include file is current prefix */
[a882316]2172#endif
[5f1e194]2173   ch_store = ch;
[cb3d1e2]2174
[0532954]2175   data_file(pth, s_str(&fnm));
[a420b49]2176
[a882316]2177#ifndef NO_DEPRECATED
[5f1e194]2178   root = root_store; /* and restore root */
[647407d]2179#endif
[5f1e194]2180   ch = ch_store;
[d1b1380]2181
[a420b49]2182   s_free(&fnm);
[ae917b96]2183   free(pth);
[d1b1380]2184}
2185
[a420b49]2186static void
[eb18f4d]2187cmd_sd(void)
[a420b49]2188{
[5f1e194]2189   real sd, variance;
2190   int units;
[67508f0]2191   unsigned long qmask, m;
[5f1e194]2192   int quantity;
[b14f44f]2193   qmask = get_qlist(BIT(Q_DECLINATION));
[46cb98f]2194   if (!qmask) return; /* no quantities found - error already reported */
2195
[a420b49]2196   if (qmask == BIT(Q_DEFAULT)) {
2197      default_grade(pcs);
[5f1e194]2198      return;
2199   }
[63d4f07]2200   sd = read_numeric(false);
[d6d3576]2201   if (sd <= (real)0.0) {
[cab0f26]2202      compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_COL, /*Standard deviation must be positive*/48);
[d6d3576]2203      return;
[5f1e194]2204   }
[63d4f07]2205   units = get_units(qmask, false);
[647407d]2206   if (units == UNITS_NULL) return;
[5f1e194]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;
[d1b1380]2213}
[5f1e194]2214
[0f8216c]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;
[62be3ee]2282    if (!read_string_warning(&name)) {
2283        skipline();
2284        return;
2285    }
[0f8216c]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));
[6b4d2e9]2299            return;
[0f8216c]2300        }
2301    }
2302}
2303
[a420b49]2304static void
[eb18f4d]2305cmd_title(void)
[a420b49]2306{
[1925d66]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. */
[63d4f07]2310      fExplicitTitle = true;
[0532954]2311      read_string(&survey_title);
[a420b49]2312   } else {
2313      /* parse and throw away this title (but still check rest of line) */
[0532954]2314      string s = S_INIT;
2315      read_string(&s);
[a420b49]2316      s_free(&s);
2317   }
2318}
2319
[82919e07]2320static const sztok case_tab[] = {
[a420b49]2321     {"PRESERVE", OFF},
[c57e9da]2322     {"TOLOWER",  LOWER},
2323     {"TOUPPER",  UPPER},
[a420b49]2324     {NULL,       -1}
2325};
[cb3d1e2]2326
[a420b49]2327static void
[eb18f4d]2328cmd_case(void)
[a420b49]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 {
[caae6cd]2336      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found “%s”, expecting “PRESERVE”, “TOUPPER”, or “TOLOWER”*/10, s_str(&token));
[a420b49]2337   }
2338}
2339
[f15c53d9]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);
[cac5622]2350    } else if (y1 > current_year) {
2351        set_pos(&fp);
2352        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Date is in the future!*/80);
[f15c53d9]2353    }
2354    if (ch == '-') {
2355        nextch();
2356        get_pos(&fp);
2357        unsigned y2 = read_uint_raw(DIAG_WARN|DIAG_UINT, /*Invalid year*/534, NULL);
2358        if (y2 < 1000) {
2359            set_pos(&fp);
2360            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year*/534, y2);
2361        } else if (y2 < y1) {
2362            set_pos(&fp);
2363            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*End of date range is before the start*/81);
[cac5622]2364        } else if (y2 > current_year) {
2365            set_pos(&fp);
2366            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Date is in the future!*/80);
[f15c53d9]2367        }
2368    }
2369
2370    string text = S_INIT;
2371    if (!read_string_warning(&text)) {
2372        skipline();
2373        return;
2374    }
2375    s_free(&text);
2376
2377    skipblanks();
2378    if (!isComm(ch) && !isEol(ch))
2379        compile_diagnostic(DIAG_WARN|DIAG_TAIL, /*End of line not blank*/15);
2380}
2381
[abd0310]2382typedef enum {
2383    CS_NONE = -1,
2384    CS_CUSTOM,
2385    CS_EPSG,
2386    CS_ESRI,
[b8fb5ac]2387    CS_EUR79Z30,
[abd0310]2388    CS_IJTSK,
[b8fb5ac]2389    CS_IJTSK03,
[abd0310]2390    CS_JTSK,
[b8fb5ac]2391    CS_JTSK03,
[abd0310]2392    CS_LAT,
2393    CS_LOCAL,
2394    CS_LONG,
2395    CS_OSGB,
2396    CS_S_MERC,
2397    CS_UTM
2398} cs_class;
2399
[82919e07]2400static const sztok cs_tab[] = {
[b8fb5ac]2401     {"CUSTOM",   CS_CUSTOM},
2402     {"EPSG",     CS_EPSG},     /* EPSG:<number> */
2403     {"ESRI",     CS_ESRI},     /* ESRI:<number> */
2404     {"EUR79Z30", CS_EUR79Z30},
2405     {"IJTSK",    CS_IJTSK},
2406     {"IJTSK03",  CS_IJTSK03},
2407     {"JTSK",     CS_JTSK},
2408     {"JTSK03",   CS_JTSK03},
2409     {"LAT",      CS_LAT},      /* LAT-LONG */
2410     {"LOCAL",    CS_LOCAL},
2411     {"LONG",     CS_LONG},     /* LONG-LAT */
2412     {"OSGB",     CS_OSGB},     /* OSGB:<H, N, O, S or T><A-Z except I> */
2413     {"S",        CS_S_MERC},   /* S-MERC */
2414     // UTM<zone><N or S or nothing> is handled separately to avoid needing 180
2415     // entries in this lookup table.
2416     {NULL,       CS_NONE}
[abd0310]2417};
2418
2419static void
2420cmd_cs(void)
2421{
[0532954]2422   char *proj_str = NULL;
[abd0310]2423   cs_class cs;
2424   int cs_sub = INT_MIN;
2425   filepos fp;
[63d4f07]2426   bool output = false;
[c092d72]2427   enum { YES, NO, MAYBE } ok_for_output = YES;
[63d4f07]2428   static bool had_cs = false;
[56db37f]2429
2430   if (!had_cs) {
[63d4f07]2431      had_cs = true;
[56db37f]2432      if (first_fix_name) {
[cab0f26]2433         compile_diagnostic_at(DIAG_ERR,
2434                               first_fix_filename, first_fix_line,
2435                               /*Station “%s” fixed before CS command first used*/442,
2436                               sprint_prefix(first_fix_name));
[56db37f]2437      }
2438   }
[abd0310]2439
[b8fb5ac]2440   skipblanks();
[abd0310]2441   get_pos(&fp);
[b8fb5ac]2442   get_token_no_blanks();
[0532954]2443   if (S_EQ(&uctoken, "OUT")) {
[63d4f07]2444      output = true;
[b8fb5ac]2445      skipblanks();
[9f55538]2446      get_pos(&fp);
[b8fb5ac]2447      get_token_no_blanks();
2448   }
2449
2450   if (s_len(&uctoken) > 3 &&
2451       memcmp(s_str(&uctoken), "UTM", 3) == 0 &&
2452       isdigit((unsigned char)s_str(&uctoken)[3])) {
2453       // The token starts "UTM" followed by a digit so handle that separately
2454       // to avoid needing 180 entries for UTM zones in the cs_tab lookup
2455       // table.
2456       cs = CS_UTM;
2457       // Reposition on the digit after "UTM".
2458       set_pos(&fp);
2459       nextch();
2460       nextch();
2461       nextch();
2462       unsigned n = read_uint();
2463       if (n >= 1 && n <= 60) {
2464           int uch = toupper(ch);
2465           cs_sub = (int)n;
2466           if (uch == 'S') {
[abd0310]2467               nextch();
[b8fb5ac]2468               cs_sub = -cs_sub;
2469           } else if (uch == 'N') {
2470               nextch();
2471           }
2472       }
2473   } else {
2474       cs = match_tok(cs_tab, TABSIZE(cs_tab));
2475       switch (cs) {
2476         case CS_NONE:
2477           break;
2478         case CS_CUSTOM:
2479           ok_for_output = MAYBE;
2480           get_pos(&fp);
2481           string str = S_INIT;
2482           read_string(&str);
2483           proj_str = s_steal(&str);
2484           cs_sub = 0;
2485           break;
2486         case CS_EPSG: case CS_ESRI:
2487           ok_for_output = MAYBE;
2488           if (ch == ':' && isdigit(nextch())) {
2489               unsigned n = read_uint();
2490               if (n < 1000000) {
2491                   cs_sub = (int)n;
[abd0310]2492               }
[b8fb5ac]2493           }
2494           break;
2495         case CS_EUR79Z30:
2496           cs_sub = 0;
2497           break;
2498         case CS_JTSK:
2499         case CS_JTSK03:
2500           ok_for_output = NO;
2501           cs_sub = 0;
2502           break;
2503         case CS_IJTSK:
2504         case CS_IJTSK03:
2505           cs_sub = 0;
2506           break;
2507         case CS_LAT: case CS_LONG:
2508           ok_for_output = NO;
2509           if (ch == '-') {
2510               nextch();
2511               get_token_no_blanks();
2512               cs_class cs2 = match_tok(cs_tab, TABSIZE(cs_tab));
2513               if ((cs ^ cs2) == (CS_LAT ^ CS_LONG)) {
2514                   cs_sub = 0;
[abd0310]2515               }
[b8fb5ac]2516           }
2517           break;
2518         case CS_LOCAL:
2519           cs_sub = 0;
2520           break;
2521         case CS_OSGB:
2522           if (ch == ':') {
2523               int uch1 = toupper(nextch());
2524               if (strchr("HNOST", uch1)) {
2525                   int uch2 = toupper(nextch());
2526                   if (uch2 >= 'A' && uch2 <= 'Z' && uch2 != 'I') {
2527                       int x, y;
2528                       nextch();
2529                       if (uch1 > 'I') --uch1;
2530                       uch1 -= 'A';
2531                       if (uch2 > 'I') --uch2;
2532                       uch2 -= 'A';
2533                       x = uch1 % 5;
2534                       y = uch1 / 5;
2535                       x = (x * 5) + uch2 % 5;
2536                       y = (y * 5) + uch2 / 5;
2537                       cs_sub = y * 25 + x;
2538                   }
2539               }
2540           }
2541           break;
2542         case CS_S_MERC:
2543           if (ch == '-') {
2544               nextch();
2545               get_token_no_blanks();
2546               if (S_EQ(&uctoken, "MERC")) {
2547                   cs_sub = 0;
2548               }
2549           }
2550           break;
2551         case CS_UTM:
2552           // Handled outside of this switch, but avoid compiler warning about
2553           // unhandled enumeration value.
2554           break;
2555       }
[abd0310]2556   }
2557   if (cs_sub == INT_MIN || isalnum(ch)) {
2558      set_pos(&fp);
[b8fb5ac]2559      compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Unknown coordinate system*/434);
[9f55538]2560      skipline();
[c092d72]2561      return;
[abd0310]2562   }
2563   /* Actually handle the cs */
[c092d72]2564   switch (cs) {
[5d36f97]2565      case CS_NONE:
2566         break;
2567      case CS_CUSTOM:
2568         /* proj_str already set */
2569         break;
[ddd24f28]2570      case CS_EPSG:
2571         proj_str = osmalloc(32);
[657fcee]2572         snprintf(proj_str, 32, "EPSG:%d", cs_sub);
[ddd24f28]2573         break;
2574      case CS_ESRI:
2575         proj_str = osmalloc(32);
[657fcee]2576         snprintf(proj_str, 32, "ESRI:%d", cs_sub);
[ddd24f28]2577         break;
[b8fb5ac]2578      case CS_EUR79Z30:
[ddd24f28]2579         proj_str = osstrdup("+proj=utm +zone=30 +ellps=intl +towgs84=-86,-98,-119,0,0,0,0 +no_defs");
2580         break;
[5598e2c]2581      case CS_IJTSK:
[b8fb5ac]2582         proj_str = osstrdup("+proj=krovak +ellps=bessel +towgs84=570.8285,85.6769,462.842,4.9984,1.5867,5.2611,3.5623 +no_defs");
2583         break;
2584      case CS_IJTSK03:
2585         proj_str = osstrdup("+proj=krovak +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +no_defs");
[5598e2c]2586         break;
[10af28e]2587      case CS_JTSK:
[b8fb5ac]2588         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");
2589         break;
2590      case CS_JTSK03:
2591         proj_str = osstrdup("+proj=krovak +czech +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +no_defs");
[10af28e]2592         break;
[16734b2]2593      case CS_LAT:
2594         /* FIXME: Requires PROJ >= 4.8.0 for +axis, and the SDs will be
[10af28e]2595          * misapplied, so we may want to swap ourselves.  Also, while
2596          * therion supports lat-long, I'm not totally convinced that it is
2597          * sensible to do so - people often say "lat-long", but probably
2598          * don't think that that's actually "Northing, Easting".  This
2599          * seems like it'll result in people accidentally getting X and Y
2600          * swapped in their fixed points...
2601          */
[5d36f97]2602#if 0
[cb0a137]2603         proj_str = osstrdup("+proj=longlat +ellps=WGS84 +datum=WGS84 +axis=neu +no_defs");
[5d36f97]2604#endif
[16734b2]2605         break;
[10af28e]2606      case CS_LOCAL:
2607         /* FIXME: Is it useful to be able to explicitly specify this? */
2608         break;
[16734b2]2609      case CS_LONG:
[b39e24a]2610         proj_str = osstrdup("EPSG:4326");
[16734b2]2611         break;
[a4cd4eea]2612      case CS_OSGB: {
2613         int x = 14 - (cs_sub % 25);
2614         int y = (cs_sub / 25) - 20;
2615         proj_str = osmalloc(160);
[657fcee]2616         snprintf(proj_str, 160,
2617                  "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 "
2618                  "+x_0=%d +y_0=%d +ellps=airy +datum=OSGB36 +units=m +no_defs",
2619                  x * 100000, y * 100000);
[a4cd4eea]2620         break;
2621      }
[2076d59]2622      case CS_S_MERC:
[b39e24a]2623         proj_str = osstrdup("EPSG:3857");
[2076d59]2624         break;
[c092d72]2625      case CS_UTM:
[b39e24a]2626         proj_str = osmalloc(32);
[c092d72]2627         if (cs_sub > 0) {
[657fcee]2628            snprintf(proj_str, 32, "EPSG:%d", 32600 + cs_sub);
[c092d72]2629         } else {
[657fcee]2630            snprintf(proj_str, 32, "EPSG:%d", 32700 - cs_sub);
[c092d72]2631         }
2632         break;
2633   }
2634
[10af28e]2635   if (!proj_str) {
2636      /* printf("CS %d:%d\n", (int)cs, cs_sub); */
2637      set_pos(&fp);
[88ba3b1]2638      compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Unknown coordinate system*/434);
[9f55538]2639      skipline();
[10af28e]2640      return;
2641   }
2642
[c092d72]2643   if (output) {
2644      if (ok_for_output == NO) {
2645         set_pos(&fp);
[88ba3b1]2646         compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Coordinate system unsuitable for output*/435);
[9f55538]2647         skipline();
[c092d72]2648         return;
2649      }
2650
[da9163b]2651      if (proj_str_out && strcmp(proj_str, proj_str_out) == 0) {
2652          /* Same as the output cs that's already set, so nothing to do. */
[ae917b96]2653          free(proj_str);
[da9163b]2654          return;
2655      }
2656
2657      if (ok_for_output == MAYBE) {
2658          /* We only actually create the transformation from input to output when
2659           * we need it, but for a custom proj string or EPSG/ESRI code we need
2660           * to check that the specified coordinate system is valid and also if
2661           * it's suitable for output so we need to test creating it here.
2662           */
[df1d579]2663          proj_errno_reset(NULL);
[da9163b]2664          PJ* pj = proj_create(PJ_DEFAULT_CTX, proj_str);
2665          if (!pj) {
2666              set_pos(&fp);
[88ba3b1]2667              compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Invalid coordinate system: %s*/443,
[d9d8f21]2668                                 proj_context_errno_string(PJ_DEFAULT_CTX,
2669                                                           proj_context_errno(PJ_DEFAULT_CTX)));
[da9163b]2670              skipline();
[ae917b96]2671              free(proj_str);
[da9163b]2672              return;
2673          }
2674          int type = proj_get_type(pj);
2675          if (type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
2676              type == PJ_TYPE_GEOGRAPHIC_3D_CRS) {
2677              set_pos(&fp);
[88ba3b1]2678              compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Coordinate system unsuitable for output*/435);
[da9163b]2679              skipline();
[ae917b96]2680              free(proj_str);
[da9163b]2681              return;
2682          }
2683      }
2684
2685      if (proj_str_out) {
2686          /* If the output cs is already set, subsequent attempts to set it
2687           * are silently ignored (so you can combine two datasets and set
2688           * the output cs to use before you include either).
2689           */
[ae917b96]2690          free(proj_str);
[da9163b]2691      } else {
2692          proj_str_out = proj_str;
[c092d72]2693      }
2694   } else {
2695      if (proj_str_out && strcmp(proj_str, proj_str_out) == 0) {
[da9163b]2696         /* Same as the current output projection, so valid for input. */
2697      } else if (pcs->proj_str && strcmp(proj_str, pcs->proj_str) == 0) {
2698         /* Same as the current input projection, so nothing to do! */
2699         return;
2700      } else if (ok_for_output == MAYBE) {
2701         /* (ok_for_output == MAYBE) also happens to indicate whether we need
2702          * to check that the coordinate system is valid for input.
2703          */
[df1d579]2704         proj_errno_reset(NULL);
[b39e24a]2705         PJ* pj = proj_create(PJ_DEFAULT_CTX, proj_str);
[c092d72]2706         if (!pj) {
2707            set_pos(&fp);
[88ba3b1]2708            compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Invalid coordinate system: %s*/443,
[d9d8f21]2709                               proj_context_errno_string(PJ_DEFAULT_CTX,
2710                                                         proj_context_errno(PJ_DEFAULT_CTX)));
[9f55538]2711            skipline();
[c092d72]2712            return;
2713         }
[b39e24a]2714         proj_destroy(pj);
[c092d72]2715      }
2716
[da9163b]2717      /* Free current input proj_str if not used by parent. */
[c092d72]2718      settings * p = pcs;
[da9163b]2719      if (!p->next || p->proj_str != p->next->proj_str)
[ae917b96]2720         free(p->proj_str);
[b39e24a]2721      p->proj_str = proj_str;
[abe7192]2722      p->input_convergence = HUGE_REAL;
[da9163b]2723      invalidate_pj_cached();
[c092d72]2724   }
[abd0310]2725}
2726
[82919e07]2727static const sztok infer_tab[] = {
[27b8b59]2728     { "EQUATES",       INFER_EQUATES },
2729     { "EXPORTS",       INFER_EXPORTS },
2730     { "PLUMBS",        INFER_PLUMBS },
2731#if 0 /* FIXME */
2732     { "SUBSURVEYS",    INFER_SUBSURVEYS },
2733#endif
2734     { NULL,            INFER_NULL }
[a420b49]2735};
[cb3d1e2]2736
[82919e07]2737static const sztok onoff_tab[] = {
[27b8b59]2738     { "OFF", 0 },
2739     { "ON",  1 },
2740     { NULL, -1 }
[a420b49]2741};
[cb3d1e2]2742
[a420b49]2743static void
[eb18f4d]2744cmd_infer(void)
[a420b49]2745{
[27b8b59]2746   infer_what setting;
[a420b49]2747   int on;
2748   get_token();
2749   setting = match_tok(infer_tab, TABSIZE(infer_tab));
[27b8b59]2750   if (setting == INFER_NULL) {
[caae6cd]2751      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found “%s”, expecting “EQUATES”, “EXPORTS”, or “PLUMBS”*/31, s_str(&token));
[647407d]2752      return;
[a420b49]2753   }
2754   get_token();
2755   on = match_tok(onoff_tab, TABSIZE(onoff_tab));
2756   if (on == -1) {
[caae6cd]2757      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found “%s”, expecting “ON” or “OFF”*/32, s_str(&token));
[647407d]2758      return;
[a420b49]2759   }
[cb3d1e2]2760
[27b8b59]2761   if (on) {
2762      pcs->infer |= BIT(setting);
[63d4f07]2763      if (setting == INFER_EXPORTS) fExportUsed = true;
[27b8b59]2764   } else {
2765      pcs->infer &= ~BIT(setting);
[a420b49]2766   }
2767}
2768
2769static void
[eb18f4d]2770cmd_truncate(void)
[a420b49]2771{
[647407d]2772   unsigned int truncate_at = 0; /* default is no truncation */
[c80bd34]2773   filepos fp;
2774
2775   get_pos(&fp);
2776
[647407d]2777   get_token();
[0532954]2778   if (!S_EQ(&uctoken, "OFF")) {
2779      if (!s_empty(&uctoken)) set_pos(&fp);
[647407d]2780      truncate_at = read_uint();
2781   }
2782   /* for backward compatibility, "*truncate 0" means "*truncate off" */
2783   pcs->Truncate = (truncate_at == 0) ? INT_MAX : truncate_at;
2784}
2785
[e8452e3]2786static void
2787cmd_ref(void)
2788{
2789   /* Just syntax check for now. */
[0532954]2790   string ref = S_INIT;
2791   read_string(&ref);
[e8452e3]2792   s_free(&ref);
2793}
2794
[647407d]2795static void
[eb18f4d]2796cmd_require(void)
[647407d]2797{
[9af1e2c0]2798    // Add extra 0 so `*require 1.4.10.1` fails with cavern version 1.4.10.
2799    const unsigned version[] = {COMMAVERSION, 0};
[c80bd34]2800
[9af1e2c0]2801    skipblanks();
2802    filepos fp;
2803    get_pos(&fp);
2804
2805    // Parse the required version number, storing its components in
2806    // required_version.  We only store at most one more component than
2807    // COMMAVERSION has since more than that can't affect the comparison.
2808    size_t i = 0;
2809    int diff = 0;
2810    while (1) {
2811        unsigned component = read_uint();
2812        if (diff == 0 && i < sizeof(version) / sizeof(version[0])) {
2813            if (diff == 0) {
2814                diff = (int)version[i++] - (int)component;
2815            }
2816        }
2817        if (ch != '.' || isBlank(nextch()) || isComm(ch) || isEol(ch))
2818            break;
2819    }
2820
2821    if (diff < 0) {
2822        // Requirement not satisfied
2823        size_t len = (size_t)(ftell(file.fh) - fp.offset);
2824        char *v = osmalloc(len + 1);
2825        set_pos(&fp);
2826        for (size_t j = 0; j < len; j++) {
2827            v[j] = ch;
[69c920d]2828            nextch();
[9af1e2c0]2829        }
2830        v[len] = '\0';
2831        /* TRANSLATORS: Feel free to translate as "or newer" instead of "or
2832         * greater" if that gives a more natural translation.  It's
2833         * technically not quite right when there are parallel active release
2834         * series (e.g. Survex 1.0.40 was released *after* 1.2.0), but this
2835         * seems unlikely to confuse users.  "Survex" is the name of the
2836         * software, so should not be translated.
2837         *
2838         * Here "survey" is a "cave map" rather than list of questions - it should be
2839         * translated to the terminology that cavers using the language would use.
2840         */
[7962c9d]2841        compile_diagnostic(DIAG_FATAL|DIAG_FROM(fp), /*Survex version %s or greater required to process this survey data.*/38, v);
[9af1e2c0]2842        // Does not return so no point freeing v here.
2843    }
[a420b49]2844}
2845
[b5a3219]2846/* allocate new meta_data if need be */
2847void
2848copy_on_write_meta(settings *s)
2849{
2850   if (!s->meta || s->meta->ref_count != 0) {
2851       meta_data * meta_new = osnew(meta_data);
2852       if (!s->meta) {
[1ee204e]2853           meta_new->days1 = meta_new->days2 = -1;
[b5a3219]2854       } else {
2855           *meta_new = *(s->meta);
2856       }
2857       meta_new->ref_count = 0;
2858       s->meta = meta_new;
2859   }
2860}
2861
[c7c0f93]2862static int
2863read_year(filepos *fp_date_ptr)
2864{
[f15c53d9]2865    int y = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, fp_date_ptr);
[c7c0f93]2866    if (y < 100) {
2867        /* Two digit year is 19xx. */
2868        y += 1900;
2869        filepos fp_save;
2870        get_pos(&fp_save);
2871        set_pos(fp_date_ptr);
2872        /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
2873        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Assuming 2 digit year is %d*/76, y);
2874        set_pos(&fp_save);
2875    } else if (y < 1900 || y > 2078) {
2876        set_pos(fp_date_ptr);
2877        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year (< 1900 or > 2078)*/58);
2878        longjmp(jbSkipLine, 1);
2879    }
2880    return y;
2881}
2882
[950a829]2883static void
2884cmd_date(void)
[421b7d2]2885{
[c7c0f93]2886    enum { DATE_SURVEYED = 1, DATE_EXPLORED = 2 };
2887    unsigned date_flags = 0;
2888    while (get_token(), !s_empty(&uctoken)) {
2889        unsigned new_flag = 0;
2890        if (S_EQ(&uctoken, "SURVEYED")) {
2891            new_flag = DATE_SURVEYED;
2892        } else if (S_EQ(&uctoken, "EXPLORED")) {
2893            new_flag = DATE_EXPLORED;
2894        } else {
2895            compile_diagnostic(DIAG_ERR|DIAG_TOKEN,
2896                               /*Expecting “%s” or “%s”*/103, "SURVEYED", "EXPLORED");
2897            continue;
2898        }
[e0c7cd1]2899
[c7c0f93]2900        if ((date_flags & new_flag)) {
2901            compile_diagnostic(DIAG_ERR|DIAG_TOKEN,
[0823cb3]2902                               /*Duplicate date type “%s”*/416, s_str(&token));
[c7c0f93]2903        }
2904        date_flags |= new_flag;
2905    }
[1ee204e]2906
[c7c0f93]2907    int date_sep = '-';
2908    if (date_flags == 0) {
2909        // `*date` without qualification sets the surveyed date.
2910        date_flags = DATE_SURVEYED;
2911        // Also allow '.' for compatibility.
2912        date_sep = 0;
[e0c7cd1]2913    }
2914
[c7c0f93]2915    filepos fp_date1, fp_date2, fp;
2916    get_pos(&fp_date1);
2917    int year = read_year(&fp_date1);
2918    int month = 0, day = 0, year2 = 0, month2 = 0, day2 = 0;
2919
[e0c7cd1]2920    if (ch == '-') {
[c7c0f93]2921        // Could be ISO-format date (e.g. 2024-10 or 2024-10-21 or 1912-11), or
2922        // a range of old-format dates (e.g. 1973-1975 or 1911-12.02.03) or an
2923        // ambiguous case like 1911-12 which we need to warn about in a `*date`
2924        // command without `surveyed` or `explored` qualifiers.
2925        nextch();
2926        get_pos(&fp_date2);
[f15c53d9]2927        int v = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date1);
[c7c0f93]2928        if (date_sep == '-') {
2929            // We're only accepting ISO dates.
2930        } else if (ch == '-') {
2931            // Two `-` so must be an ISO date, e.g. `2024-10-21`.
2932        } else if (v >= 1 && v <= 12) {
2933            // Valid month number so assume it's an ISO date.
2934            if (year < 1900 + v) {
2935                // Warn about ambiguous cases.
2936                compile_diagnostic(DIAG_WARN|DIAG_FROM(fp_date1),
2937                                   /*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,
2938                                   year, v, year, 1900 + v);
2939            }
2940        } else {
2941            date_sep = '.';
2942            year2 = v;
2943            if (year2 < 100) {
2944                /* Two digit year is 19xx. */
2945                year2 += 1900;
2946                /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
2947                compile_diagnostic(DIAG_WARN|DIAG_FROM(fp_date2),
2948                                   /*Assuming 2 digit year is %d*/76, year2);
2949            } else if (year2 < 1900 || year2 > 2078) {
2950                compile_diagnostic(DIAG_WARN|DIAG_FROM(fp_date2),
2951                                   /*Invalid year (< 1900 or > 2078)*/58);
2952                longjmp(jbSkipLine, 1);
2953            }
2954            goto process_dates;
2955        }
2956
2957        date_sep = '-';
2958        month = v;
2959        fp = fp_date2;
2960    } else if (ch == '.') {
2961        if (date_sep == '-') {
2962            char date_sep_string[2] = { date_sep, '\0' };
2963            compile_diagnostic(DIAG_ERR|DIAG_COL|DIAG_SKIP,
2964                               /*Expecting “%s”*/497, date_sep_string);
2965            return;
2966        }
2967        date_sep = ch;
[e0c7cd1]2968        nextch();
[c7c0f93]2969        get_pos(&fp);
[f15c53d9]2970        month = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date1);
[e0c7cd1]2971    } else {
[c7c0f93]2972        // Just a year - might be a ISO date range though.
2973        date_sep = '-';
2974        goto try_date2;
2975    }
2976
2977    if (month < 1 || month > 12) {
2978        set_pos(&fp);
2979        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
2980        longjmp(jbSkipLine, 1);
2981    }
2982
2983    if (ch == date_sep) {
2984        nextch();
2985        get_pos(&fp);
[f15c53d9]2986        day = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date1);
[c7c0f93]2987        if (day < 1 || day > last_day(year, month)) {
2988            set_pos(&fp);
2989            /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
2990            compile_diagnostic(DIAG_WARN|DIAG_UINT,
2991                               /*Invalid day of the month*/87);
2992            longjmp(jbSkipLine, 1);
[1ee204e]2993        }
2994    }
2995
[c7c0f93]2996try_date2:
[2d4ca34]2997    if (date_sep == '-') {
2998        skipblanks();
2999        if (!isdigit(ch)) goto process_dates;
3000    } else if (ch == '-') {
[c7c0f93]3001        nextch();
[2d4ca34]3002    } else {
3003        goto process_dates;
3004    }
3005    {
[c7c0f93]3006        get_pos(&fp_date2);
3007        year2 = read_year(&fp_date2);
3008        if (ch == date_sep) {
3009            nextch();
3010            get_pos(&fp);
[f15c53d9]3011            month2 = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198,
[c7c0f93]3012                                   &fp_date2);
3013            if (month2 < 1 || month2 > 12) {
3014                set_pos(&fp);
3015                compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
3016                longjmp(jbSkipLine, 1);
3017            }
3018
3019            if (ch == date_sep) {
3020                nextch();
3021                get_pos(&fp);
[f15c53d9]3022                day2 = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198,
[c7c0f93]3023                                     &fp_date2);
3024                if (day2 < 1 || day2 > last_day(year2, month2)) {
3025                    set_pos(&fp);
3026                    /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
3027                    compile_diagnostic(DIAG_WARN|DIAG_UINT,
3028                                       /*Invalid day of the month*/87);
3029                    longjmp(jbSkipLine, 1);
3030                }
3031            }
3032        }
3033    }
[1ee204e]3034
[50bfed5]3035process_dates:;
[c7c0f93]3036    bool date_range = (year2 != 0);
3037    if (!date_range) {
3038        year2 = year;
3039        month2 = month;
3040        day2 = day;
3041    }
3042
3043    int days1 = days_since_1900(year, month ? month : 1, day ? day : 1);
3044
3045    if (days1 > current_days_since_1900) {
3046        filepos fp_save;
3047        get_pos(&fp_save);
3048        set_pos(&fp_date1);
3049        compile_diagnostic(DIAG_WARN|DIAG_DATE, /*Date is in the future!*/80);
3050        set_pos(&fp_save);
3051    }
3052
3053    if (month2 == 0) {
3054        month2 = 12;
3055        day2 = 31;
3056    } else if (day2 == 0) {
3057        day2 = last_day(year2, month2);
3058    }
3059    int days2 = days_since_1900(year2, month2, day2);
3060    if (date_range && days2 > current_days_since_1900) {
3061        // If !date_range, either we already emitted this warning when
3062        // processing the start date, or the date is partial and for the
3063        // current year or current month, in which case it's not helpful
3064        // to warn that the end is on the future, but it makes sense to
3065        // process for the end date so the result doesn't change as time
3066        // passes.
3067        filepos fp_save;
3068        get_pos(&fp_save);
3069        set_pos(&fp_date2);
[1d74718]3070        compile_diagnostic(DIAG_WARN|DIAG_DATE, /*Date is in the future!*/80);
[c7c0f93]3071        set_pos(&fp_save);
[e0c7cd1]3072    }
[1ee204e]3073
3074    if (days2 < days1) {
[c7c0f93]3075        filepos fp_save;
3076        get_pos(&fp_save);
3077        set_pos(&fp_date1);
[d0be687d]3078        compile_diagnostic(DIAG_ERR|DIAG_WORD, /*End of date range is before the start*/81);
[c7c0f93]3079        set_pos(&fp_save);
3080        // Swap range ends to put us in a consistent state.
[95b0f1d]3081        int tmp = days1;
3082        days1 = days2;
3083        days2 = tmp;
[1ee204e]3084    }
[e0c7cd1]3085
[c7c0f93]3086    if ((date_flags & DATE_SURVEYED)) {
3087        if (!pcs->meta || pcs->meta->days1 != days1 || pcs->meta->days2 != days2) {
3088            copy_on_write_meta(pcs);
3089            pcs->meta->days1 = days1;
3090            pcs->meta->days2 = days2;
3091            /* Invalidate cached declination. */
3092            pcs->declination = HUGE_REAL;
3093        }
3094    }
3095
3096    if ((date_flags & DATE_EXPLORED)) {
3097        // FIXME: Need to revise 3d format to allow storing EXPLORED date too.
[95b0f1d]3098    }
[950a829]3099}
3100
[3aafcee]3101typedef void (*cmd_fn)(void);
3102
[82919e07]3103static const cmd_fn cmd_funcs[] = {
[dcbcae0]3104   cmd_alias,
[3aafcee]3105   cmd_begin,
3106   cmd_calibrate,
[abe7192]3107   cmd_cartesian,
[3aafcee]3108   cmd_case,
[f15c53d9]3109   cmd_copyright,
[abd0310]3110   cmd_cs,
[3aafcee]3111   cmd_data,
[950a829]3112   cmd_date,
[58c7b459]3113   cmd_declination,
[7f1ab95]3114#ifndef NO_DEPRECATED
[3aafcee]3115   cmd_default,
[7f1ab95]3116#endif
[3aafcee]3117   cmd_end,
3118   cmd_entrance,
3119   cmd_equate,
3120   cmd_export,
3121   cmd_fix,
3122   cmd_flags,
3123   cmd_include,
3124   cmd_infer,
3125   skipline, /*cmd_instrument,*/
[7f1ab95]3126#ifndef NO_DEPRECATED
[3aafcee]3127   cmd_prefix,
[7f1ab95]3128#endif
[e8452e3]3129   cmd_ref,
[3aafcee]3130   cmd_require,
3131   cmd_sd,
3132   cmd_set,
3133   solve_network,
[0f8216c]3134   cmd_team,
[3aafcee]3135   cmd_title,
3136   cmd_truncate,
3137   cmd_units
3138};
3139
[a420b49]3140extern void
3141handle_command(void)
3142{
[05b9de76]3143   filepos fp;
3144   get_pos(&fp);
[e1cbc0d]3145   get_token_legacy();
[05b9de76]3146   int cmdtok = match_tok(cmd_tab, TABSIZE(cmd_tab));
[3aafcee]3147   if (cmdtok < 0 || cmdtok >= (int)(sizeof(cmd_funcs) / sizeof(cmd_fn))) {
[05b9de76]3148      set_pos(&fp);
3149      get_token();
[caae6cd]3150      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown command “%s”*/12, s_str(&token));
[3aafcee]3151      return;
[932f7e9]3152   }
3153
[05b9de76]3154   do_legacy_token_warning();
3155
[932f7e9]3156   switch (cmdtok) {
[3aafcee]3157    case CMD_EXPORT:
3158      if (!f_export_ok)
[736f7df]3159         /* TRANSLATORS: The *EXPORT command is only valid just after *BEGIN
3160          * <SURVEY>, so this would generate this error:
3161          *
3162          * *begin fred
3163          * 1 2 1.23 045 -6
3164          * *export 2
3165          * *end fred */
[cab0f26]3166         compile_diagnostic(DIAG_ERR, /**EXPORT must immediately follow “*BEGIN <SURVEY>”*/57);
[3aafcee]3167      break;
[ce15637]3168    case CMD_ALIAS:
3169    case CMD_CALIBRATE:
[abe7192]3170    case CMD_CARTESIAN:
[ce15637]3171    case CMD_CASE:
[3aafcee]3172    case CMD_COPYRIGHT:
[ce15637]3173    case CMD_CS:
3174    case CMD_DATA:
[3aafcee]3175    case CMD_DATE:
[ce15637]3176    case CMD_DECLINATION:
3177    case CMD_DEFAULT:
3178    case CMD_FLAGS:
3179    case CMD_INFER:
[3aafcee]3180    case CMD_INSTRUMENT:
[ce15637]3181    case CMD_REF:
3182    case CMD_REQUIRE:
3183    case CMD_SD:
3184    case CMD_SET:
[3aafcee]3185    case CMD_TEAM:
3186    case CMD_TITLE:
[ce15637]3187    case CMD_TRUNCATE:
3188    case CMD_UNITS:
[4dcd3af]3189      /* These can occur between *begin and *export */
[421b7d2]3190      break;
[a420b49]3191    default:
[4dcd3af]3192      /* NB: additional handling for "*begin <survey>" in cmd_begin */
[63d4f07]3193      f_export_ok = false;
[3aafcee]3194      break;
[647407d]3195   }
[421b7d2]3196
[3aafcee]3197   cmd_funcs[cmdtok]();
[5f1e194]3198}
Note: See TracBrowser for help on using the repository browser.