/* iostring.hh - Read and write strings in parts.
 * Copyright 2007-2008 Bas Wijnen <wijnen@debian.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <string>
#include <stack>
#include <cstdlib>
#include <limits>
#include <iomanip>
#include "iostring.hh"
#include "case.hh"
#include "error.hh"
#include "debug.hh"
#include "regexp.hh"

namespace shevek
{
	static Glib::ustring::size_type find_non_whitespace
		(Glib::ustring const &str, Glib::ustring::size_type start)
	{
		Glib::ustring::size_type ret;
		for (ret = start; ret < str.size (); ++ret)
		{
			// Definition of "whitespace" as defined by
			// javascript (Ecma-262).
			gunichar c = str[ret];
			if (c != 0xa0 && c != 0x20 && c != 0xc && c != 0xb
					&& c != 0x9 && c != 0xa && c != 0xd
					&& c != 0x2028 && c != 0x2029
					&& g_unichar_type (c)
					!= G_UNICODE_SPACE_SEPARATOR)
				break;
		}
		return ret;
	}

	void istring::del_whitespace ()
	{
		pos.top () = find_non_whitespace (data, pos.top ());
	}

	bool istring::read_const (Glib::ustring const &format, Glib::ustring::size_type &inpos)
	{
		bool escaped = false;
		while (inpos < format.size ())
		{
			if (escaped && format[inpos] == ':')
			{
				if (pos.top () != 0 && pos.top () != data.size () && Glib::Unicode::isalnum (data[pos.top () - 1]) && Glib::Unicode::isalnum (data[pos.top ()]))
					return false;
				++inpos;
				escaped = false;
				continue;
			}
			if (escaped || (format[inpos] != '%' && format[inpos] != ' '))
			{
				if (format[inpos] != data[pos.top ()])
					return false;
				++inpos;
				++pos.top ();
				escaped = false;
				continue;
			}
			if (format[inpos] == ' ')
			{
				del_whitespace ();
				++inpos;
				escaped = false;
				continue;
			}
			++inpos;
			if (inpos >= format.size ())
			{
				// A single % at end of string means
				// that the input must be parsed
				// completely.
				return pos.top () == data.size ();
			}
			if (format[inpos] != '%' && format[inpos] != ' ' && format[inpos] != ':')
				return true;
			escaped = true;
		}
		return true;
	}

	int istring::pop (bool keep)
	{
		Glib::ustring::size_type p = pos.top ();
		if (pos.size () > 1)
			pos.pop ();
		else
			pos.top () = 0;
		int ret = p - pos.top ();
		if (keep)
			pos.top () = p;
		return ret;
	}

	template <>
	bool istring::read_var <double> (Glib::ustring const &format,
			double &ret, Glib::ustring::size_type &inpos)
	{
		++inpos;
		// First check for inf and/or nan
		del_whitespace ();
		if (pos.top () >= data.size ())
			return false;
		bool neg (false);
		if (data[pos.top ()] == '+')
			++pos.top ();
		else if (data[pos.top ()] == '-')
		{
			++pos.top ();
			neg = true;
		}
		if (data.size () - pos.top () >= 8 && shevek::tolower
				(data.substr (pos.top (), 8)) == "infinity")
		{
			pos.top () += 8;
			ret = std::numeric_limits <double>::infinity ()
				* (neg ? -1 : 1);
			return true;
		}
		if (data.size () - pos.top () >= 3 && shevek::tolower
				(data.substr (pos.top (), 3)) == "inf")
		{
			pos.top () += 3;
			ret = std::numeric_limits <double>::infinity ()
				* (neg ? -1 : 1);
			return true;
		}
		if (data.size () - pos.top () >= 3 && shevek::tolower
				(data.substr (pos.top (), 3)) == "nan")
		{
			pos.top () += 3;
			if (pos.top () < data.size ()
					&& data[pos.top ()] == '(')
			{
				Glib::ustring::size_type e = data.find_first_of
					(")\n", pos.top ());
				if (e != Glib::ustring::npos && data[e] == ')')
					pos.top () = e + 1;
			}
			ret = std::numeric_limits <double>::quiet_NaN ()
				* (neg ? -1 : 1);
			return true;
		}

		std::istringstream s (data.substr (pos.top ()));
		s >> ret;
		ret *= (neg ? -1 : 1);
		std::streampos p = s.tellg ();
		if (!s || p < 0)
			return false;
		pos.top () += p;
		return true;
	}

	template <>
	bool istring::read_var <float> (Glib::ustring const &format,
			float &ret, Glib::ustring::size_type &inpos)
	{
		++inpos;
		// First check for inf and/or nan
		del_whitespace ();
		if (pos.top () >= data.size ())
			return false;
		bool neg (false);
		if (data[pos.top ()] == '+')
			++pos.top ();
		else if (data[pos.top ()] == '-')
		{
			++pos.top ();
			neg = true;
		}
		if (data.size () - pos.top () >= 8 && shevek::tolower
				(data.substr (pos.top (), 8)) == "infinity")
		{
			pos.top () += 8;
			ret = std::numeric_limits <float>::infinity ()
				* (neg ? -1 : 1);
			return true;
		}
		if (data.size () - pos.top () >= 3 && shevek::tolower
				(data.substr (pos.top (), 3)) == "inf")
		{
			pos.top () += 3;
			ret = std::numeric_limits <float>::infinity ()
				* (neg ? -1 : 1);
			return true;
		}
		if (data.size () - pos.top () >= 3 && shevek::tolower
				(data.substr (pos.top (), 3)) == "nan")
		{
			pos.top () += 3;
			if (pos.top () < data.size ()
					&& data[pos.top ()] == '(')
			{
				Glib::ustring::size_type e = data.find_first_of
					(")\n", pos.top ());
				if (e != Glib::ustring::npos && data[e] == ')')
					pos.top () = e + 1;
			}
			ret = std::numeric_limits <float>::quiet_NaN ()
				* (neg ? -1 : 1);
			return true;
		}

		std::istringstream s (data.substr (pos.top ()));
		s >> ret;
		ret *= (neg ? -1 : 1);
		std::streampos p = s.tellg ();
		if (!s || p < 0)
			return false;
		pos.top () += p;
		return true;
	}

#define next(x) do { \
	if (inpos >= format.size ()) \
		return false; \
	x = format[inpos++]; \
	if (x == '%') \
	{ \
		if (inpos >= format.size ()) \
			return false; \
		if (format[inpos++] != '%') \
			return false; \
	} \
} while (false)

	template <>
	bool istring::read_var <Glib::ustring> (Glib::ustring const &format,
			Glib::ustring &ret, Glib::ustring::size_type &inpos)
	{
		startfunc;
		gunichar c;
		next (c);
		if (c == '[')
		{
			Glib::ustring range;
			bool invert (false);
			if (format[inpos] == '^')
			{
				++inpos;
				if (inpos >= format.size ())
					return false;
				invert = true;
			}
			next (c);
			range += c;
			while (format[inpos] != ']')
			{
				next (c);
				if (c == '-')
				{
					gunichar z;
					next (z);
					z &= 0xff;
					gunichar a = *--range.end () & 0xff;
					if (a > z)
					{
						int t = a;
						a = z;
						z = t;
					}
					for (++a; a <= z; ++a)
						range += a;
					continue;
				}
				range += c;
			}
			Glib::ustring::size_type orig = pos.top ();
			if (invert)
			{
				while (pos.top () < data.size ()
						&& range.find_first_of
						(data[pos.top ()])
						== Glib::ustring::npos)
					++pos.top ();
			}
			else
			{
				while (pos.top () < data.size ()
						&& range.find_first_of
						(data[pos.top ()])
						!= Glib::ustring::npos)
					++pos.top ();
			}
			ret = data.substr (orig, pos.top () - orig);
			++inpos;
			return true;
		}
		std::istringstream s (data.substr (pos.top ()));
		std::string line;
		switch (c)
		{
		default: // One word
			s >> ret;
			break;
		case 'l': // One line
			std::getline (s, line);
			ret = line;
			break;
		case 'a': // All
			ret = data.substr (pos.top ());
			pos.top () = data.size ();
			return true;
		case 'r': // Regular expression
		case 'R': // Case insensitive regular expression
			gunichar quote;
			next (quote);
			gunichar n;
			Glib::ustring expr;
			do {
				next (n);
				expr += n;
			} while (quote != n);
			expr = expr.substr (0, expr.size () - 1);
			if (expr.empty () || expr[0] != '^')
				expr = Glib::ustring (1, '^') + expr;
			shevek::regexp re (expr, c == 'r');
			if (!re (data.substr (pos.top ())))
				return false;
			ret = re.transform ("\\0");
			pos.top () += ret.size ();
			return true;
		}
		if (!s)
			return false;
		std::streampos p = s.tellg ();
		if (p >= 0)
			pos.top () += p;
		else
			pos.top () = data.size ();
		return true;
	}

	static int make_base (gunichar code)
	{
		switch (code)
		{
		case 'o':	// octal
			return 8;
		case 'x':	// hexadecimal
			return 16;
		case 'd':	// decimal
			return 10;
		case 'b':	// binary
			return 2;
		default:	// any
			return 0;
		}
	}

	static bool read_char (Glib::ustring const &data,
			Glib::ustring::size_type &pos, int &ret)
	{
		Glib::ustring::size_type p = find_non_whitespace (data, pos);
		if (p == Glib::ustring::npos || data.size () <= p + 2
				|| (data[p] != '\'' && data[p] != '"')
				|| data[p] != data[p + 2])
			return false;
		pos = p + 3;
		ret = data[p + 1];
		return true;
	}

	template <>
	bool istring::read_var <int> (Glib::ustring const &format,
			int &ret, Glib::ustring::size_type &inpos)
	{
		int base = make_base (format[inpos++]);
		if (read_char (data, pos.top (), ret))
			return true;
		Glib::ustring d = data.substr (pos.top ());
		char *end;
		char const *str = d.c_str ();
		ret = strtol (str, &end, base);
		if (str == end)
			return false;
		pos.top () += end - str;
		return true;
	}

	template <>
	bool istring::read_var <unsigned> (Glib::ustring const &format,
			unsigned &ret, Glib::ustring::size_type &inpos)
	{
		int base = make_base (format[inpos++]);
		int r;
		if (read_char (data, pos.top (), r))
		{
			ret = r;
			return true;
		}
		Glib::ustring d = data.substr (pos.top ());
		char *end;
		char const *str = d.c_str ();
		ret = strtoul (str, &end, base);
		if (str == end)
			return false;
		pos.top () += end - str;
		return true;
	}

	ostring &ostring::init (Glib::ustring const &f)
	{
		format = f;
		pos = 0;
		return *this;
	}

	void ostring::write_const ()
	{
		while (pos < format.size ())
		{
			if (format[pos] != '%')
			{
				data += format[pos++];
				continue;
			}
			if (++pos >= format.size ())
				shevek_error ("ostring format ends on %");
			if (format[pos] != '%')
				return;
			data += '%';
			++pos;
		}
		shevek_error ("Not enough % codes in format");
	}

	ostring &ostring::operator ()()
	{
		while (pos < format.size ())
		{
			if (format[pos] != '%')
			{
				data += format[pos++];
				continue;
			}
			if (++pos >= format.size ())
				shevek_error ("ostring format ends on %");
			if (format[pos] != '%')
				shevek_error ("Too many % codes in format");
			data += '%';
			++pos;
		}
		return *this;
	}

	template <> void ostring::write_var <unsigned short> (unsigned short const &a, Glib::ustring const &flags, unsigned width, unsigned precision)
	{
		(void)precision;
		std::ostringstream s;
		if (width > 0)
			s << std::setw (width);
		if (flags.find ('0') != Glib::ustring::npos)
			s << std::setfill ('0');
		if (pos < format.size () && (format[pos] == 'x' || format[pos] == 'X'))
			s << std::hex;
		s << a;
		data += s.str ();
		if (pos < format.size ())
			++pos;
	}

	template <> void ostring::write_var <short> (short const &a, Glib::ustring const &flags, unsigned width, unsigned precision)
	{
		(void)precision;
		std::ostringstream s;
		if (width > 0)
			s << std::setw (width);
		if (flags.find ('0') != Glib::ustring::npos)
			s << std::setfill ('0');
		if (pos < format.size () && (format[pos] == 'x' || format[pos] == 'X'))
			s << std::hex;
		s << a;
		data += s.str ();
		if (pos < format.size ())
			++pos;
	}

	template <> void ostring::write_var <unsigned> (unsigned const &a, Glib::ustring const &flags, unsigned width, unsigned precision)
	{
		(void)precision;
		std::ostringstream s;
		if (width > 0)
			s << std::setw (width);
		if (flags.find ('0') != Glib::ustring::npos)
			s << std::setfill ('0');
		if (pos < format.size () && (format[pos] == 'x' || format[pos] == 'X'))
			s << std::hex;
		s << a;
		data += s.str ();
		if (pos < format.size ())
			++pos;
	}

	template <> void ostring::write_var <int> (int const &a, Glib::ustring const &flags, unsigned width, unsigned precision)
	{
		(void)precision;
		std::ostringstream s;
		if (width > 0)
			s << std::setw (width);
		if (flags.find ('0') != Glib::ustring::npos)
			s << std::setfill ('0');
		if (pos < format.size () && (format[pos] == 'x' || format[pos] == 'X'))
			s << std::hex;
		s << a;
		data += s.str ();
		if (pos < format.size ())
			++pos;
	}

	template <> void ostring::write_var <Glib::ustring> (Glib::ustring const &a, Glib::ustring const &flags, unsigned width, unsigned precision)
	{
		Glib::ustring s;
		if (precision > 0 && a.size () > precision)
			s = a.substr (0, precision);
		else
			s = a;
		if (s.size () < width)
		{
			if (flags.find ('-'))
				s += Glib::ustring (width - s.size (), ' ');
			else
				s = Glib::ustring (width - s.size (), ' ') + s;
		}
		data += s;
		if (pos < format.size ())
			++pos;
	}

	static std::string const rwhitespace (" \t\n\r\0", 5);

	void ristring::del_whitespace ()
	{
		while (pos.top () < data.size () && rwhitespace.find
				(data[pos.top ()]) != std::string::npos)
			++pos.top ();
	}

	bool ristring::read_const (std::string const &format,
			std::string::size_type &inpos)
	{
		bool escaped = false;
		while (inpos < format.size ())
		{
			if (escaped && format[inpos] == ':')
			{
				if (pos.top () != 0 && pos.top () != data.size () && Glib::Ascii::isalnum (data[pos.top () - 1]) && Glib::Ascii::isalnum (data[pos.top ()]))
					return false;
				++inpos;
				escaped = false;
				continue;
			}
			if (escaped || (format[inpos] != '%' && format[inpos] != ' '))
			{
				if (format[inpos] != data[pos.top ()])
					return false;
				++inpos;
				++pos.top ();
				escaped = false;
				continue;
			}
			if (format[inpos] == ' ')
			{
				del_whitespace ();
				++inpos;
				escaped = false;
				continue;
			}
			++inpos;
			if (inpos >= format.size ())
			{
				// A single % at end of string means
				// that the input must be parsed
				// completely.
				return pos.top () == data.size ();
			}
			if (format[inpos] != '%' && format[inpos] != ' ' && format[inpos] != ':')
				return true;
			escaped = true;
		}
		return true;
	}

	int ristring::pop (bool keep)
	{
		std::string::size_type p = pos.top ();
		if (pos.size () > 1)
			pos.pop ();
		else
			pos.top () = 0;
		int ret = p - pos.top ();
		if (keep)
			pos.top () = p;
		return ret;
	}

	template <>
	bool ristring::read_var <double> (std::string const &format,
			double &ret, std::string::size_type &inpos)
	{
		++inpos;
		// First check for inf and/or nan
		del_whitespace ();
		if (pos.top () >= data.size ())
			return false;
		bool neg (false);
		if (data[pos.top ()] == '+')
			++pos.top ();
		else if (data[pos.top ()] == '-')
		{
			++pos.top ();
			neg = true;
		}
		if (data.size () - pos.top () >= 8 && shevek::tolower
				(data.substr (pos.top (), 8)) == "infinity")
		{
			pos.top () += 8;
			ret = std::numeric_limits <double>::infinity ()
				* (neg ? -1 : 1);
			return true;
		}
		if (data.size () - pos.top () >= 3 && shevek::tolower
				(data.substr (pos.top (), 3)) == "inf")
		{
			pos.top () += 3;
			ret = std::numeric_limits <double>::infinity ()
				* (neg ? -1 : 1);
			return true;
		}
		if (data.size () - pos.top () >= 3 && shevek::tolower
				(data.substr (pos.top (), 3)) == "nan")
		{
			pos.top () += 3;
			if (pos.top () < data.size ()
					&& data[pos.top ()] == '(')
			{
				std::string::size_type e = data.find_first_of
					(")\n", pos.top ());
				if (e != std::string::npos && data[e] == ')')
					pos.top () = e + 1;
			}
			ret = std::numeric_limits <double>::quiet_NaN ()
				* (neg ? -1 : 1);
			return true;
		}

		std::istringstream s (data.substr (pos.top ()));
		s >> ret;
		ret *= (neg ? -1 : 1);
		std::streampos p = s.tellg ();
		if (!s || p < 0)
			return false;
		pos.top () += p;
		return true;
	}

	template <>
	bool ristring::read_var <float> (std::string const &format,
			float &ret, std::string::size_type &inpos)
	{
		++inpos;
		// First check for inf and/or nan
		del_whitespace ();
		if (pos.top () >= data.size ())
			return false;
		bool neg (false);
		if (data[pos.top ()] == '+')
			++pos.top ();
		else if (data[pos.top ()] == '-')
		{
			++pos.top ();
			neg = true;
		}
		if (data.size () - pos.top () >= 8 && shevek::tolower
				(data.substr (pos.top (), 8)) == "infinity")
		{
			pos.top () += 8;
			ret = std::numeric_limits <float>::infinity ()
				* (neg ? -1 : 1);
			return true;
		}
		if (data.size () - pos.top () >= 3 && shevek::tolower
				(data.substr (pos.top (), 3)) == "inf")
		{
			pos.top () += 3;
			ret = std::numeric_limits <float>::infinity ()
				* (neg ? -1 : 1);
			return true;
		}
		if (data.size () - pos.top () >= 3 && shevek::tolower
				(data.substr (pos.top (), 3)) == "nan")
		{
			pos.top () += 3;
			if (pos.top () < data.size ()
					&& data[pos.top ()] == '(')
			{
				std::string::size_type e = data.find_first_of
					(")\n", pos.top ());
				if (e != std::string::npos && data[e] == ')')
					pos.top () = e + 1;
			}
			ret = std::numeric_limits <float>::quiet_NaN ()
				* (neg ? -1 : 1);
			return true;
		}

		std::istringstream s (data.substr (pos.top ()));
		s >> ret;
		ret *= (neg ? -1 : 1);
		std::streampos p = s.tellg ();
		if (!s || p < 0)
			return false;
		pos.top () += p;
		return true;
	}

	template <>
	bool ristring::read_var <std::string> (std::string const &format,
			std::string &ret, std::string::size_type &inpos)
	{
		startfunc;
		char c;
		next (c);
		if (c == '[')
		{
			std::string range;
			bool invert (false);
			if (format[inpos] == '^')
			{
				++inpos;
				if (inpos >= format.size ())
					return false;
				invert = true;
			}
			next (c);
			range += c;
			while (format[inpos] != ']')
			{
				next (c);
				if (c == '-')
				{
					int z;
					next (z);
					z &= 0xff;
					int a = *--range.end () & 0xff;
					if (a > z)
					{
						int t = a;
						a = z;
						z = t;
					}
					for (++a; a <= z; ++a)
						range += a;
					continue;
				}
				range += c;
			}
			std::string::size_type orig = pos.top ();
			if (invert)
			{
				while (pos.top () < data.size ()
						&& range.find_first_of
						(data[pos.top ()])
						== std::string::npos)
					++pos.top ();
			}
			else
			{
				while (pos.top () < data.size ()
						&& range.find_first_of
						(data[pos.top ()])
						!= std::string::npos)
					++pos.top ();
			}
			ret = data.substr (orig, pos.top () - orig);
			++inpos;
			return true;
		}
		std::istringstream s (data.substr (pos.top ()));
		switch (c)
		{
		default: // One word
			s >> ret;
			break;
		case 'l': // One line
			std::getline (s, ret);
			break;
		case 'a': // All
			ret = data.substr (pos.top ());
			pos.top () = data.size ();
			return true;
		case 'r': // Regular expression
		case 'R': // Case insensitive regular expression
			char quote;
			next (quote);
			char n;
			std::string expr;
			do {
				next (n);
				expr += n;
			} while (quote != n);
			expr = expr.substr (0, expr.size () - 1);
			if (expr.empty () || expr[0] != '^')
				expr = std::string (1, '^') + expr;
			shevek::regexp re (expr, c == 'r');
			if (!re (data.substr (pos.top ())))
				return false;
			ret = re.transform ("\\0");
			pos.top () += ret.size ();
			return true;
		}
		if (!s)
			return false;
		std::streampos p = s.tellg ();
		if (p >= 0)
			pos.top () += p;
		else
			pos.top () = data.size ();
		return true;
	}

	static bool read_char (std::string const &data,
			std::string::size_type &pos, int &ret)
	{
		std::string::size_type
			p = data.find_first_not_of (rwhitespace, pos);
		if (p == std::string::npos || data.size () <= p + 2
				|| (data[p] != '\'' && data[p] != '"')
				|| data[p] != data[p + 2])
			return false;
		pos = p + 3;
		ret = data[p + 1];
		return true;
	}

	template <>
	bool ristring::read_var <int> (std::string const &format,
			int &ret, std::string::size_type &inpos)
	{
		int base = make_base (format[inpos++]);
		if (read_char (data, pos.top (), ret))
			return true;
		std::string d = data.substr (pos.top ());
		char *end;
		char const *str = d.c_str ();
		ret = strtol (str, &end, base);
		if (str == end)
			return false;
		pos.top () += end - str;
		return true;
	}

	template <>
	bool ristring::read_var <unsigned> (std::string const &format,
			unsigned &ret, std::string::size_type &inpos)
	{
		int base = make_base (format[inpos++]);
		int r;
		if (read_char (data, pos.top (), r))
		{
			ret = r;
			return true;
		}
		std::string d = data.substr (pos.top ());
		char *end;
		char const *str = d.c_str ();
		ret = strtoul (str, &end, base);
		if (str == end)
			return false;
		pos.top () += end - str;
		return true;
	}

	rostring &rostring::init (std::string const &f)
	{
		format = f;
		pos = 0;
		return *this;
	}

	void rostring::write_const ()
	{
		while (pos < format.size ())
		{
			if (format[pos] != '%')
			{
				data += format[pos++];
				continue;
			}
			if (++pos >= format.size ())
				shevek_error ("rostring format ends on %");
			if (format[pos] != '%')
				return;
			data += '%';
			++pos;
		}
		shevek_error ("Not enough % codes in format");
	}

	rostring &rostring::operator ()()
	{
		while (pos < format.size ())
		{
			if (format[pos] != '%')
			{
				data += format[pos++];
				continue;
			}
			if (++pos >= format.size ())
				shevek_error ("rostring format ends on %");
			if (format[pos] != '%')
				shevek_error ("Too many % codes in format");
			data += '%';
			++pos;
		}
		return *this;
	}

	template <> void rostring::write_var <unsigned> (unsigned const &a,
			std::string const &flags, unsigned width,
			unsigned precision)
	{
		(void)precision;
		std::ostringstream s;
		if (width > 0)
			s << std::setw (width);
		if (flags.find ('0') != std::string::npos)
			s << std::setfill ('0');
		if (pos < format.size ()
			       	&& (format[pos] == 'x' || format[pos] == 'X'))
			s << std::hex;
		s << a;
		data += s.str ();
		if (pos < format.size ())
			++pos;
	}
	template <> void rostring::write_var <int> (int const &a,
			std::string const &flags, unsigned width,
			unsigned precision)
	{
		(void)precision;
		std::ostringstream s;
		if (width > 0)
			s << std::setw (width);
		if (flags.find ('0') != std::string::npos)
			s << std::setfill ('0');
		if (pos < format.size ()
			       	&& (format[pos] == 'x' || format[pos] == 'X'))
			s << std::hex;
		s << a;
		data += s.str ();
		if (pos < format.size ())
			++pos;
	}
}
