%{
/*
	Lexical analyser to read the Makefile.in files.

	(c) Keith Owens <kaos@ocs.com.au> February 2002.
	Released under GPL version 2, (you know the rest).

	Input from yyin is the combined set of Makefile.in files.
*/

#include "pp_makefile.h"
#include "pp_db.h"
#include "pp_makefile2_parse.tab.h"	/* tokens from yacc */

#include <ctype.h>
#include <limits.h>

static const char *program = "pp_makefile2_parse.l";

/* declarations to remove annoying warning messages */

int yylex(void);
int yyerror(const char *);

/* Feed forward from lexer to parser */

char *l_makefile_in_name;
char *l_curr_reldir;
int l_line_no;

/* Feed back from parser to lexer */

extern int y_errors;
extern int y_depth;		/* look this deep into parenthesis, anything deeper is text */
extern int y_saving_text, y_suppress_nl;
extern FILE *yyout, *y_feedback;
extern char **y_user_target, **y_user_dependencies;
extern PPDB *y_db_main;

/* Common parser and lexer functions, arbitrarily held in the parser */

extern DB_REC_TARGET *y_add_make_target(const char *, const char *);
extern void y_normalize_path(char *);
extern void y_new_string(char **, char **, int *, char *, char *, const char *);
extern int y_printf(const char *format, ...);

/* Local */

static int pp_state;
static int paren_depth;		/* parenthesis count */
static int paren_depth;

static void set_state(void);
static void reset_state(void);

/* Code to read from strings in order to handle pre-processor macros.
 * NOTE: This method is flex specific, other lex analysers are not supported.
 */

#undef YY_INPUT
#define YY_INPUT(buf, ret, max_size) (ret = input_from_string(buf, max_size))
static int input_from_string(char *buf, int max_size);
%}

%pointer	/* yytext is a pointer, not an array */
%option noyywrap

%x x_comment
%x x_pp

TRAIL	[ \t]*"("

%%

%{
  set_state();
%}

  /* Comment handling */
<*>^[ \t]*"#"			{
					BEGIN(x_comment);
					y_printf("%s", yytext);
				}
<x_comment>.*"\n"		{
					++l_line_no;
					y_printf("%s", yytext);
					if (yyleng < 2 || yytext[yyleng-1] != '\\') {
						set_state();
					}
				}

  /* Pre-processor commands, handled by the parser.  Macros such as $(objdir),
   * $(objfile ...), $(srcfile ...) are handled by the lexer and replaced by
   * text, the parser does not see macros that are expanded.
   */

"expsyms"/{TRAIL}		{
					pp_state = 1;
					return(EXPSYMS);
				}
"select_elsewhere"/{TRAIL}	{
					pp_state = 1;
					return(SELECT_ELSEWHERE);
				}
"select"/{TRAIL}		{
					pp_state = 1;
					return(SELECT);
				}
"link_subdirs"/{TRAIL}		{
					pp_state = 1;
					return(LINK_SUBDIRS);
				}
"extra_aflags"/{TRAIL}		{
					pp_state = 1;
					return(EXTRA_AFLAGS);
				}
"extra_aflags_all"/{TRAIL}	{
					pp_state = 1;
					return(EXTRA_AFLAGS_ALL);
				}
"extra_cflags"/{TRAIL}		{
					pp_state = 1;
					return(EXTRA_CFLAGS);
				}
"extra_cflags_all"/{TRAIL}	{
					pp_state = 1;
					return(EXTRA_CFLAGS_ALL);
				}
"extra_ldflags"/{TRAIL}		{
					pp_state = 1;
					return(EXTRA_LDFLAGS);
				}
"objlink"/{TRAIL}		{
					pp_state = 1;
					return(OBJLINK);
				}
"user_command"/{TRAIL}		{
					pp_state = 1;
					return(USER_COMMAND);
				}
"nodepend"/{TRAIL}		{
					pp_state = 1;
					return(NODEPEND);
				}
"create_objdir"/{TRAIL}		{
					pp_state = 1;
					return(CREATE_OBJDIR);
				}
"side_effect"/{TRAIL}		{
					pp_state = 1;
					return(SIDE_EFFECT);
				}
"shipped"/{TRAIL}		{
					pp_state = 1;
					return(SHIPPED);
				}
"issrcfile"/{TRAIL}		{
					pp_state = 1;
					return(ISSRCFILE);
				}
