source: git/src/commands.c @ 8719711

RELEASE/1.2debug-cidebug-ci-sanitisersfaster-cavernlogwalls-datawalls-data-hanging-as-warning
Last change on this file since 8719711 was 8719711, checked in by Olly Betts <olly@…>, 5 years ago

Support PROJ 5.x

PROJ 6.x won't work yet. See https://trac.survex.com/ticket/102 for
details.

  • Property mode set to 100644
File size: 67.9 KB
Line 
1/* commands.c
2 * Code for directives
3 * Copyright (C) 1991-2003,2004,2005,2006,2010,2011,2012,2013,2014,2015,2016,2019 Olly Betts
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
24#include <assert.h>
25#include <limits.h>
26#include <stddef.h> /* for offsetof */
27#include <string.h>
28
29#ifdef HAVE_PROJ_H
30/* Work around broken check in proj.h:
31 * https://github.com/OSGeo/PROJ/issues/1523
32 */
33# ifndef PROJ_H
34#  include <proj.h>
35# endif
36#endif
37#define ACCEPT_USE_OF_DEPRECATED_PROJ_API_H 1
38#include <proj_api.h>
39
40#include "cavern.h"
41#include "commands.h"
42#include "datain.h"
43#include "date.h"
44#include "debug.h"
45#include "filename.h"
46#include "message.h"
47#include "netbits.h"
48#include "netskel.h"
49#include "out.h"
50#include "readval.h"
51#include "str.h"
52
53/*** Extracted from proj.4 projects (yuck, but grass also does this): */
54struct DERIVS {
55    double x_l, x_p; /* derivatives of x for lambda-phi */
56    double y_l, y_p; /* derivatives of y for lambda-phi */
57};
58
59struct FACTORS {
60    struct DERIVS der;
61    double h, k;        /* meridinal, parallel scales */
62    double omega, thetap;       /* angular distortion, theta prime */
63    double conv;        /* convergence */
64    double s;           /* areal scale factor */
65    double a, b;        /* max-min scale error */
66    int code;           /* info as to analytics, see following */
67};
68
69int pj_factors(projLP, projPJ *, double, struct FACTORS *);
70/***/
71
72static projPJ proj_wgs84;
73
74static void
75default_grade(settings *s)
76{
77   /* Values correspond to those in bcra5.svx */
78   s->Var[Q_POS] = (real)sqrd(0.05);
79   s->Var[Q_LENGTH] = (real)sqrd(0.05);
80   s->Var[Q_BACKLENGTH] = (real)sqrd(0.05);
81   s->Var[Q_COUNT] = (real)sqrd(0.05);
82   s->Var[Q_DX] = s->Var[Q_DY] = s->Var[Q_DZ] = (real)sqrd(0.05);
83   s->Var[Q_BEARING] = (real)sqrd(rad(0.5));
84   s->Var[Q_GRADIENT] = (real)sqrd(rad(0.5));
85   s->Var[Q_BACKBEARING] = (real)sqrd(rad(0.5));
86   s->Var[Q_BACKGRADIENT] = (real)sqrd(rad(0.5));
87   /* SD of plumbed legs (0.25 degrees?) */
88   s->Var[Q_PLUMB] = (real)sqrd(rad(0.25));
89   /* SD of level legs (0.25 degrees?) */
90   s->Var[Q_LEVEL] = (real)sqrd(rad(0.25));
91   s->Var[Q_DEPTH] = (real)sqrd(0.05);
92}
93
94static void
95default_truncate(settings *s)
96{
97   s->Truncate = INT_MAX;
98}
99
100static void
101default_case(settings *s)
102{
103   s->Case = LOWER;
104}
105
106static reading default_order[] = { Fr, To, Tape, Comp, Clino, End };
107
108static void
109default_style(settings *s)
110{
111   s->style = STYLE_NORMAL;
112   s->ordering = default_order;
113   s->dash_for_anon_wall_station = fFalse;
114}
115
116static void
117default_prefix(settings *s)
118{
119   s->Prefix = root;
120}
121
122static void
123default_translate(settings *s)
124{
125   int i;
126   short *t;
127   if (s->next && s->next->Translate == s->Translate) {
128      t = ((short*)osmalloc(ossizeof(short) * 257)) + 1;
129      memcpy(t - 1, s->Translate - 1, sizeof(short) * 257);
130      s->Translate = t;
131   }
132/*  SVX_ASSERT(EOF==-1);*/ /* important, since we rely on this */
133   t = s->Translate;
134   memset(t - 1, 0, sizeof(short) * 257);
135   for (i = '0'; i <= '9'; i++) t[i] = SPECIAL_NAMES;
136   for (i = 'A'; i <= 'Z'; i++) t[i] = SPECIAL_NAMES;
137   for (i = 'a'; i <= 'z'; i++) t[i] = SPECIAL_NAMES;
138
139   t['\t'] |= SPECIAL_BLANK;
140   t[' '] |= SPECIAL_BLANK;
141   t[','] |= SPECIAL_BLANK;
142   t[';'] |= SPECIAL_COMMENT;
143   t['\032'] |= SPECIAL_EOL; /* Ctrl-Z, so olde DOS text files are handled ok */
144   t[EOF] |= SPECIAL_EOL;
145   t['\n'] |= SPECIAL_EOL;
146   t['\r'] |= SPECIAL_EOL;
147   t['*'] |= SPECIAL_KEYWORD;
148   t['-'] |= SPECIAL_OMIT;
149   t['\\'] |= SPECIAL_ROOT;
150   t['.'] |= SPECIAL_SEPARATOR;
151   t['_'] |= SPECIAL_NAMES;
152   t['-'] |= SPECIAL_NAMES; /* Added in 0.97 prerelease 4 */
153   t['.'] |= SPECIAL_DECIMAL;
154   t['-'] |= SPECIAL_MINUS;
155   t['+'] |= SPECIAL_PLUS;
156#if 0 /* FIXME */
157   t['{'] |= SPECIAL_OPEN;
158   t['}'] |= SPECIAL_CLOSE;
159#endif
160}
161
162void
163default_units(settings *s)
164{
165   int quantity;
166   for (quantity = 0; quantity < Q_MAC; quantity++) {
167      if (TSTBIT(ANG_QMASK, quantity))
168         s->units[quantity] = (real)(M_PI / 180.0); /* degrees */
169      else
170         s->units[quantity] = (real)1.0; /* metres */
171   }
172   s->f_clino_percent = s->f_backclino_percent = fFalse;
173}
174
175void
176default_calib(settings *s)
177{
178   int quantity;
179   for (quantity = 0; quantity < Q_MAC; quantity++) {
180      s->z[quantity] = (real)0.0;
181      s->sc[quantity] = (real)1.0;
182   }
183}
184
185static void
186default_flags(settings *s)
187{
188   s->flags = 0;
189}
190
191extern void
192default_all(settings *s)
193{
194   default_truncate(s);
195   s->infer = 0;
196   default_case(s);
197   default_style(s);
198   default_prefix(s);
199   default_translate(s);
200   default_grade(s);
201   default_units(s);
202   default_calib(s);
203   default_flags(s);
204}
205
206char *buffer = NULL;
207static int buf_len;
208
209static char *ucbuffer = NULL;
210
211/* read token */
212extern void
213get_token(void)
214{
215   skipblanks();
216   get_token_no_blanks();
217}
218
219extern void
220get_token_no_blanks(void)
221{
222   int i = -1;
223
224   s_zero(&buffer);
225   osfree(ucbuffer);
226   while (isalpha(ch)) {
227      s_catchar(&buffer, &buf_len, (char)ch);
228      nextch();
229   }
230
231   if (!buffer) s_catchar(&buffer, &buf_len, '\0');
232
233   ucbuffer = osmalloc(buf_len);
234   do {
235      i++;
236      ucbuffer[i] = toupper(buffer[i]);
237   } while (buffer[i]);
238#if 0
239   printf("get_token_no_blanks() got “%s”\n", buffer);
240#endif
241}
242
243/* read word */
244static void
245get_word(void)
246{
247   s_zero(&buffer);
248   skipblanks();
249   while (!isBlank(ch) && !isEol(ch)) {
250      s_catchar(&buffer, &buf_len, (char)ch);
251      nextch();
252   }
253
254   if (!buffer) s_catchar(&buffer, &buf_len, '\0');
255#if 0
256   printf("get_word() got “%s”\n", buffer);
257#endif
258}
259
260/* match_tok() now uses binary chop
261 * tab argument should be alphabetically sorted (ascending)
262 */
263extern int
264match_tok(const sztok *tab, int tab_size)
265{
266   int a = 0, b = tab_size - 1, c;
267   int r;
268   assert(tab_size > 0); /* catch empty table */
269/*  printf("[%d,%d]",a,b); */
270   while (a <= b) {
271      c = (unsigned)(a + b) / 2;
272/*     printf(" %d",c); */
273      r = strcmp(tab[c].sz, ucbuffer);
274      if (r == 0) return tab[c].tok; /* match */
275      if (r < 0)
276         a = c + 1;
277      else
278         b = c - 1;
279   }
280   return tab[tab_size].tok; /* no match */
281}
282
283typedef enum {
284   CMD_NULL = -1, CMD_ALIAS, CMD_BEGIN, CMD_CALIBRATE, CMD_CASE, CMD_COPYRIGHT,
285   CMD_CS, CMD_DATA, CMD_DATE, CMD_DECLINATION, CMD_DEFAULT, CMD_END,
286   CMD_ENTRANCE, CMD_EQUATE, CMD_EXPORT, CMD_FIX, CMD_FLAGS, CMD_INCLUDE,
287   CMD_INFER, CMD_INSTRUMENT, CMD_PREFIX, CMD_REF, CMD_REQUIRE, CMD_SD,
288   CMD_SET, CMD_SOLVE, CMD_TEAM, CMD_TITLE, CMD_TRUNCATE, CMD_UNITS
289} cmds;
290
291static const sztok cmd_tab[] = {
292     {"ALIAS",     CMD_ALIAS},
293     {"BEGIN",     CMD_BEGIN},
294     {"CALIBRATE", CMD_CALIBRATE},
295     {"CASE",      CMD_CASE},
296     {"COPYRIGHT", CMD_COPYRIGHT},
297     {"CS",        CMD_CS},
298     {"DATA",      CMD_DATA},
299     {"DATE",      CMD_DATE},
300     {"DECLINATION", CMD_DECLINATION},
301#ifndef NO_DEPRECATED
302     {"DEFAULT",   CMD_DEFAULT},
303#endif
304     {"END",       CMD_END},
305     {"ENTRANCE",  CMD_ENTRANCE},
306     {"EQUATE",    CMD_EQUATE},
307     {"EXPORT",    CMD_EXPORT},
308     {"FIX",       CMD_FIX},
309     {"FLAGS",     CMD_FLAGS},
310     {"INCLUDE",   CMD_INCLUDE},
311     {"INFER",     CMD_INFER},
312     {"INSTRUMENT",CMD_INSTRUMENT},
313#ifndef NO_DEPRECATED
314     {"PREFIX",    CMD_PREFIX},
315#endif
316     {"REF",       CMD_REF},
317     {"REQUIRE",   CMD_REQUIRE},
318     {"SD",        CMD_SD},
319     {"SET",       CMD_SET},
320     {"SOLVE",     CMD_SOLVE},
321     {"TEAM",      CMD_TEAM},
322     {"TITLE",     CMD_TITLE},
323     {"TRUNCATE",  CMD_TRUNCATE},
324     {"UNITS",     CMD_UNITS},
325     {NULL,        CMD_NULL}
326};
327
328/* masks for units which are length and angles respectively */
329#define LEN_UMASK (BIT(UNITS_METRES) | BIT(UNITS_FEET) | BIT(UNITS_YARDS))
330#define ANG_UMASK (BIT(UNITS_DEGS) | BIT(UNITS_GRADS) | BIT(UNITS_MINUTES))
331
332/* ordering must be the same as the units enum */
333const real factor_tab[] = {
334   1.0, METRES_PER_FOOT, (METRES_PER_FOOT*3.0),
335   (M_PI/180.0), (M_PI/200.0), 0.01, (M_PI/180.0/60.0)
336};
337
338const int units_to_msgno[] = {
339    /*m*/424,
340    /*ft*/428,
341    -1, /* yards */
342    /*°*/344,
343    /*ᵍ*/345,
344    /*%*/96,
345    -1 /* minutes */
346};
347
348int get_length_units(int quantity) {
349    double factor = pcs->units[quantity];
350    if (fabs(factor - METRES_PER_FOOT) <= REAL_EPSILON ||
351        fabs(factor - METRES_PER_FOOT * 3.0) <= REAL_EPSILON) {
352        return UNITS_FEET;
353    }
354    return UNITS_METRES;
355}
356
357int get_angle_units(int quantity) {
358    double factor = pcs->units[quantity];
359    if (fabs(factor - M_PI / 200.0) <= REAL_EPSILON) {
360        return UNITS_GRADS;
361    }
362    return UNITS_DEGS;
363}
364
365static int
366get_units(unsigned long qmask, bool percent_ok)
367{
368   static const sztok utab[] = {
369        {"DEGREES",       UNITS_DEGS },
370        {"DEGS",          UNITS_DEGS },
371        {"FEET",          UNITS_FEET },
372        {"GRADS",         UNITS_GRADS },
373        {"METERS",        UNITS_METRES },
374        {"METRES",        UNITS_METRES },
375        {"METRIC",        UNITS_METRES },
376        {"MILS",          UNITS_DEPRECATED_ALIAS_FOR_GRADS },
377        {"MINUTES",       UNITS_MINUTES },
378        {"PERCENT",       UNITS_PERCENT },
379        {"PERCENTAGE",    UNITS_PERCENT },
380        {"YARDS",         UNITS_YARDS },
381        {NULL,            UNITS_NULL }
382   };
383   int units;
384   get_token();
385   units = match_tok(utab, TABSIZE(utab));
386   if (units == UNITS_NULL) {
387      compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Unknown units “%s”*/35, buffer);
388      return UNITS_NULL;
389   }
390   /* Survex has long misdefined "mils" as an alias for "grads", of which
391    * there are 400 in a circle.  There are several definitions of "mils"
392    * with a circle containing 2000π SI milliradians, 6400 NATO mils, 6000
393    * Warsaw Pact mils, and 6300 Swedish streck, and they aren't in common
394    * use by cave surveyors, so we now just warn if mils are used.
395    */
396   if (units == UNITS_DEPRECATED_ALIAS_FOR_GRADS) {
397      compile_diagnostic(DIAG_WARN|DIAG_BUF|DIAG_SKIP,
398                         /*Units “%s” are deprecated, assuming “grads” - see manual for details*/479,
399                         buffer);
400      units = UNITS_GRADS;
401   }
402   if (units == UNITS_PERCENT && percent_ok &&
403       !(qmask & ~(BIT(Q_GRADIENT)|BIT(Q_BACKGRADIENT)))) {
404      return units;
405   }
406   if (((qmask & LEN_QMASK) && !TSTBIT(LEN_UMASK, units)) ||
407       ((qmask & ANG_QMASK) && !TSTBIT(ANG_UMASK, units))) {
408      /* TRANSLATORS: Note: In English you talk about the *units* of a single
409       * measurement, but the correct term in other languages may be singular.
410       */
411      compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Invalid units “%s” for quantity*/37, buffer);
412      return UNITS_NULL;
413   }
414   return units;
415}
416
417/* returns mask with bit x set to indicate quantity x specified */
418static unsigned long
419get_qlist(unsigned long mask_bad)
420{
421   static const sztok qtab[] = {
422        {"ALTITUDE",     Q_DZ },
423        {"BACKBEARING",  Q_BACKBEARING },
424        {"BACKCLINO",    Q_BACKGRADIENT },    /* alternative name */
425        {"BACKCOMPASS",  Q_BACKBEARING },     /* alternative name */
426        {"BACKGRADIENT", Q_BACKGRADIENT },
427        {"BACKLENGTH",   Q_BACKLENGTH },
428        {"BACKTAPE",     Q_BACKLENGTH },    /* alternative name */
429        {"BEARING",      Q_BEARING },
430        {"CEILING",      Q_UP },          /* alternative name */
431        {"CLINO",        Q_GRADIENT },    /* alternative name */
432        {"COMPASS",      Q_BEARING },     /* alternative name */
433        {"COUNT",        Q_COUNT },
434        {"COUNTER",      Q_COUNT },       /* alternative name */
435        {"DECLINATION",  Q_DECLINATION },
436        {"DEFAULT",      Q_DEFAULT }, /* not a real quantity... */
437        {"DEPTH",        Q_DEPTH },
438        {"DOWN",         Q_DOWN },
439        {"DX",           Q_DX },          /* alternative name */
440        {"DY",           Q_DY },          /* alternative name */
441        {"DZ",           Q_DZ },          /* alternative name */
442        {"EASTING",      Q_DX },
443        {"FLOOR",        Q_DOWN },        /* alternative name */
444        {"GRADIENT",     Q_GRADIENT },
445        {"LEFT",         Q_LEFT },
446        {"LENGTH",       Q_LENGTH },
447        {"LEVEL",        Q_LEVEL},
448        {"NORTHING",     Q_DY },
449        {"PLUMB",        Q_PLUMB},
450        {"POSITION",     Q_POS },
451        {"RIGHT",        Q_RIGHT },
452        {"TAPE",         Q_LENGTH },      /* alternative name */
453        {"UP",           Q_UP },
454        {NULL,           Q_NULL }
455   };
456   unsigned long qmask = 0;
457   int tok;
458   filepos fp;
459
460   while (1) {
461      get_pos(&fp);
462      get_token();
463      tok = match_tok(qtab, TABSIZE(qtab));
464      if (tok == Q_DEFAULT && !(mask_bad & BIT(Q_DEFAULT))) {
465          /* Only recognise DEFAULT if it is the first quantity, and then don't
466           * look for any more. */
467          if (qmask == 0)
468              return BIT(Q_DEFAULT);
469          break;
470      }
471      /* bail out if we reach the table end with no match */
472      if (tok == Q_NULL) break;
473      qmask |= BIT(tok);
474      if (qmask & mask_bad) {
475         compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Unknown instrument “%s”*/39, buffer);
476         return 0;
477      }
478   }
479
480   if (qmask == 0) {
481      /* TRANSLATORS: A "quantity" is something measured like "LENGTH",
482       * "BEARING", "ALTITUDE", etc. */
483      compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Unknown quantity “%s”*/34, buffer);
484   } else {
485      set_pos(&fp);
486   }
487
488   return qmask;
489}
490
491#define SPECIAL_UNKNOWN 0
492static void
493cmd_set(void)
494{
495   static const sztok chartab[] = {
496        {"BLANK",     SPECIAL_BLANK },
497/*FIXME {"CLOSE",     SPECIAL_CLOSE }, */
498        {"COMMENT",   SPECIAL_COMMENT },
499        {"DECIMAL",   SPECIAL_DECIMAL },
500        {"EOL",       SPECIAL_EOL }, /* EOL won't work well */
501        {"KEYWORD",   SPECIAL_KEYWORD },
502        {"MINUS",     SPECIAL_MINUS },
503        {"NAMES",     SPECIAL_NAMES },
504        {"OMIT",      SPECIAL_OMIT },
505/*FIXME {"OPEN",      SPECIAL_OPEN }, */
506        {"PLUS",      SPECIAL_PLUS },
507#ifndef NO_DEPRECATED
508        {"ROOT",      SPECIAL_ROOT },
509#endif
510        {"SEPARATOR", SPECIAL_SEPARATOR },
511        {NULL,        SPECIAL_UNKNOWN }
512   };
513   int mask;
514   int i;
515
516   get_token();
517   mask = match_tok(chartab, TABSIZE(chartab));
518
519   if (mask == SPECIAL_UNKNOWN) {
520      compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Unknown character class “%s”*/42, buffer);
521      return;
522   }
523
524#ifndef NO_DEPRECATED
525   if (mask == SPECIAL_ROOT) {
526      if (root_depr_count < 5) {
527         /* TRANSLATORS: Use of the ROOT character (which is "\" by default) is
528          * deprecated, so this error would be generated by:
529          *
530          * *equate \foo.7 1
531          *
532          * If you're unsure what "deprecated" means, see:
533          * https://en.wikipedia.org/wiki/Deprecation */
534         compile_diagnostic(DIAG_WARN|DIAG_BUF, /*ROOT is deprecated*/25);
535         if (++root_depr_count == 5)
536             /* TRANSLATORS: If you're unsure what "deprecated" means, see:
537              * https://en.wikipedia.org/wiki/Deprecation */
538            compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
539      }
540   }
541#endif
542
543   /* if we're currently using an inherited translation table, allocate a new
544    * table, and copy old one into it */
545   if (pcs->next && pcs->next->Translate == pcs->Translate) {
546      short *p;
547      p = ((short*)osmalloc(ossizeof(short) * 257)) + 1;
548      memcpy(p - 1, pcs->Translate - 1, sizeof(short) * 257);
549      pcs->Translate = p;
550   }
551
552   skipblanks();
553
554   /* clear this flag for all non-alphanums */
555   for (i = 0; i < 256; i++)
556      if (!isalnum(i)) pcs->Translate[i] &= ~mask;
557
558   /* now set this flag for all specified chars */
559   while (!isEol(ch)) {
560      if (!isalnum(ch)) {
561         pcs->Translate[ch] |= mask;
562      } else if (tolower(ch) == 'x') {
563         int hex;
564         filepos fp;
565         get_pos(&fp);
566         nextch();
567         if (!isxdigit(ch)) {
568            set_pos(&fp);
569            break;
570         }
571         hex = isdigit(ch) ? ch - '0' : tolower(ch) - 'a';
572         nextch();
573         if (!isxdigit(ch)) {
574            set_pos(&fp);
575            break;
576         }
577         hex = hex << 4 | (isdigit(ch) ? ch - '0' : tolower(ch) - 'a');
578         pcs->Translate[hex] |= mask;
579      } else {
580         break;
581      }
582      nextch();
583   }
584}
585
586static void
587check_reentry(prefix *survey, const filepos* fpos_ptr)
588{
589   /* Don't try to check "*prefix \" or "*begin \" */
590   if (!survey->up) return;
591   if (TSTBIT(survey->sflags, SFLAGS_PREFIX_ENTERED)) {
592      static int reenter_depr_count = 0;
593      filepos fp_tmp;
594
595      if (reenter_depr_count >= 5)
596         return;
597
598      get_pos(&fp_tmp);
599      set_pos(fpos_ptr);
600      /* TRANSLATORS: The first of two warnings given when a survey which has
601       * already been completed is reentered.  This example file crawl.svx:
602       *
603       * *begin crawl     ; <- second warning here
604       * 1 2 9.45 234 -01
605       * *end crawl
606       * *begin crawl     ; <- first warning here
607       * 2 3 7.67 223 -03
608       * *end crawl
609       *
610       * Would lead to:
611       *
612       * crawl.svx:4: Reentering an existing survey is deprecated
613       * crawl.svx:1: Originally entered here
614       *
615       * If you're unsure what "deprecated" means, see:
616       * https://en.wikipedia.org/wiki/Deprecation */
617      compile_diagnostic(DIAG_WARN|DIAG_TOKEN, /*Reentering an existing survey is deprecated*/29);
618      set_pos(&fp_tmp);
619      /* TRANSLATORS: The second of two warnings given when a survey which has
620       * already been completed is reentered.  This example file crawl.svx:
621       *
622       * *begin crawl
623       * 1 2 9.45 234 -01 # <- second warning here
624       * *end crawl
625       * *begin crawl     # <- first warning here
626       * 2 3 7.67 223 -03
627       * *end crawl
628       *
629       * Would lead to:
630       *
631       * crawl.svx:3: Reentering an existing survey is deprecated
632       * crawl.svx:1: Originally entered here
633       *
634       * If you're unsure what "deprecated" means, see:
635       * https://en.wikipedia.org/wiki/Deprecation */
636      compile_diagnostic_pfx(DIAG_WARN, survey, /*Originally entered here*/30);
637      if (++reenter_depr_count == 5) {
638         /* After we've warned about 5 uses of the same deprecated feature, we
639          * give up for the rest of the current processing run.
640          *
641          * If you're unsure what "deprecated" means, see:
642          * https://en.wikipedia.org/wiki/Deprecation */
643         compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
644      }
645   } else {
646      survey->sflags |= BIT(SFLAGS_PREFIX_ENTERED);
647      survey->filename = file.filename;
648      survey->line = file.line;
649   }
650}
651
652#ifndef NO_DEPRECATED
653static void
654cmd_prefix(void)
655{
656   static int prefix_depr_count = 0;
657   prefix *survey;
658   filepos fp;
659   /* Issue warning first, so "*prefix \" warns first that *prefix is
660    * deprecated and then that ROOT is...
661    */
662   if (prefix_depr_count < 5) {
663      /* TRANSLATORS: If you're unsure what "deprecated" means, see:
664       * https://en.wikipedia.org/wiki/Deprecation */
665      compile_diagnostic(DIAG_WARN|DIAG_BUF, /**prefix is deprecated - use *begin and *end instead*/6);
666      if (++prefix_depr_count == 5)
667         compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
668   }
669   get_pos(&fp);
670   survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT);
671   pcs->Prefix = survey;
672   check_reentry(survey, &fp);
673}
674#endif
675
676static void
677cmd_alias(void)
678{
679   /* Currently only two forms are supported:
680    * *alias station - ..
681    * *alias station -
682    */
683   get_token();
684   if (strcmp(ucbuffer, "STATION") != 0)
685      goto bad;
686   get_word();
687   if (strcmp(buffer, "-") != 0)
688      goto bad;
689   get_word();
690   if (*buffer && strcmp(buffer, "..") != 0)
691      goto bad;
692   pcs->dash_for_anon_wall_station = (*buffer != '\0');
693   return;
694bad:
695   compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Bad *alias command*/397);
696}
697
698static void
699cmd_begin(void)
700{
701   settings *pcsNew;
702
703   pcsNew = osnew(settings);
704   *pcsNew = *pcs; /* copy contents */
705   pcsNew->begin_lineno = file.line;
706   pcsNew->next = pcs;
707   pcs = pcsNew;
708
709   skipblanks();
710   pcs->begin_survey = NULL;
711   if (!isEol(ch) && !isComm(ch)) {
712      filepos fp;
713      prefix *survey;
714      get_pos(&fp);
715      survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT|PFX_WARN_SEPARATOR);
716      pcs->begin_survey = survey;
717      pcs->Prefix = survey;
718      check_reentry(survey, &fp);
719      f_export_ok = fTrue;
720   }
721}
722
723extern void
724free_settings(settings *p) {
725   /* don't free default ordering or ordering used by parent */
726   const reading *order = p->ordering;
727   if (order != default_order && (!p->next || order != p->next->ordering))
728      osfree((reading*)order);
729
730   /* free Translate if not used by parent */
731   if (!p->next || p->Translate != p->next->Translate)
732      osfree(p->Translate - 1);
733
734   /* free meta if not used by parent, or in this block */
735   if (p->meta && (!p->next || p->meta != p->next->meta) && p->meta->ref_count == 0)
736       osfree(p->meta);
737
738   /* free proj if not used by parent, or as the output projection */
739   if (p->proj && (!p->next || p->proj != p->next->proj) && p->proj != proj_out)
740       pj_free(p->proj);
741
742   osfree(p);
743}
744
745static void
746cmd_end(void)
747{
748   settings *pcsParent;
749   prefix *survey, *begin_survey;
750   filepos fp;
751
752   pcsParent = pcs->next;
753
754   if (pcs->begin_lineno == 0) {
755      if (pcsParent == NULL) {
756         /* more ENDs than BEGINs */
757         compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*No matching BEGIN*/192);
758      } else {
759         compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*END with no matching BEGIN in this file*/22);
760      }
761      return;
762   }
763
764   begin_survey = pcs->begin_survey;
765
766   SVX_ASSERT(pcsParent);
767   free_settings(pcs);
768   pcs = pcsParent;
769
770   /* note need to read using root *before* BEGIN */
771   skipblanks();
772   if (isEol(ch) || isComm(ch)) {
773      survey = NULL;
774   } else {
775      get_pos(&fp);
776      survey = read_prefix(PFX_SURVEY|PFX_ALLOW_ROOT);
777   }
778
779   if (survey != begin_survey) {
780      if (survey) {
781         set_pos(&fp);
782         if (!begin_survey) {
783            /* TRANSLATORS: Used when a BEGIN command has no survey, but the
784             * END command does, e.g.:
785             *
786             * *begin
787             * 1 2 10.00 178 -01
788             * *end entrance      <--[Message given here] */
789            compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Matching BEGIN command has no survey name*/36);
790         } else {
791            /* TRANSLATORS: *BEGIN <survey> and *END <survey> should have the
792             * same <survey> if it’s given at all */
793            compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Survey name doesn’t match BEGIN*/193);
794         }
795         skipline();
796      } else {
797         /* TRANSLATORS: Used when a BEGIN command has a survey name, but the
798          * END command omits it, e.g.:
799          *
800          * *begin entrance
801          * 1 2 10.00 178 -01
802          * *end     <--[Message given here] */
803         compile_diagnostic(DIAG_WARN|DIAG_COL, /*Survey name omitted from END*/194);
804      }
805   }
806}
807
808static void
809cmd_entrance(void)
810{
811   prefix *pfx = read_prefix(PFX_STATION);
812   pfx->sflags |= BIT(SFLAGS_ENTRANCE);
813}
814
815static const prefix * first_fix_name = NULL;
816static const char * first_fix_filename;
817static unsigned first_fix_line;
818
819static void
820cmd_fix(void)
821{
822   prefix *fix_name;
823   node *stn = NULL;
824   static prefix *name_omit_already = NULL;
825   static const char * name_omit_already_filename = NULL;
826   static unsigned int name_omit_already_line;
827   real x, y, z;
828   filepos fp;
829
830   fix_name = read_prefix(PFX_STATION|PFX_ALLOW_ROOT);
831   fix_name->sflags |= BIT(SFLAGS_FIXED);
832
833   get_pos(&fp);
834   get_token();
835   if (strcmp(ucbuffer, "REFERENCE") == 0) {
836      /* suppress "unused fixed point" warnings for this station */
837      fix_name->sflags |= BIT(SFLAGS_USED);
838   } else {
839      if (*ucbuffer) set_pos(&fp);
840   }
841
842   x = read_numeric(fTrue);
843   if (x == HUGE_REAL) {
844      /* If the end of the line isn't blank, read a number after all to
845       * get a more helpful error message */
846      if (!isEol(ch) && !isComm(ch)) x = read_numeric(fFalse);
847   }
848   if (x == HUGE_REAL) {
849      if (pcs->proj || proj_out) {
850         compile_diagnostic(DIAG_ERR|DIAG_COL|DIAG_SKIP, /*Coordinates can't be omitted when coordinate system has been specified*/439);
851         return;
852      }
853
854      if (fix_name == name_omit_already) {
855         compile_diagnostic(DIAG_WARN|DIAG_COL, /*Same station fixed twice with no coordinates*/61);
856         return;
857      }
858
859      /* TRANSLATORS: " *fix a " gives this message: */
860      compile_diagnostic(DIAG_WARN|DIAG_COL, /*FIX command with no coordinates - fixing at (0,0,0)*/54);
861
862      if (name_omit_already) {
863         /* TRANSLATORS: Emitted after second and subsequent "FIX command with
864          * no coordinates - fixing at (0,0,0)" warnings.
865          */
866         compile_diagnostic_at(DIAG_ERR|DIAG_COL,
867                               name_omit_already_filename,
868                               name_omit_already_line,
869                               /*Already had FIX command with no coordinates for station “%s”*/441,
870                               sprint_prefix(name_omit_already));
871      } else {
872         name_omit_already = fix_name;
873         name_omit_already_filename = file.filename;
874         name_omit_already_line = file.line;
875      }
876
877      x = y = z = (real)0.0;
878   } else {
879      real sdx;
880      y = read_numeric(fFalse);
881      z = read_numeric(fFalse);
882
883      if (pcs->proj && proj_out) {
884         if (pj_is_latlong(pcs->proj)) {
885            /* PROJ expects lat and long in radians. */
886            x = rad(x);
887            y = rad(y);
888         }
889         int r = pj_transform(pcs->proj, proj_out, 1, 1, &x, &y, &z);
890         if (r != 0) {
891            compile_diagnostic(DIAG_ERR, /*Failed to convert coordinates: %s*/436, pj_strerrno(r));
892         }
893      } else if (pcs->proj) {
894         compile_diagnostic(DIAG_ERR, /*The input projection is set but the output projection isn't*/437);
895      } else if (proj_out) {
896         compile_diagnostic(DIAG_ERR, /*The output projection is set but the input projection isn't*/438);
897      }
898
899      get_pos(&fp);
900      sdx = read_numeric(fTrue);
901      if (sdx <= 0) {
902          set_pos(&fp);
903          compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
904          return;
905      }
906      if (sdx != HUGE_REAL) {
907         real sdy, sdz;
908         real cxy = 0, cyz = 0, czx = 0;
909         get_pos(&fp);
910         sdy = read_numeric(fTrue);
911         if (sdy == HUGE_REAL) {
912            /* only one variance given */
913            sdy = sdz = sdx;
914         } else {
915            if (sdy <= 0) {
916               set_pos(&fp);
917               compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
918               return;
919            }
920            get_pos(&fp);
921            sdz = read_numeric(fTrue);
922            if (sdz == HUGE_REAL) {
923               /* two variances given - horizontal & vertical */
924               sdz = sdy;
925               sdy = sdx;
926            } else {
927               if (sdz <= 0) {
928                  set_pos(&fp);
929                  compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_NUM, /*Standard deviation must be positive*/48);
930                  return;
931               }
932               cxy = read_numeric(fTrue);
933               if (cxy != HUGE_REAL) {
934                  /* covariances given */
935                  cyz = read_numeric(fFalse);
936                  czx = read_numeric(fFalse);
937               } else {
938                  cxy = 0;
939               }
940            }
941         }
942         stn = StnFromPfx(fix_name);
943         if (!fixed(stn)) {
944            node *fixpt = osnew(node);
945            prefix *name;
946            name = osnew(prefix);
947            name->pos = osnew(pos);
948            name->ident = NULL;
949            name->shape = 0;
950            fixpt->name = name;
951            name->stn = fixpt;
952            name->up = NULL;
953            if (TSTBIT(pcs->infer, INFER_EXPORTS)) {
954               name->min_export = USHRT_MAX;
955            } else {
956               name->min_export = 0;
957            }
958            name->max_export = 0;
959            name->sflags = 0;
960            add_stn_to_list(&stnlist, fixpt);
961            POS(fixpt, 0) = x;
962            POS(fixpt, 1) = y;
963            POS(fixpt, 2) = z;
964            fix(fixpt);
965            fixpt->leg[0] = fixpt->leg[1] = fixpt->leg[2] = NULL;
966            addfakeleg(fixpt, stn, 0, 0, 0,
967                       sdx * sdx, sdy * sdy, sdz * sdz
968#ifndef NO_COVARIANCES
969                       , cxy, cyz, czx
970#endif
971                       );
972         }
973
974         if (!first_fix_name) {
975            /* We track if we've fixed a station yet, and if so what the name
976             * of the first fix was, so that we can issue an error if the
977             * output coordinate system is set after fixing a station. */
978            first_fix_name = fix_name;
979            first_fix_filename = file.filename;
980            first_fix_line = file.line;
981         }
982
983         return;
984      }
985   }
986
987   if (!first_fix_name) {
988      /* We track if we've fixed a station yet, and if so what the name of the
989       * first fix was, so that we can issue an error if the output coordinate
990       * system is set after fixing a station. */
991      first_fix_name = fix_name;
992      first_fix_filename = file.filename;
993      first_fix_line = file.line;
994   }
995
996   stn = StnFromPfx(fix_name);
997   if (!fixed(stn)) {
998      POS(stn, 0) = x;
999      POS(stn, 1) = y;
1000      POS(stn, 2) = z;
1001      fix(stn);
1002      return;
1003   }
1004
1005   if (x != POS(stn, 0) || y != POS(stn, 1) || z != POS(stn, 2)) {
1006      compile_diagnostic(DIAG_ERR, /*Station already fixed or equated to a fixed point*/46);
1007      return;
1008   }
1009   /* TRANSLATORS: *fix a 1 2 3 / *fix a 1 2 3 */
1010   compile_diagnostic(DIAG_WARN, /*Station already fixed at the same coordinates*/55);
1011}
1012
1013static void
1014cmd_flags(void)
1015{
1016   static const sztok flagtab[] = {
1017        {"DUPLICATE", FLAGS_DUPLICATE },
1018        {"NOT",       FLAGS_NOT },
1019        {"SPLAY",     FLAGS_SPLAY },
1020        {"SURFACE",   FLAGS_SURFACE },
1021        {NULL,        FLAGS_UNKNOWN }
1022   };
1023   bool fNot = fFalse;
1024   bool fEmpty = fTrue;
1025   while (1) {
1026      int flag;
1027      get_token();
1028      /* If buffer is empty, it could mean end of line, or maybe
1029       * some non-letter junk which is better reported later */
1030      if (!buffer[0]) break;
1031
1032      fEmpty = fFalse;
1033      flag = match_tok(flagtab, TABSIZE(flagtab));
1034      /* treat the second NOT in "NOT NOT" as an unknown flag */
1035      if (flag == FLAGS_UNKNOWN || (fNot && flag == FLAGS_NOT)) {
1036         compile_diagnostic(DIAG_ERR|DIAG_BUF, /*FLAG “%s” unknown*/68, buffer);
1037         /* Recover from “*FLAGS NOT BOGUS SURFACE” by ignoring "NOT BOGUS" */
1038         fNot = fFalse;
1039      } else if (flag == FLAGS_NOT) {
1040         fNot = fTrue;
1041      } else if (fNot) {
1042         pcs->flags &= ~BIT(flag);
1043         fNot = fFalse;
1044      } else {
1045         pcs->flags |= BIT(flag);
1046      }
1047   }
1048
1049   if (fNot) {
1050      compile_diagnostic(DIAG_ERR|DIAG_BUF, /*Expecting “DUPLICATE”, “SPLAY”, or “SURFACE”*/188);
1051   } else if (fEmpty) {
1052      compile_diagnostic(DIAG_ERR|DIAG_BUF, /*Expecting “NOT”, “DUPLICATE”, “SPLAY”, or “SURFACE”*/189);
1053   }
1054}
1055
1056static void
1057cmd_equate(void)
1058{
1059   prefix *name1, *name2;
1060   bool fOnlyOneStn = fTrue; /* to trap eg *equate entrance.6 */
1061   filepos fp;
1062
1063   get_pos(&fp);
1064   name1 = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_SUSPECT_TYPO);
1065   while (fTrue) {
1066      name2 = name1;
1067      skipblanks();
1068      if (isEol(ch) || isComm(ch)) {
1069         if (fOnlyOneStn) {
1070            set_pos(&fp);
1071            /* TRANSLATORS: EQUATE is a command name, so shouldn’t be
1072             * translated.
1073             *
1074             * Here "station" is a survey station, not a train station.
1075             */
1076            compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_TOKEN, /*Only one station in EQUATE command*/33);
1077         }
1078         return;
1079      }
1080
1081      name1 = read_prefix(PFX_STATION|PFX_ALLOW_ROOT|PFX_SUSPECT_TYPO);
1082      process_equate(name1, name2);
1083      fOnlyOneStn = fFalse;
1084   }
1085}
1086
1087static void
1088report_missing_export(prefix *pfx, int depth)
1089{
1090   char *s;
1091   const char *p;
1092   prefix *survey = pfx;
1093   int i;
1094   for (i = depth + 1; i; i--) {
1095      survey = survey->up;
1096      SVX_ASSERT(survey);
1097   }
1098   s = osstrdup(sprint_prefix(survey));
1099   p = sprint_prefix(pfx);
1100   if (survey->filename) {
1101      /* TRANSLATORS: A station must be exported out of each level it is in, so
1102       * this would give "Station “\outer.inner.1” not exported from survey
1103       * “\outer”)":
1104       *
1105       * *equate entrance outer.inner.1
1106       * *begin outer
1107       * *begin inner
1108       * *export 1
1109       * 1 2 1.23 045 -6
1110       * *end inner
1111       * *end outer
1112       *
1113       * Here "survey" is a "cave map" rather than list of questions - it should be
1114       * translated to the terminology that cavers using the language would use.
1115       */
1116      compile_diagnostic_pfx(DIAG_ERR, survey,
1117                             /*Station “%s” not exported from survey “%s”*/26, p, s);
1118   } else {
1119      compile_diagnostic(DIAG_ERR, /*Station “%s” not exported from survey “%s”*/26, p, s);
1120   }
1121   osfree(s);
1122}
1123
1124static void
1125cmd_export(void)
1126{
1127   prefix *pfx;
1128
1129   fExportUsed = fTrue;
1130   do {
1131      int depth = 0;
1132      pfx = read_prefix(PFX_STATION|PFX_NEW);
1133      if (pfx == NULL) {
1134         /* The argument was an existing station. */
1135         /* FIXME */
1136      } else {
1137         prefix *p = pfx;
1138         while (p != NULL && p != pcs->Prefix) {
1139            depth++;
1140            p = p->up;
1141         }
1142         /* Something like: *export \foo, but we've excluded use of root */
1143         SVX_ASSERT(p);
1144      }
1145      /* *export \ or similar bogus stuff */
1146      SVX_ASSERT(depth);
1147#if 0
1148      printf("C min %d max %d depth %d pfx %s\n",
1149             pfx->min_export, pfx->max_export, depth, sprint_prefix(pfx));
1150#endif
1151      if (pfx->min_export == 0) {
1152         /* not encountered *export for this name before */
1153         if (pfx->max_export > depth) report_missing_export(pfx, depth);
1154         pfx->min_export = pfx->max_export = depth;
1155      } else if (pfx->min_export != USHRT_MAX) {
1156         /* FIXME: what to do if a station is marked for inferred exports
1157          * but is then explicitly exported?  Currently we just ignore the
1158          * explicit export... */
1159         if (pfx->min_export - 1 > depth) {
1160            report_missing_export(pfx, depth);
1161         } else if (pfx->min_export - 1 < depth) {
1162            /* TRANSLATORS: Here "station" is a survey station, not a train station.
1163             *
1164             * Exporting a station twice gives this error:
1165             *
1166             * *begin example
1167             * *export 1
1168             * *export 1
1169             * 1 2 1.24 045 -6
1170             * *end example */
1171            compile_diagnostic(DIAG_ERR, /*Station “%s” already exported*/66,
1172                               sprint_prefix(pfx));
1173         }
1174         pfx->min_export = depth;
1175      }
1176      skipblanks();
1177   } while (!isEol(ch) && !isComm(ch));
1178}
1179
1180static void
1181cmd_data(void)
1182{
1183   static const sztok dtab[] = {
1184        {"ALTITUDE",     Dz },
1185        {"BACKBEARING",  BackComp },
1186        {"BACKCLINO",    BackClino }, /* alternative name */
1187        {"BACKCOMPASS",  BackComp }, /* alternative name */
1188        {"BACKGRADIENT", BackClino },
1189        {"BACKLENGTH",   BackTape },
1190        {"BACKTAPE",     BackTape }, /* alternative name */
1191        {"BEARING",      Comp },
1192        {"CEILING",      Up }, /* alternative name */
1193        {"CLINO",        Clino }, /* alternative name */
1194        {"COMPASS",      Comp }, /* alternative name */
1195        {"COUNT",        Count }, /* FrCount&ToCount in multiline */
1196        {"DEPTH",        Depth }, /* FrDepth&ToDepth in multiline */
1197        {"DEPTHCHANGE",  DepthChange },
1198        {"DIRECTION",    Dir },
1199        {"DOWN",         Down },
1200        {"DX",           Dx },
1201        {"DY",           Dy },
1202        {"DZ",           Dz },
1203        {"EASTING",      Dx },
1204        {"FLOOR",        Down }, /* alternative name */
1205        {"FROM",         Fr },
1206        {"FROMCOUNT",    FrCount },
1207        {"FROMDEPTH",    FrDepth },
1208        {"GRADIENT",     Clino },
1209        {"IGNORE",       Ignore },
1210        {"IGNOREALL",    IgnoreAll },
1211        {"LEFT",         Left },
1212        {"LENGTH",       Tape },
1213        {"NEWLINE",      Newline },
1214        {"NORTHING",     Dy },
1215        {"RIGHT",        Right },
1216        {"STATION",      Station }, /* Fr&To in multiline */
1217        {"TAPE",         Tape }, /* alternative name */
1218        {"TO",           To },
1219        {"TOCOUNT",      ToCount },
1220        {"TODEPTH",      ToDepth },
1221        {"UP",           Up },
1222        {NULL,           End }
1223   };
1224
1225#define MASK_stns BIT(Fr) | BIT(To) | BIT(Station)
1226#define MASK_tape BIT(Tape) | BIT(BackTape) | BIT(FrCount) | BIT(ToCount) | BIT(Count)
1227#define MASK_dpth BIT(FrDepth) | BIT(ToDepth) | BIT(Depth) | BIT(DepthChange)
1228#define MASK_comp BIT(Comp) | BIT(BackComp)
1229#define MASK_clin BIT(Clino) | BIT(BackClino)
1230
1231#define MASK_NORMAL MASK_stns | BIT(Dir) | MASK_tape | MASK_comp | MASK_clin
1232#define MASK_DIVING MASK_NORMAL | MASK_dpth
1233#define MASK_CARTESIAN MASK_stns | BIT(Dx) | BIT(Dy) | BIT(Dz)
1234#define MASK_CYLPOLAR  MASK_stns | BIT(Dir) | MASK_tape | MASK_comp | MASK_dpth
1235#define MASK_NOSURVEY MASK_stns
1236#define MASK_PASSAGE BIT(Station) | BIT(Left) | BIT(Right) | BIT(Up) | BIT(Down)
1237
1238   /* readings which may be given for each style */
1239   static const unsigned long mask[] = {
1240      MASK_NORMAL, MASK_DIVING, MASK_CARTESIAN, MASK_CYLPOLAR, MASK_NOSURVEY,
1241      MASK_PASSAGE
1242   };
1243
1244   /* readings which may be omitted for each style */
1245   static const unsigned long mask_optional[] = {
1246      BIT(Dir) | BIT(Clino) | BIT(BackClino),
1247      BIT(Dir) | BIT(Clino) | BIT(BackClino),
1248      0,
1249      BIT(Dir),
1250      0,
1251      0 /* BIT(Left) | BIT(Right) | BIT(Up) | BIT(Down), */
1252   };
1253
1254   /* all valid readings */
1255   static const unsigned long mask_all[] = {
1256      MASK_NORMAL | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1257      MASK_DIVING | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1258      MASK_CARTESIAN | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1259      MASK_CYLPOLAR | BIT(Newline) | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1260      MASK_NOSURVEY | BIT(Ignore) | BIT(IgnoreAll) | BIT(End),
1261      MASK_PASSAGE | BIT(Ignore) | BIT(IgnoreAll) | BIT(End)
1262   };
1263#define STYLE_DEFAULT   -2
1264#define STYLE_UNKNOWN   -1
1265
1266   static const sztok styletab[] = {
1267        {"CARTESIAN",    STYLE_CARTESIAN },
1268        {"CYLPOLAR",     STYLE_CYLPOLAR },
1269        {"DEFAULT",      STYLE_DEFAULT },
1270        {"DIVING",       STYLE_DIVING },
1271        {"NORMAL",       STYLE_NORMAL },
1272        {"NOSURVEY",     STYLE_NOSURVEY },
1273        {"PASSAGE",      STYLE_PASSAGE },
1274        {"TOPOFIL",      STYLE_NORMAL },
1275        {NULL,           STYLE_UNKNOWN }
1276   };
1277
1278#define m_multi (BIT(Station) | BIT(Count) | BIT(Depth))
1279
1280   int style, k = 0, kMac;
1281   reading *new_order, d;
1282   unsigned long mUsed = 0;
1283   char *style_name;
1284   int old_style = pcs->style;
1285
1286   /* after a bad *data command ignore survey data until the next
1287    * *data command to avoid an avalanche of errors */
1288   pcs->style = STYLE_IGNORE;
1289
1290   kMac = 6; /* minimum for NORMAL style */
1291   new_order = osmalloc(kMac * sizeof(reading));
1292
1293   get_token();
1294   style = match_tok(styletab, TABSIZE(styletab));
1295
1296   if (style == STYLE_DEFAULT) {
1297      default_style(pcs);
1298      return;
1299   }
1300
1301   if (style == STYLE_UNKNOWN) {
1302      if (!buffer[0]) {
1303         /* "*data" reinitialises the current style - for *data passage that
1304          * breaks the passage.
1305          */
1306         style = old_style;
1307         goto reinit_style;
1308      }
1309      /* TRANSLATORS: e.g. trying to refer to an invalid FNORD data style */
1310      compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Data style “%s” unknown*/65, buffer);
1311      return;
1312   }
1313
1314   skipblanks();
1315#ifndef NO_DEPRECATED
1316   /* Olde syntax had optional field for survey grade, so allow an omit
1317    * but issue a warning about it */
1318   if (isOmit(ch)) {
1319      static int data_depr_count = 0;
1320      if (data_depr_count < 5) {
1321         compile_diagnostic(DIAG_WARN|DIAG_BUF, /*“*data %s %c …” is deprecated - use “*data %s …” instead*/104,
1322                            buffer, ch, buffer);
1323         if (++data_depr_count == 5)
1324            compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
1325      }
1326      nextch();
1327   }
1328#endif
1329
1330   style_name = osstrdup(buffer);
1331   do {
1332      filepos fp;
1333      get_pos(&fp);
1334      get_token();
1335      d = match_tok(dtab, TABSIZE(dtab));
1336      /* only token allowed after IGNOREALL is NEWLINE */
1337      if (k && new_order[k - 1] == IgnoreAll && d != Newline) {
1338         set_pos(&fp);
1339         break;
1340      }
1341      /* Note: an unknown token is reported as trailing garbage */
1342      if (!TSTBIT(mask_all[style], d)) {
1343         /* TRANSLATORS: a data "style" is something like NORMAL, DIVING, etc.
1344          * a "reading" is one of FROM, TO, TAPE, COMPASS, CLINO for NORMAL
1345          * neither style nor reading is a keyword in the program This error
1346          * complains about a depth gauge reading in normal style, for example
1347          */
1348         compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP,
1349                            /*Reading “%s” not allowed in data style “%s”*/63,
1350                            buffer, style_name);
1351         osfree(style_name);
1352         osfree(new_order);
1353         return;
1354      }
1355      if (TSTBIT(mUsed, Newline) && TSTBIT(m_multi, d)) {
1356         /* TRANSLATORS: caused by e.g.
1357          *
1358          * *data diving station newline depth tape compass
1359          *
1360          * ("depth" needs to occur before "newline"). */
1361         compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP,
1362                            /*Reading “%s” must occur before NEWLINE*/225, buffer);
1363         osfree(style_name);
1364         osfree(new_order);
1365         return;
1366      }
1367      /* Check for duplicates unless it's a special reading:
1368       *   IGNOREALL,IGNORE (duplicates allowed) ; END (not possible)
1369       */
1370      if (!((BIT(Ignore) | BIT(End) | BIT(IgnoreAll)) & BIT(d))) {
1371         if (TSTBIT(mUsed, d)) {
1372            /* TRANSLATORS: complains about a situation like trying to define
1373             * two from stations per leg */
1374            compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Duplicate reading “%s”*/67, buffer);
1375            osfree(style_name);
1376            osfree(new_order);
1377            return;
1378         } else {
1379            /* Check for previously listed readings which are incompatible
1380             * with this one - e.g. Count vs FrCount */
1381            bool fBad = fFalse;
1382            switch (d) {
1383             case Station:
1384               if (mUsed & (BIT(Fr) | BIT(To))) fBad = fTrue;
1385               break;
1386             case Fr: case To:
1387               if (TSTBIT(mUsed, Station)) fBad = fTrue;
1388               break;
1389             case Count:
1390               if (mUsed & (BIT(FrCount) | BIT(ToCount) | BIT(Tape)))
1391                  fBad = fTrue;
1392               break;
1393             case FrCount: case ToCount:
1394               if (mUsed & (BIT(Count) | BIT(Tape)))
1395                  fBad = fTrue;
1396               break;
1397             case Depth:
1398               if (mUsed & (BIT(FrDepth) | BIT(ToDepth) | BIT(DepthChange)))
1399                  fBad = fTrue;
1400               break;
1401             case FrDepth: case ToDepth:
1402               if (mUsed & (BIT(Depth) | BIT(DepthChange))) fBad = fTrue;
1403               break;
1404             case DepthChange:
1405               if (mUsed & (BIT(FrDepth) | BIT(ToDepth) | BIT(Depth)))
1406                  fBad = fTrue;
1407               break;
1408             case Newline:
1409               if (mUsed & ~m_multi) {
1410                  /* TRANSLATORS: e.g.
1411                   *
1412                   * *data normal from to tape newline compass clino */
1413                  compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*NEWLINE can only be preceded by STATION, DEPTH, and COUNT*/226);
1414                  osfree(style_name);
1415                  osfree(new_order);
1416                  return;
1417               }
1418               if (k == 0) {
1419                  /* TRANSLATORS: error from:
1420                   *
1421                   * *data normal newline from to tape compass clino */
1422                  compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*NEWLINE can’t be the first reading*/222);
1423                  osfree(style_name);
1424                  osfree(new_order);
1425                  return;
1426               }
1427               break;
1428             default: /* avoid compiler warnings about unhandled enums */
1429               break;
1430            }
1431            if (fBad) {
1432               /* Not entirely happy with phrasing this... */
1433               /* TRANSLATORS: This is an error from the *DATA command.  It
1434                * means that a reading (which will appear where %s is isn't
1435                * valid as the list of readings has already included the same
1436                * reading, or an equivalent one (e.g. you can't have both
1437                * DEPTH and DEPTHCHANGE together). */
1438               compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Reading “%s” duplicates previous reading(s)*/77,
1439                                         buffer);
1440               osfree(style_name);
1441               osfree(new_order);
1442               return;
1443            }
1444            mUsed |= BIT(d); /* used to catch duplicates */
1445         }
1446      }
1447      if (k && new_order[k - 1] == IgnoreAll) {
1448         SVX_ASSERT(d == Newline);
1449         k--;
1450         d = IgnoreAllAndNewLine;
1451      }
1452      if (k >= kMac) {
1453         kMac = kMac * 2;
1454         new_order = osrealloc(new_order, kMac * sizeof(reading));
1455      }
1456      new_order[k++] = d;
1457   } while (d != End);
1458
1459   if (k >= 2 && new_order[k - 2] == Newline) {
1460      /* TRANSLATORS: error from:
1461       *
1462       * *data normal from to tape compass clino newline */
1463      compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*NEWLINE can’t be the last reading*/223);
1464      osfree(style_name);
1465      osfree(new_order);
1466      return;
1467   }
1468
1469   if (style == STYLE_NOSURVEY) {
1470      if (TSTBIT(mUsed, Station)) {
1471         if (k >= kMac) {
1472            kMac = kMac * 2;
1473            new_order = osrealloc(new_order, kMac * sizeof(reading));
1474         }
1475         new_order[k - 1] = Newline;
1476         new_order[k++] = End;
1477      }
1478   } else if (style == STYLE_PASSAGE) {
1479      /* Station doesn't mean "multiline" for STYLE_PASSAGE. */
1480   } else if (!TSTBIT(mUsed, Newline) && (m_multi & mUsed)) {
1481      /* TRANSLATORS: Error given by something like:
1482       *
1483       * *data normal station tape compass clino
1484       *
1485       * ("station" signifies interleaved data). */
1486      compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Interleaved readings, but no NEWLINE*/224);
1487      osfree(style_name);
1488      osfree(new_order);
1489      return;
1490   }
1491
1492#if 0
1493   printf("mUsed = 0x%x\n", mUsed);
1494#endif
1495
1496   /* Check the supplied readings form a sufficient set. */
1497   if (style != STYLE_PASSAGE) {
1498       if ((mUsed & (BIT(Fr) | BIT(To))) == (BIT(Fr) | BIT(To)))
1499           mUsed |= BIT(Station);
1500       else if (TSTBIT(mUsed, Station))
1501           mUsed |= BIT(Fr) | BIT(To);
1502   }
1503
1504   if (mUsed & (BIT(Comp) | BIT(BackComp)))
1505      mUsed |= BIT(Comp) | BIT(BackComp);
1506
1507   if (mUsed & (BIT(Clino) | BIT(BackClino)))
1508      mUsed |= BIT(Clino) | BIT(BackClino);
1509
1510   if ((mUsed & (BIT(FrDepth) | BIT(ToDepth))) == (BIT(FrDepth) | BIT(ToDepth)))
1511      mUsed |= BIT(Depth) | BIT(DepthChange);
1512   else if (mUsed & (BIT(Depth) | BIT(DepthChange)))
1513      mUsed |= BIT(FrDepth) | BIT(ToDepth) | BIT(Depth) | BIT(DepthChange);
1514
1515   if ((mUsed & (BIT(FrCount) | BIT(ToCount))) == (BIT(FrCount) | BIT(ToCount)))
1516      mUsed |= BIT(Count) | BIT(Tape) | BIT(BackTape);
1517   else if (mUsed & (BIT(Count) | BIT(Tape) | BIT(BackTape)))
1518      mUsed |= BIT(FrCount) | BIT(ToCount) | BIT(Count) | BIT(Tape) | BIT(BackTape);
1519
1520#if 0
1521   printf("mUsed = 0x%x, opt = 0x%x, mask = 0x%x\n", mUsed,
1522          mask_optional[style], mask[style]);
1523#endif
1524
1525   if (((mUsed &~ BIT(Newline)) | mask_optional[style]) != mask[style]) {
1526      /* Test should only fail with too few bits set, not too many */
1527      SVX_ASSERT((((mUsed &~ BIT(Newline)) | mask_optional[style])
1528              &~ mask[style]) == 0);
1529      /* TRANSLATORS: i.e. not enough readings for the style. */
1530      compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Too few readings for data style “%s”*/64, style_name);
1531      osfree(style_name);
1532      osfree(new_order);
1533      return;
1534   }
1535
1536   /* don't free default ordering or ordering used by parent */
1537   if (pcs->ordering != default_order &&
1538       !(pcs->next && pcs->next->ordering == pcs->ordering))
1539      osfree((reading*)pcs->ordering);
1540
1541   pcs->style = style;
1542   pcs->ordering = new_order;
1543
1544   osfree(style_name);
1545
1546reinit_style:
1547   if (style == STYLE_PASSAGE) {
1548      lrudlist * new_psg = osnew(lrudlist);
1549      new_psg->tube = NULL;
1550      new_psg->next = model;
1551      model = new_psg;
1552      next_lrud = &(new_psg->tube);
1553   }
1554}
1555
1556static void
1557cmd_units(void)
1558{
1559   int units, quantity;
1560   unsigned long qmask;
1561   unsigned long m; /* mask with bit x set to indicate quantity x specified */
1562   real factor;
1563   filepos fp;
1564
1565   qmask = get_qlist(BIT(Q_POS)|BIT(Q_PLUMB)|BIT(Q_LEVEL));
1566
1567   if (!qmask) return;
1568   if (qmask == BIT(Q_DEFAULT)) {
1569      default_units(pcs);
1570      return;
1571   }
1572
1573   get_pos(&fp);
1574   factor = read_numeric(fTrue);
1575   if (factor == 0.0) {
1576      set_pos(&fp);
1577      /* TRANSLATORS: error message given by "*units tape 0 feet" - it’s
1578       * meaningless to say your tape is marked in "0 feet" (but you might
1579       * measure distance by counting knots on a diving line, and tie them
1580       * every "2 feet"). */
1581      compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /**UNITS factor must be non-zero*/200);
1582      skipline();
1583      return;
1584   }
1585
1586   units = get_units(qmask, fTrue);
1587   if (units == UNITS_NULL) return;
1588   if (TSTBIT(qmask, Q_GRADIENT))
1589      pcs->f_clino_percent = (units == UNITS_PERCENT);
1590   if (TSTBIT(qmask, Q_BACKGRADIENT))
1591      pcs->f_backclino_percent = (units == UNITS_PERCENT);
1592
1593   if (factor == HUGE_REAL) {
1594      factor = factor_tab[units];
1595   } else {
1596      factor *= factor_tab[units];
1597   }
1598
1599   for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1)
1600      if (qmask & m) pcs->units[quantity] = factor;
1601}
1602
1603static void
1604cmd_calibrate(void)
1605{
1606   real sc, z;
1607   unsigned long qmask, m;
1608   int quantity;
1609   filepos fp;
1610
1611   qmask = get_qlist(BIT(Q_POS)|BIT(Q_PLUMB)|BIT(Q_LEVEL));
1612   if (!qmask) return; /* error already reported */
1613
1614   if (qmask == BIT(Q_DEFAULT)) {
1615      default_calib(pcs);
1616      return;
1617   }
1618
1619   if (((qmask & LEN_QMASK)) && ((qmask & ANG_QMASK))) {
1620      /* TRANSLATORS: e.g.
1621       *
1622       * *calibrate tape compass 1 1
1623       */
1624      compile_diagnostic(DIAG_ERR|DIAG_SKIP, /*Can’t calibrate angular and length quantities together*/227);
1625      return;
1626   }
1627
1628   z = read_numeric(fFalse);
1629   get_pos(&fp);
1630   sc = read_numeric(fTrue);
1631   if (sc == HUGE_REAL) {
1632      if (isalpha(ch)) {
1633         int units = get_units(qmask, fFalse);
1634         if (units == UNITS_NULL) {
1635            return;
1636         }
1637         z *= factor_tab[units];
1638         sc = read_numeric(fTrue);
1639         if (sc == HUGE_REAL) {
1640            sc = (real)1.0;
1641         } else {
1642            /* Adjustment applied is: (reading - z) * sc
1643             * We want: reading * sc - z
1644             * So divide z by sc so the applied adjustment does what we want.
1645             */
1646            z /= sc;
1647         }
1648      } else {
1649         sc = (real)1.0;
1650      }
1651   }
1652
1653   if (sc == HUGE_REAL) sc = (real)1.0;
1654   /* check for declination scale */
1655   if (TSTBIT(qmask, Q_DECLINATION) && sc != 1.0) {
1656      set_pos(&fp);
1657      /* TRANSLATORS: DECLINATION is a built-in keyword, so best not to
1658       * translate */
1659      compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Scale factor must be 1.0 for DECLINATION*/40);
1660      skipline();
1661      return;
1662   }
1663   if (sc == 0.0) {
1664      set_pos(&fp);
1665      /* TRANSLATORS: If the scale factor for an instrument is zero, then any
1666       * reading would be mapped to zero, which doesn't make sense. */
1667      compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Scale factor must be non-zero*/391);
1668      skipline();
1669      return;
1670   }
1671   for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1) {
1672      if (qmask & m) {
1673         pcs->z[quantity] = pcs->units[quantity] * z;
1674         pcs->sc[quantity] = sc;
1675      }
1676   }
1677}
1678
1679static void
1680cmd_declination(void)
1681{
1682    real v = read_numeric(fTrue);
1683    if (v == HUGE_REAL) {
1684        get_token_no_blanks();
1685        if (strcmp(ucbuffer, "AUTO") != 0) {
1686            compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_COL, /*Expected number or “AUTO”*/309);
1687            return;
1688        }
1689        /* *declination auto X Y Z */
1690        real x = read_numeric(fFalse);
1691        real y = read_numeric(fFalse);
1692        real z = read_numeric(fFalse);
1693        if (!pcs->proj) {
1694            compile_diagnostic(DIAG_ERR, /*Input coordinate system must be specified for “*DECLINATION AUTO”*/301);
1695            return;
1696        }
1697        if (!proj_wgs84) {
1698            proj_wgs84 = pj_init_plus("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs");
1699        }
1700        /* Convert to WGS84 lat long. */
1701        if (pj_is_latlong(pcs->proj)) {
1702            /* PROJ expects lat and long in radians. */
1703            x = rad(x);
1704            y = rad(y);
1705        }
1706        int r = pj_transform(pcs->proj, proj_wgs84, 1, 1, &x, &y, &z);
1707        if (r != 0) {
1708            compile_diagnostic(DIAG_ERR, /*Failed to convert coordinates: %s*/436, pj_strerrno(r));
1709            return;
1710        }
1711        pcs->z[Q_DECLINATION] = HUGE_REAL;
1712        pcs->dec_x = x;
1713        pcs->dec_y = y;
1714        pcs->dec_z = z;
1715        /* Invalidate cached declination. */
1716        pcs->declination = HUGE_REAL;
1717        {
1718#ifdef HAVE_PROJ_H
1719            PJ_COORD lp;
1720            lp.lp.lam = x;
1721            lp.lp.phi = y;
1722            PJ_FACTORS factors = proj_factors(proj_out, lp);
1723            pcs->convergence = factors.meridian_convergence;
1724#else
1725            projLP lp = { x, y };
1726            struct FACTORS factors;
1727            memset(&factors, 0, sizeof(factors));
1728            pj_factors(lp, proj_out, 0.0, &factors);
1729            pcs->convergence = factors.conv;
1730#endif
1731        }
1732    } else {
1733        /* *declination D UNITS */
1734        int units = get_units(BIT(Q_DECLINATION), fFalse);
1735        if (units == UNITS_NULL) {
1736            return;
1737        }
1738        pcs->z[Q_DECLINATION] = -v * factor_tab[units];
1739        pcs->convergence = 0;
1740    }
1741}
1742
1743#ifndef NO_DEPRECATED
1744static void
1745cmd_default(void)
1746{
1747   static const sztok defaulttab[] = {
1748      { "CALIBRATE", CMD_CALIBRATE },
1749      { "DATA",      CMD_DATA },
1750      { "UNITS",     CMD_UNITS },
1751      { NULL,        CMD_NULL }
1752   };
1753   static int default_depr_count = 0;
1754
1755   if (default_depr_count < 5) {
1756      /* TRANSLATORS: If you're unsure what "deprecated" means, see:
1757       * https://en.wikipedia.org/wiki/Deprecation */
1758      compile_diagnostic(DIAG_WARN|DIAG_COL, /**DEFAULT is deprecated - use *CALIBRATE/DATA/SD/UNITS with argument DEFAULT instead*/20);
1759      if (++default_depr_count == 5)
1760         compile_diagnostic(DIAG_WARN, /*Further uses of this deprecated feature will not be reported*/95);
1761   }
1762
1763   get_token();
1764   switch (match_tok(defaulttab, TABSIZE(defaulttab))) {
1765    case CMD_CALIBRATE:
1766      default_calib(pcs);
1767      break;
1768    case CMD_DATA:
1769      default_style(pcs);
1770      default_grade(pcs);
1771      break;
1772    case CMD_UNITS:
1773      default_units(pcs);
1774      break;
1775    default:
1776      compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Unknown setting “%s”*/41, buffer);
1777   }
1778}
1779#endif
1780
1781static void
1782cmd_include(void)
1783{
1784   char *pth, *fnm = NULL;
1785   int fnm_len;
1786#ifndef NO_DEPRECATED
1787   prefix *root_store;
1788#endif
1789   int ch_store;
1790
1791   pth = path_from_fnm(file.filename);
1792
1793   read_string(&fnm, &fnm_len);
1794
1795#ifndef NO_DEPRECATED
1796   /* Since *begin / *end nesting cannot cross file boundaries we only
1797    * need to preserve the prefix if the deprecated *prefix command
1798    * can be used */
1799   root_store = root;
1800   root = pcs->Prefix; /* Root for include file is current prefix */
1801#endif
1802   ch_store = ch;
1803
1804   data_file(pth, fnm);
1805
1806#ifndef NO_DEPRECATED
1807   root = root_store; /* and restore root */
1808#endif
1809   ch = ch_store;
1810
1811   s_free(&fnm);
1812   osfree(pth);
1813}
1814
1815static void
1816cmd_sd(void)
1817{
1818   real sd, variance;
1819   int units;
1820   unsigned long qmask, m;
1821   int quantity;
1822   qmask = get_qlist(BIT(Q_DECLINATION));
1823   if (!qmask) return; /* no quantities found - error already reported */
1824
1825   if (qmask == BIT(Q_DEFAULT)) {
1826      default_grade(pcs);
1827      return;
1828   }
1829   sd = read_numeric(fFalse);
1830   if (sd <= (real)0.0) {
1831      compile_diagnostic(DIAG_ERR|DIAG_SKIP|DIAG_COL, /*Standard deviation must be positive*/48);
1832      return;
1833   }
1834   units = get_units(qmask, fFalse);
1835   if (units == UNITS_NULL) return;
1836
1837   sd *= factor_tab[units];
1838   variance = sqrd(sd);
1839
1840   for (quantity = 0, m = BIT(quantity); m <= qmask; quantity++, m <<= 1)
1841      if (qmask & m) pcs->Var[quantity] = variance;
1842}
1843
1844static void
1845cmd_title(void)
1846{
1847   if (!fExplicitTitle && pcs->Prefix == root) {
1848       /* If we don't have an explicit title yet, and we're currently in the
1849        * root prefix, use this title explicitly. */
1850      fExplicitTitle = fTrue;
1851      read_string(&survey_title, &survey_title_len);
1852   } else {
1853      /* parse and throw away this title (but still check rest of line) */
1854      char *s = NULL;
1855      int len;
1856      read_string(&s, &len);
1857      s_free(&s);
1858   }
1859}
1860
1861static const sztok case_tab[] = {
1862     {"PRESERVE", OFF},
1863     {"TOLOWER",  LOWER},
1864     {"TOUPPER",  UPPER},
1865     {NULL,       -1}
1866};
1867
1868static void
1869cmd_case(void)
1870{
1871   int setting;
1872   get_token();
1873   setting = match_tok(case_tab, TABSIZE(case_tab));
1874   if (setting != -1) {
1875      pcs->Case = setting;
1876   } else {
1877      compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Found “%s”, expecting “PRESERVE”, “TOUPPER”, or “TOLOWER”*/10, buffer);
1878   }
1879}
1880
1881typedef enum {
1882    CS_NONE = -1,
1883    CS_CUSTOM,
1884    CS_EPSG,
1885    CS_ESRI,
1886    CS_EUR,
1887    CS_IJTSK,
1888    CS_JTSK,
1889    CS_LAT,
1890    CS_LOCAL,
1891    CS_LONG,
1892    CS_OSGB,
1893    CS_S_MERC,
1894    CS_UTM
1895} cs_class;
1896
1897static const sztok cs_tab[] = {
1898     {"CUSTOM", CS_CUSTOM},
1899     {"EPSG",   CS_EPSG},       /* EPSG:<number> */
1900     {"ESRI",   CS_ESRI},       /* ESRI:<number> */
1901     {"EUR",    CS_EUR},        /* EUR79Z30 */
1902     {"IJTSK",  CS_IJTSK},      /* IJTSK or IJTSK03 */
1903     {"JTSK",   CS_JTSK},       /* JTSK or JTSK03 */
1904     {"LAT",    CS_LAT},        /* LAT-LONG */
1905     {"LOCAL",  CS_LOCAL},
1906     {"LONG",   CS_LONG},       /* LONG-LAT */
1907     {"OSGB",   CS_OSGB},       /* OSGB:<H, N, O, S or T><A-Z except I> */
1908     {"S",      CS_S_MERC},     /* S-MERC */
1909     {"UTM",    CS_UTM},        /* UTM<zone><N or S or nothing> */
1910     {NULL,     CS_NONE}
1911};
1912
1913static void
1914cmd_cs(void)
1915{
1916   char * proj_str = NULL;
1917   int proj_str_len;
1918   cs_class cs;
1919   int cs_sub = INT_MIN;
1920   filepos fp;
1921   bool output = fFalse;
1922   enum { YES, NO, MAYBE } ok_for_output = YES;
1923   static bool had_cs = fFalse;
1924
1925   if (!had_cs) {
1926      had_cs = fTrue;
1927      if (first_fix_name) {
1928         compile_diagnostic_at(DIAG_ERR,
1929                               first_fix_filename, first_fix_line,
1930                               /*Station “%s” fixed before CS command first used*/442,
1931                               sprint_prefix(first_fix_name));
1932      }
1933   }
1934
1935   get_pos(&fp);
1936   /* Note get_token() only accepts letters - it'll stop at digits so "UTM12"
1937    * will give token "UTM". */
1938   get_token();
1939   if (strcmp(ucbuffer, "OUT") == 0) {
1940      output = fTrue;
1941      get_pos(&fp);
1942      get_token();
1943   }
1944   cs = match_tok(cs_tab, TABSIZE(cs_tab));
1945   switch (cs) {
1946      case CS_NONE:
1947         break;
1948      case CS_CUSTOM:
1949         ok_for_output = MAYBE;
1950         get_pos(&fp);
1951         read_string(&proj_str, &proj_str_len);
1952         cs_sub = 0;
1953         break;
1954      case CS_EPSG: case CS_ESRI:
1955         ok_for_output = MAYBE;
1956         if (ch == ':' && isdigit(nextch())) {
1957            unsigned n = read_uint();
1958            if (n < 1000000) {
1959               cs_sub = (int)n;
1960            }
1961         }
1962         break;
1963      case CS_EUR:
1964         if (isdigit(ch) &&
1965             read_uint() == 79 &&
1966             (ch == 'Z' || ch == 'z') &&
1967             isdigit(nextch()) &&
1968             read_uint() == 30) {
1969            cs_sub = 7930;
1970         }
1971         break;
1972      case CS_JTSK:
1973         ok_for_output = NO;
1974         /* FALLTHRU */
1975      case CS_IJTSK:
1976         if (ch == '0') {
1977            if (nextch() == '3') {
1978               nextch();
1979               cs_sub = 3;
1980            }
1981         } else {
1982            cs_sub = 0;
1983         }
1984         break;
1985      case CS_LAT: case CS_LONG:
1986         ok_for_output = NO;
1987         if (ch == '-') {
1988            nextch();
1989            get_token_no_blanks();
1990            cs_class cs2 = match_tok(cs_tab, TABSIZE(cs_tab));
1991            if ((cs ^ cs2) == (CS_LAT ^ CS_LONG)) {
1992                cs_sub = 0;
1993            }
1994         }
1995         break;
1996      case CS_LOCAL:
1997         cs_sub = 0;
1998         break;
1999      case CS_OSGB:
2000         if (ch == ':') {
2001            int uch1 = toupper(nextch());
2002            if (strchr("HNOST", uch1)) {
2003               int uch2 = toupper(nextch());
2004               if (uch2 >= 'A' && uch2 <= 'Z' && uch2 != 'I') {
2005                  int x, y;
2006                  nextch();
2007                  if (uch1 > 'I') --uch1;
2008                  uch1 -= 'A';
2009                  if (uch2 > 'I') --uch2;
2010                  uch2 -= 'A';
2011                  x = uch1 % 5;
2012                  y = uch1 / 5;
2013                  x = (x * 5) + uch2 % 5;
2014                  y = (y * 5) + uch2 / 5;
2015                  cs_sub = y * 25 + x;
2016               }
2017            }
2018         }
2019         break;
2020      case CS_S_MERC:
2021         if (ch == '-') {
2022            nextch();
2023            get_token_no_blanks();
2024            if (strcmp(ucbuffer, "MERC") == 0) {
2025               cs_sub = 0;
2026            }
2027         }
2028         break;
2029      case CS_UTM:
2030         if (isdigit(ch)) {
2031            unsigned n = read_uint();
2032            if (n >= 1 && n <= 60) {
2033               int uch = toupper(ch);
2034               cs_sub = (int)n;
2035               if (uch == 'S') {
2036                  nextch();
2037                  cs_sub = -cs_sub;
2038               } else if (uch == 'N') {
2039                  nextch();
2040               }
2041            }
2042         }
2043         break;
2044   }
2045   if (cs_sub == INT_MIN || isalnum(ch)) {
2046      set_pos(&fp);
2047      compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Unknown coordinate system*/434);
2048      skipline();
2049      return;
2050   }
2051   /* Actually handle the cs */
2052   switch (cs) {
2053      case CS_NONE:
2054         break;
2055      case CS_CUSTOM:
2056         /* proj_str already set */
2057         break;
2058      case CS_EPSG:
2059         proj_str = osmalloc(32);
2060         sprintf(proj_str, "+init=epsg:%d +no_defs", cs_sub);
2061         break;
2062      case CS_ESRI:
2063         proj_str = osmalloc(32);
2064         sprintf(proj_str, "+init=esri:%d +no_defs", cs_sub);
2065         break;
2066      case CS_EUR:
2067         proj_str = osstrdup("+proj=utm +zone=30 +ellps=intl +towgs84=-86,-98,-119,0,0,0,0 +no_defs");
2068         break;
2069      case CS_IJTSK:
2070         if (cs_sub == 0)
2071            proj_str = osstrdup("+proj=krovak +ellps=bessel +towgs84=570.8285,85.6769,462.842,4.9984,1.5867,5.2611,3.5623 +no_defs");
2072         else
2073            proj_str = osstrdup("+proj=krovak +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +no_defs");
2074         break;
2075      case CS_JTSK:
2076         if (cs_sub == 0)
2077            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");
2078         else
2079            proj_str = osstrdup("+proj=krovak +czech +ellps=bessel +towgs84=485.021,169.465,483.839,7.786342,4.397554,4.102655,0 +no_defs");
2080         break;
2081      case CS_LAT:
2082         /* FIXME: Requires PROJ >= 4.8.0 for +axis, and the SDs will be
2083          * misapplied, so we may want to swap ourselves.  Also, while
2084          * therion supports lat-long, I'm not totally convinced that it is
2085          * sensible to do so - people often say "lat-long", but probably
2086          * don't think that that's actually "Northing, Easting".  This
2087          * seems like it'll result in people accidentally getting X and Y
2088          * swapped in their fixed points...
2089          */
2090#if 0
2091         proj_str = osstrdup("+proj=longlat +ellps=WGS84 +datum=WGS84 +axis=neu +no_defs");
2092#endif
2093         break;
2094      case CS_LOCAL:
2095         /* FIXME: Is it useful to be able to explicitly specify this? */
2096         break;
2097      case CS_LONG:
2098         proj_str = osstrdup("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs");
2099         break;
2100      case CS_OSGB: {
2101         int x = 14 - (cs_sub % 25);
2102         int y = (cs_sub / 25) - 20;
2103         proj_str = osmalloc(160);
2104         sprintf(proj_str, "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=%d +y_0=%d +ellps=airy +datum=OSGB36 +units=m +no_defs", x * 100000, y * 100000);
2105         break;
2106      }
2107      case CS_S_MERC:
2108         proj_str = osstrdup("+proj=merc +lat_ts=0 +lon_0=0 +k=1 +x_0=0 +y_0=0 +a=6378137 +b=6378137 +units=m +nadgrids=@null +no_defs");
2109         break;
2110      case CS_UTM:
2111         proj_str = osmalloc(74);
2112         if (cs_sub > 0) {
2113            sprintf(proj_str, "+proj=utm +ellps=WGS84 +datum=WGS84 +units=m +zone=%d +no_defs", cs_sub);
2114         } else {
2115            sprintf(proj_str, "+proj=utm +ellps=WGS84 +datum=WGS84 +units=m +zone=%d +south +no_defs", -cs_sub);
2116         }
2117         break;
2118   }
2119
2120   if (!proj_str) {
2121      /* printf("CS %d:%d\n", (int)cs, cs_sub); */
2122      set_pos(&fp);
2123      compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Unknown coordinate system*/434);
2124      skipline();
2125      return;
2126   }
2127
2128   if (output) {
2129      if (ok_for_output == NO) {
2130         set_pos(&fp);
2131         compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Coordinate system unsuitable for output*/435);
2132         skipline();
2133         return;
2134      }
2135
2136      /* If the output projection is already set, we still need to create the
2137       * projection object for a custom projection, so we can report errors.
2138       * But if the string is identical, we know it's valid.
2139       */
2140      if (!proj_out ||
2141          (ok_for_output == MAYBE && strcmp(proj_str, proj_str_out) != 0)) {
2142         projPJ pj = pj_init_plus(proj_str);
2143         if (!pj) {
2144            set_pos(&fp);
2145            compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Invalid coordinate system: %s*/443,
2146                               pj_strerrno(pj_errno));
2147            skipline();
2148            return;
2149         }
2150         if (ok_for_output == MAYBE && pj_is_latlong(pj)) {
2151            set_pos(&fp);
2152            compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Coordinate system unsuitable for output*/435);
2153            skipline();
2154            return;
2155         }
2156         if (proj_out) {
2157            pj_free(pj);
2158            osfree(proj_str);
2159         } else {
2160            proj_out = pj;
2161            proj_str_out = proj_str;
2162         }
2163      }
2164   } else {
2165      projPJ pj;
2166      if (proj_str_out && strcmp(proj_str, proj_str_out) == 0) {
2167         /* Same as the current output projection. */
2168         pj = proj_out;
2169      } else {
2170         pj = pj_init_plus(proj_str);
2171         if (!pj) {
2172            set_pos(&fp);
2173            compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*Invalid coordinate system: %s*/443,
2174                               pj_strerrno(pj_errno));
2175            skipline();
2176            return;
2177         }
2178      }
2179
2180      /* Free proj if not used by parent, or as the output projection. */
2181      settings * p = pcs;
2182      if (p->proj && (!p->next || p->proj != p->next->proj))
2183         if (p->proj != proj_out)
2184            pj_free(p->proj);
2185      p->proj = pj;
2186   }
2187}
2188
2189static const sztok infer_tab[] = {
2190     { "EQUATES",       INFER_EQUATES },
2191     { "EXPORTS",       INFER_EXPORTS },
2192     { "PLUMBS",        INFER_PLUMBS },
2193#if 0 /* FIXME */
2194     { "SUBSURVEYS",    INFER_SUBSURVEYS },
2195#endif
2196     { NULL,            INFER_NULL }
2197};
2198
2199static const sztok onoff_tab[] = {
2200     { "OFF", 0 },
2201     { "ON",  1 },
2202     { NULL, -1 }
2203};
2204
2205static void
2206cmd_infer(void)
2207{
2208   infer_what setting;
2209   int on;
2210   get_token();
2211   setting = match_tok(infer_tab, TABSIZE(infer_tab));
2212   if (setting == INFER_NULL) {
2213      compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Found “%s”, expecting “EQUATES”, “EXPORTS”, or “PLUMBS”*/31, buffer);
2214      return;
2215   }
2216   get_token();
2217   on = match_tok(onoff_tab, TABSIZE(onoff_tab));
2218   if (on == -1) {
2219      compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Found “%s”, expecting “ON” or “OFF”*/32, buffer);
2220      return;
2221   }
2222
2223   if (on) {
2224      pcs->infer |= BIT(setting);
2225      if (setting == INFER_EXPORTS) fExportUsed = fTrue;
2226   } else {
2227      pcs->infer &= ~BIT(setting);
2228   }
2229}
2230
2231static void
2232cmd_truncate(void)
2233{
2234   unsigned int truncate_at = 0; /* default is no truncation */
2235   filepos fp;
2236
2237   get_pos(&fp);
2238
2239   get_token();
2240   if (strcmp(ucbuffer, "OFF") != 0) {
2241      if (*ucbuffer) set_pos(&fp);
2242      truncate_at = read_uint();
2243   }
2244   /* for backward compatibility, "*truncate 0" means "*truncate off" */
2245   pcs->Truncate = (truncate_at == 0) ? INT_MAX : truncate_at;
2246}
2247
2248static void
2249cmd_ref(void)
2250{
2251   /* Just syntax check for now. */
2252   char *ref = NULL;
2253   int ref_len;
2254   read_string(&ref, &ref_len);
2255   s_free(&ref);
2256}
2257
2258static void
2259cmd_require(void)
2260{
2261   const unsigned int version[] = {COMMAVERSION};
2262   const unsigned int *ver = version;
2263   filepos fp;
2264
2265   skipblanks();
2266   get_pos(&fp);
2267   while (1) {
2268      int diff = *ver++ - read_uint();
2269      if (diff > 0) break;
2270      if (diff < 0) {
2271         size_t i, len;
2272         char *v;
2273         filepos fp_tmp;
2274
2275         /* find end of version number */
2276         while (isdigit(ch) || ch == '.') nextch();
2277         get_pos(&fp_tmp);
2278         len = (size_t)(fp_tmp.offset - fp.offset);
2279         v = osmalloc(len + 1);
2280         set_pos(&fp);
2281         for (i = 0; i < len; i++) {
2282            v[i] = ch;
2283            nextch();
2284         }
2285         v[i] = '\0';
2286         /* TRANSLATORS: Feel free to translate as "or newer" instead of "or
2287          * greater" if that gives a more natural translation.  It's
2288          * technically not quite right when there are parallel active release
2289          * series (e.g. Survex 1.0.40 was released *after* 1.2.0), but this
2290          * seems unlikely to confuse users.  "Survex" is the name of the
2291          * software, so should not be translated.
2292          *
2293          * Here "survey" is a "cave map" rather than list of questions - it should be
2294          * translated to the terminology that cavers using the language would use.
2295          */
2296         fatalerror_in_file(file.filename, file.line, /*Survex version %s or greater required to process this survey data.*/2, v);
2297      }
2298      if (ch != '.') break;
2299      nextch();
2300      if (!isdigit(ch) || ver == version + sizeof(version) / sizeof(*version))
2301         break;
2302   }
2303   /* skip rest of version number */
2304   while (isdigit(ch) || ch == '.') nextch();
2305}
2306
2307/* allocate new meta_data if need be */
2308void
2309copy_on_write_meta(settings *s)
2310{
2311   if (!s->meta || s->meta->ref_count != 0) {
2312       meta_data * meta_new = osnew(meta_data);
2313       if (!s->meta) {
2314           meta_new->days1 = meta_new->days2 = -1;
2315       } else {
2316           *meta_new = *(s->meta);
2317       }
2318       meta_new->ref_count = 0;
2319       s->meta = meta_new;
2320   }
2321}
2322
2323static void
2324cmd_date(void)
2325{
2326    int year, month, day;
2327    int days1, days2;
2328    bool implicit_range = fFalse;
2329    filepos fp, fp2;
2330
2331    get_pos(&fp);
2332    read_date(&year, &month, &day);
2333    days1 = days_since_1900(year, month ? month : 1, day ? day : 1);
2334
2335    if (days1 > current_days_since_1900) {
2336        set_pos(&fp);
2337        compile_diagnostic(DIAG_WARN|DIAG_DATE, /*Date is in the future!*/80);
2338    }
2339
2340    skipblanks();
2341    if (ch == '-') {
2342        nextch();
2343        get_pos(&fp2);
2344        read_date(&year, &month, &day);
2345    } else {
2346        if (month && day) {
2347            days2 = days1;
2348            goto read;
2349        }
2350        implicit_range = fTrue;
2351    }
2352
2353    if (month == 0) month = 12;
2354    if (day == 0) day = last_day(year, month);
2355    days2 = days_since_1900(year, month, day);
2356
2357    if (!implicit_range && days2 > current_days_since_1900) {
2358        set_pos(&fp2);
2359        compile_diagnostic(DIAG_WARN|DIAG_DATE, /*Date is in the future!*/80);
2360    }
2361
2362    if (days2 < days1) {
2363        set_pos(&fp);
2364        compile_diagnostic(DIAG_ERR|DIAG_TOKEN, /*End of date range is before the start*/81);
2365        int tmp = days1;
2366        days1 = days2;
2367        days2 = tmp;
2368    }
2369
2370read:
2371    if (!pcs->meta || pcs->meta->days1 != days1 || pcs->meta->days2 != days2) {
2372        copy_on_write_meta(pcs);
2373        pcs->meta->days1 = days1;
2374        pcs->meta->days2 = days2;
2375        /* Invalidate cached declination. */
2376        pcs->declination = HUGE_REAL;
2377    }
2378}
2379
2380typedef void (*cmd_fn)(void);
2381
2382static const cmd_fn cmd_funcs[] = {
2383   cmd_alias,
2384   cmd_begin,
2385   cmd_calibrate,
2386   cmd_case,
2387   skipline, /*cmd_copyright,*/
2388   cmd_cs,
2389   cmd_data,
2390   cmd_date,
2391   cmd_declination,
2392#ifndef NO_DEPRECATED
2393   cmd_default,
2394#endif
2395   cmd_end,
2396   cmd_entrance,
2397   cmd_equate,
2398   cmd_export,
2399   cmd_fix,
2400   cmd_flags,
2401   cmd_include,
2402   cmd_infer,
2403   skipline, /*cmd_instrument,*/
2404#ifndef NO_DEPRECATED
2405   cmd_prefix,
2406#endif
2407   cmd_ref,
2408   cmd_require,
2409   cmd_sd,
2410   cmd_set,
2411   solve_network,
2412   skipline, /*cmd_team,*/
2413   cmd_title,
2414   cmd_truncate,
2415   cmd_units
2416};
2417
2418extern void
2419handle_command(void)
2420{
2421   int cmdtok;
2422   get_token();
2423   cmdtok = match_tok(cmd_tab, TABSIZE(cmd_tab));
2424
2425   if (cmdtok < 0 || cmdtok >= (int)(sizeof(cmd_funcs) / sizeof(cmd_fn))) {
2426      compile_diagnostic(DIAG_ERR|DIAG_BUF|DIAG_SKIP, /*Unknown command “%s”*/12, buffer);
2427      return;
2428   }
2429
2430   switch (cmdtok) {
2431    case CMD_EXPORT:
2432      if (!f_export_ok)
2433         /* TRANSLATORS: The *EXPORT command is only valid just after *BEGIN
2434          * <SURVEY>, so this would generate this error:
2435          *
2436          * *begin fred
2437          * 1 2 1.23 045 -6
2438          * *export 2
2439          * *end fred */
2440         compile_diagnostic(DIAG_ERR, /**EXPORT must immediately follow “*BEGIN <SURVEY>”*/57);
2441      break;
2442    case CMD_ALIAS:
2443    case CMD_CALIBRATE:
2444    case CMD_CASE:
2445    case CMD_COPYRIGHT:
2446    case CMD_CS:
2447    case CMD_DATA:
2448    case CMD_DATE:
2449    case CMD_DECLINATION:
2450    case CMD_DEFAULT:
2451    case CMD_FLAGS:
2452    case CMD_INFER:
2453    case CMD_INSTRUMENT:
2454    case CMD_REF:
2455    case CMD_REQUIRE:
2456    case CMD_SD:
2457    case CMD_SET:
2458    case CMD_TEAM:
2459    case CMD_TITLE:
2460    case CMD_TRUNCATE:
2461    case CMD_UNITS:
2462      /* These can occur between *begin and *export */
2463      break;
2464    default:
2465      /* NB: additional handling for "*begin <survey>" in cmd_begin */
2466      f_export_ok = fFalse;
2467      break;
2468   }
2469
2470   cmd_funcs[cmdtok]();
2471}
Note: See TracBrowser for help on using the repository browser.