source: git/src/commands.c @ 36efb03

RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-data
Last change on this file since 36efb03 was 36efb03, checked in by Olly Betts <olly@…>, 9 years ago

src/: Add more TRANSLATORS comments.

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