"base_target"/{TRAIL}		{
					pp_state = 1;
					return(BASE_TARGET);
				}
"update_if_changed"/{TRAIL}	{
					pp_state = 1;
					return(UPDATE_IF_CHANGED);
				}
"uses_asm_offsets"/{TRAIL}	{
					pp_state = 1;
					return(USES_ASM_OFFSETS);
				}
"hostcc"/{TRAIL}		{
					pp_state = 1;
					return(HOSTCC);
				}
"hostld"/{TRAIL}		{
					pp_state = 1;
					return(HOSTLD);
				}
"hostccld"/{TRAIL}		{
					pp_state = 1;
					return(HOSTCCLD);
				}
"setup"/{TRAIL}			{
					pp_state = 1;
					return(SETUP);
				}
"installable"/{TRAIL}		{
					pp_state = 1;
					return(INSTALLABLE);
				}
"ifsel"/{TRAIL}			{
					pp_state = 1;
					return(IFSEL);
				}
"ifnsel"/{TRAIL}		{
					pp_state = 1;
					return(IFNSEL);
				}
"ifselmod"/{TRAIL}		{
					pp_state = 1;
					return(IFSELMOD);
				}
"ifselnmod"/{TRAIL}		{
					pp_state = 1;
					return(IFSELNMOD);
				}

"\n"				{
					++l_line_no;
					if (!y_suppress_nl) {
						y_printf("\n");
					}
					y_suppress_nl = 0;
					return(EOL);
				}

  /* Be careful here, this can match the shorthand names so it must come after
   * the shorthand rules and this rule must not match longer strings.
   */
[A-Za-z0-9_]+|[ \t]+|.		{
					yylval.string = k_strdup(yytext);
					return(TEXT);
				}

  /* Start and end of file.  These are processed in any state to reset after errors */
<*>^"#pp_makefile_in_start ".*	{
					char *p1, *p2;
					char makefile_in_name[PATH_MAX + 1];
					reset_state();
					p1 = yytext + 22;
					if (!(p2 = strchr(p1, ' '))) {
						fprintf(stderr, "Invalid pp_makefile_in_start line\n'%s'\n",
							yytext);
						exit(1);
					}
					*p2 = '\0';
					snprintf(makefile_in_name, sizeof (makefile_in_name),
						 "%s%sMakefile.in", p1, p2 + 2);
					*p2 = ' ';
					l_makefile_in_name = k_strdup(makefile_in_name);
					l_curr_reldir = k_strdup(p2 + 1);
					l_line_no = 0;
					yylval.string = k_strdup(yytext);
					return (STARTONEFILE);
				}
<*>^"#pp_makefile_in_end".*"\n"	{
					reset_state();
					free(l_makefile_in_name);
					/* parser needs l_curr_reldir so it is freed in ENDONEFILE processing */
					y_printf("%s", yytext);
					return(ENDONEFILE);
				}


<x_pp>"("			{
					++paren_depth;
					if (paren_depth == y_depth) {
						return(LPAREN);
					}
					else {
						yymore();
					}
				}
<x_pp>")"			{
					if (paren_depth > y_depth) {
						--paren_depth;
						yymore();
					} else if (yyleng > 1) {
						yyless(yyleng - 1);
						yylval.string = k_strdup(yytext);
						return (TEXT);
					} else {
						if (--paren_depth == 0) {
							reset_state();
						}
						return (RPAREN);
					}
				}
<x_pp>[ \t]			{
					/* Messy.  White space outside () is a
					 * separator, space inside () is part of the
					 * parenthesised text.
					 */
					if (paren_depth > y_depth) {
						yymore();
					} else if (yyleng == 1) {
						y_printf("%s", yytext);
					} else {
						yyless(yyleng - 1);
						yylval.string = k_strdup(yytext);
						return (TEXT);
					}
				}
<x_pp>\\*\n			{
					/* Messy.  Newline outside () is a separator,
					 * newline inside () is part of the
					 * parenthesised text.  Plus we add \\ if the
					 * newline is not already escaped and is
					 * outside (), i.e. we are processing a
					 * shorthand command.  Unless we are just
					 * saving the text.
					 */
					if (paren_depth > y_depth) {
						++l_line_no;
						yymore();
					} else {
						int i;
						for (i = yyleng - 2; i >= 0; --i) {
							if (yytext[i] != '\\') {
								break;
							}
						}
						if (i < 0) {
							++l_line_no;
							if (!y_saving_text) {
								y_printf("%s%s", (yyleng & 1) ? " \\" : "",
									 yytext);
							}
						} else {
							yyless(i + 1);
							yylval.string = k_strdup(yytext);
							return (TEXT);
						}
					}
				}
