// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include <u.h>
#include <libc.h>
#include "go.h"

/*
 * a function named init is a special case.
 * it is called by the initialization before
 * main is run. to make it unique within a
 * package and also uncallable, the name,
 * normally "pkg.init", is altered to "pkg.init·1".
 */
Sym*
renameinit(void)
{
	static int initgen;

	snprint(namebuf, sizeof(namebuf), "init·%d", ++initgen);
	return lookup(namebuf);
}

/*
 * hand-craft the following initialization code
 *	var initdone· uint8 				(1)
 *	func init()					(2)
 *		if initdone· != 0 {			(3)
 *			if initdone· == 2		(4)
 *				return
 *			throw();			(5)
 *		}
 *		initdone· = 1;				(6)
 *		// over all matching imported symbols
 *			<pkg>.init()			(7)
 *		{ <init stmts> }			(8)
 *		init·<n>() // if any			(9)
 *		initdone· = 2;				(10)
 *		return					(11)
 *	}
 */
static int
anyinit(NodeList *n)
{
	uint32 h;
	Sym *s;
	NodeList *l;

	// are there any interesting init statements
	for(l=n; l; l=l->next) {
		switch(l->n->op) {
		case ODCLFUNC:
		case ODCLCONST:
		case ODCLTYPE:
		case OEMPTY:
			break;
		case OAS:
			if(isblank(l->n->left) && candiscard(l->n->right))
				break;
			// fall through
		default:
			return 1;
		}
	}

	// is this main
	if(strcmp(localpkg->name, "main") == 0)
		return 1;

	// is there an explicit init function
	snprint(namebuf, sizeof(namebuf), "init·1");
	s = lookup(namebuf);
	if(s->def != N)
		return 1;

	// are there any imported init functions
	for(h=0; h<NHASH; h++)
	for(s = hash[h]; s != S; s = s->link) {
		if(s->name[0] != 'i' || strcmp(s->name, "init") != 0)
			continue;
		if(s->def == N)
			continue;
		return 1;
	}

	// then none
	return 0;
}

void
fninit(NodeList *n)
{
	int i;
	Node *gatevar;
	Node *a, *b, *fn;
	NodeList *r;
	uint32 h;
	Sym *s, *initsym;

	if(debug['A']) {
		// sys.go or unsafe.go during compiler build
		return;
	}

	n = initfix(n);
	if(!anyinit(n))
		return;

	r = nil;

	// (1)
	snprint(namebuf, sizeof(namebuf), "initdone·");
	gatevar = newname(lookup(namebuf));
	addvar(gatevar, types[TUINT8], PEXTERN);

	// (2)
	maxarg = 0;
	snprint(namebuf, sizeof(namebuf), "init");

	fn = nod(ODCLFUNC, N, N);
	initsym = lookup(namebuf);
	fn->nname = newname(initsym);
	fn->nname->defn = fn;
	fn->nname->ntype = nod(OTFUNC, N, N);
	declare(fn->nname, PFUNC);
	funchdr(fn);

	// (3)
	a = nod(OIF, N, N);
	a->ntest = nod(ONE, gatevar, nodintconst(0));
	r = list(r, a);

	// (4)
	b = nod(OIF, N, N);
	b->ntest = nod(OEQ, gatevar, nodintconst(2));
	b->nbody = list1(nod(ORETURN, N, N));
	a->nbody = list1(b);

	// (5)
	b = syslook("throwinit", 0);
	b = nod(OCALL, b, N);
	a->nbody = list(a->nbody, b);

	// (6)
	a = nod(OAS, gatevar, nodintconst(1));
	r = list(r, a);

	// (7)
	for(h=0; h<NHASH; h++)
	for(s = hash[h]; s != S; s = s->link) {
		if(s->name[0] != 'i' || strcmp(s->name, "init") != 0)
			continue;
		if(s->def == N)
			continue;
		if(s == initsym)
			continue;

		// could check that it is fn of no args/returns
		a = nod(OCALL, s->def, N);
		r = list(r, a);
	}

	// (8)
	r = concat(r, n);

	// (9)
	// could check that it is fn of no args/returns
	for(i=1;; i++) {
		snprint(namebuf, sizeof(namebuf), "init·%d", i);
		s = lookup(namebuf);
		if(s->def == N)
			break;
		a = nod(OCALL, s->def, N);
		r = list(r, a);
	}

	// (10)
	a = nod(OAS, gatevar, nodintconst(2));
	r = list(r, a);

	// (11)
	a = nod(ORETURN, N, N);
	r = list(r, a);
	exportsym(fn->nname);

	fn->nbody = r;
	funcbody(fn);

	curfn = fn;
	typecheck(&fn, Etop);
	typechecklist(r, Etop);
	curfn = nil;
	funccompile(fn, 0);
}
