#pragma warning(disable: 4786)

#include "TCoreCalc.h"
#pragma hdrstop

NAMESPACE_UPP

#define LLOG(x) // LOG(x)

String CalcType<const CalcNode *>::Describe() { return "lambda-výraz"; }
RegisterStdCalcTypeName(const CalcNode *)

int CharFilterCalcIdent(int c)      { return IsIdent(c) ? c : 0; }
int CharFilterCalcUpperIdent(int c) { return IsIdent(c) ? ToUpper(c) : 0; }

RegisterHelpTopicTitle("CalcNode", t_("Expression evaluator"))

enum
{
	MAX_NESTING_DEPTH = 1000,
};

HelpCalc::HelpCalc(CalcGate proc, const char *ident_, const char *topic_, String (*groupfn_)())
//: generator(gen)
{
	Init(proc, ident_, topic_, groupfn_);
	AddHelpCalc(*this);
//	ord = HelpCalcIndex::Get().Add(*this);
}

void HelpCalc::Init(CalcGate proc_, const char *ident_, const char *topic_, String (*groupfn_)())
{
	proc = proc_;
	ident = ident_;
	topic = topic_;
	groupfn = groupfn_;
/*
	sortname = id = _id;

	const char *p = (head ? head : "");
	const char *b = p;

	if(*p == '|')
	{ // sort name
		b = ++p;
		sortname.Clear();
		while(*p && *p != '|' && *p != '\n')
		{
			if((*p == '\\' || *p == '`') && p[1])
				p++;
			sortname << *p++;
		}
		if(*p == '|')
			p++;
		while(*p && *p != '\n' && *p != '\t' && (byte)*p <= ' ')
			p++;
		b = p;
	}

	while(*p && *p != '\n' && *p != '\t' && *p != '(')
		p++;
	const char *e = p;
	while(e > b && e[-1] == ' ')
		e--;
	if(e > b)
		result = String(b, e);

	if(*p == '(')
	{ // arguments given
		p++;
		do
		{
			while(*p && (byte)*p <= ' ')
				p++;
			if(*p == ')')
			{
				p++;
				break;
			}
			if(*p == ':')
			{ // everything is type
				b = ++p;
				while(*p && *p != '\n' && *p != ',' && *p != ')')
					p++;
				arg_types.Add(String(b, p));
				arg_names.Add("");
			}
			else
			{ // name or type + name
				const char *s = b = p;
				while(*p && *p != '\n' && *p != ',' && *p != ')')
					if(*p == '\1')
					{ // skip inline part
						while(*p && *p++ != '\1')
							;
					}
					else if((*p == '`' || *p == '\\') && p[1])
						p += 2;
					else if(*p == '[')
					{ // skip QTF leader
						p++;
						while(*p && *p++ != ' ')
							;
					}
					else if(*p++ == ' ')
						s = p;

				for(e = p; e > b && (byte)e[-1] <= ' '; e--)
					;

				if(s == b)
				{ // name only
					arg_types.Add("");
					arg_names.Add(String(b, e));
				}
				else
				{ // type name
					arg_names.Add(String(s, e));
					while(s > b && (byte)*s <= ' ')
						s--;
					arg_types.Add(String(b, s));
				}
			}
		}
		while(*p++ == ',');
		if(p[-1] != ')')
		{
			p--;
			NEVER(); // missing right parenthesis in function declaration (found p)
		}
	}
	while(*p && *p != '\t' && *p != '\n' && (byte)*p <= ' ')
		p++;
	if(*p == '\t')
	{ // group / subgroup given
		while(*++p && (byte)*p <= ' ')
			p++;
		b = p;
		while(*p && *p != '/' && *p != '\n')
			if((*p == '\\' || *p == '`') && *++p)
				p++; // explicit char
			else if(*p == '[')
			{ // skip qtf tags
				while(*p && *p++ != ' ')
					;
			}
			else
				p++;

		for(e = p; e > b && (byte)e[-1] <= ' '; e--)
			;
		group = String(b, e);
		stripped_group = StripQtf(group);

		if(*p == '/')
			p++;

		while(*p && *p != '\n' && (byte)*p <= ' ')
			p++;
		b = p;
		while(*p && *p != '\n')
			p++;
		for(e = p; e > b && (byte)e[-1] <= ' '; e--)
			;
		subgroup = String(b, e);
		stripped_subgroup = StripQtf(subgroup);
	}
	if(*p && *p != '\n')
		NEVER(); // end of line expected after arguments & group spec (found p)
	if(*p == '\n')
		p++;
	index_entries = p;

	CalcPacket packet(*(CalcContext *)0, id);
	packet.help = true;
	VERIFY(proc(packet));
	if(packet.args.GetCount() > arg_types.GetCount())
		arg_types.SetCount(packet.args.GetCount());
	if(arg_types.GetCount() > arg_names.GetCount())
		arg_names.SetCount(arg_types.GetCount());
	else
		arg_types.SetCount(arg_names.GetCount());

	for(int i = 0; i < packet.args.GetCount(); i++)
		if(IsNull(arg_types[i]))
			arg_types[i] = packet.args[i].Format();
	if(IsNull(result))
		result = packet.result.Format();
*/
}

String HelpCalc::GetTitle() const
{
	CalcPacket packet(*(CalcContext *)0, ident);
	packet.help = true;
	VERIFY(proc(packet));
	Vector<String> arg_types;
	arg_types.SetCount(packet.args.GetCount());
	for(int i = 0; i < packet.args.GetCount(); i++)
		arg_types[i] = StdFormat(packet.args[i]);
	String result = StdFormat(packet.result);
	String out;
	if(!IsAlpha(*ident))
		out << "operátor \1" << ident << "\1 ";
	else
		out << "\1" << ident << "\1";
	if(!arg_types.IsEmpty())
	{
		for(int i = 0; i < arg_types.GetCount(); i++)
			out << (i ? "\1, \1" : "(\1") << arg_types[i];
		out << "\1)";
	}
	out << " [S \xAE] \1" << result << "\1";
	return out;
}

