<commit_before>
#include <Beard/utility.hpp>
#include <Beard/txt/Cursor.hpp>

#include <duct/char.hpp>
#include <duct/debug.hpp>

#include <cmath>
#include <iterator>

namespace Beard {
namespace txt {

// class Cursor implementation

// positioning

bool
Cursor::row_bound() noexcept {
	if (signed_cast(get_tree().lines()) <= m_row) {
		row_extent(txt::Extent::tail);
		return true;
	}
	return false;
}

void
Cursor::row_abs(
	position_type row
) noexcept {
	row = max_ce(
		row,
		max_ce(
			signed_cast(std::size_t{0u}),
			signed_cast(get_tree().lines()) - 1
		)
	);
	if (row != m_row) {
		m_row = row;
		col_recalc();
	}
}

void
Cursor::col_recalc() noexcept {
	position_type col = 0;
	auto const& node = get_node();
	auto const end = node.cend();
	auto step = node.cbegin(), from = step;
	while (
		from < (step = txt::EncUtils::next(from, end)) &&
		col > m_col
	) {
		from = step;
		++col;
	}
	m_col = col;
	m_index = std::distance(node.cbegin(), from);
}

void
Cursor::col_step(
	difference_type const n
) noexcept {
	if (0 == n) {
		return;
	}

	auto const& node = get_node();
	difference_type const dest = m_col + n;
	DUCT_DEBUGF(
		"col_step: m_col = %zd, m_index = %zd, dest = %ld, n = %ld, "
		"diff = %zd, abs = %zd",
		m_col, m_index, dest, n,
		dest - m_col,
		std::abs(dest - m_col)
	);

	// Recalculate (i.e., count from the beginning) or step
	// depending on the distance from the current column
	if (0 >= dest) {
		DUCT_DEBUG("  to head");
		m_col = 0;
		m_index = 0;
	} else if (node.points() <= unsigned_cast(dest)) {
		DUCT_DEBUG("  to tail");
		m_col = signed_cast(node.points());
		m_index = signed_cast(node.units());
	} else if (node.singular()) {
		DUCT_DEBUG("  singular");
		m_col = dest;
		m_index = dest;
	} else if (dest < std::abs(dest - m_col)) {
		DUCT_DEBUG("  recalculating");
		m_col = dest;
		col_recalc();
	} else {
		auto const begin = node.cbegin(), end = node.cend();
		auto step = begin + m_index, from = step;
		if (0 > n) {
			// Step backward
			DUCT_DEBUG("  stepping backward");
			while (
				from > (step = txt::EncUtils::prev(from, begin)) &&
				dest < m_col
			) {
				from = step;
				--m_col;
				DUCT_DEBUG("  --");
			}
		} else {
			// Step forward
			DUCT_DEBUG("  stepping forward");
			while (
				from < (step = txt::EncUtils::next(from, end)) &&
				dest > m_col
			) {
				from = step;
				++m_col;
				DUCT_DEBUG("  ++");
			}
		}
		m_index = std::distance(begin, from);
		DUCT_DEBUGF(
			"  m_col = %zd, m_index = %zd, dist = %ld",
			m_col,
			m_index,
			std::distance(begin, from)
		);
	}
}

// operations

void
Cursor::assign(
	String const& str
) {
	auto& node = get_node();
	auto const ucount = signed_cast(node.units());
	auto const pcount = signed_cast(node.points());
	node.m_buffer.assign(str.cbegin(), str.cend());
	auto const new_pcount = signed_cast(
		txt::EncUtils::count(str.cbegin(), str.cend(), false)
	);
	get_tree().update_counts(
		node,
		signed_cast(node.units()) - ucount,
		new_pcount - pcount
	);
	col_recalc();
}

std::size_t
Cursor::insert(
	char32 const cp
) {
	txt::EncUtils::char_type units[txt::EncUtils::max_units];
	auto const it = txt::EncUtils::encode(
		cp,
		std::begin(units),
		duct::CHAR_NULL
	);
	if (std::begin(units) != it) {
		auto& node = get_node();
		node.m_buffer.insert(node.cbegin() + m_index, std::begin(units), it);
		auto const size = std::distance(std::begin(units), it);
		get_tree().update_counts(node, size, 1);
		return unsigned_cast(size);
	} else {
		// Invalid code point (ignored)
		return 0u;
	}
}

std::size_t
Cursor::insert_step(
	char32 const cp
) {
	auto const size = insert(cp);
	if (0u < size) {
		DUCT_DEBUGF(
			"insert_step: m_col = %zd, m_index = %zd, size = %zu",
			m_col, m_index, size
		);
		++m_col;
		m_index += size;
	}
	return size;
}

std::size_t
Cursor::erase() {
	auto& node = get_node();
	DUCT_DEBUGF(
		"erase: m_col = %zd, m_index = %zd, ucount = %zu",
		m_col, m_index, node.units()
	);
	if (signed_cast(node.units()) <= m_index) {
		return 0u;
	}

	auto const it = node.cbegin() + m_index;
	auto const size = txt::EncUtils::required_first_whole(*it);
	DUCT_DEBUGF("  size = %u", size);
	if (it + size <= node.cend()) {
		DUCT_DEBUG("  erasing");
		node.m_buffer.erase(it, it + size);
		get_tree().update_counts(node, -size, -1);
		return size;
	} else {
		// Incomplete sequence
		DUCT_DEBUG("  ics");
		return 0u;
	}
}

std::size_t
Cursor::erase_before() {
	DUCT_DEBUGF(
		"erase_before: m_col = %zu, m_index = %zu",
		m_col, m_index
	);
	if (0 < m_col) {
		col_prev();
		return erase();
	} else {
		// Nothing preceding the cursor to erase
		return 0u;
	}
}

} // namespace Beard
} // namespace txt
<commit_msg>txt::Cursor: optimized col_recalc() when node is singular or col is invalid.<commit_after>
#include <Beard/utility.hpp>
#include <Beard/txt/Cursor.hpp>

#include <duct/char.hpp>
#include <duct/debug.hpp>

#include <cmath>
#include <iterator>

namespace Beard {
namespace txt {

// class Cursor implementation

// positioning

bool
Cursor::row_bound() noexcept {
	if (signed_cast(get_tree().lines()) <= m_row) {
		row_extent(txt::Extent::tail);
		return true;
	}
	return false;
}

void
Cursor::row_abs(
	position_type row
) noexcept {
	row = max_ce(
		row,
		max_ce(
			signed_cast(std::size_t{0u}),
			signed_cast(get_tree().lines()) - 1
		)
	);
	if (row != m_row) {
		m_row = row;
		col_recalc();
	}
}

void
Cursor::col_recalc() noexcept {
	auto const& node = get_node();
	if (0 >= m_col) {
		m_col = 0;
		m_index = 0;
	} else if (node.points() <= unsigned_cast(m_col)) {
		m_col = signed_cast(node.points());
		m_index = signed_cast(node.units());
	} else if (node.singular()) {
		m_col = dest;
		m_index = dest;
	} else {
		position_type col = 0;
		auto const end = node.cend();
		auto step = node.cbegin(), from = step;
		while (
			from < (step = txt::EncUtils::next(from, end)) &&
			col > m_col
		) {
			from = step;
			++col;
		}
		m_col = col;
		m_index = std::distance(node.cbegin(), from);
	}
}

void
Cursor::col_step(
	difference_type const n
) noexcept {
	if (0 == n) {
		return;
	}

	auto const& node = get_node();
	difference_type const dest = m_col + n;
	DUCT_DEBUGF(
		"col_step: m_col = %zd, m_index = %zd, dest = %ld, n = %ld, "
		"diff = %zd, abs = %zd",
		m_col, m_index, dest, n,
		dest - m_col,
		std::abs(dest - m_col)
	);

	// Recalculate (i.e., count from the beginning) or step
	// depending on the distance from the current column
	if (0 >= dest) {
		DUCT_DEBUG("  to head");
		m_col = 0;
		m_index = 0;
	} else if (node.points() <= unsigned_cast(dest)) {
		DUCT_DEBUG("  to tail");
		m_col = signed_cast(node.points());
		m_index = signed_cast(node.units());
	} else if (node.singular()) {
		DUCT_DEBUG("  singular");
		m_col = dest;
		m_index = dest;
	} else if (dest < std::abs(dest - m_col)) {
		DUCT_DEBUG("  recalculating");
		m_col = dest;
		col_recalc();
	} else {
		auto const begin = node.cbegin(), end = node.cend();
		auto step = begin + m_index, from = step;
		if (0 > n) {
			// Step backward
			DUCT_DEBUG("  stepping backward");
			while (
				from > (step = txt::EncUtils::prev(from, begin)) &&
				dest < m_col
			) {
				from = step;
				--m_col;
				DUCT_DEBUG("  --");
			}
		} else {
			// Step forward
			DUCT_DEBUG("  stepping forward");
			while (
				from < (step = txt::EncUtils::next(from, end)) &&
				dest > m_col
			) {
				from = step;
				++m_col;
				DUCT_DEBUG("  ++");
			}
		}
		m_index = std::distance(begin, from);
		DUCT_DEBUGF(
			"  m_col = %zd, m_index = %zd, dist = %ld",
			m_col,
			m_index,
			std::distance(begin, from)
		);
	}
}

// operations

void
Cursor::assign(
	String const& str
) {
	auto& node = get_node();
	auto const ucount = signed_cast(node.units());
	auto const pcount = signed_cast(node.points());
	node.m_buffer.assign(str.cbegin(), str.cend());
	auto const new_pcount = signed_cast(
		txt::EncUtils::count(str.cbegin(), str.cend(), false)
	);
	get_tree().update_counts(
		node,
		signed_cast(node.units()) - ucount,
		new_pcount - pcount
	);
	col_recalc();
}

std::size_t
Cursor::insert(
	char32 const cp
) {
	txt::EncUtils::char_type units[txt::EncUtils::max_units];
	auto const it = txt::EncUtils::encode(
		cp,
		std::begin(units),
		duct::CHAR_NULL
	);
	if (std::begin(units) != it) {
		auto& node = get_node();
		node.m_buffer.insert(node.cbegin() + m_index, std::begin(units), it);
		auto const size = std::distance(std::begin(units), it);
		get_tree().update_counts(node, size, 1);
		return unsigned_cast(size);
	} else {
		// Invalid code point (ignored)
		return 0u;
	}
}

std::size_t
Cursor::insert_step(
	char32 const cp
) {
	auto const size = insert(cp);
	if (0u < size) {
		DUCT_DEBUGF(
			"insert_step: m_col = %zd, m_index = %zd, size = %zu",
			m_col, m_index, size
		);
		++m_col;
		m_index += size;
	}
	return size;
}

std::size_t
Cursor::erase() {
	auto& node = get_node();
	DUCT_DEBUGF(
		"erase: m_col = %zd, m_index = %zd, ucount = %zu",
		m_col, m_index, node.units()
	);
	if (signed_cast(node.units()) <= m_index) {
		return 0u;
	}

	auto const it = node.cbegin() + m_index;
	auto const size = txt::EncUtils::required_first_whole(*it);
	DUCT_DEBUGF("  size = %u", size);
	if (it + size <= node.cend()) {
		DUCT_DEBUG("  erasing");
		node.m_buffer.erase(it, it + size);
		get_tree().update_counts(node, -size, -1);
		return size;
	} else {
		// Incomplete sequence
		DUCT_DEBUG("  ics");
		return 0u;
	}
}

std::size_t
Cursor::erase_before() {
	DUCT_DEBUGF(
		"erase_before: m_col = %zu, m_index = %zu",
		m_col, m_index
	);
	if (0 < m_col) {
		col_prev();
		return erase();
	} else {
		// Nothing preceding the cursor to erase
		return 0u;
	}
}

} // namespace Beard
} // namespace txt
<|endoftext|>


