/* Stubby - Generate stubs for dynamic-link libraries.
 * Copyright 1999 Tom Rothamel
 *
 * Stubby is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version. It is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * This program generates another computer program. I do not assert a      
 * copyright on the generated program that is the normal output of this    
 * program. This is _not_, however, an authorization for you to modify     
 * this program to cause it to output more of itself than necessary for    
 * its proper operation in order to circumvent my copyright.               
 */

/* This file handles generation of the output file. Routines that write
 * actual code are in this file.
 */

#include <stdio.h>
#include <stdlib.h>
#include "stubby.h"
#include "str.h"

char *lprefix = NULL;

FILE *of;

/* This function reads in an input file. It first looks for the file in
 * either the directory given by the environment variable STUBBYPATH, or
 * in the current directory if that's not set. If the file isn't found,
 * it looks in <library path>/<dltype>. The library path can be set using
 * the -L option, but has a sane default.
 */
char *getfile(char *fn) {
	char *str;
	char fpath[256];
	char buf[128];
	FILE *f;

	str = strdup("");

	if (getenv("STUBBYPATH")) {
		snprintf(fpath, 256, "%s/%s", getenv("STUBBYPATH"), fn);
		f = fopen(fpath, "r");
	} else {
		f = fopen(fn, "r");
	}

	if (!f) {
		snprintf(fpath, 256, "%s/%s/%s", libdir, dl_type, fn);
		f = fopen(fpath, "r");
	}
	
	if (!f) {
		error("Couldn't open '%s'.\n", fn);
		exit(-1);
	}
	
	while (fgets(buf, 128, f)) {
		if (buf[0] == '%') continue;
		str = strappend(str, buf);
	}

	fclose(f);

	return str;
}

void init_codegen() {
	char *tmpl;

	tmpl = getfile("stub.top.c");
	fprintf(of, "%s", tmpl);
}

int need_init_codegen = 1;

char *preload = NULL;
char *varlink = NULL;
char *varset = NULL;
char *varget = NULL;

char *libbottmpl = NULL;

/* This is called to finish up the processing of a library. */
void finish_library() {
	char *libbot;

	if (!lprefix) return;

	if (!libbottmpl) libbottmpl = getfile("stub.libbot.c");

	if (!varlink) varlink = strdup("");	
	if (!varset) varset = strdup("");	
	if (!varget) varget = strdup("");
	if (!preload) preload = strdup("");
	
	libbot = strdup(libbottmpl);
	libbot = strsubst(libbot, "@VARLINK@", varlink);
	libbot = strsubst(libbot, "@VARSET@", varset);
	libbot = strsubst(libbot, "@VARGET@", varget);
	libbot = strsubst(libbot, "@PREFIX@", lprefix);
	libbot = strsubst(libbot, "@PRELOAD@", preload);
	
	
	fprintf(of, "%s", libbot);

	free(preload);
	free(libbot);
	free(varlink);
	free(varset);
	free(varget);
	free(lprefix);

	preload = NULL;
	lprefix = NULL;
	varset = NULL;
	varget = NULL;
	varlink = NULL;
}
		

char *loadtmpl;

/* This handles the 'load' statement. */
void load(char *lib) {
	char *loadbuf;

	if (!lprefix) {
		error("load directive outside of library.\n");
		exit(-1);
	}

	if (!loadtmpl) loadtmpl = getfile("stub.load.c");
	loadbuf = strdup(loadtmpl);

	loadbuf = strsubst(loadbuf, "@PREFIX@", lprefix);
	loadbuf = strsubst(loadbuf, "@PRELIBNAME@", lib);

	preload = straf(preload, loadbuf);

	free(lib);
}
	
char *libtmpl = NULL;

/* This handles the 'library' statement. */
void library(char *prefix, char *name) {
	char *libbuf;
	
	finish_library();

	if (need_init_codegen) init_codegen();
	need_init_codegen = 0;
	
	if (!libtmpl) libtmpl = getfile("stub.libtop.c");
	
	lprefix = prefix;

	libbuf = strdup(libtmpl);

	libbuf = strsubst(libbuf, "@PREFIX@", lprefix);
	libbuf = strsubst(libbuf, "@LIBNAME@", name);

	fprintf(of, "%s", libbuf);

	free(libbuf); 	
}

char *vartmpl = NULL;
char *varsettmpl = NULL;
char *vargettmpl = NULL;
char *varlinktmpl = NULL;

