// SDLang-D
// Written in the D programming language.

module sdlang.exception;

import std.array;
import std.exception;
import std.range;
import std.stdio;
import std.string;

import sdlang.ast;
import sdlang.util;

/// Abstract parent class of all SDLang-D defined exceptions.
abstract class SDLangException : Exception
{
	this(string msg, string file = __FILE__, size_t line = __LINE__)
	{
		super(msg, file, line);
	}
}

/// Thrown when a syntax error is encounterd while parsing.
class ParseException : SDLangException
{
	Location location;
	bool hasLocation;

	this(string msg, string file = __FILE__, size_t line = __LINE__)
	{
		hasLocation = false;
		super(msg, file, line);
	}

	this(Location location, string msg, string file = __FILE__, size_t line = __LINE__)
	{
		hasLocation = true;
		super("%s: %s".format(location.toString(), msg), file, line);
	}
}

/// Compatibility alias
deprecated("The new name is ParseException")
alias SDLangParseException = ParseException;

/++
Thrown when attempting to do something in the DOM that's unsupported, such as:

$(UL
$(LI Adding the same instance of a tag or attribute to more than one parent.)
$(LI Writing SDLang where:
	$(UL
	$(LI The root tag has values, attributes or a namespace. )
	$(LI An anonymous tag has a namespace. )
	$(LI An anonymous tag has no values. )
	$(LI A floating point value is infinity or NaN. )
	)
))
+/
class ValidationException : SDLangException
{
	this(string msg, string file = __FILE__, size_t line = __LINE__)
	{
		super(msg, file, line);
	}
}

/// Compatibility alias
deprecated("The new name is ValidationException")
alias SDLangValidationException = ValidationException;

/// Thrown when someting is wrong with the provided arguments to a function.
class ArgumentException : SDLangException
{
	this(string msg, string file = __FILE__, size_t line = __LINE__)
	{
		super(msg, file, line);
	}
}

/// Thrown by the DOM on empty range and out-of-range conditions.
abstract class DOMException : SDLangException
{
	Tag base; /// The tag searched from

	this(Tag base, string msg, string file = __FILE__, size_t line = __LINE__)
	{
		this.base = base;
		super(msg, file, line);
	}

	/// Prefixes a message with file/line information from the tag (if tag exists).
	/// Optionally takes output range as a sink.
	string customMsg(string msg)
	{
		if(!base)
			return msg;

		Appender!string sink;
		this.customMsg(sink, msg);
		return sink.data;
	}

	///ditto
	void customMsg(Sink)(ref Sink sink, string msg) if(isOutputRange!(Sink,char))
	{
		if(base)
		{
			sink.put(base.location.toString());
			sink.put(": ");
			sink.put(msg);
		}
		else
			sink.put(msg);
	}

	/// Outputs a message to stderr, prefixed with file/line information
	void writeCustomMsg(string msg)
	{
		stderr.writeln( customMsg(msg) );
	}
}

/// Thrown by the DOM on empty range and out-of-range conditions.
class DOMRangeException : DOMException
{
	this(Tag base, string msg, string file = __FILE__, size_t line = __LINE__)
	{
		super(base, msg, file, line);
	}
}

/// Compatibility alias
deprecated("The new name is DOMRangeException")
alias SDLangRangeException = DOMRangeException;

/// Abstract parent class of `TagNotFoundException`, `ValueNotFoundException`
/// and `AttributeNotFoundException`.
///
/// Thrown by the DOM's `sdlang.ast.Tag.expectTag`, etc. functions if a matching element isn't found.
abstract class DOMNotFoundException : DOMException
{
	FullName tagName; /// The tag searched for

	this(Tag base, FullName tagName, string msg, string file = __FILE__, size_t line = __LINE__)
	{
		this.tagName = tagName;
		super(base, msg, file, line);
	}
}

/// Thrown by the DOM's `sdlang.ast.Tag.expectTag`, etc. functions if a Tag isn't found.
class TagNotFoundException : DOMNotFoundException
{
	this(Tag base, FullName tagName, string msg, string file = __FILE__, size_t line = __LINE__)
	{
		super(base, tagName, msg, file, line);
	}
}

/// Thrown by the DOM's `sdlang.ast.Tag.expectValue`, etc. functions if a Value isn't found.
class ValueNotFoundException : DOMNotFoundException
{
	/// Expected type for the not-found value.
	TypeInfo valueType;

	this(Tag base, FullName tagName, TypeInfo valueType, string msg, string file = __FILE__, size_t line = __LINE__)
	{
		this.valueType = valueType;
		super(base, tagName, msg, file, line);
	}
}

/// Thrown by the DOM's `sdlang.ast.Tag.expectAttribute`, etc. functions if an Attribute isn't found.
class AttributeNotFoundException : DOMNotFoundException
{
	FullName attributeName; /// The attribute searched for

	/// Expected type for the not-found attribute's value.
	TypeInfo valueType;

	this(Tag base, FullName tagName, FullName attributeName, TypeInfo valueType, string msg,
		string file = __FILE__, size_t line = __LINE__)
	{
		this.valueType = valueType;
		this.attributeName = attributeName;
		super(base, tagName, msg, file, line);
	}
}