// $Id: Attr.cc,v 1.8 2003/08/31 00:38:11 vern Exp $
//
// Copyright (c) 2000, 2002
//      The Regents of the University of California.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that: (1) source code distributions
// retain the above copyright notice and this paragraph in its entirety, (2)
// distributions including binary code include the above copyright notice and
// this paragraph in its entirety in the documentation or other materials
// provided with the distribution, and (3) all advertising materials mentioning
// features or use of this software display the following acknowledgement:
// ``This product includes software developed by the University of California,
// Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
// the University nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#include "config.h"

#include "Attr.h"
#include "Expr.h"

const char* attr_name(attr_tag t)
	{
	static const char* attr_names[int(NUM_ATTRS)] = {
		"&optional", "&default", "&redef",
		"&add_func", "&delete_func", "&expire_func",
		"&read_expire", "&write_expire", "&create_expire",
	};

	return attr_names[int(t)];
	}

Attr::Attr(attr_tag t, Expr* e)
	{
	tag = t;
	expr = e;
	SetLocationInfo(&start_location, &end_location);
	}

Attr::~Attr()
	{
	Unref(expr);
	}

void Attr::Describe(ODesc* d) const
	{
	AddTag(d);

	if ( expr )
		{
		if ( ! d->IsBinary() )
			d->Add("=");

		expr->Describe(d);
		}
	}

void Attr::AddTag(ODesc* d) const
	{
	if ( d->IsBinary() )
		d->Add(int(Tag()));
	else
		d->Add(attr_name(Tag()));
	}

Attributes::Attributes(attr_list* a, BroType* t)
	{
	attrs = new attr_list;
	type = t->Ref();

	SetLocationInfo(&start_location, &end_location);

	// We loop through 'a' and add each attribute individually,
	// rather than just taking over 'a' for ourselves, so that
	// the necessary checking gets done.

	loop_over_list(*a, i)
		AddAttr((*a)[i]);

	delete a;
	}

Attributes::~Attributes()
	{
	loop_over_list(*attrs, i)
		Unref((*attrs)[i]);

	delete attrs;

	Unref(type);
	}

void Attributes::AddAttr(Attr* attr)
	{
	if ( ! attrs )
		attrs = new attr_list;

	if ( FindAttr(attr->Tag()) && ! attr->RedundantAttrOkay() )
		Warn("attribute defined twice", attr);

	attrs->append(attr);
	Ref(attr);

	// We only check the attribute after we've added it, to facilitate
	// generating error messages via Attributes::Describe.
	CheckAttr(attr);

	// For ADD_FUNC or DEL_FUNC, add in an implicit REDEF, since
	// those attributes only have meaning for a redefinable value.
	if ( (attr->Tag() == ATTR_ADD_FUNC || attr->Tag() == ATTR_DEL_FUNC) &&
	     ! FindAttr(ATTR_REDEF) )
		attrs->append(new Attr(ATTR_REDEF));

	// For DEFAULT, add an implicit OPTIONAL.
	if ( attr->Tag() == ATTR_DEFAULT && ! FindAttr(ATTR_OPTIONAL) )
		attrs->append(new Attr(ATTR_OPTIONAL));
	}

void Attributes::AddAttrs(Attributes* a)
	{
	attr_list* as = a->Attrs();
	loop_over_list(*as, i)
		AddAttr((*as)[i]);

	Unref(a);
	}

Attr* Attributes::FindAttr(attr_tag t) const
	{
	if ( ! attrs )
		return 0;

	loop_over_list(*attrs, i)
		{
		Attr* a = (*attrs)[i];
		if ( a->Tag() == t )
			return a;
		}

	return 0;
	}

void Attributes::Describe(ODesc* d) const
	{
	if ( ! attrs )
		{
		d->AddCount(0);
		return;
		}

	d->AddCount(attrs->length());

	loop_over_list(*attrs, i)
		{
		if ( (d->IsReadable() || d->IsPortable()) && i > 0 )
			d->Add(", ");

		(*attrs)[i]->Describe(d);
		}
	}

void Attributes::CheckAttr(Attr* a)
	{
	switch ( a->Tag() ) {
	case ATTR_OPTIONAL:
	case ATTR_REDEF:
		break;

	case ATTR_ADD_FUNC:
	case ATTR_DEL_FUNC:
		{
		int is_add = a->Tag() == ATTR_ADD_FUNC;

		BroType* at = a->AttrExpr()->Type();
		if ( at->Tag() != TYPE_FUNC )
			{
			a->AttrExpr()->Error(
				is_add ?
					"&add_func must be a function" :
					"&delete_func must be a function");
			break;
			}

		FuncType* aft = at->AsFuncType();
		if ( ! same_type(aft->YieldType(), type) )
			{
			a->AttrExpr()->Error(
				is_add ?
					"&add_func function must yield same type as variable" :
					"&delete_func function must yield same type as variable");
			break;
			}
		}
		break;

	case ATTR_DEFAULT:
		{
		BroType* atype = a->AttrExpr()->Type();

		if ( type->Tag() != TYPE_TABLE || type->IsSet() )
			{
			if ( ! same_type(atype, type) )
				a->AttrExpr()->Error("&default value has inconsistent type", type);
			break;
			}

		TableType* tt = type->AsTableType();

		if ( atype->Tag() == TYPE_FUNC )
			{
			FuncType* f = atype->AsFuncType();
			if ( ! f->CheckArgs(tt->IndexTypes()) ||
			     ! same_type(f->YieldType(), tt->YieldType()) )
				Error("&default function type clash");
			}

		else if ( ! same_type(atype, tt->YieldType()) )
			Error("&default value has inconsistent type");
		}
		break;

	case ATTR_EXPIRE_READ:
	case ATTR_EXPIRE_WRITE:
	case ATTR_EXPIRE_CREATE:
		if ( type->Tag() != TYPE_TABLE )
			{
			Error("expiration only applicable to tables");
			break;
			}

#if 0
		//### not easy to test this w/o knowing the ID.
		if ( ! IsGlobal() )
			Error("expiration not supported for local variables");
#endif
		break;

	case ATTR_EXPIRE_FUNC:
		{
		if ( type->Tag() != TYPE_TABLE )
			{
			Error("expiration only applicable to tables");
			break;
			}

		const Expr* expire_func = a->AttrExpr();
		const FuncType* e_ft = expire_func->Type()->AsFuncType();

		if ( ((const BroType*) e_ft)->YieldType()->Tag() != TYPE_INTERVAL )
			{
			Error("&expire_func must yield a value of type interval");
			break;
			}

		if ( e_ft->Args()->NumFields() != 1 )
			{
			Error("&expire_func function must take exactly one argument");
			break;
			}

		if ( ! same_type(e_ft->Args()->FieldType(0), type->YieldType()) )
			{
			Error("&expire_func function must take argument with table's yield type");
			break;
			}
		}
		break;

	case ATTR_PERSISTENT:
		// FIXME: Check here for global ID?
		break;

	default:
		BadTag("Attributes::CheckAttr");
	}
	}
