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 if( !Readenv
|| (Readenv
&& (strchr(p
,'=') != NIL(char)) && *p
!='~' && *p
!='!')){
99 DB_PRINT( "io", ("Returning [%s]", buf
) );
105 DB_PRINT( "io", ("Done Ruletab") );
114 /* fgets() reads at most one less than Buffer_size-pos characters. */
115 if(feof( fil
) || (fgets( p
, Buffer_size
-pos
, fil
) == NIL(char)))
119 if ( p
[0] == 10 && p
[1] == COMMENT_CHAR
)
125 /* Set q to the last char in p before the \n\0. */
127 if( q
>= p
) { /* Only check for special cases if p points
128 * to a non-empty line. */
130 /* ignore each RETURN at the end of a line before any further
132 if( q
[0] == '\r' && q
[1] == '\n' ) {
137 /* you also have to deal with END_OF_FILE chars to process raw
138 * DOS-Files. Normally they are the last chars in file, but after
139 * working on these file with vi, there is an additional NEWLINE
140 * after the last END_OF_FILE. So if the second last char in the
141 * actual line is END_OF_FILE, you can skip the last char. Then
142 * you can search the line back until you find no more END_OF_FILE
143 * and nuke each you found by string termination. */
146 while( q
[1] == '\032' ) {
151 /* ignore input if ignore flag set and line ends in a continuation
155 if( q
[0] != CONTINUATION_CHAR
|| q
[1] != '\n' ) ignore
= FALSE
;
160 /* If a comment is found the line does not end in \n anymore. */
161 c
= Do_comment(p
, &q
, Group
|| (*buf
== '\t') || (Notabs
&& *buf
==' '));
163 /* Does the end of the line end in a continuation sequence? */
165 if( (q
[0] == CONTINUATION_CHAR
) && (q
[1] == '\n')) {
166 /* If the continuation was at the end of a comment then ignore the
167 * next input line, (or lines until we get one ending in just <nl>)
168 * else it's a continuation, so build the input line from several
169 * text lines on input. The maximum size of this is governened by
171 if( q
!= p
&& q
[-1] == CONTINUATION_CHAR
) {
176 else if( c
!= NIL(char) )
179 cont
= TRUE
; /* Keep the \<nl>. */
185 q
= ( c
== NIL(char) ) ? q
+2 : c
;
187 else { /* empty line or "" */
190 q
= p
+strlen(p
); /* strlen(p) is 1 or 0 */
195 while( (cont
|| !*buf
) && (pos
< Buffer_size
-1) );
197 if( pos
>= Buffer_size
-1 )
198 Fatal( "Input line too long, increase MAXLINELENGTH" );
200 /* Lines that had comments don't contain \n anymore. */
201 /* ??? Continued lines that are followed by an empty or comment only
202 * line will end in \<nl>. */
203 if( (q
> p
) && (buf
[ pos
-1 ] == '\n') )
204 buf
[ --pos
] = '\0'; /* Remove the final \n. */
206 /* STUPID AUGMAKE uses "include" at the start of a line as
207 * a signal to include a new file, so let's look for it.
208 * if we see it replace it by .INCLUDE: and stick this back
209 * into the buffer. We also allow GNU make if[n]eq/else/endif.
211 * These substitutions are made only if we are not parsing a group
213 if( (p
= DmStrSpn(buf
, " \t\r\n")) == NIL(char) )
217 if( !strncmp( "include", p
, 7 ) &&
218 (p
[7] == ' ' || p
[7] == '\t') )
219 tmp
= DmStrJoin( ".INCLUDE:", p
+7, -1, FALSE
);
220 else if( !strncmp( "ifeq", p
, 4 ) &&
221 (p
[4] == ' ' || p
[4] == '\t') )
222 tmp
= DmStrJoin( ".IFEQ", p
+4, -1, FALSE
);
223 else if( !strncmp( "ifneq", p
, 5 ) &&
224 (p
[5] == ' ' || p
[5] == '\t') )
225 tmp
= DmStrJoin( ".IFNEQ", p
+5, -1, FALSE
);
226 else if( !strncmp( "elif", p
, 4 ) &&
227 (p
[4] == ' ' || p
[4] == '\t') )
228 tmp
= DmStrJoin( ".ELIF", p
+4, -1, FALSE
);
229 else if( !strncmp( "else", p
, 4 ) &&
230 (p
[4] == ' ' || p
[4] == '\t' || p
[4] == '\0') )
231 tmp
= DmStrJoin( ".ELSE", p
+4, -1, FALSE
);
232 else if( !strncmp( "endif", p
, 5 ) &&
233 (p
[5] == ' ' || p
[5] == '\t' || p
[5] == '\0') )
234 tmp
= DmStrJoin( ".END", p
+5, -1, FALSE
);
237 if( tmp
!= NIL(char)) {
243 /* Now that we have the next line of input to make, we should check to
244 * see if it is a conditional expression. If it is then process it,
245 * otherwise pass it on to the parser. */
247 if( *(p
= DmStrSpn(buf
, " \t\r\n")) == CONDSTART
) {
250 SET_TOKEN( &token
, p
);
252 p
= Get_token( &token
, "", FALSE
);
254 if( (res
= _is_conditional(p
)) != 0 ) /* ignore non-control special */
256 res
= _handle_conditional( res
, &token
);
260 CLEAR_TOKEN( &token
);
266 buf
= buf_org
; /* ignore line just read in */
272 DB_PRINT( "io", ("Returning [%s]", buf
) );
278 Do_comment(str
, pend
, keep
)/*
279 =============================
280 Search the input string looking for comment chars. If it contains
281 comment chars then NUKE the remainder of the line, if the comment
282 char is preceeded by \ then shift the remainder of the line left
290 while( (c
= strchr(c
, COMMENT_CHAR
)) != NIL(char) ) {
291 if( Comment
|| State
== NORMAL_SCAN
)
292 if( c
!= str
&& c
[-1] == ESCAPE_CHAR
) {
293 strcpy( c
-1, c
); /* copy it left, due to \# */
294 if( pend
) (*pend
)--; /* shift tail pointer left */
297 /* Check/execute if shebang command is present. */
302 && Nestlevel() == 1 ) {
306 cmnd
[strlen(cmnd
)-1] = '\0'; /* strip last newline */
307 Current_target
= Root
;
311 Wait_for_completion
= TRUE
;
312 Do_cmnd(&cmnd
, FALSE
, TRUE
, Current_target
, A_DEFAULT
, TRUE
);
314 Swap_on_exec
= FALSE
;
316 Wait_for_completion
= FALSE
;
320 *c
= '\0'; /* a true comment so break */
338 Get_token( string
, brk
, anchor
)/*
339 ==================================
340 Return the next token in string.
342 Returns empty string when no more tokens in string.
343 brk is a list of chars that also cause breaks in addition to space and
344 tab, but are themselves returned as tokens. if brk is NULL then the
345 remainder of the line is returned as a single token.
347 'anchor' if 1, says break on chars in the brk list, but only if
348 the entire token begins with the first char of the brk list, if
349 0 then any char of brk will cause a break to occurr.
351 If 'anchor' is 2, then break only seeing the first char in the break
352 list allowing only chars in the break list to form the prefix. */
359 register char *curp
= 0;
364 DB_ENTER( "Get_token" );
366 s
= string
->tk_str
; /* Get string parameters */
367 *s
= string
->tk_cchar
; /* ... and strip leading w/s */
371 DB_PRINT( "tok", ("What's left [%s]", s
) );
374 DB_PRINT( "tok", ("Returning NULL token") );
379 /* Build the space list. space contains all those chars that may possibly
380 * cause breaks. This includes the brk list as well as white space. */
382 if( brk
!= NIL(char) ) {
383 strcpy( space
, " \t\r\n" );
384 strcat( space
, brk
);
387 space
[0] = 0xff; /* a char we know will not show up */
392 /* Handle processing of quoted tokens. Note that this is disabled if
393 * brk is equal to NIL */
395 while( *s
== '\"' && ((brk
!= NIL(char)) || !string
->tk_quote
) ) {
397 if( string
->tk_quote
) {
399 do { curp
= strchr( curp
+1, '\"' ); }
400 while( (curp
!= NIL(char)) && (*(curp
+1) == '\"'));
402 if( curp
== NIL(char) ) Fatal( "Unmatched quote in token" );
403 string
->tk_quote
= !string
->tk_quote
;
405 /* Check for "" case, and if found ignore it */
406 if( curp
== s
) continue;
412 string
->tk_quote
= !string
->tk_quote
;
416 /* Check for a token break character at the beginning of the token.
417 * If found return the next set of break chars as a token. */
419 if( anchor
== 2 && brk
!= NIL(char) ) {
421 while( *curp
&& (strchr(brk
,*curp
)!=NIL(char)) && (*curp
!=*brk
) ) curp
++;
422 done
= (*brk
== *curp
++);
424 else if( (brk
!= NIL(char)) && (strchr( brk
, *s
) != NIL(char)) ) {
425 curp
= DmStrSpn( s
, brk
);
426 done
= (anchor
== 0) ? TRUE
:
427 ((anchor
== 1)?(*s
== *brk
) : (*brk
== curp
[-1]));
431 /* Scan for the next token in the list and return it less the break char
432 * that was used to terminate the token. It will possibly be returned in
433 * the next call to Get_token */
441 curp
= DmStrPbrk(t
, space
);
443 if( anchor
&& *curp
&& !IS_WHITE( *curp
) )
444 if( ((anchor
== 1)?*curp
:DmStrSpn(curp
,brk
)[-1]) != *brk
) {
451 if( (curp
== s
) && (strchr(brk
, *curp
) != NIL(char)) ) curp
++;
455 string
->tk_str
= curp
;
456 string
->tk_cchar
= *curp
;
459 DB_PRINT( "tok", ("Returning [%s]", s
) );
465 _is_conditional( tg
)/*
466 =======================
467 Look at tg and return it's value if it is a conditional identifier
468 otherwise return 0. */
471 DB_ENTER( "_is_conditional" );
477 if( !strcmp( tg
, "IF" )) DB_RETURN( ST_IF
);
478 else if( !strcmp( tg
, "IFEQ" )) DB_RETURN( ST_IFEQ
);
479 else if( !strcmp( tg
, "IFNEQ" )) DB_RETURN( ST_IFNEQ
);
483 if( !strcmp( tg
, "END" )) DB_RETURN( ST_END
);
484 else if( !strcmp( tg
, "ENDIF")) DB_RETURN( ST_END
);
485 else if( !strcmp( tg
, "ELSE" )) DB_RETURN( ST_ELSE
);
486 else if( !strcmp( tg
, "ELIF" )) DB_RETURN( ST_ELIF
);
495 #define SEEN_END 0x00
497 #define SEEN_ELSE 0x02
498 #define SEEN_ELIF 0x04
500 #define ACCEPT_IF 0x10
501 #define ACCEPT_ELIF 0x20
504 _handle_conditional( opcode
, tg
)
508 static short action
[MAX_COND_DEPTH
];
509 static char ifcntl
[MAX_COND_DEPTH
];
511 char *lhs
, *expr
, *expr_end
;
515 DB_ENTER( "_handle_conditional" );
519 if( !(ifcntl
[Nest_level
] & SEEN_IF
) || (ifcntl
[Nest_level
]&SEEN_ELSE
) )
520 Fatal(".ELIF without a preceeding .IF" );
526 if( opcode
!= ST_ELIF
&& (Nest_level
+1) == MAX_COND_DEPTH
)
527 Fatal( ".IF .ELSE ... .END nesting too deep" );
530 expr
= Expand( Get_token( tg
, NIL(char), FALSE
));
533 /* Remove CONTINUATION_CHAR<nl> and replace with " " so that line
534 * continuations are recognized as whitespace. */
535 for( cst
=strchr(expr
,CONTINUATION_CHAR
); cst
!= NIL(char); cst
=strchr(cst
,CONTINUATION_CHAR
) )
536 if( cst
[1] == '\n' ) {
546 /* Parse the expression and get its logical result */
547 if ( ((lop
= DmStrStr(lhs
, "||" )) != NIL(char)) || ((lop
= DmStrStr(lhs
, "&&" )) != NIL(char)) )
548 result
= parse_complex_expression( lhs
, &expr_end
, opcode
);
550 result
= partcomp( lhs
, opcode
);
552 if( expr
!= NIL(char) ) FREE( expr
);
554 if( opcode
!= ST_ELIF
) {
556 action
[Nest_level
] = 1;
558 ifcntl
[Nest_level
] |= (opcode
==ST_ELIF
)?SEEN_ELIF
:SEEN_IF
;
561 if( !(ifcntl
[Nest_level
] & (ACCEPT_IF
|ACCEPT_ELIF
)) ) {
562 action
[ Nest_level
] = action
[ Nest_level
-1 ];
563 ifcntl
[Nest_level
] |= (opcode
==ST_ELIF
)?ACCEPT_ELIF
:ACCEPT_IF
;
566 action
[Nest_level
] = 1;
569 action
[Nest_level
] = 1;
573 if( Nest_level
<= 0 ) Fatal( ".ELSE without .IF" );
574 if( ifcntl
[Nest_level
] & SEEN_ELSE
)
575 Fatal( "Missing .IF or .ELIF before .ELSE" );
577 if( ifcntl
[Nest_level
] & (ACCEPT_IF
|ACCEPT_ELIF
) )
578 action
[Nest_level
] = 1;
579 else if( action
[ Nest_level
-1 ] != 1 )
580 action
[ Nest_level
] ^= 0x1; /* flip between 0 and 1 */
582 ifcntl
[Nest_level
] |= SEEN_ELSE
;
586 ifcntl
[Nest_level
] = SEEN_END
;
588 if( Nest_level
< 0 ) Fatal( "Unmatched .END[IF]" );
592 DB_RETURN( action
[ Nest_level
] );
595 /* uncomment to turn on expression debug statements */
596 /*#define PARSE_DEBUG */
597 #define PARSE_SKIP_WHITE(A) while( *A && ((*A==' ') || (*A=='\t')) ) A++;
605 int parse_complex_expression( char *expr
, char **expr_end
, int opcode
)
608 char *term_start
= p
;
612 int term_result
= FALSE
;
613 int final_result
= TRUE
;
614 unsigned int term_len
;
615 unsigned int last_op
= OP_NONE
;
618 printf( "%d: parse_complex_expression( %s ): Opcode: %d\n", n
, expr
, opcode
);
623 /* A new sub-expression */
628 term_result
= parse_complex_expression( p
+1, &p
, opcode
);
630 PARSE_SKIP_WHITE( p
);
638 /* Lets do an operation!! */
639 if ( !(*p
) /* at the end of the entire line */
640 || ((*p
== '&') && (*(p
+1) && (*(p
+1)=='&'))) /* found an && */
641 || ((*p
== '|') && (*(p
+1) && (*(p
+1)=='|'))) /* found an || */
642 || (*p
== ')') ) /* at the end of our term */
644 /* Grab the sub-expression if we parsed it. Otherwise,
645 * it was a () subexpression and we don't need to evaluate
646 * it since that was already done.
648 if ( local_term
== TRUE
)
650 /* Back up 1 to the end of the actual term */
653 /* Evaluate the term */
654 PARSE_SKIP_WHITE( term_start
);
655 term_len
= term_end
- term_start
+ 1;
656 part
= MALLOC( term_len
+ 1, char );
657 strncpy( part
, term_start
, term_len
);
658 *(part
+term_len
) = '\0';
660 printf( "%d: evaling '%s'\n", n
, part
);
662 term_result
= partcomp( part
, opcode
);
664 printf( "%d: evaled, result %d\n", n
, term_result
);
669 /* Do the actual logical operation using the _preceding_
670 * logical operator, NOT the one we just found.
672 if ( last_op
== OP_AND
)
673 final_result
= final_result
&& term_result
;
674 else if ( last_op
== OP_OR
)
675 final_result
= final_result
|| term_result
;
677 final_result
= term_result
;
679 printf( "%d: final_result:%d\n", n
, final_result
);
682 /* If we're not at the end of the line, just keep going */
685 /* Recognize the operator we just found above */
688 else if ( *p
== '|' )
693 /* Get the start of the next term */
694 PARSE_SKIP_WHITE( p
);
697 /* If this is the close of a term, we are done and return
706 else break; /* At end of line, all done */
708 else if ( local_term
== TRUE
) p
++; /* Advance to next char in expression */
713 printf( "%d: done, returning '%s', result %d\n", n
, *expr_end
, final_result
);
715 return( final_result
);
719 int partcomp( char* lhs
, int opcode
)
722 char *tok
, *rhs
, *op
= 0;
724 const int localopscount
=4;
725 char* localops
[] = { "==", "!=", "<=", ">=" };
731 #define GREATER_EQUAL 3
734 printf( "eval: %s\n", lhs
);
738 if( opcode
== ST_IFEQ
|| opcode
== ST_IFNEQ
)
740 /* IF[N]EQ syntax is: .IF[N]EQ <1> <2>
741 * Here, step over first argument and get to <2> if it exists.
743 for( op
= lhs
; ((*op
)&&(*op
!= ' ')&&(*op
!= '\t')); op
++ );
744 if( *op
) op
++; /* position op at start of <2> */
745 else op
= NIL(char); /* only 1 argument given */
749 /* Find which logical operator we are to use for this expression,
751 while ( (opsind
< localopscount
) && ((op
= DmStrStr(lhs
, localops
[opsind
])) == NIL(char)) )
755 printf(" found op %d: %s\n", opsind
, localops
[opsind
]);
759 /* If the opcode was IFEQ or IFNEQ and only 1 argument was given,
760 * or an unknown logical operator was encountered,
761 * return false if argument is empty string, true if !empty
763 if( op
== NIL(char) )
764 result
= (*lhs
!= '\0');
767 /* Make both characters of the operation the same, replacing the = in op[1]
768 * Its easier to deal with this way???
770 if( opcode
!= ST_IFEQ
&& opcode
!= ST_IFNEQ
)
774 printf(" op:%s\n", op
);
777 /* Isolate the left half of the expression */
780 for( tok
= op
-1; (tok
!= lhs
) && ((*tok
== ' ')||(*tok
== '\t')); tok
-- );
784 lhs
= NIL(char); /* Left hand side is empty. */
786 /* Jump over the operation so we can grab the right half of the expression */
787 if( opcode
== ST_IFEQ
|| opcode
== ST_IFNEQ
)
792 /* Isolate the right half of the expression */
793 rhs
= DmStrSpn( op
+1, " \t" );
794 if( !*rhs
) rhs
= NIL(char);
797 printf(" lhs:%s, rhs:%s\n", lhs
, rhs
);
800 /* Do the actual logical operation on the expression */
801 if ( opsind
> NOTEQUAL
)
807 /* Ignore quotes around the arguments */
808 if ( lhs
&& lhs
[0] == '"' ) lhs
++;
809 if ( rhs
&& rhs
[0] == '"' ) rhs
++;
811 /* Empty strings evaluate to zero. */
812 lint
= lhs
? atoi( lhs
) : 0;
813 rint
= rhs
? atoi( rhs
) : 0;
814 result
= ( lint
>= rint
) ? TRUE
: FALSE
;
815 if ( opsind
== LESS_EQUAL
&& lint
!= rint
)
824 /* Use a simple string compare to determine equality */
825 if( (rhs
== NIL(char)) || (lhs
== NIL(char)) )
826 result
= (rhs
== lhs
) ? TRUE
: FALSE
;
829 /* String off whitespace at the end of the right half of the expression */
830 tok
= rhs
+ strlen( rhs
);
831 for( tok
=tok
-1; (tok
!= lhs
) && ((*tok
== ' ')||(*tok
== '\t')); tok
--);
834 result
= (strcmp( lhs
, rhs
) == 0) ? TRUE
: FALSE
;
837 if( *op
== '!' || opcode
== ST_IFNEQ
) result
= !result
;
842 printf("partresult %d\n\n",result
);