1 /*
2      ccp4_parser.c: Functions to read in and "parse" CCP4 keyworded input.
3      Copyright (C) 2001  CCLRC, Peter Briggs
4 
5      This library is free software: you can redistribute it and/or
6      modify it under the terms of the GNU Lesser General Public License
7      version 3, modified in accordance with the provisions of the
8      license to address the requirements of UK law.
9 
10      You should have received a copy of the modified GNU Lesser General
11      Public License along with this library.  If not, copies may be
12      downloaded from http://www.ccp4.ac.uk/ccp4license.php
13 
14      This program is distributed in the hope that it will be useful,
15      but WITHOUT ANY WARRANTY; without even the implied warranty of
16      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17      GNU Lesser General Public License for more details.
18 */
19 
20 
21 /** @file ccp4_parser.c
22  *
23  *  @brief Functions to read in and "parse" CCP4-style keyworded input.
24  *
25  *  @author Peter Briggs
26  *  @date April 2001
27  */
28 
29 /*   ccp4_parser.c
30      Peter Briggs CCP4 April 2001
31 
32      Functions to read in and "parse" (scan, in reality) CCP4-style
33      "keyworded" input, plus utility routines.
34 
35      ccp4_parse_start(maxtokens)
36      initialise a CCP4PARSERARRAY to be used in subsequent calls to
37      ccp4_parser routines.
38 
39      ccp4_parse_end(parser)
40      clean up CCP4PARSERARRAY parser after use
41 
42      ccp4_parse_init_token(parser,itok)
43      initialise a single token in CCP4PARSERARRAY before use
44 
45      ccp4_parse_reset(parser)
46      initialise CCP4PARSERARRAY before use (includes calls to
47      ccp4_parse_init_token to initialise all tokens)
48 
49      ccp4_parse_delimiters(parser,delimiters,nulldelimiters)
50      set up or restore non-default delimiters
51 
52      int ccp4_parse_comments(parser,comment_chars)
53      set up or restore non-default comment characters
54 
55      ccp4_parse_maxmin(parser,max_exp,min_exp)
56      set non-default maximum and minimum values for numerical tokens
57 
58      ccp4_parse(line,parser)
59      given a string "line", break up into tokens and store in
60      CCP4PARSERARRAY "parser"
61 
62      ccp4_parser(line,parser,print)
63      read input from stdin or external file, break up into tokens
64      and store in CCP4PARSERARRAY "parser"
65 
66      ccp4_keymatch(keyin1,keyin2)
67      compare input strings to see if they match as CCP4-style keywords
68 
69      strtoupper(str1,str2)
70      convert string to uppercase
71 
72      strmatch(str1,str2)
73      check if strings are identical
74 
75      charmatch(character,charlist)
76      check if character appears in a list of possibilities
77 
78      doublefromstr(str,....)
79      convert a string representation of a number into the number, also
80      checks for exponent over/underflow, returns numbers of digits and
81      values of "components" (integer and fractional parts, and base-10
82      exponent)
83 
84 */
85 
86 /* Header files */
87 
88 #include <stdio.h>
89 #include <stdlib.h>
90 #include <string.h>
91 #ifndef __FLOAT_H__
92 #  include <float.h>
93 #endif
94 #include <math.h>
95 #include "ccp4_parser.h"
96 #include "ccp4_errno.h"
97 #include "ccp4_sysdep.h"
98 /* rcsid[] = "$Id$" */
99 
100 /* stuff for error reporting */
101 #define CPARSER_ERRNO(n) (CCP4_ERR_PARS | (n))
102 
103 /* error defs */
104 #define  CPARSERR_Ok                  0
105 #define  CPARSERR_MaxTokExceeded      1
106 #define  CPARSERR_AllocFail           2
107 #define  CPARSERR_NullPointer         3
108 #define  CPARSERR_LongLine            4
109 #define  CPARSERR_CantOpenFile        5
110 #define  CPARSERR_NoName              6
111 #define  CPARSERR_ExpOverflow         7
112 #define  CPARSERR_ExpUnderflow        8
113 #define  CPARSERR_MatToSymop          9
114 #define  CPARSERR_SymopToMat         10
115 
116 /*------------------------------------------------------------------*/
117 
118 /* Parser Functions */
119 
120 /*------------------------------------------------------------------*/
121 
122 /* ccp4_parse_start
123 
124    This initialises a CCP4PARSERARRAY to be used with the ccp4_parse/
125    ccp4_parser functions.
126    The calling function must supply the maximum number of tokens.
127 */
ccp4_parse_start(const int maxtokens)128 CCP4PARSERARRAY* ccp4_parse_start(const int maxtokens)
129 {
130   int itok,diag=0;
131   CCP4PARSERARRAY *parsePtr;
132 
133   if (diag) printf("CCP4_PARSE_START: ccp4_parse_start starting\n");
134 
135   /* Initial check for sensible values */
136   if (maxtokens < 1) return NULL;
137 
138   /* Return a pointer to a CCP4PARSERARRAY */
139   parsePtr = (CCP4PARSERARRAY *) ccp4_utils_malloc(sizeof(CCP4PARSERARRAY));
140   if (parsePtr) {
141     if (diag) printf("CCP4_PARSE_START: allocated parsePtr\n");
142     parsePtr->token = (CCP4PARSERTOKEN *) ccp4_utils_malloc(sizeof(CCP4PARSERTOKEN)*maxtokens);
143     if (!parsePtr->token) {
144       free(parsePtr);
145       parsePtr = NULL;
146     } else {
147       if (diag) printf("CCP4_PARSE_START: allocated parsePtr->token\n");
148       parsePtr->maxtokens = maxtokens;
149       parsePtr->fp = NULL;
150 
151       /* Explicitly ensure that each token's fullstring member is
152 	 set to NULL before calling ccp4_parse_reset (which tries to
153 	 free memory associated with non-NULL fullstrings) since
154 	 we can't rely on them being created with a NULL value */
155       for (itok = 0; itok < maxtokens; itok++)
156 	parsePtr->token[itok].fullstring = NULL;
157       ccp4_parse_reset(parsePtr);
158       if (diag) printf("CCP4_PARSE_START: fullstring set to NULL\n");
159 
160       /* Initialise the default maximum and minimum allowed
161 	 exponents for numerical tokens */
162       ccp4_parse_maxmin(parsePtr,DBL_MAX_10_EXP,DBL_MIN_10_EXP);
163       if (diag) printf("CCP4_PARSE_START: max and min set\n");
164 
165       /* Initialise the default delimiter and null delimiter characters
166 	 with a call to ccp4_parse_delimiters */
167       parsePtr->delim=NULL;
168       parsePtr->nulldelim=NULL;
169       if (!ccp4_parse_delimiters(parsePtr,NULL,NULL)) {
170 	ccp4_parse_end(parsePtr);
171 	parsePtr = NULL;
172       }
173       if (diag) printf("CCP4_PARSE_START: delimiters set\n");
174 
175       /* Initialise the default comment characters with a call
176 	 to ccp4_parse_comments */
177       parsePtr->comment=NULL;
178       if (!ccp4_parse_comments(parsePtr,NULL)) {
179 	ccp4_parse_end(parsePtr);
180 	parsePtr = NULL;
181       }
182       if (diag) printf("CCP4_PARSE_START: comments set\n");
183     }
184   }
185 
186   if (diag) printf("CCP4_PARSE_START: Returning from ccp4_parse_start\n");
187   return parsePtr;
188 }
189 
190 /*------------------------------------------------------------------*/
191 
192 /* ccp4_parse_end
193 
194    This cleans up a CCP4PARSEARRAY after being used by ccp4_parse/
195    ccp4_parser functions.
196 */
ccp4_parse_end(CCP4PARSERARRAY * parsePtr)197 int ccp4_parse_end(CCP4PARSERARRAY *parsePtr)
198 {
199   int i,maxtokens;
200 
201   /* Anything to do? */
202   if (parsePtr) {
203     /* Free memory for each token */
204     maxtokens = parsePtr->maxtokens;
205     if (parsePtr->token && parsePtr->maxtokens > 0) {
206       for (i=0; i<maxtokens; i++)
207 	if(parsePtr->token[i].fullstring) free(parsePtr->token[i].fullstring);
208       free(parsePtr->token);
209     }
210     /* Free memory for lists of comments and delimiters */
211     if (parsePtr->comment) free(parsePtr->comment);
212     if (parsePtr->delim) free(parsePtr->delim);
213     if (parsePtr->nulldelim) free(parsePtr->nulldelim);
214     /* Free memory for rest of parserarray structure */
215     free(parsePtr);
216   }
217   /* All done */
218   return 0;
219 }
220 
221 /*------------------------------------------------------------------*/
222 
223 /* ccp4_parse_init_token
224 
225    Initialise a token in a parser array
226    This sets all string members of the specified token to NULL and
227    all numerical values (including flags) to zero
228 */
229 
ccp4_parse_init_token(const CCP4PARSERARRAY * parsePtr,const int itok)230 int ccp4_parse_init_token(const CCP4PARSERARRAY *parsePtr, const int itok)
231 {
232   if (parsePtr) {
233     if (itok < parsePtr->maxtokens) {
234       /* Full string is dynamically allocated - free the
235 	 associated memory, if assigned */
236       if (parsePtr->token[itok].fullstring) {
237 	  free(parsePtr->token[itok].fullstring);
238 	  parsePtr->token[itok].fullstring = NULL;
239       }
240       /* Set fixed string tokens to empty string */
241       strcpy(parsePtr->token[itok].word,"");
242       /* Set numerical value to zero */
243       parsePtr->token[itok].value = 0.0;
244       /* Set flags to zero */
245       parsePtr->token[itok].isstring = 0;
246       parsePtr->token[itok].isnumber = 0;
247       parsePtr->token[itok].isquoted = 0;
248       parsePtr->token[itok].isnull   = 0;
249       /* Set start and end positions to zero */
250       parsePtr->token[itok].ibeg = 0;
251       parsePtr->token[itok].iend = 0;
252     }
253   }
254   return 0;
255 }
256 
257 /*------------------------------------------------------------------*/
258 
259 /* ccp4_parse_reset
260 
261    Reinitialise a parser array before calling ccp4_parse
262 
263    An application using ccp4_parse (rather than ccp4_parser, which
264    also calls this function) can call this function to reset the
265    parser array, rather than reinitialising the structure members
266    explicitly
267 */
ccp4_parse_reset(CCP4PARSERARRAY * parsePtr)268 int ccp4_parse_reset(CCP4PARSERARRAY *parsePtr)
269 {
270   int itok;
271 
272   if (parsePtr) {
273     /* Initialise the tokens to have null values */
274     for (itok=0; itok<parsePtr->maxtokens; itok++)
275       ccp4_parse_init_token(parsePtr,itok);
276     /* Initialise number of tokens to zero */
277     parsePtr->ntokens = 0;
278   }
279   return 0;
280 }
281 
282 /*------------------------------------------------------------------*/
283 
284 /* ccp4_parse_delimiters
285 
286    This allows the application to set its own delimiter characters
287    to be used in the ccp4_parser routines.
288 
289    If a NULL pointer is supplied for either of the two lists then
290    then the default delimiters are (re)set.
291 
292    Returns 1 on success, 0 if there was an error. In the event of
293    an error the delimiter lists will be unchanged.
294 */
295 
ccp4_parse_delimiters(CCP4PARSERARRAY * parsePtr,const char * delim,const char * nulldelim)296 int ccp4_parse_delimiters(CCP4PARSERARRAY *parsePtr,
297 			  const char *delim, const char *nulldelim)
298 {
299   const char defdelim[]=" \t,=\r",defnulldelim[]=",=";
300   char *delimPtr=NULL,*nulldelimPtr=NULL;
301   int  ldelim,lnulldelim,istatus=1;
302 
303   if (parsePtr) {
304 
305     /* If supplied delim is NULL then set to the default */
306     if (!delim) {
307       ldelim = strlen(defdelim) + 1;
308     } else {
309       ldelim = strlen(delim) + 1;
310     }
311     delimPtr = (char *) ccp4_utils_malloc(sizeof(char)*ldelim);
312     if (delimPtr) {
313       ldelim--;
314       if (!delim) {
315 	strncpy(delimPtr,defdelim,ldelim+1);
316       } else {
317 	strncpy(delimPtr,delim,ldelim+1);
318       }
319       delimPtr[ldelim] = '\0';
320     }
321 
322     /* If supplied nulldelim is NULL then set to the default */
323     if (!nulldelim) {
324       lnulldelim = strlen(defnulldelim) + 1;
325     } else {
326       lnulldelim = strlen(nulldelim) + 1;
327     }
328     nulldelimPtr = (char *) ccp4_utils_malloc(sizeof(char)*lnulldelim);
329     if (nulldelimPtr) {
330       lnulldelim--;
331       if (!nulldelim) {
332 	strncpy(nulldelimPtr,defnulldelim,lnulldelim+1);
333       } else {
334 	strncpy(nulldelimPtr,nulldelim,lnulldelim+1);
335       }
336       nulldelimPtr[lnulldelim] = '\0';
337     }
338 
339     /* Assign new delimiters in parser array */
340     if (delimPtr && nulldelimPtr) {
341       if (parsePtr->delim) free(parsePtr->delim);
342       parsePtr->delim = delimPtr;
343       if (parsePtr->nulldelim) free(parsePtr->nulldelim);
344       parsePtr->nulldelim = nulldelimPtr;
345     } else {
346       /* There is an error - don't reset the parser array */
347       if (delimPtr) free(delimPtr);
348       if (nulldelimPtr) free(nulldelimPtr);
349       istatus = 0;
350     }
351   } else {
352     istatus = 0;
353   }
354   return istatus;
355 }
356 
357 /*------------------------------------------------------------------*/
358 
359 /* ccp4_parse_comments
360 
361    This allows the application to set its own comment characters
362    to be used in the ccp4_parser routines.
363 
364    If a NULL pointer is supplied for the list of comment characters
365    then the default comment characters are (re)set.
366 
367    Returns 1 on success, 0 if there was an error. In the event of
368    an error the comment lists will be unchanged.
369 */
370 
ccp4_parse_comments(CCP4PARSERARRAY * parsePtr,const char * comment_chars)371 int ccp4_parse_comments(CCP4PARSERARRAY *parsePtr, const char *comment_chars)
372 {
373   const char def_comment_chars[]="#!";
374   char  *commentPtr=NULL;
375   int   lcomment,istatus=1;
376 
377   if (parsePtr) {
378 
379     /* If the supplied comment list is NULL then set to the default */
380     if (!comment_chars) {
381       lcomment = strlen(def_comment_chars) + 1;
382     } else {
383       lcomment = strlen(comment_chars) + 1;
384     }
385     commentPtr = (char *) ccp4_utils_malloc(sizeof(char)*lcomment);
386     if (commentPtr) {
387       if (!comment_chars) {
388 	strncpy(commentPtr,def_comment_chars,lcomment);
389       } else {
390 	strncpy(commentPtr,comment_chars,lcomment);
391       }
392       lcomment--;
393       commentPtr[lcomment] = '\0';
394     }
395 
396     /* Assign the new comments in the parser array */
397     if (commentPtr) {
398       if (parsePtr->comment) free(parsePtr->comment);
399       parsePtr->comment = commentPtr;
400     } else {
401       /* There was an error - don't reset the parser array */
402       istatus = 0;
403     }
404   } else {
405     /* The parser was unset on entry - also an error */
406     istatus = 0;
407   }
408   return istatus;
409 }
410 
411 /*------------------------------------------------------------------*/
412 
413 /* ccp4_parse_maxmin
414 
415    This allows the application to set its own maximum and minimum
416    exponent values, which are used as limits when evaluating the values
417    of numerical tokens in order to avoid over/underflow.
418 */
419 
ccp4_parse_maxmin(CCP4PARSERARRAY * parsePtr,const double max_exponent,const double min_exponent)420 int ccp4_parse_maxmin(CCP4PARSERARRAY *parsePtr, const double max_exponent,
421 		      const double min_exponent)
422 {
423   if (parsePtr) {
424     parsePtr->max_exponent = (double) max_exponent;
425     parsePtr->min_exponent = (double) min_exponent;
426   }
427   return 1;
428 }
429 
430 /*------------------------------------------------------------------*/
431 
432 /* ccp4_parse
433 
434    This is a scanner based on the old CCP4 Fortranic PARSE routine.
435 
436    It takes an input string ("line") and returns the number of
437    tokens ("ntokens") which are delimited by certain characters
438    (defaulted to space, tab, comma, equals - these can be changed by
439    the application using a call to ccp4_parse_delimiters).
440    Information about the tokens themselves is returned as members of
441    elements in an array ("tokens") of type CCP4PARSERTOKEN (see header
442    file for definition and members).
443 
444    Substrings can be delimited by single- or double-quotes but must
445    be surrounded by delimiters to be recognised.
446 
447    An unquoted comment character (defaulted to ! or #) in the input line
448    introduces a trailing comment which is ignored. The comment
449    characters can be changed using a call to ccp4_parse_comments.
450 
451    Null fields are denoted by two adjacent null delimiters (defaulted
452    to comma and equals - these can be changed by the application
453    using a call to ccp4_parse_delimiters).
454 
455    ccp4_parse returns the number of tokens found in the line. The
456    tokens are returned via the CCP4PARSERARRAY parser.
457 
458    Arguments:
459 
460    line   = pointer to a null-terminated string of characters,
461             forming the input to be processed. Unaltered on
462 	    output.
463    parser = pointer to a CCP4PARSERARRAY structure which will
464             be used to hold the results of processing the input
465 	    line.
466 */
467 
ccp4_parse(const char * line,CCP4PARSERARRAY * parser)468 int ccp4_parse(const char *line, CCP4PARSERARRAY *parser)
469 {
470   int quotedstring,starttoken,endtoken;
471   char this_char,next_char,matchquote;
472 
473   int llen,ich,lword,diag=0;
474   int token,nulltoken,isquote,iscommt=0,isdelim;
475   double value;
476   char *delim,*nulldelim,*comm;
477   char quot[]="\"\'";
478   int  ibeg,iend,start;
479 
480   double intvalue,frcvalue,expvalue;
481   int    intdigits,frcdigits,expdigits;
482 
483   /* Local parser variables */
484   int ntok,maxtok;
485   CCP4PARSERTOKEN *tokenarray;
486 
487   /* Begin */
488 
489   /* Set diag = 1 and recompile to switch on diagnostic output */
490   if (diag) printf("CCP4_PARSE: ccp4_parse starting\n");
491 
492   maxtok = parser->maxtokens;
493   ntok = parser->ntokens;
494   if (ntok < 0) ntok = 0;
495 
496   /* Initialise pointer for local version of the token array */
497   tokenarray = parser->token;
498 
499   /* Initialise pointers for lists of comments and delimiters */
500   comm = parser->comment;
501   delim = parser->delim;
502   nulldelim = parser->nulldelim;
503 
504   /* Don't process any tokens already dealt with */
505   if (ntok > 0) {
506     start = tokenarray[ntok-1].iend + 1;
507     /* Special case: if the last token was quoted
508        then in fact the character at this position will be
509        a quote - if so then step on another character */
510     if (charmatch(line[start],quot)) {
511       if (diag) printf("CCP4_PARSE: start character is a quote, stepping on\n");
512       start++;
513     }
514   } else {
515     start = 0;
516   }
517 
518   /* Don't process empty lines */
519   llen = strlen(line);
520   if (diag) printf("CCP4_PARSE: Line is: \"%s\"\nLength of line is %d\n",line,llen);
521   if (llen > 0) {
522 
523     /* Initialise flags and counters */
524     quotedstring = 0;
525     token        = 0;
526     nulltoken    = 0;
527 
528     /* Process the line two characters at a time */
529 
530     if (diag) printf("CCP4_PARSE: Start position for parsing line is %d\n",start);
531     for (ich=start-1; ich<llen; ich++) {
532 
533       /* Examine characters in pairs */
534 
535       /* Deal with special case, start and end of line
536 	 This is to avoid accessing non-existant parts of the string */
537       if (ich < start) {
538 	this_char = delim[0];
539       } else {
540 	this_char = line[ich];
541       }
542       if (ich == llen-1) {
543 	next_char = delim[0];
544       } else {
545 	next_char = line[ich+1];
546       }
547       if (diag) printf("CCP4_PARSE: %d: Current char = \"%c\" : \n",ich,this_char);
548 
549       /* Set flags for this round
550 	 The pairs of characters are analysed and flags set
551 	 accordingly to signal actions after the analysis */
552       starttoken = 0;
553       endtoken   = 0;
554 
555       if (!quotedstring) {
556 	/* Not in a quoted string so look for opening quotes
557 	   This is delimiter followed by quote character */
558 
559 	/* Is current character a delimiter character? */
560 	isdelim = charmatch(this_char,delim);
561 
562 	/* Is next charcter a quote character? */
563 	isquote = charmatch(next_char,quot);
564 
565 	/* Start of quoted string? */
566 	if (isdelim && isquote) {
567 
568 	  if (diag) printf("CCP4_PARSE: start of a quoted string\n");
569 	  quotedstring = 1;
570 	  starttoken = 1;
571 	  matchquote = next_char;
572 	  /* Advance to the next character immediately */
573 	  ich++;
574 
575 	} else {
576 	  /* Not the start of a quoted string */
577 
578 	  /* Is the current character the start of a comment? */
579 	  iscommt = charmatch(this_char,comm);
580 	  if (diag)
581 	    printf("CCP4_PARSE: character = %c comments = %s iscommt = %d\n",
582 		   this_char,comm,iscommt);
583 	  if (iscommt) {
584 	    if (diag) printf("CCP4_PARSE: start of a comment\n");
585 	  }
586 
587 	  /* Is the current character the start or end of a token? */
588 	  if (!isdelim) {
589 
590 	    /* End of the current token?
591 	       Only if we are in a token now, and the next
592 	       character is a delimiter or a comment */
593 	    if (token && (charmatch(next_char,delim) || charmatch(next_char,comm))) {
594 	      if (diag) printf("CCP4_PARSE: end of a token\n");
595 	      endtoken = 1;
596 	    }
597 
598 	  } else {
599 
600 	    /* Start of a token?
601 	       Only if we are not in a token now, and the next character
602 	       is not a delimiter (or a comment) too */
603 	    if (!token && !(charmatch(next_char,delim) || charmatch(next_char,comm))) {
604 		if (diag) printf("CCP4_PARSE: start of a token\n");
605 		starttoken = 1;
606 	    }
607 
608 	    /* Or - could be a null token?
609 	       This is a pair of null delimiters together */
610 	    if (!token && charmatch(this_char,nulldelim)
611 		&& charmatch(next_char,nulldelim)) {
612 	      if (diag) printf("CCP4_PARSE: null token\n");
613 	      starttoken = 1;
614 	      nulltoken  = 1;
615 	    }
616 
617 	  }
618 	  /* End of token identification */
619 	}
620 
621       } else {
622 	/* Inside a quoted string so look for closing quotes
623 	   This is a matching quote followed by a delimiter */
624 
625 	/* Is current character a matching quote? */
626 	isquote = (this_char == matchquote);
627 
628 	/* Is next charcter a delimiter character? */
629 	isdelim = charmatch(next_char,delim);
630 
631 	/* End of quoted string */
632 	if (isdelim && isquote) {
633 	  if (diag) printf("CCP4_PARSE: end of a quoted string\n");
634 	  quotedstring = 0;
635 	  endtoken = 1;
636 	  matchquote = ' ';
637 	}
638 	/* End of character analyses */
639       }
640 
641       /* Start of new token */
642       if (starttoken) {
643 	/* Check we don't have maximum number of tokens already */
644 	if (ntok < maxtok) {
645 	  /* Set flags */
646 	  token = 1;
647 	  if (quotedstring || !nulltoken) {
648 	    ibeg = ich + 1;
649 	  } else {
650 	    ibeg = ich;
651 	  }
652 	  if (diag) printf("CCP4_PARSE: Start of a new token... ibeg = %d\n",ibeg);
653 	  /* Initialise values */
654 	  tokenarray[ntok].fullstring = NULL;
655 	  tokenarray[ntok].value    = 0.0;
656 	  tokenarray[ntok].isstring = 0;
657 	  tokenarray[ntok].strlength = 0;
658 	  tokenarray[ntok].isnumber = 0;
659 	  tokenarray[ntok].intdigits= 0;
660 	  tokenarray[ntok].frcdigits= 0;
661 	  tokenarray[ntok].isquoted = 0;
662 	  tokenarray[ntok].isnull   = 0;
663 	  /* Flag start of quoted string */
664 	  if (quotedstring) { tokenarray[ntok].isquoted = 1; }
665 	  /* Flag null token */
666 	  if (nulltoken) {
667 	    if (diag) printf("CCP4_PARSE: Null token\n");
668 	    tokenarray[ntok].isnull = 1;
669 	    tokenarray[ntok].iend   = ich;
670 	    token = 0;
671 	    nulltoken = 0;
672 	  }
673 	} else {
674 	  /* Maximum number of tokens already found */
675           ccp4_signal(CPARSER_ERRNO(CPARSERR_MaxTokExceeded),"ccp4_parse",NULL);
676 	  parser->ntokens = ntok;
677 	  return ntok;
678 	}
679 	if (diag) printf("CCP4_PARSE: This is the start of token %d\n",ntok);
680       }
681       /* End of new token */
682 
683       /* End of current token */
684       if (endtoken) {
685 	token = 0;
686 	/* Exclude trailing quote from the token? */
687 	if (tokenarray[ntok].isquoted) {
688 	  iend = ich - 1;
689 	} else {
690 	  iend = ich;
691 	}
692 	if (diag) printf("CCP4_PARSE: End of a token... iend = %d\n",iend);
693 	/* Store the full token in the array */
694 	lword = iend - ibeg + 1;
695 	if (diag) printf("CCP4_PARSE: lword = %d - start char = %c, end char = %c\n",
696 			 lword,line[ibeg],line[iend]);
697 	tokenarray[ntok].fullstring = (char *) ccp4_utils_malloc(sizeof(char)*(lword+1));
698 	if (tokenarray[ntok].fullstring) {
699 	  strncpy(tokenarray[ntok].fullstring,&line[ibeg],lword);
700 	  tokenarray[ntok].fullstring[lword] = '\0';
701 	  if (diag) printf("CCP4_PARSE: Token is \"%s\"\n",tokenarray[ntok].fullstring);
702 	} else {
703           ccp4_signal(CPARSER_ERRNO(CPARSERR_AllocFail),"ccp4_parse",NULL);
704 	}
705 	tokenarray[ntok].ibeg = ibeg;
706 	tokenarray[ntok].iend = iend;
707 	/* Store the 4 character token in the array */
708 	if (lword > 4) lword = 4;
709 	strncpy(tokenarray[ntok].word,&line[ibeg],lword);
710 	tokenarray[ntok].word[lword] = '\0';
711 	/* Determine numerical value (if any) */
712 	if (doublefromstr(tokenarray[ntok].fullstring,parser->max_exponent,
713 			  parser->min_exponent,&value,&intvalue,&intdigits,
714 			  &frcvalue,&frcdigits,&expvalue,&expdigits)) {
715 	  if (diag) printf("CCP4_PARSE: This has a numerical value of %lf\n",value);
716 	  tokenarray[ntok].value     = value;
717 	  tokenarray[ntok].isnumber  = 1;
718 	  tokenarray[ntok].intdigits = intdigits;
719 	  tokenarray[ntok].frcdigits = frcdigits;
720 	} else {
721 	  if (diag) printf("CCP4_PARSE: There is no numerical value for this token\n");
722 	  tokenarray[ntok].isstring = 1;
723 	  tokenarray[ntok].strlength   = strlen(tokenarray[ntok].fullstring);
724 	}
725 	/* Reset flags etc ready for next token*/
726 	token  = 0;
727 	value  = 0.0;
728 	/* Increment number of tokens */
729 	ntok++;
730 	if (diag) printf("CCP4_PARSE: This is the end of a token\n");
731       }
732 
733       /* Don't do any more processing after a comment */
734 
735       if (iscommt) {
736 	parser->ntokens = ntok;
737 	if (diag) printf("CCP4_PARSE: returning after a comment\n");
738 	return ntok;
739       }
740       /* Check the next pair of characters */
741     }
742     /* Reset the number of tokens in the parser array */
743     parser->ntokens = ntok;
744     if (diag) printf("CCP4_PARSE: ntokens = %d, and ntok = %d\n",parser->ntokens,ntok);
745   }
746   if (diag) printf("CCP4_PARSE: returning at function end\n");
747   return ntok;
748 }
749 
750 /*------------------------------------------------------------------*/
751 
752 /* ccp4_parser
753 
754    This is based on the old CCP4 Fortranic PARSER routine.
755 
756    The normal behaviour is to read "keyworded" data from the input
757    stream, and interpret it. Stdin is the default, but a line
758    starting with @<name> starts reading from file <name> until eof.
759 
760    Each input line may be continued on the next line by the continuation
761    characters `&', `-' or `\' at the end of the input line. This
762    character is dropped from the list returned to the calling application.
763 
764    Pass in a zero length line to force reading from the command line.
765    nchars is the maximum number of characters which will be read into the line.
766    (If line is not blank then it will be processed and more input
767    read in if it ends in a continuation character, or forces reading from
768    an external file.)
769 
770    The "print" argument should be supplied as 0 to suppress echoing of the
771    input lines to standard output.
772 
773    ccp4_parser returns the number of tokens parsed in the input line. The
774    results of the parsing are stored as members of the CCP4PARSEARRAY
775    structure "parser" and can be accessed by the application program.
776 
777    The function returns the number of tokens, or 0 on reaching end of file.
778    On encountering an unrecoverable error ccp4_parser returns -1.
779 
780    Arguments:
781 
782    line   = pointer to a null-terminated string of characters,
783             forming the input to be processed.
784 	    On input can either be an empty string ("") or
785 	    contain characters to be processed (see above for
786 	    description).
787 	    On output "line" will be overwritten with the actual
788 	    input line, up to nchar characters.
789    nchars = maximum number of characters that can be read into
790             "line" i.e. the size of "line" in memory.
791    parser = pointer to a CCP4PARSERARRAY structure which will
792             be used to hold the results of processing the input
793 	    line.
794    print  = flag controlling echoing of input lines to stdout.
795             print=0: suppress echoing of lines to stdout
796 	    Otherwise echoing is turned on.
797 */
798 
ccp4_parser(char * line,const int nchars,CCP4PARSERARRAY * parser,const int print)799 int ccp4_parser(char *line, const int nchars, CCP4PARSERARRAY *parser,
800 		const int print)
801 {
802   int fromstdin=0,fromfile=0,fromapp=0,diag=0;
803   int nch,nold,continuation,first,trunc,llen,buflen;
804   char *linein=NULL,filename[200];
805 
806   /* Local parser variables */
807   int  ntok;
808   FILE *filein;
809   CCP4PARSERTOKEN *tokenarray;
810 
811   /* Undocumented feature - if print < 0 then also print out
812      diagnostic info */
813   if (print < 0) {
814     /*print = 1;*/
815     diag  = 1;
816   }
817 
818   /* Begin */
819   if (diag) printf("CCP4_PARSER: ccp4_parser starting\n");
820 
821   /* Abort if parser is a NULL pointer */
822   if (!parser) {
823     ccp4_signal(CPARSER_ERRNO(CPARSERR_NullPointer),"ccp4_parser",NULL);
824     return -1;
825   }
826 
827   /* Abort if line is NULL pointer */
828   if (!line) {
829     ccp4_signal(CPARSER_ERRNO(CPARSERR_NullPointer),"ccp4_parser",NULL);
830     return -1;
831   }
832 
833   /* Reset the parser array for this sweep
834      This will prevent phantom values from an earlier call
835      to ccp4_parser persisting in the parser array */
836   ccp4_parse_reset(parser);
837 
838   /* Blank the keyword */
839   strcpy(parser->keyword,"");
840 
841   /* Initialise local variables and pointers */
842   tokenarray = parser->token;
843   ntok       = parser->ntokens;
844   filein     = parser->fp;
845   if (diag) printf("CCP4_PARSER: parser->ntokens = %d, ntok = %d\n",
846 		   parser->ntokens,ntok);
847 
848   /* Set up an internal buffer for input
849      The buffer is over-allocated (twice as long as the max string
850      length allocated for line by the calling application)
851   */
852   buflen = (nchars*2)+1;
853   linein = (char *) ccp4_utils_malloc(buflen*sizeof(char));
854 
855   if (!linein) {
856     ccp4_signal(CPARSER_ERRNO(CPARSERR_AllocFail),"ccp4_parser",NULL);
857     return 0;
858   }
859 
860   /* Use nch as a count of the number of remaining characters
861      in line */
862   nch = nchars;
863 
864   /* If line is empty then read from the standard input
865      Otherwise process the line from the application first */
866   if (strlen(line)==0) {
867     if (!filein) {
868       if (diag) printf("CCP4_PARSER: Reading from stdin\n");
869       fromstdin = 1;
870     } else {
871       if (diag) printf("CCP4_PARSER: Reading from file\n");
872       fromfile = 1;
873     }
874   } else {
875     if (diag) printf("CCP4_PARSER: Reading line supplied by the application program\n");
876     fromapp = 1;
877   }
878 
879   /* Set flag for first line of input */
880   first = 1;
881 
882   /* Set flag for line continuation */
883   continuation = 1;
884 
885   /* Start the input loop */
886   while (continuation) {
887 
888     if (diag) printf("CCP4_PARSER: starting loop\n");
889     /* Read input from stdin a line at a time */
890     if (fromstdin) {
891       if (diag) printf("CCP4_PARSER: reading from stdin...\n");
892       if (!fgets(linein,buflen,stdin)) {
893 	/* Jump out at this point if eof is reached from
894 	   stdin */
895 	return 0;
896       }
897     } else if (fromfile) {
898       if (diag) printf("CCP4_PARSER: reading from external file...\n");
899       if (!fgets(linein,buflen,filein)) {
900 	/* Return to input from stdin if eof is read from
901 	   the external file */
902 	if (diag) printf("CCP4_PARSER: End of external file reached\n");
903 	fclose(filein);
904 	filein = NULL;
905 	fromfile  = 0;
906 	fromstdin = 1;
907 	/* Blank the line and reset the first flag to
908 	   force reading from standard input immediately */
909 	linein[0] = '\0';
910 	ntok      = 0;
911 	parser->ntokens = ntok;
912 	first     = 1;
913       }
914     } else if (fromapp) {
915       if (diag) printf("CCP4_PARSER: reading from application...\n");
916       /* If this line contains a continuation mark then
917 	 read from stdin next time around */
918       strncpy(linein,line,nchars);
919       linein[nchars]='\0';
920     }
921 
922     /* Strip any trailing newline e.g. from fgets */
923     llen = strlen(linein);
924     if (llen > 0)
925       if (linein[llen-1] == '\n') {
926 	linein[llen-1] = '\0';
927 	llen--;
928       }
929 
930     /* If previous line ended with a continuation character
931        then append this one to it
932        Check that we don't overflow the number of characters
933        specified by the application */
934 
935     if (llen > nch) {
936       ccp4_signal(CPARSER_ERRNO(CPARSERR_LongLine),"ccp4_parser",NULL);
937     }
938     if (first) {
939       strncpy(line,linein,nch);
940       first = 0;
941     } else {
942       strncat(line,linein,nch);
943     }
944     nch = nchars - llen;
945     if (diag) {
946       printf("CCP4_PARSER: line = \"%s\"\n",line);
947       printf("CCP4_PARSER: remaining available characters = %d\n",nch);
948     }
949 
950     /* Use ccp4_parse to break the input line up into tokens
951        Only parse the latest chunk - ccp4_parse will append
952        new tokens onto the tokenarray */
953     nold = ntok;
954     ntok = ccp4_parse(line,parser);
955 
956     if (diag) printf("CCP4_PARSER: ntok = %d, nold = %d\n",ntok,nold);
957 
958     /* Have we found more tokens since last time? */
959     if (ntok != nold) {
960       /* Check first token to see if it is an instruction
961 	 to read from an external file */
962       if (!fromfile && tokenarray[0].word[0] == '@') {
963 	if (diag) printf("CCP4_PARSER: Instruction to read from external file\n");
964 	/* Get filename and attempt to open file */
965 	if (tokenarray[0].fullstring) {
966 	  llen = strlen(tokenarray[0].fullstring);
967 	  strncpy(filename,&tokenarray[0].fullstring[1],llen);
968 	  if (diag) printf("CCP4_PARSER: External file name is \"%s\"\n",filename);
969 	  /* Open the external file as read-only */
970 	  filein = fopen(filename,"r");
971 	  if (!filein) {
972             ccp4_signal(CPARSER_ERRNO(CPARSERR_CantOpenFile),"ccp4_parser",NULL);
973 	  } else {
974 	    fromstdin = 0;
975 	    fromfile  = 1;
976 	  }
977 	} else {
978 	  /* Token with file name is null */
979           ccp4_signal(CPARSER_ERRNO(CPARSERR_NoName),"ccp4_parser",NULL);
980 	}
981 	/* Blank the line and reset the number of tokens
982 	   to force reading from the external file immediately */
983 	line[0] = '\0';
984 	ntok    = 0;
985 	parser->ntokens = ntok;
986 
987       /* Check last token to see if it is continuation
988 	 character */
989       } else if (ntok > 0 &&
990 	  (strmatch("&",tokenarray[ntok-1].word) ||
991 	   strmatch("\\",tokenarray[ntok-1].word) ||
992 	   strmatch("-",tokenarray[ntok-1].word))) {
993 	if (diag) printf("CCP4_PARSER: Detected continuation character\n");
994 	/* It's a continuation mark
995 	   Set flag to indicate this fact in later rounds */
996 	continuation = 1;
997 	/* Truncate the line to remove the continuation
998 	   character */
999 	if (ntok > 1)
1000 	  trunc = tokenarray[ntok-1].ibeg;
1001 	else
1002 	  trunc = 0;
1003 	if (diag) printf("CCP4_PARSER: Continuation character should be at position %d\n\"%c\" is the character at this position\n",trunc,line[trunc]);
1004 	line[trunc] = '\0';
1005 	/* Lose the last token */
1006 	ntok--;
1007 	parser->ntokens = ntok;
1008       } else {
1009 	/* Not a continuation character */
1010 	continuation = 0;
1011       }
1012 
1013     } else {
1014       /* Didn't get any more tokens from the last pass
1015 	 Check if it is a blank line or comment line */
1016       if (ntok == 0) {
1017 	/* Echo comment line to stdout and blank
1018 	   the line */
1019 	if (strlen(line) > 0) {
1020 	  if (print) printf(" Comment line--- %s\n",line);
1021 	  line[0] = '\0';
1022           nch = nchars;
1023 	}
1024         if (fromapp) continuation = 0;
1025       }
1026     }
1027     if (diag) printf("CCP4_PARSER: Continuation = %d\n",continuation);
1028 
1029     /* If the line was supplied by the application but is now being continued
1030        then make sure we read from stdin next time */
1031     if (continuation && fromapp) {
1032       if (diag) printf("CCP4_PARSER: switching to stdin\n");
1033       fromapp   = 0;
1034       fromstdin = 1;
1035     }
1036   }
1037 
1038   /* Fetch and uppercase keyword */
1039     if (ntok > 0) {
1040       strtoupper(parser->keyword,tokenarray[0].word);
1041       parser->keyword[strlen(tokenarray[0].word)] = '\0';
1042       if (diag) printf("CCP4_PARSER: Keyword is %s\n",parser->keyword);
1043       /*Echo the line to standard output */
1044       if (print) printf(" Data line--- %s\n",line);
1045     } else {
1046       parser->keyword[0] = '\0';
1047     }
1048 
1049   free(linein);
1050   /* Update the returned variables */
1051   parser->fp = filein;
1052 
1053   if (diag) printf("CCP4_PARSER: Returning from ccp4_parser\n");
1054   return ntok;
1055 }
1056 
1057 /*------------------------------------------------------------------*/
1058 
1059 /* ccp4_keymatch
1060 
1061    Returns 1 if keywords keyin1 and keyin2 are "identical", 0 otherwise.
1062 
1063    Keywords are identical if they are the same up to the first four
1064    characters, independent of case.
1065 */
ccp4_keymatch(const char * keyin1,const char * keyin2)1066 int ccp4_keymatch(const char *keyin1, const char *keyin2)
1067 {
1068   int  len1,len2;
1069   char key1[5],key2[5],keyup1[5],keyup2[5];
1070 
1071   /* Initial check */
1072   if (!keyin1 || !keyin2) return 0;
1073 
1074   /* Compare truncated lengths */
1075   len1 = strlen(keyin1);
1076   if (len1 > 4) len1 = 4;
1077 
1078   len2 = strlen(keyin2);
1079   if (len2 > 4) len2 = 4;
1080 
1081   /* If lengths don't match then keywords can't be identical */
1082   if (len1 != len2) return 0;
1083 
1084   /* If supplied words are longer than four characters then
1085      truncate them after the fourth character */
1086   strncpy(key1,keyin1,len1);
1087   key1[len1] = '\0';
1088 
1089   strncpy(key2,keyin2,4);
1090   key2[len2] = '\0';
1091 
1092   /* Convert strings to uppercase */
1093   strtoupper(keyup1,key1);
1094   keyup1[len1] = '\0';
1095   strtoupper(keyup2,key2);
1096   keyup2[len2] = '\0';
1097 
1098   /* Compare using strmatch */
1099   return strmatch(keyup1,keyup2);
1100 }
1101 
strtoupper(char * str1,const char * str2)1102 char *strtoupper (char *str1, const char *str2)
1103 {
1104   int len2,i;
1105 
1106   if (!str2) return NULL;
1107 
1108   len2 = strlen(str2);
1109   if (len2 > 0) for (i=0; i<len2 ; i++) str1[i] = toupper(str2[i]);
1110   str1[len2] = '\0';
1111   return str1;
1112 }
1113 
strtolower(char * str1,const char * str2)1114 char *strtolower (char *str1, const char *str2)
1115 {
1116   int len2,i;
1117 
1118   if (!str2) return NULL;
1119 
1120   len2 = strlen(str2);
1121   if (len2 > 0) for (i=0; i<len2 ; i++) str1[i] = tolower(str2[i]);
1122   str1[len2] = '\0';
1123   return str1;
1124 }
1125 
1126 /*------------------------------------------------------------------*/
1127 
1128 /* strmatch
1129 
1130    Compare two strings.
1131 
1132    Returns 1 if strings are identical, 0 if they differ.
1133 */
strmatch(const char * str1,const char * str2)1134 int strmatch (const char *str1, const char *str2)
1135 {
1136   int len1,len2,i;
1137 
1138   /* Don't process null strings */
1139   if (!str1 || !str2) return 0;
1140 
1141   len1 = strlen(str1);
1142   len2 = strlen(str2);
1143 
1144   /* Cannot be identical if lengths differ */
1145   if (len1 != len2) return 0;
1146 
1147   /* Check character by character
1148      If any character differs then strings cannot be identical */
1149   for (i=0; i<len1; i++)
1150     if (str1[i] != str2[i]) return 0;
1151 
1152   /* Must be a match */
1153   return 1;
1154 }
1155 
1156 /*------------------------------------------------------------------*/
1157 
1158 /* charmatch
1159 
1160    Returns 1 if character matches one of the characters in the string,
1161    0 otherwise.
1162 */
charmatch(const char character,const char * charlist)1163 int charmatch(const char character, const char *charlist)
1164 {
1165   int jdo,ismatch=0;
1166 
1167   if (charlist) {
1168     jdo = 0;
1169     while (jdo<strlen(charlist) && !ismatch) {
1170       if (charlist[jdo] == character) ismatch = 1;
1171       jdo++;
1172     }
1173   }
1174 
1175   return ismatch;
1176 }
1177 
1178 /*------------------------------------------------------------------*/
1179 
1180 /* doublefromstr
1181 
1182    Determine whether string represents a valid number, and return the
1183    numerical value if it is.
1184    The function returns 1 for a valid number, 0 otherwise.
1185 
1186    Valid numbers are represented by:
1187    ^[+-]?[0-9]*\.?[0-9]*[Ee]?[+-]?[0-9]*$
1188    (I think this is the correct regular expression...? -pjx)
1189 
1190    The component parts are: an integer part representing the value
1191    of the number upto the decimal point; a decimal fraction
1192    representing the value of the number upto the base-10 exponent;
1193    a base-10 exponent. The full value of the string is then
1194    int_part + frac_part * 10^(exp_part)
1195 
1196    The value of each component is returned separately via the argument
1197    list. The number of "digits" (= number of characters in the string)
1198    of each component are also returned - note that these include non-
1199    significant digits such as leading zeroes in the integer part,
1200    but non-numeric characters (+-eE) are not counted.
1201 
1202    The function can also trap for large or small exponents, to
1203    avoid overflow or underflow.
1204    The maximum and minimum allowed exponents are supplied by the calling
1205    application as the arguments max_exp and min_exp. (Values can be
1206    be taken from float.h).
1207    In the event of the limits being exceeded, only the values of the
1208    components are evaluated; the total value of the number is returned as
1209    zero.
1210 */
doublefromstr(const char * str,const double max_exp,const double min_exp,double * valuePtr,double * intvaluePtr,int * intdigitsPtr,double * frcvaluePtr,int * frcdigitsPtr,double * expvaluePtr,int * expdigitsPtr)1211 int doublefromstr(const char *str, const double max_exp, const double min_exp,
1212 		  double *valuePtr, double *intvaluePtr, int *intdigitsPtr,
1213 		  double *frcvaluePtr, int *frcdigitsPtr,
1214 		  double *expvaluePtr, int *expdigitsPtr)
1215 {
1216   int    lstr,ichar=0,char_value,diag=0;
1217   int    sign,expsign,point,exponent,is_int,is_frc,is_exp;
1218   int    n_int_digits=0,n_frc_digits=0,n_exp_digits=0;
1219   char   this_char, this_str[2];
1220   double int_value=0,frc_value=0,exp_value=0;
1221 
1222   if (diag) printf("DOUBLEFROMSTR: starting\n");
1223   if (diag) printf("DOUBLEFROMSTR: Biggest exponent is %lf, smallest is %lf\n",max_exp,min_exp);
1224 
1225   /* Initialise exported variables */
1226   *valuePtr = 0.0;
1227   *intvaluePtr = 0.0;
1228   *frcvaluePtr = 0.0;
1229   *expvaluePtr = 0.0;
1230   *intdigitsPtr = 0;
1231   *frcdigitsPtr = 0;
1232   *expdigitsPtr = 0;
1233 
1234   /* Nothing to do for an empty string */
1235   lstr = strlen(str);
1236   if (!lstr) return 0;
1237 
1238   /* Initialise internal flags and variables */
1239   sign     = 1;
1240   expsign  = 1;
1241   point    = -1;
1242   exponent = -1;
1243 
1244   is_int   = 1;
1245   is_frc   = 0;
1246   is_exp   = 0;
1247 
1248   /* Process the string character by character */
1249   while (ichar < lstr) {
1250     /* Get current character */
1251     this_char = str[ichar];
1252     if (diag) printf("DOUBLEFROMSTR: This char = %c",this_char);
1253 
1254     /* Check: is this character a digit? */
1255     if (!isdigit(this_char)) {
1256 
1257       /* Not a digit */
1258       if (diag) printf(" is not a digit...\n");
1259 
1260       if (this_char == '+' || this_char == '-') {
1261 	/* Sign? i.e. + or -
1262 	   This can only occur in two places: immediately at the
1263 	   start of the putative number, or immediately at the start
1264 	   of the putative exponent */
1265 	if (ichar == 0 && this_char == '-') {
1266 	  sign = -1;
1267 	} else if (ichar == exponent + 1 && this_char == '-') {
1268 	  expsign = -1;
1269 	} else if (this_char != '+') {
1270 	  return 0;
1271 	}
1272 
1273       } else if (this_char == '.') {
1274 	/* Decimal point? i.e. .
1275 	   There can only be one decimal point */
1276 	if (point > -1) return 0;
1277 	point = ichar;
1278 	is_int = 0;
1279 	is_frc = 1;
1280 
1281       } else if (toupper(this_char) == 'E') {
1282         char next_char = (ichar+1 < lstr ) ? str[ichar+1] : '\0';
1283         if ( next_char == '+' || next_char == '-')
1284            next_char = (ichar+2 < lstr ) ? str[ichar+2] : '\0';
1285         /* require the next active character after E to be a digit */
1286         if ( !isdigit(next_char) )  return 0;
1287 	/* Exponent? i.e. e or E
1288 	   There can only be one exponent */
1289         if (exponent > -1) return 0;
1290         exponent = ichar;
1291         is_int = 0;
1292         is_frc = 0;
1293         is_exp = 1;
1294       } else {
1295 	/* Not a permissible character
1296 	   This is not a number so get out now */
1297 	if (diag) printf("DOUBLEFROMSTR: Not a permitted character - exiting\n");
1298 	return 0;
1299       }
1300     } else {
1301       /* It is a digit
1302 	 Build up the value of each component */
1303 
1304       if (diag) printf(" is a digit ...\n");
1305 
1306       this_str[0] = this_char;
1307       this_str[1] = '\0';
1308       char_value = atoi(this_str);
1309 
1310       if (is_int) {
1311 	/* Integer part of the number */
1312 	n_int_digits++;
1313 	int_value = int_value * 10.0 + (double) char_value;
1314 	if (diag) printf("DOUBLEFROMSTR: Processing integer component: value = %lf, #digits = %d\n",int_value,n_int_digits);
1315       } else if (is_frc) {
1316 	/* Decimal part of the number */
1317 	n_frc_digits++;
1318 	frc_value = frc_value + ((double) char_value)/pow(10.0,(double) n_frc_digits);
1319 	if (diag) printf("DOUBLEFROMSTR: Processing decimal component: value = %lf, #digits = %d\n",frc_value,n_frc_digits);
1320       } else if (is_exp) {
1321 	/* Exponent */
1322 	n_exp_digits++;
1323 	exp_value = exp_value * 10.0 + (double) char_value;
1324 	if (diag) printf("DOUBLEFROMSTR: Processing exponential component: value = %lf, #digits = %d\n",exp_value,n_exp_digits);
1325       }
1326 
1327     }
1328     /* Next character */
1329     ichar++;
1330   }
1331   /*
1332     Done loop over characters - if we have got this far then it
1333     must be a number
1334   */
1335 
1336   /* Set component values */
1337   int_value = int_value * (double) sign;
1338   frc_value = frc_value * (double) sign;
1339   exp_value = exp_value * (double) expsign;
1340 
1341   /* Export component values */
1342   *intvaluePtr = int_value;
1343   *frcvaluePtr = frc_value;
1344   *expvaluePtr = exp_value;
1345 
1346   /* Export numbers of 'digits' */
1347   *intdigitsPtr = n_int_digits;
1348   *frcdigitsPtr = n_frc_digits;
1349   *expdigitsPtr = n_exp_digits;
1350 
1351   /* Is the exponent out-of-range? */
1352 
1353   /* There are two considerations:
1354      (i) can pow(10.0,exp_value) actually be evaluated?
1355      (ii) can int_part * pow(10.0,exp_value) be evaluated?
1356      This second is an issue for numbers with int_part > 0.
1357   */
1358   if ( (exp_value + (double) (n_int_digits - 1) > max_exp) &&
1359         (n_int_digits || n_frc_digits) ) {
1360     ccp4_signal(CPARSER_ERRNO(CPARSERR_ExpOverflow),"doublefromstr",NULL);
1361     printf("DOUBLEFROMSTR: Token is \"%s\"\n",str);
1362     *valuePtr = 0.0;
1363   } else if ( (exp_value < min_exp) &&
1364         (n_int_digits || n_frc_digits) ) {
1365     ccp4_signal(CPARSER_ERRNO(CPARSERR_ExpUnderflow),"doublefromstr",NULL);
1366     printf("DOUBLEFROMSTR: Token is \"%s\"\n",str);
1367     *valuePtr = 0.0;
1368   } else {
1369     /* Evaluate the number to get a value */
1370     *valuePtr = int_value + frc_value;
1371     if (is_exp) *valuePtr = (*valuePtr)*pow(10.0,exp_value);
1372   }
1373 
1374   if (diag) printf("DOUBLEFROMSTR: Integer component = %lf, (%d digits)\n",
1375 		   *intvaluePtr,*intdigitsPtr);
1376   if (diag) printf("DOUBLEFROMSTR: Decimal component = %lf, (%d digits)\n",
1377 		   *frcvaluePtr,*frcdigitsPtr);
1378   if (diag) printf("DOUBLEFROMSTR: Exponent component = %lf, (%d digits)\n",
1379 		   *expvaluePtr,*expdigitsPtr);
1380   if (diag) printf("DOUBLEFROMSTR: Finished - value is determined to be %lf\n",*valuePtr);
1381 
1382   return 1;
1383 }
1384 
symop_to_rotandtrn(const char * symchs_begin,const char * symchs_end)1385 ccp4_symop symop_to_rotandtrn(const char *symchs_begin, const char *symchs_end) {
1386 
1387   float rsm[4][4];
1388 
1389   symop_to_mat4(symchs_begin, symchs_end, rsm[0]);
1390   return (mat4_to_rotandtrn((const float (*)[4])rsm));
1391 
1392 }
1393 
1394 /*------------------------------------------------------------------*/
1395 
1396 /* symop_to_mat4
1397 
1398    Translates a single symmetry operator string into a 4x4 quine
1399    matrix representation
1400    NB Uses a utility function (symop_to_mat4_err) when reporting
1401    failures.
1402 
1403    Syntax of possible symop strings:
1404 
1405    real space symmetry operations, e.g. X+1/2,Y-X,Z
1406    reciprocal space operations,    e.g. h,l-h,-k
1407    reciprocal axis vectors,        e.g. a*+c*,c*,-b*
1408    real space axis vectors,        e.g. a,c-a,-b
1409 
1410    The strings can contain spaces, and the coordinate and translation
1411    parts may be in either order.
1412 
1413    The function returns 1 on success, 0 if there was a failure to
1414    generate a matrix representation.
1415 */
symop_to_mat4(const char * symchs_begin,const char * symchs_end,float * rot)1416 const char *symop_to_mat4(const char *symchs_begin, const char *symchs_end, float *rot)
1417 {
1418   int no_real =0, no_recip = 0, no_axis = 0;          /* counters */
1419   int col = 3, nops = 0;
1420   int nsym = 0, init_array = 1;
1421   float sign = 1.0f, value = 0.0f, value2;
1422   char *cp, ch;
1423   const char *ptr_symchs = symchs_begin;
1424   int j,k;                                 /* loop variables */
1425   int Isep = 0;                             /* parsed seperator? */
1426 
1427   while (ptr_symchs < symchs_end) {
1428     ch = *ptr_symchs;
1429 
1430     /* Parse symop */
1431     if (isspace(ch)) {
1432       /* Have to allow symop strings to contain spaces for
1433 	 compatibility with older MTZ files
1434 	 Ignore and step on to next character */
1435       ++ptr_symchs;
1436       continue;
1437     } else if (ch == ',' || ch == '*') {
1438       ++ptr_symchs;
1439       if (value == 0.0f && col == 3) {
1440         /* nothing set, this is a problem */
1441         ccp4_signal(CPARSER_ERRNO(CPARSERR_SymopToMat),"symop_to_mat4",NULL);
1442         return NULL ;
1443       } else {
1444         Isep = 1;     /* drop through to evaluation*/
1445       }
1446     } else if (ch == 'X' || ch == 'x') {
1447       no_real++, col = 0;
1448       if (value == 0.0f) value = sign * 1.0f;
1449       ++ptr_symchs;
1450       continue;
1451     } else if (ch == 'Y' || ch == 'y') {
1452       no_real++, col = 1;
1453       if (value == 0.0f) value = sign * 1.0f;
1454       ++ptr_symchs;
1455       continue;
1456     } else if (ch == 'Z' || ch == 'z') {
1457       no_real++, col = 2;
1458       if (value == 0.0f) value = sign * 1.0f;
1459       ++ptr_symchs;
1460       continue;
1461     } else if (ch == 'H' || ch == 'h') {
1462       no_recip++, col = 0;
1463       if (value == 0.0f) value = sign * 1.0f;
1464       ++ptr_symchs;
1465       continue;
1466     } else if (ch == 'K' || ch == 'k') {
1467       no_recip++, col = 1;
1468       if (value == 0.0f) value = sign * 1.0f;
1469       ++ptr_symchs;
1470       continue;
1471     } else if (ch == 'L' || ch == 'l') {
1472       no_recip++, col = 2;
1473       if (value == 0.0f) value = sign * 1.0f;
1474       ++ptr_symchs;
1475       continue;
1476     } else if (ch == 'A' || ch == 'a') {
1477       no_axis++, col = 0;
1478       if (value == 0.0f) value = sign * 1.0f;
1479       if (*++ptr_symchs == '*' && ( no_axis != 3 || no_recip )) ++ptr_symchs;
1480       continue;
1481     } else if (ch == 'B' || ch == 'b') {
1482       no_axis++, col = 1;
1483       if (value == 0.0f) value = sign * 1.0f;
1484       if (*++ptr_symchs == '*' && ( no_axis != 3 || no_recip )) ++ptr_symchs;
1485       continue;
1486     } else if (ch == 'C' || ch == 'c') {
1487       no_axis++, col = 2;
1488       if (value == 0.0f) value = sign * 1.0f;
1489       if (*++ptr_symchs == '*' && ( no_axis != 3 || no_recip )) ++ptr_symchs;
1490       continue;
1491     } else if (ch == '+' || ch == '-') {
1492       sign = ((ch == '+')? 1.0f : -1.0f) ;
1493       ++ptr_symchs;
1494       if ( value == 0.0f && col == 3)
1495 	continue;
1496       /* drop through to evaluation */
1497     } else if ( ch == '/') {
1498       ++ptr_symchs;
1499       if (value == 0.0f) {
1500 	/* error */
1501 	symop_to_mat4_err(symchs_begin);
1502 	return NULL;
1503       }
1504       value2 = strtod(ptr_symchs, &cp);
1505       if (!value2) {
1506 	/* error */
1507 	symop_to_mat4_err(symchs_begin);
1508 	return NULL;
1509       }
1510       /* Nb don't apply the sign to value here
1511 	 It will already have been applied in the previous round */
1512       value = (float) value/value2;
1513       ptr_symchs = cp;
1514       continue;
1515     } else if ( isdigit(ch) || ch == '.') {
1516       value = sign*strtod(ptr_symchs, &cp);
1517       ptr_symchs = cp;
1518       continue;
1519     } else {
1520       ++ptr_symchs;
1521       continue;
1522     }
1523 
1524   /* initialise and clear the relevant array  (init_array == 1)*/
1525   /* use knowledge that we are using a [4][4] for rot */
1526   if (init_array) {
1527     init_array = 0;
1528     for (j = 0 ; j !=4 ; ++j)
1529       for (k = 0; k !=4 ; ++k)
1530         rot[(((nsym << 2) + k ) << 2) +j] = 0.0f;
1531      rot[(((nsym << 2 ) + 3 )  << 2) +3] = 1.0f;
1532   }
1533 
1534     /* value to be entered in rot */
1535     rot[(((nsym << 2) + nops) << 2) + col] = value;
1536 
1537     /* have we passed a operator seperator */
1538     if (Isep) {
1539       Isep = 0;
1540       ++nops;
1541       sign = 1.0f;
1542       if (nops == 3 ) { ++nsym; nops=0 ; init_array = 1; }
1543     }
1544 
1545     /* reset for next cycle */
1546     col = 3;
1547     value = 0.0f;
1548     no_recip = 0, no_axis = 0, no_real = 0;
1549   }
1550 
1551   /* Tidy up last value */
1552   if (value) rot[(((nsym << 2) + nops) << 2) + col] = value;
1553 
1554   if (nops<2) {
1555     /* Processed fewer than 3 operators, raise an error */
1556     symop_to_mat4_err(symchs_begin);
1557     return NULL;
1558   }
1559 
1560   /* Return with success */
1561   return ptr_symchs;
1562 }
1563 
1564 /* Internal function: report error from symop_to_mat4_err */
symop_to_mat4_err(const char * symop)1565 int symop_to_mat4_err(const char *symop)
1566 {
1567   printf("\n **SYMMETRY OPERATOR ERROR**\n\n Error in interpreting symop \"%s\"\n\n",
1568 	 symop);
1569   ccp4_signal(CPARSER_ERRNO(CPARSERR_SymopToMat),"symop_to_mat4",NULL);
1570   return 1;
1571 }
1572 
mat4_to_rotandtrn(const float rsm[4][4])1573 ccp4_symop mat4_to_rotandtrn(const float rsm[4][4]) {
1574 
1575   int i,j;
1576   ccp4_symop symop;
1577 
1578   for (i = 0; i < 3; ++i) {
1579     for (j = 0; j < 3; ++j)
1580       symop.rot[i][j]=rsm[i][j];
1581     symop.trn[i]=rsm[i][3];
1582   }
1583 
1584   return (symop);
1585 }
1586 
rotandtrn_to_symop(char * symchs_begin,char * symchs_end,const ccp4_symop symop)1587 char *rotandtrn_to_symop(char *symchs_begin, char *symchs_end, const ccp4_symop symop)
1588 {
1589   float rsm[4][4];
1590 
1591   rotandtrn_to_mat4(rsm,symop);
1592   return(mat4_to_symop(symchs_begin,symchs_end,(const float (*)[4])rsm));
1593 }
1594 
rotandtrn_to_mat4(float rsm[4][4],const ccp4_symop symop)1595 void rotandtrn_to_mat4(float rsm[4][4], const ccp4_symop symop) {
1596 
1597   int i,j;
1598 
1599   for (i = 0; i < 3; ++i) {
1600     for (j = 0; j < 3; ++j)
1601       rsm[i][j]=symop.rot[i][j];
1602     rsm[i][3]=symop.trn[i];
1603     rsm[3][i]=0.0;
1604   }
1605   rsm[3][3]=1.0;
1606 }
1607 
mat4_to_symop(char * symchs_begin,char * symchs_end,const float rsm[4][4])1608 char *mat4_to_symop(char *symchs_begin, char *symchs_end, const float rsm[4][4])
1609 {
1610   static char axiscr[] = {'X','Y','Z'};
1611   static char numb[] = {'0','1','2','3','4','5','6','7','8','9'};
1612   static int npntr1[12] = { 0,1,1,1,0,1,0,2,3,5,0,0 };
1613   static int npntr2[12] = { 0,6,4,3,0,2,0,3,4,6,0,0 };
1614 
1615   int jdo10, jdo20, irsm, itr, ist;
1616   register char *ich;
1617   int debug=0;
1618 
1619   if (debug)
1620     for (jdo20 = 0; jdo20 != 4; ++jdo20)
1621       printf("Input matrix: %f %f %f %f \n",rsm[jdo20][0],rsm[jdo20][1],
1622           rsm[jdo20][2],rsm[jdo20][3]);
1623 
1624   /* blank output string */
1625   for (ich = symchs_begin; ich < symchs_end; ++ich)
1626     *ich = ' ';
1627   ich = symchs_begin;
1628 
1629   for (jdo20 = 0; jdo20 != 3; ++jdo20) {
1630     *ich = '0';
1631     ist = 0;    /* ---- Ist is flag for first character of operator */
1632     for (jdo10 = 0; jdo10 != 4; ++jdo10) {
1633 
1634       if (rsm[jdo20][jdo10] != 0.f) {
1635 	irsm = (int) rint(fabs(rsm[jdo20][jdo10]));
1636 
1637 	if ( rsm[jdo20][jdo10] > 0. && ist) {
1638 	  if (ich >= symchs_end) {
1639 	    ccp4_signal(CCP4_ERRLEVEL(3) | CPARSER_ERRNO(CPARSERR_MatToSymop),
1640 			"mat4_to_symop 1", NULL);
1641 	    return NULL; }
1642 	  *ich++ = '+';
1643 	} else if ( rsm[jdo20][jdo10] < 0.f ) {
1644 	  if (ich >= symchs_end) {
1645 	    ccp4_signal(CCP4_ERRLEVEL(3) | CPARSER_ERRNO(CPARSERR_MatToSymop),
1646 			"mat4_to_symop 2", NULL);
1647 	    return NULL; }
1648 	  if (jdo10 != 3) {
1649 	    *ich++ = '-';
1650 	  } else {
1651 	    /* translation part is forced to be positive, see below */
1652 	    *ich++ = '+';
1653 	  }
1654           ist = 1;
1655 	}
1656 
1657 	if (jdo10 != 3) {
1658 	  /* rotation part */
1659 	  if (ich+1 >= symchs_end) {
1660 	    ccp4_signal(CCP4_ERRLEVEL(3) | CPARSER_ERRNO(CPARSERR_MatToSymop),
1661 		      "mat4_to_symop 3", NULL);
1662 	    return NULL; }
1663 	  if (irsm != 1) {
1664 	    *ich++ = numb[irsm];
1665 	    *ich++ = axiscr[jdo10];
1666 	  } else {
1667 	    *ich++ = axiscr[jdo10];
1668 	  }
1669 	  ist = 1;
1670         } else {
1671 	  /* translation part */
1672 	  itr = (int) rint(rsm[jdo20][3]*12.0);
1673           while (itr < 0) itr += 12;
1674           itr = (itr - 1) % 12;
1675           if (npntr1[itr] > 0) {
1676 	   if (ich+2 >= symchs_end) {
1677 	    ccp4_signal(CCP4_ERRLEVEL(3) | CPARSER_ERRNO(CPARSERR_MatToSymop),
1678 		      "mat4_to_symop 4", NULL);
1679 	    return NULL; }
1680 	   *ich++ = numb[npntr1[itr]];
1681 	   *ich++ = '/';
1682            *ich++ = numb[npntr2[itr]];
1683 	  } else {
1684            *--ich = ' ';
1685 	  }
1686 	}
1687       }
1688     }
1689     if (jdo20 != 2) {
1690       if (*ich == '0')
1691         ++ich;
1692       if (ich+2 >= symchs_end) {
1693         ccp4_signal(CCP4_ERRLEVEL(3) | CPARSER_ERRNO(CPARSERR_MatToSymop),
1694                      "mat4_to_symop 5", NULL);
1695         return NULL; }
1696       *ich++ = ',';
1697       *ich++ = ' ';
1698       *ich++ = ' ';
1699     }
1700   }
1701   return symchs_begin;
1702 }
1703 
mat4_to_recip_symop(char * symchs_begin,char * symchs_end,const float rsm[4][4])1704 char *mat4_to_recip_symop(char *symchs_begin, char *symchs_end, const float rsm[4][4])
1705 {
1706   char *symop;
1707   size_t lsymop;
1708   register char *ich, *ich_out;
1709 
1710   lsymop = symchs_end-symchs_begin;
1711   symop = (char *) ccp4_utils_malloc(lsymop*sizeof(char));
1712 
1713   mat4_to_symop(symop, symop+lsymop, rsm);
1714   ich_out = symchs_begin;
1715   for (ich = symop; ich < symop+lsymop; ++ich) {
1716     if (*ich == 'X') {
1717       if (ich_out == symchs_begin || (ich_out > symchs_begin &&
1718           *(ich_out-1) != '-' && *(ich_out-1) != '+')) *ich_out++ = '+';
1719       *ich_out++ = 'h';
1720     } else if (*ich == 'Y') {
1721       if (ich_out == symchs_begin || (ich_out > symchs_begin &&
1722           *(ich_out-1) != '-' && *(ich_out-1) != '+')) *ich_out++ = '+';
1723       *ich_out++ = 'k';
1724     } else if (*ich == 'Z') {
1725       if (ich_out == symchs_begin || (ich_out > symchs_begin &&
1726           *(ich_out-1) != '-' && *(ich_out-1) != '+')) *ich_out++ = '+';
1727       *ich_out++ = 'l';
1728     } else if (*ich == ' ') {
1729       /* skip */
1730     } else {
1731       *ich_out++ = *ich;
1732     }
1733   }
1734   while (ich_out < symchs_end) *ich_out++ = ' ';
1735 
1736   free (symop);
1737   return symchs_begin;
1738 }
1739