<commit_before>/*
MIT License Block

Copyright (c) 2016 Alex Barry

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

#include "include/zmqio.h"

//-------------------------Inbound ZMQ Admin----------------------------------//

//Constructor & Destructor
Zmqi::Zmqi(zmq::context_t &context, int connection_type)
{
  if (connection_type == REQ_RESP) {
    zmqi = new zmq::socket_t (context, ZMQ_REP);
  }
  else if (connection_type == PUB_SUB) {
    zmqi = new zmq::socket_t (context, ZMQ_SUB);
  }
  conn_type = connection_type;
  request = new zmq::message_t;
}

Zmqi::~Zmqi()
{
  delete zmqi;
  delete request;
  if (rcv_cstr) {delete[] rcv_cstr;}
}

//Bind the inbound socket
void Zmqi::bind(std::string conn_str)
{
  if (conn_type == REQ_RESP) {
    zmqi->bind(conn_str);
  }
  else if (conn_type == PUB_SUB) {
    zmqi->connect(conn_str);
  }
}

//Recieve an inbound message
std::string Zmqi::recv()
{
  // Rebuild the ZeroMQ Message Object
  // Close the message object and then re-build, this means that
  // any resources from the message MAY NOT BE PRESENT after the next message has been recieved
  if (started) {
    request->rebuild();
  }

  //  Wait for next request from client
  zmqi->recv (request);

  //Convert the OMQ message into a string to be passed
  req_string.assign(static_cast<char*>(request->data()), request->size());
  started = true;
  return req_string;
}

//Recieve an inbound message
char * Zmqi::crecv() {
  // Rebuild the ZeroMQ Message Object
  // Close the message object and then re-build, this means that
  // any resources from the message MAY NOT BE PRESENT after the next message has been recieved
  if (started) {request->rebuild();}
  if (rcv_cstr) {delete[] rcv_cstr;rcv_cstr=NULL;}

  // Wait for next request from client
  zmqi->recv (request);

  // Take the data out of the message
  if (request->size() > 0 {
    rcv_cstr = new char [request->size()];
    std::memcpy(rcv_cstr, request->data(), request->size());
  }

  started = true;

  //Convert the OMQ message into a string to be passed
  //rcv_cstr = static_cast<char*>(request.data()), request.size();
  return rcv_cstr;
}

//Send a response
void Zmqi::send(const char * msg, int msg_size)
{
  //  Send reply back to client
  zmq::message_t reply (msg_size);

  //Prepare return data
  memcpy (reply.data (), msg, msg_size);
  //Send the response
  zmqi->send (reply);
}

//Send a string response
void Zmqi::send(std::string msg)
{
  msg_cstr = msg.c_str();
  send(msg_cstr, msg.size());
}

void Zmqi::subscribe(std::string filter)
{
  zmqi->setsockopt(ZMQ_SUBSCRIBE, filter.c_str(), filter.size());
}

//-------------------------Outbound ZMQ Admin---------------------------------//

//Constructor & Destructor
Zmqo::Zmqo(zmq::context_t &context, int connection_type)
{
  if (connection_type == REQ_RESP) {
    zmqo = new zmq::socket_t (context, ZMQ_REQ);
  }
  else if (connection_type == PUB_SUB) {
    zmqo = new zmq::socket_t (context, ZMQ_PUB);
  }
  conn_type = connection_type;
}

Zmqo::~Zmqo()
{
  delete zmqo;
  if (rcv_cstr) {delete[] rcv_cstr;}
}

//Connect to the Socket
void Zmqo::connect(std::string conn_str)
{
  if (conn_type == REQ_RESP) {
    zmqo->connect(conn_str);
  }
  else if (conn_type == PUB_SUB) {
    zmqo->bind(conn_str);
  }
}

//Send a message
void Zmqo::send(const char * msg, int msg_size)
{
  std::lock_guard<std::mutex> lock(send_mutex);
  //Set up the message to go out on 0MQ
  zmq::message_t req (msg_size);
  memcpy (req.data (), msg, msg_size);

  //Send the message
  zmqo->send (req);
}

//Send a string message
void Zmqo::send(std::string msg) {
  msg_cstr = msg.c_str();
  send(msg_cstr, msg.size());
}

//Recieve a Response
std::string Zmqo::recv()
{
  // Rebuild the ZeroMQ Message Object
  // Close the message object and then re-build, this means that
  // any resources from the message MAY NOT BE PRESENT after the next message has been recieved
  if (!started) {
    request.rebuild();
  }
  //  Get the reply.
  zmqo->recv (&request);

  //Process the reply
  r_str.assign(static_cast<char*>(request.data()), request.size());
  started = true;
  return r_str;
}

//Recieve an response
char * Zmqo::crecv() {
  // Rebuild the ZeroMQ Message Object
  // Close the message object and then re-build, this means that
  // any resources from the message MAY NOT BE PRESENT after the next message has been recieved
  if (!started) {
    request.rebuild();
  }

  if (rcv_cstr) {delete[] rcv_cstr;rcv_cstr=NULL;}

  // Wait for next request from client
  zmqo->recv (&request);

  // Take the data out of the message
  rcv_cstr = new char [request.size()];
  std::memcpy(rcv_cstr, request.data(), request.size());

  started = true;

  //Convert the OMQ message into a string to be passed
  //rcv_cstr = static_cast<char*>(request.data()), request.size();
  return rcv_cstr;
}
<commit_msg>ZMQ Memory allocation fix<commit_after>/*
MIT License Block

Copyright (c) 2016 Alex Barry

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

#include "include/zmqio.h"

//-------------------------Inbound ZMQ Admin----------------------------------//

//Constructor & Destructor
Zmqi::Zmqi(zmq::context_t &context, int connection_type)
{
  if (connection_type == REQ_RESP) {
    zmqi = new zmq::socket_t (context, ZMQ_REP);
  }
  else if (connection_type == PUB_SUB) {
    zmqi = new zmq::socket_t (context, ZMQ_SUB);
  }
  conn_type = connection_type;
  request = new zmq::message_t;
}

Zmqi::~Zmqi()
{
  delete zmqi;
  delete request;
  if (rcv_cstr) {delete[] rcv_cstr;}
}

//Bind the inbound socket
void Zmqi::bind(std::string conn_str)
{
  if (conn_type == REQ_RESP) {
    zmqi->bind(conn_str);
  }
  else if (conn_type == PUB_SUB) {
    zmqi->connect(conn_str);
  }
}

//Recieve an inbound message
std::string Zmqi::recv()
{
  // Rebuild the ZeroMQ Message Object
  // Close the message object and then re-build, this means that
  // any resources from the message MAY NOT BE PRESENT after the next message has been recieved
  if (started) {
    request->rebuild();
  }

  //  Wait for next request from client
  zmqi->recv (request);

  //Convert the OMQ message into a string to be passed
  req_string.assign(static_cast<char*>(request->data()), request->size());
  started = true;
  return req_string;
}

//Recieve an inbound message
char * Zmqi::crecv() {
  // Rebuild the ZeroMQ Message Object
  // Close the message object and then re-build, this means that
  // any resources from the message MAY NOT BE PRESENT after the next message has been recieved
  if (started) {request->rebuild();}
  if (rcv_cstr) {delete[] rcv_cstr;rcv_cstr=NULL;}

  // Wait for next request from client
  zmqi->recv (request);

  // Take the data out of the message
  if (request->size() > 0) {
    rcv_cstr = new char [request->size()];
    std::memcpy(rcv_cstr, request->data(), request->size());
  }

  started = true;

  //Convert the OMQ message into a string to be passed
  //rcv_cstr = static_cast<char*>(request.data()), request.size();
  return rcv_cstr;
}

//Send a response
void Zmqi::send(const char * msg, int msg_size)
{
  //  Send reply back to client
  zmq::message_t reply (msg_size);

  //Prepare return data
  memcpy (reply.data (), msg, msg_size);
  //Send the response
  zmqi->send (reply);
}

//Send a string response
void Zmqi::send(std::string msg)
{
  msg_cstr = msg.c_str();
  send(msg_cstr, msg.size());
}

void Zmqi::subscribe(std::string filter)
{
  zmqi->setsockopt(ZMQ_SUBSCRIBE, filter.c_str(), filter.size());
}

//-------------------------Outbound ZMQ Admin---------------------------------//

//Constructor & Destructor
Zmqo::Zmqo(zmq::context_t &context, int connection_type)
{
  if (connection_type == REQ_RESP) {
    zmqo = new zmq::socket_t (context, ZMQ_REQ);
  }
  else if (connection_type == PUB_SUB) {
    zmqo = new zmq::socket_t (context, ZMQ_PUB);
  }
  conn_type = connection_type;
}

Zmqo::~Zmqo()
{
  delete zmqo;
  if (rcv_cstr) {delete[] rcv_cstr;}
}

//Connect to the Socket
void Zmqo::connect(std::string conn_str)
{
  if (conn_type == REQ_RESP) {
    zmqo->connect(conn_str);
  }
  else if (conn_type == PUB_SUB) {
    zmqo->bind(conn_str);
  }
}

//Send a message
void Zmqo::send(const char * msg, int msg_size)
{
  std::lock_guard<std::mutex> lock(send_mutex);
  //Set up the message to go out on 0MQ
  zmq::message_t req (msg_size);
  memcpy (req.data (), msg, msg_size);

  //Send the message
  zmqo->send (req);
}

//Send a string message
void Zmqo::send(std::string msg) {
  msg_cstr = msg.c_str();
  send(msg_cstr, msg.size());
}

//Recieve a Response
std::string Zmqo::recv()
{
  // Rebuild the ZeroMQ Message Object
  // Close the message object and then re-build, this means that
  // any resources from the message MAY NOT BE PRESENT after the next message has been recieved
  if (!started) {
    request.rebuild();
  }
  //  Get the reply.
  zmqo->recv (&request);

  //Process the reply
  r_str.assign(static_cast<char*>(request.data()), request.size());
  started = true;
  return r_str;
}

//Recieve an response
char * Zmqo::crecv() {
  // Rebuild the ZeroMQ Message Object
  // Close the message object and then re-build, this means that
  // any resources from the message MAY NOT BE PRESENT after the next message has been recieved
  if (!started) {
    request.rebuild();
  }

  if (rcv_cstr) {delete[] rcv_cstr;rcv_cstr=NULL;}

  // Wait for next request from client
  zmqo->recv (&request);

  // Take the data out of the message
  rcv_cstr = new char [request.size()];
  std::memcpy(rcv_cstr, request.data(), request.size());

  started = true;

  //Convert the OMQ message into a string to be passed
  //rcv_cstr = static_cast<char*>(request.data()), request.size();
  return rcv_cstr;
}
<|endoftext|>


<commit_before>#ifndef GUARD_identity_map_hpp
#define GUARD_identity_map_hpp

#include "handle.hpp"
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/unordered_map.hpp>
#include <cassert>
#include <map>
#include <stdexcept>

// For debugging
	#include <jewel/debug_log.hpp>
	#include <iostream>
	#include <typeinfo>
	using std::endl;

namespace sqloxx
{

template <typename T, typename Connection>
class IdentityMap
{
public:

	typedef typename T::Id Id;
	typedef typename T::Id ProxyKey;

	IdentityMap(Connection& p_connection);
	IdentityMap(IdentityMap const& rhs);
	~IdentityMap();
	IdentityMap& operator=(IdentityMap const& rhs);
	/**
	 * Provide handle to object of T, representing a newly created object
	 * that has not yet been persisted to the database
	 */
	Handle<T> provide_object();

	/**
	 * Provide handle to object of type T, representing an object
	 * already stored in the database, with id p_id.
	 */
	Handle<T> provide_object(Id p_id);

	/**
	 * Register id of newly saved T.
	 */
	void register_id(ProxyKey proxy_key, Id allocated_id);

	void erase_object_proxied(ProxyKey proxy_key);

	/**
	 * Notify the IdentityMap that there are no handles left that are
	 * pointing to this object.
	 */
	void notify_nil_handles(ProxyKey proxy_key);

	void enable_caching();

	void disable_caching();

	/**
	 * @returns a reference to the database connection with which
	 * this IdentityMap is associated.
	 *
	 * Exception safety: nothrow guarantee.
	 *
	 * @todo Testing.
	 */
	Connection& connection();