HelpCalcMap& GetHelpCalcMap()
{
	static HelpCalcMap map;
	return map;
}

void AddHelpCalc(const HelpCalc& calc)
{
	GetHelpCalcMap().Add(&calc);
}

Vector<String> GetHelpCalcGroups()
{
	Index<String> group;
	const HelpCalcMap& map = GetHelpCalcMap();
	for(int i = 0; i < map.GetCount(); i++)
		if(map[i]->groupfn)
			group.FindAdd(map[i]->groupfn());
	Vector<String> gnames = group.PickKeys();
	Sort(gnames, GetLanguageInfo());
	if(gnames.GetCount() >= 2 && gnames[0].IsEmpty())
	{
		gnames.Remove(0);
		gnames.Add(Null);
	}
	return gnames;
}

static void HelpCalcAutoIndex(Index<WString>& entries, Vector<String>& drls)
{
	const HelpCalcMap& map = GetHelpCalcMap();
	for(int i = 0; i < map.GetCount(); i++)
	{
		const HelpCalc& hc = *map[i];
		WString entry;
		entry << WString("[/ \1") << hc.ident.ToWString()
			<< WString(IsAlpha(*hc.ident) ? "\1], funkce" : "\1], operátor");
		int f = entries.Find(entry);
		String drl = HelpAppDPP(hc.topic);
		if(f >= 0)
			drls[f] << ";!" << drl;
		else
		{
			entries.Add(entry);
			drls.Add('!' + drl);
		}
	}
}

HELPINITBLOCK { RegisterHelpAutoIndex(&HelpCalcAutoIndex); }

//////////////////////////////////////////////////////////////////////
// CalcFunctionNode::

Value CalcFunctionNode::Calc(CalcContext& context) const
{
	One<CalcPacket> packet = new CalcPacket(context, name);
	ScanArgs(*packet);
	return context.Calc(*packet);
}

String CalcFunctionNode::Format() const
{
	String out;
	bool is_oper = !IsAlpha(*name);
	if(is_oper)
		out << "operator ";
	out << name;
	if(!args.IsEmpty())
	{
		for(int i = 0; i < args.GetCount(); i++)
		{
			String s = args[i]->Format();
			if(IsNull(s))
				return Null;
			out << (i ? ", " : "(") << s;
		}
		out << ")";
	}
	else if(is_oper)
		out << "()";
	return out;
}

String CalcConstNode::Format() const
{
	if(IsNull(value))
		return "null";
	if(IsNumber(value))
		return FormatDouble(value, 15);
	if(IsString(value))
		return AsCString(String(value));
	if(IsDateTime(value))
	{
		Time tm(value);
		if(tm.hour == 0 && tm.minute == 0 && tm.second == 0)
			return NFormat("date(%d, %d, %d)", tm.year, tm.month, tm.day);
		else
			return NFormat("time(%d, %d, %d, %d, %d, %d)", tm.year, tm.month, tm.day, tm.hour, tm.minute, tm.second);
	}
	return Null;
}

Value CalcConstNode::Calc(CalcContext& context) const
{
	return value;
}

Value CalcLambdaNode::Calc(CalcContext& context) const
{
	return CalcType<const CalcNode *>::ToValue(~args[0]);
}

String CalcLambdaNode::Format() const
{
	String e = args[0]->Format();
	if(IsNull(e))
		return Null;
	return NFormat("#(%s)", e);
}

String CalcLogOrNode::Format() const
{
	String l = args[0]->Format(), r = args[1]->Format();
	if(IsNull(l) || IsNull(r))
		return Null;
	return NFormat("(%s) || (%s)", l, r);
}

CalcNodePtr CalcLogOrNode::Optimize(CalcContext& context)
{
	if(!args[0]->IsConst())
		return this;
	if(CalcType<bool>::ValueTo(args[0]->Calc(context)))
		return new CalcConstNode(1);
	return args[1];
}

Value CalcLogOrNode::Calc(CalcContext& context) const
{
	Value var = args[0]->Calc(context);
	if(!CalcType<bool>::IsType(var))
		Expect(t_("boolean value"), context.FormatNull(var));
	if(CalcType<bool>::ValueTo(var))
		return CalcType<bool>::ToValue(true);
	var = args[1]->Calc(context);
	if(!CalcType<bool>::IsType(var))
		Expect(t_("boolean value"), context.FormatNull(var));
	return CalcType<bool>::ToValue(CalcType<bool>::ValueTo(var));
}

String CalcLogAndNode::Format() const
{
	String l = args[0]->Format(), r = args[1]->Format();
	if(IsNull(l) || IsNull(r))
		return Null;
	return NFormat("(%s) && (%s)", l, r);
}

Value CalcLogAndNode::Calc(CalcContext& context) const
{
	Value var = args[0]->Calc(context);
	if(!CalcType<bool>::IsType(var))
		Expect(t_("boolean value"), context.FormatNull(var));
	if(!CalcType<bool>::ValueTo(var))
		return CalcType<bool>::ToValue(false);
	var = args[1]->Calc(context);
	if(!CalcType<bool>::IsType(var))
		Expect(t_("boolean value"), context.FormatNull(var));
	return CalcType<bool>::ToValue(CalcType<bool>::ValueTo(var));
}

