/*
 * Copyright (c) 2003
 *      David Leonard.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of David Leonard 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/* $Id: error.c 1282 2007-09-13 22:26:35Z d $ */

#if HAVE_CONFIG_H
# include <config.h>
#endif

#if STDC_HEADERS
# include <stdio.h>
# include <stdarg.h>
#endif

#if HAVE_STRING_H
# include <string.h>
#endif

#if HAVE_ERRNO_H
# include <errno.h>
#else
extern int errno;
#endif

#include <see/value.h>
#include <see/object.h>
#include <see/interpreter.h>
#include <see/try.h>
#include <see/mem.h>
#include <see/error.h>
#include <see/string.h>

#include "stringdefs.h"
#include "dprint.h"

#ifndef NDEBUG
int SEE_error_debug = 0;
#endif

static void error_throw(struct SEE_interpreter *, struct SEE_object *, int, 
			const char *, int, const char *, va_list) SEE_dead;

/*
 * Throw an error, optionally using the given string as the error message.
 * The string is prefixed it with the current try location.
 */
void
SEE_error__throw_string(interp, obj, filename, lineno, s)
	struct SEE_interpreter *interp;
	struct SEE_object *obj;
	const char *filename;
	int lineno;
	struct SEE_string *s;
{
	volatile struct SEE_try_context *ctxt_save;
	struct SEE_value res;

	/* If no try-catch context exists, we should abort immediately */
	if (!interp->try_context) {
		struct SEE_value v;
#ifndef NDEBUG
		if (s) {
			dprintf("message: ");
			dprints(s);
			dprintf("\n");
		}
#endif
		SEE_SET_OBJECT(&v, obj);
		SEE_throw_abort(interp, &v, filename, lineno);
		/* NOTREACHED */
	}

	/* 
	 * Temporarily remove the current try-catch context
	 * so that any exceptions thrown recusively during the construction
	 * of the error cause an interpreter abort in the block above. 
	 */
	ctxt_save = interp->try_context;
	interp->try_context = NULL;
	{
		struct SEE_string *msg;
		struct SEE_value v, *argv[1];

		msg = SEE_string_concat(interp, 
		    SEE_location_string(interp, interp->try_location), 
		    	s ? s : STR(error));
		SEE_SET_STRING(&v, msg);
		argv[0] = &v;
		SEE_OBJECT_CONSTRUCT(interp, obj, NULL, 1, argv, &res);
#ifndef NDEBUG
		if (SEE_error_debug)
		    dprintf("throwing object %p from %s:%d\n",
			res.u.object, filename ? filename : "unknown", lineno);
#endif
	}
	interp->try_context = ctxt_save;

	SEE__THROW(interp, &res, filename, lineno);
}

/*
 * Helper function for constructing an error message
 */
static void
error_throw(interp, obj, errval, filename, lineno, fmt, ap)
	struct SEE_interpreter *interp;
	struct SEE_object *obj;
	int errval;		/* -1 means no error message */
	const char *filename;
	int lineno;
	const char *fmt;
	va_list ap;
{
	struct SEE_string *s, *t;
	volatile struct SEE_try_context *ctxt_save;

	/* Detect recursive errors: */
	if (!interp->try_context) {
		struct SEE_value v;
		SEE_SET_OBJECT(&v, obj);
		SEE_throw_abort(interp, &v, filename, lineno); 
	}
	ctxt_save = interp->try_context;
	interp->try_context = NULL;

	if (fmt) {
	    s = SEE_string_vsprintf(interp, fmt, ap);
	    if (errval != -1) {
#if HAVE_STRERROR
		t = SEE_string_sprintf(interp, ": %s", strerror(errval));
#else
		t = SEE_string_sprintf(interp, ": error %d", errval);
#endif
		SEE_string_append(s, t);
	    }
	} else
	    s = NULL;
	interp->try_context = ctxt_save;

	SEE_error__throw_string(interp, obj, filename, lineno, s);
}

/*
 * Throw an error, using the given arguments as the error
 * message.
 */
void
SEE_error__throw(struct SEE_interpreter *interp, struct SEE_object *obj,
	const char *filename, int lineno, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	error_throw(interp, obj, -1, filename, lineno, fmt, ap);
	va_end(ap);
}

#if !HAVE_VARIADIC_MACROS
void
SEE_error__throw0(struct SEE_interpreter *interp, struct SEE_object *obj,
	const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	error_throw(interp, obj, -1, NULL, 0, fmt, ap);
	va_end(ap);
}
#endif

/*
 * Throw an error, with the given message appending ':' and the
 * system error message. (See manual page for 'errno')
 */
void
SEE_error__throw_sys(struct SEE_interpreter *interp, struct SEE_object *obj,
	const char *filename, int lineno, const char *fmt, ...)
{
	va_list ap;
	int errval = errno;

	va_start(ap, fmt);
	error_throw(interp, obj, errval, filename, lineno, fmt, ap);
	va_end(ap);
}

#if !HAVE_VARIADIC_MACROS
void
SEE_error__throw_sys0(struct SEE_interpreter *interp, struct SEE_object *obj,
	const char *fmt, ...)
{
	va_list ap;
	int errval = errno;

	va_start(ap, fmt);
	error_throw(interp, obj, errval, NULL, 0, fmt, ap);
	va_end(ap);
}
#endif

#if STDC_HEADERS
void
SEE_error_throw_va(struct SEE_interpreter *i, struct SEE_object *errorobj,
    const char *fmt, va_list ap)
{
	error_throw(i, errorobj, -1, NULL, 0, fmt, ap);
}

void
SEE_error_throw_sys_va(struct SEE_interpreter *i, struct SEE_object *errorobj,
    const char *fmt, va_list ap)
{
	error_throw(i, errorobj, errno, NULL, 0, fmt, ap);
}
#endif /* STDC_HEADERS */

