source: git/src/commands.c @ e26c966

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

Implement parsing of *copyright

  • Property mode set to 100644
File size: 88.6 KB
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);
2350    }
2351    if (ch == '-') {
2352        nextch();
2353        get_pos(&fp);
2354        unsigned y2 = read_uint_raw(DIAG_WARN|DIAG_UINT, /*Invalid year*/534, NULL);
2355        if (y2 < 1000) {
2356            set_pos(&fp);
2357            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year*/534, y2);
2358        } else if (y2 < y1) {
2359            set_pos(&fp);
2360            compile_diagnostic(DIAG_WARN|DIAG_UINT, /*End of date range is before the start*/81);
2361        }
2362    }
2363
2364    string text = S_INIT;
2365    if (!read_string_warning(&text)) {
2366        skipline();
2367        return;
2368    }
2369    s_free(&text);
2370
2371    skipblanks();
2372    if (!isComm(ch) && !isEol(ch))
2373        compile_diagnostic(DIAG_WARN|DIAG_TAIL, /*End of line not blank*/15);
2374}
2375
[abd0310]2376typedef enum {
2377    CS_NONE = -1,
2378    CS_CUSTOM,
2379    CS_EPSG,
2380    CS_ESRI,
[b8fb5ac]2381    CS_EUR79Z30,
[abd0310]2382    CS_IJTSK,
[b8fb5ac]2383    CS_IJTSK03,
[abd0310]2384    CS_JTSK,
[b8fb5ac]2385    CS_JTSK03,
[abd0310]2386    CS_LAT,
2387    CS_LOCAL,
2388    CS_LONG,
2389    CS_OSGB,
2390    CS_S_MERC,
2391    CS_UTM
2392} cs_class;
2393
[82919e07]2394static const sztok cs_tab[] = {
[b8fb5ac]2395     {"CUSTOM",   CS_CUSTOM},
2396     {"EPSG",     CS_EPSG},     /* EPSG:<number> */
2397     {"ESRI",     CS_ESRI},     /* ESRI:<number> */
2398     {"EUR79Z30", CS_EUR79Z30},
2399     {"IJTSK",    CS_IJTSK},
2400     {"IJTSK03",  CS_IJTSK03},
2401     {"JTSK",     CS_JTSK},
2402     {"JTSK03",   CS_JTSK03},
2403     {"LAT",      CS_LAT},      /* LAT-LONG */
2404     {"LOCAL",    CS_LOCAL},
2405     {"LONG",     CS_LONG},     /* LONG-LAT */
2406     {"OSGB",     CS_OSGB},     /* OSGB:<H, N, O, S or T><A-Z except I> */
2407     {"S",        CS_S_MERC},   /* S-MERC */
2408     // UTM<zone><N or S or nothing> is handled separately to avoid needing 180
2409     // entries in this lookup table.
2410     {NULL,       CS_NONE}
[abd0310]2411};
2412
2413static void
2414cmd_cs(void)
2415{
[0532954]2416   char *proj_str = NULL;
[abd0310]2417   cs_class cs;
2418   int cs_sub = INT_MIN;
2419   filepos fp;
[63d4f07]2420   bool output = false;
[c092d72]2421   enum { YES, NO, MAYBE } ok_for_output = YES;
[63d4f07]2422   static bool had_cs = false;
[56db37f]2423
2424   if (!had_cs) {
[63d4f07]2425      had_cs = true;
[56db37f]2426      if (first_fix_name) {
[cab0f26]2427         compile_diagnostic_at(DIAG_ERR,
2428                               first_fix_filename, first_fix_line,
2429                               /*Station “%s” fixed before CS command first used*/442,
2430                               sprint_prefix(first_fix_name));
[56db37f]2431      }
2432   }
[abd0310]2433
[b8fb5ac]2434   skipblanks();
[abd0310]2435   get_pos(&fp);
[b8fb5ac]2436   get_token_no_blanks();
[0532954]2437   if (S_EQ(&uctoken, "OUT")) {
[63d4f07]2438      output = true;
[b8fb5ac]2439      skipblanks();
[9f55538]2440      get_pos(&fp);
[b8fb5ac]2441      get_token_no_blanks();
2442   }
2443
2444   if (s_len(&uctoken) > 3 &&
2445       memcmp(s_str(&uctoken), "UTM", 3) == 0 &&
2446       isdigit((unsigned char)s_str(&uctoken)[3])) {
2447       // The token starts "UTM" followed by a digit so handle that separately
2448       // to avoid needing 180 entries for UTM zones in the cs_tab lookup
2449       // table.
2450       cs = CS_UTM;
2451       // Reposition on the digit after "UTM".
2452       set_pos(&fp);
2453       nextch();
2454       nextch();
2455       nextch();
2456       unsigned n = read_uint();
2457       if (n >= 1 && n <= 60) {
2458           int uch = toupper(ch);
2459           cs_sub = (int)n;
2460           if (uch == 'S') {
[abd0310]2461               nextch();
[b8fb5ac]2462               cs_sub = -cs_sub;
2463           } else if (uch == 'N') {
2464               nextch();
2465           }
2466       }
2467   } else {
2468       cs = match_tok(cs_tab, TABSIZE(cs_tab));
2469       switch (cs) {
2470         case CS_NONE:
2471           break;
2472         case CS_CUSTOM:
2473           ok_for_output = MAYBE;
2474           get_pos(&fp);
2475           string str = S_INIT;
2476           read_string(&str);
2477           proj_str = s_steal(&str);
2478           cs_sub = 0;
2479           break;
2480         case CS_EPSG: case CS_ESRI:
2481           ok_for_output = MAYBE;
2482           if (ch == ':' && isdigit(nextch())) {
2483               unsigned n = read_uint();
2484               if (n < 1000000) {
2485                   cs_sub = (int)n;
[abd0310]2486               }
[b8fb5ac]2487           }
2488           break;
2489         case CS_EUR79Z30:
2490           cs_sub = 0;
2491           break;
2492         case CS_JTSK:
2493         case CS_JTSK03:
2494           ok_for_output = NO;
2495           cs_sub = 0;
2496           break;
2497         case CS_IJTSK:
2498         case CS_IJTSK03:
2499           cs_sub = 0;
2500           break;
2501         case CS_LAT: case CS_LONG:
2502           ok_for_output = NO;
2503           if (ch == '-') {
2504               nextch();
2505               get_token_no_blanks();
2506               cs_class cs2 = match_tok(cs_tab, TABSIZE(cs_tab));
2507               if ((cs ^ cs2) == (CS_LAT ^ CS_LONG)) {
2508                   cs_sub = 0;
[abd0310]2509               }
[b8fb5ac]2510           }
2511           break;
2512         case CS_LOCAL:
2513           cs_sub = 0;
2514           break;
2515         case CS_OSGB:
2516           if (ch == ':') {
2517               int uch1 = toupper(nextch());
2518               if (strchr("HNOST", uch1)) {
2519                   int uch2 = toupper(nextch());
2520                   if (uch2 >= 'A' && uch2 <= 'Z' && uch2 != 'I') {
2521                       int x, y;
2522                       nextch();
2523                       if (uch1 > 'I') --uch1;
2524                       uch1 -= 'A';
2525                       if (uch2 > 'I') --uch2;
2526                       uch2 -= 'A';
2527                       x = uch1 % 5;
2528                       y = uch1 / 5;
2529                       x = (x * 5) + uch2 % 5;
2530                       y = (y * 5) + uch2 / 5;
2531                       cs_sub = y * 25 + x;
2532                   }
2533               }
2534           }
2535           break;
2536         case CS_S_MERC:
2537           if (ch == '-') {
2538               nextch();
2539               get_token_no_blanks();
2540               if (S_EQ(&uctoken, "MERC")) {
2541                   cs_sub = 0;
2542               }
2543           }
2544           break;
2545         case CS_UTM:
2546           // Handled outside of this switch, but avoid compiler warning about
2547           // unhandled enumeration value.
2548           break;
2549       }
[abd0310]2550   }
2551   if (cs_sub == INT_MIN || isalnum(ch)) {
2552      set_pos(&fp);
[b8fb5ac]2553      compile_diagnostic(DIAG_ERR|DIAG_WORD, /*Unknown coordinate system*/434);
[9f55538]2554      skipline();
[c092d72]2555      return;
[abd0310]2556   }
2557   /* Actually handle the cs */
[c092d72]2558   switch (cs) {
[5d36f97]2559      case CS_NONE:
2560         break;
2561      case CS_CUSTOM:
2562         /* proj_str already set */
2563         break;
[ddd24f28]2564      case CS_EPSG:
2565         proj_str = osmalloc(32);
[657fcee]2566         snprintf(proj_str, 32, "EPSG:%d", cs_sub);
[ddd24f28]2567         break;
2568      case CS_ESRI:
2569         proj_str = osmalloc(32);
[657fcee]2570         snprintf(proj_str, 32, "ESRI:%d", cs_sub);
[ddd24f28]2571         break;
[b8fb5ac]2572      case CS_EUR79Z30:
[ddd24f28]2573         proj_str = osstrdup("+proj=utm +zone=30 +ellps=intl +towgs84=-86,-98,-119,0,0,0,0 +no_defs");
2574         break;
[5598e2c]2575      case CS_IJTSK:
[b8fb5ac]2576         proj_str = osstrdup("+proj=krovak +ellps=bessel +towgs84=570.8285,85.6769,462.842,4.9984,1.5867,5.2611,3.5623 +no_defs");
2577         break;
2578      case CS_IJTSK03:
2579         proj_str = osstrdup("+proj=krovak +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +no_defs");
[5598e2c]2580         break;
[10af28e]2581      case CS_JTSK:
[b8fb5ac]2582         proj_str = osstrdup("+proj=krovak +czech +ellps=bessel +towgs84=570.8285,85.6769,462.842,4.9984,1.5867,5.2611,3.5623 +no_defs");
2583         break;
2584      case CS_JTSK03:
2585         proj_str = osstrdup("+proj=krovak +czech +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +no_defs");
[10af28e]2586         break;
[16734b2]2587      case CS_LAT:
2588         /* FIXME: Requires PROJ >= 4.8.0 for +axis, and the SDs will be
[10af28e]2589          * misapplied, so we may want to swap ourselves.  Also, while
2590          * therion supports lat-long, I'm not totally convinced that it is
2591          * sensible to do so - people often say "lat-long", but probably
2592          * don't think that that's actually "Northing, Easting".  This
2593          * seems like it'll result in people accidentally getting X and Y
2594          * swapped in their fixed points...
2595          */
[5d36f97]2596#if 0
[cb0a137]2597         proj_str = osstrdup("+proj=longlat +ellps=WGS84 +datum=WGS84 +axis=neu +no_defs");
[5d36f97]2598#endif
[16734b2]2599         break;
[10af28e]2600      case CS_LOCAL:
2601         /* FIXME: Is it useful to be able to explicitly specify this? */
2602         break;
[16734b2]2603      case CS_LONG:
[b39e24a]2604         proj_str = osstrdup("EPSG:4326");
[16734b2]2605         break;
[a4cd4eea]2606      case CS_OSGB: {
2607         int x = 14 - (cs_sub % 25);
2608         int y = (cs_sub / 25) - 20;
2609         proj_str = osmalloc(160);
[657fcee]2610         snprintf(proj_str, 160,
2611                  "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 "
2612                  "+x_0=%d +y_0=%d +ellps=airy +datum=OSGB36 +units=m +no_defs",
2613                  x * 100000, y * 100000);
[a4cd4eea]2614         break;
2615      }
[2076d59]2616      case CS_S_MERC:
[b39e24a]2617         proj_str = osstrdup("EPSG:3857");
[2076d59]2618         break;
[c092d72]2619      case CS_UTM:
[b39e24a]2620         proj_str = osmalloc(32);
[c092d72]2621         if (cs_sub > 0) {
[657fcee]2622            snprintf(proj_str, 32, "EPSG:%d", 32600 + cs_sub);
[c092d72]2623         } else {
[657fcee]2624            snprintf(proj_str, 32, "EPSG:%d", 32700 - cs_sub);
[c092d72]2625         }
2626         break;
2627   }
2628
[10af28e]2629   if (!proj_str) {
2630      /* printf("CS %d:%d\n", (int)cs, cs_sub); */
2631      set_pos(&fp);
[88ba3b1]2632      compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Unknown coordinate system*/434);
[9f55538]2633      skipline();
[10af28e]2634      return;
2635   }
2636
[c092d72]2637   if (output) {
2638      if (ok_for_output == NO) {
2639         set_pos(&fp);
[88ba3b1]2640         compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Coordinate system unsuitable for output*/435);
[9f55538]2641         skipline();
[c092d72]2642         return;
2643      }
2644
[da9163b]2645      if (proj_str_out && strcmp(proj_str, proj_str_out) == 0) {
2646          /* Same as the output cs that's already set, so nothing to do. */
[ae917b96]2647          free(proj_str);
[da9163b]2648          return;
2649      }
2650
2651      if (ok_for_output == MAYBE) {
2652          /* We only actually create the transformation from input to output when
2653           * we need it, but for a custom proj string or EPSG/ESRI code we need
2654           * to check that the specified coordinate system is valid and also if
2655           * it's suitable for output so we need to test creating it here.
2656           */
[df1d579]2657          proj_errno_reset(NULL);
[da9163b]2658          PJ* pj = proj_create(PJ_DEFAULT_CTX, proj_str);
2659          if (!pj) {
2660              set_pos(&fp);
[88ba3b1]2661              compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Invalid coordinate system: %s*/443,
[d9d8f21]2662                                 proj_context_errno_string(PJ_DEFAULT_CTX,
2663                                                           proj_context_errno(PJ_DEFAULT_CTX)));
[da9163b]2664              skipline();
[ae917b96]2665              free(proj_str);
[da9163b]2666              return;
2667          }
2668          int type = proj_get_type(pj);
2669          if (type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
2670              type == PJ_TYPE_GEOGRAPHIC_3D_CRS) {
2671              set_pos(&fp);
[88ba3b1]2672              compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Coordinate system unsuitable for output*/435);
[da9163b]2673              skipline();
[ae917b96]2674              free(proj_str);
[da9163b]2675              return;
2676          }
2677      }
2678
2679      if (proj_str_out) {
2680          /* If the output cs is already set, subsequent attempts to set it
2681           * are silently ignored (so you can combine two datasets and set
2682           * the output cs to use before you include either).
2683           */
[ae917b96]2684          free(proj_str);
[da9163b]2685      } else {
2686          proj_str_out = proj_str;
[c092d72]2687      }
2688   } else {
2689      if (proj_str_out && strcmp(proj_str, proj_str_out) == 0) {
[da9163b]2690         /* Same as the current output projection, so valid for input. */
2691      } else if (pcs->proj_str && strcmp(proj_str, pcs->proj_str) == 0) {
2692         /* Same as the current input projection, so nothing to do! */
2693         return;
2694      } else if (ok_for_output == MAYBE) {
2695         /* (ok_for_output == MAYBE) also happens to indicate whether we need
2696          * to check that the coordinate system is valid for input.
2697          */
[df1d579]2698         proj_errno_reset(NULL);
[b39e24a]2699         PJ* pj = proj_create(PJ_DEFAULT_CTX, proj_str);
[c092d72]2700         if (!pj) {
2701            set_pos(&fp);
[88ba3b1]2702            compile_diagnostic(DIAG_ERR|DIAG_STRING, /*Invalid coordinate system: %s*/443,
[d9d8f21]2703                               proj_context_errno_string(PJ_DEFAULT_CTX,
2704                                                         proj_context_errno(PJ_DEFAULT_CTX)));
[9f55538]2705            skipline();
[c092d72]2706            return;
2707         }
[b39e24a]2708         proj_destroy(pj);
[c092d72]2709      }
2710
[da9163b]2711      /* Free current input proj_str if not used by parent. */
[c092d72]2712      settings * p = pcs;
[da9163b]2713      if (!p->next || p->proj_str != p->next->proj_str)
[ae917b96]2714         free(p->proj_str);
[b39e24a]2715      p->proj_str = proj_str;
[abe7192]2716      p->input_convergence = HUGE_REAL;
[da9163b]2717      invalidate_pj_cached();
[c092d72]2718   }
[abd0310]2719}
2720
[82919e07]2721static const sztok infer_tab[] = {
[27b8b59]2722     { "EQUATES",       INFER_EQUATES },
2723     { "EXPORTS",       INFER_EXPORTS },
2724     { "PLUMBS",        INFER_PLUMBS },
2725#if 0 /* FIXME */
2726     { "SUBSURVEYS",    INFER_SUBSURVEYS },
2727#endif
2728     { NULL,            INFER_NULL }
[a420b49]2729};
[cb3d1e2]2730
[82919e07]2731static const sztok onoff_tab[] = {
[27b8b59]2732     { "OFF", 0 },
2733     { "ON",  1 },
2734     { NULL, -1 }
[a420b49]2735};
[cb3d1e2]2736
[a420b49]2737static void
[eb18f4d]2738cmd_infer(void)
[a420b49]2739{
[27b8b59]2740   infer_what setting;
[a420b49]2741   int on;
2742   get_token();
2743   setting = match_tok(infer_tab, TABSIZE(infer_tab));
[27b8b59]2744   if (setting == INFER_NULL) {
[caae6cd]2745      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found “%s”, expecting “EQUATES”, “EXPORTS”, or “PLUMBS”*/31, s_str(&token));
[647407d]2746      return;
[a420b49]2747   }
2748   get_token();
2749   on = match_tok(onoff_tab, TABSIZE(onoff_tab));
2750   if (on == -1) {
[caae6cd]2751      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Found “%s”, expecting “ON” or “OFF”*/32, s_str(&token));
[647407d]2752      return;
[a420b49]2753   }
[cb3d1e2]2754
[27b8b59]2755   if (on) {
2756      pcs->infer |= BIT(setting);
[63d4f07]2757      if (setting == INFER_EXPORTS) fExportUsed = true;
[27b8b59]2758   } else {
2759      pcs->infer &= ~BIT(setting);
[a420b49]2760   }
2761}
2762
2763static void
[eb18f4d]2764cmd_truncate(void)
[a420b49]2765{
[647407d]2766   unsigned int truncate_at = 0; /* default is no truncation */
[c80bd34]2767   filepos fp;
2768
2769   get_pos(&fp);
2770
[647407d]2771   get_token();
[0532954]2772   if (!S_EQ(&uctoken, "OFF")) {
2773      if (!s_empty(&uctoken)) set_pos(&fp);
[647407d]2774      truncate_at = read_uint();
2775   }
2776   /* for backward compatibility, "*truncate 0" means "*truncate off" */
2777   pcs->Truncate = (truncate_at == 0) ? INT_MAX : truncate_at;
2778}
2779
[e8452e3]2780static void
2781cmd_ref(void)
2782{
2783   /* Just syntax check for now. */
[0532954]2784   string ref = S_INIT;
2785   read_string(&ref);
[e8452e3]2786   s_free(&ref);
2787}
2788
[647407d]2789static void
[eb18f4d]2790cmd_require(void)
[647407d]2791{
[9af1e2c0]2792    // Add extra 0 so `*require 1.4.10.1` fails with cavern version 1.4.10.
2793    const unsigned version[] = {COMMAVERSION, 0};
[c80bd34]2794
[9af1e2c0]2795    skipblanks();
2796    filepos fp;
2797    get_pos(&fp);
2798
2799    // Parse the required version number, storing its components in
2800    // required_version.  We only store at most one more component than
2801    // COMMAVERSION has since more than that can't affect the comparison.
2802    size_t i = 0;
2803    int diff = 0;
2804    while (1) {
2805        unsigned component = read_uint();
2806        if (diff == 0 && i < sizeof(version) / sizeof(version[0])) {
2807            if (diff == 0) {
2808                diff = (int)version[i++] - (int)component;
2809            }
2810        }
2811        if (ch != '.' || isBlank(nextch()) || isComm(ch) || isEol(ch))
2812            break;
2813    }
2814
2815    if (diff < 0) {
2816        // Requirement not satisfied
2817        size_t len = (size_t)(ftell(file.fh) - fp.offset);
2818        char *v = osmalloc(len + 1);
2819        set_pos(&fp);
2820        for (size_t j = 0; j < len; j++) {
2821            v[j] = ch;
[69c920d]2822            nextch();
[9af1e2c0]2823        }
2824        v[len] = '\0';
2825        /* TRANSLATORS: Feel free to translate as "or newer" instead of "or
2826         * greater" if that gives a more natural translation.  It's
2827         * technically not quite right when there are parallel active release
2828         * series (e.g. Survex 1.0.40 was released *after* 1.2.0), but this
2829         * seems unlikely to confuse users.  "Survex" is the name of the
2830         * software, so should not be translated.
2831         *
2832         * Here "survey" is a "cave map" rather than list of questions - it should be
2833         * translated to the terminology that cavers using the language would use.
2834         */
[7962c9d]2835        compile_diagnostic(DIAG_FATAL|DIAG_FROM(fp), /*Survex version %s or greater required to process this survey data.*/38, v);
[9af1e2c0]2836        // Does not return so no point freeing v here.
2837    }
[a420b49]2838}
2839
[b5a3219]2840/* allocate new meta_data if need be */
2841void
2842copy_on_write_meta(settings *s)
2843{
2844   if (!s->meta || s->meta->ref_count != 0) {
2845       meta_data * meta_new = osnew(meta_data);
2846       if (!s->meta) {
[1ee204e]2847           meta_new->days1 = meta_new->days2 = -1;
[b5a3219]2848       } else {
2849           *meta_new = *(s->meta);
2850       }
2851       meta_new->ref_count = 0;
2852       s->meta = meta_new;
2853   }
2854}
2855
[c7c0f93]2856static int
2857read_year(filepos *fp_date_ptr)
2858{
[f15c53d9]2859    int y = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, fp_date_ptr);
[c7c0f93]2860    if (y < 100) {
2861        /* Two digit year is 19xx. */
2862        y += 1900;
2863        filepos fp_save;
2864        get_pos(&fp_save);
2865        set_pos(fp_date_ptr);
2866        /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
2867        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Assuming 2 digit year is %d*/76, y);
2868        set_pos(&fp_save);
2869    } else if (y < 1900 || y > 2078) {
2870        set_pos(fp_date_ptr);
2871        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid year (< 1900 or > 2078)*/58);
2872        longjmp(jbSkipLine, 1);
2873    }
2874    return y;
2875}
2876
[950a829]2877static void
2878cmd_date(void)
[421b7d2]2879{
[c7c0f93]2880    enum { DATE_SURVEYED = 1, DATE_EXPLORED = 2 };
2881    unsigned date_flags = 0;
2882    while (get_token(), !s_empty(&uctoken)) {
2883        unsigned new_flag = 0;
2884        if (S_EQ(&uctoken, "SURVEYED")) {
2885            new_flag = DATE_SURVEYED;
2886        } else if (S_EQ(&uctoken, "EXPLORED")) {
2887            new_flag = DATE_EXPLORED;
2888        } else {
2889            compile_diagnostic(DIAG_ERR|DIAG_TOKEN,
2890                               /*Expecting “%s” or “%s”*/103, "SURVEYED", "EXPLORED");
2891            continue;
2892        }
[e0c7cd1]2893
[c7c0f93]2894        if ((date_flags & new_flag)) {
2895            compile_diagnostic(DIAG_ERR|DIAG_TOKEN,
[0823cb3]2896                               /*Duplicate date type “%s”*/416, s_str(&token));
[c7c0f93]2897        }
2898        date_flags |= new_flag;
2899    }
[1ee204e]2900
[c7c0f93]2901    int date_sep = '-';
2902    if (date_flags == 0) {
2903        // `*date` without qualification sets the surveyed date.
2904        date_flags = DATE_SURVEYED;
2905        // Also allow '.' for compatibility.
2906        date_sep = 0;
[e0c7cd1]2907    }
2908
[c7c0f93]2909    filepos fp_date1, fp_date2, fp;
2910    get_pos(&fp_date1);
2911    int year = read_year(&fp_date1);
2912    int month = 0, day = 0, year2 = 0, month2 = 0, day2 = 0;
2913
[e0c7cd1]2914    if (ch == '-') {
[c7c0f93]2915        // Could be ISO-format date (e.g. 2024-10 or 2024-10-21 or 1912-11), or
2916        // a range of old-format dates (e.g. 1973-1975 or 1911-12.02.03) or an
2917        // ambiguous case like 1911-12 which we need to warn about in a `*date`
2918        // command without `surveyed` or `explored` qualifiers.
2919        nextch();
2920        get_pos(&fp_date2);
[f15c53d9]2921        int v = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date1);
[c7c0f93]2922        if (date_sep == '-') {
2923            // We're only accepting ISO dates.
2924        } else if (ch == '-') {
2925            // Two `-` so must be an ISO date, e.g. `2024-10-21`.
2926        } else if (v >= 1 && v <= 12) {
2927            // Valid month number so assume it's an ISO date.
2928            if (year < 1900 + v) {
2929                // Warn about ambiguous cases.
2930                compile_diagnostic(DIAG_WARN|DIAG_FROM(fp_date1),
2931                                   /*Interpreting as an ISO-format date - use “*date surveyed %d-%02d” to suppress this warning, or “*date %d %d” if you wanted a date range*/158,
2932                                   year, v, year, 1900 + v);
2933            }
2934        } else {
2935            date_sep = '.';
2936            year2 = v;
2937            if (year2 < 100) {
2938                /* Two digit year is 19xx. */
2939                year2 += 1900;
2940                /* TRANSLATORS: %d will be replaced by the assumed year, e.g. 1918 */
2941                compile_diagnostic(DIAG_WARN|DIAG_FROM(fp_date2),
2942                                   /*Assuming 2 digit year is %d*/76, year2);
2943            } else if (year2 < 1900 || year2 > 2078) {
2944                compile_diagnostic(DIAG_WARN|DIAG_FROM(fp_date2),
2945                                   /*Invalid year (< 1900 or > 2078)*/58);
2946                longjmp(jbSkipLine, 1);
2947            }
2948            goto process_dates;
2949        }
2950
2951        date_sep = '-';
2952        month = v;
2953        fp = fp_date2;
2954    } else if (ch == '.') {
2955        if (date_sep == '-') {
2956            char date_sep_string[2] = { date_sep, '\0' };
2957            compile_diagnostic(DIAG_ERR|DIAG_COL|DIAG_SKIP,
2958                               /*Expecting “%s”*/497, date_sep_string);
2959            return;
2960        }
2961        date_sep = ch;
[e0c7cd1]2962        nextch();
[c7c0f93]2963        get_pos(&fp);
[f15c53d9]2964        month = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date1);
[e0c7cd1]2965    } else {
[c7c0f93]2966        // Just a year - might be a ISO date range though.
2967        date_sep = '-';
2968        goto try_date2;
2969    }
2970
2971    if (month < 1 || month > 12) {
2972        set_pos(&fp);
2973        compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
2974        longjmp(jbSkipLine, 1);
2975    }
2976
2977    if (ch == date_sep) {
2978        nextch();
2979        get_pos(&fp);
[f15c53d9]2980        day = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198, &fp_date1);
[c7c0f93]2981        if (day < 1 || day > last_day(year, month)) {
2982            set_pos(&fp);
2983            /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
2984            compile_diagnostic(DIAG_WARN|DIAG_UINT,
2985                               /*Invalid day of the month*/87);
2986            longjmp(jbSkipLine, 1);
[1ee204e]2987        }
2988    }
2989
[c7c0f93]2990try_date2:
[2d4ca34]2991    if (date_sep == '-') {
2992        skipblanks();
2993        if (!isdigit(ch)) goto process_dates;
2994    } else if (ch == '-') {
[c7c0f93]2995        nextch();
[2d4ca34]2996    } else {
2997        goto process_dates;
2998    }
2999    {
[c7c0f93]3000        get_pos(&fp_date2);
3001        year2 = read_year(&fp_date2);
3002        if (ch == date_sep) {
3003            nextch();
3004            get_pos(&fp);
[f15c53d9]3005            month2 = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198,
[c7c0f93]3006                                   &fp_date2);
3007            if (month2 < 1 || month2 > 12) {
3008                set_pos(&fp);
3009                compile_diagnostic(DIAG_WARN|DIAG_UINT, /*Invalid month*/86);
3010                longjmp(jbSkipLine, 1);
3011            }
3012
3013            if (ch == date_sep) {
3014                nextch();
3015                get_pos(&fp);
[f15c53d9]3016                day2 = read_uint_raw(DIAG_ERR, /*Expecting date, found “%s”*/198,
[c7c0f93]3017                                     &fp_date2);
3018                if (day2 < 1 || day2 > last_day(year2, month2)) {
3019                    set_pos(&fp);
3020                    /* TRANSLATORS: e.g. 31st of April, or 32nd of any month */
3021                    compile_diagnostic(DIAG_WARN|DIAG_UINT,
3022                                       /*Invalid day of the month*/87);
3023                    longjmp(jbSkipLine, 1);
3024                }
3025            }
3026        }
3027    }
[1ee204e]3028
[50bfed5]3029process_dates:;
[c7c0f93]3030    bool date_range = (year2 != 0);
3031    if (!date_range) {
3032        year2 = year;
3033        month2 = month;
3034        day2 = day;
3035    }
3036
3037    int days1 = days_since_1900(year, month ? month : 1, day ? day : 1);
3038
3039    if (days1 > current_days_since_1900) {
3040        filepos fp_save;
3041        get_pos(&fp_save);
3042        set_pos(&fp_date1);
3043        compile_diagnostic(DIAG_WARN|DIAG_DATE, /*Date is in the future!*/80);
3044        set_pos(&fp_save);
3045    }
3046
3047    if (month2 == 0) {
3048        month2 = 12;
3049        day2 = 31;
3050    } else if (day2 == 0) {
3051        day2 = last_day(year2, month2);
3052    }
3053    int days2 = days_since_1900(year2, month2, day2);
3054    if (date_range && days2 > current_days_since_1900) {
3055        // If !date_range, either we already emitted this warning when
3056        // processing the start date, or the date is partial and for the
3057        // current year or current month, in which case it's not helpful
3058        // to warn that the end is on the future, but it makes sense to
3059        // process for the end date so the result doesn't change as time
3060        // passes.
3061        filepos fp_save;
3062        get_pos(&fp_save);
3063        set_pos(&fp_date2);
[1d74718]3064        compile_diagnostic(DIAG_WARN|DIAG_DATE, /*Date is in the future!*/80);
[c7c0f93]3065        set_pos(&fp_save);
[e0c7cd1]3066    }
[1ee204e]3067
3068    if (days2 < days1) {
[c7c0f93]3069        filepos fp_save;
3070        get_pos(&fp_save);
3071        set_pos(&fp_date1);
[d0be687d]3072        compile_diagnostic(DIAG_ERR|DIAG_WORD, /*End of date range is before the start*/81);
[c7c0f93]3073        set_pos(&fp_save);
3074        // Swap range ends to put us in a consistent state.
[95b0f1d]3075        int tmp = days1;
3076        days1 = days2;
3077        days2 = tmp;
[1ee204e]3078    }
[e0c7cd1]3079
[c7c0f93]3080    if ((date_flags & DATE_SURVEYED)) {
3081        if (!pcs->meta || pcs->meta->days1 != days1 || pcs->meta->days2 != days2) {
3082            copy_on_write_meta(pcs);
3083            pcs->meta->days1 = days1;
3084            pcs->meta->days2 = days2;
3085            /* Invalidate cached declination. */
3086            pcs->declination = HUGE_REAL;
3087        }
3088    }
3089
3090    if ((date_flags & DATE_EXPLORED)) {
3091        // FIXME: Need to revise 3d format to allow storing EXPLORED date too.
[95b0f1d]3092    }
[950a829]3093}
3094
[3aafcee]3095typedef void (*cmd_fn)(void);
3096
[82919e07]3097static const cmd_fn cmd_funcs[] = {
[dcbcae0]3098   cmd_alias,
[3aafcee]3099   cmd_begin,
3100   cmd_calibrate,
[abe7192]3101   cmd_cartesian,
[3aafcee]3102   cmd_case,
[f15c53d9]3103   cmd_copyright,
[abd0310]3104   cmd_cs,
[3aafcee]3105   cmd_data,
[950a829]3106   cmd_date,
[58c7b459]3107   cmd_declination,
[7f1ab95]3108#ifndef NO_DEPRECATED
[3aafcee]3109   cmd_default,
[7f1ab95]3110#endif
[3aafcee]3111   cmd_end,
3112   cmd_entrance,
3113   cmd_equate,
3114   cmd_export,
3115   cmd_fix,
3116   cmd_flags,
3117   cmd_include,
3118   cmd_infer,
3119   skipline, /*cmd_instrument,*/
[7f1ab95]3120#ifndef NO_DEPRECATED
[3aafcee]3121   cmd_prefix,
[7f1ab95]3122#endif
[e8452e3]3123   cmd_ref,
[3aafcee]3124   cmd_require,
3125   cmd_sd,
3126   cmd_set,
3127   solve_network,
[0f8216c]3128   cmd_team,
[3aafcee]3129   cmd_title,
3130   cmd_truncate,
3131   cmd_units
3132};
3133
[a420b49]3134extern void
3135handle_command(void)
3136{
[05b9de76]3137   filepos fp;
3138   get_pos(&fp);
[e1cbc0d]3139   get_token_legacy();
[05b9de76]3140   int cmdtok = match_tok(cmd_tab, TABSIZE(cmd_tab));
[3aafcee]3141   if (cmdtok < 0 || cmdtok >= (int)(sizeof(cmd_funcs) / sizeof(cmd_fn))) {
[05b9de76]3142      set_pos(&fp);
3143      get_token();
[caae6cd]3144      compile_diagnostic(DIAG_ERR|DIAG_TOKEN|DIAG_SKIP, /*Unknown command “%s”*/12, s_str(&token));
[3aafcee]3145      return;
[932f7e9]3146   }
3147
[05b9de76]3148   do_legacy_token_warning();
3149
[932f7e9]3150   switch (cmdtok) {
[3aafcee]3151    case CMD_EXPORT:
3152      if (!f_export_ok)
[736f7df]3153         /* TRANSLATORS: The *EXPORT command is only valid just after *BEGIN
3154          * <SURVEY>, so this would generate this error:
3155          *
3156          * *begin fred
3157          * 1 2 1.23 045 -6
3158          * *export 2
3159          * *end fred */
[cab0f26]3160         compile_diagnostic(DIAG_ERR, /**EXPORT must immediately follow “*BEGIN <SURVEY>”*/57);
[3aafcee]3161      break;
[ce15637]3162    case CMD_ALIAS:
3163    case CMD_CALIBRATE:
[abe7192]3164    case CMD_CARTESIAN:
[ce15637]3165    case CMD_CASE:
[3aafcee]3166    case CMD_COPYRIGHT:
[ce15637]3167    case CMD_CS:
3168    case CMD_DATA:
[3aafcee]3169    case CMD_DATE:
[ce15637]3170    case CMD_DECLINATION:
3171    case CMD_DEFAULT:
3172    case CMD_FLAGS:
3173    case CMD_INFER:
[3aafcee]3174    case CMD_INSTRUMENT:
[ce15637]3175    case CMD_REF:
3176    case CMD_REQUIRE:
3177    case CMD_SD:
3178    case CMD_SET:
[3aafcee]3179    case CMD_TEAM:
3180    case CMD_TITLE:
[ce15637]3181    case CMD_TRUNCATE:
3182    case CMD_UNITS:
[4dcd3af]3183      /* These can occur between *begin and *export */
[421b7d2]3184      break;
[a420b49]3185    default:
[4dcd3af]3186      /* NB: additional handling for "*begin <survey>" in cmd_begin */
[63d4f07]3187      f_export_ok = false;
[3aafcee]3188      break;
[647407d]3189   }
[421b7d2]3190
[3aafcee]3191   cmd_funcs[cmdtok]();
[5f1e194]3192}
Note: See TracBrowser for help on using the repository browser.