1 /* RCS $Id: getinp.c,v 1.10 2007-10-15 15:39:23 ihi Exp $
4 -- Handle reading of input.
7 -- The code in this file reads the input from the specified stream
8 -- into the provided buffer of size Buffer_size. In doing so it deletes
9 -- comments. Comments are delimited by the #, and
10 -- <nl> character sequences. An exception is \# which
11 -- is replaced by # in the input. Line continuations are signalled
12 -- at the end of a line and are recognized inside comments.
13 -- The line continuation is always <\><nl>.
15 -- If the file to read is NIL(FILE) then the Get_line routine returns the
16 -- next rule from the builtin rule table (Rule_tab from ruletab.c) if
20 -- Dennis Vadura, dvadura@dmake.wticorp.com
23 -- http://dmake.wticorp.com/
26 -- Copyright (c) 1996,1997 by WTI Corp. All rights reserved.
28 -- This program is NOT free software; you can redistribute it and/or
29 -- modify it under the terms of the Software License Agreement Provided
30 -- in the file <distribution-root>/readme/license.txt.
33 -- Use cvs log to obtain detailed change logs.
38 #define IS_WHITE(A) ((A == ' ') || (A == '\t') || (A == '\n') || (A == '\r'))
39 #define SCAN_WHITE(A) \
40 while( IS_WHITE(*A) ) A++;
42 static int _is_conditional
ANSI((char*));
43 static int _handle_conditional
ANSI((int, TKSTRPTR
));
45 static int rule_ind
= 0; /* index of rule when reading Rule_tab */
46 static int skip
= FALSE
; /* if true the skip input */
48 int partcomp( char* lhs
, int opcode
);
49 int parse_complex_expression( char *expr
, char **expr_end
, int opcode
);
53 Get_line( buf
, fil
)/*
54 ======================
55 Read a line of input from the file stripping off comments. The routine
56 returns TRUE if EOF. If fil equals NIL(FILE) then the next line from
57 *Rule_tab[] is used. Rule_tab is either the buildin rule table or points
58 to the current environment (used by ReadEnvironment()).
59 The function returns TRUE if the input file/buffer was read to the end
60 and FALSE otherwise. */
64 extern char **Rule_tab
;
69 static int ignore
= FALSE
;
73 register char *tmp
= NIL(char);
75 DB_ENTER( "Get_line" );
81 if( Verbose
& V_MAKE
)
82 Warning("Ignoring remainder of file %s", Filename());
87 if( fil
== NIL(FILE) ) {
88 /* Reading the internal rule table. Set rule_ind to zero after the
89 * last entry so that ReadEnvironment() works as expected every time. */
91 while( (p
= Rule_tab
[ rule_ind
++ ]) != NIL(char) ) {
92 /* The last test in this if *p != '~', handles the environment
93 * passing conventions used by MKS to pass arguments. We want to
94 * skip those environment entries. Also CYGWIN likes to export '!'
95 * prefixed environment variables that cause severe pain, axe them too.
96 * And finally it is possible to do "env 'GGG HHH'='some value' bash"
97 * which causes that there are env variables with spaces in the name
98 * defined which causes dmake to malfunction too */
99 char *equal
= strchr(p
,'=');
100 char *space
= strchr(p
,' ');
101 if( !Readenv
|| (Readenv
&& (equal
!= NIL(char)) && (space
== NIL(char) || space
> equal
) && *p
!='~' && *p
!='!')){
104 DB_PRINT( "io", ("Returning [%s]", buf
) );
111 DB_PRINT( "io", ("Done Ruletab") );
120 /* fgets() reads at most one less than Buffer_size-pos characters. */
121 if(feof( fil
) || (fgets( p
, Buffer_size
-pos
, fil
) == NIL(char)))
125 if ( p
[0] == 10 && p
[1] == COMMENT_CHAR
)
131 /* Set q to the last char in p before the \n\0. */
133 if( q
>= p
) { /* Only check for special cases if p points
134 * to a non-empty line. */
136 /* ignore each RETURN at the end of a line before any further
138 if( q
[0] == '\r' && q
[1] == '\n' ) {
143 /* you also have to deal with END_OF_FILE chars to process raw
144 * DOS-Files. Normally they are the last chars in file, but after
145 * working on these file with vi, there is an additional NEWLINE
146 * after the last END_OF_FILE. So if the second last char in the
147 * actual line is END_OF_FILE, you can skip the last char. Then
148 * you can search the line back until you find no more END_OF_FILE
149 * and nuke each you found by string termination. */
152 while( q
[1] == '\032' ) {
157 /* ignore input if ignore flag set and line ends in a continuation
161 if( q
[0] != CONTINUATION_CHAR
|| q
[1] != '\n' ) ignore
= FALSE
;
166 /* If a comment is found the line does not end in \n anymore. */
167 c
= Do_comment(p
, &q
, Group
|| (*buf
== '\t') || (Notabs
&& *buf
==' '));
169 /* Does the end of the line end in a continuation sequence? */
171 if( (q
[0] == CONTINUATION_CHAR
) && (q
[1] == '\n')) {
172 /* If the continuation was at the end of a comment then ignore the
173 * next input line, (or lines until we get one ending in just <nl>)
174 * else it's a continuation, so build the input line from several
175 * text lines on input. The maximum size of this is governened by
177 if( q
!= p
&& q
[-1] == CONTINUATION_CHAR
) {
182 else if( c
!= NIL(char) )
185 cont
= TRUE
; /* Keep the \<nl>. */
191 q
= ( c
== NIL(char) ) ? q
+2 : c
;
193 else { /* empty line or "" */
196 q
= p
+strlen(p
); /* strlen(p) is 1 or 0 */
201 while( (cont
|| !*buf
) && (pos
< Buffer_size
-1) );
203 if( pos
>= Buffer_size
-1 )
204 Fatal( "Input line too long, increase MAXLINELENGTH" );
206 /* Lines that had comments don't contain \n anymore. */
207 /* ??? Continued lines that are followed by an empty or comment only
208 * line will end in \<nl>. */
209 if( (q
> p
) && (buf
[ pos
-1 ] == '\n') )
210 buf
[ --pos
] = '\0'; /* Remove the final \n. */
212 /* STUPID AUGMAKE uses "include" at the start of a line as
213 * a signal to include a new file, so let's look for it.
214 * if we see it replace it by .INCLUDE: and stick this back
215 * into the buffer. We also allow GNU make if[n]eq/else/endif.
217 * These substitutions are made only if we are not parsing a group
219 if( (p
= DmStrSpn(buf
, " \t\r\n")) == NIL(char) )
223 if( !strncmp( "include", p
, 7 ) &&
224 (p
[7] == ' ' || p
[7] == '\t') )
225 tmp
= DmStrJoin( ".INCLUDE:", p
+7, -1, FALSE
);
226 else if( !strncmp( "ifeq", p
, 4 ) &&
227 (p
[4] == ' ' || p
[4] == '\t') )
228 tmp
= DmStrJoin( ".IFEQ", p
+4, -1, FALSE
);
229 else if( !strncmp( "ifneq", p
, 5 ) &&
230 (p
[5] == ' ' || p
[5] == '\t') )
231 tmp
= DmStrJoin( ".IFNEQ", p
+5, -1, FALSE
);
232 else if( !strncmp( "elif", p
, 4 ) &&
233 (p
[4] == ' ' || p
[4] == '\t') )
234 tmp
= DmStrJoin( ".ELIF", p
+4, -1, FALSE
);
235 else if( !strncmp( "else", p
, 4 ) &&
236 (p
[4] == ' ' || p
[4] == '\t' || p
[4] == '\0') )
237 tmp
= DmStrJoin( ".ELSE", p
+4, -1, FALSE
);
238 else if( !strncmp( "endif", p
, 5 ) &&
239 (p
[5] == ' ' || p
[5] == '\t' || p
[5] == '\0') )
240 tmp
= DmStrJoin( ".END", p
+5, -1, FALSE
);
243 if( tmp
!= NIL(char)) {
249 /* Now that we have the next line of input to make, we should check to
250 * see if it is a conditional expression. If it is then process it,
251 * otherwise pass it on to the parser. */
253 if( *(p
= DmStrSpn(buf
, " \t\r\n")) == CONDSTART
) {
256 SET_TOKEN( &token
, p
);
258 p
= Get_token( &token
, "", FALSE
);
260 if( (res
= _is_conditional(p
)) != 0 ) /* ignore non-control special */
262 res
= _handle_conditional( res
, &token
);
266 CLEAR_TOKEN( &token
);
272 buf
= buf_org
; /* ignore line just read in */
278 DB_PRINT( "io", ("Returning [%s]", buf
) );
284 Do_comment(str
, pend
, keep
)/*
285 =============================
286 Search the input string looking for comment chars. If it contains
287 comment chars then NUKE the remainder of the line, if the comment
288 char is preceeded by \ then shift the remainder of the line left
296 while( (c
= strchr(c
, COMMENT_CHAR
)) != NIL(char) ) {
297 if( Comment
|| State
== NORMAL_SCAN
)
298 if( c
!= str
&& c
[-1] == ESCAPE_CHAR
) {
299 strcpy( c
-1, c
); /* copy it left, due to \# */
300 if( pend
) (*pend
)--; /* shift tail pointer left */
303 /* Check/execute if shebang command is present. */
308 && Nestlevel() == 1 ) {
312 cmnd
[strlen(cmnd
)-1] = '\0'; /* strip last newline */
313 Current_target
= Root
;
317 Wait_for_completion
= TRUE
;
318 Do_cmnd(&cmnd
, FALSE
, TRUE
, Current_target
, A_DEFAULT
, TRUE
);
320 Swap_on_exec
= FALSE
;
322 Wait_for_completion
= FALSE
;
326 *c
= '\0'; /* a true comment so break */
344 Get_token( string
, brk
, anchor
)/*
345 ==================================
346 Return the next token in string.
348 Returns empty string when no more tokens in string.
349 brk is a list of chars that also cause breaks in addition to space and
350 tab, but are themselves returned as tokens. if brk is NULL then the
351 remainder of the line is returned as a single token.
353 'anchor' if 1, says break on chars in the brk list, but only if
354 the entire token begins with the first char of the brk list, if
355 0 then any char of brk will cause a break to occurr.
357 If 'anchor' is 2, then break only seeing the first char in the break
358 list allowing only chars in the break list to form the prefix. */
365 register char *curp
= 0;
370 DB_ENTER( "Get_token" );
372 s
= string
->tk_str
; /* Get string parameters */
373 *s
= string
->tk_cchar
; /* ... and strip leading w/s */
377 DB_PRINT( "tok", ("What's left [%s]", s
) );
380 DB_PRINT( "tok", ("Returning NULL token") );
385 /* Build the space list. space contains all those chars that may possibly
386 * cause breaks. This includes the brk list as well as white space. */
388 if( brk
!= NIL(char) ) {
389 strcpy( space
, " \t\r\n" );
390 strcat( space
, brk
);
393 space
[0] = 0xff; /* a char we know will not show up */
398 /* Handle processing of quoted tokens. Note that this is disabled if
399 * brk is equal to NIL */
401 while( *s
== '\"' && ((brk
!= NIL(char)) || !string
->tk_quote
) ) {
403 if( string
->tk_quote
) {
405 do { curp
= strchr( curp
+1, '\"' ); }
406 while( (curp
!= NIL(char)) && (*(curp
+1) == '\"'));
408 if( curp
== NIL(char) ) Fatal( "Unmatched quote in token" );
409 string
->tk_quote
= !string
->tk_quote
;
411 /* Check for "" case, and if found ignore it */
412 if( curp
== s
) continue;
418 string
->tk_quote
= !string
->tk_quote
;
422 /* Check for a token break character at the beginning of the token.
423 * If found return the next set of break chars as a token. */
425 if( anchor
== 2 && brk
!= NIL(char) ) {
427 while( *curp
&& (strchr(brk
,*curp
)!=NIL(char)) && (*curp
!=*brk
) ) curp
++;
428 done
= (*brk
== *curp
++);
430 else if( (brk
!= NIL(char)) && (strchr( brk
, *s
) != NIL(char)) ) {
431 curp
= DmStrSpn( s
, brk
);
432 done
= (anchor
== 0) ? TRUE
:
433 ((anchor
== 1)?(*s
== *brk
) : (*brk
== curp
[-1]));
437 /* Scan for the next token in the list and return it less the break char
438 * that was used to terminate the token. It will possibly be returned in
439 * the next call to Get_token */
447 curp
= DmStrPbrk(t
, space
);
449 if( anchor
&& *curp
&& !IS_WHITE( *curp
) )
450 if( ((anchor
== 1)?*curp
:DmStrSpn(curp
,brk
)[-1]) != *brk
) {
457 if( (curp
== s
) && (strchr(brk
, *curp
) != NIL(char)) ) curp
++;
461 string
->tk_str
= curp
;
462 string
->tk_cchar
= *curp
;
465 DB_PRINT( "tok", ("Returning [%s]", s
) );
471 _is_conditional( tg
)/*
472 =======================
473 Look at tg and return it's value if it is a conditional identifier
474 otherwise return 0. */
477 DB_ENTER( "_is_conditional" );
483 if( !strcmp( tg
, "IF" )) DB_RETURN( ST_IF
);
484 else if( !strcmp( tg
, "IFEQ" )) DB_RETURN( ST_IFEQ
);
485 else if( !strcmp( tg
, "IFNEQ" )) DB_RETURN( ST_IFNEQ
);
489 if( !strcmp( tg
, "END" )) DB_RETURN( ST_END
);
490 else if( !strcmp( tg
, "ENDIF")) DB_RETURN( ST_END
);
491 else if( !strcmp( tg
, "ELSE" )) DB_RETURN( ST_ELSE
);
492 else if( !strcmp( tg
, "ELIF" )) DB_RETURN( ST_ELIF
);
501 #define SEEN_END 0x00
503 #define SEEN_ELSE 0x02
504 #define SEEN_ELIF 0x04
506 #define ACCEPT_IF 0x10
507 #define ACCEPT_ELIF 0x20
510 _handle_conditional( opcode
, tg
)
514 static short action
[MAX_COND_DEPTH
];
515 static char ifcntl
[MAX_COND_DEPTH
];
517 char *lhs
, *expr
, *expr_end
;
521 DB_ENTER( "_handle_conditional" );
525 if( !(ifcntl
[Nest_level
] & SEEN_IF
) || (ifcntl
[Nest_level
]&SEEN_ELSE
) )
526 Fatal(".ELIF without a preceeding .IF" );
532 if( opcode
!= ST_ELIF
&& (Nest_level
+1) == MAX_COND_DEPTH
)
533 Fatal( ".IF .ELSE ... .END nesting too deep" );
536 expr
= Expand( Get_token( tg
, NIL(char), FALSE
));
539 /* Remove CONTINUATION_CHAR<nl> and replace with " " so that line
540 * continuations are recognized as whitespace. */
541 for( cst
=strchr(expr
,CONTINUATION_CHAR
); cst
!= NIL(char); cst
=strchr(cst
,CONTINUATION_CHAR
) )
542 if( cst
[1] == '\n' ) {
552 /* Parse the expression and get its logical result */
553 if ( ((lop
= DmStrStr(lhs
, "||" )) != NIL(char)) || ((lop
= DmStrStr(lhs
, "&&" )) != NIL(char)) )
554 result
= parse_complex_expression( lhs
, &expr_end
, opcode
);
556 result
= partcomp( lhs
, opcode
);
558 if( expr
!= NIL(char) ) FREE( expr
);
560 if( opcode
!= ST_ELIF
) {
562 action
[Nest_level
] = 1;
564 ifcntl
[Nest_level
] |= (opcode
==ST_ELIF
)?SEEN_ELIF
:SEEN_IF
;
567 if( !(ifcntl
[Nest_level
] & (ACCEPT_IF
|ACCEPT_ELIF
)) ) {
568 action
[ Nest_level
] = action
[ Nest_level
-1 ];
569 ifcntl
[Nest_level
] |= (opcode
==ST_ELIF
)?ACCEPT_ELIF
:ACCEPT_IF
;
572 action
[Nest_level
] = 1;
575 action
[Nest_level
] = 1;
579 if( Nest_level
<= 0 ) Fatal( ".ELSE without .IF" );
580 if( ifcntl
[Nest_level
] & SEEN_ELSE
)
581 Fatal( "Missing .IF or .ELIF before .ELSE" );
583 if( ifcntl
[Nest_level
] & (ACCEPT_IF
|ACCEPT_ELIF
) )
584 action
[Nest_level
] = 1;
585 else if( action
[ Nest_level
-1 ] != 1 )
586 action
[ Nest_level
] ^= 0x1; /* flip between 0 and 1 */
588 ifcntl
[Nest_level
] |= SEEN_ELSE
;
592 ifcntl
[Nest_level
] = SEEN_END
;
594 if( Nest_level
< 0 ) Fatal( "Unmatched .END[IF]" );
598 DB_RETURN( action
[ Nest_level
] );
601 /* uncomment to turn on expression debug statements */
602 /*#define PARSE_DEBUG */
603 #define PARSE_SKIP_WHITE(A) while( *A && ((*A==' ') || (*A=='\t')) ) A++;
611 int parse_complex_expression( char *expr
, char **expr_end
, int opcode
)
614 char *term_start
= p
;
618 int term_result
= FALSE
;
619 int final_result
= TRUE
;
620 unsigned int term_len
;
621 unsigned int last_op
= OP_NONE
;
624 printf( "%d: parse_complex_expression( %s ): Opcode: %d\n", n
, expr
, opcode
);
629 /* A new sub-expression */
634 term_result
= parse_complex_expression( p
+1, &p
, opcode
);
636 PARSE_SKIP_WHITE( p
);
644 /* Lets do an operation!! */
645 if ( !(*p
) /* at the end of the entire line */
646 || ((*p
== '&') && (*(p
+1) && (*(p
+1)=='&'))) /* found an && */
647 || ((*p
== '|') && (*(p
+1) && (*(p
+1)=='|'))) /* found an || */
648 || (*p
== ')') ) /* at the end of our term */
650 /* Grab the sub-expression if we parsed it. Otherwise,
651 * it was a () subexpression and we don't need to evaluate
652 * it since that was already done.
654 if ( local_term
== TRUE
)
656 /* Back up 1 to the end of the actual term */
659 /* Evaluate the term */
660 PARSE_SKIP_WHITE( term_start
);
661 term_len
= term_end
- term_start
+ 1;
662 part
= MALLOC( term_len
+ 1, char );
663 strncpy( part
, term_start
, term_len
);
664 *(part
+term_len
) = '\0';
666 printf( "%d: evaling '%s'\n", n
, part
);
668 term_result
= partcomp( part
, opcode
);
670 printf( "%d: evaled, result %d\n", n
, term_result
);
675 /* Do the actual logical operation using the _preceding_
676 * logical operator, NOT the one we just found.
678 if ( last_op
== OP_AND
)
679 final_result
= final_result
&& term_result
;
680 else if ( last_op
== OP_OR
)
681 final_result
= final_result
|| term_result
;
683 final_result
= term_result
;
685 printf( "%d: final_result:%d\n", n
, final_result
);
688 /* If we're not at the end of the line, just keep going */
691 /* Recognize the operator we just found above */
694 else if ( *p
== '|' )
699 /* Get the start of the next term */
700 PARSE_SKIP_WHITE( p
);
703 /* If this is the close of a term, we are done and return
712 else break; /* At end of line, all done */
714 else if ( local_term
== TRUE
) p
++; /* Advance to next char in expression */
719 printf( "%d: done, returning '%s', result %d\n", n
, *expr_end
, final_result
);
721 return( final_result
);
725 int partcomp( char* lhs
, int opcode
)
728 char *tok
, *rhs
, *op
= 0;
730 const int localopscount
=4;
731 char* localops
[] = { "==", "!=", "<=", ">=" };
737 #define GREATER_EQUAL 3
740 printf( "eval: %s\n", lhs
);
744 if( opcode
== ST_IFEQ
|| opcode
== ST_IFNEQ
)
746 /* IF[N]EQ syntax is: .IF[N]EQ <1> <2>
747 * Here, step over first argument and get to <2> if it exists.
749 for( op
= lhs
; ((*op
)&&(*op
!= ' ')&&(*op
!= '\t')); op
++ );
750 if( *op
) op
++; /* position op at start of <2> */
751 else op
= NIL(char); /* only 1 argument given */
755 /* Find which logical operator we are to use for this expression,
757 while ( (opsind
< localopscount
) && ((op
= DmStrStr(lhs
, localops
[opsind
])) == NIL(char)) )
761 printf(" found op %d: %s\n", opsind
, localops
[opsind
]);
765 /* If the opcode was IFEQ or IFNEQ and only 1 argument was given,
766 * or an unknown logical operator was encountered,
767 * return false if argument is empty string, true if !empty
769 if( op
== NIL(char) )
770 result
= (*lhs
!= '\0');
773 /* Make both characters of the operation the same, replacing the = in op[1]
774 * Its easier to deal with this way???
776 if( opcode
!= ST_IFEQ
&& opcode
!= ST_IFNEQ
)
780 printf(" op:%s\n", op
);
783 /* Isolate the left half of the expression */
786 for( tok
= op
-1; (tok
!= lhs
) && ((*tok
== ' ')||(*tok
== '\t')); tok
-- );
790 lhs
= NIL(char); /* Left hand side is empty. */
792 /* Jump over the operation so we can grab the right half of the expression */
793 if( opcode
== ST_IFEQ
|| opcode
== ST_IFNEQ
)
798 /* Isolate the right half of the expression */
799 rhs
= DmStrSpn( op
+1, " \t" );
800 if( !*rhs
) rhs
= NIL(char);
803 printf(" lhs:%s, rhs:%s\n", lhs
, rhs
);
806 /* Do the actual logical operation on the expression */
807 if ( opsind
> NOTEQUAL
)
813 /* Ignore quotes around the arguments */
814 if ( lhs
&& lhs
[0] == '"' ) lhs
++;
815 if ( rhs
&& rhs
[0] == '"' ) rhs
++;
817 /* Empty strings evaluate to zero. */
818 lint
= lhs
? atoi( lhs
) : 0;
819 rint
= rhs
? atoi( rhs
) : 0;
820 result
= ( lint
>= rint
) ? TRUE
: FALSE
;
821 if ( opsind
== LESS_EQUAL
&& lint
!= rint
)
830 /* Use a simple string compare to determine equality */
831 if( (rhs
== NIL(char)) || (lhs
== NIL(char)) )
832 result
= (rhs
== lhs
) ? TRUE
: FALSE
;
835 /* String off whitespace at the end of the right half of the expression */
836 tok
= rhs
+ strlen( rhs
);
837 for( tok
=tok
-1; (tok
!= lhs
) && ((*tok
== ' ')||(*tok
== '\t')); tok
--);
840 result
= (strcmp( lhs
, rhs
) == 0) ? TRUE
: FALSE
;
843 if( *op
== '!' || opcode
== ST_IFNEQ
) result
= !result
;
848 printf("partresult %d\n\n",result
);