<x_pp>\!?"CONFIG_"[A-Za-z0-9_]+ {
					if (paren_depth > y_depth) {
						yymore();
					} else {
						yylval.string = k_strdup(yytext);
						return (CONFIG);
					}
				}
<x_pp>.				{
					yymore();
				}

%%

int
yyerror(const char *msg)
{
	fprintf(stderr, "%s line %d: %s at '%s'\n", l_makefile_in_name,
		l_line_no, msg, yytext);
	++y_errors;
	return (0);
}

static void
set_state(void)
{
	if (pp_state) {
		BEGIN(x_pp);
		if (pp_state == 1) {
			paren_depth = 0;
			pp_state = 2;
		}
	} else {
		BEGIN(0);
		y_depth = 1;
	}
}

static void
reset_state(void)
{
	pp_state = 0;
	set_state();
}

/* Extract the option from a macro, Returns NULL if there is no option, if the
 * line does not contain a matching ')' or if the option contains '$' when it is
 * not allowed.  end points past the closing ')'.
 */

static char *
macro_option(char *current, char **end, int allow_dollar)
{
	char *option, *opt_start = NULL, *opt_end;
	int paren = 0, l;
	while (!isspace(*current)) {
		if (*current == '(') {
			++paren;
		} else if (*current == ')') {
			if (--paren <= 0) {
				return (NULL);
			}
		}
		++current;
	}
	while (isspace(*current)) {
		++current;
	}
	opt_start = opt_end = current;
	while (paren && *opt_end) {
		switch (*opt_end) {
		case '(':
			++paren;
			break;
		case ')':
			--paren;
			break;
		case '$':
			if (!allow_dollar) {
				return (NULL);
			}
			break;
		}
		++opt_end;
	}
	if (paren) {
		return (NULL);
	}
	l = opt_end - opt_start;
	option = k_malloc(l);
	memcpy(option, opt_start, l - 1);
	option[l - 1] = '\0';
	*end = opt_end;
	return (option);
}

/* Expand $(srcfile filename) and $(srcfile_base filename) */

static int
srcfile_common(int src_base,
	       char **is, char **is_end, int *is_size,
	       char *current, char *opt_end, char *option)
{
	DB_TOKEN token;
	DB_REC_SOURCE *s = NULL;
	const char *relname;
	char buf[PATH_MAX + 1];
	char buf2[PATH_MAX + 1];
	if (*option == '/') {
		snprintf(buf, sizeof (buf), "%s", option);
	} else {
		snprintf(buf, sizeof (buf), "%s%s", l_curr_reldir, option);
	}
	y_normalize_path(buf);
	token = lookup_db_byname_highest(y_db_main, buf, DB_RECTYPE_SOURCE);
	if (DB_IS_NULL_TOKEN(token) && !src_base) {
		fprintf(stderr,
			"Error: cannot resolve $(srcfile %s) in %s, line %d\n",
			option, l_makefile_in_name, l_line_no);
		++y_errors;
		snprintf(buf2, sizeof (buf2), "%s%s",
			 common_src_obj ? "" : srctree[0], buf + 1);
	} else if (src_base) {
		snprintf(buf2, sizeof (buf2), "%s%s",
			 common_src_obj ? "" : srctree[0], buf + 1);
	} else {
		s = read_db_source(y_db_main, token);
		relname = read_db_text(y_db_main, s->relname);
		snprintf(buf2, sizeof (buf2), "%s%s",
			 common_src_obj ? "" : srctree[s->index], relname + 1);
	}
	if (common_src_obj && !DB_IS_NULL_TOKEN(token)) {
		token = lookup_db_target(y_db_main, buf);
		assert(!DB_IS_NULL_TOKEN(token));
		read_db_target(y_db_main, token)->flags.issrcfile = 1;
	}
	y_new_string(is, is_end, is_size, current, opt_end, buf2);
	free(option);
	return (1);
}

/* Expand $(dir filename ...) and $(notdir filename ...) */

