// Inferno utils/cc/dpchk.c
// http://code.google.com/p/inferno-os/source/browse/utils/cc/dpchk.c
//
//	Copyright © 1994-1999 Lucent Technologies Inc.  All rights reserved.
//	Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
//	Portions Copyright © 1997-1999 Vita Nuova Limited
//	Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
//	Portions Copyright © 2004,2006 Bruce Ellis
//	Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
//	Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
//	Portions Copyright © 2009 The Go Authors.  All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include	<u.h>
#include	"cc.h"
#include	"y.tab.h"

enum
{
	Fnone	= 0,
	Fl,
	Fvl,
	Fignor,
	Fstar,
	Fadj,

	Fverb	= 10,
};

typedef	struct	Tprot	Tprot;
struct	Tprot
{
	Type*	type;
	Bits	flag;
	Tprot*	link;
};

typedef	struct	Tname	Tname;
struct	Tname
{
	char*	name;
	int	param;
	int	count;
	Tname*	link;
	Tprot*	prot;
};

static	Type*	indchar;
static	uchar	flagbits[512];
static	char*	lastfmt;
static	int	lastadj;
static	int	lastverb;
static	int	nstar;
static	Tprot*	tprot;
static	Tname*	tname;

void
argflag(int c, int v)
{

	switch(v) {
	case Fignor:
	case Fstar:
	case Fl:
	case Fvl:
		flagbits[c] = v;
		break;
	case Fverb:
		flagbits[c] = lastverb;
/*print("flag-v %c %d\n", c, lastadj);*/
		lastverb++;
		break;
	case Fadj:
		flagbits[c] = lastadj;
/*print("flag-l %c %d\n", c, lastadj);*/
		lastadj++;
		break;
	}
}

Bits
getflag(char *s)
{
	Bits flag;
	int f;
	Fmt fmt;
	Rune c;

	flag = zbits;
	nstar = 0;
	fmtstrinit(&fmt);
	for(;;) {
		s += chartorune(&c, s);
		if(c == 0 || c >= nelem(flagbits))
			break;
		fmtrune(&fmt, c);
		f = flagbits[c];
		switch(f) {
		case Fnone:
			argflag(c, Fverb);
			f = flagbits[c];
			break;
		case Fstar:
			nstar++;
		case Fignor:
			continue;
		case Fl:
			if(bset(flag, Fl))
				flag = bor(flag, blsh(Fvl));
		}
		flag = bor(flag, blsh(f));
		if(f >= Fverb)
			break;
	}
	free(lastfmt);
	lastfmt = fmtstrflush(&fmt);
	return flag;
}

static void
newprot(Sym *m, Type *t, char *s, Tprot **prot)
{
	Bits flag;
	Tprot *l;

	if(t == T) {
		warn(Z, "%s: newprot: type not defined", m->name);
		return;
	}
	flag = getflag(s);
	for(l=*prot; l; l=l->link)
		if(beq(flag, l->flag) && sametype(t, l->type))
			return;
	l = alloc(sizeof(*l));
	l->type = t;
	l->flag = flag;
	l->link = *prot;
	*prot = l;
}

static Tname*
newname(char *s, int p, int count)
{
	Tname *l;

	for(l=tname; l; l=l->link)
		if(strcmp(l->name, s) == 0) {
			if(p >= 0 && l->param != p)
				yyerror("vargck %s already defined\n", s);
			return l;
		}
	if(p < 0)
		return nil;

	l = alloc(sizeof(*l));
	l->name = s;
	l->param = p;
	l->link = tname;
	l->count = count;
	tname = l;
	return l;
}

void
arginit(void)
{
	int i;

/* debug['F'] = 1;*/
/* debug['w'] = 1;*/

	lastadj = Fadj;
	lastverb = Fverb;
	indchar = typ(TIND, types[TCHAR]);

	memset(flagbits, Fnone, sizeof(flagbits));

	for(i='0'; i<='9'; i++)
		argflag(i, Fignor);
	argflag('.', Fignor);
	argflag('#', Fignor);
	argflag('u', Fignor);
	argflag('h', Fignor);
	argflag('+', Fignor);
	argflag('-', Fignor);

	argflag('*', Fstar);
	argflag('l', Fl);

	argflag('o', Fverb);
	flagbits['x'] = flagbits['o'];
	flagbits['X'] = flagbits['o'];
}

