Added documentation for value_t

This commit is contained in:
John Wiegley 2007-06-16 01:50:14 +00:00
parent 6080b8ea00
commit b73b3e0fd8
2 changed files with 324 additions and 243 deletions

View file

@ -118,6 +118,33 @@ void value_t::shutdown()
false_value = intrusive_ptr<storage_t>();
}
void value_t::_dup()
{
assert(storage);
if (storage->refc > 1) {
storage = new storage_t(*storage.get());
// If the data referenced by storage is an allocated pointer, we
// need to create a new object in order to achieve duplication.
switch (storage->type) {
case BALANCE:
*(balance_t **) storage->data =
new balance_t(**(balance_t **) storage->data);
break;
case BALANCE_PAIR:
*(balance_pair_t **) storage->data =
new balance_pair_t(**(balance_pair_t **) storage->data);
break;
case SEQUENCE:
*(sequence_t **) storage->data =
new sequence_t(**(sequence_t **) storage->data);
break;
default:
break; // everything else has been duplicated
}
}
}
value_t::operator bool() const
{
switch (type()) {
@ -1482,115 +1509,4 @@ void value_t::print(std::ostream& out, const int first_width,
}
}
std::ostream& operator<<(std::ostream& out, const value_t& val)
{
switch (val.type()) {
case value_t::VOID:
out << "VOID";
break;
case value_t::BOOLEAN:
out << (val.as_boolean() ? "true" : "false");
break;
case value_t::INTEGER:
out << val.as_long();
break;
case value_t::DATETIME:
out << val.as_datetime();
break;
case value_t::AMOUNT:
out << val.as_amount();
break;
case value_t::BALANCE:
out << val.as_balance();
break;
case value_t::BALANCE_PAIR:
out << val.as_balance_pair();
break;
case value_t::STRING:
out << val.as_string();
break;
case value_t::XML_NODE:
if (val.as_xml_node()->has_flags(XML_NODE_IS_PARENT))
out << '<' << val.as_xml_node()->name() << '>';
else
out << val.as_xml_node()->to_value();
break;
case value_t::POINTER:
throw_(value_error, "Cannot output a pointer value");
case value_t::SEQUENCE: {
out << '(';
bool first = true;
for (value_t::sequence_t::const_iterator i = val.as_sequence().begin();
i != val.as_sequence().end();
i++) {
if (first)
first = false;
else
out << ", ";
out << *i;
}
out << ')';
break;
}
default:
assert(false);
break;
}
return out;
}
#if 0
value_context::value_context(const value_t& _bal,
const string& _desc) throw()
: error_context(_desc), bal(new value_t(_bal)) {}
value_context::~value_context() throw()
{
checked_delete(bal);
}
void value_context::describe(std::ostream& out) const throw()
{
if (! desc.empty())
out << desc << std::endl;
balance_t * ptr = NULL;
out << std::right;
out.width(20);
switch (bal->type()) {
case value_t::BOOLEAN:
out << (*((bool *) bal->data) ? "true" : "false");
break;
case value_t::INTEGER:
out << *((long *) bal->data);
break;
case value_t::DATETIME:
out << *((moment_t *) bal->data);
break;
case value_t::AMOUNT:
out << *((amount_t *) bal->data);
break;
case value_t::BALANCE:
ptr = (balance_t *) bal->data;
// fall through...
case value_t::BALANCE_PAIR:
if (! ptr)
ptr = &((balance_pair_t *) bal->data)->quantity;
ptr->print(out, 20);
break;
default:
assert(false);
break;
}
out << std::endl;
}
#endif
} // namespace ledger

View file