static int
dir_common(int dir,
	   char **is, char **is_end, int *is_size,
	   char *current, char *opt_end, char *option)
{
	char *new = NULL, *s, *text = option;
	char buf[PATH_MAX + 1];
	int l;
	if (strchr(option, '$')) {
		free(option);
		return (0);
	}
	while (*(text += strspn(text, " \t\n"))) {
		l = strcspn(text, " \t\n");
		if (l >= sizeof (buf)) {
			free(new);
			new = k_strdup("pathname_too_long");
			break;
		}
		memcpy(buf, text, l);
		text += l;
		buf[l] = '\0';
		s = strrchr(buf, '/');
		if (dir) {
			if (s) {
				s[1] = '\0';
			} else {
				strcpy(buf, "./");
			}
		} else {
			if (s) {
				strcpy(buf, s + 1);
			}
		}
		l = new ? strlen(new) + 2 : 1;
		new = k_realloc(new, l + strlen(buf));
		if (l == 1) {
			new[0] = '\0';
		} else {
			strcat(new, " ");
		}
		strcat(new, buf);
	}
	y_new_string(is, is_end, is_size, current, opt_end, new);
	free(new);
	free(option);
	return (1);
}

/* Expand pre-processor macros before passing the line to the main lexer.  This
 * code expands the following macros
 *
 *   $(objdir)	- no trailing '/'
 *   $(objfile filename)
 *   $(srcfile filename)
 *   $(srcfile_base filename)
 *   $(src_includelist dirname)
 *   $(obj_includelist dirname)
 *   $(c_to_o)
 *   $(tmpfile optional_suffix)
 *
 * It also expands these make variables and functions, instead of letting make
 * process them.  This is done to allow the macros above see constant text
 * instead of variable text.
 *
 *   $(ARCH)
 *   $(dir filename ...)
 *   $(notdir filename ...)
 *   $@, $(@D), $(@F) - if the make target is known.
 *   $<, $(<D), $(<F), $^, $(^D), $(^F) - if the make dependencies are known.
 */

#define extra_new_chars	40	/* large enough for longest constant extra text */

static int inline
expand_macro_objdir(char **is, char **is_end, int *is_size, char *current,
		    int allow_dollar)
{
	if (strncmp(current, "$(objdir)", 9) == 0) {
		int l = strlen(l_curr_reldir + 1);
		char objdir[l];
		memcpy(objdir, l_curr_reldir + 1, l - 1);
		objdir[l - 1] = '\0';	/* remove trailing '/' */
		y_new_string(is, is_end, is_size, current, current + 9, objdir);
		return (1);
	}
	return (0);
}

static int inline
expand_macro_objfile(char **is, char **is_end, int *is_size, char *current,
		     int allow_dollar)
{
	char *opt_end, *option;
	if (strncmp(current, "$(objfile", 9) == 0 && isspace(current[9])
	    && (option = macro_option(current, &opt_end, allow_dollar))) {
		int l =
		    strlen(l_curr_reldir + 1) +
		    strlen(option) + extra_new_chars;
		char new[l];
		if (*option == '/') {
			snprintf(new, l, "%s", option + 1);
			y_add_make_target("", option);
		} else if (strchr(option, '$')) {
			snprintf(new, l,
				 "$(patsubst %%,%s%%,%s)",
				 l_curr_reldir + 1, option);
		} else {
			snprintf(new, l, "%s%s", l_curr_reldir + 1, option);
			y_add_make_target(l_curr_reldir, option);
		}
		y_new_string(is, is_end, is_size, current, opt_end, new);
		free(option);
		return (1);
	}
	return (0);
}

static int inline
expand_macro_srcfile(char **is, char **is_end, int *is_size, char *current,
		     int allow_dollar)
{
	char *opt_end, *option;
	if (strncmp(current, "$(srcfile", 9) == 0 && isspace(current[9])
	    && (option = macro_option(current, &opt_end, allow_dollar))) {
		return(srcfile_common(0, is, is_end, is_size, current, opt_end,
				       option));
	}
	return (0);
}

static int inline
expand_macro_srcfile_base(char **is, char **is_end, int *is_size, char *current,
			  int allow_dollar)
{
	char *opt_end, *option;
	if (strncmp(current, "$(srcfile_base", 14) == 0 && isspace(current[14])
	    && (option = macro_option(current, &opt_end, allow_dollar))) {
		return(srcfile_common(1, is, is_end, is_size, current, opt_end,
				       option));
	}
	return (0);
}