private:

	// Find the next available proxy key
	// WARNING Move the implementation out of the class body.
	ProxyKey next_proxy_key();
	typedef typename boost::shared_ptr<T> Record;
	typedef boost::unordered_map<Id, Record> IdMap;
	typedef std::map<ProxyKey, Record> ProxyKeyMap;

	ProxyKeyMap& proxy_map() const
	{
		return m_map_data->proxy_map;
	}

	IdMap& id_map() const
	{
		return m_map_data->id_map;
	}

	bool& caching() const
	{
		return m_map_data->caching;
	}

	// Data members

	// Hold in a struct to facilitate safe, shallow copying.
	struct MapData
	{
		MapData(Connection& p_connection):
			connection(p_connection),
			caching(false)
		{
		}
		ProxyKeyMap proxy_map;  // For all objects.
		IdMap id_map;        // For objects that exist in the database.
		// Indicates whether the IdentityMap is currently
		// holding objects indefinitely in the cache (m_caching == true),
		// or whether it is
		// clearing each object out when there are no longer handles
		// pointing to it (m_caching == false).
		Connection& connection;
		bool caching;
	};
	boost::shared_ptr<MapData> m_map_data;
};

template <typename T, typename Connection>
inline
IdentityMap<T, Connection>::IdentityMap(Connection& p_connection):
	m_map_data(new MapData(p_connection))
{
}

template <typename T, typename Connection>
inline
IdentityMap<T, Connection>::IdentityMap(IdentityMap const& rhs):
	m_map_data(rhs.m_map_data)
{
}

template <typename T, typename Connection>
inline
IdentityMap<T, Connection>::~IdentityMap()
{
}

template <typename T, typename Connection>
inline
IdentityMap<T, Connection>&
IdentityMap<T, Connection>::operator=(IdentityMap const& rhs)
{
	m_map_data = rhs.m_map_data;
	return *this;
}

template <typename T, typename Connection>
Handle<T>
IdentityMap<T, Connection>::provide_object()
{

	Record obj_ptr((new T(*this)));
	ProxyKey const proxy_key = next_proxy_key();
	obj_ptr->set_proxy_key(proxy_key);
	proxy_map()[proxy_key] = obj_ptr;
	return Handle<T>(obj_ptr.get());
}

template <typename T, typename Connection>
Handle<T>
IdentityMap<T, Connection>::provide_object(Id p_id)
{
	typename IdMap::iterator it = id_map().find(p_id);
	if (it == id_map().end())
	{
		// Then we need to create this object.
		Record obj_ptr(new T(*this, p_id));
		id_map()[p_id] = obj_ptr;
		ProxyKey proxy_key = next_proxy_key();
		proxy_map()[proxy_key] = obj_ptr;
		obj_ptr->set_proxy_key(proxy_key);
		return Handle<T>(obj_ptr.get());
	}
	assert (it != id_map().end());
	return Handle<T>(it->second.get());
}

template <typename T, typename Connection>
void
IdentityMap<T, Connection>::register_id(ProxyKey proxy_key, Id allocated_id)
{
	id_map()[allocated_id] = proxy_map()[proxy_key];
	return;
}

template <typename T, typename Connection>
void
IdentityMap<T, Connection>::erase_object_proxied(ProxyKey proxy_key)
{
	Record const record = proxy_map().find(proxy_key)->second;
	if (record->has_id())
	{
		assert (id_map().find(record->id()) != id_map().end());
		id_map().erase(record->id());
	}
	proxy_map().erase(proxy_key);
	return;
}

template <typename T, typename Connection>
void
IdentityMap<T, Connection>::notify_nil_handles(ProxyKey proxy_key)
{
	if (!caching())
	{
		erase_object_proxied(proxy_key);
	}
	return;
}

template <typename T, typename Connection>
void
IdentityMap<T, Connection>::enable_caching()
{
	caching() = true;
}

template <typename T, typename Connection>
void
IdentityMap<T, Connection>::disable_caching()
{
	if (caching())
	{
		typename ProxyKeyMap::iterator const endpoint = proxy_map().end();
		for
		(	typename ProxyKeyMap::iterator it = proxy_map().begin();
			it != endpoint;
			++it
		)
		{
			if (it->second->is_orphaned())
			{
				erase_object_proxied(it->first);
			}
		}
		caching() = false;
	}
	return;
}