static char*
getquoted(void)
{
	int c;
	Rune r;
	Fmt fmt;

	c = getnsc();
	if(c != '"')
		return nil;
	fmtstrinit(&fmt);
	for(;;) {
		r = getr();
		if(r == '\n') {
			free(fmtstrflush(&fmt));
			return nil;
		}
		if(r == '"')
			break;
		fmtrune(&fmt, r);
	}
	free(lastfmt);
	lastfmt = fmtstrflush(&fmt);
	return strdup(lastfmt);
}

void
pragvararg(void)
{
	Sym *s;
	int n, c;
	char *t;
	Type *ty;
	Tname *l;

	if(!debug['F'])
		goto out;
	s = getsym();
	if(s && strcmp(s->name, "argpos") == 0)
		goto ckpos;
	if(s && strcmp(s->name, "type") == 0)
		goto cktype;
	if(s && strcmp(s->name, "flag") == 0)
		goto ckflag;
	if(s && strcmp(s->name, "countpos") == 0)
		goto ckcount;
	yyerror("syntax in #pragma varargck");
	goto out;

ckpos:
/*#pragma	varargck	argpos	warn	2*/
	s = getsym();
	if(s == S)
		goto bad;
	n = getnsn();
	if(n < 0)
		goto bad;
	newname(s->name, n, 0);
	goto out;

ckcount:
/*#pragma	varargck	countpos	name 2*/
	s = getsym();
	if(s == S)
		goto bad;
	n = getnsn();
	if(n < 0)
		goto bad;
	newname(s->name, 0, n);
	goto out;

ckflag:
/*#pragma	varargck	flag	'c'*/
	c = getnsc();
	if(c != '\'')
		goto bad;
	c = getr();
	if(c == '\\')
		c = getr();
	else if(c == '\'')
		goto bad;
	if(c == '\n')
		goto bad;
	if(getc() != '\'')
		goto bad;
	argflag(c, Fignor);
	goto out;

cktype:
	c = getnsc();
	unget(c);
	if(c != '"') {
/*#pragma	varargck	type	name	int*/
		s = getsym();
		if(s == S)
			goto bad;
		l = newname(s->name, -1, -1);
		s = getsym();
		if(s == S)
			goto bad;
		ty = s->type;
		while((c = getnsc()) == '*')
			ty = typ(TIND, ty);
		unget(c);
		newprot(s, ty, "a", &l->prot);
		goto out;
	}

/*#pragma	varargck	type	O	int*/
	t = getquoted();
	if(t == nil)
		goto bad;
	s = getsym();
	if(s == S)
		goto bad;
	ty = s->type;
	while((c = getnsc()) == '*')
		ty = typ(TIND, ty);
	unget(c);
	newprot(s, ty, t, &tprot);
	goto out;

bad:
	yyerror("syntax in #pragma varargck");

out:
	while(getnsc() != '\n')
		;
}

Node*
nextarg(Node *n, Node **a)
{
	if(n == Z) {
		*a = Z;
		return Z;
	}
	if(n->op == OLIST) {
		*a = n->left;
		return n->right;
	}
	*a = n;
	return Z;
}