static int inline
expand_macro_src_includelist(char **is, char **is_end, int *is_size,
			     char *current, int allow_dollar)
{
	char *opt_end, *option;
	if (strncmp(current, "$(src_includelist", 17) == 0
	    && isspace(current[17])
	    && (option = macro_option(current, &opt_end, allow_dollar))) {
		int i, lb, ln;
		char buf[PATH_MAX + 5];
		char new[(high_srctree + 1) * sizeof (buf)];
		new[0] = '\0';
		for (i = high_srctree; i >= 0; --i) {
			if (!srctree[i]) {
				continue;
			}
			if (*option == '/') {
				snprintf(buf, sizeof (buf), " -I%s%s",
					 common_src_obj ? "" : srctree[i],
					 option + 1);
			} else {
				snprintf(buf, sizeof (buf), " -I%s%s%s",
					 common_src_obj ? "" : srctree[i],
					 l_curr_reldir + 1, option);
			}
			lb = strlen(buf);
			ln = strlen(new);
			if ((lb + ln + 1) > sizeof (new)) {
				lb = sizeof (new) - ln - 1;
			}
			memcpy(new + ln, buf, lb);
			new[ln + lb] = '\0';
		}
		y_new_string(is, is_end, is_size, current, opt_end, new);
		free(option);
		return (1);
	}
	return (0);
}

static int inline
expand_macro_obj_includelist(char **is, char **is_end, int *is_size,
			     char *current, int allow_dollar)
{
	char *opt_end, *option;
	if (strncmp(current, "$(obj_includelist", 17) == 0
	    && isspace(current[17])
	    && (option = macro_option(current, &opt_end, allow_dollar))) {
		char buf[PATH_MAX + 5];
		if (*option == '/') {
			snprintf(buf, sizeof (buf), " -I%s", option + 1);
		} else {
			snprintf(buf, sizeof (buf), " -I%s%s",
				 l_curr_reldir + 1, option);
		}
		y_new_string(is, is_end, is_size, current, opt_end, buf);
		free(option);
		return (1);
	}
	return (0);
}

static int inline
expand_macro_c_to_o(char **is, char **is_end, int *is_size, char *current,
		    int allow_dollar)
{
	char *opt_end, *option;
	if (strncmp(current, "$(c_to_o)", 9) == 0) {
		DB_TOKEN token;
		DB_REC_DIRECTORY *d;
		DB_REC_CHILD_LIST *c;
		DB_REC_CHILD_1 *c1;
		int i, basename_len, relname_len, ol_len;
		char *object_list, *o;
		const char *relname, *basename;
		token = lookup_db_directory(y_db_main, l_curr_reldir);
		if (DB_IS_NULL_TOKEN(token)) {
			fprintf(stderr,
				"%s: No entry for %s, should never happen\n",
				program, l_curr_reldir);
			exit(1);
		}
		d = read_db_directory(y_db_main, token);
		o = object_list = k_malloc((ol_len = 4096));
		c = read_db_child_list(y_db_main, d->children);
		for (i = 0, c1 = c->list; i < c->count; ++i, ++c1) {
			if (c1->entry.type != DB_RECTYPE_SOURCE) {
				continue;
			}
			relname = read_db_text(y_db_main, c1->relname);
			relname_len = strlen(relname);
			if (relname_len < 3 || strcmp(relname + relname_len - 2, ".c")) {
				continue;
			}
			basename = strrchr(relname, '/') + 1;
			basename_len = strlen(basename);
			while (ol_len - (o - object_list) < basename_len + 1) {
				char *new_object_list =
				    k_realloc(object_list, ol_len += 4096);
				o = new_object_list + (o - object_list);
				free(object_list);
				object_list = new_object_list;
			}
			if (o != object_list) {
				*o++ = ' ';
			}
			memcpy(o, basename, basename_len - 2);
			memcpy(o + basename_len - 2, ".o", 2);
			o += basename_len;
		}
		*o = '\0';
		y_new_string(is, is_end, is_size, current, current + 9,
			     object_list);
		free(object_list);
		return (1);
	}
	return (0);
}