template <typename T, typename Connection>
inline
Connection&
IdentityMap<T, Connection>::connection()
{
	return m_map_data->connection;
}

template <typename T, typename Connection>
typename IdentityMap<T, Connection>::ProxyKey
IdentityMap<T, Connection>::next_proxy_key()
{
	// TODO Change this so that vacated slots are filled, rather
	// than just always taking least - 1. Currently there is
	// a danger that we will just forever move towards min, until
	// we overflow.

	// Using negative numbers to avoid any possible confusion
	// with Id.
	// Relies on this being a std::map, in which the first
	// key is less than any other key.
	ProxyKey const least = proxy_map().begin()->first;
	if (least == std::numeric_limits<ProxyKey>::min())
	{
		throw OverflowException("Proxy key has reached numeric limit.");
	}
	return least - 1;
}

}  // namespace sqloxx

#endif  // GUARD_identity_map_hpp
<commit_msg>Worked on documentation for sqloxx::IdentityMap.<commit_after>#ifndef GUARD_identity_map_hpp
#define GUARD_identity_map_hpp

#include "handle.hpp"
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/unordered_map.hpp>
#include <cassert>
#include <map>
#include <stdexcept>

// For debugging
	#include <jewel/debug_log.hpp>
	#include <iostream>
	#include <typeinfo>
	using std::endl;

namespace sqloxx
{

/**
 * Provides an in-memory cache for objects of type T, where such
 * objects are persisted to a database via a database connection of
 * type Connection. T and Connection are passed as template parameters
 * to the class template. It is expected that T is a subclass of
 * sqloxx::PersistentObject<T, Connection>, and Connection is a
 * subclass of sqloxx::DatabaseConnection.
 *
 * Each instance of IdentityMap has a particular Connection associated
 * with it. The IdentityMap caches objects loaded from the database,
 * and provides clients - in particular the sqloxx::Handle<T> class -
 * pointers to these objects. My using IdentityMap to cache objects,
 * application code can be sure that each single record of type T
 * that is stored in the database, has at most a single in-memory
 * object of type T associated with that record, loaded in memory
 * at any one time. IdentityMap thus implements the "Identity Map"
 * pattern detailed in Martin Fowler's book, "Patterns of Enterprise
 * Application Architecture". By having at most a single in-memory
 * object per in-database record, we guard against the possibility
 * of the same object being edited inconsistently in different
 * locations. By keeping objects in a cache, we speed execution of
 * read and write operations, by avoiding a trip to the disk when an
 * object has already been loaded.
 *
 * IdentityMap is intended to work in conjunction with sqloxx::Handle<T>
 * and sqloxx::PersistentObject<T, Connection>. See also the documentation
 * for those classes.
 *
 * @todo Documentation and testing.
 */
template <typename T, typename Connection>
class IdentityMap
{
public:

	typedef typename T::Id Id;
	typedef typename T::Id ProxyKey;

	/**
	 * Construct an IdentityMap associated with the database
	 * connection Connection. Connection should be a subclass
	 * of sqloxx::DatabaseConnection.
	 *
	 * @throws std::bad_alloc in the case of memory allocation
	 * failure. (This is very unlikely.)
	 *
	 * Exception safety: strong guarantee.
	 *
	 * @todo Testing.
	 */
	IdentityMap(Connection& p_connection);

	/**
	 * Copy constructor. Performs a shallow copy. The underlying
	 * structures (e.g. database connection and cache) employed
	 * by the new IdentitMap will be the very same as those employed
	 * by rhs.
	 *
	 * Exception safety: nothrow guarantee.
	 */
	IdentityMap(IdentityMap const& rhs);

	/**
	 * Destructor. The underlying cache is automatically emptied
	 * of all objects (i.e. instances of T) on destruction of
	 * the IdentityMap, by the calling the destructor of each
	 * object in the cache. The cache is then itself destructed.
	 * However the database connection (Connection instance) referenced
	 * by the IdentityMap is \e not destructed merely by virtue
	 * of the destruction of the IdentityMap.
	 *
	 * Exception safety: the nothrow guarantee is provided,
	 * providing the destructor of T does not throw.
	 *
	 * @todo Testing.
	 */
	~IdentityMap();

	/**
	 * Assignment is shallow, and behaves as per the copy constructor
	 *
	 * Exception safety: nothrow guarantee.
	 *
	 * @todo Testing.
	 */
	IdentityMap& operator=(IdentityMap const& rhs);

	/**
	 * Provide handle to object of T, representing a newly created object
	 * that has not yet been persisted to the database
	 */
	Handle<T> provide_object();

	/**
	 * Provide handle to object of type T, representing an object
	 * already stored in the database, with id p_id.
	 */
	Handle<T> provide_object(Id p_id);

	/**
	 * Register id of newly saved T.
	 */
	void register_id(ProxyKey proxy_key, Id allocated_id);

	void erase_object_proxied(ProxyKey proxy_key);

	/**
	 * Notify the IdentityMap that there are no handles left that are
	 * pointing to this object.
	 */
	void notify_nil_handles(ProxyKey proxy_key);

	void enable_caching();

	void disable_caching();

	/**
	 * @returns a reference to the database connection with which
	 * this IdentityMap is associated.
	 *
	 * Exception safety: nothrow guarantee.
	 *
	 * @todo Testing.
	 */
	Connection& connection();

private:

	// Find the next available proxy key
	// WARNING Move the implementation out of the class body.
	ProxyKey next_proxy_key();
	typedef typename boost::shared_ptr<T> Record;
	typedef boost::unordered_map<Id, Record> IdMap;
	typedef std::map<ProxyKey, Record> ProxyKeyMap;

	ProxyKeyMap& proxy_map() const
	{
		return m_map_data->proxy_map;
	}

	IdMap& id_map() const
	{
		return m_map_data->id_map;
	}

	bool& caching() const
	{
		return m_map_data->caching;
	}

	// Data members

	// Hold in a struct to facilitate safe, shallow copying.
	struct MapData
	{
		MapData(Connection& p_connection):
			connection(p_connection),
			caching(false)
		{
		}
		ProxyKeyMap proxy_map;  // For all objects.
		IdMap id_map;        // For objects that exist in the database.
		// Indicates whether the IdentityMap is currently
		// holding objects indefinitely in the cache (m_caching == true),
		// or whether it is
		// clearing each object out when there are no longer handles
		// pointing to it (m_caching == false).
		Connection& connection;
		bool caching;
	};
	boost::shared_ptr<MapData> m_map_data;
};

template <typename T, typename Connection>
inline
IdentityMap<T, Connection>::IdentityMap(Connection& p_connection):
	m_map_data(new MapData(p_connection))
{
}

template <typename T, typename Connection>
inline
IdentityMap<T, Connection>::IdentityMap(IdentityMap const& rhs):
	m_map_data(rhs.m_map_data)
{
}

template <typename T, typename Connection>
inline
IdentityMap<T, Connection>::~IdentityMap()
{
}

template <typename T, typename Connection>
inline
IdentityMap<T, Connection>&
IdentityMap<T, Connection>::operator=(IdentityMap const& rhs)
{
	m_map_data = rhs.m_map_data;
	return *this;
}

template <typename T, typename Connection>
Handle<T>
IdentityMap<T, Connection>::provide_object()
{

	Record obj_ptr((new T(*this)));
	ProxyKey const proxy_key = next_proxy_key();
	obj_ptr->set_proxy_key(proxy_key);
	proxy_map()[proxy_key] = obj_ptr;
	return Handle<T>(obj_ptr.get());
}

template <typename T, typename Connection>
Handle<T>
IdentityMap<T, Connection>::provide_object(Id p_id)
{
	typename IdMap::iterator it = id_map().find(p_id);
	if (it == id_map().end())
	{
		// Then we need to create this object.
		Record obj_ptr(new T(*this, p_id));
		id_map()[p_id] = obj_ptr;
		ProxyKey proxy_key = next_proxy_key();
		proxy_map()[proxy_key] = obj_ptr;
		obj_ptr->set_proxy_key(proxy_key);
		return Handle<T>(obj_ptr.get());
	}
	assert (it != id_map().end());
	return Handle<T>(it->second.get());
}

template <typename T, typename Connection>
void
IdentityMap<T, Connection>::register_id(ProxyKey proxy_key, Id allocated_id)
{
	id_map()[allocated_id] = proxy_map()[proxy_key];
	return;
}

template <typename T, typename Connection>
void
IdentityMap<T, Connection>::erase_object_proxied(ProxyKey proxy_key)
{
	Record const record = proxy_map().find(proxy_key)->second;
	if (record->has_id())
	{
		assert (id_map().find(record->id()) != id_map().end());
		id_map().erase(record->id());
	}
	proxy_map().erase(proxy_key);
	return;
}

template <typename T, typename Connection>
void
IdentityMap<T, Connection>::notify_nil_handles(ProxyKey proxy_key)
{
	if (!caching())
	{
		erase_object_proxied(proxy_key);
	}
	return;
}

template <typename T, typename Connection>
void
IdentityMap<T, Connection>::enable_caching()
{
	caching() = true;
}

template <typename T, typename Connection>
void
IdentityMap<T, Connection>::disable_caching()
{
	if (caching())
	{
		typename ProxyKeyMap::iterator const endpoint = proxy_map().end();
		for
		(	typename ProxyKeyMap::iterator it = proxy_map().begin();
			it != endpoint;
			++it
		)
		{
			if (it->second->is_orphaned())
			{
				erase_object_proxied(it->first);
			}
		}
		caching() = false;
	}
	return;
}

template <typename T, typename Connection>
inline
Connection&
IdentityMap<T, Connection>::connection()
{
	return m_map_data->connection;
}

template <typename T, typename Connection>
typename IdentityMap<T, Connection>::ProxyKey
IdentityMap<T, Connection>::next_proxy_key()
{
	// TODO Change this so that vacated slots are filled, rather
	// than just always taking least - 1. Currently there is
	// a danger that we will just forever move towards min, until
	// we overflow.

	// Using negative numbers to avoid any possible confusion
	// with Id.
	// Relies on this being a std::map, in which the first
	// key is less than any other key.
	ProxyKey const least = proxy_map().begin()->first;
	if (least == std::numeric_limits<ProxyKey>::min())
	{
		throw OverflowException("Proxy key has reached numeric limit.");
	}
	return least - 1;
}

}  // namespace sqloxx