CalcNodePtr CalcLogAndNode::Optimize(CalcContext& context)
{
	if(!args[0]->IsConst())
		return this;
	if(!CalcType<bool>::ValueTo(args[0]->Calc(context)))
		return new CalcConstNode(0);
	return args[1];
}

String CalcSelectNode::Format() const
{
	String c = args[COND]->Format(), t = args[IFTRUE]->Format(), f = args[IFFALSE]->Format();
	if(IsNull(c) || IsNull(t) || IsNull(f))
		return Null;
	return NFormat("(%s) ? (%s) : (%s)", c, t, f);
}

Value CalcSelectNode::Calc(CalcContext& context) const
{
	Value var = args[COND]->Calc(context);
	if(!CalcType<bool>::IsType(var))
		Expect(t_("boolean value"), context.FormatNull(var));
	return CalcType<bool>::ValueTo(var) ? args[IFTRUE]->Calc(context) : args[IFFALSE]->Calc(context);
}

CalcNodePtr CalcSelectNode::Optimize(CalcContext& context)
{
	if(!args[0]->IsConst())
		return this;
	return args[CalcType<bool>::ValueTo(args[0]->Calc(context)) ? 1 : 2];
}

String CalcSwitchNode::Format() const
{
	String out;
	String c = args[0]->Format();
	if(IsNull(c))
		return Null;
	out = NFormat("(%s){", c);
	for(int i = 1; i < args.GetCount(); i++)
	{
		String s = args[i]->Format();
		if(IsNull(s))
			return Null;
		out << (i == 1 ? "(" : ", (") << s << ')';
	}
	out << '}';
	return out;
}

Value CalcSwitchNode::Calc(CalcContext& context) const
{
	Value value = args[0]->Calc(context);
	if(args.GetCount() == 2)
	{ // special case - 1-arg switch works as Nvl
		if(!IsNull(value))
			return value;
		return args[1]->Calc(context);
	}
	int i = 0, t = (args.GetCount() - 1) & ~1;
	for(; i < t; i += 2)
		if(value == args[i + 1]->Calc(context))
			return args[i + 2]->Calc(context);
	if(i + 1 < args.GetCount())
		return args[i + 1]->Calc(context);
	else
		return value;
}

CalcNodePtr CalcSwitchNode::Optimize(CalcContext& context)
{
	if(!args[0]->IsConst())
		return this;
	Value value = args[0]->Calc(context);
	if(args.GetCount() == 2)
	{ // special case - 1-arg switch works as Nvl
		if(!IsNull(value))
			return new CalcConstNode(value);
		return args[1];
	}
	int i = 0, t = (args.GetCount() - 1) & ~1;
	for(; i < t; i += 2)
	{
		if(!args[i + 1]->IsConst())
			return this;
		if(value == args[i + 1]->Calc(context))
			return args[i + 2];
	}
	if(i + 1 < args.GetCount())
		return args[i + 1];
	else
		return new CalcConstNode(value);
}

void CalcSymbols::Clear()
{
	var_index.Clear();
	var_value.Clear();
	var_const.Clear();
	functions.Clear();
}

void CalcSymbols::Remove(String name)
{
	int i = var_index.Find(name);
	if(i >= 0)
	{
		var_index.Remove(i);
		var_value.Remove(i);
		var_const.Remove(i);
	}
}

CalcContext::CalcContext()
: nesting(0)
, opt_const(false)
{
	language = GetCurrentLanguage();
	UseCalcBasic();
	stack.Add();
}

CalcContext::CalcContext(const CalcContext& context, int) : nesting(context.nesting), stack(context.stack, 0) {}
CalcContext::~CalcContext() {}

Value CalcContext::Get(String name) const
{
	for(int l = stack.GetCount(); --l >= 0;)
	{
		const CalcSymbols& level = stack[l];
		int f = level.var_index.Find(name);
		if(f >= 0)
			return level.var_value[f];
	}
	return Value();
}

Value CalcContext::Evaluate(String expr)
{
	CalcParser parser;
	try
	{
		CalcNodePtr cn = parser.ScanVoid(expr);
		if(!cn)
			return Value();
		return cn->Calc(*this);
	}
	catch(Exc e)
	{
		return ErrorValue(e);
	}
}

double CalcContext::EvaluateDouble(String expr)
{
	CalcNodePtr node = CalcParser().ScanVoid(expr);
	return !!node ? node->CalcDouble(*this) : double(Null);
}

String CalcContext::EvaluateString(String expr)
{
	CalcNodePtr node = CalcParser().ScanVoid(expr);
	return !!node ? node->CalcString(*this) : String(Null);
}

int CalcContext::EvaluateInt(String expr)
{
	CalcNodePtr node = CalcParser().ScanVoid(expr);
	return !!node ? node->CalcInt(*this) : int(Null);
}

Date CalcContext::EvaluateDate(String expr)
{
	CalcNodePtr node = CalcParser().ScanVoid(expr);
	return !!node ? node->CalcDate(*this) : Date(Null);
}

Time CalcContext::EvaluateTime(String expr)
{
	CalcNodePtr node = CalcParser().ScanVoid(expr);
	return !!node ? node->CalcTime(*this) : Time(Null);
}

bool CalcContext::EvaluateBool(String expr)
{
	CalcNodePtr node = CalcParser().ScanVoid(expr);
	return !!node && node->CalcBool(*this);
}

String CalcContext::OptimizeConstant(String expr)
{
	CalcNodePtr in_expr = CalcParser().ScanVoid(expr);
	if(!in_expr)
		return expr;
	CalcNodePtr out_expr = OptimizeConstant(in_expr);
	if(out_expr == in_expr)
		return expr;
	return Nvl(out_expr->Format(), expr);
}

