915 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			915 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands.
 | |
|  * See the copyright notice in the ACK home directory, in the file "Copyright".
 | |
|  */
 | |
| /* $Id$ */
 | |
| /* PREPROCESSOR: CONTROLLINE INTERPRETER */
 | |
| 
 | |
| #include <assert.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include "arith.h"
 | |
| #include "LLlex.h"
 | |
| #include "Lpars.h"
 | |
| #include "idf.h"
 | |
| #include "input.h"
 | |
| 
 | |
| #include "parameters.h"
 | |
| #include <alloc.h>
 | |
| #include "class.h"
 | |
| #include "macro.h"
 | |
| #include "bits.h"
 | |
| #include "replace.h"
 | |
| 
 | |
| extern char options[];
 | |
| extern char** inctable; /* list of include directories		*/
 | |
| extern char* getwdir();
 | |
| char ifstack[IFDEPTH]; /* if-stack: the content of an entry is	*/
 | |
| /* 1 if a corresponding ELSE has been	*/
 | |
| /* encountered.				*/
 | |
| 
 | |
| int nestlevel = -1;
 | |
| int svnestlevel[30] = { -1 };
 | |
| int nestcount;
 | |
| extern int do_preprocess;
 | |
| 
 | |
| void macro_def();
 | |
| void do_define();
 | |
| 
 | |
| char* GetIdentifier(skiponerr) int skiponerr; /* skip the rest of the line on error */
 | |
| {
 | |
| 	/*	Returns a pointer to the identifier that is read from the
 | |
| 	    input stream. When the input does not contain an
 | |
| 	    identifier, the rest of the line is skipped when skiponerr
 | |
| 	    is on, and a null-pointer is returned.
 | |
| 	    The substitution of macros is disabled.
 | |
| 	    Remember that on end-of-line EOF is returned.
 | |
| 	*/
 | |
| 	int tmp = UnknownIdIsZero;
 | |
| 	int tok;
 | |
| 	struct token tk;
 | |
| 
 | |
| 	UnknownIdIsZero = ReplaceMacros = 0;
 | |
| 	tok = GetToken(&tk);
 | |
| 	ReplaceMacros = 1;
 | |
| 	UnknownIdIsZero = tmp;
 | |
| 	if (tok != IDENTIFIER)
 | |
| 	{
 | |
| 		if (skiponerr && tok != EOF)
 | |
| 			SkipToNewLine();
 | |
| 		return (char*)0;
 | |
| 	}
 | |
| 	return tk.tk_str;
 | |
| }
 | |
| 
 | |
| /*	domacro() is the control line interpreter. The '#' has already
 | |
|     been read by the lexical analyzer by which domacro() is called.
 | |
|     The token appearing directly after the '#' is obtained by calling
 | |
|     the basic lexical analyzing function GetToken() and is interpreted
 | |
|     to perform the action belonging to that token.
 | |
|     An error message is produced when the token is not recognized.
 | |
|     Pragma's are handled by do_pragma(). They are passed on to the
 | |
|     compiler.
 | |
| */
 | |
| domacro()
 | |