#endif  // GUARD_identity_map_hpp
<|endoftext|>


<commit_before>//=============================================================================
// File:       token.cpp
// Contents:   Definitions for DwTokenizer, DwRfc822Tokenizer
// Maintainer: Doug Sauder <dwsauder@fwb.gulf.net>
// WWW:        http://www.fwb.gulf.net/~dwsauder/mimepp.html
// $Revision$
// $Date$
//
// Copyright (c) 1996, 1997 Douglas W. Sauder
// All rights reserved.
//
// IN NO EVENT SHALL DOUGLAS W. SAUDER BE LIABLE TO ANY PARTY FOR DIRECT,
// INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF
// THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF DOUGLAS W. SAUDER
// HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// DOUGLAS W. SAUDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT
// NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
// BASIS, AND DOUGLAS W. SAUDER HAS NO OBLIGATION TO PROVIDE MAINTENANCE,
// SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
//
//=============================================================================

#define DW_IMPLEMENTATION

#include <mimelib/config.h>
#include <mimelib/debug.h>
#include <assert.h>
#include <ctype.h>
#include <mimelib/string.h>
#include <mimelib/token.h>

std::ostream* DwTokenizer::mDebugOut = 0;

DwTokenizer::DwTokenizer(const DwString& aStr)
  : mString(aStr)
{
    mTokenStart  = 0;
    mTokenLength = 0;
    mNextStart   = 0;
    mTkType      = eTkError;
}

DwTokenizer::DwTokenizer(const char* aCStr)
  : mString(aCStr)
{
    mTokenStart  = 0;
    mTokenLength = 0;
    mNextStart   = 0;
    mTkType      = eTkError;
}

DwTokenizer::~DwTokenizer()
{
}

void DwTokenizer::StripDelimiters()
{
    if (mTokenLength < 2) return;
    // const ref -- avoids copy on write when using operator[]
    const DwString& token = mToken;
    switch (mTkType) {
    case eTkQuotedString:
        if (token[0] == '"') {
            mToken = mToken.substr(1);
            ++mTokenStart;
            --mTokenLength;
        }
        if (mTokenLength > 0 && token[mTokenLength-1] == '"') {
            mToken = mToken.substr(0, mTokenLength-1);
            --mTokenLength;
        }
        break;
    case eTkDomainLiteral:
        if (token[0] == '[') {
            mToken = mToken.substr(1);
            ++mTokenStart;
            --mTokenLength;
        }
        if (mTokenLength > 0 && token[mTokenLength-1] == ']') {
            mToken = mToken.substr(0, mTokenLength-1);
            --mTokenLength;
        }
        break;
    case eTkComment:
        if (token[0] == '(') {
            mToken = mToken.substr(1);
            ++mTokenStart;
            --mTokenLength;
        }
        if (mTokenLength > 0 && token[mTokenLength-1] == ')') {
            mToken = mToken.substr(0, mTokenLength-1);
            --mTokenLength;
        }
        break;
    }
}

void DwTokenizer::ParseQuotedString()
{
    size_t pos = mTokenStart;
    while (1) {
        ++pos;
        if (pos >= mString.length()) {
            // Ran out of string
            mTokenLength = 0;
            mToken = "";
            mNextStart = pos;
            mTkType = eTkError;
            break;
        }
        else if (mString[pos] == '\\') {
            // Quoted character
            ++pos;
            if (pos >= mString.length()) {
                // Ran out of string
                mTokenLength = 0;
                mToken = "";
                mNextStart = pos;
                mTkType = eTkError;
                break;
            }
        }
        else if (mString[pos] == '"') {
            // End of quoted string
            ++pos;
            mTokenLength = pos - mTokenStart;
            mToken = mString.substr(mTokenStart, mTokenLength);
            mNextStart = pos;
            break;
        }
    }
}

void DwTokenizer::ParseComment()
{
    size_t pos = mTokenStart;
    int level = 1;
    while (1) {
        ++pos;
        if (pos >= mString.length()) {
            // Ran out of string
            mTokenLength = 0;
            mToken = "";
            mNextStart = pos;
            mTkType = eTkError;
            break;
        }
        else if (mString[pos] == '\\') {
            // Quoted character
            ++pos;
            if (pos >= mString.length()) {
                // Ran out of string
                mTokenLength = 0;
                mToken = "";
                mNextStart = pos;
                mTkType = eTkError;
                break;
            }
        }
        else if (mString[pos] == ')') {
            --level;
            if (level == 0) {
                // End of comment
                ++pos;
                mTokenLength = pos - mTokenStart;
                mToken = mString.substr(mTokenStart, mTokenLength);
                mNextStart = pos;
                break;
            }
        }
        else if (mString[pos] == '(') {
            ++level;
        }
    }
}

void DwTokenizer::ParseDomainLiteral()
{
    size_t pos = mTokenStart;
    while (1) {
        ++pos;
        if (pos >= mString.length()) {
            // Ran out of string
            mTokenLength = 0;
            mToken = "";
            mNextStart = pos;
            mTkType = eTkError;
            break;
        }
        else if (mString[pos] == '\\') {
            // Quoted character
            ++pos;
            if (pos >= mString.length()) {
                // Ran out of string
                mTokenLength = 0;
                mToken = "";
                mNextStart = pos;
                mTkType = eTkError;
                break;
            }
        }
        else if (mString[pos] == ']') {
            // End of domain literal
            ++pos;
            mTokenLength = pos - mTokenStart;
            mToken = mString.substr(mTokenStart, mTokenLength);
            mNextStart = pos;
            break;
        }
    }
}

void DwTokenizer::PrintToken(std::ostream* aOut)
{
    if (!aOut) return;
    const char* type = 0;
    switch (mTkType) {
    case eTkError:
        type = "error          ";
        break;
    case eTkNull:
        type = "null           ";
        break;
    case eTkSpecial:
        type = "special        ";
        break;
    case eTkAtom:
        type = "atom           ";
        break;
    case eTkComment:
        type = "comment        ";
        break;
    case eTkQuotedString:
        type = "quoted string  ";
        break;
    case eTkDomainLiteral:
        type = "domain literal ";
        break;
    case eTkTspecial:
        type = "tspecial       ";
        break;
    case eTkToken:
        type = "token          ";
        break;
    default:
        type = "unknown        ";
        break;
    }
    *aOut << type << mToken << '\n';
}

#define isspecial(c) ((c)=='('||(c)==')'||(c)=='<'||(c)=='>'||(c)=='@'\
    ||(c)==','||(c)==';'||(c)==':'||(c)=='\\'||(c)=='"'||(c)=='.'\
    ||(c)=='['||(c)==']')

DwRfc822Tokenizer::DwRfc822Tokenizer(const DwString& aStr)
  : DwTokenizer(aStr)
{
    ParseToken();
}

DwRfc822Tokenizer::DwRfc822Tokenizer(const char* aCStr)
  : DwTokenizer(aCStr)
{
    ParseToken();
}

DwRfc822Tokenizer::~DwRfc822Tokenizer()
{
}

int DwRfc822Tokenizer::Restart()
{
    mNextStart = 0;
    ParseToken();
    return mTkType;
}

int DwRfc822Tokenizer::operator ++ ()
{
    ParseToken();
    return mTkType;
}

void DwRfc822Tokenizer::ParseToken()
{
    // Assume the field body has already been extracted.  That is, we don't
    // have to watch for the end of the field body or folding.  We just
    // treat any CRs or LFs as white space.
    mTokenStart = mNextStart;
    mTokenLength = 0;
    mTkType = eTkNull;
    if (mTokenStart >= mString.length()) {
        return;
    }
    // Skip leading space.  Also, since control chars are not permitted
    // in atoms, skip these, too.
    while (1) {
        if (mTokenStart >= mString.length()) {
            return;
        }
        if (!isspace(mString[mTokenStart]) && !iscntrl(mString[mTokenStart]))
            break;
        ++mTokenStart;
    }
    char ch = mString[mTokenStart];
    // Quoted string
    if (ch == '"') {
        mTkType = eTkQuotedString;
        ParseQuotedString();
    }
    // Comment
    else if (ch == '(') {
        mTkType = eTkComment;
        ParseComment();
    }
    // Domain literal
    else if (ch == '[') {
        mTkType = eTkDomainLiteral;
        ParseDomainLiteral();
    }
    // Special
    else if (isspecial(ch)) {
        mTkType = eTkSpecial;
        mTokenLength = 1;
        mToken = mString.substr(mTokenStart, 1);
        mNextStart = mTokenStart + 1;
    }
    // Atom
    else {
        mTkType = eTkAtom;
        ParseAtom();
    }
    if (mDebugOut) PrintToken(mDebugOut);
}