static CalcNodePtr OptimizeConstantRaw(CalcNodePtr src, CalcContext& context)
{
	CalcNodePtr clone = src->Clone();
	if(!clone)
		return src;
	bool modf = false;
	for(int i = 0; i < clone->args.GetCount(); i++)
	{
		CalcNodePtr opt = OptimizeConstantRaw(src->args[i], context);
		if(!!opt && opt != src->args[i])
		{
			modf = true;
			clone->args[i] = opt;
		}
	}
	if(modf)
		src = clone;
	src = src->Optimize(context);
	if(src->IsConst())
		return src;

	try
	{
		Value v = src->Calc(context);
		if(!v.IsError())
			return new CalcConstNode(v);
	}
	catch(Exc e)
	{
	}

	return src;
}

CalcNodePtr CalcContext::OptimizeConstant(CalcNodePtr src)
{
	if(!src)
		return src;
	try
	{
		opt_const = true;
		CalcNodePtr out = OptimizeConstantRaw(src, *this);
		opt_const = false;
		return out;
	}
	catch(Exc e)
	{
		opt_const = false;
		return src;
	}
}

String CalcContext::Convert(String s, bool throw_errors, const UPP::Convert& convert)
{
	if(s.IsEmpty())
		return Null;
	Vector<String> sparts;
	Vector<CalcNodePtr> cparts;
	ParseConvert(s, sparts, cparts, throw_errors);
	return CalcConvert(sparts, cparts, throw_errors, convert);
}

void CalcContext::ParseConvert(String s, Vector<String>& sparts, Vector<CalcNodePtr>& cparts, bool throw_errors)
{
	const char *p = s;
	sparts.Add();
	while(*p)
	{
		const char *b = p;
		while(*p && (*p != '<' || p[1] != ':'))
			p++;
		sparts.Top().Cat(b, p - b);
		if(*p)
		{
			p += 2;
			b = p;
			while(*p && (*p != ':' || p[1] != '>'))
				if(*p++ == '\"')
				{
					while(*p && *p != '\"')
						if(*p++ == '\\' && *p)
							p++;
					if(*p)
						p++;
				}
			try
			{
				CalcNodePtr cn = CalcParser().ScanVoid(String(b, p));
				if(!!cn)
				{
					cparts.Add() = cn;
					sparts.Add();
				}
			}
			catch(Exc e)
			{
				if(throw_errors)
					throw;
				sparts.Top().Cat(e);
			}
			if(*p)
				p += 2;
		}
	}
}

String CalcContext::CalcConvert(const Vector<String>& sparts, const Vector<CalcNodePtr>& cparts, bool throw_errors, const UPP::Convert& convert)
{
	String out;
	for(int i = 0; i < sparts.GetCount(); i++)
	{
		out.Cat(sparts[i]);
		if(i < cparts.GetCount())
			try
			{
				out.Cat(FormatText(cparts[i]->Calc(*this), convert));
			}
			catch(Exc e)
			{
				if(throw_errors)
					throw;
				out.Cat(e);
			}
	}
	return out;
}

Value CalcContext::Calc(CalcPacket& packet)
{
	try {
#ifdef _DEBUG
		int level = nesting;
		Value out = TryCalc(packet);
		ASSERT(nesting == level);
		return out;
#else
		return TryCalc(packet);
#endif
	}
	catch(Exc e) {
		LOG(e);
		throw Exc(packet.name + ": " + e);
	}
}

Value CalcContext::TryCalc(CalcPacket& packet)
{
	LLOG("TryCalc <- " << packet.name << " + " << packet.args.GetCount() << " args" << BeginIndent);

	// calculate locals & globals
	if(++nesting > MAX_NESTING_DEPTH) {
		nesting--;
		throw Exc(NFormat(t_("function nesting too deep (%d levels)"), MAX_NESTING_DEPTH));
	}

	bool none = true;

	for(int l = stack.GetCount(); --l >= 0;) {
		const CalcSymbols& level = stack[l];
		int i = level.functions.Find(packet.name);
		if(i >= 0) { // try functions
			none = false;
			for(; i >= 0; i = level.functions.FindNext(i))
				try {
					if(level.functions[i](packet)) {
						LLOG(EndIndent << "//TryCalc->" << StdFormat(packet.result));
						nesting--;
						return packet.result;
					}
				}
				catch(...) {
					nesting--;
					throw;
				}
		}
		if(packet.args.IsEmpty() && (i = level.var_index.Find(packet.name)) >= 0) {
			if(opt_const && !level.var_const[i]) {
				nesting--;
				throw Exc(NFormat(t_("%s is not a constant"), packet.name));
			}
			LLOG(EndIndent << "//TryCalc->" << StdFormat(level.var_value[i]));
			nesting--;
			return level.var_value[i];
		}
	}

	for(int e = externals.GetCount(); --e >= 0;) {
		const CalcSymbols& level = *externals[e];
		int i = level.functions.Find(packet.name);
		if(i >= 0) { // try functions
			none = false;
			for(; i >= 0; i = level.functions.FindNext(i))
				try {
					if(level.functions[i](packet)) {
						LLOG(EndIndent << "//TryCalc->" << StdFormat(packet.result));
						nesting--;
						return packet.result;
					}
				}
				catch(...) {
					nesting--;
					throw;
				}
		}
		if(packet.args.IsEmpty() && (i = level.var_index.Find(packet.name)) >= 0) {
			if(opt_const && !level.var_const[i]) {
				nesting--;
				throw Exc(NFormat(t_("%s is not a constant"), packet.name));
			}
			LLOG(EndIndent << "//TryCalc->" << StdFormat(level.var_value[i]));
			nesting--;
			return level.var_value[i];
		}
	}

	{ // try globals
		const CalcProcMap& globals = GetGlobals();
		int i = globals.Find(packet.name);
		if(i >= 0) {
			none = false;
			for(; i >= 0; i = globals.FindNext(i))
				try {
					if(globals[i](packet)) {
						LLOG(EndIndent << "//TryCalc->" << StdFormat(packet.result));
						nesting--;
						return packet.result;
					}
				}
				catch(...) {
					nesting--;
					throw;
				}
		}
	}

	nesting--;

	if(none)
		throw Exc((packet.args.IsEmpty() ? t_("variable/constant '") : t_("function '")) + packet.name + t_("' not found"));

	String s = packet.name;
	if(!packet.args.IsEmpty()) {
		bool no_text = (s == "text");
		s << '(' << FormatNull(packet.args[0], no_text);
		for(const Value *p = packet.args.Begin() + 1, *e = packet.args.End(); p < e; p++)
			s << ", " << FormatNull(*p, no_text);
		s << ')';
	}
	s << ": ";
	switch(packet.args.GetCount()) {
		case 0:  s << t_("missing arguments"); break;
		case 1:  s << t_("illegal type ") << packet.GetTypeNames() << t_(" or number of parameters"); break;
		default: s << t_("illegal type combination ") << packet.GetTypeNames(); break;
	}

	throw Exc(s);
}

