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