// 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.

/*
 * select
 */

#include "go.h"

void
typecheckselect(Node *sel)
{
	Node *ncase, *n, *def;
	NodeList *l;
	int lno, count;

	def = nil;
	lno = setlineno(sel);
	count = 0;
	typechecklist(sel->ninit, Etop);
	for(l=sel->list; l; l=l->next) {
		count++;
		ncase = l->n;
		setlineno(ncase);
		if(ncase->op != OXCASE)
			fatal("typecheckselect %O", ncase->op);

		if(ncase->list == nil) {
			// default
			if(def != N)
				yyerror("multiple defaults in select (first at %L)", def->lineno);
			else
				def = ncase;
		} else if(ncase->list->next) {
			yyerror("select cases cannot be lists");
		} else {
			n = typecheck(&ncase->list->n, Etop);
			ncase->left = n;
			ncase->list = nil;
			setlineno(n);
			switch(n->op) {
			default:
				yyerror("select case must be receive, send or assign recv");;
				break;

			case OAS:
				// convert x = <-c into OSELRECV(x, c)
				if(n->right->op != ORECV) {
					yyerror("select assignment must have receive on right hand side");
					break;
				}
				n->op = OSELRECV;
				n->right = n->right->left;
				break;

			case ORECV:
				// convert <-c into OSELRECV(N, c)
				n->op = OSELRECV;
				n->right = n->left;
				n->left = N;
				break;

			case OSEND:
				break;
			}
		}
		typechecklist(ncase->nbody, Etop);
	}
	sel->xoffset = count;
	if(count == 0)
		yyerror("empty select");
	lineno = lno;
}

void
walkselect(Node *sel)
{
	int lno;
	Node *n, *ncase, *r, *a, *tmp, *var;
	NodeList *l, *init;

	lno = setlineno(sel);
	init = sel->ninit;
	sel->ninit = nil;

	// generate sel-struct
	var = nod(OXXX, N, N);
	tempname(var, ptrto(types[TUINT8]));
	r = nod(OAS, var, mkcall("newselect", var->type, nil, nodintconst(sel->xoffset)));
	typecheck(&r, Etop);
	init = list(init, r);

	if(sel->list == nil)
		fatal("double walkselect");	// already rewrote

	// register cases
	for(l=sel->list; l; l=l->next) {
		ncase = l->n;
		n = ncase->left;
		r = nod(OIF, N, N);
		r->nbody = ncase->ninit;
		ncase->ninit = nil;
		if(n != nil) {
			r->nbody = concat(r->nbody, n->ninit);
			n->ninit = nil;
		}
		if(n == nil) {
			// selectdefault(sel *byte);
			r->ntest = mkcall("selectdefault", types[TBOOL], &init, var);
		} else if(n->op == OSEND) {
			// selectsend(sel *byte, hchan *chan any, elem any) (selected bool);
			r->ntest = mkcall1(chanfn("selectsend", 2, n->left->type), types[TBOOL], &init, var, n->left, n->right);
		} else if(n->op == OSELRECV) {
			tmp = N;
			if(n->left == N)
				a = nodnil();
			else {
				// introduce temporary until we're sure this will succeed.
				tmp = nod(OXXX, N, N);
				tempname(tmp, n->left->type);
				a = nod(OADDR, tmp, N);
			}
			// selectrecv(sel *byte, hchan *chan any, elem *any) (selected bool);
			r->ntest = mkcall1(chanfn("selectrecv", 2, n->right->type), types[TBOOL], &init, var, n->right, a);
			if(tmp != N) {
				a = nod(OAS, n->left, tmp);
				typecheck(&a, Etop);
				r->nbody = list(r->nbody, a);
			}
		} else
			fatal("select %O", n->op);
		r->nbody = concat(r->nbody, ncase->nbody);
		r->nbody = list(r->nbody, nod(OBREAK, N, N));
		init = list(init, r);
	}

	// run the select
	init = list(init, mkcall("selectgo", T, nil, var));
	sel->nbody = init;
	sel->list = nil;
	walkstmtlist(init);

	lineno = lno;
}
