source: git/src/commands.c @ 0b3f35e

Last change on this file since 0b3f35e was 98b705d, checked in by Olly Betts <olly@…>, 2 months ago

cavern: Track column as well as file:line for pfx

This allows us to report columns diagnostics which get the position
from a station or survey name.

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