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
) {
178 size_t len
= strlen(q
+1)+1;
179 memmove( q
, q
+1, len
);
183 else if( c
!= NIL(char) )
186 cont
= TRUE
; /* Keep the \<nl>. */
192 q
= ( c
== NIL(char) ) ? q
+2 : c
;
194 else { /* empty line or "" */
197 q
= p
+strlen(p
); /* strlen(p) is 1 or 0 */
202 while( (cont
|| !*buf
) && (pos
< Buffer_size
-1) );
204 if( pos
>= Buffer_size
-1 )
205 Fatal( "Input line too long, increase MAXLINELENGTH" );
207 /* Lines that had comments don't contain \n anymore. */
208 /* ??? Continued lines that are followed by an empty or comment only
209 * line will end in \<nl>. */
210 if( (q
> p
) && (buf
[ pos
-1 ] == '\n') )
211 buf
[ --pos
] = '\0'; /* Remove the final \n. */
213 /* STUPID AUGMAKE uses "include" at the start of a line as
214 * a signal to include a new file, so let's look for it.
215 * if we see it replace it by .INCLUDE: and stick this back
216 * into the buffer. We also allow GNU make if[n]eq/else/endif.
218 * These substitutions are made only if we are not parsing a group
220 if( (p
= DmStrSpn(buf
, " \t\r\n")) == NIL(char) )
224 if( !strncmp( "include", p
, 7 ) &&
225 (p
[7] == ' ' || p
[7] == '\t') )
226 tmp
= DmStrJoin( ".INCLUDE:", p
+7, -1, FALSE
);
227 else if( !strncmp( "ifeq", p
, 4 ) &&
228 (p
[4] == ' ' || p
[4] == '\t') )
229 tmp
= DmStrJoin( ".IFEQ", p
+4, -1, FALSE
);
230 else if( !strncmp( "ifneq", p
, 5 ) &&
231 (p
[5] == ' ' || p
[5] == '\t') )
232 tmp
= DmStrJoin( ".IFNEQ", p
+5, -1, FALSE
);
233 else if( !strncmp( "elif", p
, 4 ) &&
234 (p
[4] == ' ' || p
[4] == '\t') )
235 tmp
= DmStrJoin( ".ELIF", p
+4, -1, FALSE
);
236 else if( !strncmp( "else", p
, 4 ) &&
237 (p
[4] == ' ' || p
[4] == '\t' || p
[4] == '\0') )
238 tmp
= DmStrJoin( ".ELSE", p
+4, -1, FALSE
);
239 else if( !strncmp( "endif", p
, 5 ) &&
240 (p
[5] == ' ' || p
[5] == '\t' || p
[5] == '\0') )
241 tmp
= DmStrJoin( ".END", p
+5, -1, FALSE
);
244 if( tmp
!= NIL(char)) {
250 /* Now that we have the next line of input to make, we should check to
251 * see if it is a conditional expression. If it is then process it,
252 * otherwise pass it on to the parser. */
254 if( *(p
= DmStrSpn(buf
, " \t\r\n")) == CONDSTART
) {
257 SET_TOKEN( &token
, p
);
259 p
= Get_token( &token
, "", FALSE
);
261 if( (res
= _is_conditional(p
)) != 0 ) /* ignore non-control special */
263 res
= _handle_conditional( res
, &token
);
267 CLEAR_TOKEN( &token
);
273 buf
= buf_org
; /* ignore line just read in */
279 DB_PRINT( "io", ("Returning [%s]", buf
) );
285 Do_comment(str
, pend
, keep
)/*
286 =============================
287 Search the input string looking for comment chars. If it contains
288 comment chars then NUKE the remainder of the line, if the comment
289 char is preceeded by \ then shift the remainder of the line left
297 while( (c
= strchr(c
, COMMENT_CHAR
)) != NIL(char) ) {
298 if( Comment
|| State
== NORMAL_SCAN
)
299 if( c
!= str
&& c
[-1] == ESCAPE_CHAR
) {
300 size_t len
= strlen(c
)+1;
301 memmove( c
-1, c
, len
); /* copy it left, due to \# */
302 if( pend
) (*pend
)--; /* shift tail pointer left */
305 /* Check/execute if shebang command is present. */
310 && Nestlevel() == 1 ) {
314 cmnd
[strlen(cmnd
)-1] = '\0'; /* strip last newline */
315 Current_target
= Root
;
319 Wait_for_completion
= TRUE
;
320 Do_cmnd(&cmnd
, FALSE
, TRUE
, Current_target
, A_DEFAULT
, TRUE
);
322 Swap_on_exec
= FALSE
;
324 Wait_for_completion
= FALSE
;
328 *c
= '\0'; /* a true comment so break */
346 Get_token( string
, brk
, anchor
)/*
347 ==================================
348 Return the next token in string.
350 Returns empty string when no more tokens in string.
351 brk is a list of chars that also cause breaks in addition to space and
352 tab, but are themselves returned as tokens. if brk is NULL then the
353 remainder of the line is returned as a single token.
355 'anchor' if 1, says break on chars in the brk list, but only if
356 the entire token begins with the first char of the brk list, if
357 0 then any char of brk will cause a break to occurr.
359 If 'anchor' is 2, then break only seeing the first char in the break
360 list allowing only chars in the break list to form the prefix. */
367 register char *curp
= 0;
372 DB_ENTER( "Get_token" );
374 s
= string
->tk_str
; /* Get string parameters */
375 *s
= string
->tk_cchar
; /* ... and strip leading w/s */
379 DB_PRINT( "tok", ("What's left [%s]", s
) );
382 DB_PRINT( "tok", ("Returning NULL token") );
387 /* Build the space list. space contains all those chars that may possibly
388 * cause breaks. This includes the brk list as well as white space. */
390 if( brk
!= NIL(char) ) {
391 strcpy( space
, " \t\r\n" );
392 strcat( space
, brk
);
395 space
[0] = 0xff; /* a char we know will not show up */
400 /* Handle processing of quoted tokens. Note that this is disabled if
401 * brk is equal to NIL */
403 while( *s
== '\"' && ((brk
!= NIL(char)) || !string
->tk_quote
) ) {
405 if( string
->tk_quote
) {
407 do { curp
= strchr( curp
+1, '\"' ); }
408 while( (curp
!= NIL(char)) && (*(curp
+1) == '\"'));
410 if( curp
== NIL(char) ) Fatal( "Unmatched quote in token" );
411 string
->tk_quote
= !string
->tk_quote
;
413 /* Check for "" case, and if found ignore it */
414 if( curp
== s
) continue;
420 string
->tk_quote
= !string
->tk_quote
;
424 /* Check for a token break character at the beginning of the token.
425 * If found return the next set of break chars as a token. */
427 if( anchor
== 2 && brk
!= NIL(char) ) {
429 while( *curp
&& (strchr(brk
,*curp
)!=NIL(char)) && (*curp
!=*brk
) ) curp
++;
430 done
= (*brk
== *curp
++);
432 else if( (brk
!= NIL(char)) && (strchr( brk
, *s
) != NIL(char)) ) {
433 curp
= DmStrSpn( s
, brk
);
434 done
= (anchor
== 0) ? TRUE
:
435 ((anchor
== 1)?(*s
== *brk
) : (*brk
== curp
[-1]));
439 /* Scan for the next token in the list and return it less the break char
440 * that was used to terminate the token. It will possibly be returned in
441 * the next call to Get_token */
449 curp
= DmStrPbrk(t
, space
);
451 if( anchor
&& *curp
&& !IS_WHITE( *curp
) )
452 if( ((anchor
== 1)?*curp
:DmStrSpn(curp
,brk
)[-1]) != *brk
) {
459 if( (curp
== s
) && (strchr(brk
, *curp
) != NIL(char)) ) curp
++;
463 string
->tk_str
= curp
;
464 string
->tk_cchar
= *curp
;
467 DB_PRINT( "tok", ("Returning [%s]", s
) );
473 _is_conditional( tg
)/*
474 =======================
475 Look at tg and return it's value if it is a conditional identifier
476 otherwise return 0. */
479 DB_ENTER( "_is_conditional" );
485 if( !strcmp( tg
, "IF" )) DB_RETURN( ST_IF
);
486 else if( !strcmp( tg
, "IFEQ" )) DB_RETURN( ST_IFEQ
);
487 else if( !strcmp( tg
, "IFNEQ" )) DB_RETURN( ST_IFNEQ
);
491 if( !strcmp( tg
, "END" )) DB_RETURN( ST_END
);
492 else if( !strcmp( tg
, "ENDIF")) DB_RETURN( ST_END
);
493 else if( !strcmp( tg
, "ELSE" )) DB_RETURN( ST_ELSE
);
494 else if( !strcmp( tg
, "ELIF" )) DB_RETURN( ST_ELIF
);
503 #define SEEN_END 0x00
505 #define SEEN_ELSE 0x02
506 #define SEEN_ELIF 0x04
508 #define ACCEPT_IF 0x10
509 #define ACCEPT_ELIF 0x20
512 _handle_conditional( opcode
, tg
)
516 static short action
[MAX_COND_DEPTH
];
517 static char ifcntl
[MAX_COND_DEPTH
];
519 char *lhs
, *expr
, *expr_end
;
523 DB_ENTER( "_handle_conditional" );
527 if( !(ifcntl
[Nest_level
] & SEEN_IF
) || (ifcntl
[Nest_level
]&SEEN_ELSE
) )
528 Fatal(".ELIF without a preceeding .IF" );
534 if( opcode
!= ST_ELIF
&& (Nest_level
+1) == MAX_COND_DEPTH
)
535 Fatal( ".IF .ELSE ... .END nesting too deep" );
538 expr
= Expand( Get_token( tg
, NIL(char), FALSE
));
541 /* Remove CONTINUATION_CHAR<nl> and replace with " " so that line
542 * continuations are recognized as whitespace. */
543 for( cst
=strchr(expr
,CONTINUATION_CHAR
); cst
!= NIL(char); cst
=strchr(cst
,CONTINUATION_CHAR
) )
544 if( cst
[1] == '\n' ) {
554 /* Parse the expression and get its logical result */
555 if ( ((lop
= DmStrStr(lhs
, "||" )) != NIL(char)) || ((lop
= DmStrStr(lhs
, "&&" )) != NIL(char)) )
556 result
= parse_complex_expression( lhs
, &expr_end
, opcode
);
558 result
= partcomp( lhs
, opcode
);
560 if( expr
!= NIL(char) ) FREE( expr
);
562 if( opcode
!= ST_ELIF
) {
564 action
[Nest_level
] = 1;
566 ifcntl
[Nest_level
] |= (opcode
==ST_ELIF
)?SEEN_ELIF
:SEEN_IF
;
569 if( !(ifcntl
[Nest_level
] & (ACCEPT_IF
|ACCEPT_ELIF
)) ) {
570 action
[ Nest_level
] = action
[ Nest_level
-1 ];
571 ifcntl
[Nest_level
] |= (opcode
==ST_ELIF
)?ACCEPT_ELIF
:ACCEPT_IF
;
574 action
[Nest_level
] = 1;
577 action
[Nest_level
] = 1;
581 if( Nest_level
<= 0 ) Fatal( ".ELSE without .IF" );
582 if( ifcntl
[Nest_level
] & SEEN_ELSE
)
583 Fatal( "Missing .IF or .ELIF before .ELSE" );
585 if( ifcntl
[Nest_level
] & (ACCEPT_IF
|ACCEPT_ELIF
) )
586 action
[Nest_level
] = 1;
587 else if( action
[ Nest_level
-1 ] != 1 )
588 action
[ Nest_level
] ^= 0x1; /* flip between 0 and 1 */
590 ifcntl
[Nest_level
] |= SEEN_ELSE
;
594 ifcntl
[Nest_level
] = SEEN_END
;
596 if( Nest_level
< 0 ) Fatal( "Unmatched .END[IF]" );
600 DB_RETURN( action
[ Nest_level
] );
603 /* uncomment to turn on expression debug statements */
604 /*#define PARSE_DEBUG */
605 #define PARSE_SKIP_WHITE(A) while( *A && ((*A==' ') || (*A=='\t')) ) A++;
613 int parse_complex_expression( char *expr
, char **expr_end
, int opcode
)
616 char *term_start
= p
;
620 int term_result
= FALSE
;
621 int final_result
= TRUE
;
622 unsigned int term_len
;
623 unsigned int last_op
= OP_NONE
;
626 printf( "%d: parse_complex_expression( %s ): Opcode: %d\n", n
, expr
, opcode
);
631 /* A new sub-expression */
636 term_result
= parse_complex_expression( p
+1, &p
, opcode
);
638 PARSE_SKIP_WHITE( p
);
646 /* Lets do an operation!! */
647 if ( !(*p
) /* at the end of the entire line */
648 || ((*p
== '&') && (*(p
+1) && (*(p
+1)=='&'))) /* found an && */
649 || ((*p
== '|') && (*(p
+1) && (*(p
+1)=='|'))) /* found an || */
650 || (*p
== ')') ) /* at the end of our term */
652 /* Grab the sub-expression if we parsed it. Otherwise,
653 * it was a () subexpression and we don't need to evaluate
654 * it since that was already done.
656 if ( local_term
== TRUE
)
658 /* Back up 1 to the end of the actual term */
661 /* Evaluate the term */
662 PARSE_SKIP_WHITE( term_start
);
663 term_len
= term_end
- term_start
+ 1;
664 part
= MALLOC( term_len
+ 1, char );
665 strncpy( part
, term_start
, term_len
);
666 *(part
+term_len
) = '\0';
668 printf( "%d: evaling '%s'\n", n
, part
);
670 term_result
= partcomp( part
, opcode
);
672 printf( "%d: evaled, result %d\n", n
, term_result
);
677 /* Do the actual logical operation using the _preceding_
678 * logical operator, NOT the one we just found.
680 if ( last_op
== OP_AND
)
681 final_result
= final_result
&& term_result
;
682 else if ( last_op
== OP_OR
)
683 final_result
= final_result
|| term_result
;
685 final_result
= term_result
;
687 printf( "%d: final_result:%d\n", n
, final_result
);
690 /* If we're not at the end of the line, just keep going */
693 /* Recognize the operator we just found above */
696 else if ( *p
== '|' )
701 /* Get the start of the next term */
702 PARSE_SKIP_WHITE( p
);
705 /* If this is the close of a term, we are done and return
714 else break; /* At end of line, all done */
716 else if ( local_term
== TRUE
) p
++; /* Advance to next char in expression */
721 printf( "%d: done, returning '%s', result %d\n", n
, *expr_end
, final_result
);
723 return( final_result
);
727 int partcomp( char* lhs
, int opcode
)
730 char *tok
, *rhs
, *op
= 0;
732 const int localopscount
=4;
733 char* localops
[] = { "==", "!=", "<=", ">=" };
739 #define GREATER_EQUAL 3
742 printf( "eval: %s\n", lhs
);
746 if( opcode
== ST_IFEQ
|| opcode
== ST_IFNEQ
)
748 /* IF[N]EQ syntax is: .IF[N]EQ <1> <2>
749 * Here, step over first argument and get to <2> if it exists.
751 for( op
= lhs
; ((*op
)&&(*op
!= ' ')&&(*op
!= '\t')); op
++ );
752 if( *op
) op
++; /* position op at start of <2> */
753 else op
= NIL(char); /* only 1 argument given */
757 /* Find which logical operator we are to use for this expression,
759 while ( (opsind
< localopscount
) && ((op
= DmStrStr(lhs
, localops
[opsind
])) == NIL(char)) )
763 printf(" found op %d: %s\n", opsind
, localops
[opsind
]);
767 /* If the opcode was IFEQ or IFNEQ and only 1 argument was given,
768 * or an unknown logical operator was encountered,
769 * return false if argument is empty string, true if !empty
771 if( op
== NIL(char) )
772 result
= (*lhs
!= '\0');
775 /* Make both characters of the operation the same, replacing the = in op[1]
776 * Its easier to deal with this way???
778 if( opcode
!= ST_IFEQ
&& opcode
!= ST_IFNEQ
)
782 printf(" op:%s\n", op
);
785 /* Isolate the left half of the expression */
788 for( tok
= op
-1; (tok
!= lhs
) && ((*tok
== ' ')||(*tok
== '\t')); tok
-- );
792 lhs
= NIL(char); /* Left hand side is empty. */
794 /* Jump over the operation so we can grab the right half of the expression */
795 if( opcode
== ST_IFEQ
|| opcode
== ST_IFNEQ
)
800 /* Isolate the right half of the expression */
801 rhs
= DmStrSpn( op
+1, " \t" );
802 if( !*rhs
) rhs
= NIL(char);
805 printf(" lhs:%s, rhs:%s\n", lhs
, rhs
);
808 /* Do the actual logical operation on the expression */
809 if ( opsind
> NOTEQUAL
)
815 /* Ignore quotes around the arguments */
816 if ( lhs
&& lhs
[0] == '"' ) lhs
++;
817 if ( rhs
&& rhs
[0] == '"' ) rhs
++;
819 /* Empty strings evaluate to zero. */
820 lint
= lhs
? atoi( lhs
) : 0;
821 rint
= rhs
? atoi( rhs
) : 0;
822 result
= ( lint
>= rint
) ? TRUE
: FALSE
;
823 if ( opsind
== LESS_EQUAL
&& lint
!= rint
)
832 /* Use a simple string compare to determine equality */
833 if( (rhs
== NIL(char)) || (lhs
== NIL(char)) )
834 result
= (rhs
== lhs
) ? TRUE
: FALSE
;
837 /* String off whitespace at the end of the right half of the expression */
838 tok
= rhs
+ strlen( rhs
);
839 for( tok
=tok
-1; (tok
!= lhs
) && ((*tok
== ' ')||(*tok
== '\t')); tok
--);
842 result
= (strcmp( lhs
, rhs
) == 0) ? TRUE
: FALSE
;
845 if( *op
== '!' || opcode
== ST_IFNEQ
) result
= !result
;
850 printf("partresult %d\n\n",result
);