void DwRfc822Tokenizer::ParseAtom()
{
    size_t pos = mTokenStart;
    while (1) {
        ++pos;
        char ch = (pos < mString.length()) ? mString[pos] : (char) 0;
        if (pos >= mString.length()
            || isspace(ch)
            || iscntrl(ch)
            || isspecial(ch)) {

            mTokenLength = pos - mTokenStart;
            mToken = mString.substr(mTokenStart, mTokenLength);
            mNextStart = pos;
            break;
        }
    }
}

static inline bool istspecial( char c )
{
  switch ( c ) {
     case '(':
     case ')':
     case '<':
     case '>':
     case '@':
     case ',':
     case ';':
     case ':':
     case '\\':
     case '"':
     case '/':
     case '[':
     case ']':
     case '?':
     case '=':
        return true;
     default:
        return false;
  }
 }

DwRfc1521Tokenizer::DwRfc1521Tokenizer(const DwString& aStr)
  : DwTokenizer(aStr)
{
    ParseToken();
}

DwRfc1521Tokenizer::DwRfc1521Tokenizer(const char* aCStr)
  : DwTokenizer(aCStr)
{
    ParseToken();
}

DwRfc1521Tokenizer::~DwRfc1521Tokenizer()
{
}

int DwRfc1521Tokenizer::Restart()
{
    mNextStart = 0;
    ParseToken();
    return mTkType;
}

int DwRfc1521Tokenizer::operator ++ ()
{
    ParseToken();
    return mTkType;
}

void DwRfc1521Tokenizer::ParseToken()
{
    // Assume the field body has already been extracted.  That is, we don't
    // have to watch for the end of the field body or folding.  We just
    // treat any CRs or LFs as white space.
    mTokenStart = mNextStart;
    mTokenLength = 0;
    mTkType = eTkNull;
    if (mTokenStart >= mString.length()) {
        return;
    }
    // Skip leading space.  Also, since control chars are not permitted
    // in atoms, skip these, too.
    while (1) {
        if (mTokenStart >= mString.length()) {
            return;
        }
        if (!isspace(mString[mTokenStart]) && !iscntrl(mString[mTokenStart]))
            break;
        ++mTokenStart;
    }
    char ch = mString[mTokenStart];
    // Quoted string
    if (ch == '"') {
        mTkType = eTkQuotedString;
        ParseQuotedString();
    }
    // Comment
    else if (ch == '(') {
        mTkType = eTkComment;
        ParseComment();
    }
    // Domain literal
    else if (ch == '[') {
        mTkType = eTkDomainLiteral;
        ParseDomainLiteral();
    }
    // Special
    else if (istspecial(ch)) {
        mTkType = eTkTspecial;
        mTokenLength = 1;
        mToken = mString.substr(mTokenStart, 1);
        mNextStart = mTokenStart + 1;
    }
    // Atom
    else {
        mTkType = eTkToken;
        ParseAtom();
    }
    if (mDebugOut) PrintToken(mDebugOut);
}

void DwRfc1521Tokenizer::ParseAtom()
{
    size_t pos = mTokenStart;
    while (1) {
        ++pos;
        char ch = (pos < mString.length()) ? mString[pos] : (char) 0;
        if (pos >= mString.length()
            || isspace(ch)
            || iscntrl(ch)
            || istspecial(ch)) {

            mTokenLength = pos - mTokenStart;
            mToken = mString.substr(mTokenStart, mTokenLength);
            mNextStart = pos;
            break;
        }
    }
}

DwTokenString::DwTokenString(const DwString& aStr)
  : mString(aStr)
{
    mTokensStart  = 0;
    mTokensLength = 0;
}

DwTokenString::~DwTokenString()
{
}

void DwTokenString::SetFirst(const DwTokenizer& aTkzr)
{
    switch (aTkzr.Type()) {
    case eTkError:
    case eTkNull:
        mTokensStart  = aTkzr.mTokenStart;
        mTokensLength = 0;
        break;
    case eTkComment:
    case eTkDomainLiteral:
    case eTkQuotedString:
    case eTkSpecial:
    case eTkAtom:
    case eTkTspecial:
    case eTkToken:
        mTokensStart  = aTkzr.mTokenStart;
        mTokensLength = aTkzr.mTokenLength;
        break;
    }
    mTokens = mString.substr(mTokensStart, mTokensLength);
}

void DwTokenString::SetLast(const DwTokenizer& aTkzr)
{
    assert(aTkzr.mTokenStart >= mTokensStart);
    if (aTkzr.mTokenStart < mTokensStart) return;
    mTokensLength = aTkzr.mTokenStart + aTkzr.mTokenLength - mTokensStart;
    mTokens = mString.substr(mTokensStart, mTokensLength);
}

void DwTokenString::ExtendTo(const DwTokenizer& aTkzr)
{
    assert(aTkzr.mTokenStart >= mTokensStart);
    if (aTkzr.mTokenStart < mTokensStart) return;
    mTokensLength = aTkzr.mTokenStart - mTokensStart;
    mTokens = mString.substr(mTokensStart, mTokensLength);
}
<commit_msg>5% faster kmail (or equivalently, 5% less cpu depending on how fast your chip is) by using table lookups instead of if() chains and function calls.  Also eliminate redundant calls.<commit_after>//=============================================================================
// File:       token.cpp
// Contents:   Definitions for DwTokenizer, DwRfc822Tokenizer
// Maintainer: Doug Sauder <dwsauder@fwb.gulf.net>
// WWW:        http://www.fwb.gulf.net/~dwsauder/mimepp.html
// $Revision$
// $Date$
//
// Copyright (c) 1996, 1997 Douglas W. Sauder
// All rights reserved.
//
// IN NO EVENT SHALL DOUGLAS W. SAUDER BE LIABLE TO ANY PARTY FOR DIRECT,
// INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF
// THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF DOUGLAS W. SAUDER
// HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// DOUGLAS W. SAUDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT
// NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
// BASIS, AND DOUGLAS W. SAUDER HAS NO OBLIGATION TO PROVIDE MAINTENANCE,
// SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
//
//=============================================================================

#define DW_IMPLEMENTATION

#include <mimelib/config.h>
#include <mimelib/debug.h>
#include <assert.h>
#include <ctype.h>
#include <mimelib/string.h>
#include <mimelib/token.h>

std::ostream* DwTokenizer::mDebugOut = 0;

DwTokenizer::DwTokenizer(const DwString& aStr)
  : mString(aStr)
{
    mTokenStart  = 0;
    mTokenLength = 0;
    mNextStart   = 0;
    mTkType      = eTkError;
}

DwTokenizer::DwTokenizer(const char* aCStr)
  : mString(aCStr)
{
    mTokenStart  = 0;
    mTokenLength = 0;
    mNextStart   = 0;
    mTkType      = eTkError;
}

DwTokenizer::~DwTokenizer()
{
}

void DwTokenizer::StripDelimiters()
{
    if (mTokenLength < 2) return;
    // const ref -- avoids copy on write when using operator[]
    const DwString& token = mToken;
    switch (mTkType) {
    case eTkQuotedString:
        if (token[0] == '"') {
            mToken = mToken.substr(1);
            ++mTokenStart;
            --mTokenLength;
        }
        if (mTokenLength > 0 && token[mTokenLength-1] == '"') {
            mToken = mToken.substr(0, mTokenLength-1);
            --mTokenLength;
        }
        break;
    case eTkDomainLiteral:
        if (token[0] == '[') {
            mToken = mToken.substr(1);
            ++mTokenStart;
            --mTokenLength;
        }
        if (mTokenLength > 0 && token[mTokenLength-1] == ']') {
            mToken = mToken.substr(0, mTokenLength-1);
            --mTokenLength;
        }
        break;
    case eTkComment:
        if (token[0] == '(') {
            mToken = mToken.substr(1);
            ++mTokenStart;
            --mTokenLength;
        }
        if (mTokenLength > 0 && token[mTokenLength-1] == ')') {
            mToken = mToken.substr(0, mTokenLength-1);
            --mTokenLength;
        }
        break;
    }
}

void DwTokenizer::ParseQuotedString()
{
    size_t pos = mTokenStart;
    while (1) {
        ++pos;
        if (pos >= mString.length()) {
            // Ran out of string
            mTokenLength = 0;
            mToken = "";
            mNextStart = pos;
            mTkType = eTkError;
            break;
        }
        else if (mString[pos] == '\\') {
            // Quoted character
            ++pos;
            if (pos >= mString.length()) {
                // Ran out of string
                mTokenLength = 0;
                mToken = "";
                mNextStart = pos;
                mTkType = eTkError;
                break;
            }
        }
        else if (mString[pos] == '"') {
            // End of quoted string
            ++pos;
            mTokenLength = pos - mTokenStart;
            mToken = mString.substr(mTokenStart, mTokenLength);
            mNextStart = pos;
            break;
        }
    }
}