void
checkargs(Node *nn, char *s, int pos)
{
	Node *a, *n;
	Bits flag;
	Tprot *l;

	if(!debug['F'])
		return;
	n = nn;
	for(;;) {
		s = strchr(s, '%');
		if(s == 0) {
			nextarg(n, &a);
			if(a != Z)
				warn(nn, "more arguments than format %T",
					a->type);
			return;
		}
		s++;
		flag = getflag(s);
		while(nstar > 0) {
			n = nextarg(n, &a);
			pos++;
			nstar--;
			if(a == Z) {
				warn(nn, "more format than arguments %s",
					lastfmt);
				return;
			}
			if(a->type == T)
				continue;
			if(!sametype(types[TINT], a->type) &&
			   !sametype(types[TUINT], a->type))
				warn(nn, "format mismatch '*' in %s %T, arg %d",
					lastfmt, a->type, pos);
		}
		for(l=tprot; l; l=l->link)
			if(sametype(types[TVOID], l->type)) {
				if(beq(flag, l->flag)) {
					s++;
					goto loop;
				}
			}

		n = nextarg(n, &a);
		pos++;
		if(a == Z) {
			warn(nn, "more format than arguments %s",
				lastfmt);
			return;
		}
		if(a->type == 0)
			continue;
		for(l=tprot; l; l=l->link)
			if(sametype(a->type, l->type)) {
/*print("checking %T/%ux %T/%ux\n", a->type, flag.b[0], l->type, l->flag.b[0]);*/
				if(beq(flag, l->flag))
					goto loop;
			}
		warn(nn, "format mismatch %s %T, arg %d", lastfmt, a->type, pos);
	loop:;
	}
}

void
dpcheck(Node *n)
{
	char *s;
	Node *a, *b;
	Tname *l;
	Tprot *tl;
	int i, j;

	if(n == Z)
		return;
	b = n->left;
	if(b == Z || b->op != ONAME)
		return;
	s = b->sym->name;
	for(l=tname; l; l=l->link)
		if(strcmp(s, l->name) == 0)
			break;
	if(l == 0)
		return;

	if(l->count > 0) {
		// fetch count, then check remaining length
		i = l->count;
		a = nil;
		b = n->right;
		while(i > 0) {
			b = nextarg(b, &a);
			i--;
		}
		if(a == Z) {
			diag(n, "can't find count arg");
			return;
		}
		if(a->op != OCONST || !typechl[a->type->etype]) {
			diag(n, "count is invalid constant");
			return;
		}
		j = a->vconst;
		i = 0;
		while(b != Z) {
			b = nextarg(b, &a);
			i++;
		}
		if(i != j)
			diag(n, "found %d argument%s after count %d", i, i == 1 ? "" : "s", j);
	}

	if(l->prot != nil) {
		// check that all arguments after param or count
		// are listed in type list.
		i = l->count;
		if(i == 0)
			i = l->param;
		if(i == 0)
			return;
		a = nil;
		b = n->right;
		while(i > 0) {
			b = nextarg(b, &a);
			i--;
		}
		if(a == Z) {
			diag(n, "can't find count/param arg");
			return;
		}
		while(b != Z) {
			b = nextarg(b, &a);
			for(tl=l->prot; tl; tl=tl->link)
				if(sametype(a->type, tl->type))
					break;
			if(tl == nil)
				diag(a, "invalid type %T in call to %s", a->type, s);
		}
	}

	if(l->param <= 0)
		return;
	i = l->param;
	a = nil;
	b = n->right;
	while(i > 0) {
		b = nextarg(b, &a);
		i--;
	}
	if(a == Z) {
		diag(n, "can't find format arg");
		return;
	}
	if(!sametype(indchar, a->type)) {
		diag(n, "format arg type %T", a->type);
		return;
	}
	if(a->op != OADDR || a->left->op != ONAME || a->left->sym != symstring) {
/*		warn(n, "format arg not constant string");*/
		return;
	}
	s = a->left->cstring;
	checkargs(b, s, l->param);
}

void
pragpack(void)
{
	Sym *s;

	packflg = 0;
	s = getsym();
	if(s) {
		packflg = atoi(s->name+1);
		if(strcmp(s->name, "on") == 0 ||
		   strcmp(s->name, "yes") == 0)
			packflg = 1;
	}
	while(getnsc() != '\n')
		;
	if(debug['f'])
		if(packflg)
			print("%4d: pack %d\n", lineno, packflg);
		else
			print("%4d: pack off\n", lineno);
}