CalcProcMap& CalcContext::GetGlobals()
{
	static CalcProcMap list;
	return list;
}

String CalcContext::GetTypeName(Value value) const
{
	if(IsNull(value))
		return "Null";
/*
	switch(value.GetType())
	{
	case INT_V:    return CalcType<int>::Describe();
	case DOUBLE_V: return CalcType<double>::Describe();
	case STRING_V: return CalcType<String>::Describe();
	case TIME_V:   return CalcType<Time>::Describe();
	case DATE_V:   return CalcType<Date>::Describe();
	}
*/	return CalcTypeNameConvert::Format(typeid(*value.GetVoidPtr()).name());
}

String CalcContext::FormatNull(Value value, bool no_text)
{
	if(value.IsError())
		return GetErrorText(value);
	if(IsNull(value))
		return "Null";
	if(IsString(value))
		return '\"' + (String)value + '\"';
	if(no_text)
	{
		if(IsDateTime(value) || IsNumber(value))
			return StdFormat(value);
		return GetTypeName(value) + "(?)";
	}

	try
	{
		return FormatText(value);
	}
	catch(Exc e)
	{
		return e;
	}
}

String CalcContext::FormatText(Value value, const UPP::Convert& convert)
{
	if(value.IsError())
		throw Exc(GetErrorText(value));
	Value v = convert.Format(value);
	if(IsNull(v) || IsString(v))
		return v;
	CalcPacket packet(*this, "text");
	packet.args.SetCount(1);
	packet.args[0] = v;
	return StdFormat(Calc(packet));
}

void CalcContext::PushLevel()
{
	if(stack.GetCount() >= MAX_STACK_DEPTH)
		throw Exc(t_("Maximum nesting depth (") + IntStr(MAX_STACK_DEPTH) + t_(") exceeded (infinite recursion?)"));
	stack.Add();
}

void CalcContext::PopLevel()
{
	if(stack.GetCount() <= 1)
		throw Exc(t_("Expression stack underflow"));
	stack.Drop();
}

void CalcContext::RemoveExternal(CalcSymbols& e)
{
	int i = FindIndex(externals, &e);
	if(i >= 0)
		externals.Remove(i);
}

//////////////////////////////////////////////////////////////////////
// CalcPacket::

String CalcPacket::GetTypeNames() const
{
	String result;
	result << '(';
	for(int i = 0; i < args.GetCount(); i++)
	{
		if(i)
			result << ", ";
		result << context.GetTypeName(args[i]);
	}
	result << ')';
	return result;
}

//////////////////////////////////////////////////////////////////////
// CalcNode::

CalcNode::~CalcNode()
{
}

Value CalcNode::TryCalc(CalcContext& context) const
{
	try
	{
		return Calc(context);
	}
	catch(Exc e)
	{
		return ErrorValue(e);
	}
}

double CalcNode::CalcDouble(CalcContext& context) const
{
	if(this == 0)
		return Null;
	Value v = Calc(context);
	if(!IsNull(v) && !IsNumber(v))
		throw Exc(t_("Number expected, found: ") + context.FormatNull(v));
	return v;
}

String CalcNode::CalcString(CalcContext& context) const
{
	if(this == 0)
		return Null;
	Value v = Calc(context);
	if(!IsNull(v) && !IsString(v))
		throw Exc(t_("String expected, found: ") + context.FormatNull(v));
	return v;
}

int CalcNode::CalcInt(CalcContext& context) const
{
	if(this == 0)
		return Null;
	Value v = Calc(context);
	if(!IsNull(v) && !IsNumber(v))
		throw Exc(t_("Number expected, found: ") + context.FormatNull(v));
	return v;
}

Date CalcNode::CalcDate(CalcContext& context) const
{
	if(this == 0)
		return Null;
	Value v = Calc(context);
	if(!IsNull(v) && !IsDateTime(v))
		throw Exc(t_("Date expected, found: ") + context.FormatNull(v));
	return v;
}

Time CalcNode::CalcTime(CalcContext& context) const
{
	if(this == 0)
		return Null;
	Value v = Calc(context);
	if(!IsNull(v) && !IsDateTime(v))
		throw Exc(t_("Date/time expected, found: ") + context.FormatNull(v));
	return v;
}