void DwTokenizer::ParseComment()
{
    size_t pos = mTokenStart;
    int level = 1;
    while (1) {
        ++pos;
        if (pos >= mString.length()) {
            // Ran out of string
            mTokenLength = 0;
            mToken = "";
            mNextStart = pos;
            mTkType = eTkError;
            break;
        }
        else if (mString[pos] == '\\') {
            // Quoted character
            ++pos;
            if (pos >= mString.length()) {
                // Ran out of string
                mTokenLength = 0;
                mToken = "";
                mNextStart = pos;
                mTkType = eTkError;
                break;
            }
        }
        else if (mString[pos] == ')') {
            --level;
            if (level == 0) {
                // End of comment
                ++pos;
                mTokenLength = pos - mTokenStart;
                mToken = mString.substr(mTokenStart, mTokenLength);
                mNextStart = pos;
                break;
            }
        }
        else if (mString[pos] == '(') {
            ++level;
        }
    }
}

void DwTokenizer::ParseDomainLiteral()
{
    size_t pos = mTokenStart;
    while (1) {
        ++pos;
        if (pos >= mString.length()) {
            // Ran out of string
            mTokenLength = 0;
            mToken = "";
            mNextStart = pos;
            mTkType = eTkError;
            break;
        }
        else if (mString[pos] == '\\') {
            // Quoted character
            ++pos;
            if (pos >= mString.length()) {
                // Ran out of string
                mTokenLength = 0;
                mToken = "";
                mNextStart = pos;
                mTkType = eTkError;
                break;
            }
        }
        else if (mString[pos] == ']') {
            // End of domain literal
            ++pos;
            mTokenLength = pos - mTokenStart;
            mToken = mString.substr(mTokenStart, mTokenLength);
            mNextStart = pos;
            break;
        }
    }
}

void DwTokenizer::PrintToken(std::ostream* aOut)
{
    if (!aOut) return;
    const char* type = 0;
    switch (mTkType) {
    case eTkError:
        type = "error          ";
        break;
    case eTkNull:
        type = "null           ";
        break;
    case eTkSpecial:
        type = "special        ";
        break;
    case eTkAtom:
        type = "atom           ";
        break;
    case eTkComment:
        type = "comment        ";
        break;
    case eTkQuotedString:
        type = "quoted string  ";
        break;
    case eTkDomainLiteral:
        type = "domain literal ";
        break;
    case eTkTspecial:
        type = "tspecial       ";
        break;
    case eTkToken:
        type = "token          ";
        break;
    default:
        type = "unknown        ";
        break;
    }
    *aOut << type << mToken << '\n';
}

static inline bool isspecialorspaceorcntrl( char c )
{
  switch ( c ) {
     case '(':
     case ')':
     case '<':
     case '>':
     case '@':
     case ',':
     case ';':
     case ':':
     case '\\':
     case '"':
     case '.':
     case '[':
     case ']':
     // isspace()
     case ' ':
     //case '\r': included in iscntrl()
     //case '\f': included in iscntrl()
     //case '\t': included in iscntrl()
     //case '\n': included in iscntrl()
     //case '\v': included in iscntrl()
     // iscntrl()
     case 0 ... 15:
     case 17 ... 31:
        return true;
     default:
        return false;
  }
}

static inline bool isnotspaceorcntrl( char c )
{
  switch ( c ) {
     // isspace()
     case ' ':
     //case '\r': included in iscntrl()
     //case '\f': included in iscntrl()
     //case '\t': included in iscntrl()
     //case '\n': included in iscntrl()
     //case '\v': included in iscntrl()
     // iscntrl()
     case 0 ... 15:
     case 17 ... 31:
        return false;
     default:
        return true;
  }
}

DwRfc822Tokenizer::DwRfc822Tokenizer(const DwString& aStr)
  : DwTokenizer(aStr)
{
    ParseToken();
}

DwRfc822Tokenizer::DwRfc822Tokenizer(const char* aCStr)
  : DwTokenizer(aCStr)
{
    ParseToken();
}

DwRfc822Tokenizer::~DwRfc822Tokenizer()
{
}

int DwRfc822Tokenizer::Restart()
{
    mNextStart = 0;
    ParseToken();
    return mTkType;
}

int DwRfc822Tokenizer::operator ++ ()
{
    ParseToken();
    return mTkType;
}

void DwRfc822Tokenizer::ParseToken()
{
    // Assume the field body has already been extracted.  That is, we don't
    // have to watch for the end of the field body or folding.  We just
    // treat any CRs or LFs as white space.
    mTokenStart = mNextStart;
    mTokenLength = 0;
    mTkType = eTkNull;
    // Skip leading space.  Also, since control chars are not permitted
    // in atoms, skip these, too.
    while (1) {
        if (mTokenStart >= mString.length()) {
            return;
        }
        if (isnotspaceorcntrl(mString[mTokenStart]))
            break;
        ++mTokenStart;
    }
    char ch = mString[mTokenStart];
    switch (ch) {
        // Quoted string
        case '"':
            mTkType = eTkQuotedString;
            ParseQuotedString();
        break;
        // Comment
        case '(':
            mTkType = eTkComment;
            ParseComment();
        break;
        // Domain literal
        case '[':
            mTkType = eTkDomainLiteral;
            ParseDomainLiteral();
        break;
        // Special
        case ')':
        case '<':
        case '>':
        case '@':
        case ',':
        case ';':
        case ':':
        case '\\':
        case '.':
        case ']':
            mTkType = eTkSpecial;
            mTokenLength = 1;
            mToken = mString.substr(mTokenStart, 1);
            mNextStart = mTokenStart + 1;
        break;
        default:
            mTkType = eTkAtom;
            ParseAtom();
        break;
    }
    if (mDebugOut) PrintToken(mDebugOut);
}

void DwRfc822Tokenizer::ParseAtom()
{
    size_t pos = mTokenStart;
    while (1) {
        ++pos;
        char ch = (pos < mString.length()) ? mString[pos] : (char) 0;
        if (pos >= mString.length()
            || isspecialorspaceorcntrl(ch)) {

            mTokenLength = pos - mTokenStart;
            mToken = mString.substr(mTokenStart, mTokenLength);
            mNextStart = pos;
            break;
        }
    }
}

static inline bool istspecialorspaceorcntrl( char c )
{
  switch ( c ) {
     case '(':
     case ')':
     case '<':
     case '>':
     case '@':
     case ',':
     case ';':
     case ':':
     case '\\':
     case '"':
     case '/':
     case '[':
     case ']':
     case '?':
     case '=':
     // isspace()
     case ' ':
     //case '\r': included in iscntrl()
     //case '\f': included in iscntrl()
     //case '\t': included in iscntrl()
     //case '\n': included in iscntrl()
     //case '\v': included in iscntrl()
     // iscntrl()
     case 0 ... 15:
     case 17 ... 31:
        return true;
     default:
        return false;
  }
 }

DwRfc1521Tokenizer::DwRfc1521Tokenizer(const DwString& aStr)
  : DwTokenizer(aStr)
{
    ParseToken();
}

DwRfc1521Tokenizer::DwRfc1521Tokenizer(const char* aCStr)
  : DwTokenizer(aCStr)
{
    ParseToken();
}

DwRfc1521Tokenizer::~DwRfc1521Tokenizer()
{
}

int DwRfc1521Tokenizer::Restart()
{
    mNextStart = 0;
    ParseToken();
    return mTkType;
}

int DwRfc1521Tokenizer::operator ++ ()
{
    ParseToken();
    return mTkType;
}

void DwRfc1521Tokenizer::ParseToken()
{
    // Assume the field body has already been extracted.  That is, we don't
    // have to watch for the end of the field body or folding.  We just
    // treat any CRs or LFs as white space.
    mTokenStart = mNextStart;
    mTokenLength = 0;
    mTkType = eTkNull;
    // Skip leading space.  Also, since control chars are not permitted
    // in atoms, skip these, too.
    while (1) {
        if (mTokenStart >= mString.length()) {
            return;
        }
        if (isnotspaceorcntrl(mString[mTokenStart]))
            break;
        ++mTokenStart;
    }
    char ch = mString[mTokenStart];
    switch (ch) {
        // Quoted string
        case '"':
            mTkType = eTkQuotedString;
            ParseQuotedString();
        break;
        // Comment
        case '(':
            mTkType = eTkComment;
            ParseComment();
        break;
        // Domain literal
        case '[':
            mTkType = eTkDomainLiteral;
            ParseDomainLiteral();
        break;
        // Special
        case ')':
        case '<':
        case '>':
        case '@':
        case ',':
        case ';':
        case ':':
        case '\\':
        case '/':
        case ']':
        case '?':
        case '=':
            mTkType = eTkTspecial;
            mTokenLength = 1;
            mToken = mString.substr(mTokenStart, 1);
            mNextStart = mTokenStart + 1;
        break;
        default:
            mTkType = eTkToken;
            ParseAtom();
        break;
    }
    if (mDebugOut) PrintToken(mDebugOut);
}

void DwRfc1521Tokenizer::ParseAtom()
{
    size_t pos = mTokenStart;
    while (1) {
        ++pos;
        char ch = (pos < mString.length()) ? mString[pos] : (char) 0;
        if (pos >= mString.length()
            || istspecialorspaceorcntrl(ch)) {

            mTokenLength = pos - mTokenStart;
            mToken = mString.substr(mTokenStart, mTokenLength);
            mNextStart = pos;
            break;
        }
    }
}

DwTokenString::DwTokenString(const DwString& aStr)
  : mString(aStr)
{
    mTokensStart  = 0;
    mTokensLength = 0;
}

DwTokenString::~DwTokenString()
{
}