static int inline
expand_macro_tmpfile(char **is, char **is_end, int *is_size, char *current,
		     int allow_dollar)
{
	char *opt_end, *option;
	if (strncmp(current, "$(tmpfile", 9) == 0
	    && (isspace(current[9]) || current[9] == ')')) {
		/* Preserve the optional suffix and the closing ')'. */
		for (option = current + 9; isspace(*option); ++option) {
		};
		y_new_string(is, is_end, is_size, current, option,
			     "$(objfile .tmp_file_$(notdir $@)");
		return (1);
	}
	return (0);
}

static int inline
expand_macro_ARCH(char **is, char **is_end, int *is_size, char *current,
		  int allow_dollar)
{
	if (strncmp(current, "$(ARCH)", 7) == 0) {
		y_new_string(is, is_end, is_size, current, current + 7, arch);
		return (1);
	}
	return (0);
}

static int inline
expand_macro_dir(char **is, char **is_end, int *is_size, char *current,
		 int allow_dollar)
{
	char *opt_end, *option;
	if (strncmp(current, "$(dir", 5) == 0 && isspace(current[5])
	    && (option = macro_option(current, &opt_end, allow_dollar))) {
		return (dir_common
			(1, is, is_end, is_size, current, opt_end, option));
	}
	return (0);
}

static int inline
expand_macro_notdir(char **is, char **is_end, int *is_size, char *current,
		    int allow_dollar)
{
	char *opt_end, *option;
	if (strncmp(current, "$(notdir", 8) == 0 && isspace(current[8])
	    && (option = macro_option(current, &opt_end, allow_dollar))) {
		return (dir_common
			(0, is, is_end, is_size, current, opt_end, option));
	}
	return (0);
}

static int inline
expand_macro_targets(char **is, char **is_end, int *is_size, char *current,
		     int allow_dollar)
{
	if (y_user_target
	    && (strncmp(current, "$@", 2) == 0
		|| strncmp(current, "$(@D)", 5) == 0
		|| strncmp(current, "$(@F)", 5) == 0)) {
		/* drop through */
	} else {
		return (0);
	}
	if (current[1] != '(') {
		y_new_string(is, is_end,
			     is_size, current, current + 2, *y_user_target);
	} else {
		int l = strlen(*y_user_target);
		char target[l + extra_new_chars];
		if (current[3] == 'D') {
			strcpy(target, "$(dir ");
		} else {
			strcpy(target, "$(notdir ");
		}
		strcat(target, *y_user_target);
		strcat(target, ")");
		y_new_string(is, is_end, is_size, current, current + 5, target);
	}
	return (1);
}

static int inline
expand_macro_dependencies(char **is, char **is_end, int *is_size, char *current,
			  int allow_dollar)
{
	int i, l, n;
	if (y_user_dependencies &&
	    (strncmp(current, "$<", 2) == 0 ||
	     strncmp(current, "$(<D)", 5) == 0 ||
	     strncmp(current, "$(<F)", 5) == 0 ||
	     strncmp(current, "$^", 2) == 0 ||
	     strncmp(current, "$(^D)", 5) == 0 ||
	     strncmp(current, "$(^F)", 5) == 0)) {
		/* drop through */
	} else {
		return (0);
	}
	for (n = 0, l = 0; y_user_dependencies[n]; ++n) {
		l += strlen(y_user_dependencies[n]) + 1;
	}
	if (n && (current[1] == '<' || strncmp(current + 1, "(<", 2) == 0)) {
		n = 1;
	}
	{
		char dependencies[l + n * extra_new_chars];
		dependencies[0] = '\0';
		for (i = 0; i < n; ++i) {
			if (current[1] == '(') {
				if (current[3]
				    == 'D') {
					strcat(dependencies, "$(dir ");
				} else {
					strcat(dependencies, "$(notdir ");
				}
			}
			strcat(dependencies, y_user_dependencies[i]);
			if (current[1] == '(') {
				strcat(dependencies, ")");
			}
			strcat(dependencies, " ");
		}
		if ((l = strlen(dependencies))) {
			dependencies[l - 1] = '\0';
		}
		y_new_string(is, is_end, is_size, current,
			     current + (current[1] == '(' ? 5 : 2),
			     dependencies);
	}
	return (1);
}