bool CalcNode::CalcBool(CalcContext& context) const
{
	if(this == 0)
		return false;
	Value v = Calc(context);
	if(IsNull(v))
		return false;
	if(IsString(v))
		return !String(v).IsEmpty();
	if(IsNumber(v))
		return double(v) != 0;
	throw Exc(t_("Date/time expected, found: ") + context.FormatNull(v));
}

void CalcNode::Expect(const char *s, String found) const
{
	String e;
	e << name << t_(": expected: ") << s << t_(", found: ") << found;
	throw Exc(e);
}

CalcNodePtr GetVariableNode(String varname)
{
	return new CalcFunctionNode(varname);
}

CalcNodePtr GetFunctionNode(String fnname, pick_ Vector<CalcNodePtr>& args)
{
	return new CalcFunctionNode(fnname, args);
}

//////////////////////////////////////////////////////////////////////
// CalcFunctionNode::

void CalcFunctionNode::ScanArgs(CalcPacket& packet) const
{
	packet.args.SetCount(args.GetCount());
	Value *a = packet.args.Begin();
	for(const CalcNodePtr *p = args.Begin(), *e = args.End(); p < e; p++, a++)
		if((*a = (*p)->Calc(packet.context)).IsError())
			throw Exc(GetErrorText(*a));
}

//////////////////////////////////////////////////////////////////////
// CalcParser::

CalcParser::CalcParser()
: start(0), pos(0)
, op_begin(0), op_end(0), op_last(OP_NONE)
{
}

int CalcParser::Skip()
{
REPEAT:
	while(*pos && *pos <= ' ')
		pos++;
	if(*pos == '/')
		if(pos[1] == '/')
		{ // line comment
			while(*pos && *pos != '\n')
				pos++;
			goto REPEAT;
		}
		else if(pos[1] == '*')
		{
			const char *comment = pos++;
			while(*++pos && !(*pos == '*' && pos[1] == '/'))
				;
			if(*pos)
			{
				pos += 2;
				goto REPEAT;
			}
			else
				throw Exc(t_("/* without matching */"));
		}
	return *pos;
}

bool CalcParser::Expect(const String& s)
{
	String err = t_("expected ");
	err << (s.GetLength() > 1 ? t_("symbol") : t_("character")) << t_(" '") << s << t_("', found: ");
	int l = strlen(pos);
	if(l > 15)
		err.Cat(pos, 15);
	else if(l)
		err.Cat(pos);
	else
		err.Cat(t_("end of line"));
	throw Exc(err);
}

String CalcParser::GetIdent()
{
	Skip();
	const char *begin = pos;
	while(IsIdent(*pos))
		pos++;
	const char *end = pos;
	while(end > begin && end[-1] == ' ')
		end--;
	if(end - begin == 8 && !memcmp(begin, "operator", 8))
	{
		while(*pos && (byte)*pos <= ' ')
			pos++;
		begin = pos;
		while(*pos && (byte)*pos > ' ' && *pos != '(')
			pos++;
		end = pos;
	}
	return String(begin, end);
}

Value CalcParser::GetNumberOrDate()
{
	Skip();
	ASSERT(IsDigit(*pos));
	const char *begin = pos;
	bool comma = false;
	while(IsDigit(*pos))
		pos++;
	if(*pos == '.' && pos[1] != '.')
	{ // try date
		if(!IsDigit(*++pos) && *pos != 'E' && *pos != 'e')
		{ // day
			Date date = GetSysDate();
			int day = atoi(begin);
			if(day < 1 || day > LastDayOfMonth(date).day)
				throw Exc(NFormat(t_("invalid day number ("), day));
			else
				date.day = day;
			return date;
		}
		// might be a decimal
		const char *begin2 = pos;
		while(IsDigit(*++pos))
			;
		if(*pos == '.')
		{ // no, it's a date
			Date date = GetSysDate();
			int day = atoi(begin);
			int month = atoi(begin2);
			int year = date.year;
			if(*++pos == '-' || *pos == '+' || IsDigit(*pos))
			{ // year number given
				const char *begin3 = pos;
				while(IsDigit(*++pos))
					;
				year = atoi(begin3);
				if(pos - begin3 <= 2)
					date.year = year + (year < 80 ? 2000 : 1900);
				else if(date.year >= -4000 && date.year <= 4000)
					date.year = year;
				else
					throw Exc(NFormat("neplatné číslo roku (%d)", year));
			}
			if(month >= 1 && month <= 12)
				date.month = month;
			else
				throw Exc(NFormat("neplatné číslo měsíce (%d)", month));
			if(day >= 1 && day <= LastDayOfMonth(date).day)
				date.day = day;
			else
				throw Exc(NFormat("neplatné číslo dne (%d)", day));
			return date;
		}
	}
/*	else if(*pos == ',' && IsDigit(pos[1]))
	{ // alternative decimal separator
		comma = true;
		while(IsDigit(*++pos))
			;
	}
*/
	if(*pos == 'E' || *pos == 'e')
	{ // exponent
		if(*++pos == '+' || *pos == '-')
			pos++;
		while(IsDigit(*pos))
			pos++;
	}

	const char *temp = NULL;
	errno = 0;
	double value;
	String copy(begin, pos);
	value = ScanDouble(copy, &temp);
	if(IsNull(value) || temp != copy.End())
		throw Exc(NFormat("neplatná číselná konstanta '%s\'", copy));
	return value;
}

String CalcParser::GetString()
{
	CParser parser(pos);
	try
	{
		String result = parser.ReadOneString();
		pos = parser.GetPtr();
		return result;
	}
	catch(CParser::Error e)
	{
		throw Exc(e);
	}
}