void DwTokenString::SetFirst(const DwTokenizer& aTkzr)
{
    switch (aTkzr.Type()) {
    case eTkError:
    case eTkNull:
        mTokensStart  = aTkzr.mTokenStart;
        mTokensLength = 0;
        break;
    case eTkComment:
    case eTkDomainLiteral:
    case eTkQuotedString:
    case eTkSpecial:
    case eTkAtom:
    case eTkTspecial:
    case eTkToken:
        mTokensStart  = aTkzr.mTokenStart;
        mTokensLength = aTkzr.mTokenLength;
        break;
    }
    mTokens = mString.substr(mTokensStart, mTokensLength);
}

void DwTokenString::SetLast(const DwTokenizer& aTkzr)
{
    assert(aTkzr.mTokenStart >= mTokensStart);
    if (aTkzr.mTokenStart < mTokensStart) return;
    mTokensLength = aTkzr.mTokenStart + aTkzr.mTokenLength - mTokensStart;
    mTokens = mString.substr(mTokensStart, mTokensLength);
}

void DwTokenString::ExtendTo(const DwTokenizer& aTkzr)
{
    assert(aTkzr.mTokenStart >= mTokensStart);
    if (aTkzr.mTokenStart < mTokensStart) return;
    mTokensLength = aTkzr.mTokenStart - mTokensStart;
    mTokens = mString.substr(mTokensStart, mTokensLength);
}
<|endoftext|>


<commit_before>#pragma once

#include <stdexcept>

#include <boost/lexical_cast.hpp>

#include <rapidjson/document.h>

#include "blackhole/config/node.hpp"
#include "blackhole/config/option.hpp"

namespace blackhole {
namespace detail {
namespace config {

using blackhole::config::make_option;
using blackhole::config::node_t;

typedef blackhole::config::option<node_t> option_t;

class type_mismatch : public std::logic_error {
    struct {
        std::string cursor;
        std::string expected;
        std::string actual;
    } data;

public:
    type_mismatch(std::string cursor, std::string expected, std::string actual) :
        std::logic_error("type mismatch at \"" + cursor + "\": " +
            "expected \"" + expected + "\", actual \"" + actual + "\"")
    {
        data.cursor = std::move(cursor);
        data.expected = std::move(expected);
        data.actual = std::move(actual);
    }

    ~type_mismatch() throw() {}

    auto expected() const -> std::string {
        return data.expected;
    }

    auto actual() const -> std::string {
        return data.actual;
    }

    auto cursor() const -> std::string {
        return data.cursor;
    }
};

// TODO: Separate hpp/cpp.
class json_t : public node_t {
    const rapidjson::Value& value;
    std::string cursor;

public:
    explicit json_t(const rapidjson::Value& value) :
        value(value)
    {}

    json_t(const rapidjson::Value& value, std::string cursor) :
        value(value),
        cursor(std::move(cursor))
    {}

    auto to_bool() const -> bool {
        if (value.IsBool()) {
            return value.GetBool();
        }

        type_mismatch("bool");
    }

    auto to_sint64() const -> std::int64_t {
        if (value.IsInt64()) {
            return value.GetInt64();
        }

        type_mismatch("int64");
    }

    auto to_uint64() const -> std::uint64_t {
        if (value.IsUint64()) {
            return value.GetUint64();
        }

        type_mismatch("uint64");
    }

    auto to_double() const -> double {
        if (value.IsDouble()) {
            return value.GetDouble();
        }

        type_mismatch("double");
    }

    auto to_string() const -> std::string {
        if (value.IsString()) {
            return value.GetString();
        }

        type_mismatch("string");
    }

    auto each(const each_function& fn) -> void {
        if (!value.IsArray()) {
            type_mismatch("array");
        }

        for (auto it = value.Begin(); it != value.End(); ++it) {
            fn(json_t(*it, advance(static_cast<std::size_t>(std::distance(value.Begin(), it)))));
        }
    }

    auto each_map(const member_function& fn) -> void {
        if (!value.IsObject()) {
            type_mismatch("object");
        }

        for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) {
            fn(it->name.GetString(), json_t(it->value, advance(it->name.GetString())));
        }
    }

    auto operator[](const std::size_t& idx) const -> option_t {
        if (value.IsArray() && idx < value.Size()) {
            return make_option<json_t>(value[static_cast<rapidjson::SizeType>(idx)], advance(idx));
        }

        return {};
    }

    auto operator[](const std::string& key) const -> option_t {
        if (value.IsObject() && value.HasMember(key.c_str())) {
            return make_option<json_t>(value[key.c_str()], advance(key));
        }

        return {};
    }

private:
    auto advance(const std::size_t& idx) const -> std::string {
        return cursor + "/" + boost::lexical_cast<std::string>(idx);
    }

    auto advance(const std::string& key) const -> std::string {
        return cursor + "/" + key;
    }

    __attribute((noreturn)) auto type_mismatch(const std::string& expected) const -> void {
        throw config::type_mismatch(cursor.empty() ? "/" : cursor, expected, type());
    }

    auto type() const -> std::string {
        switch (value.GetType()) {
        case rapidjson::kNullType:
            return "null";
        case rapidjson::kTrueType:
        case rapidjson::kFalseType:
            return "bool";
        case rapidjson::kNumberType:
            return "number";
        case rapidjson::kStringType:
            return "string";
        case rapidjson::kArrayType:
            return "array";
        case rapidjson::kObjectType:
            return "object";
        }
    }
};

}  // namespace config
}  // namespace detail
}  // namespace blackhole
<commit_msg>fix(config/json): upgrade specifiers<commit_after>#pragma once

#include <stdexcept>

#include <boost/lexical_cast.hpp>

#include <rapidjson/document.h>

#include "blackhole/config/node.hpp"
#include "blackhole/config/option.hpp"

namespace blackhole {
namespace detail {
namespace config {

using blackhole::config::make_option;
using blackhole::config::node_t;

typedef blackhole::config::option<node_t> option_t;

class type_mismatch : public std::logic_error {
    struct {
        std::string cursor;
        std::string expected;
        std::string actual;
    } data;

public:
    type_mismatch(std::string cursor, std::string expected, std::string actual) :
        std::logic_error("type mismatch at \"" + cursor + "\": " +
            "expected \"" + expected + "\", actual \"" + actual + "\"")
    {
        data.cursor = std::move(cursor);
        data.expected = std::move(expected);
        data.actual = std::move(actual);
    }

    type_mismatch(const type_mismatch& other) = default;
    type_mismatch(type_mismatch&& other) = default;

    ~type_mismatch() noexcept {}

    auto expected() const -> std::string {
        return data.expected;
    }

    auto actual() const -> std::string {
        return data.actual;
    }

    auto cursor() const -> std::string {
        return data.cursor;
    }
};

class json_t : public node_t {
    const rapidjson::Value& value;
    std::string cursor;

public:
    explicit json_t(const rapidjson::Value& value) :
        value(value)
    {}

    json_t(const rapidjson::Value& value, std::string cursor) :
        value(value),
        cursor(std::move(cursor))
    {}

    auto to_bool() const -> bool {
        if (value.IsBool()) {
            return value.GetBool();
        }

        type_mismatch("bool");
    }

    auto to_sint64() const -> std::int64_t {
        if (value.IsInt64()) {
            return value.GetInt64();
        }

        type_mismatch("int64");
    }

    auto to_uint64() const -> std::uint64_t {
        if (value.IsUint64()) {
            return value.GetUint64();
        }

        type_mismatch("uint64");
    }

    auto to_double() const -> double {
        if (value.IsDouble()) {
            return value.GetDouble();
        }

        type_mismatch("double");
    }

    auto to_string() const -> std::string {
        if (value.IsString()) {
            return value.GetString();
        }

        type_mismatch("string");
    }

    auto each(const each_function& fn) -> void {
        if (!value.IsArray()) {
            type_mismatch("array");
        }

        for (auto it = value.Begin(); it != value.End(); ++it) {
            fn(json_t(*it, advance(static_cast<std::size_t>(std::distance(value.Begin(), it)))));
        }
    }

    auto each_map(const member_function& fn) -> void {
        if (!value.IsObject()) {
            type_mismatch("object");
        }

        for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) {
            fn(it->name.GetString(), json_t(it->value, advance(it->name.GetString())));
        }
    }

    auto operator[](const std::size_t& idx) const -> option_t {
        if (value.IsArray() && idx < value.Size()) {
            return make_option<json_t>(value[static_cast<rapidjson::SizeType>(idx)], advance(idx));
        }

        return {};
    }

    auto operator[](const std::string& key) const -> option_t {
        if (value.IsObject() && value.HasMember(key.c_str())) {
            return make_option<json_t>(value[key.c_str()], advance(key));
        }

        return {};
    }

private:
    auto advance(const std::size_t& idx) const -> std::string {
        return cursor + "/" + boost::lexical_cast<std::string>(idx);
    }

    auto advance(const std::string& key) const -> std::string {
        return cursor + "/" + key;
    }

    __attribute((noreturn)) auto type_mismatch(const std::string& expected) const -> void {
        throw config::type_mismatch(cursor.empty() ? "/" : cursor, expected, type());
    }

    auto type() const -> std::string {
        switch (value.GetType()) {
        case rapidjson::kNullType:
            return "null";
        case rapidjson::kTrueType:
        case rapidjson::kFalseType:
            return "bool";
        case rapidjson::kNumberType:
            return "number";
        case rapidjson::kStringType:
            return "string";
        case rapidjson::kArrayType:
            return "array";
        case rapidjson::kObjectType:
            return "object";
        }
    }
};

}  // namespace config
}  // namespace detail
}  // namespace blackhole
<|endoftext|>