/* This is called when a variable has been parsed. */
void variable(PTE *pte) {
	char *varbuf;
	char *varsetbuf;
	char *vargetbuf;
	char *varlinkbuf;

	char *vardef;
	char *ptvardef;
	char *ptvarname;
	
	if (!vartmpl) vartmpl = getfile("stub.var.c");
	if (!varsettmpl) varsettmpl = getfile("stub.varset.c");
	if (!vargettmpl) vargettmpl = getfile("stub.varget.c");
	if (!varlinktmpl) varlinktmpl = getfile("stub.varlink.c");	
	
	vardef = strpte(pte);

	ptvarname = strdup(lprefix);
	ptvarname = strappend(ptvarname, "_var_");
	ptvarname = strappend(ptvarname, pte->name);
	
	switch(pte->type) {
	case PTE_VAR:
		ptvardef = strdup(pte->proto);
		ptvardef = strappend(ptvardef, "*");
		ptvardef = strappend(ptvardef, ptvarname);
		break;
	case PTE_VARF:
		ptvardef = strpte(pte->fproto);
		ptvardef = strappend(ptvardef, "(");
		ptvardef = straf(ptvardef, strptr(pte->pdepth + 1));
		ptvardef = strappend(ptvardef, ptvarname);
		ptvardef = strappend(ptvardef, ")");
		ptvardef = straf(ptvardef, strargs(pte));
		break;
	}

	varbuf = strdup(vartmpl);

	varbuf = strsubst(varbuf, "@VARNAME@", pte->name);
	varbuf = strsubst(varbuf, "@PTVARNAME@", ptvarname);
	varbuf = strsubst(varbuf, "@VARDEF@", vardef);
	varbuf = strsubst(varbuf, "@PTVARDEF@", ptvardef);
	varbuf = strsubst(varbuf, "@PREFIX@", lprefix );

	fprintf(of, "%s", varbuf);

	varsetbuf = strdup(varsettmpl);
	varsetbuf = strsubst(varsetbuf, "@VARNAME@", pte->name);
	varsetbuf = strsubst(varsetbuf, "@PTVARNAME@", ptvarname);
	varsetbuf = strsubst(varsetbuf, "@PREFIX@", lprefix);

	vargetbuf = strdup(vargettmpl);
	vargetbuf = strsubst(vargetbuf, "@VARNAME@", pte->name);
	vargetbuf = strsubst(vargetbuf, "@PTVARNAME@", ptvarname);
	vargetbuf = strsubst(vargetbuf, "@PREFIX@", lprefix);

	varlinkbuf = strdup(varlinktmpl);
	varlinkbuf = strsubst(varlinkbuf, "@VARNAME@", pte->name);
	varlinkbuf = strsubst(varlinkbuf, "@PTVARNAME@", ptvarname);
	varlinkbuf = strsubst(varlinkbuf, "@PREFIX@", lprefix);

	varlink = strappend(varlink, varlinkbuf);
	varget = strappend(varget, vargetbuf);
	varset = strappend(varset, varsetbuf);	
	
	free(varbuf);
	free(ptvarname);
	free(vardef);
	free(ptvardef);
}


char *functmpl = NULL;

/* This is called when a function has been parsed. */
void function(PTE *pte) {
	PTE *arg;
	char buf[16];
	int an = 0;
	char *funcbuf;
	
	char *funcdec;
	char *fvarname;
	char *fwithargs;
	char *arglist;
	char *rvdec;
	char *rvequal;
	
	char *needret;	

	if (!functmpl) functmpl = getfile("stub.func.c");
	
	if (!pte->fproto) {
		funcdec = strdup("void ");
	} else {
		funcdec = strpte(pte->fproto);
	}

	fvarname = strdup(lprefix);
	fvarname = strappend(fvarname, "_func_");
	fvarname = strappend(fvarname, pte->name);

	
	funcdec = strappend(funcdec, "(*");
	funcdec = strappend(funcdec, fvarname);
	funcdec = strappend(funcdec, ")");
	funcdec = straf(funcdec, strargs(pte));

	arg = pte->args;
	while (arg) {
		snprintf(buf, 16, "a%d", an++);
		promotepte(arg, buf);
		arg = arg->next;
	}	

	fwithargs = strpte(pte);

	arglist = strdup("");
	arg = pte->args;
	while (arg) {
		arglist = strappend(arglist, arg->name);
		if (arg->next)
			arglist = strappend(arglist, ", ");
		arg = arg->next;
	}

	if (!pte->fproto) {
		rvdec = strdup("");
		rvequal = "";
		needret = "";
	} else {
		promotepte(pte->fproto, "rv");
		rvdec = strpte(pte->fproto);
		rvdec = strappend(rvdec, ";");
		rvequal = "rv = ";
		needret = "return rv;";
	}
	
	funcbuf = strdup(functmpl);

	funcbuf = strsubst(funcbuf, "@PREFIX@", lprefix);
	funcbuf = strsubst(funcbuf, "@FUNCDEC@", funcdec);
	funcbuf = strsubst(funcbuf, "@FVARNAME@", fvarname);
	funcbuf = strsubst(funcbuf, "@FNAME@", pte->name);
	funcbuf = strsubst(funcbuf, "@FWITHARGS@", fwithargs);
	funcbuf = strsubst(funcbuf, "@ARGLIST@", arglist);
	funcbuf = strsubst(funcbuf, "@RVDEC@", rvdec);
	funcbuf = strsubst(funcbuf, "@RVEQUAL@", rvequal);
	funcbuf = strsubst(funcbuf, "@RETURN@", needret);

	fprintf(of, "%s", funcbuf);

	free(funcbuf);

	free(rvdec);
	free(funcdec);
	free(fvarname);
	free(fwithargs);
	free(arglist);	
}

/* This is called from the parser to say that either a function or a
 * variable has been parsed. It dispatches to the functions above, as
 * appropriate. */
void definition(PTE *pte) {
	if (!lprefix) {
		warn("Attempting to define %s before declaring a library.",
		     pte->name);
		return;
	}

	switch(pte->type) {
	case PTE_FUNCTION:
		function(pte);
		break;
	case PTE_VAR:
	case PTE_VARF:
		variable(pte);
		break;
	default:
		warn("Code generation not implemented for %s.", pte->name);
		break;
	}	

	delPTE(pte);
}