CalcNodePtr CalcParser::Scan(const char *t)
{
	Clear(t);
	CalcNodePtr result = ScanAny();
	if(Skip())
		throw Exc(NFormat(t_("illegal characters after expression: %s (expression: %s)"), StringSample(pos, 10), StringSample(t, 10)));
	return result;
}

CalcNodePtr CalcParser::ScanVoid(const char *t)
{
	Clear(t);
	if(!Skip())
		return CalcNodePtr();
	CalcNodePtr result = ScanAny();
	if(Skip())
		throw Exc(NFormat(t_("illegal characters after expression: %s (expression: %s)"), StringSample(pos, 10), StringSample(t, 10)));
	return result;
}

void CalcParser::Clear(const char *t)
{
	start = pos = t;
}

CalcNodePtr CalcParser::ScanAny()
{
	return ScanSelect();
}

/*
CalcNodePtr CalcParser::ScanLambda()
{
	return Check(OP_LAMBDA) ? new CalcLambdaNode(ScanAny()) : ScanSelect();
}
*/

CalcNodePtr CalcParser::ScanSelect()
{
	CalcNodePtr node = ScanLogOr();
	if(Check(OP_QUESTION))
	{ // ?:
		CalcNodePtr when1 = ScanAny();
		Force(OP_COLON, ":");
		CalcNodePtr when0 = ScanAny();
		node = new CalcSelectNode(node, when1, when0);
	}
	return node;
}

CalcNodePtr CalcParser::ScanLogOr()
{
	CalcNodePtr node = ScanLogAnd();
	while(Check(OP_LOG_OR))
		node = new CalcLogOrNode(node, ScanLogAnd());
	return node;
}

CalcNodePtr CalcParser::ScanLogAnd()
{
	CalcNodePtr node = ScanEq();
	while(Check(OP_LOG_AND))
		node = new CalcLogAndNode(node, ScanEq());
	return node;
}

CalcNodePtr CalcParser::ScanEq()
{
	CalcNodePtr node = ScanCompare();
	for(;;)
		if(Check(OP_EQ))
			node = new CalcFunctionNode("==", node, ScanCompare());
		else if(Check(OP_NE))
			node = new CalcFunctionNode("!=", node, ScanCompare());
		else
			return node;
}

CalcNodePtr CalcParser::ScanCompare()
{
	CalcNodePtr node = ScanBitOr();
	for(;;)
		if(Check(OP_LE))
			node = new CalcFunctionNode("<=", node, ScanBitOr());
		else if(Check(OP_GE))
			node = new CalcFunctionNode(">=", node, ScanBitOr());
		else if(Check(OP_LT))
			node = new CalcFunctionNode("<", node, ScanBitOr());
		else if(Check(OP_GT))
			node = new CalcFunctionNode(">", node, ScanBitOr());
		else
			return node;
}

CalcNodePtr CalcParser::ScanBitOr()
{
	CalcNodePtr node = ScanBitXor();
	while(Check(OP_BIT_OR))
		node = new CalcFunctionNode("|", node, ScanBitXor());
	return node;
}

CalcNodePtr CalcParser::ScanBitXor()
{
	CalcNodePtr node = ScanBitAnd();
	while(Check(OP_BIT_XOR))
		node = new CalcFunctionNode("^", node, ScanBitAnd());
	return node;
}

CalcNodePtr CalcParser::ScanBitAnd()
{
	CalcNodePtr node = ScanAdditive();
	while(Check(OP_BIT_AND))
		node = new CalcFunctionNode("&", node, ScanAdditive());
	return node;
}

CalcNodePtr CalcParser::ScanAdditive()
{
	CalcNodePtr node = ScanShift();
	for(;;)
		if(Check(OP_ADD))
			node = new CalcFunctionNode("+", node, ScanShift());
		else if(Check(OP_SUB))
			node = new CalcFunctionNode("-", node, ScanShift());
		else
			return node;
}

CalcNodePtr CalcParser::ScanShift()
{
	CalcNodePtr node = ScanMultiplicative();
	for(;;)
		if(Check(OP_LSHIFT))
			node = new CalcFunctionNode("<<", node, ScanMultiplicative());
		else if(Check(OP_RSHIFT))
			node = new CalcFunctionNode(">>", node, ScanMultiplicative());
		else
			return node;
}

CalcNodePtr CalcParser::ScanMultiplicative()
{
	CalcNodePtr node = ScanPow();
	for(;;)
		if(Check(OP_MUL))
			node = new CalcFunctionNode("*", node, ScanPow());
		else if(Check(OP_DIV))
			node = new CalcFunctionNode("/", node, ScanPow());
		else if(Check(OP_MOD))
			node = new CalcFunctionNode("%", node, ScanPow());
		else
			return node;
}

CalcNodePtr CalcParser::ScanPow()
{
	CalcNodePtr node = ScanPrefix();
	while(Check(OP_POW))
		node = new CalcFunctionNode("pow", node, ScanPow());
	return node;
}