| {
 | |
| 	struct token tk; /* the token itself			*/
 | |
| 	register struct idf* id;
 | |
| 	int toknum;
 | |
| 
 | |
| 	ReplaceMacros = 0;
 | |
| 	toknum = GetToken(&tk);
 | |
| 	ReplaceMacros = 1;
 | |
| 	switch (toknum)
 | |
| 	{ /* select control line action	*/
 | |
| 		case IDENTIFIER: /* is it a macro keyword?	*/
 | |
| 			id = findidf(tk.tk_str);
 | |
| 			if (!id)
 | |
| 			{
 | |
| 				error("%s: unknown control", tk.tk_str);
 | |
| 				SkipToNewLine();
 | |
| 				free(tk.tk_str);
 | |
| 				break;
 | |
| 			}
 | |
| 			free(tk.tk_str);
 | |
| 			switch (id->id_resmac)
 | |
| 			{
 | |
| 				case K_DEFINE: /* "define"	*/
 | |
| 					do_define();
 | |
| 					break;
 | |
| 				case K_ELIF: /* "elif"	*/
 | |
| 					do_elif();
 | |
| 					break;
 | |
| 				case K_ELSE: /* "else"	*/
 | |
| 					do_else();
 | |
| 					break;
 | |
| 				case K_ENDIF: /* "endif"	*/
 | |
| 					do_endif();
 | |
| 					break;
 | |
| 				case K_IF: /* "if"		*/
 | |
| 					do_if();
 | |
| 					break;
 | |
| 				case K_IFDEF: /* "ifdef"	*/
 | |
| 					do_ifdef(1);
 | |
| 					break;
 | |
| 				case K_IFNDEF: /* "ifndef"	*/
 | |
| 					do_ifdef(0);
 | |
| 					break;
 | |
| 				case K_INCLUDE: /* "include"	*/
 | |
| 					do_include();
 | |
| 					break;
 | |
| 				case K_LINE: /* "line"	*/
 | |
| 					/*	set LineNumber and FileName according to
 | |
| 					    the arguments.
 | |
| 					*/
 | |
| 					if (GetToken(&tk) != INTEGER)
 | |
| 					{
 | |
| 						error("bad #line syntax");
 | |
| 						SkipToNewLine();
 | |
| 					}
 | |
| 					else
 | |
| 						do_line((unsigned int)tk.tk_val);
 | |
| 					break;
 | |
| 				case K_ERROR: /* "error"	*/
 | |
| 					do_error();
 | |
| 					break;
 | |
| 				case K_PRAGMA: /* "pragma"	*/
 | |
| 					do_pragma();
 | |
| 					break;
 | |
| 				case K_UNDEF: /* "undef"	*/
 | |
| 					do_undef((char*)0);
 | |
| 					break;
 | |
| 				default:
 | |
| 					/* invalid word seen after the '#'	*/
 | |
| 					error("%s: unknown control", id->id_text);
 | |
| 					SkipToNewLine();
 | |
| 			}
 | |
| 			break;
 | |
| 		case INTEGER: /* # <integer> [<filespecifier>]?	*/
 | |
| 			do_line((unsigned int)tk.tk_val);
 | |
| 			break;
 | |
| 		case EOF: /* only `#' on this line: do nothing, ignore	*/
 | |
| 			break;
 | |
| 		default: /* invalid token following '#'		*/
 | |
| 			error("illegal # line");
 | |
| 			SkipToNewLine();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void skip_block(to_endif) int to_endif;
 | |
| {
 | |
| 	/*	skip_block() skips the input from
 | |
| 	    1)	a false #if, #ifdef, #ifndef or #elif until the
 | |
| 	        corresponding #elif (resulting in true), #else or
 | |
| 	        #endif is read.
 | |
| 	    2)	a #else corresponding to a true #if, #ifdef,
 | |
| 	        #ifndef or #elif until the corresponding #endif is
 | |
| 	        seen.
 | |
| 	*/
 | |
| 	register int ch;
 | |
| 	register int skiplevel = nestlevel; /* current nesting level	*/
 | |
| 	struct token tk;
 | |
| 	int toknum;
 | |
| 	struct idf* id;
 | |
| 
 | |
| 	NoUnstack++;
 | |
| 	for (;;)
 | |
| 	{
 | |
| 		ch = GetChar(); /* read first character after newline	*/
 | |
| 		while (class(ch) == STSKIP)
 | |
| 			ch = GetChar();
 | |
| 		if (ch != '#')
 | |
| 		{
 | |
| 			if (ch == EOI)
 | |
| 			{
 | |
| 				NoUnstack--;
 | |
| 				return;
 | |
| 			}
 | |
| 			if (ch == '/')
 | |
| 			{
 | |
| 				ch = GetChar();
 | |
| 				if (ch == '*')
 | |
| 				{
 | |
| 					skipcomment();
 | |
| 					continue;
 | |
| 				}
 | |
| 				else if (ch == '/')
 | |
| 				{
 | |
| 					skiplinecomment();
 | |
| 					continue;
 | |
| 				}
 | |
| 				else
 | |
| 					UnGetChar();
 | |
| 			}
 | |
| 			else
 | |
| 				UnGetChar();
 | |
| 			SkipToNewLine();
 | |
| 			continue;
 | |
| 		}
 | |
| 		ReplaceMacros = 0;
 | |
| 		toknum = GetToken(&tk);
 | |
| 		ReplaceMacros = 1;
 | |
| 		if (toknum != IDENTIFIER)
 | |
| 		{
 | |
| 			if (toknum != INTEGER)
 | |
| 			{
 | |
| 				error("illegal # line");
 | |
| 			}
 | |
| 			SkipToNewLine();
 | |
| 			continue;
 | |
| 		}
 | |
| 		/*	an IDENTIFIER: look for #if, #ifdef and #ifndef
 | |
| 		    without interpreting them.
 | |
| 		    Interpret #else, #elif and #endif if they occur
 | |
| 		    on the same level.
 | |
| 		*/
 | |
| 		id = findidf(tk.tk_str);
 | |
| 		if (id == (struct idf*)0)
 | |
| 		{
 | |
| 			/* invalid word seen after the '#' */
 | |
| 			warning("%s: unknown control", tk.tk_str);
 | |
| 		}
 | |
| 		free(tk.tk_str);
 | |
| 		if (id == (struct idf*)0)
 | |
| 			continue;
 | |
| 		switch (id->id_resmac)
 | |
| 		{
 | |
| 			case K_DEFINE:
 | |
| 			case K_ERROR:
 | |
| 			case K_INCLUDE:
 | |
| 			case K_LINE:
 | |
| 			case K_PRAGMA:
 | |
| 			case K_UNDEF:
 | |
| 			case K_FILE:
 | |
| 				SkipToNewLine();
 | |
| 				break;
 | |
| 			case K_IF:
 | |
| 			case K_IFDEF:
 | |
| 			case K_IFNDEF:
 | |
| 				push_if();
 | |
| 				SkipToNewLine();
 | |
| 				break;
 | |
| 			case K_ELIF:
 | |
| 				if (ifstack[nestlevel])
 | |
| 					error("#elif after #else");
 | |
| 				if (!to_endif && nestlevel == skiplevel)
 | |
| 				{
 | |
| 					nestlevel--;
 | |
| 					push_if();
 | |
| 					if (ifexpr())
 | |
| 					{
 | |
| 						NoUnstack--;
 | |
| 						return;
 | |
| 					}
 | |
| 				}
 | |
| 				else
 | |
| 					SkipToNewLine(); /* otherwise done in ifexpr() */
 | |
| 				break;
 | |
| 			case K_ELSE:
 | |
| 				if (ifstack[nestlevel])
 | |
| 					error("#else after #else");
 | |
| 				++(ifstack[nestlevel]);
 | |
| 				if (!to_endif && nestlevel == skiplevel)
 | |
| 				{
 | |
| 					if (SkipToNewLine())
 | |
| 					{
 | |
| 						if (!options['o'])
 | |
| 							strict("garbage following #else");
 | |
| 					}
 | |
| 					NoUnstack--;
 | |
| 					return;
 | |
| 				}
 | |
| 				else
 | |
| 					SkipToNewLine();
 | |
| 				break;
 | |
| 			case K_ENDIF:
 | |
| 				assert(nestlevel > svnestlevel[nestcount]);
 | |
| 				if (nestlevel == skiplevel)
 | |
| 				{
 | |
| 					if (SkipToNewLine())
 | |
| 					{
 | |
| 						if (!options['o'])
 | |
| 							strict("garbage following #endif");
 | |
| 					}
 | |
| 					nestlevel--;
 | |
| 					NoUnstack--;
 | |
| 					return;
 | |
| 				}
 | |
| 				else
 | |
| 					SkipToNewLine();
 | |
| 				nestlevel--;
 | |
| 				break;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| ifexpr()
 | |
| {
 | |
| 	/*	ifexpr() returns whether the restricted constant
 | |
| 	    expression following #if or #elif evaluates to true.  This
 | |
| 	    is done by calling the LLgen generated subparser for
 | |
| 	    constant expressions.  The result of this expression will
 | |
| 	    be given in the extern long variable "ifval".
 | |
| 	*/
 | |
| 	extern arith ifval;
 | |
| 	int errors = err_occurred;
 | |
| 
 | |
| 	ifval = (arith)0;
 | |
| 	AccDefined = 1;
 | |
| 	UnknownIdIsZero = 1;
 | |
| 	DOT = 0; /* tricky */
 | |
| 	If_expr(); /* invoke constant expression parser	*/
 | |
| 	AccDefined = 0;
 | |
| 	UnknownIdIsZero = 0;
 | |
| 	return (errors == err_occurred) && (ifval != (arith)0);
 | |
| }
 | |
| 
 | |
| do_include()
 | |
| {
 | |
| 	/*	do_include() performs the inclusion of a file.
 | |
| 	*/
 | |
| 	char* filenm;
 | |
| 	char* result;
 | |
| 	int tok;
 | |
| 	struct token tk;
 | |
| 
 | |
| 	AccFileSpecifier = 1;
 | |
| 	if (((tok = GetToken(&tk)) == FILESPECIFIER) || tok == STRING)
 | |
| 		filenm = tk.tk_str;
 | |
| 	else
 | |
| 	{
 | |
| 		error("bad include syntax");
 | |
| 		filenm = (char*)0;
 | |
| 	}
 | |
| 	AccFileSpecifier = 0;
 | |
| 	if (SkipToNewLine())
 | |
| 	{
 | |
| 		error("bad include syntax");
 | |
| 	}
 | |
| 	inctable[0] = WorkingDir;
 | |
| 	if (filenm)
 | |
| 	{
 | |
| 		if (!InsertFile(filenm, &inctable[tok == FILESPECIFIER], &result))
 | |
| 		{
 | |
| 			if (do_preprocess)
 | |
| 				error("cannot open include file \"%s\"", filenm);
 | |
| 			else
 | |
| 				warning("cannot open include file \"%s\"", filenm);
 | |
| 			add_dependency(filenm);
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			add_dependency(result);
 | |
| 			WorkingDir = getwdir(result);
 | |
| 			svnestlevel[++nestcount] = nestlevel;
 | |
| 			FileName = result;
 | |
| 			LineNumber = 1;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void do_define()
 | |
| {
 | |
| 	/*	do_define() interprets a #define control line.
 | |
| 	*/
 | |
| 	register char* str; /* the #defined identifier's descriptor	*/
 | |
| 	int nformals = -1; /* keep track of the number of formals	*/
 | |
| 	char* formals[NPARAMS]; /* pointers to the names of the formals	*/
 | |
| 	char parbuf[PARBUFSIZE]; /* names of formals	*/
 | |
| 	char* repl_text; /* start of the replacement text	*/
 | |
| 	int length; /* length of the replacement text	*/
 | |
| 	register ch;
 | |
| 	char* get_text();
 | |
| 
 | |
| 	/* read the #defined macro's name	*/
 | |
| 	if (!(str = GetIdentifier(1)))
 | |
| 	{
 | |
| 		error("#define: illegal macro name");
 | |
| 		return;
 | |
| 	}
 | |
| 	/*	there is a formal parameter list if the identifier is
 | |
| 	    followed immediately by a '('.
 | |
| 	*/
 | |
| 	ch = GetChar();
 | |
| 	if (ch == '(')
 | |
| 	{
 | |
| 		if ((nformals = getparams(formals, parbuf)) == -1)
 | |
| 		{
 | |
| 			SkipToNewLine();
 | |
| 			free(str);
 | |
| 			return; /* an error occurred	*/
 | |
| 		}
 | |
| 		ch = GetChar();
 | |
| 	}
 | |
| 	/* read the replacement text if there is any			*/
 | |
| 	ch = skipspaces(ch, 0); /* find first character of the text	*/
 | |
| 	assert(ch != EOI);
 | |
| 	/* UnGetChar() is not right when replacement starts with a '/' */
 | |
| 	ChPushBack(ch);
 | |
| 	repl_text = get_text((nformals > 0) ? formals : 0, &length);
 | |
| 	macro_def(str2idf(str, 0), repl_text, nformals, length, NOFLAG);
 | |
| 	LineNumber++;
 | |
| }
 | |
| 
 | |
| push_if()
 | |
| {
 | |
| 	if (nestlevel >= IFDEPTH)
 | |
| 		fatal("too many nested #if/#ifdef/#ifndef");
 | |
| 	else
 | |
| 		ifstack[++nestlevel] = 0;
 | |
| }
 | |
| 
 | |
| do_elif()
 | |
| {
 | |
| 	if (nestlevel <= svnestlevel[nestcount])
 | |
| 	{
 | |
| 		error("#elif without corresponding #if");
 | |
| 		SkipToNewLine();
 | |
| 	}
 | |
| 	else
 | |
| 	{ /* restart at this level as if a #if is detected.  */
 | |
| 		if (ifstack[nestlevel])
 | |
| 		{
 | |
| 			error("#elif after #else");
 | |
| 			SkipToNewLine();
 | |
| 		}
 | |
| 		nestlevel--;
 | |
| 		push_if();
 | |
| 		skip_block(1);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| do_else()
 | |
| {
 | |
| 	if (SkipToNewLine())
 | |
| 	{
 | |
| 		if (!options['o'])
 | |
| 			strict("garbage following #else");
 | |
| 	}
 | |
| 	if (nestlevel <= svnestlevel[nestcount])
 | |
| 		error("#else without corresponding #if");
 | |
| 	else
 | |
| 	{ /* mark this level as else-d */
 | |
| 		if (ifstack[nestlevel])
 | |
| 		{
 | |
| 			error("#else after #else");
 | |
| 		}
 | |
| 		++(ifstack[nestlevel]);
 | |
| 		skip_block(1);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| do_endif()
 | |
| {
 | |
| 	if (SkipToNewLine())
 | |
| 	{
 | |
| 		if (!options['o'])
 | |
| 			strict("garbage following #endif");
 | |
| 	}
 | |
| 	if (nestlevel <= svnestlevel[nestcount])
 | |
| 	{
 | |
| 		error("#endif without corresponding #if");
 | |
| 	}
 | |
| 	else
 | |
| 		nestlevel--;
 | |
| }
 | |
| 
 | |
| do_if()
 | |
| {
 | |
| 	push_if();
 | |
| 	if (!ifexpr()) /* a false #if/#elif expression */
 | |
| 		skip_block(0);
 | |
| }
 | |
| 
 | |
| do_ifdef(how)
 | |
| {
 | |
| 	register struct idf* id;
 | |
| 	register char* str;
 | |
| 
 | |
| 	/*	how == 1 : ifdef; how == 0 : ifndef
 | |
| 	*/
 | |
| 	push_if();
 | |
| 	if (!(str = GetIdentifier(1)))
 | |
| 	{
 | |
| 		error("illegal #ifdef construction");
 | |
| 		id = (struct idf*)0;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		id = findidf(str);
 | |
| 		free(str);
 | |
| 	}
 | |
| 	if (SkipToNewLine())
 | |
| 	{
 | |
| 		if (str && !options['o'])
 | |
| 			strict("garbage following #%s <identifier>", how ? "ifdef" : "ifndef");
 | |
| 	}
 | |
| 
 | |
| 	/* The next test is a shorthand for:
 | |
| 	    (how && !id->id_macro) || (!how && id->id_macro)
 | |
| 	*/
 | |
| 	if (how ^ (id && id->id_macro != 0))
 | |
| 		skip_block(0);
 | |
| }
 | |
| 
 | |
| /* argstr != NULL when the undef came from a -U option */
 | |
| do_undef(argstr) char* argstr;
 | |
| {
 | |
| 	register struct idf* id;
 | |
| 	register char* str = argstr;
 | |
| 
 | |
| 	/* Forget a macro definition.	*/
 | |
| 	if (str || (str = GetIdentifier(1)))
 | |
| 	{
 | |
| 		if ((id = findidf(str)) && id->id_macro)
 | |
| 		{
 | |
| 			if (id->id_macro->mc_flag & NOUNDEF)
 | |
| 			{
 | |
| 				error("it is not allowed to #undef %s", str);
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				free(id->id_macro->mc_text);
 | |
| 				free_macro(id->id_macro);
 | |
| 				id->id_macro = (struct macro*)0;
 | |
| 			}
 | |
| 		} /* else: don't complain */
 | |
| 		if (!argstr)
 | |
| 		{
 | |
| 			free(str);
 | |
| 			if (SkipToNewLine())
 | |
| 			{
 | |
| 				if (!options['o'])
 | |
| 					strict("garbage following #undef");
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	else
 | |
| 		error("illegal #undef construction");
 | |
| }
 | |
| 
 | |
| do_error()
 | |
| {
 | |
| 	int len;
 | |
| 	char* get_text();
 | |
| 	char* bp = get_text((char**)0, &len);
 | |
| 
 | |
| 	error("user error: %s", bp);
 | |
| 	free(bp);
 | |
| 	LineNumber++;
 | |
| }
 | |
| 
 | |
| int getparams(buf, parbuf) char* buf[];
 | |
| char parbuf[];
 | |
| {
 | |
| 	/*	getparams() reads the formal parameter list of a macro
 | |
| 	    definition.
 | |
| 	    The number of parameters is returned.
 | |
| 	    As a formal parameter list is expected when calling this
 | |
| 	    routine, -1 is returned if an error is detected, for
 | |
| 	    example:
 | |
| 	        #define one(1), where 1 is not an identifier.
 | |
| 	    Note that the '(' has already been eaten.
 | |
| 	    The names of the formal parameters are stored into parbuf.
 | |
| 	*/
 | |
| 	register char** pbuf = &buf[0];
 | |
| 	register int c;
 | |
| 	register char* ptr = &parbuf[0];
 | |
| 	register char** pbuf2;
 | |
| 
 | |
| 	c = GetChar();
 | |
| 	c = skipspaces(c, 0);
 | |
| 	if (c == ')')
 | |
| 	{ /* no parameters: #define name()	*/
 | |
| 		*pbuf = (char*)0;
 | |
| 		return 0;
 | |
| 	}
 | |
| 	for (;;)
 | |
| 	{ /* eat the formal parameter list	*/
 | |
| 		if (class(c) != STIDF && class(c) != STELL)
 | |
| 		{
 | |
| 			error("#define: bad formal parameter");
 | |
| 			return -1;
 | |
| 		}
 | |
| 		*pbuf = ptr; /* name of the formal	*/
 | |
| 		*ptr++ = c;
 | |
| 		if (ptr >= &parbuf[PARBUFSIZE])
 | |
| 			fatal("formal parameter buffer overflow");
 | |
| 		do
 | |
| 		{ /* eat the identifier name	*/
 | |
| 			c = GetChar();
 | |
| 			*ptr++ = c;
 | |
| 			if (ptr >= &parbuf[PARBUFSIZE])
 | |
| 				fatal("formal parameter buffer overflow");
 | |
| 		} while (in_idf(c));
 | |
| 		*(ptr - 1) = '\0'; /* mark end of the name		*/
 | |
| 
 | |
| 		/*	Check if this formal parameter is already used.
 | |
| 		    Usually, macros do not have many parameters, so ...
 | |
| 		*/
 | |
| 		for (pbuf2 = pbuf - 1; pbuf2 >= &buf[0]; pbuf2--)
 | |
| 		{
 | |
| 			if (!strcmp(*pbuf2, *pbuf))
 | |
| 			{
 | |
| 				warning("formal parameter \"%s\" already used", *pbuf);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		pbuf++;
 | |
| 		c = skipspaces(c, 0);
 | |
| 		if (c == ')')
 | |
| 		{ /* end of the formal parameter list	*/
 | |
| 			*pbuf = (char*)0;
 | |
| 			return pbuf - buf;
 | |
| 		}
 | |
| 		if (c != ',')
 | |
| 		{
 | |
| 			error("#define: bad formal parameter list");
 | |
| 			return -1;
 | |
| 		}
 | |
| 		c = GetChar();
 | |
| 		c = skipspaces(c, 0);
 | |
| 	}
 | |
| 	/*NOTREACHED*/
 | |
| }
 | |
| 
 | |
| void macro_def(id, text, nformals, length, flags) register struct idf* id;
 | |
| char* text;
 | |
| {
 | |
| 	register struct macro* newdef = id->id_macro;
 | |
| 
 | |
| 	/*	macro_def() puts the contents and information of a macro
 | |
| 	    definition into a structure and stores it into the symbol
 | |
| 	    table entry belonging to the name of the macro.
 | |
| 	    An error is given if there was already a definition
 | |
| 	*/
 | |
| 	if (newdef)
 | |
| 	{ /* is there a redefinition?	*/
 | |
| 		if (newdef->mc_flag & NOUNDEF)
 | |
| 		{
 | |
| 			error("it is not allowed to redefine %s", id->id_text);
 | |
| 		}
 | |
| 		else if (!macroeq(newdef->mc_text, text))
 | |
| 			error("illegal redefine of \"%s\"", id->id_text);
 | |
| 		free(text);
 | |
| 		return;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| #ifdef DOBITS
 | |
| 		register char* p = id->id_text;
 | |
| #define setbit(bx)                                                                                 \
 | |
| 	if (!*p)                                                                                       \
 | |
| 		goto go_on;                                                                                \
 | |
| 	bits[*p++] |= (bx)
 | |
| 		setbit(bit0);
 | |
| 		setbit(bit1);
 | |
| 		setbit(bit2);
 | |
| 		setbit(bit3);
 | |
| 		setbit(bit4);
 | |
| 		setbit(bit5);
 | |
| 		setbit(bit6);
 | |
| 		setbit(bit7);
 | |
| 
 | |
| 	go_on:
 | |
| #endif
 | |
| 		id->id_macro = newdef = new_macro();
 | |
| 	}
 | |
| 	newdef->mc_text = text; /* replacement text	*/
 | |
| 	newdef->mc_nps = nformals; /* nr of formals	*/
 | |
| 	newdef->mc_length = length; /* length of repl. text	*/
 | |
| 	newdef->mc_flag = flags; /* special flags	*/
 | |
| }
 | |
| 
 | |
| int find_name(nm, index) char *nm, *index[];
 | |
| {
 | |
| 	/*	find_name() returns the index of "nm" in the namelist
 | |
| 	    "index" if it can be found there.  0 is returned if it is
 | |
| 	    not there.
 | |
| 	*/
 | |
| 	register char** ip = &index[0];
 | |
| 
 | |
| 	while (*ip)
 | |
| 		if (strcmp(nm, *ip++) == 0)
 | |
| 			return ip - &index[0];
 | |
| 	/* arrived here, nm is not in the name list.	*/
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| #define BLANK(ch) ((ch == ' ') || (ch == '\t'))
 | |
| 
 | |
| char* get_text(formals, length) char* formals[];
 | |
| int* length;
 | |
| {
 | |
| 	/*	get_text() copies the replacement text of a macro
 | |
| 	    definition with zero, one or more parameters, thereby
 | |
| 	    substituting each formal parameter by a special character
 | |
| 	    (non-ascii: 0200 & (order-number in the formal parameter
 | |
| 	    list)) in order to substitute this character later by the
 | |
| 	    actual parameter. The replacement text is copied into
 | |
| 	    itself because the copied text will contain fewer or the
 | |
| 	    same amount of characters. The length of the replacement
 | |
| 	    text is returned.
 | |
| 
 | |
| 	    Implementation:
 | |
| 	    finite automaton : we are interested in
 | |
| 	    1-  white space, sequences must be mapped onto 1 single
 | |
| 	        blank.
 | |
| 	    2-  identifiers, since they might be replaced by some
 | |
| 	        actual parameter.
 | |
| 	    3-  strings and character constants, since replacing
 | |
| 	        variables within them is illegal, and white-space is
 | |
| 	        significant.
 | |
| 	    4-  comment, same as for 1
 | |
| 	    Other tokens will not be seen as such.
 | |
| 	*/
 | |
| 	register int c;
 | |
| 	struct repl repls;
 | |
| 	register struct repl* repl = &repls;
 | |
| 	int blank = 0;
 | |
| 
 | |
| 	c = GetChar();
 | |
| 
 | |
| 	repl->r_ptr = repl->r_text = Malloc((unsigned)(repl->r_size = ITEXTSIZE));
 | |
| 	*repl->r_ptr = '\0';
 | |
| 	while ((c != EOI) && (class(c) != STNL))
 | |
| 	{
 | |
| 		if (BLANK(c))
 | |
| 		{
 | |
| 			blank++;
 | |
| 			c = GetChar();
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if (c == '\'' || c == '"')
 | |
| 		{
 | |
| 			register int delim = c;
 | |
| 
 | |
| 			if (blank)
 | |
| 			{
 | |
| 				blank = 0;
 | |
| 				add2repl(repl, ' ');
 | |
| 			}
 | |
| 			do
 | |
| 			{
 | |
| 				add2repl(repl, c);
 | |
| 				if (c == '\\')
 | |
| 					add2repl(repl, GetChar());
 | |
| 				c = GetChar();
 | |
| 			} while (c != delim && c != EOI && class(c) != STNL);
 | |
| 			if (c != delim)
 | |
| 			{
 | |
| 				strict("unclosed opening %c", delim);
 | |
| 				break;
 | |
| 			}
 | |
| 			add2repl(repl, c);
 | |
| 			c = GetChar();
 | |
| 		}
 | |
| 		else if (c == '/')
 | |
| 		{
 | |
| 			c = GetChar();
 | |
| 			if (c == '*')
 | |
| 			{
 | |
| 				skipcomment();
 | |
| 				blank++;
 | |
| 				c = GetChar();
 | |
| 				continue;
 | |
| 			}
 | |
| 			else if (c == '/')
 | |
| 			{
 | |
| 				skiplinecomment();
 | |
| 				blank++;
 | |
| 				c = GetChar();
 | |
| 				continue;
 | |
| 			}
 | |
| 			if (blank)
 | |
| 			{
 | |
| 				blank = 0;
 | |
| 				add2repl(repl, ' ');
 | |
| 			}
 | |
| 			add2repl(repl, '/');
 | |
| 		}
 | |
| 		else if (formals && (class(c) == STIDF || class(c) == STELL))
 | |
| 		{
 | |
| 			char id_buf[IDFSIZE + 1];
 | |
| 			register char* idp = id_buf;
 | |
| 			int n;
 | |
| 
 | |
| 			/* read identifier: it may be a formal parameter */
 | |
| 			*idp++ = c;
 | |
| 			do
 | |
| 			{
 | |
| 				c = GetChar();
 | |
| 				if (idp <= &id_buf[IDFSIZE])
 | |
| 					*idp++ = c;
 | |
| 			} while (in_idf(c));
 | |
| 			*--idp = '\0';
 | |
| 
 | |
| 			if (blank)
 | |
| 			{
 | |
| 				blank = 0;
 | |
| 				add2repl(repl, ' ');
 | |
| 			}
 | |
| 			/* construct the formal parameter mark or identifier */
 | |
| 			if (n = find_name(id_buf, formals))
 | |
| 				add2repl(repl, FORMALP | (char)n);
 | |
| 			else
 | |
| 			{
 | |
| 				idp = id_buf;
 | |
| 				while (*idp)
 | |
| 					add2repl(repl, *idp++);
 | |
| 			}
 | |
| 		}
 | |
| 		else if (class(c) == STNUM)
 | |
| 		{
 | |
| 			if (blank)
 | |
| 			{
 | |
| 				blank = 0;
 | |
| 				add2repl(repl, ' ');
 | |
| 			}
 | |
| 			add2repl(repl, c);
 | |
| 			if (c == '.')
 | |
| 			{
 | |
| 				c = GetChar();
 | |
| 				if (class(c) != STNUM)
 | |
| 				{
 | |
| 					continue;
 | |
| 				}
 | |
| 				add2repl(repl, c);
 | |
| 			}
 | |
| 			c = GetChar();
 | |
| 			while (in_idf(c) || c == '.')
 | |
| 			{
 | |
| 				add2repl(repl, c);
 | |
| 				if ((c = GetChar()) == 'e' || c == 'E')
 | |
| 				{
 | |
| 					add2repl(repl, c);
 | |
| 					c = GetChar();
 | |
| 					if (c == '+' || c == '-')
 | |
| 					{
 | |
| 						add2repl(repl, c);
 | |
| 						c = GetChar();
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			if (blank)
 | |
| 			{
 | |
| 				blank = 0;
 | |
| 				add2repl(repl, ' ');
 | |
| 			}
 | |
| 			add2repl(repl, c);
 | |
| 			c = GetChar();
 | |
| 		}
 | |
| 	}
 | |
| 	*length = repl->r_ptr - repl->r_text;
 | |
| 	return Realloc(repl->r_text, (unsigned)(repl->r_ptr - repl->r_text + 1));
 | |
| }
 | |
| 
 | |
| /*	macroeq() decides whether two macro replacement texts are
 | |
|     identical. This version compares the texts, which occur
 | |
|     as strings, without taking care of the leading and trailing
 | |
|     blanks (spaces and tabs).
 | |
| */
 | |
| macroeq(s, t) register char* s, *t;
 | |
| {
 | |
| 
 | |
| 	/* skip leading spaces	*/
 | |
| 	while (BLANK(*s))
 | |
| 		s++;
 | |
| 	while (BLANK(*t))
 | |
| 		t++;
 | |
| 	/* first non-blank encountered in both strings	*/
 | |
| 	/* The actual comparison loop:			*/
 | |
| 	while (*s && *s == *t)
 | |
| 		s++, t++;
 | |
| 	/* two cases are possible when arrived here:	*/
 | |
| 	if (*s == '\0')
 | |
| 	{ /* *s == '\0'		*/
 | |
| 		while (BLANK(*t))
 | |
| 			t++;
 | |
| 		return *t == '\0';
 | |
| 	}
 | |
| 	else
 | |
| 	{ /* *s != *t		*/
 | |
| 		while (BLANK(*s))
 | |
| 			s++;
 | |
| 		while (BLANK(*t))
 | |
| 			t++;
 | |
| 		return (*s == '\0') && (*t == '\0');
 | |
| 	}
 | |
| }
 | |
| 
 | |
| do_line(l) unsigned int l;
 | |
| {
 | |
| 	struct token tk;
 | |
| 	int t = GetToken(&tk);
 | |
| 
 | |
| 	if (t != EOF)
 | |
| 		SkipToNewLine();
 | |
| 	LineNumber = l; /* the number of the next input line */
 | |
| 	if (t == STRING) /* is there a filespecifier? */
 | |
| 		FileName = tk.tk_str;
 | |
| }
 |