static int
expand_macros_1(char **is, char **is_end, int *is_size, int allow_dollar)
{
	char *current, *comment;
	int paren = 0, dquote = 0;

	/* Ignore macros in make comments */
	for (comment = *is; *comment; ++comment) {
		if (*comment == '(' && !dquote) {
			++paren;
		} else if (*comment == ')' && !dquote) {
			--paren;
		} else if (*comment == '"') {
			dquote = !dquote;	/* simplistic, may break on some strings */
		} else if (*comment == '#') {
			if (paren == 0 && dquote == 0
			    && (comment == *is || *(comment - 1) != '\\')) {
				break;
			}
		}
	}

	current = *is;
	while ((current = strchr(current, '$')) && current < comment) {
		if (strncmp(current, "$$", 2) == 0) {
			current += 2;
			continue;
		}
		if (expand_macro_objdir
		    (is, is_end, is_size, current, allow_dollar)) {
			return (1);
		}
		if (expand_macro_objfile
		    (is, is_end, is_size, current, allow_dollar)) {
			return (1);
		}
		if (expand_macro_srcfile
		    (is, is_end, is_size, current, allow_dollar)) {
			return (1);
		}
		if (expand_macro_srcfile_base
		    (is, is_end, is_size, current, allow_dollar)) {
			return (1);
		}
		if (expand_macro_src_includelist
		    (is, is_end, is_size, current, allow_dollar)) {
			return (1);
		}
		if (expand_macro_obj_includelist
		    (is, is_end, is_size, current, allow_dollar)) {
			return (1);
		}
		if (expand_macro_c_to_o
		    (is, is_end, is_size, current, allow_dollar)) {
			return (1);
		}
		if (expand_macro_tmpfile
		    (is, is_end, is_size, current, allow_dollar)) {
			return (1);
		}
		if (expand_macro_ARCH
		    (is, is_end, is_size, current, allow_dollar)) {
			return (1);
		}
		if (expand_macro_dir
		    (is, is_end, is_size, current, allow_dollar)) {
			return (1);
		}
		if (expand_macro_notdir
		    (is, is_end, is_size, current, allow_dollar)) {
			return (1);
		}
		if (expand_macro_targets
		    (is, is_end, is_size, current, allow_dollar)) {
			return (1);
		}
		if (expand_macro_dependencies
		    (is, is_end, is_size, current, allow_dollar)) {
			return (1);
		}
		++current;
	}
	return (0);
}

static void
expand_macros(char **is, char **is_end, int *is_size)
{
	/* The macro expansion is done in two passes.  First expand macros whose
	 * options do not contain '$', doing a brute force rescan of the entire line
	 * after each set of replacements to handle nested macros.  Then do a final
	 * scan over the line to handle macros whose options still contain '$'.
	 */
	int allow_dollar;
	for (allow_dollar = 0; allow_dollar < 2; ++allow_dollar) {
		while (expand_macros_1(is, is_end, is_size, allow_dollar)) {
		};
	}
}

/* Read the next line from yyin. */

static int
input_from_string(char *buf, int max_size)
{
	static char *is, *is_ptr, *is_end;
	static int is_size;
	static FILE *yyin;
	static const char *in_name = ".tmp_global_makefile_in.1";
	FILE *infile;
	int size_returned, eof;

	if (!is_ptr) {
		if (!(yyin = fopen(in_name, "r"))) {
			fprintf(stderr, "%s: fopen(%s, r) failed: %m\n",
				program, in_name);
			++y_errors;
			return (0);
		}
		is_size = 4096;
		is = k_malloc(is_size);
	}

	while (1) {
		if (is_ptr >= is_end) {
			if (y_feedback) {
				infile = y_feedback;
			} else {
				infile = yyin;
			}
			if ((eof = fgets_long_line(&is, &is_size, infile))) {
				if (eof < 0) {
					fprintf(stderr,
						"%s: Error reading from %s: %m\n",
						program, in_name);
					++y_errors;
				}
				fclose(infile);
				if (infile == y_feedback) {
					y_feedback = NULL;
					continue;	/* drop back to yyin */
				}
				free(is);
				return (0);
			}
			/*debug if (infile == y_feedback) fprintf(yyout, "#fb %s", is); */
			is_ptr = is;
			is_end = is + strlen(is);
			expand_macros(&is, &is_end, &is_size);
		}
		size_returned = is_end - is_ptr;
		if (size_returned > max_size) {
			size_returned = max_size;
		}
		memcpy(buf, is_ptr, size_returned);
		is_ptr += size_returned;
		return (size_returned);
	}
}