@ -29,6 +29,16 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file value.h
* @author John Wiegley
* @date Thu Jun 14 21:54:00 2007
*
* @brief Abstract dynamic type representing various numeric types.
*
* A value_t object can be one of many types, and can also change its
* type dynamically based on how it is used.
*/
#ifndef _VALUE_H
#define _VALUE_H
@ -40,61 +50,106 @@ namespace xml {
class node_t;
}
// The following type is a polymorphous value type used solely for
// performance reasons. The alternative is to compute value
// expressions (valexpr.cc) in terms of the largest data type,
// balance_t. This was found to be prohibitively expensive, especially
// when large logic chains were involved, since many temporary
// allocations would occur for every operator. With value_t, and the
// fact that logic chains only need boolean values to continue, no
// memory allocations need to take place at all.
/**
* @class value_t
*
* @brief Dynamic type representing various numeric types.
*
* The following type is a polymorphous value type used solely for
* performance reasons. The alternative is to compute value
* expressions (valexpr.cc) in terms of the largest data type,
* balance_t. This was found to be prohibitively expensive, especially
* when large logic chains were involved, since many temporary
* allocations would occur for every operator. With value_t, and the
* fact that logic chains only need boolean values to continue, no
* memory allocations need to take place at all.
*/
class value_t
: public ordered_field_operators<value_t,
ordered_field_operators<value_t, balance_pair_t,
ordered_field_operators<value_t, balance_t,
equality_comparable<value_t, balance_pair_t,
equality_comparable<value_t, balance_t,
additive<value_t, balance_pair_t,
additive<value_t, balance_t,
multiplicative<value_t, balance_pair_t,
multiplicative<value_t, balance_t,
ordered_field_operators<value_t, amount_t,
ordered_field_operators<value_t, double,
ordered_field_operators<value_t, unsigned long,
ordered_field_operators<value_t, long> > > > > > >
ordered_field_operators<value_t, long> > > > > > > > > > >
{
public:
/**
* The sequence_t member type abstracts the type used to represent a
* resizable "array" of value_t objects.
*/
typedef std::vector<value_t> sequence_t;
typedef sequence_t::iterator iterator;
typedef sequence_t::const_iterator const_iterator;
typedef sequence_t::difference_type difference_type;
/**
* type_t gives the type of the data contained or referenced by a
* value_t object. Use the type() method to get a value of type
* type_t.
*/
enum type_t {
VOID,
BOOLEAN,
DATETIME,
INTEGER,
AMOUNT,
BALANCE,
BALANCE_PAIR,
STRING,
SEQUENCE,
XML_NODE,
POINTER
VOID, // a null value (i.e., uninitialized)
BOOLEAN, // a boolean
DATETIME, // a date and time (Boost posix_time)
INTEGER, // a signed integer value
AMOUNT, // a ledger::amount_t
BALANCE, // a ledger::balance_t
BALANCE_PAIR, // a ledger::balance_pair_t
STRING, // a string object
SEQUENCE, // a vector of value_t objects
XML_NODE, // a pointer to an ledger::xml::node_t
POINTER // an opaque pointer of any type
};
private:
class storage_t
{
friend class value_t;
/**
* The `data' member holds the actual bytes relating to whatever
* has been stuffed into this storage object. There is a set of
* asserts in value.cc to guarantee that the sizeof expression
* used here is indeed at least as big as the largest object that
* will ever be copied into `data'.
*
* The `type' member holds the value_t::type_t value representing
* the type of the object stored.
*/
char data[sizeof(amount_t)];
type_t type;
/**
* `refc' holds the current reference count for each storage_t
* object.
*/
mutable int refc;
/**
* Constructor. Since all storage object are assigned to after
* construction, the only constructors allowed are explicit, and
* copy (see below). The default starting type is VOID, which
* should rarely ever be seen in practice, since the first thing
* that value_t typically does is to assign a valid value.
*/
explicit storage_t() : type(VOID), refc(0) {
TRACE_CTOR(value_t::storage_t, "");
}
explicit storage_t(const storage_t& rhs)
: type(rhs.type), refc(0) {
TRACE_CTOR(value_t::storage_t, "");
std::memcpy(data, rhs.data, sizeof(data));
}
public: // so `checked_delete' can access it
/**
* Destructor. Must only be called when the reference count has
* reached zero. The `destroy' method is used to do the actual
* cleanup of the data, since it's quite possible for `destroy' to
* be called while the object is still active -- to clear the
* stored data for subsequent reuse of the storage_t object.
*/
~storage_t() {
TRACE_DTOR(value_t::storage_t);
DEBUG("value.storage.refcount", "Destroying " << this);
@ -102,15 +157,29 @@ private:
destroy();
}
void destroy();
private:
/**
* Assignment and copy operators. These are called when making a
* new copy of a storage object in order to modify the copy.
*/
explicit storage_t(const storage_t& rhs)
: type(rhs.type), refc(0) {
TRACE_CTOR(value_t::storage_t, "");
std::memcpy(data, rhs.data, sizeof(data));
}
storage_t& operator=(const storage_t& rhs) {
type = rhs.type;
std::memcpy(data, rhs.data, sizeof(data));
return *this;
}
mutable int refc;
/**
* Reference counting methods. The intrusive_ptr_* methods are
* used by boost::intrusive_ptr to manage the calls to acquire and
* release.
*/
void acquire() const {
DEBUG("value.storage.refcount",
"Acquiring " << this << ", refc now " << refc + 1);
@ -125,10 +194,6 @@ private:
checked_delete(this);
}
void destroy();
friend class value_t;
friend inline void intrusive_ptr_add_ref(value_t::storage_t * storage) {
storage->acquire();
}
@ -137,26 +202,65 @@ private:
}
};
/**
* The actual data for each value_t is kept in the `storage' member.
* Data is modified using a copy-on-write policy.
*/
intrusive_ptr<storage_t> storage;
/**
* _dup() makes a private copy of the current value so that it can
* subsequently be modified.
*
* _clear() removes our pointer to the current value and initializes
* a new value for things to be stored in.
*
* _reset() makes the current object appear as if it had been
* default initialized.
*/
void _dup();
void _clear() {
if (! storage || storage->refc > 1)
storage = new storage_t;
else
storage->destroy();
}
void _reset() {
if (storage)
storage = intrusive_ptr<storage_t>();
}
/**
* Because boolean "true" and "false" are so common, a pair of
* static references are kept to prevent the creation of throwaway
* storage_t objects just to represent these two common values.
*/
static intrusive_ptr<storage_t> true_value;
static intrusive_ptr<storage_t> false_value;
// jww (2007-05-03): Make this private, and then make
// ledger::initialize into a member function of session_t.
public:
// jww (2007-05-03): Make these private, and make ledger::initialize
// a member function of session_t.
static void initialize();
static void shutdown();
public:
/**
* Constructors. value_t objects may be constructed from almost any
* value type that they can contain, including variations on those
* types (such as long, unsigned long, etc). The ordering of the
* methods here reflects the ordering of the constants in type_t
* above.
*
* One constructor of special note is that taking a string or
* character pointer as an argument. Because value_t("$100") is
* interpreted as a commoditized amount, the form value_t("$100",
* true) is required to represent the literal string "$100", and not
* the amount "one hundred dollars".
*/
value_t() {
TRACE_CTOR(value_t, "");
}
value_t(const value_t& val) {
TRACE_CTOR(value_t, "copy");
*this = val;
}
value_t(const bool val) {
TRACE_CTOR(value_t, "const bool");
set_boolean(val);
@ -216,10 +320,25 @@ public:
TRACE_CTOR(value_t, "T *");
set_pointer(item);
}
/**
* Destructor. This does not do anything, because the intrusive_ptr
* that refers to our storage object will decrease its reference
* count itself upon destruction.
*/
~value_t() {
TRACE_DTOR(value_t);
}
/**
* Assignment and copy operators. Values are cheaply copied by
* simply creating another reference to the other value's storage
* object. A true copy is only ever made prior to modification.
*/
value_t(const value_t& val) {
TRACE_CTOR(value_t, "copy");
*this = val;
}
value_t& operator=(const value_t& val) {
if (! (this == &val || storage == val.storage))
storage = val.storage;
@ -227,50 +346,63 @@ public:
}
/**
* _dup() makes a private copy of the current value so that it can
* subsequently be modified.
*
* _clear() removes our pointer to the current value and initializes
* a new value for things to be stored in.
* Comparison operators.
*/
void _dup() {
assert(storage);
if (storage->refc > 1) {
storage = new storage_t(*storage.get());
bool operator==(const value_t& val) const;
bool operator<(const value_t& val) const;
#if 0
bool operator>(const value_t& val) const;
#endif
// If the data referenced by storage is an allocated pointer, we
// need to create a new object in order to achieve duplication.
switch (storage->type) {
case BALANCE:
*(balance_t **) storage->data =
new balance_t(**(balance_t **) storage->data);
break;
case BALANCE_PAIR:
*(balance_pair_t **) storage->data =
new balance_pair_t(**(balance_pair_t **) storage->data);
break;
case SEQUENCE:
*(sequence_t **) storage->data =
new sequence_t(**(sequence_t **) storage->data);
break;
default:
break; // everything else has been duplicated
}
}
/**
* Binary arithmetic operators.
*
* add(amount_t, optional<amount_t>) allows for the possibility of
* adding both an amount and its cost in a single operation.
* Otherwise, there is no way to separately represent the "cost"
* part of an amount addition statement.
*/
value_t& operator+=(const value_t& val);
value_t& operator-=(const value_t& val);
value_t& operator*=(const value_t& val);
value_t& operator/=(const value_t& val);
value_t& add(const amount_t& amount,
const optional<amount_t>& cost = none);
/**
* Unary arithmetic operators.
*/
value_t negate() const {
value_t temp = *this;
temp.in_place_negate();
return temp;
}
void _clear() {
if (! storage || storage->refc > 1)
storage = new storage_t;
else
storage->destroy();
}
void _reset() {
if (storage)
storage = intrusive_ptr<storage_t>();
void in_place_negate();
value_t operator-() const {
return negate();
}
value_t abs() const;
value_t round() const;
value_t unround() const;
value_t reduce() const {
value_t temp(*this);
temp.in_place_reduce();
return temp;
}
void in_place_reduce();
value_t value(const optional<moment_t>& moment = none) const;
/**
* Truth tests.
*/
operator bool() const;
bool is_realzero() const;
bool is_null() const {
if (! storage) {
return true;
@ -279,16 +411,17 @@ public:
return false;
}
}
type_t type() const {
type_t result = storage ? storage->type : VOID;
assert(result >= VOID && result <= POINTER);
return result;
}
private:
bool is_type(type_t _type) const {
return type() == _type;
}
private:
void set_type(type_t new_type) {
assert(new_type >= VOID && new_type <= POINTER);
if (new_type == VOID) {
@ -302,6 +435,36 @@ private:
}
public:
/**
* Data manipulation methods. A value object may be truth tested
* for the existence of every type it can contain:
*
* is_boolean()
* is_long()
* is_datetime()
* is_amount()
* is_balance()
* is_balance_pair()
* is_string()
* is_sequence()
* is_xml_node()
* is_pointer()
*
* There are corresponding as_*() methods that represent a value as
* a reference to its underlying type. For example, as_integer()
* returns a reference to a "const long".
*
* There are also as_*_lval() methods, which represent the
* underlying data as a reference to a non-const type. The
* difference here is that an _lval() call causes the underlying
* data to be fully copied before the resulting reference is
* returned.
*
* Lastly, there are corresponding set_*(data) methods for directly
* assigning data of a particular type, rather than using the
* regular assignment operator (whose implementation simply calls
* the various set_ methods).
*/
bool is_boolean() const {
return is_type(BOOLEAN);
}
@ -499,6 +662,11 @@ public:
new((boost::any *) storage->data) boost::any(val);
}
/**
* Data conversion methods. These methods convert a value object to
* its underlying type, where possible. If not possible, an
* exception is thrown.
*/
bool to_boolean() const;
long to_long() const;
moment_t to_datetime() const;
@ -508,6 +676,29 @@ public:
string to_string() const;
sequence_t to_sequence() const;
/**
* Dynamic typing conversion methods.
*
* `cast(type_t)' returns a new value whose type has been cast to
* the given type, but whose value is based on the original value.
* For example, the uncommoditized AMOUNT "100.00" could be cast to
* an INTEGER value. If a cast would lose information or is not
* meaningful, an exception is thrown.
*
* `simplify()' is an automatic cast to the simplest type that can
* still represent the original value.
*
* There are also "in-place" versions of these two methods:
* in_place_cast
* in_place_simplify
*/
value_t cast(type_t cast_type) const {
value_t temp(*this);
temp.in_place_cast(cast_type);
return temp;
}
void in_place_cast(type_t cast_type);
value_t simplify() const {
value_t temp = *this;
temp.in_place_simplify();
@ -515,6 +706,20 @@ public:
}
void in_place_simplify();
/**
* Annotated commodity methods.
*/
value_t annotated_price() const;
value_t annotated_date() const;
value_t annotated_tag() const;
value_t strip_annotations(const bool keep_price = amount_t::keep_price,
const bool keep_date = amount_t::keep_date,
const bool keep_tag = amount_t::keep_tag) const;
/**
* Collection-style access methods
*/
value_t& operator[](const int index) {
assert(! is_null());
if (is_sequence())
@ -583,17 +788,9 @@ public:
return 1;
}
value_t& operator+=(const value_t& val);
value_t& operator-=(const value_t& val);
value_t& operator*=(const value_t& val);
value_t& operator/=(const value_t& val);
bool operator==(const value_t& val) const;
bool operator<(const value_t& val) const;
#if 0
bool operator>(const value_t& val) const;
#endif
/**
* Informational methods.
*/
string label(optional<type_t> the_type = none) const {
switch (the_type ? *the_type : type()) {
case VOID:
@ -626,52 +823,17 @@ public:
return "<invalid>";
}
value_t operator-() const {
return negate();
}
value_t negate() const {
value_t temp = *this;
temp.in_place_negate();
return temp;
}
void in_place_negate();
bool is_realzero() const;
value_t abs() const;
void in_place_cast(type_t cast_type);
value_t cost() const;
value_t annotated_price() const;
value_t annotated_date() const;
value_t annotated_tag() const;
value_t cast(type_t cast_type) const {
value_t temp(*this);
temp.in_place_cast(cast_type);
return temp;
}
value_t strip_annotations(const bool keep_price = amount_t::keep_price,
const bool keep_date = amount_t::keep_date,
const bool keep_tag = amount_t::keep_tag) const;
value_t& add(const amount_t& amount,
const optional<amount_t>& cost = none);
value_t value(const optional<moment_t>& moment = none) const;
void in_place_reduce();
value_t reduce() const {
value_t temp(*this);
temp.in_place_reduce();
return temp;
}
value_t round() const;
value_t unround() const;
/**
* Printing methods.
*/
void print(std::ostream& out, const int first_width,
const int latter_width = -1) const;
friend std::ostream& operator<<(std::ostream& out, const value_t& val);
/**
* Debugging methods.
*/
};
#define NULL_VALUE (value_t())
@ -680,7 +842,10 @@ inline value_t string_value(const string& str) {
return value_t(str, true);
}
std::ostream& operator<<(std::ostream& out, const value_t& val);
inline std::ostream& operator<<(std::ostream& out, const value_t& val) {
val.print(out, 12);
return out;
}
DECLARE_EXCEPTION(value_error);