void
pragfpround(void)
{
	Sym *s;

	fproundflg = 0;
	s = getsym();
	if(s) {
		fproundflg = atoi(s->name+1);
		if(strcmp(s->name, "on") == 0 ||
		   strcmp(s->name, "yes") == 0)
			fproundflg = 1;
	}
	while(getnsc() != '\n')
		;
	if(debug['f'])
		if(fproundflg)
			print("%4d: fproundflg %d\n", lineno, fproundflg);
		else
			print("%4d: fproundflg off\n", lineno);
}

void
pragtextflag(void)
{
	textflag = getnsn();
	while(getnsc() != '\n')
		;
	if(debug['f'])
		print("%4d: textflag %d\n", lineno, textflag);
}

void
pragdataflag(void)
{
	dataflag = getnsn();
	while(getnsc() != '\n')
		;
	if(debug['f'])
		print("%4d: dataflag %d\n", lineno, dataflag);
}

void
pragincomplete(void)
{
	Sym *s;
	Type *t;
	int istag, w, et;

	istag = 0;
	s = getsym();
	if(s == nil)
		goto out;
	et = 0;
	w = s->lexical;
	if(w == LSTRUCT)
		et = TSTRUCT;
	else if(w == LUNION)
		et = TUNION;
	if(et != 0){
		s = getsym();
		if(s == nil){
			yyerror("missing struct/union tag in pragma incomplete");
			goto out;
		}
		if(s->lexical != LNAME && s->lexical != LTYPE){
			yyerror("invalid struct/union tag: %s", s->name);
			goto out;
		}
		dotag(s, et, 0);
		istag = 1;
	}else if(strcmp(s->name, "_off_") == 0){
		debug['T'] = 0;
		goto out;
	}else if(strcmp(s->name, "_on_") == 0){
		debug['T'] = 1;
		goto out;
	}
	t = s->type;
	if(istag)
		t = s->suetag;
	if(t == T)
		yyerror("unknown type %s in pragma incomplete", s->name);
	else if(!typesu[t->etype])
		yyerror("not struct/union type in pragma incomplete: %s", s->name);
	else
		t->garb |= GINCOMPLETE;
out:
	while(getnsc() != '\n')
		;
	if(debug['f'])
		print("%s incomplete\n", s->name);
}

Sym*
getimpsym(void)
{
	int c;
	char *cp;

	c = getnsc();
	if(isspace(c) || c == '"') {
		unget(c);
		return S;
	}
	for(cp = symb;;) {
		if(cp <= symb+NSYMB-4)
			*cp++ = c;
		c = getc();
		if(c > 0 && !isspace(c) && c != '"')
			continue;
		unget(c);
		break;
	}
	*cp = 0;
	if(cp > symb+NSYMB-4)
		yyerror("symbol too large: %s", symb);
	return lookup();
}

void
pragdynimport(void)
{
	Sym *local, *remote;
	char *path;
	Dynimp *f;

	local = getimpsym();
	if(local == nil)
		goto err;

	remote = getimpsym();
	if(remote == nil)
		goto err;

	path = getquoted();
	if(path == nil)
		goto err;

	if(ndynimp%32 == 0)
		dynimp = realloc(dynimp, (ndynimp+32)*sizeof dynimp[0]);
	f = &dynimp[ndynimp++];
	f->local = local->name;
	f->remote = remote->name;
	f->path = path;
	goto out;

err:
	yyerror("usage: #pragma dynimport local remote \"path\"");

out:
	while(getnsc() != '\n')
		;
}

void
pragdynexport(void)
{
	Sym *local, *remote;
	Dynexp *f;

	local = getsym();
	if(local == nil)
		goto err;

	remote = getsym();
	if(remote == nil)
		goto err;

	if(ndynexp%32 == 0)
		dynexp = realloc(dynexp, (ndynexp+32)*sizeof dynexp[0]);
	f = &dynexp[ndynexp++];
	f->local = local->name;
	f->remote = remote->name;
	goto out;

err:
	yyerror("usage: #pragma dynexport local remote");

out:
	while(getnsc() != '\n')
		;
}