CalcNodePtr CalcParser::ScanPrefix()
{
	Skip();
	const char *start = pos;
	switch(GetOperator())
	{
	case OP_LAMBDA: SkipOperator(); return new CalcLambdaNode(ScanPrefix());
	case OP_ADD: SkipOperator(); return new CalcFunctionNode("+", ScanPrefix());
	case OP_SUB: SkipOperator(); return new CalcFunctionNode("-", ScanPrefix());
	case OP_BIT_NOT: SkipOperator(); return new CalcFunctionNode("~", ScanPrefix());
	case OP_LOG_NOT: SkipOperator(); return new CalcFunctionNode("!", ScanPrefix());
	case OP_LPAR:
		{ // parenthesised subexpression
			const char *begin = pos;
			SkipOperator();
			CalcNodePtr exp = ScanAny();
			if(!Force(OP_RPAR, ")"))
				throw Exc(NFormat(t_("left parenthesis not matched; subexpression starts at %s"), StringSample(begin, 20)));
			return ScanPostfix(exp);
		}
	case OP_LBRACKET:
		SkipOperator();
		{ // array constructor
			CalcNodePtr ctr;
			if(Check(OP_RBRACKET))
				ctr = new CalcFunctionNode("[,,]");
			else
			{
				CalcNodePtr arg = ScanAny();
				if(Check(OP_DOTS))
				{
					CalcNodePtr maxrng = ScanAny();
					ctr = new CalcFunctionNode("[..]", arg, maxrng);
				}
				else
				{
					Vector<CalcNodePtr> arglist;
					arglist.Add() = arg;
					while(Check(OP_COMMA))
						arglist.Add() = ScanAny();
					ctr = new CalcFunctionNode("[,,]", arglist);
				}
				if(!Force(OP_RBRACKET, "]"))
					throw Exc("chybí pravá hranatá závorka ']'");
			}
			return ScanPostfix(ctr);
		}
	}
	if(*pos == '\"')
	{
		return ScanPostfix(new CalcConstNode(GetString()));
	}
	if(IsDigit(*pos)) // numeric or date constant
		return ScanPostfix(new CalcConstNode(GetNumberOrDate()));
	if(IsIdent(*pos))
	{
		String ident = GetIdent();
		Vector<CalcNodePtr> arguments;
		ScanArgs(arguments);
		return ScanPostfix(new CalcFunctionNode(ident, arguments));
	}
	const char *p = pos;
	if(*p == 0)
		p = t_("end of line");
	throw Exc(t_("constant or function expected, found: ") + StringSample(p, 15));
	return 0;
}

CalcNodePtr CalcParser::ScanPostfix(CalcNodePtr node)
{
	for(;;)
		if(Check(OP_DOT))
		{ // member access
			byte c = Skip();
			String id = GetIdent();
			Vector<CalcNodePtr> nodes;
			nodes | node;
			ScanArgs(nodes);
			node = new CalcFunctionNode(id, nodes);
		}
		else if(Check(OP_LBRACE))
		{
			Vector<CalcNodePtr> list;
			do
			{
				list.Add() = ScanAny();
				if(!Check(OP_COMMA)) // default value
					break;
				list.Add() = ScanAny();
			}
			while(Check(OP_COMMA));
			Force(OP_RBRACE, "}");
			node = new CalcSwitchNode(node, list);
		}
		else if(Check(OP_LBRACKET))
		{
			CalcNodePtr index = ScanAny();
			Force(OP_RBRACKET, "]");
			node = new CalcFunctionNode("[]", node, index);
		}
		else
			return node;
}

void CalcParser::ScanArgs(Vector<CalcNodePtr>& dest)
{
	if(Check(OP_LPAR) && !Check(OP_RPAR))
	{
		const char *begin = pos;
		do
			dest | ScanAny();
		while(Check(OP_COMMA));
		if(!Check(OP_RPAR))
			Expect(NFormat("')' (začátek podvýrazu: %s)", StringSample(begin - 1, 20)));
	}
}

int CalcParser::GetOperator()
{
	if(op_begin == pos)
		return op_last;
	Skip();
	op_begin = op_end = pos;
	switch(*op_end++)
	{
	case '?': op_last = OP_QUESTION; break;
	case ':': op_last = OP_COLON; break;
	case '&': op_last = (*op_end == '&' ? op_end++, OP_LOG_AND : OP_BIT_AND); break;
	case '|': op_last = (*op_end == '|' ? op_end++, OP_LOG_OR  : OP_BIT_OR); break;
	case '^': op_last = OP_BIT_XOR; break;
	case '<': op_last = (*op_end == '=' ? op_end++, OP_LE : *op_end == '<' ? op_end++, OP_LSHIFT : OP_LT); break;
	case '>': op_last = (*op_end == '=' ? op_end++, OP_GE : *op_end == '>' ? op_end++, OP_RSHIFT : OP_GT); break;
	case '!': op_last = (*op_end == '=' ? op_end++, OP_NE : OP_LOG_NOT); break;
	case '=': op_last = (*op_end == '=' ? op_end++, OP_EQ : (op_end--, OP_NONE)); break;
	case '+': op_last = OP_ADD; break;
	case '-': op_last = OP_SUB; break;
	case '*': op_last = (*op_end == '*' ? op_end++, OP_POW : OP_MUL); break;
	case '/': op_last = OP_DIV; break;
	case '%': op_last = OP_MOD; break;
	case '~': op_last = OP_BIT_NOT; break;
	case '#': op_last = OP_LAMBDA; break;
	case '(': op_last = OP_LPAR; break;
	case ')': op_last = OP_RPAR; break;
	case '[': op_last = OP_LBRACKET; break;
	case ']': op_last = OP_RBRACKET; break;
	case '{': op_last = OP_LBRACE; break;
	case '}': op_last = OP_RBRACE; break;
	case ',': op_last = OP_COMMA; break;
	case '.': op_last = (*op_end == '.' ? op_end++, OP_DOTS : OP_DOT); break;
	default: op_end--; op_last = OP_NONE; break;
	}
	return op_last;
}

int CalcParser::SkipOperator()
{
	int op = GetOperator();
	pos = op_end;
	return op;
}

bool CalcParser::Check(int op)
{
	if(op != GetOperator())
		return false;
	SkipOperator();
	return true;
}

bool CalcParser::Force(int op, const char *expect)
{
	if(Check(op))
		return true;
	Expect(expect);
	return false;
}

END_UPP_NAMESPACE
