1 /*============================================================================
2   WCSLIB 7.7 - an implementation of the FITS WCS standard.
3   Copyright (C) 1995-2021, Mark Calabretta
4 
5   This file is part of WCSLIB.
6 
7   WCSLIB is free software: you can redistribute it and/or modify it under the
8   terms of the GNU Lesser General Public License as published by the Free
9   Software Foundation, either version 3 of the License, or (at your option)
10   any later version.
11 
12   WCSLIB is distributed in the hope that it will be useful, but WITHOUT ANY
13   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14   FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
15   more details.
16 
17   You should have received a copy of the GNU Lesser General Public License
18   along with WCSLIB.  If not, see http://www.gnu.org/licenses.
19 
20   Author: Mark Calabretta, Australia Telescope National Facility, CSIRO.
21   http://www.atnf.csiro.au/people/Mark.Calabretta
22   $Id: fitshdr.l,v 7.7 2021/07/12 06:36:49 mcalabre Exp $
23 *=============================================================================
24 *
25 * fitshdr.l is a Flex description file containing a lexical scanner
26 * definition for extracting keywords and keyvalues from a FITS header.
27 *
28 * It requires Flex v2.5.4 or later.
29 *
30 * Refer to fitshdr.h for a description of the user interface and operating
31 * notes.
32 *
33 *===========================================================================*/
34 
35 /* Options. */
36 %option full
37 %option never-interactive
38 %option noinput
39 %option nounput
40 %option noyywrap
41 %option outfile="fitshdr.c"
42 %option prefix="fitshdr"
43 %option reentrant
44 %option extra-type="struct fitshdr_extra *"
45 
46 /* Keywords. */
47 KEYCHR	[-_A-Z0-9]
48 KW1	{KEYCHR}{1}" "{7}
49 KW2	{KEYCHR}{2}" "{6}
50 KW3	{KEYCHR}{3}" "{5}
51 KW4	{KEYCHR}{4}" "{4}
52 KW5	{KEYCHR}{5}" "{3}
53 KW6	{KEYCHR}{6}" "{2}
54 KW7	{KEYCHR}{7}" "{1}
55 KW8	{KEYCHR}{8}
56 KEYWORD	({KW1}|{KW2}|{KW3}|{KW4}|{KW5}|{KW6}|{KW7}|{KW8})
57 
58 /* Keyvalue data types. */
59 LOGICAL	[TF]
60 INT32	[+-]?0*[0-9]{1,9}
61 INT64	[+-]?0*[0-9]{10,18}
62 INTVL	[+-]?0*[0-9]{19,}
63 INTEGER	[+-]?[0-9]+
64 FLOAT	[+-]?([0-9]+\.?[0-9]*|\.[0-9]+)([eEdD][+-]?[0-9]+)?
65 ICOMPLX	\(" "*{INTEGER}" "*," "*{INTEGER}" "*\)
66 FCOMPLX	\(" "*{FLOAT}" "*," "*{FLOAT}" "*\)
67 STRING	'([^']|'')*'
68 
69 /* Characters forming standard unit strings (jwBIQX are not used). */
70 UNITSTR \[[-+*/^(). 0-9a-zA-Z]+\]
71 
72 /* Exclusive start states. */
73 %x VALUE INLINE UNITS COMMENT ERROR FLUSH
74 
75 %{
76 #include <math.h>
77 #include <limits.h>
78 #include <setjmp.h>
79 #include <stdlib.h>
80 #include <string.h>
81 
82 #include "fitshdr.h"
83 #include "wcsutil.h"
84 
85 // User data associated with yyscanner.
86 struct fitshdr_extra {
87   // Values passed to YY_INPUT.
88   const char *hdr;
89   int  nkeyrec;
90 
91   // Used in preempting the call to exit() by yy_fatal_error().
92   jmp_buf abort_jmp_env;
93 };
94 
95 #define YY_DECL int fitshdr_scanner(const char header[], int nkeyrec, \
96   int nkeyids, struct fitskeyid keyids[], int *nreject, \
97   struct fitskey **keys, yyscan_t yyscanner)
98 
99 #define YY_INPUT(inbuff, count, bufsize) \
100 	{ \
101 	  if (yyextra->nkeyrec) { \
102 	    strncpy(inbuff, yyextra->hdr, 80); \
103 	    inbuff[80] = '\n'; \
104 	    yyextra->hdr += 80; \
105 	    yyextra->nkeyrec--; \
106 	    count = 81; \
107 	  } else { \
108 	    count = YY_NULL; \
109 	  } \
110 	}
111 
112 // Preempt the call to exit() by yy_fatal_error().
113 #define exit(status) longjmp(yyextra->abort_jmp_env, status);
114 
115 // Internal helper functions.
116 static YY_DECL;
117 static void nullfill(char cptr[], int len);
118 
119 // Map status return value to message.
120 const char *fitshdr_errmsg[] = {
121    "Success",
122    "Null fitskey pointer-pointer passed",
123    "Memory allocation failed",
124    "Fatal error returned by Flex parser"};
125 
126 %}
127 
128 %%
129 	char ctmp[72];
130 
131 	if (keys == 0x0) {
132 	  return FITSHDRERR_NULL_POINTER;
133 	}
134 
135 	// Allocate memory for the required number of fitskey structs.
136 	// Recall that calloc() initializes allocated memory to zero.
137 	struct fitskey *kptr;
138 	if (!(kptr = *keys = calloc(nkeyrec, sizeof(struct fitskey)))) {
139 	  return FITSHDRERR_MEMORY;
140 	}
141 
142 	// Initialize returned values.
143 	*nreject = 0;
144 
145 	// Initialize keyids[].
146 	struct fitskeyid *iptr = keyids;
147 	for (int j = 0; j < nkeyids; j++, iptr++) {
148 	  iptr->count  = 0;
149 	  iptr->idx[0] = -1;
150 	  iptr->idx[1] = -1;
151 	}
152 
153 	int keyno = 0;
154 
155 	int blank = 0;
156 	int continuation = 0;
157 	int end = 0;
158 
159 	#ifdef WCSLIB_INT64
160 	  #define asString(S) stringize(S)
161 	  #define stringize(S) #S
162 
163 	  const char *int64fmt;
164 	  if (strcmp(asString(WCSLIB_INT64), "long long int") == 0) {
165 	    int64fmt = "%lld";
166 	  } else if (strcmp(asString(WCSLIB_INT64), "long int") == 0) {
167 	    int64fmt = "%ld";
168 	  } else if (strcmp(asString(WCSLIB_INT64), "int") == 0) {
169 	    int64fmt = "%d";
170 	  } else {
171 	    return FITSHDRERR_DATA_TYPE;
172 	  }
173 	#endif
174 
175 	// User data associated with yyscanner.
176 	yyextra->hdr = header;
177 	yyextra->nkeyrec = nkeyrec;
178 
179 	// Return here via longjmp() invoked by yy_fatal_error().
180 	if (setjmp(yyextra->abort_jmp_env)) {
181 	  return FITSHDRERR_FLEX_PARSER;
182 	}
183 
184 	BEGIN(INITIAL);
185 
186 ^" "{80} {
187 	  // A completely blank keyrecord.
188 	  strncpy(kptr->keyword, yytext, 8);
189 	  yyless(0);
190 	  blank = 1;
191 	  BEGIN(COMMENT);
192 	}
193 
194 ^(COMMENT|HISTORY|" "{8}) {
195 	  strncpy(kptr->keyword, yytext, 8);
196 	  BEGIN(COMMENT);
197 	}
198 
199 ^END" "{77} {
200 	  strncpy(kptr->keyword, yytext, 8);
201 	  end = 1;
202 	  BEGIN(FLUSH);
203 	}
204 
205 ^END" "{5}=" "+ {
206 	  // Illegal END keyrecord.
207 	  strncpy(kptr->keyword, yytext, 8);
208 	  kptr->status |= FITSHDR_KEYREC;
209 	  BEGIN(VALUE);
210 	}
211 
212 ^END" "{5} {
213 	  // Illegal END keyrecord.
214 	  strncpy(kptr->keyword, yytext, 8);
215 	  kptr->status |= FITSHDR_KEYREC;
216 	  BEGIN(COMMENT);
217 	}
218 
219 ^{KEYWORD}=" "+ {
220 	  strncpy(kptr->keyword, yytext, 8);
221 	  BEGIN(VALUE);
222 	}
223 
224 ^CONTINUE"  "+{STRING} {
225 	  // Continued string keyvalue.
226 	  strncpy(kptr->keyword, yytext, 8);
227 
228 	  if (keyno > 0 && (kptr-1)->type%10 == 8) {
229 	    // Put back the string keyvalue.
230 	    int k;
231 	    for (k = 10; yytext[k] != '\''; k++);
232 	    yyless(k);
233 	    continuation = 1;
234 	    BEGIN(VALUE);
235 
236 	  } else {
237 	    // Not a valid continuation.
238 	    yyless(8);
239 	    BEGIN(COMMENT);
240 	  }
241 	}
242 
243 ^{KEYWORD} {
244 	  // Keyword without value.
245 	  strncpy(kptr->keyword, yytext, 8);
246 	  BEGIN(COMMENT);
247 	}
248 
249 ^.{8}=" "+ {
250 	  // Illegal keyword, carry on regardless.
251 	  strncpy(kptr->keyword, yytext, 8);
252 	  kptr->status |= FITSHDR_KEYWORD;
253 	  BEGIN(VALUE);
254 	}
255 
256 ^.{8}	{
257 	  // Illegal keyword, carry on regardless.
258 	  strncpy(kptr->keyword, yytext, 8);
259 	  kptr->status |= FITSHDR_KEYWORD;
260 	  BEGIN(COMMENT);
261 	}
262 
263 <VALUE>" "*/\/ {
264 	  // Null keyvalue.
265 	  BEGIN(INLINE);
266 	}
267 
268 <VALUE>{LOGICAL} {
269 	  // Logical keyvalue.
270 	  kptr->type = 1;
271 	  kptr->keyvalue.i = (*yytext == 'T');
272 	  BEGIN(INLINE);
273 	}
274 
275 <VALUE>{INT32} {
276 	  // 32-bit signed integer keyvalue.
277 	  kptr->type = 2;
278 	  if (sscanf(yytext, "%d", &(kptr->keyvalue.i)) < 1) {
279 	    kptr->status |= FITSHDR_KEYVALUE;
280 	    BEGIN(ERROR);
281 	  }
282 
283 	  BEGIN(INLINE);
284 	}
285 
286 <VALUE>{INT64} {
287 	  // 64-bit signed integer keyvalue (up to 18 digits).
288 	  double dtmp;
289 	  if (wcsutil_str2double(yytext, &dtmp)) {
290 	    kptr->status |= FITSHDR_KEYVALUE;
291 	    BEGIN(ERROR);
292 
293 	  } else if (INT_MIN <= dtmp && dtmp <= INT_MAX) {
294 	    // Can be accomodated as a 32-bit signed integer.
295 	    kptr->type = 2;
296 	    if (sscanf(yytext, "%d", &(kptr->keyvalue.i)) < 1) {
297 	      kptr->status |= FITSHDR_KEYVALUE;
298 	      BEGIN(ERROR);
299 	    }
300 
301 	  } else {
302 	    // 64-bit signed integer.
303 	    kptr->type = 3;
304 	    #ifdef WCSLIB_INT64
305 	      // Native 64-bit integer is available.
306 	      if (sscanf(yytext, int64fmt, &(kptr->keyvalue.k)) < 1) {
307 	        kptr->status |= FITSHDR_KEYVALUE;
308 	        BEGIN(ERROR);
309 	      }
310 	    #else
311 	      // 64-bit integer (up to 18 digits) implemented as int[3].
312 	      kptr->keyvalue.k[2] = 0;
313 
314 	      sprintf(ctmp, "%%%dd%%9d", yyleng-9);
315 	      if (sscanf(yytext, ctmp, kptr->keyvalue.k+1,
316 	                 kptr->keyvalue.k) < 1) {
317 	        kptr->status |= FITSHDR_KEYVALUE;
318 	        BEGIN(ERROR);
319 	      } else if (*yytext == '-') {
320 	        kptr->keyvalue.k[0] *= -1;
321 	      }
322 	    #endif
323 	  }
324 
325 	  BEGIN(INLINE);
326 	}
327 
328 <VALUE>{INTVL} {
329 	  // Very long integer keyvalue (and 19-digit int64).
330 	  kptr->type = 4;
331 	  strcpy(ctmp, yytext);
332 	  int j, k = yyleng;
333 	  for (j = 0; j < 8; j++) {
334 	    // Read it backwards.
335 	    k -= 9;
336 	    if (k < 0) k = 0;
337 	    if (sscanf(ctmp+k, "%d", kptr->keyvalue.l+j) < 1) {
338 	      kptr->status |= FITSHDR_KEYVALUE;
339 	      BEGIN(ERROR);
340 	    }
341 	    if (*yytext == '-') {
342 	      kptr->keyvalue.l[j] = -abs(kptr->keyvalue.l[j]);
343 	    }
344 
345 	    if (k == 0) break;
346 	    ctmp[k] = '\0';
347 	  }
348 
349 	  // Can it be accomodated as a 64-bit signed integer?
350 	  if (j == 2 && abs(kptr->keyvalue.l[2]) <=  9 &&
351 	                abs(kptr->keyvalue.l[1]) <=  223372036 &&
352 	                    kptr->keyvalue.l[0]  <=  854775807 &&
353 	                    kptr->keyvalue.l[0]  >= -854775808) {
354 	    kptr->type = 3;
355 
356 	    #ifdef WCSLIB_INT64
357 	      // Native 64-bit integer is available.
358 	      kptr->keyvalue.l[2] = 0;
359 	      if (sscanf(yytext, int64fmt, &(kptr->keyvalue.k)) < 1) {
360 	        kptr->status |= FITSHDR_KEYVALUE;
361 	        BEGIN(ERROR);
362 	      }
363 	    #endif
364 	  }
365 
366 	  BEGIN(INLINE);
367 	}
368 
369 <VALUE>{FLOAT} {
370 	  // Float keyvalue.
371 	  kptr->type = 5;
372 	  if (wcsutil_str2double(yytext, &(kptr->keyvalue.f))) {
373 	    kptr->status |= FITSHDR_KEYVALUE;
374 	    BEGIN(ERROR);
375 	  }
376 
377 	  BEGIN(INLINE);
378 	}
379 
380 <VALUE>{ICOMPLX} {
381 	  // Integer complex keyvalue.
382 	  kptr->type = 6;
383 	  if (sscanf(yytext, "(%lf,%lf)", kptr->keyvalue.c,
384 	      kptr->keyvalue.c+1) < 2) {
385 	    kptr->status |= FITSHDR_KEYVALUE;
386 	    BEGIN(ERROR);
387 	  }
388 
389 	  BEGIN(INLINE);
390 	}
391 
392 <VALUE>{FCOMPLX} {
393 	  // Floating point complex keyvalue.
394 	  kptr->type = 7;
395 
396 	  char *cptr;
397 	  int k;
398 	  for (cptr = ctmp, k = 1; yytext[k] != ','; cptr++, k++) {
399 	    *cptr = yytext[k];
400 	  }
401 	  *cptr = '\0';
402 
403 	  if (wcsutil_str2double(ctmp, kptr->keyvalue.c)) {
404 	    kptr->status |= FITSHDR_KEYVALUE;
405 	    BEGIN(ERROR);
406 	  }
407 
408 	  for (cptr = ctmp, k++; yytext[k] != ')'; cptr++, k++) {
409 	    *cptr = yytext[k];
410 	  }
411 	  *cptr = '\0';
412 
413 	  if (wcsutil_str2double(ctmp, kptr->keyvalue.c+1)) {
414 	    kptr->status |= FITSHDR_KEYVALUE;
415 	    BEGIN(ERROR);
416 	  }
417 
418 	  BEGIN(INLINE);
419 	}
420 
421 <VALUE>{STRING} {
422 	  // String keyvalue.
423 	  kptr->type = 8;
424 	  char *cptr = kptr->keyvalue.s;
425 	  strcpy(cptr, yytext+1);
426 
427 	  // Squeeze out repeated quotes.
428 	  int k = 0;
429 	  for (int j = 0; j < 72; j++) {
430 	    if (k < j) {
431 	      cptr[k] = cptr[j];
432 	    }
433 
434 	    if (cptr[j] == '\0') {
435 	      if (k) cptr[k-1] = '\0';
436 	      break;
437 	    } else if (cptr[j] == '\'' && cptr[j+1] == '\'') {
438 	      j++;
439 	    }
440 
441 	    k++;
442 	  }
443 
444 	  if (*cptr) {
445 	    // Retain the initial blank in all-blank strings.
446 	    nullfill(cptr+1, 71);
447 	  } else {
448 	    nullfill(cptr, 72);
449 	  }
450 
451 	  BEGIN(INLINE);
452 	}
453 
454 <VALUE>. {
455 	  kptr->status |= FITSHDR_KEYVALUE;
456 	  BEGIN(ERROR);
457 	}
458 
459 <INLINE>" "*$ {
460 	  BEGIN(FLUSH);
461 	}
462 
463 <INLINE>" "*\/" "*$ {
464 	  BEGIN(FLUSH);
465 	}
466 
467 <INLINE>" "*\/" "* {
468 	  BEGIN(UNITS);
469 	}
470 
471 <INLINE>" " {
472 	  kptr->status |= FITSHDR_COMMENT;
473 	  BEGIN(ERROR);
474 	}
475 
476 <INLINE>. {
477 	  // Keyvalue parsing must now also be suspect.
478 	  kptr->status |= FITSHDR_COMMENT;
479 	  kptr->type = 0;
480 	  BEGIN(ERROR);
481 	}
482 
483 <UNITS>{UNITSTR} {
484 	  kptr->ulen = yyleng;
485 	  yymore();
486 	  BEGIN(COMMENT);
487 	}
488 
489 <UNITS>. {
490 	  yymore();
491 	  BEGIN(COMMENT);
492 	}
493 
494 <COMMENT>.* {
495 	  strcpy(kptr->comment, yytext);
496 	  nullfill(kptr->comment, 84);
497 	  BEGIN(FLUSH);
498 	}
499 
500 <ERROR>.* {
501 	  if (!continuation) kptr->type = -abs(kptr->type);
502 
503 	  sprintf(kptr->comment, "%.80s", yyextra->hdr-80);
504 	  kptr->comment[80] = '\0';
505 	  nullfill(kptr->comment+80, 4);
506 
507 	  BEGIN(FLUSH);
508 	}
509 
510 <FLUSH>.*\n {
511 	  // Discard the rest of the input line.
512 	  kptr->keyno = ++keyno;
513 
514 	  // Null-fill the keyword.
515 	  kptr->keyword[8] = '\0';
516 	  nullfill(kptr->keyword, 12);
517 
518 	  // Do indexing.
519 	  iptr = keyids;
520 	  kptr->keyid = -1;
521 	  for (int j = 0; j < nkeyids; j++, iptr++) {
522 	    int k;
523 	    char *cptr = iptr->name;
524 	    cptr[8] = '\0';
525 	    nullfill(cptr, 12);
526 	    for (k = 0; k < 8; k++, cptr++) {
527 	      if (*cptr != '.' && *cptr != kptr->keyword[k]) break;
528 	    }
529 
530 	    if (k == 8) {
531 	      // Found a match.
532 	      iptr->count++;
533 	      if (iptr->idx[0] == -1) {
534 	        iptr->idx[0] = keyno-1;
535 	      } else {
536 	        iptr->idx[1] = keyno-1;
537 	      }
538 
539 	      kptr->keyno = -abs(kptr->keyno);
540 	      if (kptr->keyid < 0) kptr->keyid = j;
541 	    }
542 	  }
543 
544 	  // Deal with continued strings.
545 	  if (continuation) {
546 	    // Tidy up the previous string keyvalue.
547 	    if ((kptr-1)->type == 8) (kptr-1)->type += 10;
548 	    char *cptr = (kptr-1)->keyvalue.s;
549 	    if (cptr[strlen(cptr)-1] == '&') cptr[strlen(cptr)-1] = '\0';
550 
551 	    kptr->type = (kptr-1)->type + 10;
552 	  }
553 
554 	  // Check for keyrecords following the END keyrecord.
555 	  if (end && (end++ > 1) && !blank) {
556 	    kptr->status |= FITSHDR_TRAILER;
557 	  }
558 	  if (kptr->status) (*nreject)++;
559 
560 	  kptr++;
561 	  blank = 0;
562 	  continuation = 0;
563 
564 	  BEGIN(INITIAL);
565 	}
566 
567 <<EOF>>	{
568 	  // End-of-input.
569 	  return 0;
570 	}
571 
572 %%
573 
574 /*----------------------------------------------------------------------------
575 * External interface to the scanner.
576 *---------------------------------------------------------------------------*/
577 
578 int fitshdr(
579   const char header[],
580   int nkeyrec,
581   int nkeyids,
582   struct fitskeyid keyids[],
583   int *nreject,
584   struct fitskey **keys)
585 
586 {
587   // Function prototypes.
588   int yylex_init_extra(YY_EXTRA_TYPE extra, yyscan_t *yyscanner);
589   int yylex_destroy(yyscan_t yyscanner);
590 
591   struct fitshdr_extra extra;
592   yyscan_t yyscanner;
593   yylex_init_extra(&extra, &yyscanner);
594   int status = fitshdr_scanner(header, nkeyrec, nkeyids, keyids, nreject,
595                                keys, yyscanner);
596   yylex_destroy(yyscanner);
597 
598   return status;
599 }
600 
601 /*----------------------------------------------------------------------------
602 * Pad a string with null characters.
603 *---------------------------------------------------------------------------*/
604 
nullfill(char cptr[],int len)605 void nullfill(char cptr[], int len)
606 
607 {
608   // Propagate the terminating null to the end of the string.
609   int j;
610   for (j = 0; j < len; j++) {
611     if (cptr[j] == '\0') {
612       for (int k = j+1; k < len; k++) {
613         cptr[k] = '\0';
614       }
615       break;
616     }
617   }
618 
619   // Remove trailing blanks.
620   for (int k = j-1; k >= 0; k--) {
621     if (cptr[k] != ' ') break;
622     cptr[k] = '\0';
623   }
624 
625   return;
626 }
627